3.41. kink/datetime/OFFSET_DATETIME

Provides offset_datetime type.

3.41.1. type offset_datetime

An offset_datetime represents a combination of date-and-time, and UTC offset, in the ISO 8601 calendar system without inserting leap seconds.

The date-and-time part is represented to nanosecond precision. The subsecond part is represented within “second” attribute, together with the integral second part.

The range of the date-and-time part is [0001-00-00T00:00:00.000000000, 9999-12-31T23:59:59.999999999].

The range of the UTC offset part is [UTC-18:00, UTC+18:00].

Example:

:OFFSET_DATETIME.require_from('kink/datetime/')
:OFFSET.require_from('kink/datetime/')

:Odt <- OFFSET_DATETIME.new(2020 1 10 23 14 25.123456789 OFFSET.new(-(5 * 60 + 30)))
stdout.print_line(Odt.year.repr)    # => 2020
stdout.print_line(Odt.month.repr)   # => 1
stdout.print_line(Odt.day.repr)     # => 10
stdout.print_line(Odt.hour.repr)    # => 23
stdout.print_line(Odt.minute.repr)  # => 14
stdout.print_line(Odt.second.repr)  # => 25.123456789
stdout.print_line(Odt.day_of_week.repr) # => DAY_OF_WEEK.friday
stdout.print_line(Odt.datetime.repr)    # => (datetime 2020-01-10T23:14:25.123456789)
stdout.print_line(Odt.offset.repr)  # => (offset -05:30)

Odt.year

“year” method returns the year part of Odt.

Postcondition:

• The result is an int num, in the range [1, 9999].

Odt.month

“month” method returns the month part of Odt.

Postcondition:

• The result is an int num, in the range [1, 12].

Odt.day

“day” method returns the day part of Odt.

Postcondition:

• The result is an int num, in the range [1, 31].

Odt.hour

“hour” method returns the hour part of Odt.

Postcondition:

• The result is an int num, in the range [0, 23].

Odt.minute

“minute” method returns the minute part of Odt.

Postcondition:

• The result is an int num, in the range [0, 59].

Odt.second

“second” method returns the second part of Odt.

Postcondition:

• The result is a num with scale=9, in the range [0.000000000, 59.999999999].

Odt.day_of_week

“day_of_week” method returns the day of week corresponding to the date of Odt.

Postcondition:

• The result is a day_of_week. See kink/datetime/DAY_OF_WEEK.

Odt.offset

“offset” method returns the UTC offset of Odt.

Postcondition:

• The result is a `offset`. See kink/datetime/OFFSET.

Odt.datetime

`datetime` returns the datetime without UTC offset.

Postcondition:

• The result is a `datetime` val. See kink/datetime/DATETIME.

Odt.instant

`instant` method returns an `instant` val representing the point of time same as `Odt`.

Postcondition

• The result is an instant. See kink/datetime/INSTANT.

Odt.show(...[$config])

`show` returns a str representation of `Odt`, which is intended to be used in UI messages.

Precondition:

• $config must be a fun which takes a show_conf. See kink/STR for show_conf type.

The spec must be

• an empty str, or

• a str which matches `(?<Field>[^\u0020]+)(\u0020+(?<Option>[^\u0020]))*`.

Specifying the empty spec is equivalent with specifying 'iso8601'.

▎ When spec is an empty str, or `Field` = 'iso8601'

`show` returns a str which represents `Odt` by ISO 8601 format, such as '2001-07-04T01:23:45.123456789-05:30'. The number of digits of the fractional part of the second is always 9. When the offset is zero, the offset is displayed as '+00:00'.

No `Option` is allowed.

▎ When `Field` = 'year'

`show` returns a str which represents the year part of `Odt`.

`Option` can be '0001-9999' (default) or '00-99'. The options are mutually exclusive. When '0001-9999' is specified, the year is represented by 4 digits (such as '0042' and '1969'). When '00-99' is specified, the year is represented by the latter 2 digits (such as '42' and '69').

