DateTimeモジュール で遊んでみたシリーズ④ -DateTime.from_iso8601 ~ DateTime.from_naive! の紹介

こんにちは！
プログラミング未経験文系出身、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 の紹介](https://qiita.com/Alicesky2127/items/74e646264c7a55ea6f81)
4.DateTimeモジュール で遊んでみたシリーズ④ -DateTime.from_iso8601 ~ DateTime.from_naive! の紹介(本記事)
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.from_iso8601とは(from_iso8601/2)
`DateTime.from_iso8601(string, format_or_calendar \\ Calendar.ISO)`はISO 8601の拡張形式[^1]をDatetime構造体に変換します。

この関数の逆は`DateTime.to_iso8601(datetime, format \\ :extended, offset \\ nil)`です。

ISO 8601にタイムゾーン情報は含まれないため、UTCからの時差をISO 8601の書式に則って記載する必要があります。
すなわち、下記で時差を示します。
- UTCなら末尾に記号`Z`を付ける
- 他のタイムゾーンなら末尾に`+`または`−`で区切り、続けて時差を書く
 
例: 各タイムゾーンにおける`2015年1月23日23:50:07`を示したいとする。
| タイムゾーン     | 時差 | ISO 8601拡張形式 |
|:---------------:|:-----:|:--------------------------:|
| 日本標準時       | UTC+9 | 2015-01-23T23:50:07+09:00  |
| UTC             | 無し   | 2015-01-23T23:50:07Z       |
| 米国東部標準時   | UTC-5 | 2015-01-23T23:50:07-05:00  |


## 例
### 時差無しの場合
```diff_elixir:iex
DateTime.from_iso8601("2015-01-23T23:50:07Z")
```
```
{:ok, ~U[2015-01-23 23:50:07Z], 0}
```

### 時差ありの場合
```diff_elixir:iex
DateTime.from_iso8601("2015-01-23T23:50:07+09:00")
```
時差にあたる`+09:00`が9時間分のオフセット(32400秒)として返されます
```
{:ok, ~U[2015-01-23 14:50:07Z], 32400}
```

```diff_elixir:iex
DateTime.from_iso8601("2015-01-23T23:50:07-05:00")
```
時差にあたる`-05:00`が9時間分のオフセット(-18000秒)として返されます
```
{:ok, ~U[2015-01-24 04:50:07Z], -18000}
```
### 小数点以下
小数点以下は秒の後に`.`または`,`で表記します[^2]
すなわち、下記の2例は同じ値を返します。
```diff_elixir:iex
DateTime.from_iso8601("2015-01-23T23:50:07.123-05:00")
```
```
{:ok, ~U[2015-01-24 04:50:07.123Z], -18000}
```

```diff_elixir:iex
DateTime.from_iso8601("2015-01-23T23:50:07,123-05:00")
```
```
{:ok, ~U[2015-01-24 04:50:07.123Z], -18000}
```

### 紀元前
紀元前は年の前に`-`で表記します[^3]
この結果、年の桁数が5桁になり得ます。
```diff_elixir:iex
DateTime.from_iso8601("-2015-01-23T23:50:07,123-05:00")
```
```
{:ok, ~U[-2015-01-24 04:50:07.123Z], -18000}
```

### ISO8601基本形式への対応
`format_or_calendar`に`:basic`を渡すことでISO8601基本形式へ対応できます。
```diff_elixir:iex
DateTime.from_iso8601("20150123T235007.123-0500", :basic)
```
```
{:ok, ~U[2015-01-24 04:50:07.123Z], -18000}
```



# DateTime.from_iso8601とは(from_iso8601/3)
`DateTime.from_iso8601(string, calendar, format)`はISO 8601の拡張形式[^1]をDatetime構造体に変換します。
`calendar`と`format`を明示します。

## 例
```diff_elixir:iex
DateTime.from_iso8601("2015-01-23T23:50:07.123-05:00", Calendar.ISO, :extended)
```
```
{:ok, ~U[2015-01-24 04:50:07.123Z], -18000}
```

### ISO8601基本形式への対応
```diff_elixir:iex
DateTime.from_iso8601("20150123T235007.123-0500", Calendar.ISO, :basic)
```
```
{:ok, ~U[2015-01-24 04:50:07.123Z], -18000}
```


# DateTime.from_naiveとは
`DateTime.from_naive(naive_datetime, time_zone, time_zone_database \\ Calendar.get_time_zone_database())`はNaiveDatetime構造体をDatetime構造体に変換します。
タイムゾーンはカレンダーデータベース（今回の場合はtzdata）と照合します。

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

## 例
```diff_elixir:iex
DateTime.from_naive(~N[2016-05-24 13:26:08.003], "Etc/UTC")
```
```
{:ok, ~U[2016-05-24 13:26:08.003Z]}
```

```diff_elixir:iex
DateTime.from_naive(~N[2016-05-24 13:26:08.003], "Asia/Tokyo")
```
```
{:ok, #DateTime<2016-05-24 13:26:08.003+09:00 JST Asia/Tokyo>}
```


### サマータイム開始／終了時刻をまたぐ場合
米国ニューヨークの次のサマータイム開始時刻は、現地時刻の3/9の02:00です。
このタイミングで時計が1時間進みます
これをまたいで使用する場合`{:gap, gap直前の有効な日時, gap直後の有効な日時}`のタプルが返されます。

```diff_elixir:iex
DateTime.from_naive(~N[2025-03-09 02:00:00], "America/New_York", Tzdata.TimeZoneDatabase)
```
```
{:gap, #DateTime<2025-03-09 01:59:59.999999-05:00 EST America/New_York>,
 #DateTime<2025-03-09 03:00:00-04:00 EDT America/New_York>}
```


# DateTime.from_naive!とは
`DateTime.from_naive!(naive_datetime, time_zone, time_zone_database \\ Calendar.get_time_zone_database())`はNaiveDatetime構造体をDatetime構造体に変換します。
タイムゾーンはカレンダーデータベース（今回の場合はtzdata）と照合します。

## 例
```diff_elixir:iex
DateTime.from_naive!(~N[2016-05-24 13:26:08.003], "Etc/UTC")
```
```
~U[2016-05-24 13:26:08.003Z]
```

```diff_elixir:iex
DateTime.from_naive!(~N[2016-05-24 13:26:08.003], "Asia/Tokyo")
```
```
#DateTime<2016-05-24 13:26:08.003+09:00 JST Asia/Tokyo>
```

変換に成功した場合`DateTime.from_naive`は `{:ok, Datetime構造体}`の***タプル*** が返されるが、`DateTime.from_naive!`は ***Datetime構造体*** が返されるので注意。


### サマータイム開始／終了時刻をまたぐ場合
米国ニューヨークの次のサマータイム開始時刻[^1]は、現地時刻の3/9の02:00です。
このタイミングで時計が1時間進みます[^2]
これをまたいで使用する場合`ArgumentError`を返します。

```diff_elixir:iex
DateTime.from_naive!(~N[2025-03-09 02:00:00], "America/New_York", Tzdata.TimeZoneDatabase)
```
```
** (ArgumentError) cannot convert ~N[2025-03-09 02:00:00] to datetime because such instant does not exist in time zone America/New_York as there is a gap between #DateTime<2025-03-09 01:59:59.999999-05:00 EST America/New_York> and #DateTime<2025-03-09 03:00:00-04:00 EDT America/New_York>
    (elixir 1.17.3) lib/calendar/datetime.ex:691: DateTime.from_naive!/3
    #cell:7rsn2mhh5hf3osdw:1: (file)
```



# ～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/ISO_8601#%E5%9F%BA%E6%9C%AC%E5%BD%A2%E5%BC%8F%E3%81%A8%E6%8B%A1%E5%BC%B5%E5%BD%A2%E5%BC%8F

[^2]:https://ja.wikipedia.org/wiki/ISO_8601#%E5%9B%BD%E5%AE%B6%E8%A6%8F%E6%A0%BC%E3%81%AB%E3%82%88%E3%82%8B%E6%8B%A1%E5%BC%B5

[^3]:https://ja.wikipedia.org/wiki/ISO_8601#%E5%B9%B4%E3%81%AE%E8%A1%A8%E8%A8%98%EF%BC%880000%E5%B9%B4%E3%82%88%E3%82%8A%E5%89%8D%E3%80%819999%E5%B9%B4%E3%82%88%E3%82%8A%E5%BE%8C%EF%BC%89

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


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