From d0219774d284b696d2516f06c9eca9af93336c5d Mon Sep 17 00:00:00 2001 From: Samuel Smith Date: Fri, 24 Oct 2025 20:30:36 -0500 Subject: [PATCH 1/9] Remove parse() No clue what this was ever used for --- Piece.pm | 18 ------------------ README.md | 3 +++ t/99legacy.t | 26 -------------------------- 3 files changed, 3 insertions(+), 44 deletions(-) delete mode 100644 t/99legacy.t diff --git a/Piece.pm b/Piece.pm index 2896672..e10269b 100644 --- a/Piece.pm +++ b/Piece.pm @@ -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) = @_; diff --git a/README.md b/README.md index d0955c5..64bbec5 100644 --- a/README.md +++ b/README.md @@ -281,6 +281,9 @@ To get local time objects, you can: my $local = localtime(); Time::Piece->strptime($string, $format, { defaults => $local }) +The islocal and defaults options were added in version 1.37; the instance +method can be used for compatibility with previous versions. + ### Locale Considerations By default, `strptime` only parses English day and month names, while diff --git a/t/99legacy.t b/t/99legacy.t deleted file mode 100644 index 175e335..0000000 --- a/t/99legacy.t +++ /dev/null @@ -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' ); -} From c8548d021fdaf2454c767faa8a9087fb9f3c556b Mon Sep 17 00:00:00 2001 From: Samuel Smith Date: Fri, 24 Oct 2025 20:32:49 -0500 Subject: [PATCH 2/9] Add add_days() Fixes GH #65 --- Piece.pm | 12 ++++++++++++ README.md | 4 ++++ t/07arith.t | 19 ++++++++++++++++++- 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/Piece.pm b/Piece.pm index e10269b..be40df4 100644 --- a/Piece.pm +++ b/Piece.pm @@ -773,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) = @_; @@ -1071,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 @@ -1104,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 All arithmetic uses epoch seconds (UTC). When daylight saving time (DST) changes occur: diff --git a/README.md b/README.md index 64bbec5..8900765 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,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 ## Global Configuration @@ -148,6 +151,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 **Note:** All arithmetic uses epoch seconds (UTC). When daylight saving time (DST) changes occur: diff --git a/t/07arith.t b/t/07arith.t index 58ca17b..1b9e39d 100644 --- a/t/07arith.t +++ b/t/07arith.t @@ -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 ); From 0b0d158552f1d95d290d8b26730a84c2e1f5942b Mon Sep 17 00:00:00 2001 From: Samuel Smith Date: Fri, 24 Oct 2025 20:37:39 -0500 Subject: [PATCH 3/9] Support years < 1900 in strptime Fixes GH #56 --- Piece.xs | 2 -- t/06large.t | 61 +++++++++++++++++++++++++++++++++-------------------- 2 files changed, 38 insertions(+), 25 deletions(-) diff --git a/Piece.xs b/Piece.xs index a90c39b..a99148b 100644 --- a/Piece.xs +++ b/Piece.xs @@ -858,8 +858,6 @@ label: i -= 1900; if (c == 'y' && i < 69) i += 100; - if (i < 0) - return NULL; tm->tm_year = i; diff --git a/t/06large.t b/t/06large.t index dbc62e2..807a281 100644 --- a/t/06large.t +++ b/t/06large.t @@ -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; + + 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 ) + ); + + $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; - - $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); From 5a7cecd4dd9547fd928ee5b3930978be3b22d37e Mon Sep 17 00:00:00 2001 From: Samuel Smith Date: Fri, 24 Oct 2025 20:46:29 -0500 Subject: [PATCH 4/9] Add hh:mm support to %z Fixes GH #74 --- Piece.xs | 18 ++++++++++++++---- t/12strptime_timezones.t | 14 +++++++++++++- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/Piece.xs b/Piece.xs index a99148b..d9b6f52 100644 --- a/Piece.xs +++ b/Piece.xs @@ -898,8 +898,10 @@ label: if (*buf != '+') { if (*buf == '-') sign = -1; - else + else { + warn("%%z must contain '-' or '+'"); return NULL; + } } buf++; @@ -910,10 +912,18 @@ label: 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 */ diff --git a/t/12strptime_timezones.t b/t/12strptime_timezones.t index a009f1e..b23f398 100644 --- a/t/12strptime_timezones.t +++ b/t/12strptime_timezones.t @@ -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); From 0cc4dfebe1c720237509c15b09b3df6fab67d731 Mon Sep 17 00:00:00 2001 From: Samuel Smith Date: Fri, 24 Oct 2025 20:51:02 -0500 Subject: [PATCH 5/9] Doc updates --- Piece.pm | 143 ++++++++++++++++++++++++++++-------------------------- README.md | 142 +++++++++++++++++++++++++++-------------------------- 2 files changed, 147 insertions(+), 138 deletions(-) diff --git a/Piece.pm b/Piece.pm index be40df4..f932031 100644 --- a/Piece.pm +++ b/Piece.pm @@ -1207,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 - Standard time components (as returned by localtime): - - my @defaults = localtime(); - my $t = Time::Piece->strptime("15 Mar", "%d %b", - { defaults => \@defaults }); - -B - Specify only needed components: - - my $t = Time::Piece->strptime("15 Mar", "%d %b", - { defaults => { - year => 2023, - hour => 14, - min => 30 - } }); - -Valid keys: C, C, C, C, C, C, C, C, C - -B: For the C parameter numbers less than 1000 are treated as an -offset from 1900. Whereas numbers larger than 1000 are treated as the actual year. - -B - Uses all components from the object: - - my $base = localtime(); - my $t = Time::Piece->strptime("15 Mar", "%d %b", - { defaults => $base }); - -B In all cases, parsed values always override defaults. Only missing -components use default values. - =head2 GMT vs Local Time By default, C returns GMT objects when called as a class method: @@ -1260,40 +1226,6 @@ To get local time objects, you can: my $local = localtime(); Time::Piece->strptime($string, $format, { defaults => $local }) -The islocal and defaults options were added in version 1.37; the instance -method can be used for compatibility with previous versions. - -=head3 Locale Considerations - -By default, C only parses English day and month names, while -C uses your system locale. This can cause parsing failures for -non-English dates. - -To parse localized dates, call Cuse_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 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 function has some limited support for parsing timezone @@ -1304,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"); @@ -1345,9 +1278,81 @@ Other timezone names are parsed B: "%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 Full timezone name support is not currently implemented. For reliable timezone handling beyond GMT/UTC, consider using the L module. +=head2 Handling Partial Dates + +When parsing incomplete date strings, you can provide defaults for missing +components in several ways: + +B - Standard time components (as returned by localtime): + + my @defaults = localtime(); + my $t = Time::Piece->strptime("15 Mar", "%d %b", + { defaults => \@defaults }); + +B - Specify only needed components: + + my $t = Time::Piece->strptime("15 Mar", "%d %b", + { defaults => { + year => 2023, + hour => 14, + min => 30 + } }); + +Valid keys: C, C, C, C, C, C, C, C, C + +B: For the C parameter numbers less than 1000 are treated as an +offset from 1900. Whereas numbers larger than 1000 are treated as the actual year. + +B - Uses all components from the object: + + my $base = localtime(); + my $t = Time::Piece->strptime("15 Mar", "%d %b", + { defaults => $base }); + +B In all cases, parsed values always override defaults. Only missing +components use default values. + +=head2 Locale Considerations + +By default, C only parses English day and month names, while +C uses your system locale. This can cause parsing failures for +non-English dates. + +To parse localized dates, call Cuse_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 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: diff --git a/README.md b/README.md index 8900765..3951561 100644 --- a/README.md +++ b/README.md @@ -232,40 +232,6 @@ The default format string is `"%a, %d %b %Y %H:%M:%S %Z"`, so these are equivale my $t1 = Time::Piece->strptime($string); my $t2 = Time::Piece->strptime($string, "%a, %d %b %Y %H:%M:%S %Z"); -## Handling Partial Dates - -When parsing incomplete date strings, you can provide defaults for missing -components in several ways: - -**Array Reference** - Standard time components (as returned by localtime): - - my @defaults = localtime(); - my $t = Time::Piece->strptime("15 Mar", "%d %b", - { defaults => \@defaults }); - -**Hash Reference** - Specify only needed components: - - my $t = Time::Piece->strptime("15 Mar", "%d %b", - { defaults => { - year => 2023, - hour => 14, - min => 30 - } }); - -Valid keys: `sec`, `min`, `hour`, `mday`, `mon`, `year`, `wday`, `yday`, `isdst` - -**Note**: For the `year` parameter numbers less than 1000 are treated as an -offset from 1900. Whereas numbers larger than 1000 are treated as the actual year. - -**Time::Piece Object** - Uses all components from the object: - - my $base = localtime(); - my $t = Time::Piece->strptime("15 Mar", "%d %b", - { defaults => $base }); - -**Note:** In all cases, parsed values always override defaults. Only missing -components use default values. - ## GMT vs Local Time By default, `strptime` returns GMT objects when called as a class method: @@ -285,40 +251,6 @@ To get local time objects, you can: my $local = localtime(); Time::Piece->strptime($string, $format, { defaults => $local }) -The islocal and defaults options were added in version 1.37; the instance -method can be used for compatibility with previous versions. - -### Locale Considerations - -By default, `strptime` only parses English day and month names, while -`strftime` uses your system locale. This can cause parsing failures for -non-English dates. - -To parse localized dates, call `Time::Piece->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"); - -**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); - ## Timezone Parsing with %z and %Z Time::Piece's `strptime()` function has some limited support for parsing timezone @@ -329,7 +261,8 @@ Consider the current implementation somewhat "alpha" and in need of feedback. ### Numeric Offsets (%z) -The `%z` specifier parses numeric timezone offsets (format: `+HHMM` or `-HHMM`): +The `%z` specifier parses numeric timezone offsets +(format: `[+-]HHMM`, `[+-]HH:MM`, or `[+-]HH`): my $t = Time::Piece->strptime("2024-01-15 15:30:00 +0500", "%Y-%m-%d %H:%M:%S %z"); @@ -364,9 +297,80 @@ Other timezone names are parsed **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 + **Note:** Full timezone name support is not currently implemented. For reliable timezone handling beyond GMT/UTC, consider using the [DateTime](https://metacpan.org/pod/DateTime) module. +## Handling Partial Dates + +When parsing incomplete date strings, you can provide defaults for missing +components in several ways: + +**Array Reference** - Standard time components (as returned by localtime): + + my @defaults = localtime(); + my $t = Time::Piece->strptime("15 Mar", "%d %b", + { defaults => \@defaults }); + +**Hash Reference** - Specify only needed components: + + my $t = Time::Piece->strptime("15 Mar", "%d %b", + { defaults => { + year => 2023, + hour => 14, + min => 30 + } }); + +Valid keys: `sec`, `min`, `hour`, `mday`, `mon`, `year`, `wday`, `yday`, `isdst` + +**Note**: For the `year` parameter numbers less than 1000 are treated as an +offset from 1900. Whereas numbers larger than 1000 are treated as the actual year. + +**Time::Piece Object** - Uses all components from the object: + + my $base = localtime(); + my $t = Time::Piece->strptime("15 Mar", "%d %b", + { defaults => $base }); + +**Note:** In all cases, parsed values always override defaults. Only missing +components use default values. + +## Locale Considerations + +By default, `strptime` only parses English day and month names, while +`strftime` uses your system locale. This can cause parsing failures for +non-English dates. + +To parse localized dates, call `Time::Piece->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"); + +**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); + # Global Overriding To override localtime and gmtime everywhere: From 1a5fbff920aee8a0427b914f48595ece04605813 Mon Sep 17 00:00:00 2001 From: Samuel Smith Date: Fri, 24 Oct 2025 22:32:28 -0500 Subject: [PATCH 6/9] Migrate isdigit => isDIGIT Fixes GH #80 --- Piece.xs | 78 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/Piece.xs b/Piece.xs index d9b6f52..16c909a 100644 --- a/Piece.xs +++ b/Piece.xs @@ -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--; @@ -861,8 +861,8 @@ label: 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; @@ -871,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)); @@ -907,7 +907,7 @@ label: buf++; i = 0; for (len = 4; len > 0; len--) { - if (isdigit((unsigned char)*buf)) { + if (isDIGIT((unsigned char)*buf)) { i *= 10; i += *buf - '0'; buf++; From 04212c98b83a953accd3ff9d1d2131e50e78dd04 Mon Sep 17 00:00:00 2001 From: Samuel Smith Date: Fri, 24 Oct 2025 22:35:45 -0500 Subject: [PATCH 7/9] MANIFEST update --- MANIFEST | 1 - 1 file changed, 1 deletion(-) diff --git a/MANIFEST b/MANIFEST index 6cbeef0..15dc15f 100644 --- a/MANIFEST +++ b/MANIFEST @@ -21,5 +21,4 @@ t/10overload.t t/11strptime_defaults.t t/12strptime_timezones.t t/13date_arithmetic_edge_cases.t -t/99legacy.t t/lib/Time/Piece/Twin.pm From 6cc21121c841f33818c4cc8f52318747853bb28d Mon Sep 17 00:00:00 2001 From: Samuel Smith Date: Fri, 24 Oct 2025 22:50:02 -0500 Subject: [PATCH 8/9] Version bump --- Piece.pm | 2 +- Seconds.pm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Piece.pm b/Piece.pm index f932031..e089382 100644 --- a/Piece.pm +++ b/Piece.pm @@ -19,7 +19,7 @@ our %EXPORT_TAGS = ( ':override' => 'internal', ); -our $VERSION = '1.38'; +our $VERSION = '1.39'; XSLoader::load( 'Time::Piece', $VERSION ); diff --git a/Seconds.pm b/Seconds.pm index 0e1089e..ba6f1a9 100644 --- a/Seconds.pm +++ b/Seconds.pm @@ -1,7 +1,7 @@ package Time::Seconds; use strict; -our $VERSION = '1.38'; +our $VERSION = '1.39'; use Exporter 5.57 'import'; From 0781d548dbbca55d1d36fff35afe33c8990fd140 Mon Sep 17 00:00:00 2001 From: Samuel Smith Date: Fri, 24 Oct 2025 22:50:13 -0500 Subject: [PATCH 9/9] Update changes --- Changes | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Changes b/Changes index 80a642d..5a13dd5 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,13 @@ Revision history for Time-Piece +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() + 1.38 2025-10-18 - Doc updates - Fix Windows-2025 crash with %P