devio
devio

Reputation: 1169

Perl : calculation of time in format hh:mm:ss,sss

I need to calculate the time difference between these two variables in Perl :

my $end = "17:23:31,576";
my $start = "17:23:30,858";

How do I calculate the time difference ($end - $start) ? It's important the the returned value keeps the same format.

I would prefer, ideally, to do that with a native function

I've already tried packages like Time::Piece; and DateTime::Format::Strptime; but couldn't make it work.

Upvotes: 1

Views: 2545

Answers (3)

ikegami
ikegami

Reputation: 385647

A DateTime solution, since it has native support for fractional seconds. Once you got the two DateTime objects, you can use the following program:

# delta_ms loses nanoseconds, but we need it to convert days into hours.
my ($h, $m, $s) = $dt2->delta_ms($dt1)->in_units(qw( hours minutes seconds ));
my $ns = ( $dt2 - $dt1 )->nanoseconds;

say sprintf '%d:%02d:%02d,%03.0f', $h, $m, $s, $ns/1_000_000;

Now, let's look at how to get the two DateTime objects. Given the information provided, the best you can do is the following:

use DateTime::Format::Strptime qw( );

my $format = DateTime::Format::Strptime->new(
   pattern   => '%H:%M:%S,%3N',
   time_zone => 'floating',
   on_error  => 'croak',
);

my $dt1 = $format->parse_datetime('01:59:58,123');
my $dt2 = $format->parse_datetime('03:01:02,456');

Like all other solutions, this won't always give the right answer because the difference can depend on the date and time zone (e.g. due to DST changes), and this information wasn't made available.

If you do have the information available, then you'd use the following, which correctly gives 1:01:04,333 instead of 0:01:04,333 on the day DST starts.

use DateTime::Format::Strptime qw( );

my $format = DateTime::Format::Strptime->new(
   pattern   => '%Y-%m-%d %H:%M:%S,%3N',
   time_zone => 'America/New_York',  # Often: 'local'
   on_error  => 'croak',
);

my $dt1 = $format->parse_datetime('2015-03-09 01:59:58,000');
my $dt2 = $format->parse_datetime('2015-03-09 03:01:02,000');

Upvotes: 5

FtLie
FtLie

Reputation: 773

Just a similar, but more manual approach, caclulating the diff manually, and using gmtime to split it out into hours, minutes and seconds. Handling fractions manually, as gmtime only uses integers.

This is kind of reinventing Time::Piece, so I actually prefer ysth's solution.

my $start = "17:23:30,858";
my $end   = "17:23:31,576";
#split by ; and ,
my ($h_start,$m_start,$s_start, $frac_start) = split(/[:,]/, $start) ;
my ($h_end,$m_end,$s_end, $frac_end) = split(/[:,]/, $end) ;

#find #seconds since epoch (whatever it is)
my $start_time = $s_start + $m_start * 60 + $h_start * 3600;
my $end_time   = $s_end   + $m_end   * 60 + $h_end   * 3600;
my $diff_sec   = $end_time - $start_time;

#handling fractions independently, paying attention to negative fractions
#0.0 + first to force float
my $diff_frac = 0.0 + "0.$frac_end" - "0.$frac_start";
if ($diff_frac < 0) {
    $diff_frac++;
    $diff_sec--;
}
#then using the build in function to convert from seconds from epoch to sec min hrs
#using gmt (not local) to convert without timezones and daylight savings...
my ($s_diff,$m_diff,$h_diff) = gmtime( $diff_sec);

#and finally manual formatting using sprintf    
my $diff_date = sprintf("%02u:%02u:%02u,%03u", $h_diff,$m_diff,$s_diff,$diff_frac*1000);
print $diff_date;

Upvotes: 1

ysth
ysth

Reputation: 98388

With Time::Piece, I think you have to handle the milliseconds separately.

use Time::Piece;
my $start = "17:23:31,576";
my $end = "17:23:30,858";
my ($start_time,$start_ms) = split /,/, $start;
my ($end_time,$end_ms) = split /,/, $end;
my $difference = Time::Piece->strptime($end_time,'%H:%M:%S') - Time::Piece->strptime($start_time,'%H:%M:%S') + ( $end_ms - $start_ms ) / 1000;

To turn the fractional number of seconds back into a formatted time is easy, if messy:

my $negative = $difference < 0 ? '-' : '';
$difference = abs $difference;
my $seconds = int $difference;
my $ms = sprintf '%03d', 1000 * ($difference - $seconds);
my $formatted_difference = Time::Piece->gmtime($seconds)->strftime("$negative%H:%M:%S,$ms");

Upvotes: 1

Related Questions