NaiveDateTime型、DateTime型、Time型のわけわからんを解消する！

### はじめに
---
アプリ制作をしているとユーザーのログイン日時、ログアウト日時など日時の操作や理解がとても重要だということがわかってきました。
UTC日時？Asia/Tokyoのタイムゾーン？それらの情報を持っていないNaiveDateTime型？頭がこんがらがった状態から試行錯誤して学んだ結果を書いきます。

### 実現したいこと概要
---

DBにNativeDateTime型で入っているUTC時刻を日付部分だけをみてAsia/Tokyoの本日(UTC時刻
+9)かを判定すること

例)DBの`2024-03-17 20:03:15`を`2024-03-18 05:03:15`に変換し、2024/3/18が本日か判定したい。

### 仕様
---
- DBの`dummy`テーブルの`inserted_at`カラムに`NativeDateTime`型の日時が入っている
- 日時は世界標準時間(ただし、`NativeDateTime`型なのでタイムゾーン情報は含まない)

### つまづきポイント
---
最初は以下のように修正していました。
```elixir
current_date =
          Timex.to_datetime(dummy.inserted_at, "Asia/Tokyo")
            |> DateTime.to_date() 
```

 - [参考hexdocs：Timex.to_datetime/2](https://hexdocs.pm/timex/Timex.html#to_datetime/2)
 NaiveDateTime型をDateTime型に変換

```bash

Timex.to_datetime(~N[2022-01-01 12:00:00], "America/New_York")
#DateTime<2022-01-01 12:00:00-05:00 EST America/New_York>
```
- [参考hexdocs:DateTime.to_date/1](https://hexdocs.pm/elixir/1.15.7/DateTime.html#to_date/1)
DateTime型をDate型に変換

```bash
dt = %DateTime{year: 2000, month: 2, day: 29, zone_abbr: "CET",
               hour: 23, minute: 0, second: 7, microsecond: {0, 0},
               utc_offset: 3600, std_offset: 0, time_zone: "Europe/Warsaw"}
DateTime.to_date(dt)
~D[2000-02-29]
```
ここに2つの問題があります。

#### 問題点1
---
`Timex.to_datetime(dummy.inserted_at, "Asia/Tokyo")`
2024-03-17 20:03:15`を`2024-03-18 05:03:15`変換して使いと思っていましたが、そもそもタイムゾーン情報を持たないため、`dummy.inserted_at`をAsia/Tokyoのタイムゾーン情報としていることになります。

例)
```
iex(1)> Timex.to_datetime(~N[2024-03-17 20:03:15], "Asia/Tokyo")
#DateTime<2024-03-17 20:03:15+09:00 JST Asia/Tokyo>
```

3/17の20:03はJST(日本標準時)でUTC(協定世界時)よりも9時間進んでいるという結果になります。
つまり、+9時間した`3/18 2024-03-18 05:03`に変換されていない。

#### 問題点2
---
`DateTime.to_date`は日時情報からタイムゾーン情報を含む時刻を切り捨てるだけなので、変換されていないまま、日付部分だけを抽出しています。


### やりたいこと実現のステップ
---

- DBの`NativeDateTime`型の日時に標準時間としてUTCタイムゾーン情報を付加
- Asia/Tokyoのタイムゾーンに変換
- 日付のみに変換
- Asia/Tokyoでの本日日付を取得
- DBの日付と本日日付を比較

### 成功したコード

```
  def is_last_dummy_today?(%User{id: user_id}) do
    dummy =
      Dummy
      |> where([l], l.user_id == ^user_id)
      |> where([l], l.category == "dummy")
      |> order_by(asc: :inserted_at)
      |> last()
      |> Repo.one()

      last_dummy_datetime =
      DateTime.from_naive!(dummy.inserted_at, "Etc/UTC")
        |> IO.inspect(label: "Etc/UTCのログイン最新")
        |> Timex.Timezone.convert("Asia/Tokyo")
        |> IO.inspect(label: "Asia/tokyoのログイン最新")
        |> DateTime.to_date()
        |> IO.inspect(label: "Asia/tokyoログイン最新日付")

    # 最後のログインが今日かどうかを判断
    Date.diff(Timex.today("Asia/Tokyo"), last_dummy_datetime) == 0
 
  end
```

やっとAsia/Tokyoで表示されました！

```bash 
Etc/UTCのログイン最新: ~U[2024-03-17 21:16:22Z]
Asia/tokyoのログイン最新: #DateTime<2024-03-18 06:16:22+09:00 JST Asia/Tokyo>
Asia/tokyoログイン最新日付: ~D[2024-03-18]
```

以下でもOK

```
last_dummy_datetime =
          dummy.inserted_at
            |> DateTime.from_naive!("Etc/UTC")
            |> Timex.to_datetime("Asia/Tokyo") #タイムゾーン情報変更
            |> DateTime.to_date() # DateTime型からDate型へ変換
```

### 基本の整理
---
基本が曖昧な初学者向けに(私w)上記以外のことも整理しました。

### ~N[2022-01-01 12:00:00]とは

シジルのNは`NaiveDateTime`(ナイーブデートタイム)。タイムゾーン情報を持たない日時を表す。
Timexライブラリを使用して`~N[2022-01-01 12:00:00]`（ナイーブデートタイム）をタイムゾーン付きのDateTimeに変換することができます。`Timex.to_datetime/2`関数は、第一引数にナイーブデートタイム（NaiveDateTime）、第二引数にタイムゾーンの文字列（この場合は"Asia/Tokyo"）を取り、指定されたタイムゾーンを考慮したDateTime構造体を返します。


### おまけ
---
DateTimeモジュールの基本についてYoutubeのショート動画にまとめていますので学びの一助に活用していただけると嬉しいです！

[DateTimeモジュール概要編](https://www.youtube.com/shorts/_7p7VOyJhq4)

### さいごに
---
私と同じく、IO.inspectでひたすら結果を出して頭を悩ませている初学者の方の一助になればと思い書きました。
誤りなどありましたらご指摘お願いします！
また、thewaggleではElixirでの開発にジョインしてくださる仲間を募集しています！
ご興味ある方、お気軽にご連絡ください。