Reputation: 81
I want to subtract two timestamps in Perl. I converted them to unix-time via the function below and convert the unix timestamp back to how it was. In the example below the result is 01:20:00 instead of 00:20:00 (I think it has sth to do with the start of the unix timestamp 1.1.1970 01:00:00 but not sure how to resolve it)
Any idea? Many thanks for your help in advance.
use POSIX qw( strftime );
use Time::Local qw( timelocal );
sub to_epoch {
$_ = shift;
my @a = split /\W+/, $_;
my $b = timelocal($a[5],$a[4],$a[3],$a[2],$a[1],$a[0]);
return $b;
}
my $h_end = "2018.11.12 00:50:00";
my $h_start = "2018.11.12 00:30:00";
my $duration = to_epoch($h_end) - to_epoch($h_start);
my $convert_back = POSIX::strftime("%H:%M:%S", localtime($duration));
print $convert_back , "\n";
Ouptut: 01:20:00
Upvotes: 1
Views: 385
Reputation: 69314
It works for me. But I think that's because I'm in GMT and you're in CET (GMT+1).
The flaw is in your final step. You are confusing two concepts - a point in time and a duration.
You correctly convert your two points in time to Unix epoch numbers and then you subtract those numbers to get the number of seconds between them. That number is a duration. And you want to convert that duration into a human-readable format. Using localtime()
and POSIX::strtime()
is not the way to do that. POSIX::strftime()
and localtime()
deal with points in time, not durations.
The number you get is 1,200. By passing that to localtime()
you are saying "what is the epoch number 1,200 when converted to a date and time in my local timezone?" 1,200 is 20 past midnight on Jan 1st 1970 GMT. But in your local, Frankfurt, timezone, it's 20 past 1am. Which is why you're getting 1:20 and I'm getting 0:20.
There are a couple of ways to fix this. You can do the conversion manually.
my $duration = 1_200;
my $mins = int($duration/60);
my $secs = $duration % 60;
Or you can use a proper date/time handling module like DateTime (along with its associated module DateTime::Duration).
It might work if you use timegm()
and gmtime()
in place of timelocal()
and localtime()
- but I really don't recommend this approach as it perpetuates the confusion between points in time and durations.
Update: A version using DateTime.
#/usr/bin/perl
use strict;
use warnings;
use DateTime;
use DateTime::Format::Strptime;
my $h_end = '2018.11.12 00:50:00';
my $h_start = '2018.11.12 00:30:00';
my $date_p = DateTime::Format::Strptime->new(
pattern => '%Y.%m.%d %H:%M:%S'
);
my $duration = $date_p->parse_datetime($h_end)
- $date_p->parse_datetime($h_start);
printf '%02d:%02d:%02d', $duration->in_units('hours', 'minutes', 'seconds');
Upvotes: 2
Reputation: 386706
1200, the value of $duration
, signifies the following when treated as a epoch timestamp
1970-01-01T01:20:00+01:00
^^^^^^^^
The solution is to replace
strftime("%H:%M:%S", localtime($duration));
with
strftime("%H:%M:%S", gmtime($duration));
This gives
1970-01-01T00:20:00Z
^^^^^^^^
Of course, this is still a hack. You're not suppose to be passing a duration to gmtime
. Use an appropriate module instead.
use DateTime::Format::Strptime qw( );
my $format = DateTime::Format::Strptime->new(
pattern => '%Y.%m.%d %H:%M:%S',
on_error => 'croak',
);
my $h_end = $format->parse_datetime('2018.11.12 00:50:00');
my $h_start = $format->parse_datetime('2018.11.12 00:30:00');
my $dur = $h_end - $h_start;
printf "%02d:%02d:%02d\n", $dur->in_units(qw( hours minutes seconds ));
By the way,
timelocal($a[5],$a[4],$a[3],$a[2],$a[1],$a[0])
should be
timelocal($a[5],$a[4],$a[3],$a[2],$a[1]-1,$a[0])
Upvotes: 1