ApiDateTime
final class ApiDateTime (View source)
An immutable, UTC-only date/time value parsed from an API response.
The Team Internet APIs declare their date columns in UTC and emit exactly two
shapes: a full timestamp (2026-07-25 07:46:34, optionally with a fractional
second part, as CNR sends) and a bare calendar date (2030/07/17, as
IBS/Moniker send — accepted directly, rather than being rewritten upstream).
This class parses both into one flat struct and does nothing else — it is a
parser, not a formatter. There is no in($tz), no locale formatting and
no ext-intl dependency; presenting a value in the viewer's timezone is a
display concern and belongs in the consuming application.
The date separator may be - or /, but must be consistent within one
value — 2026-02/20 and 2026/02-20 are both rejected. {\CNIC\self::$date}
and {\CNIC\self::$dateTime} always emit - regardless of the input
separator, so a consumer never has to branch on which one the source used.
Responses are not rewritten to use this type. getPlain(), getHash()
and getListHash() keep returning the raw API strings; this parser is opt-in
at the point where a value is actually used.
A bare calendar date names no instant, so {\CNIC\self::$ts} and
{\CNIC\self::$dateTime} are both null for one — deliberately, instead of
defaulting to midnight, which would be an invented instant a consumer could
not tell apart from a real one. {\CNIC\self::$date} is always populated.
{\CNIC\self::$raw} keeps the original input string exactly as given — the
only place the discarded fractional-second precision, or the source's own
separator, survives. It is for display, logging and round-trip fidelity
only; comparison and sorting must use $ts or $date.
$dt = \CNIC\ApiDateTime::from("2026-07-25 07:46:34");
$dt->ts; // 1784965594
$dt->date; // "2026-07-25"
$dt->dateTime; // "2026-07-25 07:46:34"
$dt->raw; // "2026-07-25 07:46:34"
// Presenting it elsewhere is the caller's job:
(new \DateTimeImmutable("@{$dt->ts}"))->setTimezone(new \DateTimeZone("Europe/Berlin")); Constants
| private TIMEZONE |
The only timezone this type ever represents. The API declares UTC and the
parser refuses offset-bearing input, so the value can never be anything
else — see {self::$tz}. |
| private PATTERN |
Shape gate for the two accepted formats, anchored at both ends. This runs before The date separator may be The trailing |
Properties
| int|null | $ts | Unix timestamp of the instant, or |
|
| string | $date | The calendar date as |
|
| string|null | $dateTime | The full timestamp as |
|
| string | $tz | Timezone of the source declaration — always |
|
| string | $raw | The original input string, exactly as passed to {self::from()} /
{self::tryFrom()} — no separator normalisation, no fractional-second
stripping. Before the |
Methods
Parse an API date/time value.
Null-tolerant, non-throwing counterpart of {self::from()}.
Whether the source value was a bare calendar date, naming no instant.
The value as a plain array — ready for json_encode() and for handing to
a template or frontend.
Details
static ApiDateTime
from(string $value)
Parse an API date/time value.
Accepts Y-m-d H:i:s / Y/m/d H:i:s (with an optional fractional-second
part, which is discarded) and Y-m-d / Y/m/d — the separator may be
either, as long as it is the same one twice. Anything else throws —
including values PHP would otherwise roll over silently, such as
2026-02-30 (which createFromFormat() turns into 2026-03-02) or
0000-00-00.
static ApiDateTime|null
tryFrom(string|null $value)
Null-tolerant, non-throwing counterpart of {self::from()}.
Returns null for a null input and for anything from() would reject.
Use it for optional columns that may be absent or empty; use from()
when an unparsable value is a bug you want to hear about.
bool
isDateOnly()
Whether the source value was a bare calendar date, naming no instant.
Equivalent to $dt->ts === null.
array
toArray()
The value as a plain array — ready for json_encode() and for handing to
a template or frontend.