in reply to Yesterday's date

I would be using Time::Piece for this since it is in core.

#!/usr/bin/env perl use strict; use warnings; use Time::Piece; my $t = Time::Piece->strptime ($ARGV[0], '%d/%m/%Y'); printf "Date as supplied: %s\n", $t->dmy ('/'); $t = $t - 86400; printf "Date minus one day: %s\n", $t->dmy ('/');
$ ./yesterday.pl 01/12/2022 Date as supplied: 01/12/2022 Date minus one day: 30/11/2022 $

🦛

Replies are listed 'Best First'.
Re^2: Yesterday's date
by Corion (Patriarch) on May 28, 2023 at 13:47 UTC

    Note that this approach will have a problem for those days with less than 24 hours (like when switching between normal and daylight savings time.

    I prefer the approach to iteratively subtract 22 hours until the (stringified) date changes:

    ... my $res = $t - 22*60*60; while( $res->ymd('-') eq $t->ymd('-') ) { $res -= 22*60*60; } return $res
      Note that this approach will have a problem for those days with less than 24 hours

      I would be interested to see a test case showing that, if you could provide one. The tests I tried all worked fine, even on 23-hour days. That's why I posted it as-is rather than trying to work around a problem which didn't appear to be there.

      Here's my trivial test script for comparison:

      use strict; use warnings; use Time::Piece; use Test::More tests => 366 * 2 + 1; my $dstr = '01/01/2023'; for (1 .. 366) { my $t = Time::Piece->strptime ($dstr, '%d/%m/%Y'); my $nt = $t - 86400; isnt $dstr, $nt->dmy ('/'); my $ddiff = $t->mday - $nt->mday; ok ($ddiff < 0 || $ddiff == 1), '1 day different or month wrap'; $dstr = $nt->dmy ('/'); } is $dstr, '31/12/2021';

      🦛

        This is because Time::Piece doesn't care as much about timezones and DST. When using DateTime (because I know it is exact with timezones, bordering on the unusable), in the following program, I can make things skip from 03/27/2023 to 03/25/2023:

        use 5.020; use Test2::V0; use DateTime; use DateTime::Format::Strptime; my $dstr = '2023-03-27 00:30'; my $strp = DateTime::Format::Strptime->new( pattern => '%Y-%m-%d %H:%M', locale => 'de_DE', time_zone => 'Europe/Paris', ); my $t = $strp->parse_datetime($dstr); diag $t->strftime('%Y-%m-%d %H:%M %z'); my $nt = $t->clone->subtract( seconds => 24*60*60 ); isnt $t->ymd('-'), $nt->ymd('-'); diag $nt->strftime('%Y-%m-%d %H:%M %z'); my $ddiff = $t->mday - $nt->mday; ok( ($ddiff < 0 || $ddiff == 1), '1 day different or month wrap'); $dstr = $nt->ymd ('-'); is $dstr, '2023-03-26';

        Update: I just realized that your code doesn't care about the time, and it (implicitly) always is at 00:00. Adjusting my example from 00:30 to 00:00 shows the problem there:

        use 5.020; use Test2::V0; use DateTime; use DateTime::Format::Strptime; my $dstr = '2023-03-27'; my $strp = DateTime::Format::Strptime->new( pattern => '%Y-%m-%d', locale => 'de_DE', time_zone => 'Europe/Paris', ); my $t = $strp->parse_datetime($dstr); diag $t->strftime('%Y-%m-%d %H:%M %z'); my $nt = $t->clone->subtract( seconds => 24*60*60 ); isnt $t->ymd('-'), $nt->ymd('-'); diag $nt->strftime('%Y-%m-%d %H:%M %z'); my $ddiff = $t->mday - $nt->mday; ok( ($ddiff < 0 || $ddiff == 1), '1 day different or month wrap'); $dstr = $nt->ymd ('-'); is $dstr, '2023-03-26';
      > Note that this approach will have a problem for those days with less than 24 hours

      If you want Time::Piece to respect time zones, you need to initialize the object from localtime, not from the class itself:

      $ TZ=Europe/London perl -MTime::Piece -wE ' for my $source ("Time::Piece", scalar localtime()) { my $t = $source->strptime("2023/03/27", "%Y/%m/%d"); say $t - 86400; }' Sun Mar 26 00:00:00 2023 Sat Mar 25 23:00:00 2023

      map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]

      Note that this approach will have a problem for those days with less than 24 hours

      That's not true. Or rather, there are no days with less than 24 hours when dealing with UTC, as the parent code does.

Re^2: Yesterday's date
by kcott (Archbishop) on May 29, 2023 at 04:05 UTC

    G'day hippo,

    ++ I agree with your approach; particularly in light of the OP's "I am not concerned about daylight savings time, time zone nor do I need HMS.".

    Having said that, I believe use of the ONE_DAY constant from Time::Seconds (also a core module) would add clarity.

    It's the same value:

    $ perl -E 'use Time::Seconds; say ONE_DAY' 86400

    It works as a direct replacement in your code:

    $ perl -e ' use strict; use warnings; use Time::Piece; use Time::Seconds; my $t = Time::Piece->strptime($ARGV[0], "%d/%m/%Y"); printf "Date as supplied: %s\n", $t->dmy("/"); $t = $t - ONE_DAY; printf "Date minus one day: %s\n", $t->dmy("/"); ' 01/12/2022 Date as supplied: 01/12/2022 Date minus one day: 30/11/2022

    — Ken

      If $Time::Seconds::ONE_DAY remains constant regardless of current time, then do not see how that would add "clarity" or see its purpose. If 24 *60 *60 conjures up magic number, then just assign that to a local variable.

        G'day parv,

        hippo's code uses 86400. For those regularly working with dates and times, this might immediately leap out as the number of seconds in a day; for others, it might just be a cryptic number (which takes some thought to understand).

        Time::Seconds puts that number into a named constant. From its source:

        use constant { ... ONE_DAY => 86_400, ... };

        I do believe that ONE_DAY is clearer than 86400. Even more so in this particular instance where "$t - ONE_DAY" aligns with the text in the following printf statement, "Date minus one day".

        I don't have a problem with "24 * 60 * 60". I would have used that calculation many times myself, over the decades, where ONE_DAY (or equivalent) was not available. It does, however, seem like extra, unecessary work:

        ... my $one_day = 24 * 60 * 60; ... $t = $t - $one_day; ...

        Unless it's needed more than once, I probably wouldn't bother to "assign that to a local variable":

        ... $t = $t - 24 * 60 * 60; ...

        — Ken