The Oddler
The Oddler

Reputation: 6708

Elm Current Date

In elm I have a model that includes a currentDate, it's a string representing the date. I update it with a signal:

Signal.map (SetCurrentDate << timeToDateString) (Time.every Time.second)

SetCurrentDateis an action that updates the model (SetCurrentDate date -> { model | currentDate <- date}), and timeToDateString converts from time to a string like "yyyy-mm-dd".

However, there are two problems with this:

Is there a way to fix this? So the currentDate is set at the start of the app (without the one second delay), and that it's only updated one a day?

Extra findings:

If I change the signal to Signal.dropRepeats <| Signal.map (SetCurrentDate << timeToDateString) (Time.every Time.second) (so I drop the repeats), the signal is only fired when the day changes, and not also once on the start of the app.

Edit: If there is a better way of knowing the currentDate in an Elm application, I would love to hear it. My Googling gave nothing.

Thanks!

Upvotes: 3

Views: 1753

Answers (2)

Raine Revere
Raine Revere

Reputation: 33637

Apanatshka's answer (and others) made sense, but as a beginner to Elm I had trouble applying the explanation to actual code. After some fuddling around, here is a minimal amount of code needed to render an auto-updating date (using Elm v0.16.0):

import Signal
import Html exposing (Html, text)
import Time exposing (Time, every)
import Date exposing (Date, Month, fromTime, year, month, day, hour, minute, second)

-- not used by foldp (https://stackoverflow.com/a/34095298/480608)
startTime = 0

type Action = Update Date

type alias Model = Date

showDate : Date -> String
showDate date = toString (month date) ++ " " ++
  toString (day date) ++ ", " ++
  toString (year date) ++ "  " ++
  toString (hour date) ++ ":" ++
  toString (minute date) ++ ":" ++
  toString (second date)

update : Action -> Model -> Model
update (Update date) _ = date

model : Signal Model
model =
  Signal.foldp update (fromTime startTime) clock

timeToAction : Time -> Action
timeToAction time = Update <| fromTime time

clock : Signal Action
clock =
  Signal.map timeToAction <| every Time.second

main : Signal Html
main =
  Signal.map view model

view : Model -> Html
view model =
  text <| showDate model

Here's a slightly more complex example that merges the clock into other signals. No idea how well this conforms to elm best practices, but it works, and it may serve as a helpful learning tool as it did for me.

import Signal
import Html exposing (..)
import Html.Events exposing (..)
import Keyboard
import Char
import Time exposing (Time)
import Date exposing (Date, Month, fromTime, year, month, day, hour, minute, second)

startTime = 0

type Action =
  NoOp
  | Increment
  | Decrement
  | Update Time

type alias Model = {
  count: Int,
  time: Time
}

showDate : Date -> String
showDate date = toString (month date) ++ " " ++
  toString (day date) ++ ", " ++
  toString (year date) ++ "  " ++
  toString (hour date) ++ ":" ++
  toString (minute date) ++ ":" ++
  toString (second date)

actions : Signal.Mailbox Action
actions =
  Signal.mailbox NoOp

update : Action -> Model -> Model
update action model =
  case action of
    NoOp -> model
    Increment -> { model | count = model.count + 1 }
    Decrement -> { model | count = model.count - 1 }
    Update time -> { model | time = time }

model : Signal Model
model =
  Signal.foldp update { count = 0, time = startTime } (Signal.mergeMany [
    actions.signal,
    keyPressesToAction,
    clock
  ])

keyPressesToAction : Signal Action
keyPressesToAction =
  let
    keyCodeToAction keyCode =
      case Char.fromCode keyCode of
        '=' -> Increment
        '-' -> Decrement
        _ -> NoOp
  in
    Signal.map keyCodeToAction Keyboard.presses

timeToAction : Time -> Action
timeToAction time = Update time

clock : Signal Action
clock = Signal.map timeToAction (Time.every Time.second)

main : Signal Html
main =
  Signal.map (view actions.address) model

view : Signal.Address Action -> Model -> Html
view address model =
  div [] [
    text <| showDate <| fromTime model.time,
    div []
      [ button [ onClick address Decrement ] [ text "-" ]
      , text <| toString model.count
      , button [ onClick address Increment ] [ text "+" ]
      ]
  ]

Upvotes: 1

Apanatshka
Apanatshka

Reputation: 5958

Using Signals

Are you using StartApp or do you have your own foldp? If you are using StartApp it's best to look up the code for the start function and inline it so you have access to the foldp.

Signal.foldp doesn't do anything with the initial value of a signal. So if you use the Time.every second signal, you only get your update after a second, and only after a day if you use the date conversion and Signal.dropRepeats. You can use Signal.Extra.foldp' from the 3rd party signal-extra library* to fix that. It takes a function to create the initial state of the foldp from the initial value of the input.

*Full disclosure: I'm the author of the library

Using Tasks

There is a very useful task in the task-tutorial library called getCurrentTime. I think that can serve your needs without needing a signal that updates every second. Along with Task.sleep, you can probably get something that only checks the time once or twice a day.

Upvotes: 4

Related Questions