DateTime

Inherits from Value
Sealed

DateTime — UTC or fixed-offset date and time via Erlang calendar/os modules.

Provides class-side constructors for creating datetime values and instance methods for access, arithmetic, comparison, pattern-based formatting/parsing, and fixed UTC-offset conversion.

year..second are wall-clock fields; offsetMinutes is the UTC offset they are expressed in (0 for UTC). Ordering (<, >, <=, >=), asTimestamp, and arithmetic all operate on the underlying instant, not the raw wall-clock fields. Equality is different: =:=//= cannot be overridden (they always compare the raw representation structurally, so a +02:00 DateTime and its UTC equivalent are NOT =:=) — use sameInstant: for instant-based equality.

Timezone scope: only fixed UTC offsets are supported. OTP ships no IANA tzdata, so named timezones (e.g. "America/New_York", with DST rules) are out of scope here — pass the offset explicitly (from a client or a tzdata lookup elsewhere) to toOffset:.

Examples

now := DateTime now                              // current UTC time
dt := DateTime year: 2026 month: 2 day: 18
dt year                                          // => 2026
dt asString                                      // => "2026-02-18T00:00:00Z"
dt addDays: 7                                    // => DateTime(2026-02-25T00:00:00Z)
offset := DateTime fromString: "2026-07-25T14:30:00+02:00"
offset offsetMinutes                             // => 120
offset toUtc asString                            // => "2026-07-25T12:30:00Z"
offset sameInstant: offset toUtc                 // => true
dt format: "yyyy/MM/dd"                          // => "2026/02/18"

Class Methods

new: _ source

Use 'DateTime year: y month: m day: d', 'DateTime fromTimestamp: ts', or 'DateTime fromString: str' to create a DateTime.

now Sealed source

Current UTC time.

Examples

DateTime now                                   // => DateTime(...)
monotonicNow Sealed source

Monotonic clock value in nanoseconds (for measuring intervals). Returns an Integer, not a DateTime.

Examples

start := DateTime monotonicNow
// ... do work ...
elapsed := DateTime monotonicNow - start       // nanoseconds
year: y month: m day: d Sealed source

Construct from year, month, day (time defaults to 00:00:00).

Examples

DateTime year: 2026 month: 2 day: 18
// => DateTime(2026-02-18T00:00:00Z)
year: y month: m day: d hour: h minute: mi second: s Sealed source

Construct from year, month, day, hour, minute, second.

Examples

DateTime year: 2026 month: 2 day: 18 hour: 10 minute: 30 second: 0
// => DateTime(2026-02-18T10:30:00Z)
year: y month: m day: d hour: h minute: mi second: s offsetMinutes: off Sealed source

Construct from year, month, day, hour, minute, second, and a fixed UTC offset in minutes (e.g. 120 for +02:00, -300 for -05:00).

The wall-clock fields are stored as given, not normalized to UTC — use toUtc to convert.

Examples

DateTime year: 2026 month: 7 day: 25 hour: 14 minute: 30 second: 0 offsetMinutes: 120
// => DateTime(2026-07-25T14:30:00+02:00)
fromTimestamp: ts Sealed source

Construct from Unix epoch timestamp (seconds). Result is UTC (offsetMinutes 0).

Examples

DateTime fromTimestamp: 0
// => DateTime(1970-01-01T00:00:00Z)
fromString: str Sealed source

Parse a strict ISO 8601 string, including an optional UTC offset suffix (Z, or +hh:mm/-hh:mm). Raises #type_error if the string doesn't match.

Examples

DateTime fromString: "2026-02-18T10:30:00Z"
// => DateTime(2026-02-18T10:30:00Z)
DateTime fromString: "2026-07-25T14:30:00+02:00"
// => DateTime(2026-07-25T14:30:00+02:00)
parse: str format: pattern Sealed source

Parse str using the pattern language documented at format: — the fallible, Result-based counterpart to fromString:.

Returns Result ok: dateTime on success, Result error: if str doesn't match pattern or the parsed fields form an invalid date/time. The pattern language has no offset token, so the result is always UTC.

Examples

DateTime parse: "2026-07-25 14:30:00" format: "yyyy-MM-dd HH:mm:ss"
// => Result ok: DateTime(2026-07-25T14:30:00Z)
(DateTime parse: "not a date" format: "yyyy-MM-dd") isOk
// => false

