DateTimeモジュール で遊んでみたシリーズ③ -DateTime.convert ~ DateTime.from_gregorian_seconds の紹介

こんにちは！
プログラミング未経験文系出身、Elixirの国に迷い込んだ？！見習いアルケミストのaliceと申します。
今回はDateTimeモジュールについて学んだことをまとめます。

# 目次
1.[DateTimeモジュール で遊んでみたシリーズ① -事前準備編](https://qiita.com/Alicesky2127/items/71d6905684f6f0f936c2)
2.[DateTimeモジュール で遊んでみたシリーズ② -DateTime.add ~ DateTime.compare の紹介](https://qiita.com/Alicesky2127/items/971a013f34f0f1fb7c90)
3.DateTimeモジュール で遊んでみたシリーズ③ -DateTime.convert ~ DateTime.from_gregorian_seconds の紹介(本記事)
4.[DateTimeモジュール で遊んでみたシリーズ④ -DateTime.from_iso8601 ~ DateTime.from_naive! の紹介](https://qiita.com/Alicesky2127/items/79869efcf8bb5cee9d82)
5.[DateTimeモジュール で遊んでみたシリーズ⑤ -DateTime.from_unix ~ DateTime.new! の紹介](https://qiita.com/Alicesky2127/items/6ce1eb3071880db5dec2)
6.[DateTimeモジュール で遊んでみたシリーズ⑥ -DateTime.now ~ DateTime.shift_zone! の紹介](https://qiita.com/Alicesky2127/items/b706801a887ceb5427ee)
7.[DateTimeモジュール で遊んでみたシリーズ⑦ -DateTime.to_date ~ DateTime.to_string の紹介](https://qiita.com/Alicesky2127/items/a3fcf13bd2577d57b431)
8.[DateTimeモジュール で遊んでみたシリーズ⑧ -DateTime.to_time ~ DateTime.utc_now の紹介](https://qiita.com/Alicesky2127/items/ebd5b063c041adf2d1d0)
9.[DateTimeモジュール で遊んでみたシリーズ番外編 -Livebookでtzdata導入](https://qiita.com/Alicesky2127/items/4ad2072cbca04193a3d4)

# 目的 
DateTimeモジュールに含まれる関数を触って機能を理解したい

https://hexdocs.pm/elixir/DateTime.html

## 実行環境
Windows 11 ＋ WSL2 ＋ Ubuntu 22.04
Elixir v1.17.3
Erlang v27.0

# 前提
本記事を手元検証するには、前提としてtzdataの導入が必要です。
まだの場合は下記1.または2.で準備をしてください。

1.local環境でtzdataの導入

https://qiita.com/Alicesky2127/items/71d6905684f6f0f936c2

2.Livebookでtzdataの導入

https://qiita.com/Alicesky2127/items/4ad2072cbca04193a3d4


# DateTime.convertとは
`DateTime.convert(datetime, calendar)`は`datetime`を`calendar`基準に変換したものを返します。
カレンダー間で明確に変換できない場合は`{:error, :incompatible_calendars}`を返します。

## 例
※前提として`calendar`の実装が必要です。
下記の例では現在のグレゴリオ暦に10000年を追加するカレンダーを実装しているとします。
```diff_elixir:iex
dt1 = %DateTime{year: 2000, month: 2, day: 29, zone_abbr: "AMT",
                hour: 23, minute: 0, second: 7, microsecond: {0, 0},
                utc_offset: -14400, std_offset: 0, time_zone: "America/Manaus"}
```

```diff_elixir:iex
DateTime.convert(dt1, Calendar.Holocene)
```

```
{:ok, %DateTime{calendar: Calendar.Holocene, day: 29, hour: 23,
                microsecond: {0, 0}, minute: 0, month: 2, second: 7, std_offset: 0,
                time_zone: "America/Manaus", utc_offset: -14400, year: 12000,
                zone_abbr: "AMT"}}
```



# DateTime.convert!とは
`DateTime.convert(datetime, calendar)`は`datetime`を`calendar`基準に変換したものを返します。
カレンダー間で明確に変換できない場合は`ArgumentError`を返します。

## 例
※前提として`calendar`の実装が必要です。
下記の例では現在のグレゴリオ暦に10000年を追加するカレンダーを実装しているとします。
```diff_elixir:iex
dt1 = %DateTime{year: 2000, month: 2, day: 29, zone_abbr: "AMT",
                hour: 23, minute: 0, second: 7, microsecond: {0, 0},
                utc_offset: -14400, std_offset: 0, time_zone: "America/Manaus"}
```

```diff_elixir:iex
DateTime.convert!(dt1, Calendar.Holocene)
```

変換に成功した場合`DateTime.convert`は `{:ok, Datetime構造体}`の***タプル*** が返されるが、`DateTime.convert!`は ***Datetime構造体*** が返されるので注意。
```
%DateTime{calendar: Calendar.Holocene, day: 29, hour: 23,
          microsecond: {0, 0}, minute: 0, month: 2, second: 7, std_offset: 0,
          time_zone: "America/Manaus", utc_offset: -14400, year: 12000,
          zone_abbr: "AMT"}
```



# DateTime.diffとは
`DateTime.diff(datetime1, datetime2, unit \\ :second)`は`datetime1`から`datetime2`を減算します。
結果は`unit`単位で返されます。

## 例
```diff_elixir:iex
DateTime.diff(~U[2024-01-15 10:00:10Z], ~U[2024-01-15 10:00:00Z])
```
```
10
```

`datetime1`よりも`datetime2`が大きい場合
```diff_elixir:iex
DateTime.diff(~U[2024-01-15 10:00:00Z], ~U[2024-01-15 10:00:10Z])
```
```
-10
```

### `unit`を変更した場合
小数の結果はサポートされておらず、切り捨てられます。

#### 例
`unit`変更前。2つのDatetime構造体には「1日(=86400秒) + 10秒」の差分があります。
```diff_elixir:iex
DateTime.diff(~U[2024-01-16 10:00:10Z], ~U[2024-01-15 10:00:00Z])
```
```
86410
```

`unit`変更後。10秒の差分が切り捨てられました。
```diff_elixir:iex
DateTime.diff(~U[2024-01-16 10:00:10Z], ~U[2024-01-15 10:00:00Z], :day)
```
```
1
```



# DateTime.from_gregorian_secondsとは
`DateTime.from_gregorian_seconds(seconds, arg \\ {0, 0}, calendar \\ Calendar.ISO)`は現在のグレゴリオ暦基準で、`seconds`をDatetime構造体に変換します。

`seconds`特定の日付と時刻を秒数で示し、`arg`で1秒以下の時刻を示します。

タイムゾーンはUTCで返されます。
UTC以外のタイムゾーンが必要な場合は、この関数の結果に対して`DateTime.shift_zone/3`を使用してタイムゾーンを変換してください。

この関数の逆は`DateTime.to_gregorian_seconds(datetime)`です。

## 例
```diff_elixir:iex
DateTime.from_gregorian_seconds(1)
```
```
~U[0000-01-01 00:00:01Z]
```

```diff_elixir:iex
DateTime.from_gregorian_seconds(63_872_532_000)
```
```
~U[2024-01-15 10:00:00Z]
```

### 余談
ISO 8601形式で0000年は現実の紀元前1年を指します。
現実には紀元1年の前は紀元前1年ですが、コンピュータにとって算術のしやすさを優先しています[^1]



# ～Elixirの国のご案内～
↓Elixirって何ぞや？と思ったらこちらもどぞ。Elixirは先端のアレコレをだいたい全部できちゃいます:laughing::sparkles::sparkles:

https://qiita.com/piacerex/items/09876caa1e17169ec5e1

↓ゼロからElixirを始めるなら「エリクサーチ」がおすすめ！私もエンジニア未経験から学習中です。

https://elixir-lang.info/topics/entry_column


↓[We Are The Alchemists, my friends!:bouquet:](https://qiita.com/torifukukaiou/items/6bcd5bbb80df9e594e9e "Awesome-san's Awesome article")[^10]
Elixirコミュニティは本当に優しくて温かい人たちばかり！
私が挫折せずにいられるのもこの恵まれた環境のおかげです。
まずは気軽にコミュニティを訪れてみてください。[^11]


https://speakerdeck.com/elijo/elixirkomiyunitei-falsebu-kifang-guo-nei-onrainbian

[^1]: https://ja.wikipedia.org/wiki/0%E5%B9%B4#%E8%A5%BF%E6%9A%A60%E5%B9%B4

[^10]: @torifukukaiouさんのAwesomeな名言をお借りしました。Elixirコミュニティを一言で表すと、これに尽きます。


[^11]: @kn339264さんの素敵なスライドをお借りしました。Elixirコミュニティはいろんな形で活動中！