I first posted this idea half a year ago, finally got around to implementing it.
It’s common to have several different units of time in your app. You might have some timestamps that are in milliseconds or seconds, datepickers that are down to the day, and others that only let you select the month. If you’re doing scheduling you might also have hours and minutes.
You could use Time.Posix for everything, but that can lead to bugs if you for example store a date as “The first millisecond of the day”, and then when displaying it, you use the local timezone, which causes the date to jump forwards or backwards one day.
What we currently do in our app is use both Time.Posix and Date (justinmimbs/date) types. This works fairly well, although there is still the issue that there is no differentiation in type signatures between for example year+month and year+month+day. Also, packages tend to only work with one of them, for example ryannhg/date-format only works with Time.Posix, not Dates.
So here’s my attempt to solve this, using a single base type, parameterized with phantom record types, similar to how rtfeldman/elm-css works.
Internally everything is stored as Time.Posix. This saves on memory compared to a big record of Ints which was my first idea, and also lets me reuse all the functions from elm/time and justinmimbs/time-extra, so that I don’t have to worry about getting leap years etc wrong.
It’s still a prototype and needs refinement, testing and docs, so I haven’t published it yet. Here’s the repo, you can run it with elm reactor to play around.
I would very much like to get some feedback, if you see some issue with this approach.
Examples
This gives a type error, because you’re trying to display the day when it’s undefined:
DateTime.withYear 1989
|> DateTime.withMonth Time.Aug
|> Format.format
[ Format.year
, Format.text "-"
, Format.monthNumber
, Format.text "-"
, Format.day
]
This works!
DateTime.withYear 1989
|> DateTime.withMonth Time.Aug
|> DateTime.withDay 30
|> Format.format
[ Format.year
, Format.text "-"
, Format.monthNumber
, Format.text "-"
, Format.day
]
This gives a type error, because you’re trying to display the hour, but you haven’t specified the time zone:
DateTime.withYear 1989
|> DateTime.withMonth Time.Aug
|> DateTime.withDay 30
|> DateTime.withHour 13
|> Format.format
[ Format.year
, Format.text "-"
, Format.monthNumber
, Format.text "-"
, Format.day
, Format.text " "
, Format.hour
]
This works!
DateTime.withYear 1989
|> DateTime.withMonth Time.Aug
|> DateTime.withDay 30
|> DateTime.withHour 13
|> DateTime.withZone Time.utc
|> Format.format
[ Format.year
, Format.text "-"
, Format.monthNumber
, Format.text "-"
, Format.day
, Format.text " "
, Format.hour
]
This gives a type error, because dates shouldn’t care about zones:
DateTime.withYear 1989
|> DateTime.withMonth Time.Aug
|> DateTime.withDay 30
|> DateTime.withZone Time.utc
This gives a type error, because you’re trying to get the difference in months, but one of them doesn’t have a month:
DateTime.diffMonths
(DateTime.withYear 1989 |> DateTime.withMonth Time.Aug)
(DateTime.withYear 2020)
This works!
DateTime.diffYears
(DateTime.withYear 1989 |> DateTime.withMonth Time.Aug)
(DateTime.withYear 2020)