▎ When `Field` = 'month'

`show` returns a str which represents the month part of `Odt`.

`Option` can be '01-12' (default), '1-12', 'full', or 'short'. The options are mutually exclusive. When '01-12' is specified, the month is represented by 2 digits (such as '01' and '12'). When '1-12' is specified, the month is represented by 1 or 2 digits (such as '1' and '12'). When 'full' is specified, the month is represented by a locale-dependent full name (such as 'January' and 'December'). When 'short' is specified, the month is represented by a locale-dependent short name (such as 'Jan' and 'Dec').

▎ When `Field` = 'day'

`show` returns a str which represents the day part of `Odt`.

`Option` can be '01-31' (default) or '1-31'. The options are mutually exclusive. When '01-31' is specified, the day is represented by 2 digits (such as '01' and '31'). When '1-31' is specified, the day is represented by 1 or 2 digits (such as '1' and '31').

▎ When `Field` = 'day_of_week'

`show` returns a str which represents the day of week of `Odt`

`Option` can be 'full' (default) or 'short'. The options are mutually exclusive. When 'full' is specified, the day of week is represented by a locale-dependent full name (such as 'Monday' and 'Sunday'). When 'short' is specified, the day of week is represented by a locale-dependent long name (such as 'Mon' and 'Sun').

▎ When `Field` = 'ampm'

`show` returns a locale-dependent str which represents whether `Odt` is AM or PM.

No `Option` is accepted.

▎ When `Field` = 'hour'

`show` returns a str which represents the hour part of `Odt`.

`Option` can be '00-23', '0-23', or '1-12'. The options are mutually exclusive. When '00-23' is specified, the hour is represented by 2 digits (such as '00' and '23'). When '0-23' is specified, the hour is represented by 1 or 2 digits (such as '0' and '23'). When '1-12' is specified, the hour is represented by 1 or 2 digits as the hour in a 12-hour clock (such as '1' and '12').

▎ When `Field` = 'minute'

`show` returns a str which represents the minute part of `Odt`.

`Option` can be '00-59' or '0-59'. The options are mutually exclusive. When '00-59' is specified, the minute is represented by 2 digits (such as '00' and '59'). When '0-59' is specified, the minute is represented by 1 or 2 digits (such as '0' and '59').

▎ When `Field` = 'second'

`show` returns a str which represents the second part of `Odt`.

`Option` can be '00-59' (default), '0-59', '.0', '.1', '.2',,, '.8', or '.9' (default).

'00-59' and '0-59' are mutually exclusive. When '00-59' is specified, the integral part of the second is represented by 2 digits (such as '00' and '59'). When '0-59' is specified, the integral part of the second is represented by 1 or 2 digits (such as '0' and '59').

'.0', '.1', '.2',,, '.8', and '.9' are mutually exclusive. When '.0' is specified, the fractional part of the second is not displayed. When '.1', '.2',,, '.8', or '.9' is specified, the fractional part is floored to the specified scale and displayed after '.'.

▎ When `Field` = 'offset'

`show` returns a str which represents the offset part of `Odt`.

`Option` can be '00:00' (default), '0000', 'noZ' (default), and 'Z'.

'00:00' and '0000' are mutually exclusive. When '00:00' is specified, the offset is represented using ':' as a separator (such as '+09:00' and '-05:30'). When '0000' is specified, the offset is represented without a separator (such as '+0900' and '-0530').

'noZ' and 'Z' are mutually exclusive. When 'noZ' is specified, offset=0 is represented by '+00:00' (if '00:00' is specified) or '+0000' (if '0000' is specified). When 'Z' is specified, offset=0 is represented by 'Z'.

▎ Examples

:LOCALE.require_from('kink/')
:OFFSET.require_from('kink/datetime/')
:OFFSET_DATETIME.require_from('kink/datetime/')

:Odt <- OFFSET_DATETIME.new(2001 1 2 3 4 5.123456789 OFFSET.new(-(5 * 60 + 30)))

stdout.print_line(Odt.show{(:C) C.spec('year') })       # => 2001
stdout.print_line(Odt.show{(:C) C.spec('year 00-99') }) # => 01

stdout.print_line(Odt.show{(:C) C.spec('month') })          # => 01
stdout.print_line(Odt.show{(:C) C.spec('month 1-12') })     # => 1
stdout.print_line(Odt.show{(:C)
    C.spec('month full')
    C.locale(LOCALE.for('fr'))
  }
)  # => janvier
stdout.print_line(Odt.show{(:C)
    C.spec('month short')
    C.locale(LOCALE.for('fr'))
  }
)  # => janv.

stdout.print_line(Odt.show{(:C) C.spec('day') })      # => 02
stdout.print_line(Odt.show{(:C) C.spec('day 1-31') }) # => 2

stdout.print_line(Odt.show{(:C) C.spec('day_of_week') })     # => Tue
stdout.print_line(Odt.show{(:C)
    C.spec('day_of_week full')
    C.locale(LOCALE.for('fr'))
  }
)  # => mardi
stdout.print_line(Odt.show{(:C)
    C.spec('day_of_week short')
    C.locale(LOCALE.for('fr'))
  }
)  # => mar.

stdout.print_line(Odt.show{(:C) C.spec('ampm') }) # => AM
stdout.print_line(Odt.show{(:C)
    C.spec('ampm')
    C.locale(LOCALE.for('ja'))
  }
)  # => 午前

stdout.print_line(Odt.show{(:C) C.spec('hour') })       # => 03
stdout.print_line(Odt.show{(:C) C.spec('hour 0-23') })  # => 3
stdout.print_line(Odt.show{(:C) C.spec('hour 1-12') })  # => 3

stdout.print_line(Odt.show{(:C) C.spec('minute') })       # => 04
stdout.print_line(Odt.show{(:C) C.spec('minute 0-59') })  # => 4

stdout.print_line(Odt.show{(:C) C.spec('second') })         # => 05.123456789
stdout.print_line(Odt.show{(:C) C.spec('second 0-59 .3') }) # => 5.123

stdout.print_line(Odt.show{(:C) C.spec('offset') })         # => -05:00
stdout.print_line(Odt.show{(:C) C.spec('offset 0000') })    # => -0500
stdout.print_line(Odt.show{(:C) C.spec('offset 0000 Z') })  # => -0500

:Utc_odt <- OFFSET_DATETIME.new(1 1 1 0 0 0 OFFSET.new(0))
stdout.print_line(Utc_odt.show{(:C) C.spec('offset') })         # => +00:00
stdout.print_line(Utc_odt.show{(:C) C.spec('offset 0000') })    # => +0000
stdout.print_line(Utc_odt.show{(:C) C.spec('offset Z') })       # => Z
stdout.print_line(Utc_odt.show{(:C) C.spec('offset 0000 Z') })  # => Z

Usually, `show` method is used from Str.format.

:LOCALE.require_from('kink/')
:OFFSET.require_from('kink/datetime/')
:OFFSET_DATETIME.require_from('kink/datetime/')

:Odt <- OFFSET_DATETIME.new(1993 3 6 3 40 20 OFFSET.new(60 * 9))
stdout.print_line('Odt: {}'.format(Odt))  # => Odt: 1993-03-06T03:40:20.000000000+09:00
stdout.print_line('{Odt%month Jan} {Odt%day 1-31}, {Odt%year} ({Odt%day_of_week})'.format{(:C)
    C.named_arg('Odt' Odt)
    C.locale(LOCALE.for('fr'))
  }
) # => mars 6, 1993 (samedi)

Odt1 == Odt2

offset_datetime values Odt1 and Odt2 are equal when:

• They have the same civil time, and

• They have the same UTC offset.

Odt.repr

“repr” returns a str representation of Odt, such as "(offset_datetime 2020-01-10T23:07:04.123456789-05:30)"

3.41.2. OFFSET_DATETIME.new(Year Month Day Hour Minute Second Offset)

“new” fun makes an offset_datetime, with the speicifed attributes.

• Year must be an int num in the range [1, 9999].

• Month must be an int num in the range [1, 12].

• Day must be an int num.

• The combination of Year, Month and Day must be a valid date in ISO 8601 calendar system.

• Hour must be an int num in the range [0, 23].

• Minute must be an int num in the range [0, 59].

• Second must be a num with scale<=9 in the range [0.000000000, 59.999999999].

• Offset must be an `offset` val.

3.41.3. OFFSET_DATETIME.is?(Val)

OFFSET_DATETIME.is? returns whether Val is an offset_datetime val.

3.41.4. OFFSET_DATETIME.from_instant_and_offset(Instant Offset)

`from_instant_and_offset` makes an `offset_datetime` val representing the `Instant` at the `Offset`.

Preconditions:

• `Instant` must be an `instant`

• `Offset` must be a `offset`

• The date and time of the resulting offset_datetime must be in the range [0001-00-00T00:00:00.000000000, 9999-12-31T23:59:59.999999999]

Example:

:INSTANT.require_from('kink/datetime/')
:OFFSET.require_from('kink/datetime/')
:OFFSET_DATETIME.require_from('kink/datetime/')

:Ins <- INSTANT.new(1579580443.123456789)
:Odt <- OFFSET_DATETIME.from_instant_and_offset(Ins OFFSET.new(60 * 9))
stdout.print_line(Odt.repr) # => (offset_datetime 2020-01-21T13:20:43.123456789+09:00)

3.41.5. OFFSET_DATETIME.from_instant_and_timezone(Instant Tz)

`from_instant_and_timezone` makes an `offset_datetime` val representing the `Instant` at the timezone.

Preconditions:

• `Instant` must be an instant

• `Tz` must be a timezone

• The date and time of the resulting offset_datetime must be in the range [0001-00-00T00:00:00.000000000, 9999-12-31T23:59:59.999999999]

Example:

:INSTANT.require_from('kink/datetime/')
:TIMEZONE.require_from('kink/datetime/')
:OFFSET_DATETIME.require_from('kink/datetime/')

:Ins <- INSTANT.new(1579580443.123456789)
:Pt <- TIMEZONE.for('America/Los_Angeles')
:Odt <- OFFSET_DATETIME.from_instant_and_timezone(Ins Pt)
stdout.print_line(Odt.repr) # => (offset_datetime 2020-01-20T20:20:43.123456789-08:00)

3.41.6. OFFSET_DATETIME.from_datetime_and_offset(Dt Offset)

`from_datetime_and_offset` makes an `offset_datetime` val representing the datetime at the `Offset`.

Preconditions:

• `Dt` must be a `datetime` val

• `Offset` must be a offset

Example:

:DATETIME.require_from('kink/datetime/')
:OFFSET.require_from('kink/datetime/')
:OFFSET_DATETIME.require_from('kink/datetime/')

:Dt <- DATETIME.new(2020 1 21 13 20 43.123456789)
:Odt <- OFFSET_DATETIME.from_datetime_and_offset(Dt OFFSET.new(60 * 9))
stdout.print_line(Odt.repr) # => (offset_datetime 2020-01-21T13:20:43.123456789+09:00)

3.41.7. OFFSET_DATETIME.from_datetime_and_timezone(Dt Tz)

`from_datetime_and_timezone` makes an `offset_datetime` val representing the `datetime` at the `timezone`.

Preconditions:

• `Dt` must be a `datetime`

• `Tz` must be a `timezone`

Result:

• If there is only one `offset` valid at the datetime, the fun produces an `offset_datetime` val with the offset.

• If there are two `offset` vals valid at the datetime due to transition, the fun produces an `offset_datetime` with the offset before the transition.

