时间

现在我们要制作一个数字时钟。(模拟时钟将是练习!)

到目前为止,我们专注于命令。利用 HTTP 和随机示例,我们命令 Elm 立即执行特定工作,但对于时钟来说,这是一种奇怪的模式。我们始终希望知道当前时间。这就是订阅派上用场的地方!

首先,单击蓝色的“编辑”按钮,在在线编辑器中大致浏览一下代码。

import Browser
import Html exposing (..)
import Task
import Time



-- MAIN


main =
  Browser.element
    { init = init
    , view = view
    , update = update
    , subscriptions = subscriptions
    }



-- MODEL


type alias Model =
  { zone : Time.Zone
  , time : Time.Posix
  }


init : () -> (Model, Cmd Msg)
init _ =
  ( Model Time.utc (Time.millisToPosix 0)
  , Task.perform AdjustTimeZone Time.here
  )



-- UPDATE


type Msg
  = Tick Time.Posix
  | AdjustTimeZone Time.Zone



update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of
    Tick newTime ->
      ( { model | time = newTime }
      , Cmd.none
      )

    AdjustTimeZone newZone ->
      ( { model | zone = newZone }
      , Cmd.none
      )



-- SUBSCRIPTIONS


subscriptions : Model -> Sub Msg
subscriptions model =
  Time.every 1000 Tick



-- VIEW


view : Model -> Html Msg
view model =
  let
    hour   = String.fromInt (Time.toHour   model.zone model.time)
    minute = String.fromInt (Time.toMinute model.zone model.time)
    second = String.fromInt (Time.toSecond model.zone model.time)
  in
  h1 [] [ text (hour ++ ":" ++ minute ++ ":" ++ second) ]

新增内容均来自 elm/time 包。我们来看看这些部分!

Time.PosixTime.Zone

要在编程中成功处理时间,我们需要三个不同的概念

  • 人类时间 — 这是你时钟上(上午 8 点)或日历上(5 月 3 日)看到的内容。很好!但如果我下午 8 点打电话给波士顿的朋友,那么加拿大温哥华的朋友是什么时间?如果东京时上午 8 点,那么纽约难道不是同一天吗?(否!)因此,在以不断变化的政治边界和 夏令时 不一致的使用为基础的 时区 之间,基本上绝不应将人类时间存储在你的 Model 或数据库中!它仅供显示!

  • POSIX 时间 — 使用 POSIX 时间,你的居住地点或一年中的时间并不重要。它只是自某个任意时刻(1970 年)以来经过的秒数。无论你在地球上的哪个地方,POSIX 时间都是一样的。

  • 时区 — “时区”是一组数据,使你能够将 POSIX 时间转换为人类时间。这不仅仅UTC-7UTC+3!时区比一个简单的偏移量复杂得多!每次 佛罗里达州永久切换至夏令时萨摩亚从 UTC-11 切换至 UTC+13 时,某个可怜的家伙就会在 IANA 时区数据库 中添加注释。该数据库已加载到每台计算机上,在 POSIX 时间和数据库中的所有特殊情况下,我们可以确定人类时间!

因此,若向人显示时间,你必须始终知道 Time.PosixTime.Zone。就这样!所以所有“人类时间”内容都是针对 视图 函数,而不是 模型。事实上,你可以在我们 视图 中看到它

view : Model -> Html Msg
view model =
  let
    hour   = String.fromInt (Time.toHour   model.zone model.time)
    minute = String.fromInt (Time.toMinute model.zone model.time)
    second = String.fromInt (Time.toSecond model.zone model.time)
  in
  h1 [] [ text (hour ++ ":" ++ minute ++ ":" ++ second) ]

Time.toHour 函数采用 Time.ZoneTime.Posix 为我们返回一个来自 023Int,表示你的时区中 currently is 的时间。

elm/time 的自述文件中提供了更多有关在时间处理中的信息的Definitely 在使用更多时间之前阅读它!尤其是在使用计划、日历等功能时。

订阅

好的,我们该如何获取我们的 Time.Posix 呢?使用订阅

subscriptions : Model -> Sub Msg
subscriptions model =
  Time.every 1000 Tick

我们使用 Time.every 函数

every : Float -> (Time.Posix -> msg) -> Sub msg

它采用两个参数

  1. 以毫秒为单位的时间间隔。我们说 1000,表示每秒。但我们也可以说每分钟 60 * 1000 或每五分钟 5 * 60 * 1000
  2. 一个将当前时间转换成 Msg 的函数。因此,每秒,当前时间都将转换为 Tick <time>,用于我们的 更新 函数。

这是任何订阅的基本模式。你提供一些配置,并描述如何生成 Msg 值。还不错!

任务执行

获取 Time.Zone 有点棘手。我们的程序创建了一个命令,其中包含

Task.perform AdjustTimeZone Time.here

通读 Task 文档是理解该行的最佳方式。这些文档旨在实际解释新概念,我认为在这里包含该信息的较差版本会产生很多干扰。关键在于我们命令运行时无论代码在哪里运行,都要向我们提供 Time.Zone

练习

  • 添加一个用于暂停时钟的按钮,关闭 Time.every 订阅。
  • 使数字时钟看起来更好一些。可能需要添加一些 style 属性。
  • 使用 elm/svg 创建具有红色秒针的模拟时钟!

与 "" 匹配的 结果

    没有与 "" 匹配的结果