Civil.Date

A date on the Gregorian calendar, with no time of day and no time zone.

2024-02-29 is a date. It is not a moment in time: it began at different instants in Auckland and in Denver, and asking when it "was" has no answer until someone names a time zone. It is a position on the calendar, and that is useful on its own: a birthday, an invoice date, the day a file is filed under.

Every Date that exists is a real date. fromParts, fromRataDie and fromString are the only ways to make one, and all of them check the calendar. 2023-02-29 never becomes a value, so code that receives a Date never has to check it.

Range

Year 1 through year 9999, which is the range RFC 3339 allows and therefore the range TOML allows. Year zero and negative years are not dates in this module. Dates before 1582 are computed as if the Gregorian calendar had always been in use, which is what RFC 3339 specifies.

type Date

A date on the Gregorian calendar.

Two Dates are == exactly when they are the same day.

Creating

fromParts : { year : Int, month : Int, day : Int } -> Maybe Date

A date from a year, a month numbered 1 to 12, and a day numbered from 1.

Date.fromParts { year = 2024, month = 2, day = 29 }
    |> Maybe.map Date.toString
--> Just "2024-02-29"

Nothing if the year is outside 1 to 9999, the month outside 1 to 12, or the day outside the length of that month in that year:

Date.fromParts { year = 2100, month = 2, day = 29 }
--> Nothing
fromRataDie : Int -> Maybe Date

A date from its Rata Die day number. Rata Die is a plain count of days: 0001-01-01 is day 1, and each later day is one more.

Date.fromRataDie 738945 |> Maybe.map Date.toString
--> Just "2024-02-29"

Nothing outside 1 through 3652059, which is 9999-12-31.

Reading

year : Date -> Int

The year, 1 to 9999.

month : Date -> Int

The month, 1 to 12.

day : Date -> Int

The day of the month, counting from 1.

monthOf : Date -> Month

The month as the Time.Month type from Gren's core library, for code that expects that type.

Date.fromString "2024-02-29" |> Maybe.map Date.monthOf
--> Just Time.Feb
weekday : Date -> Weekday

Which day of the week this date fell on.

Date.fromString "2024-02-29" |> Maybe.map Date.weekday
--> Just Time.Thu
ordinalDay : Date -> Int

The day of the year, 1 to 366.

Date.fromString "2024-03-01" |> Maybe.map Date.ordinalDay
--> Just 61
toRataDie : Date -> Int

The Rata Die day number: 0001-01-01 is day 1, and each later day is one more. Use it to compare dates or to do arithmetic on them; fromRataDie turns the result back into a date.

Date.fromString "0001-01-01" |> Maybe.map Date.toRataDie
--> Just 1

The calendar

isLeapYear : Int -> Bool

Whether a year has a 29th of February.

Date.isLeapYear 2000
--> True

Date.isLeapYear 2100
--> False

A year is a leap year when it is divisible by 4, unless it is also divisible by 100, unless it is also divisible by 400. So 2000 was a leap year and 2100 will not be.

daysInMonth : { year : Int, month : Int } -> Int

How many days a month has in a given year.

Date.daysInMonth { year = 2023, month = 2 }
--> 28

Zero for a month outside 1 to 12, so a check of the day against this value also rejects a bad month.

Arithmetic

addDays : Int -> Date -> Maybe Date

Move a date forward, or backward with a negative number.

Date.fromString "2024-02-28"
    |> Maybe.andThen (Date.addDays 1)
    |> Maybe.map Date.toString
--> Just "2024-02-29"

Nothing if the result would fall outside years 1 to 9999.

difference : Date -> Date -> Int

How many days from the first date to the second. Negative if the second is earlier.

Maybe.map2 Date.difference
    (Date.fromString "2024-02-28")
    (Date.fromString "2024-03-01")
--> Just 2

Strings

fromString : String -> Maybe Date

Read a YYYY-MM-DD date.

Date.fromString "1979-05-27" |> Maybe.map Date.ordinalDay
--> Just 147

The whole string has to be the date, each field has to have exactly the number of digits shown, and the date has to exist on the calendar. All of these are Nothing:

Date.fromString "1979-5-27"     --> Nothing
Date.fromString "01979-05-27"   --> Nothing
Date.fromString "1979-05-27T00" --> Nothing
Date.fromString "1979-02-30"    --> Nothing
toString : Date -> String

Write a YYYY-MM-DD date, zero padded.

Date.fromParts { year = 7, month = 1, day = 2 }
    |> Maybe.map Date.toString
--> Just "0007-01-02"