cpan/Time-Piece - Update to version 1.39

1.39  2025-10-24
        - strptime: allow year < 1900 (GH56)
        - add add_days() (GH65)
        - strptime(): More %z formats (GH74)
        - Test failures in 11strptime_defaults.t (GH81)
        - XS cleanup (GH80)
        - Remove parse()
This commit is contained in:
Samuel Smith 2025-10-25 12:21:20 -04:00 committed by James E Keenan
parent baca3aa06d
commit 4222a60022
10 changed files with 213 additions and 182 deletions

View File

@ -3183,7 +3183,6 @@ cpan/Time-Piece/t/10overload.t Test file related to Time::Piece
cpan/Time-Piece/t/11strptime_defaults.t Time-Piece
cpan/Time-Piece/t/12strptime_timezones.t Time-Piece
cpan/Time-Piece/t/13date_arithmetic_edge_cases.t Time-Piece
cpan/Time-Piece/t/99legacy.t Test file related to Time::Piece
cpan/Time-Piece/t/lib/Time/Piece/Twin.pm Module related to Time::Piece
cpan/Unicode-Collate/Collate.pm Unicode::Collate
cpan/Unicode-Collate/Collate.xs Unicode::Collate

View File

@ -1256,8 +1256,8 @@ our %Modules = (
},
'Time::Piece' => {
'DISTRIBUTION' => 'ESAYM/Time-Piece-1.38.tar.gz',
'SYNCINFO' => 'jkeenan on Mon Oct 20 21:40:37 2025',
'DISTRIBUTION' => 'ESAYM/Time-Piece-1.39.tar.gz',
'SYNCINFO' => 'jkeenan on Sat Oct 25 12:20:55 2025',
'FILES' => q[cpan/Time-Piece],
'EXCLUDED' => [ qw[reverse_deps.txt] ],
},

View File

