DateTime-Lite

 view release on metacpan or  search on metacpan

t/10.timezone.t  view on Meta::CPAN

# NOTE: Error handling: unknown arguments
subtest 'Error handling: unknown arguments' => sub
{
    my $tz;
    {
        local $SIG{__WARN__} = sub{};
        $tz = DateTime::Lite::TimeZone->new( name => 'UTC', Names => 'UTC' );
    }
    ok( !defined( $tz ), 'new(): unknown arg returns undef' );
    like( DateTime::Lite::TimeZone->error . '', qr/unknown argument/i, 'new(): error mentions unknown argument' );
    like( DateTime::Lite::TimeZone->error . '', qr/'Names'/, "new(): error names the offending key" );

    # Valid args still work
    $tz = DateTime::Lite::TimeZone->new( name => 'UTC' );
    ok( defined( $tz ), 'new(): valid args still work' );
};

# NOTE: POSIX footer lookup for future dates beyond stored transitions
#
# The tz.sqlite3 DB has stored transitions only up to 2007 for NY and 1996 for
# Paris. Dates beyond those thresholds require the POSIX TZ string from the
# TZif footer (zones.footer_tz_string) to determine the correct offset and DST
# status.
subtest 'POSIX footer lookup' => sub
{
    # Unix-to-RD epoch offset used internally by DateTime::Lite
    my $U = 62_135_683_200;

    my $ny    = DateTime::Lite::TimeZone->new( name => 'America/New_York' );
    my $paris = DateTime::Lite::TimeZone->new( name => 'Europe/Paris'     );
    my $tok   = DateTime::Lite::TimeZone->new( name => 'Asia/Tokyo'       );
    my $syd   = DateTime::Lite::TimeZone->new( name => 'Australia/Sydney'  );

    # Helper: fake DT object with a fixed RD seconds value
    my $fake = sub
    {
        my $rd = shift( @_ );
        return( bless( { _rd => $rd }, 'FakeDTForFooter' ) );
    };

    # Future UTC offset lookups via _lookup_span -> POSIX footer
    is( $ny->offset_for_datetime( $fake->( timegm(0,0,12,15,0,126) + $U ) ),
        -18000, 'NY 2026-01-15: winter EST (footer UTC lookup)' );

    is( $ny->offset_for_datetime( $fake->( timegm(0,0,15,4,6,126) + $U ) ),
        -14400, 'NY 2026-07-04: summer EDT (footer UTC lookup)' );

    is( $ny->is_dst_for_datetime( $fake->( timegm(0,0,12,15,0,126) + $U ) ),
        0, 'NY 2026-01-15: is_dst=0 (footer)' );

    is( $ny->is_dst_for_datetime( $fake->( timegm(0,0,15,4,6,126) + $U ) ),
        1, 'NY 2026-07-04: is_dst=1 (footer)' );

    is( $ny->short_name_for_datetime( $fake->( timegm(0,0,12,15,0,126) + $U ) ),
        'EST', 'NY 2026-01-15: abbr=EST (footer)' );

    is( $ny->short_name_for_datetime( $fake->( timegm(0,0,15,4,6,126) + $U ) ),
        'EDT', 'NY 2026-07-04: abbr=EDT (footer)' );

    is( $paris->offset_for_datetime( $fake->( timegm(0,0,12,15,0,126) + $U ) ),
        3600, 'Paris 2026-01-15: winter CET (footer)' );

    is( $paris->offset_for_datetime( $fake->( timegm(0,0,15,4,6,126) + $U ) ),
        7200, 'Paris 2026-07-04: summer CEST (footer)' );

    is( $tok->offset_for_datetime( $fake->( timegm(0,0,12,15,0,126) + $U ) ),
        32400, 'Tokyo 2026: JST +9h, no DST (footer)' );

    is( $tok->is_dst_for_datetime( $fake->( timegm(0,0,12,15,0,126) + $U ) ),
        0, 'Tokyo 2026: is_dst=0 (footer)' );

    # DST boundary precision for NY 2026
    # DST start: 2026-03-08 02:00 EST = 07:00 UTC
    is( $ny->offset_for_datetime(
            $fake->( timegm(0,0,6,8,2,2026) + $U ) ),
        -18000, 'NY 2026-03-08 06:00Z: still EST (1h before DST start)' );

    is( $ny->offset_for_datetime(
            $fake->( timegm(0,0,7,8,2,2026) + $U ) ),
        -14400, 'NY 2026-03-08 07:00Z: EDT (exactly at DST start)' );

    # DST end: 2026-11-01 02:00 EDT = 06:00 UTC
    is( $ny->offset_for_datetime(
            $fake->( timegm(0,0,6,1,10,2026) + $U ) ),
        -18000, 'NY 2026-11-01 06:00Z: EST (at DST end)' );

    # Southern hemisphere: Australia/Sydney (DST in austral summer)
    # AEST-10AEDT,M10.1.0,M4.1.0/3
    is( $syd->offset_for_datetime( $fake->( timegm(0,0,12,15,0,126) + $U ) ),
        39600, 'Sydney 2026-01-15: AEDT +11h (austral summer, footer)' );

    is( $syd->is_dst_for_datetime( $fake->( timegm(0,0,12,15,0,126) + $U ) ),
        1, 'Sydney 2026-01-15: is_dst=1 (austral summer, footer)' );

    is( $syd->offset_for_datetime( $fake->( timegm(0,0,12,1,6,126) + $U ) ),
        36000, 'Sydney 2026-07-02: AEST +10h (austral winter, footer)' );

    is( $syd->is_dst_for_datetime( $fake->( timegm(0,0,12,1,6,126) + $U ) ),
        0, 'Sydney 2026-07-02: is_dst=0 (austral winter, footer)' );

    # Future local-time lookups via _lookup_span_local -> POSIX footer
    my $dt_local_win = DateTime::Lite->new(
        year      => 2026,
        month     => 1,
        day       => 15,
        hour      => 12,
        time_zone => 'floating',
    );
    my $dt_local_sum = DateTime::Lite->new(
        year      => 2026,
        month     => 7,
        day       => 4,
        hour      => 19,
        time_zone => 'floating',
    );

    is( $ny->offset_for_local_datetime( $dt_local_win ),
        -18000, 'NY 2026-01 local winter: EST (footer local lookup)' );

    is( $ny->offset_for_local_datetime( $dt_local_sum ),
        -14400, 'NY 2026-07 local summer: EDT (footer local lookup)' );



( run in 1.046 second using v1.01-cache-2.11-cpan-d8267643d1d )