March Docs

DateTime

DateTime module: date/time types and arithmetic.

Types: Date(year, month, day) -- Gregorian calendar, month 1-12, day 1-31 Time(hour, min, sec) -- 24-hour clock, all zero-based except day DateTime(date, time) -- combined date and time

Timestamps are Unix timestamps: integer seconds since 1970-01-01 00:00:00 UTC. now() returns the current time as a Unix timestamp (Int).

Calendar arithmetic assumes the proleptic Gregorian calendar throughout. Day-of-week encoding: 0 = Sunday, 1 = Monday, ..., 6 = Saturday.

Types

typeDateDate = Date(Int, Int, Int) -- year, month (1-12), day (1-31)#
typeTimeTime = Time(Int, Int, Int) -- hour (0-23), min (0-59), sec (0-59)#
typeDateTimeDateTime = DateTime(Date, Time)#
typeTzTz = Tz(String, Int)#

Tz(name, offset_seconds) represents a zone with a constant offset east of UTC. offset_seconds is positive for zones east of UTC (UTC+05:30 → +19800) and negative for zones west (UTC−08:00 → −28800).

DST-aware zones (America/New_York, Europe/London, Pacific/Auckland, …) are deliberately out of scope for this phase — they require loading IANA tzdata or hard-coding transition rules, and the ecosystem pattern is to layer that on top of a fixed-offset primitive rather than bake it in. For now any zone you need must be modeled as a fixed offset; a future phase can add IANA support and keep this primitive.

LocalDateTime(civil, tz) pairs a calendar "wall clock" DateTime with its zone. local_to_utc and local_from_utc convert back and forth; local_with_zone shifts to another zone preserving the instant.

Functions

fnaddadd(dt : DateTime, dur : Int) : DateTime#

Add a span of dur milliseconds to a DateTime; same as add_ms. Despite the name it takes a plain Int, not a Duration.Duration value: convert with Duration.to_milliseconds first.

Examples

march> DateTime.add(DateTime(Date(2024, 1, 1), Time(0, 0, 0)), Duration.to_milliseconds(Duration.minutes(90)))
DateTime(Date(2024, 1, 1), Time(1, 30, 0))
fnadd_daysadd_days(dt, n)#

Add n days (of exactly 86400 seconds) to a DateTime. n may be negative. Month, year and leap-day rollover are handled.

Examples

march> DateTime.add_days(DateTime(Date(2024, 2, 28), Time(12, 0, 0)), 1)
DateTime(Date(2024, 2, 29), Time(12, 0, 0))
march> DateTime.add_days(DateTime(Date(2024, 3, 1), Time(0, 0, 0)), -1)
DateTime(Date(2024, 2, 29), Time(0, 0, 0))
fnadd_hoursadd_hours(dt, n)#

Add n hours to a DateTime. n may be negative.

Examples

march> DateTime.add_hours(DateTime(Date(2024, 12, 31), Time(23, 0, 0)), 2)
DateTime(Date(2025, 1, 1), Time(1, 0, 0))
fnadd_msadd_ms(dt : DateTime, n : Int) : DateTime#

Add n milliseconds to a DateTime. n may be negative. DateTime has whole-second resolution, so n is truncated toward zero to whole seconds first (2500 adds 2 s, -1500 subtracts 1 s, 999 adds nothing).

Examples

march> DateTime.add_ms(DateTime(Date(2024, 1, 1), Time(0, 0, 0)), 2500)
DateTime(Date(2024, 1, 1), Time(0, 0, 2))
march> DateTime.add_ms(DateTime(Date(2024, 1, 1), Time(0, 0, 0)), -1500)
DateTime(Date(2023, 12, 31), Time(23, 59, 59))
fnadd_secondsadd_seconds(dt, n)#

Add n seconds to a DateTime. n may be negative.

Examples

march> DateTime.add_seconds(DateTime(Date(1970, 1, 1), Time(0, 0, 0)), -1)
DateTime(Date(1969, 12, 31), Time(23, 59, 59))
fncomparecompare(a, b)#

Compare two DateTimes chronologically. Returns -1 if a < b, 0 if a == b, 1 if a > b.

Examples