@ -19,7 +19,7 @@ our %EXPORT_TAGS = (
':override' => 'internal',
);
our $VERSION = '1.38';
our $VERSION = '1.39';
XSLoader::load( 'Time::Piece', $VERSION );
@ -102,24 +102,6 @@ sub new {
return bless $self, ref($class) || $class;
}
sub parse {
my $proto = shift;
my $class = ref($proto) || $proto;
my @components;
warnings::warnif("deprecated",
"parse() is deprecated, use strptime() instead.");
if (@_ > 1) {
@components = @_;
}
else {
@components = shift =~ /(\d+)$DATE_SEP(\d+)$DATE_SEP(\d+)(?:(?:T|\s+)(\d+)$TIME_SEP(\d+)(?:$TIME_SEP(\d+)))/;
@components = reverse(@components[0..5]);
}
return $class->new( timelocal(@components ));
}
sub _mktime {
my ($class, $time, $islocal) = @_;
@ -791,6 +773,14 @@ sub compare {
return $lhs <=> $rhs;
}
sub add_days {
my ( $time, $num_days ) = @_;
croak("add_days requires a number of days") unless defined($num_days);
return add( $time, $num_days * ONE_DAY );
}
sub add_months {
my ($time, $num_months) = @_;
@ -1089,6 +1079,9 @@ the actual offset including any DST adjustment.
$t->is_leap_year # true if it's a leap year
$t->month_last_day # 28-31
$t->add_days # Add days
$t->add_months # Add months
$t->add_years # Add years
=head2 Global Configuration
@ -1122,6 +1115,7 @@ The following are valid ($t1 and $t2 are Time::Piece objects):
$t1 - $t2; # returns Time::Seconds object
$t1 - 42; # returns Time::Piece object
$t1 + 533; # returns Time::Piece object
$t1->add_days(2); # returns Time::Piece object
B<Note:> All arithmetic uses epoch seconds (UTC). When daylight saving time
(DST) changes occur:
@ -1213,40 +1207,6 @@ The default format string is C<"%a, %d %b %Y %H:%M:%S %Z">, so these are equival
my $t1 = Time::Piece->strptime($string);
my $t2 = Time::Piece->strptime($string, "%a, %d %b %Y %H:%M:%S %Z");
=head2 Handling Partial Dates
When parsing incomplete date strings, you can provide defaults for missing
components in several ways:
B<Array Reference> - Standard time components (as returned by localtime):
my @defaults = localtime();
my $t = Time::Piece->strptime("15 Mar", "%d %b",
{ defaults => \@defaults });
B<Hash Reference> - Specify only needed components:
my $t = Time::Piece->strptime("15 Mar", "%d %b",
{ defaults => {
year => 2023,
hour => 14,
min => 30
} });
Valid keys: C<sec>, C<min>, C<hour>, C<mday>, C<mon>, C<year>, C<wday>, C<yday>, C<isdst>
B<Note>: For the C<year> parameter numbers less than 1000 are treated as an
offset from 1900. Whereas numbers larger than 1000 are treated as the actual year.
B<Time::Piece Object> - Uses all components from the object:
my $base = localtime();
my $t = Time::Piece->strptime("15 Mar", "%d %b",
{ defaults => $base });
B<Note:> In all cases, parsed values always override defaults. Only missing
components use default values.
=head2 GMT vs Local Time
By default, C<strptime> returns GMT objects when called as a class method:
@ -1266,37 +1226,6 @@ To get local time objects, you can:
my $local = localtime();
Time::Piece->strptime($string, $format, { defaults => $local })
=head3 Locale Considerations
By default, C<strptime> only parses English day and month names, while
C<strftime> uses your system locale. This can cause parsing failures for
non-English dates.
To parse localized dates, call C<Time::Piece-E<gt>use_locale()> to build
a list of your locale's day and month names:
# Enable locale-aware parsing (global setting)
Time::Piece->use_locale();
# Now strptime can parse names in your system locale
my $t = Time::Piece->strptime("15 Marzo 2024", "%d %B %Y");
B<Note:> This is a global change affecting all Time::Piece instances.
You can also override the day/month names manually:
my @days = qw( Domingo Lunes Martes Miercoles Jueves Viernes Sabado );
my $spanish_day = localtime->day(@days);
my @months = qw( Enero Febrero Marzo Abril Mayo Junio
Julio Agosto Septiembre Octubre Noviembre Diciembre );
print localtime->month(@months);
Set globally with:
Time::Piece::day_list(@days);
Time::Piece::mon_list(@months);
=head2 Timezone Parsing with %z and %Z
Time::Piece's C<strptime()> function has some limited support for parsing timezone
@ -1307,7 +1236,8 @@ Consider the current implementation somewhat "alpha" and in need of feedback.
=head3 Numeric Offsets (%z)
The C<%z> specifier parses numeric timezone offsets (format: C<+HHMM> or C<-HHMM>):
The C<%z> specifier parses numeric timezone offsets
(format: C<[+-]HHMM>, C<[+-]HH:MM>, or C<[+-]HH>):
my $t = Time::Piece->strptime("2024-01-15 15:30:00 +0500",
"%Y-%m-%d %H:%M:%S %z");
@ -1348,9 +1278,81 @@ Other timezone names are parsed B<but ignored>:
"%Y-%m-%d %H:%M:%S %Z");
print $t2->hour; # prints 10 (PST ignored - no adjustment)
# Parse and convert to local timezone
my $t3 = Time::Piece->strptime("2024-01-15 15:30:00 UTC",
"%Y-%m-%d %H:%M:%S %Z",
{ islocal => 1 });
print $t3->hour; # prints 10:30 UTC converted to your local timezone
B<Note:> Full timezone name support is not currently implemented. For reliable
timezone handling beyond GMT/UTC, consider using the L<DateTime> module.
=head2 Handling Partial Dates
When parsing incomplete date strings, you can provide defaults for missing
components in several ways:
B<Array Reference> - Standard time components (as returned by localtime):
my @defaults = localtime();
my $t = Time::Piece->strptime("15 Mar", "%d %b",
{ defaults => \@defaults });
B<Hash Reference> - Specify only needed components:
my $t = Time::Piece->strptime("15 Mar", "%d %b",
{ defaults => {
year => 2023,
hour => 14,
min => 30
} });
Valid keys: C<sec>, C<min>, C<hour>, C<mday>, C<mon>, C<year>, C<wday>, C<yday>, C<isdst>
B<Note>: For the C<year> parameter numbers less than 1000 are treated as an
offset from 1900. Whereas numbers larger than 1000 are treated as the actual year.
B<Time::Piece Object> - Uses all components from the object:
my $base = localtime();
my $t = Time::Piece->strptime("15 Mar", "%d %b",
{ defaults => $base });
B<Note:> In all cases, parsed values always override defaults. Only missing
components use default values.
=head2 Locale Considerations
By default, C<strptime> only parses English day and month names, while
C<strftime> uses your system locale. This can cause parsing failures for
non-English dates.
To parse localized dates, call C<Time::Piece-E<gt>use_locale()> to build
a list of your locale's day and month names:
# Enable locale-aware parsing (global setting)
Time::Piece->use_locale();
# Now strptime can parse names in your system locale
my $t = Time::Piece->strptime("15 Marzo 2024", "%d %B %Y");
B<Note:> This is a global change affecting all Time::Piece instances.
You can also override the day/month names manually:
my @days = qw( Domingo Lunes Martes Miercoles Jueves Viernes Sabado );
my $spanish_day = localtime->day(@days);
my @months = qw( Enero Febrero Marzo Abril Mayo Junio
Julio Agosto Septiembre Octubre Noviembre Diciembre );
print localtime->month(@months);
Set globally with:
Time::Piece::day_list(@days);
Time::Piece::mon_list(@months);
=head1 Global Overriding
To override localtime and gmtime everywhere:

View File

@ -380,8 +380,8 @@ _strptime(pTHX_ const char *buf, const char *fmt, struct tm *tm, int *got_GMT, H
c = *ptr++;
if (c != '%') {
if (isspace((unsigned char)c))
while (*buf != 0 && isspace((unsigned char)*buf))
if (isSPACE((unsigned char)c))
while (*buf != 0 && isSPACE((unsigned char)*buf))
buf++;
else if (c != *buf++) {
warn("Time string mismatches format string");
@ -408,12 +408,12 @@ label:
break;
case 'C':
if (!isdigit((unsigned char)*buf))
if (!isDIGIT((unsigned char)*buf))
return NULL;
/* XXX This will break for 3-digit centuries. */
len = 2;
for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
for (i = 0; len && *buf != 0 && isDIGIT((unsigned char)*buf); buf++) {
i *= 10;
i += *buf - '0';
len--;
@ -477,9 +477,9 @@ label:
case 'n': /* whitespace */
case 't':
if (!isspace((unsigned char)*buf))
if (!isSPACE((unsigned char)*buf))
return NULL;
while (isspace((unsigned char)*buf))
while (isSPACE((unsigned char)*buf))
buf++;
break;
@ -502,11 +502,11 @@ label:
break;
case 'j':
if (!isdigit((unsigned char)*buf))
if (!isDIGIT((unsigned char)*buf))
return NULL;
len = 3;
for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
for (i = 0; len && *buf != 0 && isDIGIT((unsigned char)*buf); buf++) {
i *= 10;
i += *buf - '0';
len--;
@ -520,14 +520,14 @@ label:
case 'M':
case 'S':
if (*buf == 0 || isspace((unsigned char)*buf))
if (*buf == 0 || isSPACE((unsigned char)*buf))
break;
if (!isdigit((unsigned char)*buf))
if (!isDIGIT((unsigned char)*buf))
return NULL;
len = 2;
for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
for (i = 0; len && *buf != 0 && isDIGIT((unsigned char)*buf); buf++) {
i *= 10;
i += *buf - '0';
len--;
@ -543,8 +543,8 @@ label:
tm->tm_sec = i;
}
if (*buf != 0 && isspace((unsigned char)*buf))
while (*ptr != 0 && !isspace((unsigned char)*ptr))
if (*buf != 0 && isSPACE((unsigned char)*buf))
while (*ptr != 0 && !isSPACE((unsigned char)*ptr))
ptr++;
break;
@ -560,11 +560,11 @@ label:
* XXX The %l specifier may gobble one too many
* digits if used incorrectly.
*/
if (!isdigit((unsigned char)*buf))
if (!isDIGIT((unsigned char)*buf))
return NULL;
len = 2;
for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
for (i = 0; len && *buf != 0 && isDIGIT((unsigned char)*buf); buf++) {
i *= 10;
i += *buf - '0';
len--;
@ -579,8 +579,8 @@ label:
tm->tm_hour = i;
if (*buf != 0 && isspace((unsigned char)*buf))
while (*ptr != 0 && !isspace((unsigned char)*ptr))
if (*buf != 0 && isSPACE((unsigned char)*buf))
while (*ptr != 0 && !isSPACE((unsigned char)*ptr))
ptr++;
break;
@ -681,11 +681,11 @@ label:
* point to calculate a real value, so just check the
* range for now.
*/
if (!isdigit((unsigned char)*buf))
if (!isDIGIT((unsigned char)*buf))
return NULL;
len = 2;
for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
for (i = 0; len && *buf != 0 && isDIGIT((unsigned char)*buf); buf++) {
i *= 10;
i += *buf - '0';
len--;
@ -693,14 +693,14 @@ label:
if (i > 53)
return NULL;
if (*buf != 0 && isspace((unsigned char)*buf))
while (*ptr != 0 && !isspace((unsigned char)*ptr))
if (*buf != 0 && isSPACE((unsigned char)*buf))
while (*ptr != 0 && !isSPACE((unsigned char)*ptr))
ptr++;
break;
case 'u':
case 'w':
if (!isdigit((unsigned char)*buf))
if (!isDIGIT((unsigned char)*buf))
return NULL;
i = *buf - '0';
@ -712,8 +712,8 @@ label:
tm->tm_wday = i;
buf++;
if (*buf != 0 && isspace((unsigned char)*buf))
while (*ptr != 0 && !isspace((unsigned char)*ptr))
if (*buf != 0 && isSPACE((unsigned char)*buf))
while (*ptr != 0 && !isSPACE((unsigned char)*ptr))
ptr++;
break;
@ -727,11 +727,11 @@ label:
* XXX The %e specifier may gobble one too many
* digits if used incorrectly.
*/
if (!isdigit((unsigned char)*buf))
if (!isDIGIT((unsigned char)*buf))
return NULL;
len = 2;
for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
for (i = 0; len && *buf != 0 && isDIGIT((unsigned char)*buf); buf++) {
i *= 10;
i += *buf - '0';
len--;
@ -741,8 +741,8 @@ label:
tm->tm_mday = i;
if (*buf != 0 && isspace((unsigned char)*buf))
while (*ptr != 0 && !isspace((unsigned char)*ptr))
if (*buf != 0 && isSPACE((unsigned char)*buf))
while (*ptr != 0 && !isSPACE((unsigned char)*ptr))
ptr++;
break;
@ -787,11 +787,11 @@ label:
break;
case 'm':
if (!isdigit((unsigned char)*buf))
if (!isDIGIT((unsigned char)*buf))
return NULL;
len = 2;
for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
for (i = 0; len && *buf != 0 && isDIGIT((unsigned char)*buf); buf++) {
i *= 10;
i += *buf - '0';
len--;
@ -801,8 +801,8 @@ label:
tm->tm_mon = i - 1;
if (*buf != 0 && isspace((unsigned char)*buf))
while (*ptr != 0 && !isspace((unsigned char)*ptr))
if (*buf != 0 && isSPACE((unsigned char)*buf))
while (*ptr != 0 && !isSPACE((unsigned char)*ptr))
ptr++;
break;
@ -842,14 +842,14 @@ label:
case 'Y':
case 'y':
if (*buf == 0 || isspace((unsigned char)*buf))
if (*buf == 0 || isSPACE((unsigned char)*buf))
break;
if (!isdigit((unsigned char)*buf))
if (!isDIGIT((unsigned char)*buf))
return NULL;
len = (c == 'Y') ? 4 : 2;
for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
for (i = 0; len && *buf != 0 && isDIGIT((unsigned char)*buf); buf++) {
i *= 10;
i += *buf - '0';
len--;
@ -858,13 +858,11 @@ label:
i -= 1900;
if (c == 'y' && i < 69)
i += 100;
if (i < 0)
return NULL;
tm->tm_year = i;
if (*buf != 0 && isspace((unsigned char)*buf))
while (*ptr != 0 && !isspace((unsigned char)*ptr))
if (*buf != 0 && isSPACE((unsigned char)*buf))
while (*ptr != 0 && !isSPACE((unsigned char)*ptr))
ptr++;
break;
@ -873,7 +871,7 @@ label:
const char *cp;
char *zonestr;
for (cp = buf; *cp && isupper((unsigned char)*cp); ++cp)
for (cp = buf; *cp && isUPPER((unsigned char)*cp); ++cp)
{/*empty*/}
if (cp - buf) {
zonestr = (char *)safemalloc((size_t) (cp - buf + 1));
@ -900,22 +898,32 @@ label:
if (*buf != '+') {
if (*buf == '-')
sign = -1;
else
else {
warn("%%z must contain '-' or '+'");
return NULL;
}
}
buf++;
i = 0;
for (len = 4; len > 0; len--) {
if (isdigit((int)*buf)) {
if (isDIGIT((unsigned char)*buf)) {
i *= 10;
i += *buf - '0';
buf++;
} else if (len == 2) {
i *= 100;
break;
} else
/* Support ISO 8601 HH:MM format in addition to RFC 822 HHMM */
if (*buf == ':') {
buf++;
len++;
} else {
i *= 100;
break;
}
} else {
warn("%%z format mismatch");
return NULL;
}
}
/* Valid if between UTC+14 and UTC-12 and minutes <= 60 */

View File

@ -1,7 +1,7 @@
package Time::Seconds;
use strict;
our $VERSION = '1.38';
our $VERSION = '1.39';
use Exporter 5.57 'import';

View File

@ -17,10 +17,13 @@ my $one_year = ONE_YEAR;
for ( 1 .. 50 ) {
$t = $t + $one_year;
cmp_ok( $t->year, '==', $base_year + $_,
"Year is: " . ( $base_year + $_ ) );
cmp_ok(
$t->year, '==',
Time::Piece->strptime( $t->year . "-07-15 00:00:00", "%F %T" )->year,
'==',
$base_year + $_,
"Year is: " . ( $base_year + $_ )
"Strptime year is: " . ( $base_year + $_ )
);
}
@ -29,34 +32,46 @@ $base_year = $t->year;
$t = $t - ( $one_year * 25 );
cmp_ok( $t->year, '==', $base_year - 25, "Year is: " . ( $base_year - 25 ) );
cmp_ok(
Time::Piece->strptime( $t->year . "-07-15 00:00:00", "%F %T" )->year,
'==',
$base_year - 25,
"Strptime year is: " . ( $base_year - 25 )
);
$base_year -= 25;
$t = $t - ( $one_year * 25 );
cmp_ok( $t->year, '==', $base_year - 25, "Year is: " . ( $base_year - 25 ) );
cmp_ok(
Time::Piece->strptime( $t->year . "-07-15 00:00:00", "%F %T" )->year,
'==',
$base_year - 25,
"Strptime year is: " . ( $base_year - 25 )
);
$base_year -= 25;
SKIP: {
skip "No time64 on Win32 if perl < 5.12", 5, if $is_win32 && $] < 5.012;
skip "No time64 on Win32 if perl < 5.12", 12, if $is_win32 && $] < 5.012;
$t = $t - ( $one_year * 25 );
cmp_ok( $t->year, '==', $base_year - 25, "Year is: " . ( $base_year - 25 ) );
$base_year -= 25;
for ( 1 .. 6 ) {
$t = $t - ( $one_year * 25 );
cmp_ok(
$t->year, '==',
$base_year - 25,
"Year is: " . ( $base_year - 25 )
);
cmp_ok(
Time::Piece->strptime( $t->year . "-07-15 00:00:00", "%F %T" )
->year,
'==',
$base_year - 25,
"Strptime year is: " . ( $base_year - 25 )
);
$t = $t - ( $one_year * 25 );
cmp_ok( $t->year, '==', $base_year - 25, "Year is: " . ( $base_year - 25 ) );
$base_year -= 25;
$base_year -= 25;
$t = $t - ( $one_year * 25 );
cmp_ok( $t->year, '==', $base_year - 25, "Year is: " . ( $base_year - 25 ) );
$base_year -= 25;
}
$t = $t - ( $one_year * 25 );
cmp_ok( $t->year, '==', $base_year - 25, "Year is: " . ( $base_year - 25 ) );
$base_year -= 25;
$t = $t - ( $one_year * 25 );
cmp_ok( $t->year, '==', $base_year - 25, "Year is: " . ( $base_year - 25 ) );
$base_year -= 25;
}
done_testing(57);
done_testing(116);

View File

@ -1,4 +1,4 @@
use Test::More tests => 43;
use Test::More tests => 52;
BEGIN { use_ok('Time::Piece'); use_ok('Time::Seconds'); }
@ -81,3 +81,20 @@ is($s2->seconds, 0, 'Subtract one Time::Seconds object from another');
eval { $s2 = $s2 + $t; };
like($@, qr/Can't use non Seconds object in operator overload/);
# Tests for add_days
$t = Time::Piece->strptime( "01 01 2024", "%d %m %Y" );
my $t10 = $t->add_days(59);
is( $t10->year, 2024 );
is( $t10->mon, 2 );
is( $t10->mday, 29 );
my $t11 = $t->add_days(-366);
is( $t11->year, 2022 );
is( $t11->mon, 12 );
is( $t11->mday, 31 );
my $t12 = $t->add_days(366);
is( $t12->year, 2025 );
is( $t12->mon, 1 );
is( $t12->mday, 1 );

View File

@ -5,6 +5,10 @@ my $is_linux = ( $^O =~ /linux/ );
BEGIN { use_ok('Time::Piece'); }
# Start with a known TZ
$ENV{TZ} = "EST5EDT4,M3.2.0/2,M11.1.0/2";
Time::Piece::_tzset();
# Using known epoch 1753440879 = Friday, July 25, 2025 10:54:39 AM GMT
my @known_gmtime = gmtime(1753440879); # (39, 54, 10, 25, 6, 125, 5, 206, 0)
my @known_localtime = localtime(1753440879);

View File

@ -75,6 +75,18 @@ my @z_offset_tests = (
[ "-0100", 10, 11, 30, "UTC-1" ],
[ "-0500", 10, 15, 30, "UTC-5" ],
[ "-0730", 10, 18, 0, "UTC-7:30" ],
# ISO 8601 [+-]HH format (verify existing support)
[ "+00", 10, 10, 30, "UTC (HH format)" ],
[ "+05", 10, 5, 30, "UTC+5 (HH format)" ],
[ "-08", 10, 18, 30, "UTC-8 (HH format)" ],
# ISO 8601 [+-]HH:MM format (new support)
[ "+00:00", 10, 10, 30, "UTC (HH:MM format)" ],
[ "+01:00", 10, 9, 30, "UTC+1 (HH:MM format)" ],
[ "+05:30", 10, 5, 0, "UTC+5:30 (HH:MM format)" ],
[ "-01:00", 10, 11, 30, "UTC-1 (HH:MM format)" ],
[ "-07:30", 10, 18, 0, "UTC-7:30 (HH:MM format)" ],
);
for my $test (@z_offset_tests) {
@ -450,4 +462,4 @@ with_tz(
}
);
done_testing(234);
done_testing(290);

View File

@ -1,26 +0,0 @@
use strict;
use warnings;
no warnings 'deprecated';
use Test::More tests => 5;
BEGIN { use_ok('Time::Piece'); }
# The parse() legacy method is deprecated and will not be maintained.
# The tests in this script illustrate both its functionality and some of
# its bugs. This script should be removed from the test suite once
# parse() has been deleted from Time::Piece.
SKIP: {
skip "Linux only", 4 if $^O !~ /linux/i;
my $timestring = '2000-01-01T06:00:00';
my $t1 = Time::Piece->parse($timestring);
isnt( $t1->datetime, $timestring, 'LEGACY: parse string months fail' );
my $t2 = $t1->parse( 0, 0, 6, 1, 0, 100 );
is( $t2->datetime, $timestring, 'LEGACY: parse array' );
eval { $t2 = Time::Piece->parse(); };
is( $t2->datetime, $timestring, 'LEGACY: parse with no args dies' );
eval { $t2 = Time::Piece::parse( 0, 0, 12, 1, 0, 100 ); };
is( $t2->datetime, $timestring, 'LEGACY: parse as non-method dies' );
}