👏

DateTime.now()の値は嘘らしい。そんなバカな........と思ったけどマジっぽい。普通に悲しい

2023/01/05に公開

Q1 蓮美(はすみ)は本名ですか?
→ いいえ、源氏名です。

Q2 元ホストですか?
→ はい、そうです。エンジニア界では珍しいですか?

あぁ、DateTime.now()は嘘つきらしい

↑これ僕は体験した事ないですよ、
ちなみにミンプロの先生も知らないし体感した事ないから分からん。と言ってました。

でも、そうらしいですね。噂を聞くと...。

まず、DateTime.now()が取得するのって、そのデバイスが設定している時間を取得するって知ってましたか?

僕はまぁまぁ最近知りました、というか、まだ知って10日も経ってません。
なので、知ってる風を装ったイタイやつです。

要は、時間を変えられるとその時間基準で動いてしまうんです。

じゃあ、どうするのか?
ですよね。

答えは簡単、データベースの中の時間を取ってきて突っ込めば良いだけです。

僕はFirebaseしか使った事がないニワカのエンジニアなので、
それ以外のデータベースの事は知ったこっちゃねぇって感じですけど
↓を突っ込めば解決するっぽいです。

FieldValue.serverTimestamp()

見たことも聞いたこともないヤァァァァって方は下記URLから勉強してみてください。
まだじっくり中のサイトを読んでませんが、それっぽい事書いてあったので、貼っておきます。

https://chaika.hatenablog.com/entry/2021/04/18/083000
このサイトの運営者さん、こんな記事に引用されてウゼェと思ったら連絡ください、すぐ削除しますのでmm

懸念点はtoJson時の型が違うこと

DateTime.now()をFieldValue.serverTimestamp()に変更したら良いんだな
それだけなら、楽チン。

とか思った、そこのあなた!
甘いです。

今までDateTime型だったものが、急にFieldValue型になるんですよ。
入らないじゃないですか。
(もちろん、現実的には入る事もあります、オブジェクトクラスを利用せずにそのままMap型を使用している場合とかは)

じゃあ、FieldValue型に変えたら良いじゃんって??

いや、そうしたら、fromJson時やUIで表示したい時、上手く出来ませんよね。

要は、
書き込み時には、FieldValue型にしたくて、
取得時には、DateTime型にしたい訳です。

なんか世間体では、共用タイプ(??)UnionType(??)
名前は忘れてしまいましたが、2つの型を持つやり方があるらしいです。

でも、Dartにはねぇぇぇぇ
って問題が発生します。

あぁ、眠い。これ書いてるの深夜0時22分なんですよね。
とりま、一旦寝ます。おやすみ。

(...)

はい、おはようございます。
朝9時に起きました。。。完全に寝坊です。

Freezed使えば何とかなるっぽい

らしいけど、正直言って難し過ぎました。(僕に取っては)
もし、下記のコードから読み解ける方、コーヒーかカフェオレ奢るので教えてくださいmm

https://github.com/KosukeSaigusa/flutter-infinite-scroll-chat/blob/e7a483cba342230942add1c053c904e87e01a04d/lib/utils/json_converters/union_timestamp.dart

これ、試そうにもコードが訳分からんし、実践レベルまで持っていくには
相当時間かかりそうやなぁぁぁ、あぁ、めんどぉ
と思って辞めました。

あ、このさいぐささんはcodeboyのメンターやってて
僕もかれこれ、5,6万課金した超優秀な先生です。

ミンプロの先生に相談

した結果、思いもよらない回答をいただきました。
その際の回答をそのまま貼り付けておきますね

講座で使っているDart Data Class Pluginを使うなら、添付画像のような感じでもイケるかもしれませんね(Cloud Firestoreは同じコレクションにぶらさがるDocument同士でも型を揃える必要がないので、それを逆手にとって(?)アプリ側では型を揃えるけど、Cloud Firestore側では異なる型を共存させて放っておくイメージ?)。

なるほど!!

toJson(toMap)メソッドに、最初からFieldValue.serverTimestamp()を突っ込めば良かったんだ

と、こうなる訳です。困ったらミンプロの先生、頼りになります。

ということで、上記の写真では要らない変数とか色々と入っているので
パパッと簡略化してしまうと、こんな感じでしょうかね。

class Test {
  final DateTime createdAt;
  const Test({required this.createdAt});

  Map<String, dynamic> toMap() {
    return {
      'createdAt': FieldValue.serverTimestamp(),
    };
  }

  factory Test.fromMap(Map<String, dynamic> map) {
    return Test(
      createdAt: map['createdAt'].toDate(),
    );
  }
}

一旦このオブジェクトクラスをインスタンス化する際、
createdAtにDateTime.now()を入れたとしても、
toMapメソッドを使用する時には、
FieldValue.serverTimeStamp()
が強制的に入るので、問題なさそう。

よし、これでクリア。多分。

おまけ

悪意を持って時間を変えるとどうなるんだろうなぁと思って
実験してみましたが、LINEとかではこんな挙動なってました↓

明後日とか、明日とかにメッセージが届いてます。
まぁ流石、ITの大企業ですね。ちゃんと対策されてます(つまらん)

ただ、個人でやっていたり、中小レベルの企業のアプリなら対策されてないかもなので
また違った感じで面白い事になるかもしれませんね。
(訴訟されると怖いのでやりませんが)

もしやるなら知り合いのアプリとかでちょっとしたイタズラくらいに留めておくのが良いかもですね〜
あと、テストリリース時に、バグを伝える意味とかで使ってあげると良いかも〜

Discussion