NAME

Langertha::Moment - Instant on the wire — a Time::Moment that numifies to its Unix epoch

VERSION

version 0.503

SYNOPSIS

my $m = Langertha::Moment->from_wire('2026-05-05T02:53:03.138043625Z');

print 0 + $m;        # 1777949583   — the Unix epoch
print "$m";          # 2026-05-05T02:53:03.138043625Z
print $m->nanosecond;# 138043625
print $m->year;      # 2026 — every Time::Moment method is available

my $o = Langertha::Moment->from_wire(1777949583);   # OpenAI-shaped epoch
my $x = Langertha::Moment->from_wire('garbage');    # undef, never dies

DESCRIPTION

Canonical value object for an instant reported by a provider — currently "created" in Langertha::Response.

Langertha::Moment is a subclass of Time::Moment and inherits its whole API: nanosecond resolution, UTC offsets, comparison, arithmetic. What it adds is an overload set that makes the object drop into the places a plain Unix timestamp used to sit:

  • 0+ yields "epoch" in Time::Moment — whole seconds since the epoch. This is the compatibility contract. Every provider on the OpenAI-compatible wire reports created as an epoch integer, and that is the number "to_hash" in Langertha::Response keeps emitting.

  • "" yields "to_string" in Time::Moment — the full ISO-8601 stamp, sub-seconds included. Nothing is rounded away on the way in, so an Ollama stamp round-trips byte for byte.

  • <=> compares instants. Against another Time::Moment it delegates to "compare" in Time::Moment and is nanosecond-exact; against a plain number it compares whole epoch seconds. The inherited overload would die on the second case ("can only be compared to another Time::Moment object"), which is precisely what code written against the old Int does.

  • cmp compares the ISO-8601 strings.

  • bool is a constant true — an instant that exists is true, including the epoch-0 one. Note this differs from the plain Int the attribute used to hold, where 0 was false; "has_created" in Langertha::Response is the predicate for "did the provider report a stamp at all", and it is unaffected.

Why a subclass rather than an attribute pair

The alternative — keep an Int and hang a second "and here are the nanoseconds" field beside it — is the shape ADR 0011 already rejected once for timing. One value, one object, and the provider's native form stays reachable under "raw" in Langertha::Response.

JSON

TO_JSON returns the epoch number, so an encoder with convert_blessed enabled serializes a moment exactly where an Int used to sit — inside "to_hash" in Langertha::Response and anywhere else a caller drops the value into a structure of their own.

This overrides Time::Moment's own TO_JSON, which returns the ISO-8601 string. Inheriting it would have been the quieter bug of the two: no error, just a field that silently changed from number to string in every trace and log that carries a response. The number is the compatibility surface; the string is one "$moment" away.

An encoder without convert_blessed still dies on the object, as it does on every other value object a Langertha::Response carries (Langertha::Usage, Langertha::ToolCall, Langertha::RateLimit). That is a real break against the old plain Int — see "Comparing and printing created" in Langertha::Response.

TO_JSON

Returns the Unix epoch as a number, so a moment encodes as the plain integer the field held before this class existed. Overrides Time::Moment's TO_JSON, which returns the ISO-8601 string.

from_wire

my $m = Langertha::Moment->from_wire( $value );   # or undef

The lenient inbound constructor: the one entry point that turns whatever a provider put in a timestamp field into a Langertha::Moment, and returns undef — never dies — when it cannot.

Accepts, in this order:

  • an existing Langertha::Moment (returned unchanged), or any other Time::Moment, re-parsed from its canonical string;

  • an epoch number, integer or fractional, as a number or as a digit string — the OpenAI-compatible wire form; a 13-digit value is read as a millisecond epoch and scaled to seconds, keeping its sub-second precision, since no representable seconds epoch reaches that magnitude;

  • an ISO-8601 / RFC3339 string, parsed with "from_string" in Time::Moment in lenient mode, keeping sub-seconds at full precision.

Returns undef for anything else, for a value outside Time::Moment's representable range, and for a stamp in year 1..999 — the band Go-based servers use for their "no timestamp" zero value.

Leniency is the point. A timestamp is metadata; it must never be able to take a whole provider response down with it, which is exactly what happened when "created" in Langertha::Response was a Maybe[Int] and Ollama sent a string (GitHub issue #3, karr #92 / #117). Callers that want a parse failure to be loud should use the inherited "from_string" in Time::Moment or "from_epoch" in Time::Moment directly — those still die.

SEE ALSO

SUPPORT

Issues

Please report bugs and feature requests on GitHub at https://github.com/Getty/langertha/issues.

IRC

Join #langertha on irc.perl.org or message Getty directly.

CONTRIBUTING

Contributions are welcome! Please fork the repository and submit a pull request.

AUTHOR

Torsten Raudssus <getty@cpan.org>

COPYRIGHT AND LICENSE

This software is copyright (c) 2026 by Torsten Raudssus https://raudssus.de/.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.