Standard library - calendar

module calendar
  // Types
  type t_date
  type t_dateTime
  type t_timestamp
  type t = t_date | t_dateTime | t_timestamp
  type t_withTime = t_dateTime | t_timestamp
  type t_time

  case failIfBad
  case undefIfBad
  case normIfBad
  type errorMode = failIfBad | undefIfBad | normIfBad

  // Constructors
  def makeDate : errorMode -> number -> number -> number -> t_date
  def importDate : errorMode -> data -> t_date
  def makeDateTime : errorMode -> number -> number -> number ->
                     number -> number -> number -> t_dateTime
  def importDateTime : errorMode -> data -> t_dateTime
  def makeTimestamp : errorMode -> number -> number -> number ->
                       number -> number -> number -> number -> string ->
                       t_timestamp
  def importTimestamp : errorMode -> data -> t_timestamp
  def reprTimestamp : number -> string -> t_timestamp
  def makeTime : number -> number -> number -> t_time
  def importTime : data -> t_time

  // Projections
  def year : t -> number
  def month : t -> number
  def day : t -> number
  def hour : (t_withTime | t_time) -> number
  def minute : (t_withTime | t_time) -> number
  def second : (t_withTime | t_time) -> number
  def tzName : t_timestamp -> string
  def tzOffset : t_timestamp -> number

  // Conversions
  def toDate : t -> t_date
  def toDateTime : t_withTime -> t_dateTime
  def toTimestamp : string -> t -> t_timestamp    // since PR#769
  def toString : t -> string
  def toDBString : t_timestamp -> string          // since PR#1284
  def parse : string -> t
  def parseResult : string -> result(string, t)
  def export : (t | t_time) -> data                          // since PR#769
  def format : string -> (t | t_time) -> string              // public since #1856
  def reprTime : t_time -> data
  def toTime : t_withTime -> t_time

  // Updaters
  def setDay : number -> t -> t
  def setMonth : number -> t -> t
  def setYear : number -> t -> t
  def setHour : number -> (t_withTime | t_time) -> (t_withTime | t_time)
  def setMinute : number -> (t_withTime | t_time) -> (t_withTime | t_time)
  def setSecond : number -> (t_withTime | t_time) -> (t_withTime | t_time)
  def setTimezone : string -> t_timestamp -> t_timestamp

  // Addition
  def addDays : number -> t -> t
  def addMonths : number -> t -> t
  def addYears : number -> t -> t
  def addHours : number -> (t_withTime | t_time) -> (t_withTime | t_time)
  def addMinutes : number -> (t_withTime | t_time) -> (t_withTime | t_time)
  def addSeconds : number -> (t_withTime | t_time) -> (t_withTime | t_time)
  def addTimes : t_time -> t_time -> [t_time, bool]

  // Subtraction (since PR#1011)
  def subDays : number -> t -> t
  def subMonths : number -> t -> t
  def subYears : number -> t -> t
  def subHours : number -> t_withTime -> t_withTime
  def subMinutes : number -> t_withTime -> t_withTime
  def subSeconds : number -> t_withTime -> t_withTime
  def subTimes : t_time -> t_time -> [t_time, bool] // since PR #1856

  // Time difference
  def diffDays : t -> t -> number
  def diffMonths : t -> t -> number
  def diffYears : t -> t -> number
  def diffHours : (t_withTime | t_time) -> (t_withTime | t_time) -> number
  def diffMinutes : (t_withTime | t_time) -> (t_withTime | t_time) -> number
  def diffSeconds : (t_withTime | t_time) -> (t_withTime | t_time) -> number
  def diffTimes : t_time -> t_time -> [t_time, bool]

  // Helper functions and derived properties
  def isLeapYear : number -> bool
  def numDaysOfYear : number -> number
  def numDaysOfMonth : number -> number -> number
  def firstDayOfMonth : t -> t
  def lastDayOfMonth : t -> t
  def firstDayOfYear : t -> t
  def lastDayOfYear : t -> t
  def dayOfYear : t -> number
  def epochDate : t_date
  def epochDateTime : t_dateTime
  def epoch : t_timestamp
  def daysSinceEpoch : t -> number
  def secondsSinceEpoch : t_withTime -> number
  def now : null -> t_timestamp
  def localNow : null -> t_timestamp     # since PR#1011

  // looping over days
  def dayRange : t -> t -> t_date*

  // periods
  type t_period
  type t_periodInfo = data
  def makePeriod : number -> number -> t_date -> t_period
  def periodDayOfDate : t_period -> t -> number
  def periodInfoOfDate : t_period -> t -> t_periodInfo
  def dateOfPeriodInfo : t_period -> t_periodInfo -> t_date
  def firstDayOfPeriod : t_period -> t_periodInfo -> t_date
  def lastDayOfPeriod : t_period -> t_periodInfo -> t_date
  def succPeriodInfo : t_period -> t_periodInfo -> t_periodInfo
  def periodRange : t_period -> t -> t -> t_periodInfo*

  // weeks
  case weekUS
  case weekISO
  type t_weekKind = weekUS | weekISO
  type t_weekInfo = data
  def weekSunSat : t_period
  def weekMonSun : t_period
  def weekPeriod : t_weekKind -> t_period
  def weekDay : t_weekKind -> t -> number
  def weekInfoOfPeriodInfoISO : t_periodInfo -> t_weekInfo
  def weekInfoOfDate : t_weekKind -> t -> t_weekInfo
  def dateOfWeekInfo : t_weekKind -> t_weekInfo -> t_date
  def weeksOfYear : t_weekKind -> number -> number
  def diffWeeks : t_weekKind -> t -> t -> number
  def weekRangeISO : t -> t -> t_weekInfo*
  def weekTable : t_weekKind -> t -> t -> array(array(t_date)).   // since PR#769

  // quarters
  type t_quarter
  type t_quarterInfo = data
  def makeQuarter : number -> t_quarter
  def naturalQuarter : t_quarter
  def quarterOfDate : t_quarter -> t -> number
  def quarterInfoOfDate : t_quarter -> t -> t_quarterInfo
  def firstDayOfQuarter : t_quarter -> t_quarterInfo -> t_date
  def lastDayOfQuarter : t_quarter -> t_quarterInfo -> t_date
  def dateOfQuarterInfo : t_quarter -> t_quarterInfo -> t_date
  def succQuarterInfo : t_quarterInfo -> t_quarterInfo
  def numDaysOfQuarter : t_quarter -> t_quarterInfo -> number
  def diffQuarters : t_quarter -> t -> t -> number
  def quarterRange : t_quarter -> t -> t -> t_quarterInfo*
