ANTIGRAVITY LABEN
記事一覧/アプリ開発
アプリ開発/2026-06-25上級

日付が変わった瞬間に、昨日の壁紙がもう一度出た — 時刻処理をエージェント任せにしないための日付境界設計

毎日入れ替わる壁紙が、日付の変わり目に昨日と同じものを出したり、海外移動中の利用者だけ二回切り替わったりしていた問題を、端末時計・タイムゾーン・夏時間の三つに分けて設計し直した記録です。エージェントが書いた素朴な時刻コードのどこが本番で崩れるか、安定した日付キーの作り方、巻き戻し時計への防御、注入可能な時計でのテストまでを個人開発の視点でまとめました。

Android19タイムゾーン日付処理設計6Jetpack Compose4

プレミアム記事

毎日ひとつ「今日の壁紙」を出す機能を直していたとき、利用者から「日付が変わったのに昨日と同じ壁紙のままです」という連絡をいただきました。別の方からは逆に「夜のうちに二回入れ替わった」という報告です。同じ機能で、まったく逆の症状が同時に出ていました。

手元の Pixel で何度試しても再現しません。再現したのは、端末の日付を手で進めたときと、タイムゾーンを海外に変えたときでした。個人開発で同じアプリを長く触っていると、自分の端末がいつも同じ時刻・同じ地域にあることに慣れてしまって、時刻がずれる世界を想像しなくなります。気づいたきっかけは、AdMob のリワード広告で「一日一回」のはずの特典が、ある利用者だけ何度も付いていたログでした。時計の問題が、表示の崩れと収益の両方に同時に効いていたわけです。

最初に手を入れたコードは、Antigravity のエージェントに「今日の壁紙を日替わりで選ぶロジックを書いて」と頼んで出てきたものでした。読めば自然で、レビューでも素直に通してしまう書き方です。けれど本番の時計は、開発端末ほど行儀よく進んでくれませんでした。

エージェントが書いた「自然なコード」がどこで崩れたか

最初の実装はおおむねこうでした。エージェントの提案そのものではありませんが、要点は同じです。

// 最初の実装(本番で崩れた)
fun todaysWallpaperIndex(total: Int): Int {
    val now = System.currentTimeMillis()
    val daysSinceEpoch = now / (24 * 60 * 60 * 1000)  // ミリ秒 → 日
    return (daysSinceEpoch % total).toInt()
}

一見、端末の地域に依存しない素直な計算に見えます。けれど System.currentTimeMillis() は UTC からの経過ミリ秒です。これを 24*60*60*1000 で割ると、切り替わりの瞬間は「UTC の真夜中」に固定されます。日本の利用者にとっては毎朝 9 時に壁紙が替わることになり、「日付が変わったのに替わらない」という体感につながっていました。

ここを直そうとした次の実装が、逆方向の事故を起こしました。

// 二番目の実装(別の壊れ方をした)
fun todaysWallpaperIndex(total: Int): Int {
    val cal = java.util.Calendar.getInstance()  // 端末の既定タイムゾーン
    val year = cal.get(java.util.Calendar.YEAR)
    val day = cal.get(java.util.Calendar.DAY_OF_YEAR)
    return ((year * 366 + day) % total)
}

端末のタイムゾーンを見るようになったので、日本では深夜 0 時に替わるようになりました。けれど Calendar.getInstance() は端末の現在のタイムゾーンをそのまま読みます。飛行機で時差のある地域へ移動した利用者は、移動の前後で DAY_OF_YEAR が一日進んだり戻ったりして、同じ夜に二回切り替わる、あるいは一日飛ぶ、という挙動になりました。year * 366 + day という日付キーの作り方も雑で、うるう年や年境界で破綻します。

二つの実装の失敗は、別々の原因を一つの式に詰め込んでいたことに尽きます。「いつ日付が変わるか」「どの地域の時計で測るか」「時計が巻き戻ったらどうするか」は、本来それぞれ独立した設計判断です。エージェントは目の前の一行をきれいに書いてくれますが、この三つを分けて考えるのは、アプリの性質を知っている人間の仕事だと感じました。

三つの失敗源を分けて考える

時刻まわりのバグは、たいてい次の三層のどこかから来ます。私はこの切り分けを最初に紙に書き出してから、コードに戻るようにしています。

第一に、端末時計そのもののずれです。利用者は時刻を手で変えられますし、放っておくと数分ずれている端末もあります。デイリー特典のように「進んだら得をする」境界は、ここを突かれます。

第二に、タイムゾーンです。「今日」がいつ始まるかは地域で違います。表示用の「今日」は利用者の地域で決めるのが自然ですが、その地域は移動で変わります。

第三に、夏時間です。夏時間の切り替え日には、一日が 23 時間や 25 時間になり、存在しない時刻や二度訪れる時刻が生まれます。「毎日 0 時に実行」のような素朴な前提が、年に二回だけ静かに崩れます。

この三つは、対策の方向がそれぞれ違います。表示の「今日」はタイムゾーンの問題、特典の不正は端末時計の問題、定期実行のずれは夏時間の問題、というふうに割り当てると、一つの式で全部を背負わせる無理がなくなります。

時刻ソース保証されること使いどころ
System.currentTimeMillis()UTC 基準・単調ではない(巻き戻る)表示用の日付計算(ゾーンを明示する前提で)
端末のタイムゾーン設定利用者の体感する「今日」・移動で変わる表示の日付境界。ただし固定はしない
SystemClock.elapsedRealtime()起動からの単調増加・手で変えられない経過時間の判定。日付の判定には使わない
サーバー時刻端末非依存・通信が要る不正を厳密に防ぎたい特典の最終判定

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
端末時計・タイムゾーン・夏時間の三つを別々の失敗源として切り分け、毎日入れ替わるコンテンツの「今日」を推測ではなく一つの日付キーに定める設計の型を持てるようになります
巻き戻された端末時計でデイリー特典や AdMob のリワード境界が破られる経路を塞ぎ、最終表示日を単調増加で記録して二重付与を止める実装を Kotlin の具体コードで追えます
Clock を注入して夏時間の前後や日付の変わり目を再現するテストの組み方まで示し、個人開発でどこまでエージェントに任せどの判断を自分で握るかの線引きを持てるようになります
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Antigravity Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

アプリ開発2026-06-25
お気に入りを一つ押すたびに、見えている壁紙が全部描き直されていた — Compose の再コンポーズを実測で止めた記録
壁紙アプリのグリッドで、お気に入りを一つ切り替えるたびに可視サムネイル全件が再コンポーズされていた問題を、Composition Tracing で実測してから直した記録です。不安定な引数の特定、データモデルの安定化、ラムダと派生状態の扱い、修正前後の再コンポーズ回数の比較までを個人開発の視点でまとめました。
アプリ開発2026-03-15
Antigravity × Android プロダクション設計 — Jetpack Compose + MVI で「出荷できるアプリ」を AI と作る
Antigravity を使って本番品質の Android アプリを設計・実装する上級ガイド。MVI アーキテクチャ、Jetpack Compose のパフォーマンス最適化、テスト戦略、CI/CD まで、AI と協働する実践手法を解説します。
アプリ開発2026-03-10
Android Studio × Antigravity Android 開発ガイド — Kotlin/Jetpack Compose を AI で加速する
Android Studio と Antigravity を組み合わせて Android アプリ開発を効率化する方法を、Kotlin と Jetpack Compose のコード生成からテスト、Google Play 公開まで解説します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →