214 lines
8.9 KiB
Markdown
214 lines
8.9 KiB
Markdown
# Lyng time functions
|
|
|
|
Lyng date and time support requires importing `lyng.time`. The module provides four related types:
|
|
|
|
- `Instant` for absolute timestamps.
|
|
- `Date` for calendar dates without time-of-day or timezone.
|
|
- `DateTime` for calendar-aware points in time in a specific timezone.
|
|
- `Duration` for absolute elapsed time.
|
|
|
|
## Time instant: `Instant`
|
|
|
|
`Instant` represents some moment of time independently of the calendar. It is similar to SQL `TIMESTAMP`
|
|
or Kotlin `Instant`.
|
|
|
|
### Constructing and converting
|
|
|
|
import lyng.time
|
|
|
|
val t1 = Instant()
|
|
val t2 = Instant(1704110400)
|
|
val t3 = Instant("2024-01-01T12:00:00.123456Z")
|
|
|
|
val t4 = t3.truncateToMinute()
|
|
assertEquals("2024-01-01T12:00:00Z", t4.toRFC3339())
|
|
|
|
val dt = t3.toDateTime("+02:00")
|
|
assertEquals(14, dt.hour)
|
|
|
|
val d = t3.toDate("Z")
|
|
assertEquals(Date(2024, 1, 1), d)
|
|
|
|
### Instant members
|
|
|
|
| member | description |
|
|
|--------------------------------|------------------------------------------------------|
|
|
| epochSeconds: Real | offset in seconds since Unix epoch |
|
|
| epochWholeSeconds: Int | whole seconds since Unix epoch |
|
|
| nanosecondsOfSecond: Int | nanoseconds within the current second |
|
|
| isDistantFuture: Bool | true if it is `Instant.distantFuture` |
|
|
| isDistantPast: Bool | true if it is `Instant.distantPast` |
|
|
| truncateToMinute(): Instant | truncate to minute precision |
|
|
| truncateToSecond(): Instant | truncate to second precision |
|
|
| truncateToMillisecond(): Instant | truncate to millisecond precision |
|
|
| truncateToMicrosecond(): Instant | truncate to microsecond precision |
|
|
| toRFC3339(): String | format as RFC3339 string in UTC |
|
|
| toDateTime(tz?): DateTime | localize to a timezone |
|
|
| toDate(tz?): Date | convert to a calendar date in a timezone |
|
|
|
|
## Calendar date: `Date`
|
|
|
|
`Date` represents a pure calendar date. It has no time-of-day and no attached timezone. Use it for values
|
|
like birthdays, due dates, invoice dates, and SQL `DATE` columns.
|
|
|
|
### Constructing
|
|
|
|
import lyng.time
|
|
|
|
val today = Date()
|
|
val d1 = Date(2026, 4, 15)
|
|
val d2 = Date("2024-02-29")
|
|
val d3 = Date.parseIso("2024-02-29")
|
|
val d4 = Date(DateTime(2024, 5, 20, 15, 30, 45, "+02:00"))
|
|
val d5 = Date(Instant("2024-01-01T23:30:00Z"), "+02:00")
|
|
|
|
### Date members
|
|
|
|
| member | description |
|
|
|--------------------------------|------------------------------------------------------------|
|
|
| year: Int | year component |
|
|
| month: Int | month component (1..12) |
|
|
| day: Int | day of month (alias `dayOfMonth`) |
|
|
| dayOfWeek: Int | day of week (1=Monday, 7=Sunday) |
|
|
| dayOfYear: Int | day of year (1..365/366) |
|
|
| isLeapYear: Bool | whether this date is in a leap year |
|
|
| lengthOfMonth: Int | number of days in this month |
|
|
| lengthOfYear: Int | 365 or 366 |
|
|
| toIsoString(): String | ISO `YYYY-MM-DD` string |
|
|
| toSortableString(): String | alias to `toIsoString()` |
|
|
| toDateTime(tz="Z"): DateTime | start-of-day `DateTime` in the specified timezone |
|
|
| atStartOfDay(tz="Z"): DateTime | alias to `toDateTime()` |
|
|
| addDays(n): Date | add or subtract calendar days |
|
|
| addMonths(n): Date | add or subtract months, normalizing end-of-month |
|
|
| addYears(n): Date | add or subtract years |
|
|
| daysUntil(other): Int | calendar days until `other` |
|
|
| daysSince(other): Int | calendar days since `other` |
|
|
| static today(tz?): Date | today in the specified timezone |
|
|
| static parseIso(s): Date | parse ISO `YYYY-MM-DD` |
|
|
|
|
### Date arithmetic
|
|
|
|
`Date` supports only whole-day arithmetic. This is deliberate: calendar dates should not silently accept
|
|
sub-day durations.
|
|
|
|
import lyng.time
|
|
|
|
val d1 = Date(2026, 4, 15)
|
|
val d2 = d1.addDays(10)
|
|
|
|
assertEquals(Date(2026, 4, 25), d2)
|
|
assertEquals(Date(2026, 4, 18), d1 + 3.days)
|
|
assertEquals(Date(2026, 4, 12), d1 - 3.days)
|
|
assertEquals(10, d1.daysUntil(d2))
|
|
assertEquals(10, d2.daysSince(d1))
|
|
assertEquals(10, d2 - d1)
|
|
|
|
### Date conversions
|
|
|
|
import lyng.time
|
|
|
|
val i = Instant("2024-01-01T23:30:00Z")
|
|
assertEquals(Date(2024, 1, 1), i.toDate("Z"))
|
|
assertEquals(Date(2024, 1, 2), i.toDate("+02:00"))
|
|
|
|
val dt = DateTime(2024, 5, 20, 15, 30, 45, "+02:00")
|
|
assertEquals(Date(2024, 5, 20), dt.date)
|
|
assertEquals(Date(2024, 5, 20), dt.toDate())
|
|
assertEquals(DateTime(2024, 5, 20, 0, 0, 0, "Z"), Date(2024, 5, 20).toDateTime("Z"))
|
|
assertEquals(DateTime(2024, 5, 20, 0, 0, 0, "+02:00"), Date(2024, 5, 20).atStartOfDay("+02:00"))
|
|
|
|
## Calendar time: `DateTime`
|
|
|
|
`DateTime` represents a point in time in a specific timezone. It provides access to calendar components
|
|
such as year, month, day, and hour.
|
|
|
|
### Constructing
|
|
|
|
import lyng.time
|
|
|
|
val now = DateTime.now()
|
|
val offsetTime = DateTime.now("+02:00")
|
|
val dt = Instant().toDateTime("Z")
|
|
val dt2 = DateTime(2024, 1, 1, 12, 0, 0, "Z")
|
|
val dt3 = DateTime.parseRFC3339("2024-01-01T12:00:00+02:00")
|
|
|
|
### DateTime members
|
|
|
|
| member | description |
|
|
|----------------------------------|-----------------------------------------------|
|
|
| year: Int | year component |
|
|
| month: Int | month component (1..12) |
|
|
| day: Int | day of month (alias `dayOfMonth`) |
|
|
| hour: Int | hour component (0..23) |
|
|
| minute: Int | minute component (0..59) |
|
|
| second: Int | second component (0..59) |
|
|
| dayOfWeek: Int | day of week (1=Monday, 7=Sunday) |
|
|
| timeZone: String | timezone ID string |
|
|
| date: Date | calendar date component |
|
|
| toInstant(): Instant | convert back to absolute Instant |
|
|
| toDate(): Date | extract the calendar date in this timezone |
|
|
| toUTC(): DateTime | shortcut to convert to UTC |
|
|
| toTimeZone(tz): DateTime | convert to another timezone |
|
|
| addMonths(n): DateTime | add/subtract months (normalizes end of month) |
|
|
| addYears(n): DateTime | add/subtract years |
|
|
| toRFC3339(): String | format with timezone offset |
|
|
| static now(tz?): DateTime | create DateTime with current time |
|
|
| static parseRFC3339(s): DateTime | parse RFC3339 string |
|
|
|
|
### Arithmetic and normalization
|
|
|
|
`DateTime` handles calendar arithmetic correctly:
|
|
|
|
import lyng.time
|
|
|
|
val leapDay = Instant("2024-02-29T12:00:00Z").toDateTime("Z")
|
|
val nextYear = leapDay.addYears(1)
|
|
assertEquals(28, nextYear.day)
|
|
|
|
# `Duration` class
|
|
|
|
`Duration` represents absolute elapsed time between two instants.
|
|
|
|
import lyng.time
|
|
|
|
val t1 = Instant()
|
|
delay(1.millisecond)
|
|
val t2 = Instant()
|
|
|
|
assert(t2 - t1 >= 1.millisecond)
|
|
assert(t2 - t1 < 100.millisecond)
|
|
>>> void
|
|
|
|
Duration values can be created from numbers using extensions on `Int` and `Real`:
|
|
|
|
- `n.millisecond`, `n.milliseconds`
|
|
- `n.second`, `n.seconds`
|
|
- `n.minute`, `n.minutes`
|
|
- `n.hour`, `n.hours`
|
|
- `n.day`, `n.days`
|
|
|
|
Larger units like months or years are calendar-dependent and are intentionally not part of `Duration`.
|
|
|
|
Each duration instance can be converted to numbers in these units:
|
|
|
|
- `d.microseconds`
|
|
- `d.milliseconds`
|
|
- `d.seconds`
|
|
- `d.minutes`
|
|
- `d.hours`
|
|
- `d.days`
|
|
|
|
Example:
|
|
|
|
import lyng.time
|
|
|
|
assertEquals(60, 1.minute.seconds)
|
|
assertEquals(10.milliseconds, 0.01.seconds)
|
|
>>> void
|
|
|
|
# Utility functions
|
|
|
|
## `delay(duration: Duration)`
|
|
|
|
Suspends the current coroutine for at least the specified duration.
|