Instance Methods

year source

Year component.

month source

Month component (1-12).

day source

Day component (1-31).

hour source

Hour component (0-23).

minute source

Minute component (0-59).

second source

Second component (0-59).

offsetMinutes source

UTC offset (minutes) that the wall-clock fields are expressed in (0 for UTC).

Examples

(DateTime fromString: "2026-07-25T14:30:00+02:00") offsetMinutes  // => 120
asTimestamp source

Convert to Unix epoch timestamp (seconds) — the instant, honoring offsetMinutes.

asString source

Format as ISO 8601 string: Z suffix for UTC, +hh:mm/-hh:mm otherwise.

Examples

(DateTime year: 2026 month: 2 day: 18) asString
// => "2026-02-18T00:00:00Z"
printString source

Human-readable representation.

toUtc source

Convert to the equivalent UTC (offsetMinutes 0) DateTime, same instant.

Examples

(DateTime fromString: "2026-07-25T14:30:00+02:00") toUtc asString
// => "2026-07-25T12:30:00Z"
toOffset: minutes source

Convert to an equivalent DateTime expressed at a different fixed UTC offset (minutes). Same instant; only the wall-clock fields and offsetMinutes change.

Examples

(DateTime fromString: "2026-07-25T12:30:00Z") toOffset: 120
// => DateTime(2026-07-25T14:30:00+02:00)
format: pattern source

Format the wall-clock fields using a documented pattern language (a subset of CLDR/strftime-style date-time patterns; see parse:format: for the inverse).

Recognized pattern letters — a run of N identical letters controls width: y/yy/yyyy (year: unpadded / last-2-digits / zero-padded to N digits), M/MM, d/dd, H/HH, m/mm, s/ss (month, day, hour 24h, minute, second: unpadded / zero-padded to N digits). Any other character (-, :, T, , ...) passes through literally. No offset token — use asString/fromString: for offset-aware ISO 8601 formatting. For parse:format:, put a literal separator between two adjacent unpadded numeric tokens (e.g. M/d, not Md) — without one, an unpadded field greedily consumes digits that belong to its neighbor. yy always parses as 2000 + NN — it does not round-trip years outside 2000-2099; use yyyy for those.

Examples

(DateTime year: 2026 month: 7 day: 25 hour: 14 minute: 30 second: 0)
  format: "yyyy-MM-dd HH:mm:ss"
// => "2026-07-25 14:30:00"
addSeconds: secs source

Add seconds, return new DateTime.

Examples

(DateTime year: 2026 month: 1 day: 1) addSeconds: 3600
// => DateTime(2026-01-01T01:00:00Z)
addDays: days source

Add days, return new DateTime.

Examples

(DateTime year: 2026 month: 1 day: 1) addDays: 7
// => DateTime(2026-01-08T00:00:00Z)
addDuration: d source

Add a Duration, return a new DateTime.

DateTime has second resolution; any sub-second milliseconds in the Duration are truncated toward zero.

Examples

(DateTime year: 2026 month: 1 day: 1) addDuration: (Duration hours: 1)
// => DateTime(2026-01-01T01:00:00Z)
diffSeconds: other source

Difference in seconds between this and another DateTime.

Examples

a := DateTime year: 2026 month: 1 day: 2
b := DateTime year: 2026 month: 1 day: 1
a diffSeconds: b                               // => 86400
- other source

Difference between this and another DateTime as a Duration.

Positive when this DateTime is later than other. Stays inline FFI (not self delegate, ADR 0101 / BT-2731): an operator selector is not a valid Erlang function name, so it dispatches through the subtract shim.

Examples

a := DateTime year: 2026 month: 1 day: 2
b := DateTime year: 2026 month: 1 day: 1
(a - b) asHours                                // => 24
< other source

True if this DateTime is earlier than other.

Examples

(DateTime year: 2025 month: 1 day: 1) < (DateTime year: 2026 month: 1 day: 1)  // => true
> other source

True if this DateTime is later than other.

Examples

(DateTime year: 2026 month: 1 day: 1) > (DateTime year: 2025 month: 1 day: 1)  // => true
<= other source

True if this DateTime is earlier than or equal to other.

Examples

