atcroft has asked for the wisdom of the Perl Monks concerning the following question:

The documentation for Time::Piece in 5.30.x states the following:

Date Parsing
Time::Piece has a built-in strptime() function (from FreeBSD), allowing you incredibly flexible date parsing routines. For example:
my $t = Time::Piece->strptime("Sunday 3rd Nov, 1943", "%A %drd %b, %Y"); print $t->strftime("%a, %d %b %Y");
Outputs:
Wed, 03 Nov 1943
(see, it's even smart enough to fix my obvious date bug) For more information see "man strptime", which should be on all unix systems. Alternatively look here: http://www.unix.com/man-page/FreeBSD/3/strftime/

The link includes the following conversion specifications:

%a is replaced by national representation of the abbreviated weekday name.
%d is replaced by the day of the month as a decimal number (01-31).
%b is replaced by national representation of the abbreviated month name.
%Y is replaced by the year with century as a decimal number.
%T is equivalent to ``%H:%M:%S''.
%H is replaced by the hour (24-hour clock) as a decimal number (00-23).
%M is replaced by the minute as a decimal number (00-59).
%S is replaced by the second as a decimal number (00-60).
%Z is replaced by the time zone name.
%z is replaced by the time zone offset from UTC; a leading plus sign stands for east of UTC, a minus sign for west of UTC, hours and minutes follow with two digits each and no delimiter between them (common form for RFC 822 date headers).

The page for strptime on the same site says, "The strptime() function parses the string in the buffer buf according to the string pointed to by format, and fills in the elements of the structure pointed to by timeptr. The resulting values will be relative to the local time zone. Thus, it can be considered the reverse operation of strftime(3)."

I created the following test code as tp_test.pl:

#!/usr/bin/perl # vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: use strict; use warnings; use Carp; use Time::Piece; $SIG{__WARN__} = sub { Carp::cluck @_; }; $SIG{__DIE__} = sub { Carp::confess @_; }; $| = 1; my $t = localtime; my $pattern = "%a, %d %b %Y %T %Z"; my $str = $t->strftime( $pattern ); print "Time is:\n", $str, "\n"; # Format: Wed, 13 Jan 2021 17:22:23 CST my $u = Time::Piece->strptime( $str, $pattern, ); print "Time is:\n", $u->strftime( $pattern ), "\n";

I tested the code on the following three (3) platforms:

When I execute the script above, I get the following on all three platforms (differing only by the path to Piece.pm):
$ perl ./tp_test.pl Time is: Wed, 13 Jan 2021 22:21:49 CST Error parsing time at /usr/lib64/perl5/Time/Piece.pm line 597. at ./tp_test.pl line 11. main::__ANON__("Error parsing time at /usr/lib64/perl5/Time/Piece.pm line 597.\x{a}") called at /usr/lib64/perl5/Time/Piece.pm line 597 Time::Piece::strptime("Time::Piece", "Wed, 13 Jan 2021 22:21:49 CST", "%a, %d %b %Y %T %Z") called at ./tp_test.pl line 23 $

Expected output:

$ perl ./tp_test.pl Time is: Wed, 13 Jan 2021 22:21:49 CST Time is: Wed, 13 Jan 2021 22:21:49 CST $

The behavior also appears to occur if I change my $t = localtime; to my $t = gmtime; as well, where the timezone is then 'UTC'. If it were only occurring under Cygwin and Strawberry, my first guess would be MSWin-related, but since I am seeing it on a Linux system as well, I'm not sure where to look for the cause of the issue.

Thoughts?

Replies are listed 'Best First'.
Re: Question regarding Time::Piece and timezones
by choroba (Archbishop) on Jan 14, 2021 at 09:38 UTC
    I remember we've discussed Time::Piece and timezones here. I can't find it anymore (maybe most of it was ChatterBox), but I took two lessons from it:
    • If you want the object to know about timezones, use localtime (see below);
    • If you want to work with timezones, don't use Time::Piece.

    #! /usr/bin/perl use strict; use warnings; use feature qw{ say }; use Time::Piece; my $pattern = "%a, %d %b %Y %T"; my $str = 'Wed, 13 Jan 2021 17:22:23'; local $ENV{TZ} = 'CST'; my $u1 = Time::Piece->strptime($str, $pattern); my $u2 = localtime($u1); say $_->strftime('%F %T %Z') for $u1, $u2; __END__ 2021-01-13 17:22:23 UTC 2021-01-13 17:22:23 CST
    map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
      If you want to work with timezones, don't use Time::Piece.

      This is usually my approach too. Since noone has mentioned it yet: I use DateTime and DateTime::Format::Strptime.

        I've been trying to implement the OP's application using your suggestions. I did get DateTime and DateTime::Format::Strptime installed.
        However I seem to be doing something stupid as I can't get to square #1 - creating the $strp object (error shown below).

        I would appreciate your help in correcting my error. I am also curious as to what happens with these ambiguous local times, like in the US 1:23 AM can occur twice during the same day. Almost all my work is in GMT/UTC so I don't often work with local time myself.

        use strict; use warnings; use 5.010; use DateTime; use DateTime::Format::Strptime; $|=1; my $str = 'Wed, 13 Jan 2021 17:22:23'; my $pattern = '%a, %d %b %Y %T'; my $strp = DateTime::Format::Strptime->new( pattern => $pattern, time_zone => 'CST', zone_map => { CST => '-0700', EST => '-0600' } ); say "I got this far (not!)"; my $dt = $strp->parse_datetime( $str ); say "String: $str"; say "DateTime: $dt"; say ''; __END__ Invalid offset: CST
        Update: BTW, there are all kind of "weirdo" time zones in the world time zones. Also occasionally a specific location can change its time zone - the International Date Line was moved some years back - this affected some islands in the Pacific.
Re: Question regarding Time::Piece and timezones
by hippo (Chancellor) on Jan 14, 2021 at 10:10 UTC
    The behavior also appears to occur if I change my $t = localtime; to my $t = gmtime; as well, where the timezone is then 'UTC'.

    That is very interesting. If I run your original code as posted (with Time::Piece 1.3401) with the TZ environment variable unset, it runs to completion and I see this output:

    Time is: Thu, 14 Jan 2021 09:48:00 GMT Time is: Thu, 14 Jan 2021 09:48:00 UTC

    Note that the textual representation of the timezone differs in the two lines of output.

    If I subsequently re-run it with TZ set to apparently anything other than "GMT", then it bombs out. Even if I set it to "UTC" it will bomb out. This looks like a bug in Time::Piece .

    Time is: Thu, 14 Jan 2021 10:03:32 UTC Error parsing time at /usr/local/lib64/perl5/Time/Piece.pm line 598. at tp.pl line 11. main::__ANON__("Error parsing time at /usr/local/lib64/perl5/Time/ +Piece.pm li"...) called at /usr/local/lib64/perl5/Time/Piece.pm line +598 Time::Piece::strptime("Time::Piece", "Thu, 14 Jan 2021 10:03:32 UT +C", "%a, %d %b %Y %T %Z") called at tp.pl line 23

    🦛

      Before I consider it a bug, I want to make sure the issue is not on my side or in my understanding. This is part of the reason my sample code attempted to go from object to string and back to object-to test both the strftime() and strptime() methods.

      My C is quite rusty (and I haven't dug around inside the perl source tree before), but it appears the process goes as follows when looking at the perl-5.30.3 source on MetaCPAN (PLEASE CORRECT ME IF YOU SPOT ANY ERRORS BELOW):

      1. When Time::Piece->strptime($time_string, $format_string) (found in cpan/Time::Piece/Piece.pm in the source distribution) is called, the format is first parsed by Time::Piece->_translate_format($format, $strptime_trans_map) . $strptime_trans_map appears to only contain the content of $trans_map_common (which defines formats 'c', 'r', and 'X', and NOT the formats 'e', 'D', 'F', 'R', 's', 'T', 'u', 'V', 'x', 'z', or 'Z', from $strftime_trans_map ).
      2. _strptime($string, $format, $islocal, $locales) (found in cpan/Time::Piece/Piece.xs) is then called. There are two such functions in Piece.xs:
        • line 345: static char * _strptime( pTHX_ const char buf*, const char *fmt, struct tm *tm, int *gotGMT )
        • line 1025: void _strptime ( string, format, got_GMT, SV* localization )
        Based on the signature, it appears the second (on line 1025) is called. After loading the locale data structure via _cpopulate_c_time_locale(aTHX_ locales ), this version of _strptime calls the version on line 345 as remainder = (char *)_strptime(aTHX_ string, format, &mytm, &gotGMT). If this returns with anything other than a '\0', either an "Error parsing time" or "Garbage at end of string in strptime:" message is returned.
      3. In _strptime( string, format, islocal, locales ) (to use the names from the original calls), when the "Z" character is found it appears to seek the end of the time zone name, then calls my_tzset(aTHX). According to perlguts, aTHX is an (a)rgument (TH)ingy(X).
      4. According to the comments, my_tzset(pTHX) ((p)rototype (TH)ingy(X)) is a wrapper to tzset() designed to make it work better on Win32. It does so by way of two #IFDEFs that determine if fix_win32_tzenv() is called. As my test failed on a Linux system, I do not believe the #IFDEFs are involved. tzset() (I believe from the system time.h) is then called.

      Questions:

      1. Are there any (obvious?) flaws in my tracing of the logic above?
      2. If indeed it is a bug (can someone confirm?), is it a problem with perl, or with a library used while compiling perl?

      Thank you for your time and attention. Stay safe!

      I agree that Time::Piece has a bug.
Re: Question regarding Time::Piece and timezones
by parv (Vicar) on Jan 14, 2021 at 06:11 UTC

    The string time zone seems to be an issue ...

    #!perl use warnings; use strict; use Carp; use Time::Piece; $SIG{__WARN__} = sub { Carp::cluck @_; }; $SIG{__DIE__} = sub { Carp::confess @_; }; $| = 1; my $format_notz = "%a, %d %b %Y %T"; my $format_tz_name = "${format_notz} %Z"; my $format_tz_offset = "${format_notz} %z"; my $time = 'Wed, 13 Jan 2021 17:00:00'; my %time_map = ( $time => $format_notz, qq[$time CST] => $format_tz_name, qq[$time -0600] => $format_tz_offset, ); for my $t ( sort keys %time_map ) { my $format = $time_map{ $t }; my $tp = Time::Piece->strptime( $t, $format ); printf "%s\n -> %s ->\n", $t, $format; printf " %s\n\n", $tp->strftime( $format ); } __END__ Wed, 13 Jan 2021 17:00:00 -> %a, %d %b %Y %T -> Wed, 13 Jan 2021 17:00:00 Wed, 13 Jan 2021 17:00:00 -0600 -> %a, %d %b %Y %T %z -> Wed, 13 Jan 2021 23:00:00 +0000 Error parsing time at C:/Users/parv/mine--no-backup/strawberry-perl-5. +32.0.1-64bit-portable/perl/lib/Time/Piece.pm line 598. at x-timepc.pl line 9. main::__ANON__("Error parsing time at C:/Users/parv/mine--no-b +ackup/strawberr"...) called at C:/Users/parv/mine--no-backup/strawber +ry-perl-5.32.0.1-64bit-portable/perl/lib/Time/Piece.pm line 598 Time::Piece::strptime("Time::Piece", "Wed, 13 Jan 2021 17:00:0 +0 CST", "%a, %d %b %Y %T %Z") called at x-timepc.pl line 26

    FreeBSD man strftime.

Re: Question regarding Time::Piece and timezones
by Marshall (Canon) on Jan 14, 2021 at 06:39 UTC
    Edited..
    use strict; use warnings; use Time::Piece; my $t = localtime(); my $pattern = "%a, %d %b %Y %T %Z"; my $str = $t->strftime ($pattern); print "Time is:\n", $str, "\n"; __END__ example: Time is: Wed, 13 Jan 2021 23:08:46 Pacific Standard Time
      my $t = localtime;

      There are more moving parts than the minimum required to show the problem, which is surprising in such a short example script! The issue could as well have been illustrated using:

      use strict; use warnings; use Time::Piece; my $pattern = "%a, %d %b %Y %T %Z"; my $str = 'Wed, 13 Jan 2021 17:22:23 CST'; my $u = Time::Piece->strptime($str, $pattern); print "Time is:\n", $u->strftime($pattern), "\n";

      which doesn't give the trace back, but does show the error:

      Error parsing time at d:/berrybrew/5.30.1_32/perl/lib/Time/Piece.pm li +ne 597.
      Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond
        Yes, there are many moving parts to time/date calculations!

        I personally always use gmtime(), which is close to utc time.
        There is a slight difference between gm and utc time, but not much.
        The difference is so small that I don't want to argue about it.

        I recommend using GM or UTC time for all values stored in a DB.
        Use a a conversion module like Time::Piece for translation to
        translate the DB time to a user time zone.

        My local government had a problem with the fall time change.
        This actually allowed bars to remain open for one extra hour on one day of the year.
        They fixed that "problem".
        gmtime() just marches onward- there is no "fall back".