NAME

Developer::Dashboard::IsoTimestamp - shared ISO-8601-to-epoch parser

SYNOPSIS

use Developer::Dashboard::IsoTimestamp qw(_iso8601_to_epoch);
my $epoch = _iso8601_to_epoch( '2026-09-16T00:00:00Z', on_error => 'die' );
my $epoch = _iso8601_to_epoch( $maybe_bad, on_error => 'zero' );

DESCRIPTION

Provides _iso8601_to_epoch, the single home for a timestamp-parsing helper that used to be written out twice across Developer::Dashboard::Collector and Developer::Dashboard::SessionStore (DD-904) - the same "small helper reimplemented per file instead of shared" pattern this project already fixed in Developer::Dashboard::DirEntries (DD-762), Developer::Dashboard::TextUtils (DD-891), Developer::Dashboard::TimeUtils (DD-894), and others.

PURPOSE

Give every module that needs to convert an ISO-8601 timestamp string into UTC epoch seconds one canonical implementation to call, supporting the union of both format surfaces this project's timestamps actually use (bare Z suffix and a numeric timezone offset), rather than each maintaining its own private copy that can silently drift.

WHY IT EXISTS

Collector.pm needed the full timezone grammar (Z, compact and colon-separated offsets) and dies on anything else, because a malformed timestamp in its own self-generated collector log format indicates real corruption that should be loud. SessionStore.pm needed only the Z form and silently returned 0 on anything else - not a bug, but DD-764's deliberate, documented fail-closed session-expiry behavior: a missing or malformed expires_at must be treated as already-expired, never as "never expires". Both call sites relying on that 0 (the cookie-based expiry check and the housekeeper's stale-session cleanup sweep) depend on it exactly as written.

This is the same "explicit parameter, not a collapsed default" shape as TimeUtils.pm's tz parameter, applied to an error-handling contract rather than an output format: on_error is required with no default, so every caller states which behavior it needs rather than inheriting whichever caller's convention got extracted first.

WHEN TO USE

Any module in this codebase that needs to convert an ISO-8601 timestamp string to epoch seconds should use this module rather than writing a private _iso8601_to_epoch, choosing on_error = 'die'> when an unparseable timestamp indicates a real defect worth failing loudly on, or on_error = 'zero'> when the caller specifically wants a fail-closed "treat as already expired / always in the past" default.

HOW TO USE

use Developer::Dashboard::IsoTimestamp qw(_iso8601_to_epoch);
my $epoch = _iso8601_to_epoch( $text, on_error => 'die' );   # dies on bad input
my $epoch = _iso8601_to_epoch( $text, on_error => 'zero' );  # returns 0 on bad input

Calling without on_error, or with any value other than 'die'/'zero', dies naming on_error rather than silently defaulting - a caller must state which contract it needs.

WHAT USES IT

Developer::Dashboard::Collector (on_error = 'die'>, via its _entry_timestamp_epoch instance-method wrapper); Developer::Dashboard::SessionStore (on_error = 'zero'>, at its from_cookie expiry check and its housekeeping cleanup sweep) - the 2 call sites the DD-904 extraction migrated.

EXAMPLES

_iso8601_to_epoch( '2026-09-16T00:00:00Z', on_error => 'die' )        # 1789516800
_iso8601_to_epoch( '2026-09-16T00:00:00+0100', on_error => 'die' )    # 1789513200
_iso8601_to_epoch( 'garbage', on_error => 'zero' )                    # 0
_iso8601_to_epoch( 'garbage', on_error => 'die' )                     # dies