• If there is no `offset` valid at the datetime due to transition, the fun first makes an `offset_datetime` with the offset before the transition (A), then produces an `offset_datetime` which represents the instant of (A) at the offset after the transition.

Example:

:DATETIME.require_from('kink/datetime/')
:TIMEZONE.require_from('kink/datetime/')
:OFFSET_DATETIME.require_from('kink/datetime/')

:Pt <- TIMEZONE.for('America/Los_Angeles')

# winter
stdout.print_line(
  OFFSET_DATETIME.from_datetime_and_timezone(
    DATETIME.new(2019 1 1 08 00 00)  # UTC-08:00 is valid
    Pt
  ).repr
) # => (offset_datetime 2019-01-01T08:00:00.000000000-08:00)

# gap: winter -> summer
stdout.print_line(
  OFFSET_DATETIME.from_datetime_and_timezone(
    DATETIME.new(2019 3 10 02 30 00) # no valid offset for the date and time
    Pt
  ).repr
) # => (offset_datetime 2019-03-10T03:30:00.000000000-07:00)

# summer
stdout.print_line(
  OFFSET_DATETIME.from_datetime_and_timezone(
    DATETIME.new(2019 7 1 08 00 00)  # UTC-07:00 is valid
    Pt
  ).repr
) # => (offset_datetime 2019-07-01T08:00:00.000000000-07:00)

# overlap: summer -> winter
stdout.print_line(
  OFFSET_DATETIME.from_datetime_and_timezone(
    DATETIME.new(2019 11 3 01 30 00) # both UTC-08:00 and UTC-07:00 are valid
    Pt
  ).repr
) # => (offset_datetime 2019-11-03T01:30:00.000000000-07:00)

If you need more control over conversion, consider using Tz.offset_for_datetime.

3.41.8. OFFSET_DATETIME.parse(Str)

`parse` parses an offset_datetime val from `Str`.

Precondition:

• `Str` must be a str

Result:

• If `Str` is accepted by the syntax and the limiting conditions, `parse` returns a single-element vec [Offset_datetime], where `Offset_datetime` is the parsed offset_datetime.

• If `Str` is not accepted, `parse` returns an empty vec [].

Syntax: `{Year}-{Month}-{Day}T{Hour}:{Minute}:{Second}Z` (offset is zero), or `{Year}-{Month}-{Day}T{Hour}:{Minute}:{Second}{Offset_sign}{Offset_hour}:{Offset_minute}`.

• `Year` is four decimal digits from `0001` to `9999`

• `Month` is two decimal digits from `01` to `12`

• `Day` is two decimal digits from `01` to `31`

• `Hour` is two decimal digits from `00` to `23`

• `Minute` is two decimal digits from `00` to `59`

• `Second` is two decimal digits from `00` to `59`, or `{Int}.{Frac}` where `Int`is two decimal digits from `00` to `59` and `Frac` is 1 to 9 decimal digits.

• `Offset_sign` is either `-` or `+`

• `Offset_hour` is two decimal digits from `00` to `18`

• `Offset_minute` is `00` if `Offset_hour` is `18`, otherwise two decimal digits from `00` to `59`

Limiting conditions:

• `Year`, `Month`, and `Day` must constitute a valid date in ISO 8601 calendar system.

Example:

:OFFSET_DATETIME.require_from('kink/datetime/')

stdout.print_line(OFFSET_DATETIME.parse('2020-11-04T12:34:56Z').repr)
# => [(offset_datetime 2020-11-04T12:34:56.000000000+00:00)]

stdout.print_line(OFFSET_DATETIME.parse('2020-11-04T12:34:56+18:00').repr)
# => [(offset_datetime 2020-11-04T12:34:56.000000000+18:00)]

stdout.print_line(OFFSET_DATETIME.parse('2020-11-04T12:34:56-18:00').repr)
# => [(offset_datetime 2020-11-04T12:34:56.000000000-18:00)]

stdout.print_line(OFFSET_DATETIME.parse('boo').repr)
# => []