ZonedDateTime

The built-in type ZonedDateTime represents instantaneous points on the timeline to millisecond precision, represented as a DateTime together with a time zone identifier (e.g. Europe/Copenhagen) and an offset (e.g. +01:00). Values of type ZonedDateTime are used to work with global time points (see also Instant) in terms of a local calendar.

An example of a ZonedDateTime value is 2021-01-04T12:24:01.123+01:00[Europe/Copenhagen] - the 4th of January 2021 at 12:24:01.123 in the Europe/Copenhagen time zone which has an offset of 1 hour relative to GMT.

The offset in the above example is the only valid offset for that particular combination of date, time and time zone. This is the case for most date, time and time zone combinations, and for all those values the offset is a redundant component of the underlying representation. However, for certain time ranges called overlaps the offset is necessary to uniquely determine the corresponding instant. For example, the date and time 2021-10-31T02:30 is a time point which happens twice in the local calendar for time zone Europe/Copenhagen because this zone stops observing daylight saving time at 03:00 at which point clocks are set back one hour. Both time points are uniquely represented as the ZonedDateTime values 2021-10-31T02:30:00+02:00[Europe/Copenhagen] and 2021-10-31T02:30:00+01:00[Europe/Copenhagen], with the latter occurring one hour later than the former.

Just as the transition from daylight saving time to standard time results in overlaps, transitions from standard time to daylight saving time results in gaps which are sequences of local date and time pairs that never occur in the given time zone. For example, the date and time 2021-03-28T02:01 is not valid for the time zone Europe/Copenhagen because this time zone starts observing daylight saving time at 02:00 at which point clocks are advanced by one hour.

components : ZonedDateTime -> ZonedDateTime::ComponentsWithOffset

Returns the underlying representation of the ZonedDateTime. The returned data type ZonedDateTime::ComponentsWithOffset has the following definition:

module ZonedDateTime {
  type Components {
    date : Date,
    time : Time,
    zone : String
  }

  type ComponentsWithOffset : ZonedDateTime::Components {
    offset : ZoneOffset
  }
}

Note that the time zone identifier component is represented as a String. It is however restricted to the valid time zone identifiers as defined by IANA.

Examples

val a = ZonedDateTime::components
  (ZonedDateTime::fromStrict
    (Date::from 2021 December 31)
    (Time::from 2 30 0 0)
    "Europe/Copenhagen"
    (ZoneOffset::fromSeconds (60 * 60)))
//    = ZonedDateTime::ComponentsWithOffset {
//        date = Date::from 2021 December 31,
//        offset = ZoneOffset::fromSeconds 3600,
//        time = Time::from 2 30 0 0,
//        zone = "Europe/Copenhagen"
//      }

fromComponents : ZonedDateTime::Components -> ZonedDateTime

Constructs a ZonedDateTime from a date, time and zone identifier. If the resulting value falls within an overlap, then the earlier offset is chosen. If the resulting value would fall within a gap, then the time is adjusted forward by the duration of the gap to obtain a valid ZonedDateTime.

If the later offset should be preferred in overlaps, then compose with the function ZonedDateTime::withLaterOffsetAtOverlap. If the exact preferred offset is known, then use ZonedDateTime::fromComponentsWithOffset.

The function is partial and fails if the zone identifier is invalid.

Examples

// Constructs a ZonedDateTime with no special case.
// The desired value is not within an overlap nor a gap.
val a = ZonedDateTime::fromComponents
  ZonedDateTime::Components {
    date = Date::from 2021 January 1,
    time = Time::from 12 30 0 0,
    zone = "Europe/Copenhagen"
  }
// ==> ZonedDatetime representation of 2021-01-01T12:30+01:00[Europe/Copenhagen]

// Constructs a ZonedDateTime where the desired value is in a gap.
val b = ZonedDateTime::fromComponents
  ZonedDateTime::Components {
    // Clocks are advanced by one hour on 2021-03-28T02:00 in the zone Europe/Copenhagen
    date = Date::from 2021 March 28,
    // The time 2021-03T02:01 thus does not exist
    time = Time::from 2 1 0 0,
    zone = "Europe/Copenhagen"
  }
