本記事は、「Elixirのアドベントカレンダー2024」の15日目の記事です。
Elixirのv1.18がもうすぐ出てきそうというタイミングで、久々にローカル環境のElixir環境をみたら、v1.14で止まっていました。
直近で触っていたのはLivebookだったため、Elixir本体を直接触っていませんでした。そのため、随分とバージョンに差があいてしまっていました。とほほ...
さて、遅れてしまったものを嘆いていても仕方ない。
気持ちを切り替えて、新しいバージョンをインストール...と思ったら、少し前にマシンのCPUがAppleシリコン製に変わっていたこともあり、asdfも失敗。
幸いなことに、先人達がAppleシリコン製のインストール方法を残してくださっていますが、手っ取り早く動かしておきたかったので、今回はdockerを頼ることにしました。
Elixir v1.17-otp-27をDocker経由でインストール
Elixirの公式イメージを確認。
2024年12月15日時点では、1.17.3系が最新。
v1.17系であればいいが、optは最新の27系がいいなと思い、「1.17-otp-27」にする。
ひとまず、iexで起動すればいいかなと、以下を実施。
docker run -it --rm elixir:1.17-otp-27
実行結果はこちら。
$docker run -it --rm elixir:1.17-otp-27
Unable to find image 'elixir:1.17-otp-27' locally
1.17-otp-27: Pulling from library/elixir
82312fccb35f: Pull complete
4ac722d9cf93: Pull complete
261351ed796d: Pull complete
a9d319298afc: Pull complete
e094548c0cea: Pull complete
d1a116ea1e30: Pull complete
40d63b8a7c51: Pull complete
94b3c35d2121: Pull complete
Digest: sha256:197b5e195e8413cf898548d71d3023ab8b074f9e5b7ac90c60d37804a9515da1
Status: Downloaded newer image for elixir:1.17-otp-27
Erlang/OTP 27 [erts-15.1.2] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1] [jit]
Interactive Elixir (1.17.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
良かった。起動した。
Duration and shift/2 functionsを試す
さて、起動したのはいいけど、何しようかな...とリリースノートを見ていると、「Adding Duration and shift/2 functions」の文字が。
そういえば、直近お仕事で「起点日からNヶ月後の開始日/終了日を算出する」という小さなロジックを書いてたっけ...と思い出し、興味を持ちました。
なので、リハビリがてら、ここをちょっと触ってみることに。
では、早速。
シジルで、NativeData型を定義
iex> now = ~N[2024-12-15 23:21:11]
~N[2024-12-15 23:21:11]
サンプルに従い、「shift/2」を試してみる。
- 「Date.shift/2」の場合、当然「時刻」部分はカットされる
- 「Time.shift/2」の場合、当然「日付」部分はカットされる
- 「NaiveDateTime.shift/2」の場合、日時は残ったまま
iex> now |> Date.shift(month: 1)
~D[2025-01-15]
iex> now |> Time.shift(hour: 1)
~T[00:21:11]
iex> now |> NaiveDateTime.shift(hour: 1)
~N[2024-12-16 00:21:11]
年月日、時分秒はそれぞれ指定できる。
もちろん、Dateでは「年月日」のみ、Timeでは「時分秒」しか指定できない。
指定すると、怒れる。
iex> now |> NaiveDateTime.shift(year: 2)
~N[2026-12-15 23:21:11]
iex> now |> NaiveDateTime.shift(month: 2)
~N[2025-02-15 23:21:11]
iex> now |> NaiveDateTime.shift(day: 2)
~N[2024-12-17 23:21:11]
iex> now |> NaiveDateTime.shift(hour: 2)
~N[2024-12-16 01:21:11]
iex> now |> NaiveDateTime.shift(minute: 2)
~N[2024-12-15 23:23:11]
iex> now |> NaiveDateTime.shift(second: 2)
~N[2024-12-15 23:21:13]
## Dateの場合
iex> now |> Date.shift(year: 2)
~D[2026-12-15]
iex> now |> Date.shift(month: 2)
~D[2025-02-15]
iex> now |> Date.shift(day: 2)
~D[2024-12-17]
iex> now |> Date.shift(hour: 2)
** (ArgumentError) unsupported unit :hour. Expected :year, :month, :week, :day
(elixir 1.17.3) lib/calendar/date.ex:819: Date.validate_duration_unit!/1
(elixir 1.17.3) lib/enum.ex:987: Enum."-each/2-lists^foreach/1-0-"/2
(elixir 1.17.3) lib/calendar/date.ex:813: Date.__duration__!/1
(elixir 1.17.3) lib/calendar/date.ex:802: Date.shift/2
iex:63: (file)
## Timeの場合
iex> now |> Time.shift(hour: 2)
~T[01:21:11]
iex> now |> Time.shift(minute: 2)
~T[23:23:11]
iex> now |> Time.shift(second: 2)
~T[23:21:13]
iex> now |> Time.shift(day: 2)
** (ArgumentError) unsupported unit :day. Expected :hour, :minute, :second, :microsecond
(elixir 1.17.3) lib/calendar/time.ex:617: Time.validate_duration_unit!/1
(elixir 1.17.3) lib/enum.ex:987: Enum."-each/2-lists^foreach/1-0-"/2
(elixir 1.17.3) lib/calendar/time.ex:602: Time.__duration__!/1
(elixir 1.17.3) lib/calendar/time.ex:584: Time.shift/2
iex:67: (file)
複数の設定も可能
iex> now |> NaiveDateTime.shift(year: 4, day: 3, hour: 2)
~N[2028-12-19 01:21:11]
マイクロ秒も指定できる。が、指定の仕方は、他の場合と異なり、値と有効桁数の指定になっている
iex> now |> NaiveDateTime.shift(year: 4, day: 3, hour: 2, microsecond: {500, 6})
~N[2028-12-19 01:21:11.000500]
iex> now |> NaiveDateTime.shift(year: 4, day: 3, hour: 2, microsecond: {500, 5})
~N[2028-12-19 01:21:11.00050]
iex> now |> NaiveDateTime.shift(year: 4, day: 3, hour: 2, microsecond: {500, 4})
~N[2028-12-19 01:21:11.0005]
iex> now |> NaiveDateTime.shift(year: 4, day: 3, hour: 2, microsecond: {500, 3})
~N[2028-12-19 01:21:11.000]
iex> now |> NaiveDateTime.shift(year: 4, day: 3, hour: 2, microsecond: {500, 2})
~N[2028-12-19 01:21:11.00]
iex> now |> NaiveDateTime.shift(year: 4, day: 3, hour: 2, microsecond: {500, 1})
~N[2028-12-19 01:21:11.0]
iex> now |> NaiveDateTime.shift(year: 4, day: 3, hour: 2, microsecond: {500, 0})
~N[2028-12-19 01:21:11]
0指定や逆算もできるようです。
iex> now |> NaiveDateTime.shift(day: 0)
~N[2024-12-15 23:21:11]
iex> now |> NaiveDateTime.shift(day: -2)
~N[2024-12-13 23:21:11]
解説にもあるように、shift/2を2回実施するのと、shift/2で2つ分を一気にずらるのとでは、進み方が違います。
ただ、これについては、影響を受けるのは、「月末+年または月のshift」という気がしています。
影響を受けない場合(どちらも変わらない)
iex> now |> NaiveDateTime.shift(month: 1) |> NaiveDateTime.shift(month: 1)
~N[2025-02-15 23:21:11]
iex> now |> NaiveDateTime.shift(month: 2)
~N[2025-02-15 23:21:11]
影響を受ける場合
iex> ~N[2024-01-31 23:21:11] |> NaiveDateTime.shift(month: 1) |> NaiveDateTime.shift(month: 1) |> NaiveDateTime.shift(month: 1)
~N[2024-04-29 23:21:11]
iex> ~N[2024-01-31 23:21:11] |> NaiveDateTime.shift(month: 3)
~N[2024-04-30 23:21:11]
iex> ~N[2024-02-29 23:21:11] |> NaiveDateTime.shift(year: 1) |> NaiveDateTime.shift(year: 1) |> NaiveDateTime.shift(year: 1) |> NaiveDateTime.shift(year: 1)
~N[2028-02-28 23:21:11]
iex> ~N[2024-02-29 23:21:11] |> NaiveDateTime.shift(year: 4)
~N[2028-02-29 23:21:11]
タイムゾーン周りの設定もあるようだけど、そこはまたの機会に。