個人開発のライブラリに「ガチ勢」が現れて、激アツ進化した話
個人開発のライブラリに「ガチ勢」が現れて、激アツ進化した話
タイムゾーン、辛くないですか?
サーバーはUTC、アプリはJST、でもユーザーの端末はアメリカ時間。
リリース直後、問い合わせフォームに届いたのは『ログインボーナスがもらえない』という怒りの声でした。
あくまでも一例ですが、Flutterで日本向けのアプリを開発していると、必ずと言っていいほどこのような問題にぶち当たるでしょう。
例えば、ユーザーが端末を米国設定にしていると、日本時間ではまだ「今日」なのにアプリ内では「明日」と判定されてしまい、ログインボーナスやキャンペーンの判定がおかしくなる……あるあるですよね。
多くのアプリでは端末時刻を基準に日付判定を行っています。
そのため、ログインボーナスやキャンペーンなど 日付境界が仕様に直結する機能では、端末のタイムゾーン設定によって意図しないズレが発生しやすい という問題があります。
こうしたケースでは、計算の基準となるタイムゾーンをJSTなどに固定して扱える仕組みが必要になります。
しかし、これだけの解決のために timezone や Jiffy などのライブラリを入れるのは少し大げさに感じるし、
かといって自前で計算するのはバグの温床です。
「もっと手軽に、特定のタイムゾーン固定で日付操作がしたい」
そんなシンプルな動機で、easy_date_time というライブラリを作りました。
当初は「自分が便利ならそれでいい」くらいの気持ちでしたが、公開して間もなく、予想外の出来事が起きました。
ある海外エンジニアからPull Requestが届き、その方とのやりとりを通じて、私のライブラリが思わぬほど大きく成長したのです。
今回は、easy_date_time の紹介とともに、OSS活動を通じて学んだ「使われるライブラリになるための設計」についてお話しします。
easy_date_time とは?
まずは、どんなライブラリを作ったのかざっくり紹介させてください。
一言で言うと、「標準の DateTime と全く同様に使える、タイムゾーンや日付計算の苦しみを解決するもの」 です。
特徴
-
Drop-in Replacement:
- 標準
DateTimeと同じ形のAPIを提供し、主要なライブラリ(intlなど)とシームレスに連携できる。
- 標準
-
Timezone Aware:
- 特定のタイムゾーン(JSTなど)を固定して扱える。
-
Intuitive:
-
+ 1.daysのような便利な記述をサポート。
-
// 日本時間(JST)で現在時刻を取得
final now = EasyDateTime.now();
// 便利な加算・減算
final tomorrow = now + 1.days;
final nextWeek = now + 1.weeks;
// 標準のDateTimeと同じようにフォーマット可能
print(now.toString());
どんな進化が起きたのか?
【事例①】「型安全性」か「相互運用性」か
当初、私は 「あえて DateTime を継承しない(implementsしない)」 設計にしていました。
独自の型にしておけば、タイムゾーン付きの EasyDateTimeと、タイムゾーンなしの DateTime が混ざる事故を防げて安全だろう……という、堅牢性へのコミットメントでした。
しかし、既存コードを大規模に移行しようとしていた彼にとって、この「型分離」はただのブロッカーでした。
例えば intl パッケージの DateFormat など、Flutterエコシステムには DateTime 型を要求する処理が山ほどあります。
final myDate = EasyDateTime.now();
// 😭 型エラー!
// DateFormatはDateTime型を要求するため、EasyDateTime型をそのまま渡せない
// String formatted = DateFormat('yyyy-MM-dd').format(myDate);
// 😩 毎回変換メソッド(.toDateTime)を挟む必要があり、手間がかかる
String formatted = DateFormat('yyyy-MM-dd').format(myDate.toDateTime());
「Drop-in replacement(そのまま置き換え可能)」を謳うなら、ユーザーに変換の手間をかけさせるべきではない。
ユーザーからの強い要望を受け、私たちは議論を重ねました。
最終的に私たちは、「型分離による理論的な安全性」よりも、Flutterエコシステムとの相互運用性を優先する設計への合意を取りました。
final myDate = EasyDateTime.now();
// 🚀 変換不要!
// implements DateTime したので、intlなど主要ライブラリにそのまま渡せる
String formatted = DateFormat('yyyy-MM-dd').format(myDate);
【事例②】忘れがちな「DST(夏時間)」の闇
もう一つの壁は、日本を含む東アジアに住んでいると完全に盲点となる DST(夏時間) の問題でした。
標準の DateTime の Duration 加算は、暦日ではなく物理的な秒数を加算します。
そのため、DST(夏時間)のある地域(米国や欧州など)では、「カレンダー上の明日」と一致しない結果になることがあります。
// 夏時間への切り替わり日に「+1日」すると...
final today = DateTime(2025, 3, 9, 0, 0); // DST開始
final tomorrow = today.add(Duration(days: 1));
// 期待値: 2025-03-10 00:00
// 現実: 2025-03-10 01:00 (3月9日の実際の長さは23時間で、1時間ズレた!😱)
Duration を足すという行為は、物理的な時間を足すことなので、時計の針がずれる夏時間では「カレンダー上の明日」と一致しなくなるのです。
私が当初書いていたコードも、この配慮が漏れていました。
しかし、世界中で使われるプロダクトにおいては、これは致命傷です。
その方からのPRは、まさにこの致命的な問題を見つけてくれたものでした。
現在、easy_date_time は標準の add(物理加算) は互換性のためにそのまま残し、新たにカレンダー加算用の拡張メソッドを提供するなど、用途に応じて使い分ける対策を講じています。
「僕一人で開発していたら絶対に出会わなかったバグ」を、海外のコントリビューターが塞いでくれました。
OSSならではの瞬間でした。
OSSメンテナとして一段上に行く瞬間
easy_date_time を公開して強く実感したのは、
「自分の理想の設計」と「ユーザーが本当に求めているもの」は、必ずしも一致しない
という現実でした。
当初の私は、
- 型を分けて事故を防ぐほうが“正しい設計”だ
- 多少不便でも、安全性を優先すべきだ
と考えていました。
しかし実際には、
- 既存コードにそのまま組み込めること
- 既存ライブラリと違和感なく連携できること
のほうが、ユーザーにとっては圧倒的に重要でした。
理論上は美しくても、現場で使われなければ意味がない。
OSSにおいては、「作者の美学」よりも「利用者の現実」が勝つ瞬間があります。
そしてその「現実」を受け入れたとき、私は「設計者」から「メンテナ」へと、一段レベルアップした感覚がありました。
- メンテナンスコストを引き受ける覚悟
- それでもユーザー体験を優先する判断
これは、OSSを「作る側」から「支える側」へと意識が変わった瞬間でもあります。
もしあなたがこれからライブラリを公開しようとしているなら、この体験が何かの参考になれば嬉しいです。
おわりに
ライブラリを公開していなければ、私はそれらの現実にきっと気づけなかったと思います。
OSSでは、正しさや美しさだけでは足りません。実際の現場で「使われること」そのものが、設計の正解になります。
easy_date_time は、そうした利用者の現実に向き合う中で、少しずつ形を変えながら育ってきたライブラリです。
もし日付操作やタイムゾーン周りで
「これ、毎回ちょっと面倒だな」と感じる瞬間があれば、
このライブラリがその手間を減らす助けになれば嬉しいです。
Pub.dev
Github
(Star⭐をもらえると、メンテナンスの励みになります☺️)
Discussion