// ==> ZonedDateTime representation of 2021-03-28T03:01+02:00[Europe/Copenhagen]

// Constructs a ZonedDateTime where the desired value is in an overlap
val c = ZonedDateTime::fromComponents
  ZonedDateTime::Components {
    // Clocks are set back one hour on 2021-10-31T03:00 in the zone Europe/Copenhagen
    date = Date::from 2021 December 31,
    // The time 2021-10T02:01 thus happens twice;
    // first in the DST offset +02:00, then in +01:00 one hour later.
    time = Time::from 2 1 0 0,
    zone = "Europe/Copenhagen"
  }
// ==> ZonedDateTime representation of 2021-10-31T02:01+02:00[Europe/Copenhagen]
//     (defaults to earlier offset)

fromComponentsWithOffset : ZonedDateTime::ComponentsWithOffset -> ZonedDateTime

Constructs a ZonedDateTime from a date, time, zone identifier and offset. Unlike ZonedDateTime::fromComponents the arguments always uniquely specify an instant, also in overlaps. It is up to the caller to ensure that the given offset is valid for the given combination of date, time and time zone.

The function is partial and fails if the zone identifier is invalid or if the offset is not valid for the date, time and time zone. As a result, the function fails if the desired value is in a gap.

Examples

// Constructs a ZonedDateTime where the desired value is in an overlap
val a = ZonedDateTime::fromComponentsWithOffset
  ZonedDateTime::ComponentsWithOffset {
    // Clocks are set back one hour on 2021-10-31T03:00 in the zone Europe/Copenhagen
    date = Date::from 2021 December 31,
    // The time 2021-10T02:01 thus happens twice; first in the DST offset +02:00, then in +01:00 one hour later.
    time = Time::from 2 30 0 0,
    zone = "Europe/Copenhagen",
    offset = ZoneOffset::fromSeconds (2 * 60 * 60) // +02:00
  }
//    = ZonedDateTime::fromStrict
//      (Date::from 2021 December 31)
//      (Time::from 2 30 0 0)
//      "Europe/Copenhagen"
//      (ZoneOffset::fromSeconds 7200)

// Same as previous example, but with the later offset
val b = ZonedDateTime::fromComponentsWithOffset
  ZonedDateTime::ComponentsWithOffset {
    date = Date::from 2021 December 31,
    time = Time::from 2 30 0 0,
    zone = "Europe/Copenhagen",
    offset = ZoneOffset::fromSeconds (60 * 60) // +01:00
  }
//    = ZonedDateTime::fromStrict
//      (Date::from 2021 December 31)
//      (Time::from 2 30 0 0)
//      "Europe/Copenhagen"
//      (ZoneOffset::fromSeconds 3600)

from : Date -> Time -> String -> ZonedDateTime

Convenience wrapper of ZonedDateTime::fromComponents where the components are given as positional arguments.

The order of arguments is

  1. date

  2. time

  3. time zone identifier as string

The function is partial and fails if the time zone identifier is invalid.

fromStrict : Date -> Time -> String -> ZoneOffset -> ZonedDateTime

Examples

Convenience wrapper of ZonedDateTime::fromComponentsWithOffset where the components are given as positional arguments.

The order of arguments is

  1. date

  2. time

  3. time zone identifier as string

  4. zone offset

The function is partial and fails if the zone identifier is invalid or if the offset is not valid for the date, time and time zone.

fromInstant : Instant -> String -> ZonedDateTime

Constructs a ZonedDateTime from an instant and a time zone identifier.

The function is partial and fails if the time zone identifier is invalid.

Examples

val a = ZonedDateTime::fromInstant #2021-01-04T13:57Z# "Europe/Copenhagen"
//    = ZonedDateTime::fromStrict
//      (Date::from 2021 January 4)
//      (Time::from 14 57 0 0)
//      "Europe/Copenhagen"
//      (ZoneOffset::fromSeconds 3600)