march> DateTime.compare(DateTime(Date(2024, 1, 2), Time(0, 0, 0)), DateTime(Date(2024, 1, 1), Time(12, 0, 0)))
1
march> DateTime.compare(DateTime(Date(2024, 1, 1), Time(0, 0, 0)), DateTime(Date(2024, 1, 1), Time(0, 0, 0)))
0
fnday_of_weekday_of_week(dt)#

Return the day of the week for the date component of a DateTime. 0 = Sunday, 1 = Monday, ..., 6 = Saturday. (January 1, 1970 was a Thursday = 4.) The time component is ignored. Note: dates more than four days before 1970-01-01 currently return a negative number (the remainder is not floored).

Examples

march> DateTime.day_of_week(DateTime(Date(1970, 1, 1), Time(0, 0, 0)))
4
march> DateTime.day_of_week(DateTime(Date(2024, 1, 15), Time(18, 0, 0)))
1
fndiff_secondsdiff_seconds(a, b)#

Difference between two DateTimes in seconds: to_timestamp(a) - to_timestamp(b), so negative when a is earlier.

Examples

march> DateTime.diff_seconds(DateTime(Date(2024, 1, 2), Time(0, 0, 0)), DateTime(Date(2024, 1, 1), Time(12, 0, 0)))
43200
march> DateTime.diff_seconds(DateTime(Date(2024, 1, 1), Time(0, 0, 0)), DateTime(Date(2024, 1, 1), Time(0, 1, 0)))
-60
fnfixed_zonefixed_zone(name : String, offset_seconds : {Int | _ >= -50400 && _ <= 50400}) : Tz#

Build a fixed-offset zone from an explicit label and second offset. offset_seconds is positive east of UTC, negative west. Panics if the offset is outside ±14 hours (the widest real-world legal range).

Examples

march> DateTime.fixed_zone("IST", 19800)
Tz("IST", 19800)
fnfixed_zone_hmfixed_zone_hm(hours : Int, minutes : {Int | _ >= 0 && _ < 60}) : Tz#

Convenience constructor: fixed offset as (hours, minutes) with a single sign applied by hours (per ISO convention). For UTC+05:30 pass (5, 30); for UTC−03:30 pass (-3, 30) (minutes always positive, sign comes from hours). Name is auto-derived. Because the sign comes from hours, a negative offset under one hour (such as -00:30) cannot be built here; use fixed_zone for that. Panics if minutes is outside [0, 60).

Examples

march> DateTime.fixed_zone_hm(5, 30)
Tz("+05:30", 19800)
march> DateTime.fixed_zone_hm(-3, 30)
Tz("-03:30", -12600)
fnfixed_zone_hoursfixed_zone_hours(hours : Int) : Tz#

Convenience constructor: fixed offset in whole hours. The name is auto-derived in ISO 8601 form (e.g. "+05:00", "-08:00", "Z"). For custom labels use fixed_zone(name, seconds). Unlike fixed_zone, the ±14 h range is not checked.

Examples

march> DateTime.fixed_zone_hours(-8)
Tz("-08:00", -28800)
march> DateTime.fixed_zone_hours(0)
Tz("Z", 0)
fnformatformat(dt, fmt)#

Format a DateTime as a string by replacing the specifiers %Y (4-digit year) and %m %d %H %M %S (2-digit, zero-padded). Any other text, including %%, is copied through unchanged (%% is not collapsed to %).

march> DateTime.format(DateTime.from_timestamp(1700000000), "%Y-%m-%d %H:%M:%S")
"2023-11-14 22:13:20"
march> DateTime.format(DateTime(Date(2024, 1, 5), Time(9, 5, 0)), "%d/%m/%Y")
"05/01/2024"
fnformat_offsetformat_offset(tz : Tz) : String#

Format a zone's offset as ISO 8601: \"Z\" for UTC, \"+HH:MM\" or \"-HH:MM\" otherwise. Seconds-of-offset are ignored (no real zone uses sub-minute offsets since 1972). The name is not consulted: any zone with offset 0 renders as "Z".

Examples

march> DateTime.format_offset(DateTime.fixed_zone("PST", -28800))
"-08:00"
march> DateTime.format_offset(DateTime.utc_zone())
"Z"
fnfrom_timestampfrom_timestamp(ts)#

Convert a Unix timestamp (Int seconds) to a DateTime.

