Dates and times¶
This tutorial builds a small subscription schedule with std.datetime. It uses UTC clocks for "now", civil values for dates users type into forms, fixed offsets for external timestamps, and interval types for arithmetic.
Import the pieces¶
Most application code can import from std.datetime directly:
from std.datetime import (
Date,
DateTime,
DateTimeError,
DateTimeOffset,
FixedOffset,
Time,
TimeDelta,
YearMonthInterval,
)
std.datetime re-exports the public runtime and civil APIs. Reach for submodules only when you are reading stdlib source or intentionally keeping imports narrow.
Read UTC from the host clock¶
Use UTC factories when you need a civil date or datetime anchored to the host system clock:
from std.datetime import Date, DateTime
def print_clock() -> None:
println(Date.utc_today().isoformat())
println(DateTime.utc_now().isoformat())
These helpers deliberately return UTC civil values. Named timezones such as Europe/Amsterdam are not part of std.datetime; they require rule data that belongs in separately versioned packages.
Parse user input¶
Civil constructors return Result because calendar data can be invalid:
from std.datetime import Date, DateTimeError, Time
def read_start_date(value: str) -> Result[Date, DateTimeError]:
return Date.fromisoformat(value)
def read_cutoff_time(value: str) -> Result[Time, DateTimeError]:
return Time.fromisoformat(value)
fromisoformat is the best default for machine-readable input. It accepts values such as "2026-04-14" and "12:34:56.123456789".
Add days and months¶
Use TimeDelta for fixed day/time movement and YearMonthInterval when the unit is calendar months or years:
from std.datetime import Date, DateTimeError, TimeDelta, YearMonthInterval
def trial_end(start: str) -> Result[Date, DateTimeError]:
signup = Date.fromisoformat(start)?
return Ok(signup + TimeDelta.days(14))
def renewal_date(start: str, months: int) -> Result[Date, DateTimeError]:
signup = Date.fromisoformat(start)?
return Ok(signup + YearMonthInterval.months(months))
The distinction matters. Seven days is a fixed amount of elapsed civil days. One month is a calendar operation whose day count depends on the month and year.
Combine date and time¶
DateTime is a naive civil datetime. It has date and time fields but no offset or named timezone:
from std.datetime import Date, DateTime, DateTimeError, Time
def renewal_cutoff(date_text: str, time_text: str) -> Result[DateTime, DateTimeError]:
date = Date.fromisoformat(date_text)?
time = Time.fromisoformat(time_text)?
return Ok(DateTime.combine(date, time))
Use a naive DateTime for calendar records where the timezone lives somewhere else in the domain model, or where the value is intentionally local civil time.
Format for people and protocols¶
All civil values support isoformat(). Use strftime(...) when a protocol, log line, or user-facing display needs a specific shape:
from std.datetime import DateTime, DateTimeError
def render_stamp(stamp: DateTime) -> Result[None, DateTimeError]:
println(stamp.isoformat())
println(stamp.strftime("%a %b %_d %H:%M:%S.%f %Y")?)
return Ok(None)
The format surface is Python-shaped. Incan's %f is intentionally wider than Python's: it formats and parses nine nanosecond digits. The full directive table is in the std.datetime reference.
Emit a fixed-offset timestamp¶
Use DateTimeOffset when an external system needs a concrete UTC offset in the timestamp:
from std.datetime import DateTime, DateTimeError, DateTimeOffset, FixedOffset
def stamp_for_amsterdam_winter(local: DateTime) -> Result[str, DateTimeError]:
offset = FixedOffset.hours(1)?
stamp = DateTimeOffset(datetime=local, offset=offset)
return stamp.strftime("%F %T.%f%:z")
This stores +01:00, not the name Europe/Amsterdam. A named timezone package can decide which fixed offset applies to a named zone at a specific instant.
Put it together¶
This function parses a signup date, adds one calendar month, combines the result with a cutoff time, and serializes the timestamp with a fixed offset:
from std.datetime import (
Date,
DateTime,
DateTimeError,
DateTimeOffset,
FixedOffset,
Time,
YearMonthInterval,
)
def first_invoice_stamp(signup_text: str) -> Result[str, DateTimeError]:
signup = Date.fromisoformat(signup_text)?
due_date = signup + YearMonthInterval.months(1)
cutoff = Time.fromisoformat("17:00:00")?
local_stamp = DateTime.combine(due_date, cutoff)
offset_stamp = DateTimeOffset(datetime=local_stamp, offset=FixedOffset.utc())
return offset_stamp.strftime("%Y-%m-%dT%H:%M:%S.%f%z")
The result is explicit about every temporal decision: the signup date is civil, the monthly renewal is calendar-based, the cutoff is a wall-clock time, and the serialized timestamp is fixed-offset UTC.