datetime module
Provides five classes: date, datetime, time, timedelta,
timezone. The module-level tzinfo, MINYEAR / MAXYEAR symbols are
not exposed.
Error messages name date, timedelta and timezone without their
module, where CPython qualifies them.
time(0, tzinfo=timedelta(hours=1)) reports not type 'timedelta',
time(1) < date(2020, 1, 1) ends and 'date', and
datetime.combine(date(2020, 1, 1), date(2020, 1, 1)) reports
not date — CPython prefixes datetime. to each.
datetime and time are qualified.
Passing None where an integer component is expected reports 'None'
rather than CPython’s 'NoneType' (time(None), datetime(None, 1, 1)).
min, max and resolution are defined on date, datetime, time
and timedelta, and min / max / utc on timezone. CPython caches
each constant, so date.min is date.min is True there and False
here: every access allocates a new object. Equality and ordering are
unaffected. timezone.utc is the exception — it is a singleton on both,
so timezone.utc is timezone.utc holds.
date.fromisoformat, datetime.fromisoformat and time.fromisoformat
all parse with speedate, which accepts a
narrower grammar than CPython 3.11+:
- The compact forms are rejected (
time.fromisoformat('123005'),datetime.fromisoformat('20200101T123005')). - A leading
Tis rejected (time.fromisoformat('T12:30')). - Sub-minute UTC offsets are rejected
(
'12:30:05+01:00:30'), even though the same offset is accepted from thetimezoneconstructor. - More than 6 fractional-second digits are rejected rather than
truncated (
'12:30:05.1234567').
Every rejection raises ValueError: Invalid isoformat string: '...'.
CPython instead reports the offending component for a syntactically
valid string with an out-of-range value: time.fromisoformat('25:00')
raises hour must be in 0..23, not 25 there, and
'12:30:05+99:00' raises the offset must be a timedelta strictly between ... message.
Constructor: date(year, month, day).
Attributes: year, month, day.
Methods: isoformat, strftime, replace, weekday, isoweekday.
Class methods supported: today(), fromisoformat().
fromisocalendar(), fromtimestamp(), fromordinal() are not
implemented. strptime(), added in CPython 3.14, is not implemented
either.
today() reads the clock, so it depends on the embedder granting one —
see “Reading the clock” below.
Constructor overflow wording on Windows: CPython’s i converter goes
through C long, which is 32 bits on Windows, so date(2**40, 1, 1)
raises OverflowError: Python int too large to convert to C long there,
while 64-bit-long platforms raise the sign-aware signed integer is greater than maximum / less than minimum.
Monty’s ints are i64 on
every host, so it always uses the 64-bit wording, matching CPython on
Linux/macOS but not on Windows. Same for datetime; values wider than
i64 raise the C long message on all platforms, matching CPython.
Constructor: datetime(year, month, day, hour=0, minute=0, second=0, microsecond=0, tzinfo=None, *, fold=0). fold is
accepted and validated
(must be 0 or 1) for CPython argument-parsing parity but does not affect
the stored value: Monty does not track DST-fold disambiguation.
Attributes: year, month, day, hour, minute, second,
microsecond, tzinfo.
Methods: isoformat(sep='T', timespec='auto'), strftime, replace,
weekday, isoweekday, date, time, timetz, timestamp, astimezone(tz=None),
utcoffset, tzname, dst.
astimezone() and a naive timestamp() read the session zone where CPython uses the host’s;
as in CPython the tzinfo astimezone() attaches is a timezone carrying the offset and abbreviation at that
instant (BST).
The default zone is timezone(timedelta(0), 'UTC'), what CPython reports under TZ=UTC, so
datetime.now().astimezone().tzinfo == timezone.utc holds but it is not the timezone.utc singleton.
A naive value is read in the session zone, as CPython reads it in the host’s, at its first occurrence in a DST fold
and with the offset from before a DST gap: CPython’s fold=0 reading, since fold is not stored (below).
dt.astimezone(dt.tzinfo) returns an equal copy, not dt itself.
fold is not readable: datetime(2020, 1, 1, fold=1).fold raises
AttributeError, where CPython returns 1.
Class methods supported: now(tz=None), strptime(date_string, format),
fromisoformat(date_string), combine(date, time, tzinfo=self.tzinfo).
now()reads the clock — see “Reading the clock” below.- Host-answered
now(tz)reconstructstzinfofrom its offset and name, so it equalstzbut is a different object. The default sandbox answer preserves identity, as CPython does. strptime()requires the string to carry a date: a time-only format (strptime('12:30', '%H:%M')) raisesValueError: time data '12:30' does not match format '%H:%M', where CPython defaults the missing date to 1900-01-01.strptime()’s%ztakes a sign, hours and minutes with an optional colon, optional seconds, or a bareZ, but not CPython’s fractional form:'+010203.123456'does not match, where CPython attaches an offset carrying those microseconds, which no Monty offset can hold (seetimezone).- A
%zoffset whose minute and second separators disagree is rejected by both, but'+0102:03'(a colon before the seconds only) raisesValueError: Inconsistent use of : in +0102:03in Monty, where CPython leaksValueError: invalid literal for int() with base 10: ':0'. The mirrored'+01:0203'matches CPython exactly. - Input the format does not consume raises
ValueError: time data '...' does not match format '...', where CPython distinguishes trailing input withValueError: unconverted data remains: .... utcnow()(the deprecated class method) andtoday()are not implemented.fromtimestamp(),fromordinal()andutcfromtimestamp()are not implemented.time()andtimetz()return atimewhosefoldis always 0, sincedatetimedoes not store the flag (above).combine()likewise discards thefoldof thetimeit is given, where CPython carries it over.
Subclassing datetime is not possible, since there is no class inheritance
(see classes.md).
datetime.replace(), date.replace() and time.replace() accept only
keyword arguments in Monty. CPython accepts positional args too
(d.replace(2025) is valid in CPython 3.14). Calling with positionals
in Monty raises TypeError: replace expected at most 0 arguments, got N.
The session clock has separate datetime and timezone settings.
The time module’s wall clocks share the datetime source, and its conversion functions the timezone
(see time.md).
datetime:
- A fixed instant never advances: repeated
datetime.now()calls are equal and elapsed-time calculations stay zero. Bindings also settimezoneunless supplied explicitly: naive Python datetimes and JavaScript dates select UTC; aware Python datetimes supply their offset and name. A Rust fixed instant outside years 1–9999 raisesOverflowError: date value out of rangefrom all three clock calls. 'call_host'delegates to the pool’sos=handler (OSAccess.date_today()ordatetime_now()in Python). Unanswered calls raiseRuntimeError: 'date.today' is not supported in this environment(datetime.nowlikewise). Non-suspending Rust execution instead raisesNotImplementedError.runandfeed_runreportOS function 'datetime.now' not implemented with standard execution;call_functionreportsMontyRepl::call_function: OS function 'datetime.now' is not yet supported in this context.
timezone:
- The default is UTC, not the host’s zone, so a naive
datetime.now()is the UTC wall clock andastimezone()attachestimezone(timedelta(0), 'UTC'). - A fixed zone is a UTC offset with an optional name and no DST rules.
astimezone(),%Zandtime.tznamereport the name (UTC±HH:MMwhen there is none). - An IANA name (
'Europe/London') is resolved in the worker against its tz database: the OS copy underTZDIRor/usr/share/zoneinfo, or the copy bundled into the binary when the worker can read none (Windows, and the wasm worker). The offset and abbreviation then follow the instant, so the results depend on that database’s version, as CPython’s do on the host’s. An unknown name is refused when the session is checked out.time.timezone,time.altzone,time.daylightandtime.tznameare computed at import and need the clock’s year, so a named zone underdatetime='call_host'leaves them absent; see time.md.
Constructor: time(hour=0, minute=0, second=0, microsecond=0, tzinfo=None, *, fold=0).
Attributes: hour, minute, second, microsecond, tzinfo, fold.
Methods: isoformat(timespec='auto'), strftime, replace,
utcoffset, tzname, dst.
Class methods: fromisoformat(time_string). strptime(string, format)
— which CPython added in 3.14 — is not implemented and raises
AttributeError. datetime.strptime is not a workaround for a time-only
format: it requires the string to carry a date, where CPython defaults the
missing one to 1900-01-01.
tzinfo accepts only None or a built-in timezone instance. The
tzinfo ABC is not implemented, so custom subclasses are rejected, the
same restriction as datetime.
fold is stored and reported by .fold and repr(), and survives the host
boundary, but is never read: Monty has no DST model, so it cannot use the flag
to pick between the two readings of a repeated wall clock. As in CPython, fold
is excluded from == and hash(), and omitted by isoformat().
Ordering an aware time against a naive one raises
TypeError: '<' not supported between instances of 'datetime.time' and 'datetime.time', where CPython raises
TypeError: can't compare offset-naive and offset-aware times. == returns False without
raising, matching CPython. The same wording divergence applies to
datetime.
A host datetime.time carrying a tzinfo that is not a datetime.timezone
(a ZoneInfo, say) is rejected with cannot convert datetime.time with tzinfo of type '...' to a Monty value. A bare
time has no instant to resolve
a named zone against — CPython’s own t.utcoffset() returns None there —
so there is no offset to carry. An aware datetime is not affected: it has a
date, and its zone resolves through utcoffset(dt).
Constructor: timedelta(days=0, seconds=0, microseconds=0, *, milliseconds=0, minutes=0, hours=0, weeks=0).
milliseconds,
minutes, hours, and weeks are keyword-only in Monty; CPython accepts
all seven positionally.
Attributes: days, seconds, microseconds.
Methods: total_seconds.
A non-int component raises TypeError: '{type}' object cannot be interpreted as an integer; CPython names the offending
component instead
(unsupported type for timedelta days component: str).
Arithmetic (+, -, *, /, //, %, divmod, comparisons) works
between timedeltas and between datetime/date and timedelta.
Constructor: timezone(offset, name=None) where offset is a
timedelta.
Attributes: none.
Methods: utcoffset(dt), tzname(dt), dst(dt). The dt argument is
validated and then ignored, since the offset is the same at every
instant.
fromutc(dt) is not implemented, and neither is the abstract tzinfo
base class. Only fixed offsets can be constructed, so on timezone,
datetime and time alike, utcoffset() is constant over time and
dst() is always None.
An offset is a whole number of seconds: timezone(timedelta(seconds=1, microseconds=1)) raises
ValueError: offset must be a timedelta representing a whole number of seconds, CPython’s own message before 3.7,
where CPython now accepts it.
One error-ordering corner: timezone('x', offset=td) (a non-timedelta
positional and an offset kwarg) raises the name-and-position conflict in
Monty, but the type error in CPython (timezone() argument 1 must be datetime.timedelta, not str). CPython’s parser
type-checks offset while
binding, whereas Monty validates the timedelta in the constructor body
after binding completes.
strftime supports the directives that map onto Rust’s chrono
formatting; locale-specific directives (%c, %x, %X, %p) follow
Rust’s defaults rather than the C locale and may differ from CPython.
An unrecognised directive is passed through verbatim, matching glibc/Linux
CPython (strftime('%Q') == '%Q', strftime('%') == '%'). This is a choice
of one CPython, not all of them: macOS CPython instead drops the %
(strftime('%Q') == 'Q'), because unknown-directive handling is delegated to
the platform C library and is genuinely platform-dependent. The same
pass-through applies to f-string and str.format() formatting (below).
A directive that chrono recognises but can’t render for the given value
raises ValueError: Invalid format string where CPython hands it to the C
library: %+ (chrono’s RFC 3339 form) and %#z (chrono’s hour-only offset)
both need an offset the naive components lack, so they raise even on an aware
value’s naive components. The other flagged forms of %z (%-z, %_z,
%Ez, %Oz) are unrecognised and pass through verbatim, where macOS CPython
renders them as an empty string.
%z, %:z and %Z are filled from utcoffset() and tzname() as in
CPython: empty for a naive date, datetime or time, and '+0200',
'+02:00' and the zone name for an aware value. The name of an unnamed zone
is UTC±HH:MM, and a sub-minute offset renders its seconds ('+023015').
f-strings and str.format() format date, datetime and time values through
strftime, matching CPython’s __format__: f'{dt:%Y-%m-%d}' and
'{:%Y-%m-%d}'.format(dt) are equivalent to dt.strftime('%Y-%m-%d'), and
an empty spec uses str(dt). One edge-case divergence remains for a literal
f-string spec that is also a valid format mini-language spec (e.g.
f'{dt:>10}' or a lone f'{dt:%}'): Monty applies generic string formatting,
where CPython treats the entire spec as a strftime string. Dynamically built
f-string specs and str.format() specs are handed to strftime.