Adding formatting options to Date/Time lib (elm-cldr)

I would like to expand my locale-aware date/time formatting library (elm-cldr) to support the more granular formatting options that would be required for it to support more use cases, e.g. Fluent.

My current plan is to add a new module Cldr.Format.Options:

module Cldr.Format.Options exposing (..)

type alias DateTimeOptions =
    { era : Maybe TextOption
    , year : Maybe NumberOption
    , month : Maybe NumberOrTextOption
    , day : Maybe NumberOption
    , weekday : Maybe TextOption
    , period : Maybe TextOption
    , hour : Maybe NumberOption
    , minute : Maybe NumberOption
    , second : Maybe NumberOption
    , fractionalSecondDigits : Maybe FractionalDigits
    , zone : Maybe NameOption
    , hour12 : Maybe HourType
    }

defaultDateTimeOptions : DateTimeOptions

type alias DateOptions = 
    { era : Maybe TextOption
    , year : Maybe NumberOption
    , month : Maybe NumberOrTextOption
    , day : Maybe NumberOption
    , weekday : Maybe TextOption
    }

defaultDateOptions : DateOptions 

type TextOption
    = Short
    | Long
    | Narrow

type NumberOption
    = Numeric
    | TwoDigit

type NumberOrTextOption
    = Text TextOption
    | Number NumberOption

type FractionalDigits
    = One
    | Two
    | Three

type NameOption
    = ShortName
    | LongName

type HourType
    = Hour12
    | Hour24

The FormatType in Cldr.Format.DateTime module would then be updated:

module Cldr.Format.DateTime exposing (..)

type FormatType 
    = DateOnly Length 
    | TimeOnly Length 
    | DateAndTime { date : Length, time : Length }
    | WithOptions DateTimeOptions

The Cldr.Format.Date module would also gain a FormatType and the format function would change signature to use it:

module Cldr.Format.Date exposing (..)

type FormatType 
    = WithLength Length 
    | WithOptions DateOptions

format : FormatType -> Locale -> Date -> String 

I am interested in feedback generally, but specifically in thoughts about these questions:

  • Should the Cldr.Format.Options module have setX helper functions, e.g. setMonthNumber : NumberOption -> DatePlusOptions a -> DatePlusOptions a?
  • Is the Cldr.Format.Date.format refactor ok? It would turn format Short myLocale date into format (WithLength Short) myLocale date. The alternative would be to add a second format function, like formatWithOptions : DateOptions -> Locale -> Date -> String, which is less consistent with Cldr.Format.DateTime.

Many thanks to everyone who has already provided feedback and interest in this project!

1 Like

Here is an example of using the DateTimeOptions with and without the helper functions (in a response to keep the main post concise):

  • year, month, day:
{ defaultDateTimeOptions 
    | year = Just Numeric
    , month = Just (Text Short)
    , day = Just Numeric 
}
defaultDateTimeOptions 
    |> setYear Numeric 
    |> setMonthNumber Short 
    |> setDay Numeric

As you can see, both versions are about equally verbose. The advantage of the helper functions is that you can avoid writing Just over and over, and can choose the correct Month helper function. The disadvantage is bloat in the API.

The helper functions might also benefit from using phantom builder pattern to prevent someone from doing

dateTime
    |> setYear Numeric
    |> setDay Numeric
    |> setYear Short

This is really exciting to see though! Thank you for all of your hard work on it.

1 Like

@wolfadex Thank you for pointing me to the phantom builder pattern! I was already planning on having the helper functions use extensible record types to work with both the DateTimeOptions and DateOptions when appropriate. It looks to me like this could be combined with the phantom builder pattern something like this:

module Cldr.Format.Options exposing (Options, ...)

type Options a b = Options b 

type alias DateTimeOptions a = 
    Options a { year : ... , hour : ..., ... }

type alias DateOptions a = 
    Options a { year : ..., ... }

setYear : NumberOption -> Option {a | yearNotSetYet : () } {b | year : ... } -> Option a {b | year : ... }

If I understand correctly, this would allow for these to work:

initDateTimeOptions |> setMonthText Short |> setDay Numeric |> setHour Numeric 

initDateOptions |> setMonthText Short |> setDay Numeric

but these would all throw compiler errors:

initDateTimeOptions |> setYear Numeric |> setYear TwoDigit

initDateOptions |> setHour Numeric 

This would make the helper function API nice. The only way that people would be able to use the {defaultRecord | fields = updatedWhenNeeded } pattern is if there is a separate function that converts that record into the tagged version.

If there was a helper function based API like above, would anyone still want to be able to use the record update syntax pattern?

  • Yes, I want a record update syntax option
  • No, the helper function pipeline is always good enough

0 voters

3 Likes

I have written up a proposed API and added basic documentation to it.

This API changes the format functions as discussed above, and adds Cldr.Format.Options and Cldr.Format.OptionsBuilder. The options themselves are record types with default records available, so people can use the record update syntax if they want. There is also the OptionsBuilder module for those who prefer to stick with the helper functions. Both options are fully type-safe; people can use whichever they find most convenient.

What do you think of this API?

  • I would enjoy using this API if it were released like this. Ship it.
  • It is mostly good, but I have constructive criticism.
  • I see a major problem, which I will explain.
  • Other. My thoughts cannot be contained by your poll!

0 voters

1 Like

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.