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.
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');
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");
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;
Related
PERL : I take mday value from locatime as below ,now I want value of day before yesterday . How can I subtract 1 from mday which I take from localtime
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
my $part = "P".$mday;
print "Today value is $part \n";
my $part_yes = "P".$mday - $num;
print "$part_yes \n";
Using DateTime:
my $dt =
DateTime
->now( time_zone => 'local' )
->set_time_zone('floating') # Do this when working with dates.
->truncate( to => 'days' ); # Optional.
$dt->subtract( days => 2 );
my $yesterday = $dt->day;
DateTime is pretty heavy, and it seems people asking date-time questions invariably come back and say "core modules only!", so here's a solution using only core modules.
use Time::Local qw( timegm );
# Create an timestamp with the same date in UTC as the one local one.
my $epoch = timegm(0, 0, 0, ( localtime() )[3,4,5]);
# We can now do date arithmetic without having to worry about DST switches.
$epoch -= 2 * 24*60*60;
my $yesterday = ( gmtime($epoch) )[3] + 1;
my $now = time(); # time as seconds since 1970-01-01
my $day_ago = $now - 24*60*60;
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($day_ago);
my $part = "P".$mday;
WARNING (ikegami comment): Not all days have 24 hours. This can produce a result that's 0, 1 or 2 days earlier
https://perldoc.perl.org/functions/time
https://perldoc.perl.org/functions/localtime
I want to get the offset in hours between localtime and gmtime given microseconds since epoch. What I have so far is something like this
# Microseconds since Epoch
my $msec = 555329743301750;
# Convert to seconds
my $sec = $msec/1000000;
my $val = (POSIX::mktime(localtime $sec) - POSIX::mktime(gmtime $sec)) / 60 / 60;
print "$val\n";
1) The output I get is -6.(CST localtime) However I am expecting -5. The behavior I am expecting is something similar to the result from running bash command
`date -d 20190514 "+%z"`;
2) Long story short, how do I compute offset similar to the date -d 20190514 "+%z" in perl?
Using the core Time::Piece:
use strict;
use warnings;
use Time::Piece;
# Microseconds since Epoch
my $usec = 555329743301750;
# Convert to seconds
my $sec = $usec/1000000;
my $offset = localtime($sec)->tzoffset / 3600;
Time::Moment and DateTime can also return this value, if you construct them as I showed on your other question.
my $offset = $time_moment->offset / 60;
my $offset = $datetime->offset / 3600;
You can also create these objects from dates, but it will of course give you the offset at midnight (local time) on that day.
use Time::Piece;
my $time = localtime->strptime('20190514', '%Y%m%d');
use Time::Moment;
use Role::Tiny ();
my $class = Role::Tiny->create_class_with_roles('Time::Moment', 'Time::Moment::Role::TimeZone');
my $time = $class->new(year => 2019, month => 5, day => 14)->with_system_offset_same_local;
use DateTime;
my $time = DateTime->new(year => 2019, month => 5, day => 14, time_zone => 'local');
This will give you the same [+-]HHMM format as the Linux "%z" format:
use POSIX qw[mktime];
my $tz = (localtime time)[8] * 60 - mktime(gmtime 0) / 60;
$tz = sprintf "%+03d%02d\n", $tz / 60, abs($tz) % 60;
If you just want the offset in hours, you can do this:
my $tz_offset_hours = (localtime time)[8] - mktime(gmtime 0)) / 3600;
Bonus: The following subroutine will return a full timestamp with time zone offset and microseconds, as in "YYYY-MM-DD HH:MM:SS.nnnnnn [+-]HHMM":
use POSIX qw[mktime strftime];
use Time::HiRes qw[gettimeofday];
sub timestamp () {
my #now = gettimeofday;
my $tz = (localtime $now[0])[8] * 60 - mktime(gmtime 0) / 60;
my $ts = strftime("%Y-%m-%d %H:%M:%S", localtime $now[0]);
return sprintf "%s.%06d %+03d%02d", $ts, $now[1], $tz / 60, abs($tz) % 60;
}
Is there a perl function that converts YYYYMMDD to a 16digit epoch time?
I'm currently calling the date command:
date -d 20160219 +%s%6N
for the conversion but my script takes a long time to go through the millions of dates in my data set.
By "16digit epoch time", I mean a 16-digit decimal integer representing microseconds since the epoch. For example, 2016-02-19 00:00:00 UTC is 1455840000 seconds after the epoch, so the result I want is "1455840000000000".
You can also use Time::Moment. In the interest of full disclosure, I am the author of Time::Moment.
use v5.10;
use Time::Moment;
say Time::Moment->from_string('20160219' . 'T00Z')
->strftime('%s%6N');
Output:
1455840000000000
Time::Piece can do all the things you want. After that just right pad 0's to epoch, so that it becomes 16 digits.
#!/usr/bin/perl
use strict;
use warnings;
use Time::Piece;
my $t = Time::Piece -> strptime("20160219",
"%Y%m%d");
my $epoch = $t -> epoch;
# make $epoch 16 digits
$epoch .= "0" x (16 - length($epoch));
print "$epoch\n";
You can use the Time::Local core module:
use Time::Local 'timelocal';
$date = "20160218";
($year, $month, $day) = unpack "A4A2A2", $date;
$time = timelocal(0, 0, 0, $day, $month-1, $year);
$time .= '000000';
Wow, I just actually wrote a script to take the timestamp where it takes a timestamp where it's in seconds since epoch and convert it to human readable format. Not what you asked, but perhaps you can reverse what I wrote.
my $ts = $ARGV[0]; #2012-09-04-20:24:19.870 1030825460
my $newTs = toTimeString($ts);
print "\n".$newTs."\n";
## Convert from time since epoch in seconds to string YYYY-MM-DD-HH:mm:ss.sss
sub toTimeString{
my $ts=shift;
$ts += 432000; # Add 5 days to make computation easy.
my $sec = $ts-int($ts/60)*60; # Seconds
$ts = int($ts/60); # Convert to minutes
my $min = $ts%60; # minutes
$ts = int($ts/60); # Convert to hours.
my $hr = $ts%24; # hours;
$ts = int($ts/24); # Convert to days.
my $yr;
my $mo;
my $day;
my $days = [ [31,28,31,30,31,30,31,31,30,31,30,31],
[31,29,31,30,31,30,31,31,30,31,30,31]
];
our $cumDays = [ [0,31,59,90,120,151,181,212,243,273,304,334],
[0,31,60,91,121,152,182,213,244,274,305,335]
];
if($ts<366){$yr=1980;}
else{
$yr = 1981 + int(($ts-366)/1461)*4;
$ts = $ts - 366 - int(($ts-366)/1461)*1461;
if($ts < 1095){$yr += int($ts/365); $ts = $ts - int($ts/365)*365;}
else{$yr = $yr+3; $ts=$ts-1095;}
}
my $leap=0;
my $i;
if($yr%4 == 0){$leap=1;}
for($i=0;$i<12;$i++){
if($ts > $cumDays->[$leap]->[$i]){
$mo=$i+1;
}
}
$day=$ts-$cumDays->[$leap]->[$mo-1]+1;
my $ret;
$ret = sprintf("%04d",$yr)."-".sprintf("%02d",$mo)."-".sprintf("%02d",$day)."-";
$ret = $ret.sprintf("%02d",$hr).":".sprintf("%02d",$min).":".sprintf("%02d",int($sec));
$ret = $ret."\.".sprintf("%03d",int($sec*1000)%1000);
return ($ret)
}
My script is :
use warnings;
use strict;
my $start_time = localtime();
$sql = "INSERT into Table1, Select (VAr1, Var2, Var3 ...........)";
my $end_time = localtime();
my $run_time = ($end_time - $start_time);
my #months = ("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec");
my ($sec, $min, $hour, $day,$month,$year) = (localtime($run_time))[0,1,2,3,4,5];
print "Unix time ".$run_time." converts to ".$months[$month]." ".$day.", ".($year +=1900);
print " ".$hour.":".$min.":".$sec."\n";
When I run it, I don't get the desire output.
Unix time 648 converts to Jan , 1900 ::
I need to get hour:min:sec
the time the script took to calculate the insert into the table.
Thank you
$run_time isn't a timestamp (a number of seconds since epoch representing a date-time), so it makes no sense to pass it to localtime.
my $s = $run_time % 60; $run_time = ($run_time - $s) / 60;
my $m = $run_time % 60; $run_time = ($run_time - $m) / 60;
my $h = $run_time;
my $formatted_run_time = sprintf "%d:%02d:%02d", $h, $m, $s;
You can get fancier too:
my $formatted_run_time =
$h ? sprintf "%d:%02d:%02d", $h, $m, $s
$m ? sprintf "%d:%02d", $m, $s
$s;
localtime is used to convert unix timestamps to a human-readable time (or a list of parts of it), but you are giving it a duration in seconds. It will treat it like a timestamp, and give you a very low date and time.
print scalar localtime 648;
# gives Thu Jan 1 01:10:48 1970
Your code gives the following output:
Unix time 648 converts to Jan 1, 1970 1:10:48
The problem is that you are essentially mixing two concepts here.
You might want to use the Benchmark module instead, which is intended for this exact purpose.
use strict;
use warning;
use Benchmark;
my $t0 = Benchmark->new;
# do your database stuff here
my $t1 = Benchmark->new;
my $td = timediff($t1, $t0);
print "the code took:",timestr($td),"\n";
If you are on a Linux or Unix, you can also use the time program to meassure the overall runtime of your program.
$ time perl foo.pl
real 0m2.241s
user 0m2.236s
sys 0m0.000s
$
I saved a DateTime stamp to my database:
2015-02-23T16:59:25
I have a new DateTime Stamp which represents current time.
2015-02-24T16:59:25
I need to compare the two with DateTime to check if 24 hours has passed.
#!/usr/bin/perl
use strict;
use DateTime;
my $longenough;
Testing123();
exit;
sub Testing123{
my $yesterday = DateTime->now;
$yesterday->add( days => -1 );
#This $yesterday time gets saved to my database.
#For question purposes, I'll just pass it along instead of from db.
CheckLapse($yesterday);
if ($longenough eq 'y'){ print qq~24 hours have passed<br />~; }
else{print qq~24 hours have not passed<br />~;}
}
sub CheckLapse{
$yesterday = shift;
my $now = DateTime->now;
# leftovers from many many hours of different attempts from my old bramaged drain
# my $elapse = $now - $yesterday;
# $now->subtract_duration( $yesterday ) ;
# $TimeLapse = $elapse->in_units('seconds'); #Left over from another try
# print "Elapsed time : ".$elapse->in_units('seconds')."m\n";
## I need to compare the two times and pass $longenough y/n back:
if ($TimeLapse >= [24 hours whatever seconds minutes to do calc]){
$longenough = 'y';
}
else {$longenough = 'n';}
return $longenough;
}
exit;
I have read and read the cpan DateTime docs and tried everything except for obviously, the correct solution.
I just keep getting errors like "Can't call method "yada yada" without a package or object reference".
Could someone educate me here?
You can construct a DateTime object from your string as follows:
use DateTime::Format::Strptime qw( );
my $dt_format = DateTiFormat::Strptime->new(
pattern => '%Y-%m-%dT%H:%M:%S',
time_zone => 'local',
on_error => 'croak',
);
my $dt = $dt_format->parse_datetime('2015-02-23T16:59:25');
Then you can check if it's 24 hours old like this:
my $now = DateTime->now( time_zone => $dt->time_zone );
if ($dt->clone->add( hours => 24 ) >= $now) {
# It's been more than 24 hours.
...
}
Now, the above does a lot of work every time you check if 24 hours have passed. If you do this repeatedly, you can use reduce the workload as follows:
use DateTime::Format::Strptime qw( );
my $dt_format = DateTime::Format::Strptime->new(
pattern => '%Y-%m-%dT%H:%M:%S',
time_zone => 'local',
on_error => 'croak',
);
my $dt = $dt_format->parse_datetime('2015-02-23T16:59:25');
$dt->add( hours => 24 );
my $target_time = $dt->epoch;
Then check then simplifies to
if ($target_time >= time)
# It's been more than 24 hours.
...
}
You probably want ->add( days => 1 ) (same time on the next calendar day) instead of ->add( hours => 24 ).
The DateTime module is enormous and slow, and is a complete toolbox for anything date-time related. Time::Piece is a much lighter weight core module (so it shouldn't need to be installed) and is quite up to this task.
Here's a demonstration of a solution using Time::Piece
use strict;
use warnings;
use 5.010;
use Time::Piece ();
use Time::Seconds 'ONE_DAY';
my ($t1, $t2) = qw/
2015-02-23T16:59:25
2015-02-24T16:59:25
/;
my $diff = delta_time($t1, $t2);
say $diff >= ONE_DAY ? 'over one day' : 'less than one day';
sub delta_time {
my ($t1, $t2) = map Time::Piece->strptime($_, '%Y-%m-%dT%H:%M:%S'), #_;
return $t2 > $t1 ? $t2 - $t1 : $t1 - $t2;
}
output
over one day