diff --git a/Changes b/Changes index 714884a..6a9d492 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,9 @@ Revision history for Time-Piece +1.41 2025-11-12 + - strptime: parse micro seconds (RT165677, RT133599) + - add to_gmtime and to_localtime (RT113979) + 1.40 2025-11-08 - strptime: locale parse fixes (GH86) - Add fullmon_list() and fullday_list() (GH85) diff --git a/Piece.pm b/Piece.pm index cfa53a4..4b72f83 100644 --- a/Piece.pm +++ b/Piece.pm @@ -19,7 +19,7 @@ our %EXPORT_TAGS = ( ':override' => 'internal', ); -our $VERSION = '1.40'; +our $VERSION = '1.41'; XSLoader::load( 'Time::Piece', $VERSION ); @@ -66,6 +66,13 @@ sub gmtime { $class->_mktime($time, 0); } +sub to_gmtime { + &gmtime( $_[0]->epoch ); +} + +sub to_localtime { + &localtime( $_[0]->epoch ); +} # Check if the supplied param is either a normal array (as returned from # localtime in list context) or a Time::Piece-like wrapper around one. @@ -1073,6 +1080,8 @@ platform's C manual page (C on Unix-like systems). $t->tzoffset # timezone offset in a Time::Seconds object $t->isdst # also available as $t->daylight_savings + $t->to_gmtime # convert to GMT, preserving the epoch + $t->to_localtime # convert to local time, preserving the epoch The C method returns: @@ -1090,6 +1099,10 @@ The C method returns the offset from UTC as a Time::Seconds object. For GMT/UTC times, this always returns 0. For local times, it calculates the actual offset including any DST adjustment. +The C and C methods convert between timezone contexts +while preserving the same moment in time (epoch). They always return a new +Time::Piece object. + =head2 Utility Methods $t->is_leap_year # true if it's a leap year @@ -1240,6 +1253,7 @@ following format flags: %d Day of month (01-31) %D Equivalent to %m/%d/%y %e Day of month ( 1-31, space-padded) + %f Fractional seconds as microseconds (up to 6 digits, parsed but ignored) %F Equivalent to %Y-%m-%d (ISO 8601 date format) %h Abbreviated month name (same as %b) %H Hour in 24-hour format (00-23) @@ -1276,6 +1290,10 @@ B C<%U>, C<%V>, and C<%W> (week number formats) are parsed but not fully implemented in the strptime logic, as they require additional date components to calculate the actual date. +B C<%f> (fractional seconds) is only supported in C for parsing. +It is not available in C for output formatting, as Time::Piece uses +epoch seconds which do not store subsecond precision. + =head2 GMT vs Local Time By default, C returns GMT objects when called as a class method: diff --git a/Piece.xs b/Piece.xs index 48f059e..4c46b04 100644 --- a/Piece.xs +++ b/Piece.xs @@ -758,6 +758,19 @@ label: ptr++; break; + case 'f': + if (!isDIGIT((unsigned char)*buf)) + return NULL; + + len = 6; + for (i = 0; len && *buf != 0 && isDIGIT((unsigned char)*buf); buf++) { + i *= 10; + i += *buf - '0'; + len--; + } + /* Value is discarded - fractional seconds not stored */ + break; + case 'B': case 'b': case 'h': diff --git a/README.md b/README.md index 4ad1d95..3290862 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,8 @@ platform's `strftime(3)` manual page (`man strftime` on Unix-like systems). $t->tzoffset # timezone offset in a Time::Seconds object $t->isdst # also available as $t->daylight_savings + $t->to_gmtime # convert to GMT, preserving the epoch + $t->to_localtime # convert to local time, preserving the epoch The `isdst` method returns: @@ -129,6 +131,10 @@ The `tzoffset` method returns the offset from UTC as a Time::Seconds object. For GMT/UTC times, this always returns 0. For local times, it calculates the actual offset including any DST adjustment. +The `to_gmtime` and `to_localtime` methods convert between timezone contexts +while preserving the same moment in time (epoch). They always return a new +Time::Piece object. + ## Utility Methods $t->is_leap_year # true if it's a leap year @@ -268,6 +274,7 @@ following format flags: %d Day of month (01-31) %D Equivalent to %m/%d/%y %e Day of month ( 1-31, space-padded) + %f Fractional seconds as microseconds (up to 6 digits, parsed but ignored) %F Equivalent to %Y-%m-%d (ISO 8601 date format) %h Abbreviated month name (same as %b) %H Hour in 24-hour format (00-23) @@ -304,6 +311,10 @@ flags listed above. For example, `%c` is typically equivalent to something like: implemented in the strptime logic, as they require additional date components to calculate the actual date. +**Note:** `%f` (fractional seconds) is only supported in `strptime` for parsing. +It is not available in `strftime` for output formatting, as Time::Piece uses +epoch seconds which do not store subsecond precision. + ## GMT vs Local Time By default, `strptime` returns GMT objects when called as a class method: diff --git a/Seconds.pm b/Seconds.pm index fe27e48..2ea3a3e 100644 --- a/Seconds.pm +++ b/Seconds.pm @@ -1,7 +1,7 @@ package Time::Seconds; use strict; -our $VERSION = '1.40'; +our $VERSION = '1.41'; use Exporter 5.57 'import'; diff --git a/t/02core.t b/t/02core.t index 52c58e4..9b29b27 100644 --- a/t/02core.t +++ b/t/02core.t @@ -1,4 +1,4 @@ -use Test::More tests => 103; +use Test::More tests => 113; my $is_qnx = ($^O eq 'qnx'); my $is_vos = ($^O eq 'vos'); @@ -235,3 +235,24 @@ $s = Time::Seconds->new(130); is($s->pretty, '2 minutes, 10 seconds'); $s = Time::Seconds->new(7330); is($s->pretty, '2 hours, 2 minutes, 10 seconds', "Format correct"); + +my $t_frac = Time::Piece->strptime("2000-02-29T13:34:56.123", '%Y-%m-%dT%H:%M:%S.%f'); +cmp_ok($t_frac->epoch, '==', 951831296, "Fractional seconds with 3 digits parsed correctly"); +cmp_ok($t_frac->sec, '==', 56, "Seconds correct with 3 digit fractional"); + +$t_frac = Time::Piece->strptime("2000-02-29T13:34:56.123456", '%Y-%m-%dT%H:%M:%S.%f'); +cmp_ok($t_frac->epoch, '==', 951831296, "Fractional seconds with 6 digits parsed correctly"); +cmp_ok($t_frac->sec, '==', 56, "Seconds correct with 6 digit fractional"); + +$t_frac = Time::Piece->strptime("2000-02-29T13:34:56.1Z", '%Y-%m-%dT%H:%M:%S.%fZ'); +cmp_ok($t_frac->epoch, '==', 951831296, "Fractional seconds with 1 digit parsed correctly"); +cmp_ok($t_frac->sec, '==', 56, "Seconds correct with 1 digit fractional"); + +my $gmt_obj = gmtime(951831296); +my $local_obj = $gmt_obj->to_localtime(); +cmp_ok($local_obj->epoch, '==', 951831296, 'to_localtime preserves epoch'); +cmp_ok($local_obj->[Time::Piece::c_islocal], '==', 1, 'to_localtime sets islocal flag'); + +my $gmt2 = $local_obj->to_gmtime(); +cmp_ok($gmt2->epoch, '==', 951831296, 'to_gmtime preserves epoch'); +cmp_ok($gmt2->[Time::Piece::c_islocal], '==', 0, 'to_gmtime clears islocal flag');