withEarlierOffsetAtOverlap : ZonedDateTime -> ZonedDateTime

Adjusts the given ZonedDateTime to use the earlier of the two possible offsets if the value is within an overlap period. The resulting value may not represent the same instant as the input. If the value is not within an overlap or if it is already using the earliest offset, then the function behaves as the identity.

Examples

val a = ZonedDateTime::withEarlierOffsetAtOverlap
  (ZonedDateTime::fromStrict
    (Date::from 2021 December 31)
    (Time::from 2 30 0 0)
    "Europe/Copenhagen"
    (ZoneOffset::fromSeconds 3600))
//    = ZonedDateTime::fromStrict
//      (Date::from 2021 December 31)
//      (Time::from 2 30 0 0)
//      "Europe/Copenhagen"
//      (ZoneOffset::fromSeconds 7200)

withLaterOffsetAtOverlap : ZonedDateTime -> ZonedDateTime

Adjusts the given ZonedDateTime to use the later of the two possible offsets if the value is within an overlap period. The resulting value may not represent the same instant as the input. If the value is not within an overlap or if it is already using the earliest offset, then the function behaves as the identity.

Examples

val a = ZonedDateTime::withLaterOffsetAtOverlap
  (ZonedDateTime::fromStrict
    (Date::from 2021 December 31)
    (Time::from 2 30 0 0)
    "Europe/Copenhagen"
    (ZoneOffset::fromSeconds 7200))
//    = ZonedDateTime::fromStrict
//      (Date::from 2021 December 31)
//      (Time::from 2 30 0 0)
//      "Europe/Copenhagen"
//      (ZoneOffset::fromSeconds 3600)

addPeriod : Period -> ZonedDateTime -> ZonedDateTime

Adds a calendar Period to a ZonedDateTime value.

This function models the usual notion of what it means to say e.g. “X days after date D at 08:00” in daily speech in that the local time of day is retained when possible. That is, when a transition to/from DST occurs within the period that is being added, one day is not necessarily equal to 24 hours. If you want to add an absolute amount of time instead, use ZonedDateTime::addDuration.

The calendar period is first added to the underlying Date component, keeping the time, time zone and offset unchanged. If the combination of components is valid, then the resulting ZonedDateTime is returned. Otherwise they are adjusted into a valid ZonedDateTime value as follows:

  1. If the date and time is not in a gap then the current offset is used if it is valid, and otherwise the earliest valid offset is used.

  2. If the date and time is in a transition gap then the time is adjusted forward by the duration of the gap and the offset after the transition is used.

The function is partial and fails if the resulting date component is out of range.

Examples

// Add 1 day to 2021-10-30T02:59+02:00[Europe/Copenhagen].
// Yields 2021-10-31T02:59+02:00[Europe/Copenhagen] which is in an overlap.
// The input offset is still valid, so that is used without further adjustment.
// (c.f. next example `b`)
val a = ZonedDateTime::addPeriod
  (Period::from 0 0 1)
  (ZonedDateTime::from (Date::from 2021 December 30) (Time::from 2 59 0 0) "Europe/Copenhagen")
//   ==> ZonedDateTime representation of 2021-10-31T02:59+02:00[Europe/Copenhagen]

// Add 9 months to 2021-01-31T02:59+01:00[Europe/Copenhagen]
// Yields 2021-10-31T02:59+01:00[Europe/Copenhagen] which is in an overlap.
// The input offset is still valid, so that is used without further adjustment.
// (c.f. previous example `a`)
val b = ZonedDateTime::addPeriod
  (Period::from 0 9 0)
  (ZonedDateTime::from (Date::from 2021 January 31) (Time::from 2 59 0 0) "Europe/Copenhagen")
//   ==> ZonedDateTime representation of 2021-10-31T02:59+01:00[Europe/Copenhagen]

