Beefy Boxes and Bandwidth Generously Provided by pair Networks
good chemistry is complicated,
and a little bit messy -LW
 
PerlMonks  

DateTime::Format::Strptime Parsing Seems to have a Problem?

by ozboomer (Friar)
on Mar 26, 2019 at 01:34 UTC ( #1231677=perlquestion: print w/replies, xml ) Need Help??

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

I'm finding something of an inconsistent behaviour when using DateTime::Format::Strptime.

The program as shown below illustrates the problem... but in short, the parsing of an 'hh:mm:ss p' format on a 12-hour clock doesn't seem to work with a 'PM' included in the string. Using a 24-hour clock format (and no 'PM') seems to work Ok... but my data isn't presented in that format.

An example program:-

use strict; use warnings; #use diagnostics; use DateTime; use DateTime::Format::Strptime; our $LocalTZ = DateTime::TimeZone->new( name => 'local' ); my $dtnow = DateTime->now(time_zone => $LocalTZ); # Current *local* + time printf("\$dtnow: !%s!\n", $dtnow); my $lo = "18/03/2019 10:12:53 am"; # Always works my $hi = "24/03/2019 3:15:00 pm"; # Does not work... the format I + need to use # my $hi = "24/03/2019 03:15:00 pm"; # Does not work # my $hi = "24/03/2019 3:15:00 pm"; # Does not work # my $hi = "24/03/2019 15:15:00 pm"; # Works but 15hrs with 'pm' i +s silly # my $hi = "24/03/2019 15:15:00"; # Works my $dmy_fmt = DateTime::Format::Strptime->new( # Let DateTime use 'dd +/mm/yyyy' pattern => '%d/%m/%Y %H:%M:%S %p', # pattern => '%d/%m/%Y %H:%M:%S', # Use this when no 'am/pm' + in the date string time_zone => 'Australia/Victoria', # time_zone => 'local', ); my $lodt = $dmy_fmt->parse_datetime($lo); printf(" \$lodt: !%s!\n", $lodt); my $hidt = $dmy_fmt->parse_datetime($hi); printf(" \$hidt: !%s!\n", $hidt);

I have updated the modules to the latest versions via CPAN... but it doesn't make any difference. Otherwise, I'm talking about ActiveState Perl v5.16.3 Build 1603, running under Windows 10 64-bit.

Any clues on what's wrong here... with what I'm trying to do OR with the ways the functions are working?

Thanks a heap for any forthcoming suggestions.

Replies are listed 'Best First'.
Re: DateTime::Format::Strptime Parsing Seems to have a Problem?
by tangent (Vicar) on Mar 26, 2019 at 03:09 UTC
    You need to use %I for the 12-hour clock, not %H
    pattern => '%d/%m/%Y %I:%M:%S %p'
    You can also use %r for the whole time to avoid confusion
    pattern => '%d/%m/%Y %r'
Re: DateTime::Format::Strptime Parsing Seems to have a Problem?
by Athanasius (Bishop) on Mar 26, 2019 at 03:20 UTC

    Hello ozboomer,

    tangent beat me to it with %I. But you should also check for errors; e.g.:

    my $dmy_fmt = DateTime::Format::Strptime->new ( pattern => '%d/%m/%Y %I:%M:%S %p', time_zone => 'Australia/Victoria', on_error => 'croak', # <-- ADD THIS );

    With %H instead of %I, this reports:

    13:08 >perl 1989_SoPW.pl $dtnow: !2019-03-25T20:10:32! $lodt: !2019-03-18T10:12:53! Parsed an input with 24-hour and AM/PM values that do not match - "3" +versus "pm" at 1989_SoPW.pl line 48. 13:10 >

    — somewhat cryptic, but an indication that DateTime::Format::Strptime is expecting 24-hour and not 12-hour time here.

    Hope that helps,

    Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

Re: DateTime::Format::Strptime Parsing Seems to have a Problem?
by ozboomer (Friar) on Mar 26, 2019 at 04:23 UTC

    Appreciate the info... Seems I "couldn't see the forest for the trees" or something, as I was looking for the "12-hour" formatter... but didn't "see" the "%I"...(!)

    The final part of the puzzle is to understand how to ensure we can set-up a sequence of datetimes that are 1 day apart. If the mentioned mods are made to the code... and I add some additional code for setting-up a Recurrence, the code becomes:-

    use strict; use warnings; #use diagnostics; use DateTime; use DateTime::Format::Strptime; use DateTime::Event::Recurrence; # PART 2 our $LocalTZ = DateTime::TimeZone->new( name => 'local' ); my $dtnow = DateTime->now(time_zone => $LocalTZ); # Current *local* + time printf("\$dtnow: !%s!\n\n", $dtnow); my $lo = "18/03/2019 10:12:53 am"; # Always works my $hi = "24/03/2019 3:15:00 pm"; # Does not work... the format I + need to use # my $hi = "24/03/2019 03:15:00 pm"; # Does not work # my $hi = "24/03/2019 3:15:00 pm"; # Does not work # my $hi = "24/03/2019 15:15:00 pm"; # Works but 15hrs with 'pm' i +s silly # my $hi = "24/03/2019 15:15:00"; # Works my $dmy_fmt = DateTime::Format::Strptime->new( # Let DateTime use 'dd +/mm/yyyy' pattern => '%d/%m/%Y %I:%M:%S %p', # pattern => '%d/%m/%Y %I:%M:%S', # Use this when no 'am/pm' + in the date string # time_zone => 'Australia/Victoria', time_zone => 'local', on_error => 'croak' ); my $lodt = $dmy_fmt->parse_datetime($lo); printf(" \$lodt: !%s!\n", $lodt); my $hidt = $dmy_fmt->parse_datetime($hi); printf(" \$hidt: !%s!\n", $hidt); # ========== PART 2 ========== my $rec1 = DateTime::Event::Recurrence->daily(start => $lodt); # $rec1->set_time_zone('local'); # Doesn't change the 'mis-conversion' my @days = $rec1->as_list( start => $lodt, end => $hidt ); printf("1st Format DAYS:\n"); foreach my $day (@days) { printf(" %s\n", $day->dmy("-")); } printf("\n"); # ---- A different (truncated) format my $strp = DateTime::Format::Strptime->new( pattern => '%d/%m/%Y', time_zone => 'local', on_error => 'croak' ); my $dt1 = $strp->parse_datetime("18/03/2019"); #my $dt1 = $strp->parse_datetime($lo); printf(" \$dt1: !%s!\n", $dt1); my $dt2 = $strp->parse_datetime("24/03/2019"); #my $dt2 = $strp->parse_datetime($hi); printf(" \$dt2: !%s!\n", $dt2); my $rec2 = DateTime::Event::Recurrence->daily(start => $dt1); @days = $rec2->as_list( start => $dt1, end => $dt2 ); printf("2nd Format DAYS:\n"); foreach my $day (@days) { printf(" %s\n", $day->dmy("-")); } printf("\n");

    Here, the output is:-

    $dtnow: !2019-03-26T15:05:02! $lodt: !2019-03-18T10:12:53! $hidt: !2019-03-24T15:15:00! 1st Format DAYS: 19-03-2019 20-03-2019 21-03-2019 22-03-2019 23-03-2019 24-03-2019 $dt1: !2019-03-18T00:00:00! $dt2: !2019-03-24T00:00:00! 2nd Format DAYS: 18-03-2019 19-03-2019 20-03-2019 21-03-2019 22-03-2019 23-03-2019 24-03-2019

    So, it seems DateTime::Format::Strptime is looking at the input strings (which are local times) and converting them according to the 'time_zone' item (which is 'local')... and thus, the resultant objects contain the same time field values.

    However, when the recurrence is set-up for the "dd/mm/yyyy hh:mm:ss p"-derived objects, the date is incorrect. I thought it might have to do with the time zone... but setting the time zone on the occurrence made no difference to the dates it came up with.

    More confusing, if I set-up a recurrence using the "dd/mm/yyyy"-derived objects, the date is correct, although the time of day is set to 00:00:00 (obviously and reasonably).

    So, how does the recurrence using "dd/mm/yyyy hh:mm:ss p"-derived objects need to be configured to display the correct dates, including time of day?

    "It's all rather confusing, really...."

      You have not made your code easy enough to test each pair of date-time string & format-to-parse (by using a list of either array references or hash references).

      Do you have issues with both *::Strptime & *::Recurrence? If so, could you please address them in separate threads?

      About *::Strptime output as listed, I do not see anything wrong as far as values of $lodt, $hidt, $dt1, & $dt2 are concerned. What am I missing?

      About *:Recurrence, as I understood the documentation, each event happens at the start of the interval. If it is "daily", then the even will start at 00:00:00. It can be changed to a different time via options of "hours" & "minutes".

      now dt: 2019-03-27T02:46:41 start dt: 2019-03-18T10:12:53 end dt (24/03/2019 3:15:00 pm) : 2019-03-24T15:15:00 end dt (24/03/2019 03:15:00 pm) : 2019-03-24T15:15:00 end dt (24/03/2019 15:15:00 pm) : 2019-03-24T15:15:00 end dt (24/03/2019 15:15:00) : 2019-03-24T15:15:00 Recurring event at 11:23 from 2019-03-18T10:12:53 to 2019-03-24T15:15: +00 ... !! 2019-03-18T11:23:00 !! 2019-03-19T11:23:00 !! 2019-03-20T11:23:00 !! 2019-03-21T11:23:00 !! 2019-03-22T11:23:00 !! 2019-03-23T11:23:00 !! 2019-03-24T11:23:00

        Thanks for your thoughts, parv ...and granted, my code was a bit 'off' and could have made the comparisons clearer.

        Still, after looking at what we've covered, I'd say DateTime::Format::Strptime is doing what's expected -- it was more about how I (mis-)understood how the routine works (but see the bottom of this post).

        ...but the problem with 'Recurrence' is still not explained (well enough), to my thinking. To quote the docs:-

        "If no parameters are given, then the set members each occur at the be +ginning of the specified recurrence. For example, by default, the monthly() method returns a set containin +g the first day of each month. Without parameters, the weekly() method returns a set containing Mond +ays...."

        Unfortunately, we have no example in the docs for the 'daily' method we're looking at. Therefore, we can only assume that the 'daily' method gives us a set where each element (each repeat) begins at '00:00:00', as parv suggested... so we'll test that out. Note that as we're testing the 'beginning of the specified recurrence' note in the docs, we should follow what the docs declare and ensure "no parameters are given" -- hence, we'll modify parv's line of code from:-

        my $day_ev = DateTime::Event::Recurrence->daily( 'hours' => $hour , 'm +inutes' => $min );

        ...to be instead:-

        my $day_ev = DateTime::Event::Recurrence->daily;

        Doing so and running parv's code with no other modifications gives the following output:-

        now dt: 2019-03-30T23:41:09 start dt: 2019-03-18T10:12:53 end dt (24/03/2019 3:15:00 pm) : 2019-03-24T15:15:00 end dt (24/03/2019 03:15:00 pm) : 2019-03-24T15:15:00 end dt (24/03/2019 15:15:00 pm) : 2019-03-24T15:15:00 end dt (24/03/2019 15:15:00) : 2019-03-24T15:15:00 Recurring event at 11:23 from 2019-03-18T10:12:53 to 2019-03-24T15:15: +00 ... !! 2019-03-19T00:00:00 !! 2019-03-20T00:00:00 !! 2019-03-21T00:00:00 !! 2019-03-22T00:00:00 !! 2019-03-23T00:00:00 !! 2019-03-24T00:00:00

        ...showing the same error - the start of the period is '00:00:00' as we expected (hoped)... but the date is still wrong.

        Therefore, there appears to be something wrong with either our assumption of the 'beginning' of a 'daily' recurrence being '00:00:00' ...OR... there is a fault in the Recurrence code when 'no parameters are given'.

        I lean more to there being a fault in the Recurrence code, as if you change the 'hours' and 'minutes' parameters to '0' in the 'as_list()' method in parv's original code, the same error in date occurs.

        The other thing of interest is that if the DateTime::Format::Strptime usage includes a pattern that is only "%d/%m/%Y" and the string dates are specified to match, we still get the '00:00:00' beginning times on each element of the Recurrence set... but the date is correct -- this is what I did in the first modifications I made to the code in my original posting.

        Edit 31-Mar-2019 00:42 UTC: Something else that may be relevant here: The section "Floating DateTimes" in the DateTime module docs includes the comment:

        If you are planning to use any objects with a real time zone, it is strongly recommended that you do not mix these with floating datetimes.

        ...which would indicate we need to include a 'time zone' parameter at every call to a DateTime method... maybe?

        Any other thoughts?

Re: DateTime::Format::Strptime Parsing Seems to have a Problem?
by ikegami (Pope) on Mar 30, 2019 at 13:45 UTC

    Tip:

    our $LocalTZ = DateTime::TimeZone->new( name => 'local' ); my $dtnow = DateTime->now(time_zone => $LocalTZ);

    can be written as

    my $dtnow = DateTime->now( time_zone => 'local' );

      Right you are, ikegami... but the use of the code:

      our $LocalTZ = DateTime::TimeZone->new( name => 'local' ); my $dtnow = DateTime->now(time_zone => $LocalTZ);

      ...seems to be a practice that many folks follow, mainly in the interests of performance and consistency.

      The details are discussed in the DateTime module docs under the heading "Determining the Local Time Zone Can Be Slow".

        mainly in the interests of performance

        If you are going to use $LocalTZ more than once then storing the DateTime::TimeZone object as a variable has a performance benefit. Conversely if you are going to use it only the once then there's likely to be a (very small) performance penalty.

Re: DateTime::Format::Strptime Parsing Seems to have a Problem?
by thanos1983 (Parson) on Apr 04, 2019 at 15:14 UTC

    Hello ozboomer,

    It looks that fellow Monks have already answered your question. Just for fun here is an alternative solution using Date::Manip.

    #!/usr/bin/perl use strict; use warnings; use Date::Manip; use feature 'say'; sub getDate { my $tz = new Date::Manip::TZ; return $tz->convert_from_local(ParseDate(shift), 'Australia/Victor +ia'); } sub getDateFormat { my ($date, $from, $to) = @_; return UnixDate(Date_ConvTZ(ParseDate($date), $from, $to), '%d/%m/ +%Y %r'); } my @dates = ( "now", "03/18/2019 03:15:00", "03/18/2019 03:15:00 am", "03/18/2019 03:15:00 pm", ); foreach my $date (@dates) { my ( $err, $date, $offset, $isdst, $abbrev ) = getDate($date); say "Err: $err" if $err; say "Date: " . join(":", @$date); # say "@$date[2]/@$date[1]/@$date[0] @$date[3]:@$date[4]:@$date[5] +"; # say "Offset: " . join(":", @$offset); # say "Isdst: $isdst"; # say "Abbrev: $abbrev"; say getDateFormat($date,"CST", "AEDT"); } __END__ $ perl test.pl Date: 2019:4:5:2:3:8 05/04/2019 03:00:00 AM Date: 2019:3:18:13:15:0 18/03/2019 03:00:00 AM Date: 2019:3:18:13:15:0 18/03/2019 03:00:00 AM Date: 2019:3:19:1:15:0 19/03/2019 03:00:00 AM

    On the solution provided above I have two different functions. One is simply converting the time from the local timezone getDate (read more Date::Manip::TZ) and the other is converting from the timezone provided to the function getDateFormat (read more for timezone Date::Manip::Zones). The first function unfortunately it returns the date already formatted so there the format is fixed. The second it returns to your desired format.

    Hope this helps, BR.

    Seeking for Perl wisdom...on the process of learning...not there...yet!

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://1231677]
Approved by Athanasius
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others contemplating the Monastery: (9)
As of 2019-06-26 18:12 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    Is there a future for codeless software?



    Results (110 votes). Check out past polls.

    Notices?