(DateTime year: 2026 month: 1 day: 1) <= (DateTime year: 2026 month: 1 day: 1)  // => true
>= other source

True if this DateTime is later than or equal to other.

Examples

(DateTime year: 2026 month: 1 day: 1) >= (DateTime year: 2026 month: 1 day: 1)  // => true
sameInstant: other source

True if this DateTime represents the same instant as other, regardless of offsetMinutes. A plain named method, not an operator override — =:= cannot be overridden (see the comment above); use this instead for instant-based equality.

Examples

dt sameInstant: dt    // => true
(DateTime fromString: "2026-07-25T12:00:00Z")
  sameInstant: (DateTime fromString: "2026-07-25T14:00:00+02:00")
// => true
differentInstant: other source

True if this DateTime does not represent the same instant as other. The negation of sameInstant:.

Examples

(DateTime year: 2026 month: 1 day: 1)
  differentInstant: (DateTime year: 2026 month: 1 day: 2)
// => true

Inherited Methods

From Value

printString

Return a developer-readable string representation showing fields.

Produces ClassName(field: value, ...) via the canonical structural renderer (ADR 0094). Field values are rendered with their own printString (strings stay quoted, nested values show their structural form), in sorted field order. A class with no fields produces ClassName(). Recursion is bounded by depth/width/length caps with a cycle guard.

Examples

ValuePoint x: 3 y: 4        printString   // => "ValuePoint(x: 3, y: 4)"
ValuePoint new              printString   // => "ValuePoint(x: 0, y: 0)"

From Object

class

Return the class of the receiver.

Examples

42 class              // => Integer
"hello" class         // => String
isNil

Test if the receiver is nil. Returns false for all objects except nil.

Examples

42 isNil              // => false
nil isNil             // => true
notNil

Test if the receiver is not nil. Returns true for all objects except nil.

Examples

42 notNil             // => true
nil notNil            // => false
ifNil: _nilBlock

If the receiver is nil, evaluate nilBlock. Otherwise return self.

