はじめに
先日、Elm v0.19 がリリースされました。公式ライブラリのリポジトリが elm-lang
から elm
に変更され、その中身も大きく再構成されています。
本記事では、これらの変更のうち特に時刻や日付の扱いに関する部分について、新しい API の使い方を含めて簡単に解説します。
v0.18 における時刻の扱い
v0.18 では、時刻を扱う機能は標準パッケージ elm-lang/core
の中で提供されていました。時刻を扱う Time
モジュールと日付を扱う Date
モジュールで、それぞれデータ型や関数が定義されているのが特徴です。
なお、旧バージョンのライブラリは現在 Elm Packages の検索にはヒットしない ので、中身を確認するためには直接 URL にアクセスする必要があります。
旧 Time モジュール
時刻を扱う Time
型を提供します。Time
型の実体は Float
型のエイリアスで、Unix Epoch からの経過ミリ秒数を表します。
メインの関数は現在時刻を取得する now
と指定した時間間隔で Msg を送出する every
です。その他、every
と組み合わせて使用する単位として second : Time
や minute : Time
が定義されており、例えば毎秒何かを行う Subscription は次のように書けます。
type Msg = DoSomethingAt Time.Time subscriptions : Model -> Sub Msg subscriptions _ = Time.every Time.second DoSomethingAt
旧 Date モジュール
日付を扱う Date
型を提供します。また Time
型との変換用の関数もこちらで定義されています。
その他、紛らわしいですが Date
モジュールでも now
関数がエクスポートされており、こちらは現在の日付を取得します。
特徴的なのは、Time
型と Date
型の変換においてタイムゾーンを指定する機能がないことです。したがって API 定義上は
toTime : Date -> Time fromTime : Time -> Date
の変換は純粋な関数に見えますが、実際にはシステムのタイムゾーンに依存する副作用を持っていることになります。
v0.19 における時刻の扱い
さて、今回新しくなったバージョンでは、旧 Time
と Date
に相当する機能が再編成されてひとつのモジュール Time
になり、さらに別ライブラリ elm/time
として切り出されました。
新 Time モジュール
大きな変更点は、Unix 時間を表す Posix
型に加えて、タイムゾーンを表す Zone
型が陽に導入されたことです。
現在時刻を取得するには今まで通り now
で、現在のタイムゾーンを取得するには here
を使用します。例えば初期化の際、両者を同時に取得する Cmd
は以下のように書けます。
type Msg = SetSystemTime (Time.Zone, Time,Posix) setSystemTime : Cmd Msg setSystemTime = Task.perform SetSystemTime <| Task.map2 Tuple.pair Time.here Time.now
旧 Date
モジュールにあった日付への変換も Time
の中にまとめられました。Unix 時間が同じでも実際の日付はタイムゾーンに依存するため、変換には Zone
型が必要になっているのが分かります。新しい変換関数名には toXXX
で統一されており、Day
型は Weekday
型に、dayOfWeek
は toWeekday
に変更されました。
toYear : Zone -> Posix -> Int toMonth : Zone -> Posix -> Month toWeekday : Zone -> Posix -> Weekday toHour : Zone -> Posix -> Int toMinute : Zone -> Posix -> Int toSecond : Zone -> Posix -> Int toMillis : Zone -> Posix -> Int
また、旧 Time
モジュールにあった時間の単位 minute
や second
は外されました。ミリ秒単位で直接指定する必要があります。
サンプル:デジタル時計
以上をまとめると、Elm v0.19 対応の簡単なデジタル時計は次のように実装することができます。
- 初期化時に
setSystemTime
で現在のタイムゾーンと時刻を取得 - それ以後 1000 ミリ秒ごとに
setCurrentTime
で現在時刻を更新 - 表示の際は
toHour
とtoMinute
でタイムゾーン依存の時刻に変換
という流れになっています。
module Main exposing (main) import Browser import Html exposing (..) import Task import Time main : Program () Model Msg main = Browser.element { init = init , update = update , view = view , subscriptions = subscriptions } type alias Model = { zone : Time.Zone , posix : Time.Posix } type Msg = SetSystemTime ( Time.Zone, Time.Posix ) | SetCurrentTime Time.Posix init : () -> ( Model, Cmd Msg ) init _ = ( { zone = Time.utc, posix = Time.millisToPosix 0 }, setSystemTime ) update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of SetSystemTime ( zone, time ) -> ( { zone = zone, posix = time }, Cmd.none ) SetCurrentTime time -> ( { model | posix = time }, Cmd.none ) view : Model -> Html Msg view model = let h = Time.toHour model.zone model.posix m = Time.toMinute model.zone model.posix in div [] [ text <| String.fromInt h ++ ":" ++ String.fromInt m ] setSystemTime : Cmd Msg setSystemTime = Task.perform SetSystemTime <| Task.map2 Tuple.pair Time.here Time.now subscriptions : Model -> Sub Msg subscriptions _ = Time.every 1000 SetCurrentTime
Time.Extra モジュール
ところで、実際に時刻を扱うアプリを書いてみると、elm/time
はかなり非力であることがわかります。特に以下のようなケースは問題になりそうです。
- 時刻 + タイムゾーンから Unix 時間に変換できない(例:特定の日付と現在時刻を比較したい)
- 時刻の和や差が取れない(例:ちょうど 1 か月後の日付が欲しい)
- 時刻を丸めることができない(例:次に 00 秒になるタイミングで Msg を発生させたい)
このような問題を解決するために、justinmimbs/time-extra
が使用できます。
Time.Extra
モジュールでは旧 Date
型に代わるものとして Parts
型を定義しており、Zone
型を組み合わせることで各タイムゾーンにおけるその時刻の Unix 時間を得ることができます。
partsToPosix : Zone -> Parts -> Posix -- UTC における 2018/09/26 11:23.45.00 time1 : Posix time1 = partsToPosix utc <| Parts 2018 Sep 26 11 23 45 0
また、旧 Time
モジュールの minute
や second
や代わる時間単位として Interval
型を定義しており、これを使って「1 か月後の日付」や「次に 00 秒ちょうどになる時刻」が取得できるようになっています。
add : Interval -> Int -> Zone -> Posix -> Posix ceiling : Interval -> Zone -> Posix -> Posix -- UTC における 2018/09/26 11:23.45.00 の 1 か月後 time2 : Posix time2 = add Month 1 utc <| partsToPosix utc <| Parts 2018 Sep 26 11 23 45 0 -- UTC における 2018/09/26 11:23.45.00 以降、最初に 00 秒になる瞬間 time3 : Posix time3 = ceiling Minute utc <| partsToPosix utc <| Parts 2018 Sep 26 11 23 45 0
まとめ
本記事では Elm v0.19 で刷新された elm/time
について、旧バージョンとの違いや使い方について簡単に解説しました。
- 時刻パッケージは
elm-lang/core
からelm/time
に移動 - 原則
Posix
を操作し、通常の時刻表示に変換した時はZone
と合わせる - 時刻を操作するには
justinmimbs/time-extra
ライブラリが使える
ところで、今回紹介した新しい API を使ってちょっと面白いサンプルアプリを作ってみたので、GIF アニメにしたものを貼っておきます。
元ネタは Humans since 1982 の作品 ClockClock 24 です。ソースコードは以下にあるので elm/time
の使い方の参考に。
ちなみにこのサンプル、実際には elm/time
ではなくそれ以外の部分の実装のほうが大変だったのですが、それはまた別の話。