Why does my Time::Piece code give strange results? - perl

I am trying to do a basic comparison of two dates in Perl. The Current DateTime and the Past time are correct but the subtraction gives incorrect results. The Difference should be ~24 hours yet it returns ~13 hours. Any idea why and how to fix it? thanks.
use Time::Piece;
my $now = Time::Piece->new;
my $then = Time::Piece->strptime("2014-04-14 16:30:20", "%Y-%m-%d %H:%M:%S");
my $diff = $now - $then;
print "Current time: $now\n";
print "Past time: $then\n";
print "Diff in Seconds:", $diff, "\n";
print "Pretty Diff:", $diff->pretty, "\n";
Results
------
Current time: Tue Apr 15 16:13:39 2014
Past time: Mon Apr 14 16:30:20 2014
Diff in Seconds:49399
Pretty Diff:13 hours, 43 minutes, 19 seconds

The two timepoints are in different timezones. So the difference is in fact correct. You can see that with
print $now->tzoffset, "\n"; # 7200 (I am in UTC +2 hence have 7200s offset)
print $then->tzoffset, "\n"; # 0
So basically $then is a UTC time while $now is in whatever timezone your environment thinks it is in.
To fix that, you need to decide on what timezone you want.

As DeVadder has already stated, it's because Time::Piece defaults to UTC for parsed times.
Assuming you want everything done using your localtime, you can actually encourage the parsed times to inherit their Timezone from local like so:
use Time::Piece;
use strict;
use warnings;
my $now = Time::Piece->new;
my $then = localtime->strptime("2014-04-14 16:30:20", "%Y-%m-%d %H:%M:%S");
my $diff = $now - $then;
print "Current time: $now\n";
print "Past time: $then\n";
print "Diff in Seconds:", $diff, "\n";
print "Pretty Diff:", $diff->pretty, "\n";
Outputs:
Current time: Tue Apr 15 17:12:08 2014
Past time: Mon Apr 14 16:30:20 2014
Diff in Seconds:88908
Pretty Diff:1 days, 0 hours, 41 minutes, 48 seconds

Related

To subtract two Time:Piece objects

I want to get the difference between time in minutes. Both the timings to be compared are in same timezone so no worries about the time zone difference and all.
Say if Start_time = 14 Apr 2016 05:02:26 (which is collected form log line) and for end time would be the current time of machine,
I want to calculate end time - start time. For that I need the Start time to be in format of current time.
I tried subtracting current time which is already a Time::Piece Object and converting $start_time into Time:Piece object.
But I am getting "Error parsing time at C:/Perl/lib/Time/Piece.pm line 469, line 1071883." error. Please suggest.
Also this "1071883" num in error is changing everytime I run the script. Not sure if its a kind of garbage value or what.
Editing the code with below suggested answer
I am getting below output. Seems the problem is coming as $now contains the DAY value like saturday but our $start_time doesnt. However i cant make any change in start time like adding the day value to it as we are collecting it from a log file. If the problem is due to the reason I stated, Kindly suggest how to ignore that day value from $now.
Output :
last line of log: 16 Apr 2016 03:41:49 -- DEBUG -- 16 Apr 2016 03:41:49
Time is Sat Apr 16 03:43:02 2016
difference is 21673
Below is what I tried:
#get last line of log
open my $fh ,"<","$slogfile";
my $last_line;
$last_line = $_,while (<$fh>);
print OUTLOG "last line of log: $last_line \n";
if ($last_line=~ m/^(\d\d) (\w{3}) (\d{4}) (\d\d):(\d\d):(\d\d) --/) {
$start_time = "$1 $2 $3 $4:$5:$6";
print OUTLOG "$start_time\n";
} else {
print OUTLOG "pattern matching didnt work\n";
}
#get current time
my $t = localtime;
#my $current_time = $t ;
print OUTLOG "Time is $current_time \n";
my $format = '%d %b %Y %H:%M:%S';
my $diff = $t - Time::Piece->strptime($var, $format);
print OUTLOG "difference is $diff \n";
Your format doesn't match the format of your date. The format you're using is "'%a %b %d %H:%M:%S %Y", which would match "Thu Apr 14 05:02:26 2016". You can get an explanation of the various pieces of your format from "man strftime" or "man strptime".
I've corrected the format definition in this example.
#!/usr/bin/perl
use strict;
use warnings;
use 5.010;
use Time::Piece;
my $format = '%d %b %Y %H:%M:%S';
my $start_time = '14 Apr 2016 05:02:26';
my $now = localtime;
my $diff = $now - Time::Piece->strptime($start_time, $format);
say "$diff seconds"; # $diff stringifies to seconds
say $diff->minutes, ' minutes';
The output is:
186402 seconds
3106.7 minutes
Update:
Seems the problem is coming as $now contains the DAY value like
saturday but our $start_time doesnt. However i cant make any change in
start time like adding the day value to it as we are collecting it
from a log file. If the problem is due to the reason I stated, Kindly
suggest how to ignore that day value from $now.
No. That's not the problem at all. If you print out the the Time::Piece object that you parse from $start_time then you'll see that also has the day name included. That's just how Time::Piece objects stringify.
The actual problem is more subtle. It seems that when Time::Piece parses a date string, it assumes that it is in UTC unless the string contains an explicit time zone. From what you're saying I'm assuming that your on the east coast of the US, which would explain the ~6 hour differences that you're getting.
I'm investigating this further and will almost certainly submit a patch to Time::Piece to fix this (it might just be a documentation patch to make the behaviour clearer). But in the meantime, you need a fix. And that's pretty simple. You just need to add the time zone to your code. The relevant sections of your code will now look like this:
my $format = '%d %b %Y %H:%M:%S%z'; # %z added here
# Append timezone here (-0600 as you're six hours behind UTC -
# adjust that if my assumption is wrong)
my $diff = $t - Time::Piece->strptime($var . '-0600', $format);
Or (as, quite rightly, pointed out in the comments by Borodin) you could just switch to using UTC throughout).
my $t = gmtime;
my $format = '%d %b %Y %H:%M:%S';
my $diff = $t - Time::Piece->strptime($var, $format);