// Add 1 day to 2021-10-30T03:01+02:00[Europe/Copenhagen]
// Yields 2021-10-31T03:01+02:00[Europe/Copenhagen] which has an invalid offset.
//  Since the time is not in a gap, the offset is adjusted to the earliest valid one (+01:00).
// Note that the actual duration between the input and output values is 25 hours.
val c = ZonedDateTime::addPeriod
  (Period::from 0 0 1)
  (ZonedDateTime::from (Date::from 2021 December 30) (Time::from 3 1 0 0) "Europe/Copenhagen")
//   ==> ZonedDateTime representation of 2021-10-31T03:01+01:00[Europe/Copenhagen]

// Add 1 day to 2021-03-27T02:01+01:00[Europe/Copenhagen]
// Yields 2021-03-28T02:01+01:00[Europe/Copenhagen] which is in a gap.
// The time is therefore adjusted by adding one hour (the gap length of Europe/Copenhagen) and
// setting the offset to the DST offset (+02:00).
val d = ZonedDateTime::addPeriod
  (Period::from 0 0 1)
  (ZonedDateTime::from (Date::from 2021 March 27) (Time::from 2 1 0 0) "Europe/Copenhagen")
//   ==> ZonedDateTime representation of 2021-03-28T03:01+02:00[Europe/Copenhagen]

// Add 1 day to 2021-03-27T03:01+01:00[Europe/Copenhagen]
// Yields 2021-03-28T03:01+01:00[Europe/Copenhagen] which is not in a gap.
// The input offset (+01:00) is no longer valid, so the offset is adjusted to the
// earliest valid one (+02:00).
// Note that the actual duration between the input and output values is 23 hours.
val e = ZonedDateTime::addPeriod
  (Period::from 0 0 1)
  (ZonedDateTime::from (Date::from 2021 March 27) (Time::from 3 1 0 0) "Europe/Copenhagen")
//   ==> ZonedDateTime representation of 2021-03-28T03:01+02:00[Europe/Copenhagen]

addDuration : Duration -> ZonedDateTime -> ZonedDateTime

Adds an absolute duration to a ZonedDateTime value.

The function works by adding the given amount of time to the underlying ZonedDateTime::instant: For any ZonedDateTime value z and Duration value d, if ZonedDateTime::addDuration d z evaluates without failure to a value zd, then d equals the result of Duration::betweenInstants (ZonedDateTime::instant z) (ZonedDateTime::instant zd).

Note that if the period between the input value and the result contains a DST transition, then the local time component may change even if multiples of 24 hours are added. If you want to advance a ZonedDateTime by a number of days or months while keeping the local time component unchanged, use ZonedDateTime::addPeriod instead.

The function is partial and fails if the resulting date component is out of range.

Examples

// Add 24 hours to 2021-10-30T03:30+02:00[Europe/Copenhagen].
// Yields 2021-10-31T02:30+01:00[Europe/Copenhagen] because there is a transition from daylight saving to standard time at 2021-10-31:T03:00+02:00[Europe/Copenhagen].
val a = ZonedDateTime::addDuration
  (Duration::fromDays 1)
  (ZonedDateTime::from (Date::from 2021 December 30) (Time::from 3 30 0 0) "Europe/Copenhagen")
//   ==> ZonedDateTime representation of 2021-10-31T02:30+01:00[Europe/Copenhagen]

instant : ZonedDateTime -> Instant

Returns the underlying Instant represented by the given ZonedDateTime value.

Examples

// Instant of 2021-01-01T12:30+01:00[Europe/Copenhagen]
val a = ZonedDateTime::instant
  (ZonedDateTime::from (Date::from 2021 January 1) (Time::from 12 30 0 0) "Europe/Copenhagen")
//    = #2021-01-01T11:30:00Z#

// Instant of 2021-07-01T12:30+02:00[Europe/Copenhagen]
val b = ZonedDateTime::instant
  (ZonedDateTime::from (Date::from 2021 July 1) (Time::from 12 30 0 0) "Europe/Copenhagen")
//    = #2021-07-01T10:30:00Z#