-1

Every time I tried to find the difference of these date strings, there is an error. I wonder if you could help me this.

my $datecreated = '2021-09-06 04:52:38';
my $dateresolved = '2021-09-06 04:52:48';

my $time_elapsed= $dateresolved - $datecreated;
print $time_elapsed;

And I want to convert the result into minutes and hours.

Dada
  • 6,313
  • 7
  • 24
  • 43
blizzy
  • 55
  • 6
  • 1
    Does this answer your question? [How to calculate date difference in perl](https://stackoverflow.com/questions/18525671/how-to-calculate-date-difference-in-perl) – Dada Sep 06 '21 at 06:29
  • 1
    Other potential duplicates: [Perl subtract two dates](https://stackoverflow.com/q/40279955/4990392), [How can I calculate the number of days between two dates in Perl?](https://stackoverflow.com/q/821423/4990392), [Subtract two date strings in Perl with conversion to unix time and reverting back](https://stackoverflow.com/q/53759211/4990392), [Perl - Calculate difference between two timestamps](https://stackoverflow.com/q/29546190/4990392), and probably a dozen more. – Dada Sep 06 '21 at 06:32
  • @Dada While there's certainly a lot of related material out there, neither of these quite match what is asked here (many answer different questions, or use outdated or inadequate tools, or are even outright wrong). For one thing, none of them deal with conversion which can be slightly tricky – zdim Sep 06 '21 at 09:28
  • (in short, no, these aren't quite dupes) – zdim Sep 06 '21 at 09:33
  • @zdim I disagree. The first one ([How to calculate date difference in perl](https://stackoverflow.com/q/18525671/4990392)) provides the code needed to get the number of seconds between 2 dates: you only need two divisions and a modulo to get the number of hours/minutes. In the second one ([Perl subtract two dates](https://stackoverflow.com/q/40279955/4990392)), you can just copy-paste the answer and remove the seconds from the function `sec2duration`. The definition of a duplicate is not "the answers can be copy-pasted from one question to another" (I can find a reference for that if needed). – Dada Sep 06 '21 at 09:43
  • Although I understand your arguments, and if OP had put the slightest effort into solving his problem, I might not have voted to close as dup. Anyways, this is a democracy, I've cast my vote and I stand by it, but other users are free to vote as they feel :) – Dada Sep 06 '21 at 09:45
  • @Dada I Absolutely do not contest your good right to vote or not any way you like! Please do :) But-- the first link in your response merely gets the difference (w/o regard for timezones btw) -- and walks away, no conversion; the second one employs an arduous manual approach which begs for alternatives (while it's good to have). Having any one solution doesn't have to mean a closed ticket. So I stand by my decision to post an answer here and I don't think that this should be closed. (While it is indeed not a good question i think because it lacks a a good attempt.) My opinion. – zdim Sep 06 '21 at 19:18

2 Answers2

3

These two timestamps are mere strings. In order to get the duration between these two moments in time ("subtract" them) one needs to build date-time objects from them, in a library that knows how to then find duration between them. One good choice is DateTime

use warnings;
use strict;
use feature 'say';

use DateTime; 
use DateTime::Format::Strptime; 

my ($ts1, $ts2) = (@ARGV == 2) 
    ? @ARGV : ('2021-09-05 04:52:38', '2021-09-01 04:52:48');

my $strp = DateTime::Format::Strptime->new(
    pattern => '%F %T', time_zone => 'floating', on_error => 'croak'
);    
my ($dt1, $dt2) = map { $strp->parse_datetime($_) } $ts1, $ts2;

# Get difference in hours and minutes (seconds discarded per question)
my ($hrs, $min) = delta_hm($dt1, $dt2);
say "$hrs hours and $min minutes";

# Or (time-stamp hh:mm in scalar context)
my $ts_hm = delta_hm($dt1, $dt2);
say $ts_hm;

# To get wanted units (hours+minutes here) best use a delta_X
sub delta_hm {
    my ($dt1, $dt2) = @_;
    my ($min, $sec) = $dt1->delta_ms($dt2)->in_units('minutes', 'seconds');
    my $hrs = int( $min / 60 );
    $min = $min % ($hrs*60) if $hrs;

    return (wantarray)    # discard seconds
        ? ($hrs, $min)
        : join ':', map { sprintf "%02d", $_ } $hrs, $min;
}

The hard-coded input time-stamps here are different than the ones in the question; those would make an hour+minute difference a zero, since they differ only in seconds! (Is that intended?) One can also submit two time-stamp strings as input to this program.

Note that a generic duration object makes it harder to convert to any particular desired units

One cannot in general convert between seconds, minutes, days, and months, so this class will never do so. Instead, create the duration with the desired units to begin with, for example by calling the appropriate subtraction/delta method on a DateTime.pm object.

So above I use delta_ms since minutes are easily converted to hours+minutes. Seconds are discarded as the question implies (if that is in fact unintended add them in the routine).

For more general uses one can do

use DateTime::Duration;

my $dur = $dt1->subtract_datetime($dt2);

# Easy to extract parts (components) of the duration
say "Hours: ", $dur->hours, " and minutes: ", $dur->minutes;  # NOT conversion

Can do this with the core Time::Piece as well

use warnings;
use strict;
use feature 'say';

use Time::Piece;

my ($ts1, $ts2) = (@ARGV) 
    ? @ARGV : ('2021-09-05 04:52:38', '2021-09-01 04:52:48');

my ($dt1, $dt2) = map { Time::Piece->strptime($_, "%Y-%m-%d %T") } $ts1, $ts2; 
# In older module versions the format specifier `%F` (`%Y-%m-%d`) may fail 
# so I spell it out here; the %T (for %H:%M:%S) should always be good
# For local times (not UTC) better use Time::Piece::localtime->strptime

my $delta = $dt1 - $dt2; 
# say $delta->pretty;

my $hrs = int( $delta->hours );  
my $min = int($delta->minutes) - ($hrs//=0)*60;
say "$hrs:$min"; 

This is much simpler, but watch out for occasional tricky (error-inducing) API of Time::Piece.

Note, while Time::Piece is core, succinct, and much lighter (and correct!), the DateTime is far more rounded and powerful, also with an ecosystem of extensions.

zdim
  • 64,580
  • 5
  • 52
  • 81
  • Hi thank you for your help! But when I print $duration it is in DateTime::Duration=HASH(0x55630e4ca6c8) I want to convert this in minutes. – blizzy Sep 06 '21 at 06:54
  • @blizzy Right, I got pulled away from this and didn't complete -- did now. See update – zdim Sep 06 '21 at 07:02
  • @blizzy Updated further – zdim Sep 06 '21 at 08:43
  • Thank you so much for your help! I just would like to verify if I can get the result in minutes? I'm thinking that I'm going to have an: $dateresolved: 2021-09-08 02:29:46 $datecreated: 2021-07-16 05:28:05 – blizzy Sep 08 '21 at 03:13
  • The first code you gave was great but can I output time difference only in minutes? Disregard hours? – blizzy Sep 08 '21 at 04:21
  • @blizzy In the first program, which uses `DateTime`, just return those minutes (from the subroutine `delta_hm`). It's just `my $min = $dt1->delta_ms($dt2)->in_units('minutes');`. That is the difference between `$dt1` and `$dt2` expressed in minutes. – zdim Sep 08 '21 at 04:35
  • @blizzy In the second program, using `Time::Piece`, once you got the `$delta = $dt1 - $dt2;`, just use `$delta->minutes`. That's the difference between `$dt1` and `$dt2` in minutes. – zdim Sep 08 '21 at 04:37
  • @blizzy Corrected a silly typo in the second program (which uses `Time::Piece`) and changed format to a safer version (that `%F` doesn't work in older module versions) – zdim Sep 08 '21 at 05:05
  • @blizzy I didn't see your first comment at first! The difference between those two is going to be a lot of minutes! :) You can run both programs in this answer by giving them those strings as arguments, like `perl program_name.pl "2021-09-08 02:29:46" "2021-07-16 05:28:05"` (that `program_name.pl` is for whatever name you choose). With these two date-times I get the difference in minutes to be `77581`. (When I add a line to print just in minutes, like `say $dt1->delta_ms($dt2)->in_units('minutes');`) – zdim Sep 08 '21 at 05:11
  • I still have something to ask, if you could also help me with this. When I subtract dates $dateresolved= 2021-09-12 04:17:10 $datecreated= 2021-09-12 04:17:10 I get an output of negative something, I'm just going to have this kind of possiblity where I might get a negative output. Can I get a value of '0' instead of negative value? How to format/convert that? Thanks! – blizzy Sep 13 '21 at 02:13
  • @blizzy (1) In the first case (with DateTime), it's never negative; it just gives the difference. But if you _want to_ make it zero when it would be negative, after you get the result (`$ts_hm`) test whether a date-time is before another and make that result zero if needed, like: `$ts_hm = 0 if if $dt1 <= $dt2;` (makes it zero if first time is less-than or equal to the second). (2) Second case, with Time::Piece, again, once you get the result (`$delta`) test whether it is negative and if it is assign zero: `$delta = 0 if $delta < 0;` – zdim Sep 13 '21 at 05:27
  • Got the idea. Thanks! There is still something I want to ask, I want to add 30 hours to $datecreated, how to do that using Time::Piece Module? – blizzy Sep 13 '21 at 05:40
  • @blizzy (I also fixed a couple of blunders in code) – zdim Sep 13 '21 at 05:55
  • @blizzy "_to add X hours_" -- there is a companion module, [Time::Seconds](https://perldoc.perl.org/Time::Seconds), which defines various simple operations and some constants. (Some of `Time::Piece` functions use that.) So there is `ONE_HOUR` constant, so you can do `use Time::Seconds; ... $dt += 30*ONE_HOUR;` (have to import `Time::Seconds` separately). This constant is a name for a number so use it as a number. – zdim Sep 13 '21 at 06:08
1

Use Time::Piece which is a standard part of the Perl library since 2007.

#!/usr/bin/perl

use strict;
use warnings;

use Time::Piece;

# Define the format of your inputs
my $format = '%Y-%m-%d %H:%M:%S';

# Convert your date strings into Time::Piece objects
my $datecreated  = Time::Piece->strptime('2021-09-06 04:52:38', $format);
my $dateresolved = Time::Piece->strptime('2021-09-06 04:52:48', $format);

# Time::Piece objects can be subtracted from each other.
# This gives the elapsed time in seconds.
my $time_elapsed = $dateresolved - $datecreated;

# Do the calculations to displace the elapsed time in hours,
# minutes and seconds.
printf "%02dh:%02dm:%02ds\n",
       $time_elapsed->hours,
       $time_elapsed->minutes % 60,
       $time_elapsed->seconds % 60;
Dave Cross
  • 68,119
  • 3
  • 51
  • 97
  • Can I also get the result in minutes only? Like time difference in minutes. I want to get the timediff of: $datecreated:2021-07-16 05:28:05 $dateresolved:2021-09-08 02:29:46 – blizzy Sep 08 '21 at 03:18
  • When I tested the script above, it says time parsing error. – blizzy Sep 08 '21 at 03:24
  • @blizzy: Unsurprisingly, if you just want the minutes, you can use `$time_elapsed->minutes`. I don't know why you're getting an error. It works fine for me. Are you parsing different date/time strings? – Dave Cross Sep 08 '21 at 16:28
  • Yes I am trying to get the difference of these two $datecreated: 2021-07-16 05:28:05 $dateresolved: 2021-09-08 02:29:46 but it says: Error parsing time at /usr/lib/x86_64-linux-gnu/perl/5.26/Time/Piece.pm line 481. – blizzy Sep 10 '21 at 05:09
  • @blizzy: Yes, I had the date format string wrong. I had `%Y-%d-%m`. I've just corrected it to `%Y-%m-%d`. – Dave Cross Sep 10 '21 at 06:06