march> DateTime.from_timestamp(0)
DateTime(Date(1970, 1, 1), Time(0, 0, 0))
march> DateTime.from_timestamp(86400)
DateTime(Date(1970, 1, 2), Time(0, 0, 0))
fnlocal_datetimelocal_datetime(ldt : LocalDateTime) : DateTime#

Return the civil (wall-clock) DateTime component.

Examples

march> DateTime.local_datetime(DateTime.local_from_timestamp(0, DateTime.fixed_zone_hours(-5)))
DateTime(Date(1969, 12, 31), Time(19, 0, 0))
fnlocal_formatlocal_format(ldt : LocalDateTime, fmt : String) : String#

Format a LocalDateTime's civil time. All DateTime.format specifiers are supported (%Y %m %d %H %M %S) plus: %z — offset without colon, e.g. +0530, -0800, +0000 %:z — ISO 8601 offset, e.g. +05:30, -08:00, Z for UTC %Z — zone name (whatever was passed to the Tz constructor)

Examples

march> DateTime.local_format(DateTime.local_from_timestamp(0, DateTime.fixed_zone_hm(5, 30)), "%Y-%m-%dT%H:%M:%S%:z")
"1970-01-01T05:30:00+05:30"
march> DateTime.local_format(DateTime.local_from_timestamp(0, DateTime.fixed_zone("PST", -28800)), "%H:%M %z %Z")
"16:00 -0800 PST"
fnlocal_from_timestamplocal_from_timestamp(ts : Int, tz : Tz) : LocalDateTime#

Build a LocalDateTime from a UTC instant expressed as a Unix timestamp (Int seconds): the civil part is the wall-clock time in tz at that instant.

Examples

march> DateTime.local_from_timestamp(0, DateTime.fixed_zone_hm(5, 30))
LocalDateTime(DateTime(Date(1970, 1, 1), Time(5, 30, 0)), Tz("+05:30", 19800))
fnlocal_from_utclocal_from_utc(utc_dt : DateTime, tz : Tz) : LocalDateTime#

