Purescript-datetime library newbie help

I’m learning purescript coming from typescript. I’m trying to use the puresecript-datetime library. I’m trying to figure out how to instantiate a Date from Data.Date.

I try this:.

newDate :: Date
newDate = exactDate 2000 June 14

The type constructor, exactDate’s type signature is:

exactDate :: Year -> Month -> Day -> Maybe Date

So, the first problem I have is that the integer 2000 is not a Year type. So, I look at how to construct the Year type. https://pursuit.purescript.org/packages/purescript-datetime/3.4.1/docs/Data.Date.Component

newtype Year

A year component for a date.

The constructor is private as the Year type is bounded to the range -271820 to 275759, inclusive. The toEnum function can be used to safely acquire a year value from an integer.

Since the Year type is private, it seems like I cannot construct the components to compose this Date object.

How do I properly use this library? What obvious things am I missing?

As you correctly note, you need to construct a Year, Month, and Day to use exactDate. These types can’t be constructed directly from numbers, because then you would be able to construct invalid values. The docstring you copy-pasted says:

The toEnum function can be used to safely acquire a year value from an integer.

You can search the toEnum function: https://pursuit.purescript.org/search?q=toEnum

toEnum :: forall a. BoundedEnum a => Int -> Maybe a

This means that given an Int, you get a Maybe Year, which is a year or nothing. The same toEnum function can in fact be used to construct also a Day and a Month, because the BoundedEnum instance is also listed in the docs of Day and Month. The final Date construction from integers could look something like this:

makeDate :: Int -> Int -> Int -> Maybe Date
makeDate year month day = case toEnum year of
  Nothing -> Nothing
  Just y -> case toEnum month of
    Nothing -> Nothing
    Just m -> case toEnum day of
      Nothing -> Nothing
      Just d -> exactDate y m d

I didn’t need to construct a Month from a number, I could have used a month constructor instead.

This function can be used in your code, again, using pattern-matching to isolate the construction failure:

case makeDate 2021 1 5 of
  Nothing -> log "Provided numbers don't form a valid date."
  Just date -> log ("Success. The date is: " <> show date)

The code for makeDate is very repetitive and nested, so there are some ways to make it shorter.
The problem is that each step may fail, and we handle each failure separately. Ideally, you would handle the construction failure only once (when pattern matching on the resulting Maybe Data), and short-circuit all the Nothings which could be created on the way to return Nothing immediately.

The do-notation is a convenient way to do just that:

makeDate :: Int -> Int -> Int -> Maybe Date
makeDate year month day = do
  y <- toEnum year
  m <- toEnum month
  d <- toEnum day
  exactDate y m d

Or you can use applicative composition operators. This takes some practice to be comfortable with:

makeDate :: Int -> Int -> Int -> Maybe Date
makeDate year month day =
  join $ exactDate <$> toEnum year <*> toEnum month <*> toEnum day

or the same thing, using slightly different combinators:

makeDate :: Int -> Int -> Int -> Maybe Date
makeDate year month day =
  join $ lift3 exactDate (toEnum year) (toEnum month) (toEnum day)

To sum up, in Purescript, all failures must be handled. You normally don’t have functions which would throw an exception or create illegal state when the input is bad. Failures are instead indicated by returning a Nothing, or some other “error” value. Working with these comfortably requires some extra plumbing, such as the do-notation.

EDIT: to work with Javascript dates, you can use the JSDate type: https://pursuit.purescript.org/packages/purescript-js-date/7.0.0/docs/Data.JSDate#t:JSDate . JSDate can be converted to Date or DateTime using the provided functions.

2 Likes