perl to open file with yesterday's date in localtime()

i need to open files with todays date and yesterdays date i can open todays file ok but am don't know how to open yesterdays, i am using localtime because my perl verson is 5.8.8.
so the other time/date modules are not available
this is what i have so far
#!/usr/local/bin/perl
#months = qw( Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec );
#days = qw(Sun Mon Tue Wed Thu Fri Sat Sun);
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
$year = $year+1900;
print "c:\\DBR_$year\\$months[$mon]\\Failures_output\\$mday$months[$mon]report.csv"
|| die "can't open output file for reading: $!";
this prints
c:\DBR_2014\May\Failures_output\5Mayreport.csv
now how do i open same files with yesterdays date
A localtime/mktime roundtrip will give you the epoch number for midnight at the start of yesterday (i.e. the time where mday is 1 less than now)
use POSIX 'mktime';
my #now = localtime();
my $yesterday = mktime 0, 0, 0, $now[3]-1, $now[4], $now[5];
You can then put this into strftime to give you the time string; you don't need to have an array of months like that
use POSIX 'strftime';
my $path = strftime("c:\\DBR_%Y\\%b\\Failures_output\\%d%breport.csv", localtime($yesterday));
(You don't have to worry about the case where mday is already 1 because mktime will handle that properly).
localtime can take an epoch seconds argument, so do get yesterday's date, just call
#yesterday = localtime( time - 86400 ); # 86400 = 24 * 60 * 60

getting minutes difference between two Time::Piece objects

I have the code:
use Time::Piece;
use Time::Seconds;
my $timespan = $latest_time - $timestamp;
print $latest_time . "\n";
print $timestamp . "\n";
print $timespan->minutes;
where $latest_time = Time::Piece->new; and $timestamp = Time::Piece->strptime();
and I get the results:
Thu Mar 27 09:40:19 2014
Thu Mar 27 09:40:00 2014
-479.683333333333
What went wrong? there should be 0 minutes for $timespan, correct? Where is -479 coming from?
Reproducing the "bug"
This issue arises because strptime defaults to UTC instead of to the local timezone. This can be demonstrated in the following code which takes a current time, prints it out, then reparses it and shows the difference:
use strict;
use warnings;
use Time::Piece;
my $now = Time::Piece->new();
print $now->strftime(), "\n";
my $fmt = "%Y-%m-%d %H:%M:%S";
my $nowstr = $now->strftime($fmt);
my $parsed = Time::Piece->strptime("$nowstr", $fmt);
print "($nowstr)\n";
print $parsed->strftime(), "\n";
my $diff = $now - $parsed;
print $diff->hours, " hours difference\n";
Outputs:
Wed, 26 Mar 2014 21:42:08 Pacific Daylight Time
(2014-03-26 21:42:08)
Wed, 26 Mar 2014 21:42:08 UTC
7 hours difference
One hackish solution - getting parsed times to read as local
Now, in hacking around, I've discovered one potential hack for this on my strawberry perl system. It's by calling strptime like this: $now->strptime.
my $nowstr = "2014-03-26 21:51:00"; #$now->strftime($fmt);
my $parsed = $now->strptime("$nowstr", $fmt); #Time::Piece->strptime("$nowstr", $fmt);
print "($nowstr)\n";
print $parsed->strftime(), "\n";
my $diff = $now - $parsed;
print $diff->hours, " hours difference\n";
To confirm that strptime was actually using the time I set it, I gave it one that was 6 minutes before the current time. The output is as follows:
Wed, 26 Mar 2014 21:57:00 Pacific Daylight Time
(2014-03-26 21:51:00)
Wed, 26 Mar 2014 21:51:00 Pacific Standard Time
0.1 hours difference
The parsed time will inherit the c_islocal value from $now. $now just needs to be initialized with either localtime or ->new() and not gmtime of course.
As you can see one claims DST while the other does not, but date math is still done correctly. I was able to figure out this hack by looking at the source for strptime, _mktime, and new.
Hopefully, at the very least my code to reproduce the error will be helpful to someone with more experience with Time::Piece, and I'd love a better solution.
When I use strftime("%H:%M:%S %Z") for both $latest_time and $timestamp they both show the same timezone, but the $latest_time - $timestamp operation shows that there is difference between the timezones as tobyink pointed out. This might be a bug in Time::Piece module.
So it seems that Time::Piece->new; gets the current machine time including the timezone.
So either I fix the timezone in Time::Piece->new or include a timezone value when I use Time::Piece->strptime(); to fix the timezone problem. Thanks for the info, tobyink.

How to calculate date difference in perl

I am a novice in perl scripting. I have a requirement where in I need to find the difference of two dates in the minutes/seconds
$date1 = Fri Aug 30 10:53:38 2013
$date2 = Fri Aug 30 02:12:25 2013
can you tell me how do we achieve this, Parsing , calculation , modules req and all
Thanks
Goutham
Time::Piece has been a standard part of Perl since 2007.
#!/usr/bin/perl
use strict;
use warnings;
use 5.010;
use Time::Piece;
my $date1 = 'Fri Aug 30 10:53:38 2013';
my $date2 = 'Fri Aug 30 02:12:25 2013';
my $format = '%a %b %d %H:%M:%S %Y';
my $diff = Time::Piece->strptime($date1, $format)
- Time::Piece->strptime($date2, $format);
say $diff;
Convert both dates to UNIX time
See http://metacpan.org/pod/Date::Parse
Then you can do a simple mathematical subtraction to find the number of seconds between the two.
Then it is simple maths all the way to get minutes, hours, etc.

How do you read the system time and date in Perl?

I need to read the system clock (time and date) and display it in a human-readable format in Perl.
Currently, I'm using the following method (which I found here):
#!/usr/local/bin/perl
#months = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
#weekDays = qw(Sun Mon Tue Wed Thu Fri Sat Sun);
($second, $minute, $hour, $dayOfMonth, $month, $yearOffset, $dayOfWeek, $dayOfYear, $daylightSavings) = localtime();
$year = 1900 + $yearOffset;
$theTime = "$hour:$minute:$second, $weekDays[$dayOfWeek] $months[$month] $dayOfMonth, $year";
print $theTime;
When you run the program, you should see a much more readable date and time like this:
9:14:42, Wed Dec 28, 2005
This seems like it's more for illustration than for actual production code. Is there a more canonical way?
Use localtime function:
In scalar context, localtime() returns
the ctime(3) value:
$now_string = localtime; # e.g., "Thu Oct 13 04:54:34 1994"
You can use localtime to get the time and the POSIX module's strftime to format it.
While it'd be nice to use Date::Format's and its strftime because it uses less overhead, the POSIX module is distributed with Perl, and is thus pretty much guaranteed to be on a given system.
use POSIX;
print POSIX::strftime( "%A, %B %d, %Y", localtime());
# Should print something like Wednesday, January 28, 2009
# ...if you're using an English locale, that is.
# Note that this and Date::Format's strftime are pretty much identical
As everyone else said "localtime" is how you tame date, in an easy and straight forward way.
But just to give you one more option. The DateTime module. This module has become a bit of a favorite of mine.
use DateTime;
my $dt = DateTime->now;
my $dow = $dt->day_name;
my $dom = $dt->mday;
my $month = $dt->month_abbr;
my $chr_era = $dt->year_with_christian_era;
print "Today is $dow, $month $dom $chr_era\n";
This would print "Today is Wednesday, Jan 28 2009AD". Just to show off a few of the many things it can do.
use DateTime;
print DateTime->now->ymd;
It prints out "2009-01-28"
Like someone else mentioned, you can use localtime, but I would parse it with Date::Format. It'll give you the timestamp formatted in pretty much any way you need it.
The simplest one-liner print statement to print localtime in clear, readable format is:
print scalar localtime (); #Output: Fri Nov 22 14:25:58 2019