module end

Types

  type t_date
  type t_dateTime
  type t_timestamp
  type t = t_date | t_dateTime | t_timestamp
  type t_withTime = t_dateTime | t_timestamp

  case failIfBad
  case undefIfBad
  case normIfBad
  type errorMode = failIfBad | undefIfBad | normIfBad

We provide three different date types:

There is also t which can be any of the three, and t_withTime meaning the two types having time fields.

Only real dates can be represented, e.g. there is no day 2019/2/29 and because of this you cannot represent this bad day as t_date.

While we take leap days into account, leap seconds are not supported. Seconds are only allowed in the range 0 to 59 (and not 60, as it would be needed for leap seconds).

The constructors allow it to specify how to deal with bad days:

Comparisons

You can compare date values with the normal comparisons ==, <, <= etc. You can compare a t_date with a t_date, a t_dateTime with a t_dateTime and a t_timestamp with a t_timestamp. It is not meaningful to do mixed comparisons, e.g. a date with a timestamp.

Regarding timestamps, the comparison behaves as if the corresponding second since the epoch was compared.

Constructors

  def makeDate : errorMode -> number -> number -> number -> t_date
  def importDate : errorMode -> data -> t_date
  def makeDateTime : errorMode -> number -> number -> number ->
                     number -> number -> number -> t_dateTime
  def importDateTime : errorMode -> data -> t_dateTime
  def makeTimestamp : errorMode -> number -> number -> number ->
                       number -> number -> number -> number -> string ->
                       t_timestamp
  def importTimestamp : errorMode -> data -> t_timestamp
  def reprTimestamp : number -> string -> t_timestamp
  def makeTime : number -> number -> number -> t_time

The make functions create date / time values from individual fields:

Note that makeTimestamp gives the tzOffsetprecedence over the tzName when interpreting the timestamp relative to a time zone. If you want to give the time zone by name only, do it this way:

let ts =
  calendar.makeDateTime(emode, year, month, day, hour, minute, second)
  |> calendar.toTimestamp(tzName)

The import functions create date values from objects:

Timezones are given by both the timezone offset in minutes and a timezone name. While the timezone name is usually a fixed string like “Europe/Amsterdam” designating a geo-political region, the offset is the actual offset used at the particular day. The offset varies over the year because of daylight savings.

When calculating with timestamps, the timezone name remains the same, and the offset is adapted to the final day, e.g.