紡Book の勉強ノートに戻る
開発記録2026.07.16

「約11年12ヶ月」と表示された — 人間向けの時間表示は繰り上げ漏れでバグる

JavaScript日付処理紡Book

テンプレートの全景スクショをレビューしていて見つけた表示バグの記録です。修正済み。

何が起きていたか

「柱のきず」という作品テンプレート(写真の並べ方の型)には、写真と写真の間に経過時間のラベルが出ます。背比べの柱に刻みを増やしていくイメージで、「それから 約11年12ヶ月」のように表示されます——って、11年12ヶ月は12年です。

2014年の写真と2026年の写真の間——ちょうど12年になる少し手前で、この表示が出ていた。

原因: 月の計算が12に到達できてしまう

そもそもなぜ 365 や 30 で割っているかというと、ここで出したいのが正確な年月差ではなく「約◯年◯ヶ月」という体感の粒度だからです。暦どおりの月差を求めるライブラリもあるが、柱に刻む一言に月末や閏年の厳密さは要りません。

経過ラベルを組み立てる処理は、ざっくりこうなっていた。

ts
const years = Math.floor(days / 365);            // 365で割って切り捨て = 何年ぶんか
const remMonths = Math.round((days % 365) / 30); // ← ここ。年で割った「余りの日数」を30で割って四捨五入 = 何ヶ月ぶんか
写真の日付は作品の写真。経過日数と年・月は描画時のメモリ。チップは保存しない。作品の写真隣り合う2枚の日付作品に載る経過ラベルはここに無い描画時のメモリ経過日数 → 年と余り月年は切り捨て、月は四捨五入保存しない画面のチップそれから 約◯年◯ヶ月描画のたびに組み立てる保存しない

days % 365 は最大364です。余りが345日を超えたあたりから月が12になってしまいます(345 ÷ 30 = 11.5 → 四捨五入で12)。

余りの日数は描画時のメモリ。保存しない。4,345日経過日数年 11切り捨て余り 330日→ 11ヶ月四捨五入約11年11ヶ月正常4,378日経過日数年 11切り捨て余り 363日→ 12ヶ月四捨五入約11年12ヶ月バグ。繰り上げ漏れ

この割り算による近似は、単位ごとの上限を自分では持っていません。「月は11までしかない」という人間側の約束は、後から手で足さないとどこにも書かれていません。

直し方

丸めた結果が12に達したら年へ繰り上げる、という条件分岐(if)を足しただけ。

ts
// remMonths が12に達したら年を+1して月を0に戻す=「約12年」と言い切る
if (remMonths >= 12) return `それから 約${years + 1}年`;

約12年0ヶ月 ではなく月ごと落として 約12年 にしたのは、もともと「約」の表示だから。繰り上がった時点で端数は誤差に沈んでいるので、0を表示するより言い切ったほうが読み手に嘘がありません。

再発防止に、境界値(remMonths が12になる日数)のテストケースも追加した。使ったのは 4,378日 = 2010.01.01 → 2021.12.27 の実測値。修正・テストとも AI に書いてもらい、自分は境界の考え方が合っているかを確認した。

学んだこと

  • 人間向けの「読みやすい時間表示」で丸めを書くときは、繰り上がりの境界を先に潰しておきます。今回踏んだのは「11ヶ月→1年」だが、「59秒→1分」も同じ形で踏めそうだと後から思った
  • このバグは単体テストでは見つからず、本物の写真データを流し込んで描画させた画面のスクショから見つかった。テストは自分が想定した範囲しか見てくれないので、想定そのものが抜けていた今回は、画面を目で見るまで気づけなかった。今回足した境界値テストも発見には一切寄与していなくて、二度と踏まないための後追いの杭でしかない

この記事は2026年7月時点の自分の理解で書いています。仕様や認識が変わってる可能性があります。