Reinterpret a UTC DateTime as the same instant in zone tz (the civil part is shifted by the zone's offset).

Examples

march> DateTime.local_from_utc(DateTime(Date(2024, 1, 15), Time(22, 0, 0)), DateTime.fixed_zone_hours(3))
LocalDateTime(DateTime(Date(2024, 1, 16), Time(1, 0, 0)), Tz("+03:00", 10800))
fnlocal_nowlocal_now(tz : Tz) : LocalDateTime#

Return the current wall-clock time in zone tz (see now). Nondeterministic, so there is no runnable example.

DateTime.local_format(DateTime.local_now(DateTime.fixed_zone_hours(9)), "%H:%M%:z")
fnlocal_oflocal_of(dt : DateTime, tz : Tz) : LocalDateTime#

Pair a civil DateTime (interpreted as the wall-clock time in tz) with its zone. No conversion happens — dt is used as-is. Use local_from_utc if you start from a UTC instant.

Examples

march> DateTime.local_of(DateTime(Date(2024, 1, 15), Time(10, 0, 0)), DateTime.fixed_zone_hours(2))
LocalDateTime(DateTime(Date(2024, 1, 15), Time(10, 0, 0)), Tz("+02:00", 7200))
fnlocal_to_timestamplocal_to_timestamp(ldt : LocalDateTime) : Int#

Unix timestamp (UTC seconds) of the instant this LocalDateTime denotes.

Examples

march> DateTime.local_to_timestamp(DateTime.local_from_timestamp(86400, DateTime.fixed_zone_hours(9)))
86400
fnlocal_to_utclocal_to_utc(ldt : LocalDateTime) : DateTime#

Convert a LocalDateTime to the corresponding UTC DateTime. Subtracts the zone's offset from the civil time.

Examples

march> DateTime.local_to_utc(DateTime.local_of(DateTime(Date(2024, 1, 15), Time(1, 0, 0)), DateTime.fixed_zone_hours(2)))
DateTime(Date(2024, 1, 14), Time(23, 0, 0))
fnlocal_with_zonelocal_with_zone(ldt : LocalDateTime, new_tz : Tz) : LocalDateTime#

Return the same instant rendered in a different zone. local_with_zone(ldt, z) satisfies local_to_timestamp(result) == local_to_timestamp(ldt) (same instant, different wall-clock).

Examples

march> DateTime.local_datetime(DateTime.local_with_zone(DateTime.local_of(DateTime(Date(2024, 1, 15), Time(10, 0, 0)), DateTime.fixed_zone_hours(2)), DateTime.fixed_zone_hours(-5)))
DateTime(Date(2024, 1, 15), Time(3, 0, 0))
fnlocal_zonelocal_zone(ldt : LocalDateTime) : Tz#

Return the zone of a LocalDateTime.

Examples

march> DateTime.local_zone(DateTime.local_from_timestamp(0, DateTime.fixed_zone_hours(-5)))
Tz("-05:00", -18000)
fnnownow()#

Return the current Unix timestamp as an Int (whole seconds since 1970-01-01 UTC; the fractional part of the clock is truncated). Nondeterministic, so there is no runnable example.

let started = DateTime.now()
DateTime.format(DateTime.from_timestamp(started), "%H:%M:%S")
fnparseparse(s)#

Parse a UTC DateTime from a string. Accepts "YYYY-MM-DD" and "YYYY-MM-DD HH:MM:SS". Returns Ok(DateTime) or Err(message) — rejects out-of-range fields (month 13, day 32, hour 25, …) rather than constructing a garbage calendar value.

This entry point is timezone-naive: the returned DateTime always represents UTC. For ISO 8601 strings with an explicit offset (Z, +HH:MM, -HHMM, …) use parse_offset to get a LocalDateTime whose zone is recovered from the suffix. Anything after the seconds field is currently ignored, so "2024-01-15 10:30:45xyz" parses as 10:30:45.

Examples

march> DateTime.parse("2024-01-15")
Ok(DateTime(Date(2024, 1, 15), Time(0, 0, 0)))
march> DateTime.parse("2024-01-15 10:30:45")
Ok(DateTime(Date(2024, 1, 15), Time(10, 30, 45)))
march> DateTime.parse("2024-02-30")
Err("invalid day 30 for 2024-02")
march> DateTime.parse("2024-01-15T10:30:45")
Err("expected ' ' or end after date")
fnparse_offsetparse_offset(s : String) : Result(LocalDateTime, String)#

Parse an ISO 8601 datetime with explicit offset, e.g. "2024-01-15T10:30:45Z" or "2024-01-15 10:30:45+05:30". Returns Ok(LocalDateTime) or Err(msg).

The offset suffix is required: a string without one is an Err (use parse for offset-less UTC strings). The T/space separator is required, and trailing text after the offset is an Err. Offsets may use : (ISO form) or the compact +HHMM form. Civil fields are range-validated, as is the offset itself: an hour outside [0, 24) or a minute outside [0, 60) (e.g. "+01:60") is an Err, not a panic. The parsed civil time is kept as-is (not converted to UTC).

Examples

march> DateTime.parse_offset("2024-01-15T10:30:45Z")
Ok(LocalDateTime(DateTime(Date(2024, 1, 15), Time(10, 30, 45)), Tz("UTC", 0)))
march> DateTime.parse_offset("2024-01-15 10:30:45-0800")
Ok(LocalDateTime(DateTime(Date(2024, 1, 15), Time(10, 30, 45)), Tz("-08:00", -28800)))
march> DateTime.parse_offset("2024-01-15T10:30:45")
Err("expected offset (Z / +HH:MM / -HH:MM) after time")
fnto_timestampto_timestamp(dt)#

Convert a DateTime to a Unix timestamp (Int seconds since 1970-01-01 UTC).

march> DateTime.to_timestamp(DateTime(Date(1970, 1, 1), Time(0, 0, 0)))
0
fnutc_zoneutc_zone() : Tz#

The UTC zone (offset 0, name "UTC"). format_offset renders it as "Z".

Examples

march> DateTime.utc_zone()
Tz("UTC", 0)
fnzone_namezone_name(tz : Tz) : String#

Return the zone's human label (e.g. "UTC", "+05:30", "PST").

Examples

march> DateTime.zone_name(DateTime.fixed_zone_hm(5, 30))
"+05:30"
fnzone_offset_secondszone_offset_seconds(tz : Tz) : Int#

Return the zone's offset from UTC in seconds (east is positive).

Examples

march> DateTime.zone_offset_seconds(DateTime.fixed_zone_hm(-3, 30))
-12600