_nilBlock is never evaluated on this Object-level definition (only UndefinedObject's override calls it, with Block(R) -> R), so R is unconstrained here — parameterizing it still documents the zero-arg shape without implying this branch produces R (BT-2834).

Examples

42 ifNil: [0]         // => 42
nil ifNil: [0]        // => 0
ifNotNil: notNilBlock

If the receiver is not nil, evaluate notNilBlock with self.

Left as a bare Block deliberately (BT-2834): notNilBlock is invoked one-arg with self and its result becomes the method's own result, so a precise signature needs a Self-in-Block(...) type-arg form (Block(Self, R) -> R) with no precedent elsewhere in stdlib. The type checker doesn't consult this declared signature for ifNotNil: anyway — it narrows the block's parameter from the receiver's own type via infer_args_for_if_not_nil in inference.rs (BT-2046), independent of this stdlib declaration.

Examples

42 ifNotNil: [:v | v + 1]   // => 43
nil ifNotNil: [:v | v + 1]  // => nil
ifNil: _nilBlock ifNotNil: notNilBlock

If nil, evaluate nilBlock; otherwise evaluate notNilBlock with self.

Bare Block params here (unlike UndefinedObject's Block(R) -> R override, BT-2824) are intentional, not an oversight: the type checker never consults this declared signature for these two selectors — it reads the block arguments' actual inferred return types directly (if_nil_branch_union_ret_ty in inference.rs, BT-2047) since a bare -> R here can't express "whatever the block returns" without a Self-in-Block(...) type-arg form with no precedent elsewhere in stdlib.

Examples

42 ifNil: [0] ifNotNil: [:v | v + 1]    // => 43
nil ifNil: [0] ifNotNil: [:v | v + 1]   // => 0
ifNotNil: notNilBlock ifNil: _nilBlock

If not nil, evaluate notNilBlock with self; otherwise evaluate nilBlock.

See ifNil:ifNotNil: above — the bare Block params are intentional.

Examples

42 ifNotNil: [:v | v + 1] ifNil: [0]    // => 43
nil ifNotNil: [:v | v + 1] ifNil: [0]   // => 0
printString

Return the developer-readable (Debug) string representation.

printString is the Debug protocol (ADR 0094): the self-describing, structural form used by the REPL, logs, and by any other printString that nests this object. It is the REPL default — evaluating an expression shows its printString.

This default returns the bare class name (no a/an article — the old "a ClassName" form was dropped in ADR 0094). Value overrides it with the structural ClassName(field: value, ...) form, actors render as Actor(ClassName, pid), supervisors as Supervisor(ClassName, pid) / DynamicSupervisor(ClassName, pid), and primitive types (Integer, String, List, …) override it with their own richer output. Authors rarely override printString directly — the default is derived.

Examples

42 printString            // => "42"
displayString

Return the user-facing (Display) string representation.

displayString is the Display protocol (ADR 0094): the human-facing form. It is the hook the language pulls during string interpolation — every {...} segment renders via the value's displayString. Developers rarely call it directly; they override it when a value has a natural human rendering (e.g. Money$10.50, where printString would still show the Debug form).

It defaults to printString, so most types need no override. String and Symbol demonstrate the split: "hi" printString"\"hi\"" (quoted, Debug) while "hi" displayString"hi" (plain, Display); likewise #foo drops its # prefix under displayString.

displayString is not part of the Printable protocol (deferred per ADR 0094 §5).

Examples

42 displayString             // => "42"
inspect

Open a navigable Inspector cursor on the receiver.

ADR 0095 Phase 3 (BT-2504). inspect is repurposed from -> String (the ADR-0094 deferral) to the verb that produces an Inspector — a live, immutable cursor for drilling into the object (Inspector on: self). anObject inspect is the shorthand; Inspector on: anObject is the explicit spelling. The cursor exposes fields/at:/path/refresh/ printString (an indented text tree) and asDictionaries (the MCP/browser wire form); see Inspector.

This is a breaking change: code that used inspect for its old String result must switch to printString (the structural Debug string, ADR 0094) — a transitional lint flags inspect used directly in ++/ string position.

Examples

42 inspect kind                  // => #value
(Point x: 3 y: 4) inspect fields size   // => 2
(Point x: 3 y: 4) printString    // => "Point(x: 3, y: 4)"  (the old inspect string)
yourself Sealed

Return the receiver itself. Useful for cascading side effects.

Examples

42 yourself            // => 42
hash

Return a hash value for the receiver.

Examples

42 hash
equals: other

Test value equality — the overridable counterpart to =:=.

Defaults to =:= (Erlang structural term equality), so for most classes the two agree. Unlike =:=, this is an ordinary message send, so a class whose logical value is not its representation can override it: a persistent structure whose shape depends on construction history, a timestamp that should compare by instant rather than by wall-clock fields, or a type with a normalising form. =:=, =/=, == and /= are lowered straight to Erlang BIFs (ADR 0002) and cannot be overridden — the compiler rejects any attempt (BT-2997).

What honours it

The linear scans: includes: on Collection, List, Array and Dictionary (which searches values), plus List>>indexOf: and TestCase>>assert:equals:.

Contract

An override must agree with =:= wherever =:= holds — a =:= b must imply a equals: b. It may only make more values equal, never fewer. The scans above rely on this: each tries raw =:= first and dispatches only on a miss, so a fast-path hit is always a genuine equals: hit.

Caveat

Set, Dictionary keys, and List>>unique decide identity in the VM with raw =:=, never through this method — keying and deduplication need an order or a hash that a user-defined equals: cannot supply. A class that overrides equals: still deduplicates by representation when used as a key or element. Normalise the representation if that matters.

Examples

42 equals: 42           // => true
42 equals: 42.0         // => false (defaults to strict `=:=`)
"ab" equals: "ab"       // => true
respondsTo: selector Sealed

Test if the receiver responds to the given selector.

Examples

42 respondsTo: #abs    // => true
fieldNames Sealed

Return the names of fields.

Examples

42 fieldNames             // => #()
fieldAt: name Sealed

Return the value of the named field.

Examples

object fieldAt: #name
fieldAt: name put: value Sealed

Set the value of the named field (returns new state).

Examples

object fieldAt: #name put: "Alice"
perform: selector Sealed

Send a unary message dynamically.

Examples

42 perform: #abs       // => 42
perform: selector withArguments: args Sealed

Send a message dynamically with arguments.

Examples

3 perform: #max: withArguments: #(5)   // => 5
subclassResponsibility

Raise an error indicating this method must be overridden by a subclass.

Examples

self subclassResponsibility
notImplemented

Raise an error indicating this method has not yet been implemented.

Use this for work-in-progress stubs. Distinct from subclassResponsibility, which signals an interface contract violation.

Examples

self notImplemented
show: aValue

Send aValue to the current transcript without a trailing newline.

Nil-safe: does nothing when no transcript is set (batch compile, tests).

Examples

42 show: "value: "
showCr: aValue

Send aValue to the current transcript followed by a newline.

Nil-safe: does nothing when no transcript is set (batch compile, tests).

Examples

42 showCr: "hello world"
isKindOf: aClass

Test if the receiver is an instance of aClass or any of its subclasses.

For class-object receivers, follows Smalltalk semantics: self class is the metaclass, so the check walks the parallel metaclass hierarchy. The parallel chain is grounded at ProtoObject class superclass == Class (ADR 0036), so the metaclass tower merges into the instance-side Class → Behaviour → Object → ProtoObject chain. As a result, Integer isKindOf: Object and Integer isKindOf: Class both return true.

Examples

42 isKindOf: Integer        // => true
42 isKindOf: Object         // => true
#foo isKindOf: Symbol       // => true
#foo isKindOf: String       // => false
Integer isKindOf: Number    // => false (metaclass chain, not instance chain)
Integer isKindOf: Number class  // => true  (Number class is in the parallel chain)
Integer isKindOf: Object    // => true (grounded — Object is reachable via the metaclass tower)
Integer isKindOf: Class     // => true (Integer class inherits from Class)
error: message

Raise an error with the given message.

Examples

self error: "something went wrong"
delegate Sealed

Delegate message dispatch to the backing Erlang module (ADR 0101, BT-2720).

This method is a sentinel — a plain Object has no backing Erlang module, so calling delegate raises an Error at runtime. Stateless Objects declared with native: have their self delegate method bodies rewritten by the compiler's codegen phase to call the backing module directly, so the sentinel is never reached on a native: class.

Unlike Actor's delegate (visible only to Actor subclasses), this Object-base sentinel is visible to every class, so delegate is a reserved selector on the Object protocol.

Examples

42 delegate   // => ERROR: delegate called on a non-native Object

From ProtoObject

== other

Test value equality (Erlang ==, non-strict — 1 == 1.0 is true).

Examples

42 == 42           // => true
"abc" == "abc"     // => true
/= other

Test value inequality (negation of ==).

Examples

1 /= 2             // => true
42 /= 42           // => false
=:= other

Test strict value equality (Erlang =:=1 =:= 1.0 is false, unlike ==).

Every object supports this at runtime (it lowers directly to Erlang's =:= operator regardless of receiver type, ADR 0002) — declared here, once, so it is visible in method listings/completions/respondsTo: for every class, not just the handful (Integer, String, ...) that separately redeclare it with a narrower, class-typed other for documentation purposes.

Examples

1 =:= 1             // => true
1 =:= 1.0           // => false (strict — different types)
(Dictionary new) =:= #{}   // => true
=/= other

Test strict value inequality (negation of =:=).

Examples

1 =/= 1.0           // => true (strict — different types)
1 =/= 1             // => false
class

Return the class of the receiver.

Examples

42 class            // => Integer
"hello" class       // => String
doesNotUnderstand: selector args: arguments

Handle messages the receiver does not understand. Override for custom dispatch.

Examples

42 unknownMessage   // => ERROR: does_not_understand
perform: selector withArguments: arguments

Send a message dynamically with an arguments list.

Examples

42 perform: #abs withArguments: #()   // => 42
performLocally: selector withArguments: arguments

Execute a class method in the caller's process, bypassing gen_server dispatch.

The caller takes responsibility for knowing the method does not mutate class state. Useful for long-running class methods that would otherwise block the class object's gen_server.

Limitations: only resolves methods defined directly on the target class module (does not walk the superclass chain). Class variables and self are not available to the method (nil and #{} are passed).

Examples

MyClass performLocally: #run:ctx: withArguments: #(input, ctx)
perform: selector withArguments: arguments timeout: timeoutMs

Send a message dynamically with an arguments list and explicit timeout.

The timeout (in milliseconds or #infinity) applies to the gen_server:call when the receiver is an actor. For value types, timeout is ignored.

Examples

actor perform: #query withArguments: #(sql) timeout: 30000