Algorithm-ToNumberMunger

 view release on metacpan or  search on metacpan

t/mungers-datetime-fast.t  view on Meta::CPAN

#!perl
# The datetime fast path (compiled regex + integer date math) must be
# value-identical to the strptime path for every part, and the one-slot memo
# must never serve a stale result.
use 5.006;
use strict;
use warnings;
use Test::More;

BEGIN {
	eval { require Time::Piece; 1 }
		or plan skip_all => 'Time::Piece not available';
}

use Algorithm::ToNumberMunger;
my $M = 'Algorithm::ToNumberMunger';

my $FMT = '%Y-%m-%dT%H:%M:%S';    # fast-eligible: all six numeric codes

my @PARTS = qw(epoch year mon mday hour min sec wday yday
	frac_day frac_week sin_day cos_day sin_week cos_week);

# Stamps chosen to poke the arithmetic: epoch origin, leap day, year edges,
# pre-1970 (negative epoch days), and an ordinary modern stamp.
my @STAMPS = qw(
	1970-01-01T00:00:00
	1969-12-31T23:59:59
	2000-02-29T23:59:59
	1999-12-31T00:00:00
	2024-03-01T00:00:01
	2026-07-05T13:37:42
);

# ---- fast == strptime for every part on every stamp ------------------------
for my $part (@PARTS) {
	my $fast = $M->build( { munger => 'datetime', format => $FMT, part => $part } );
	for my $stamp (@STAMPS) {
		# expected value straight off Time::Piece, computed here in the test so
		# the module's slow path is not the oracle for itself.
		my $t   = Time::Piece->strptime( $stamp, $FMT );
		my %exp = (
			epoch     => $t->epoch,
			year      => $t->year,
			mon       => $t->mon,
			mday      => $t->mday,
			hour      => $t->hour,
			min       => $t->min,
			sec       => $t->sec,
			wday      => $t->day_of_week,
			yday      => $t->yday,
			frac_day  => ( $t->hour * 3600 + $t->min * 60 + $t->sec ) / 86400,
			frac_week => ( $t->day_of_week * 86400 + $t->hour * 3600 + $t->min * 60 + $t->sec ) / 604800,
		);
		my $pi = 2 * atan2( 0, -1 );
		$exp{sin_day}  = sin( $pi * $exp{frac_day} );
		$exp{cos_day}  = cos( $pi * $exp{frac_day} );
		$exp{sin_week} = sin( $pi * $exp{frac_week} );
		$exp{cos_week} = cos( $pi * $exp{frac_week} );

		my $got = $fast->($stamp);
		ok( abs( $got - $exp{$part} ) < 1e-9, "$part($stamp) fast == strptime" )
			or diag("got $got, expected $exp{$part}");
	} ## end for my $stamp (@STAMPS)
} ## end for my $part (@PARTS)

# ---- memo: repeat hits and non-stale updates --------------------------------
{
	my $c = $M->build( { munger => 'datetime', format => $FMT, part => 'hour' } );
	is( $c->('2026-07-05T13:00:00'), 13, 'first call' );
	is( $c->('2026-07-05T13:00:00'), 13, 'memo repeat hit' );
	is( $c->('2026-07-05T14:00:00'), 14, 'different stamp is not served stale' );
	is( $c->('2026-07-05T13:00:00'), 13, 'switching back re-parses correctly' );

	# a parse failure must not poison the memo
	eval { $c->('garbage') };
	like( $@, qr/cannot parse 'garbage'/, 'bad input croaks on the fast path' );
	is( $c->('2026-07-05T14:00:00'), 14, 'memo survives a failed parse' );

	# multi-output memo: same pair, then a different stamp
	my ( $code, $arity ) = do {
		# via compile, the public route to a multi munger
		my $plan = $M->compile(
			tags    => [qw(s c)],



( run in 0.709 second using v1.01-cache-2.11-cpan-7fcb06a456a )