To subtract two Time:Piece objects - perl

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);

Related

Perl convert localtime to unix (epoch) time

using perl, I am trying to estimate the time since a file was created.
I would like to convert the local time to unix time (epoch), then take unix time of the file & subtract.
The problem I face is that when I convert localtime to unixtime , it is converted incorrectly!
my $current = str2time (localtime(time));
print $current;
The results I get are
2768504400 = Sun, 23 Sep 2057 21:00:00 GMT
2421349200 = Sun, 23 Sep 2046 21:00:00 GMT
Do I have to feed str2time with a specific date format?
You're doing something bizarre here - localtime(time) takes - the epoch time (time) and converts it to a string.
And then you convert it back.
Just use time()
Or perhaps better yet -M which tells you how long ago a file was modified. (In days, so you'll have to multiply up).
e.g.:
my $filename = "sample.csv";
my $modification = -M $filename;
print $modification * 84600;
But if you really want to take the time and convert it back again - you'll need to look at how localtime(time) returns the result.
If you do:
print localtime(time);
You get:
5671624811542661
Because localtime is being evaluated in a list context, and so returning an array of values. (Which you can use without needing to parse).
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
localtime(time);
If you do it in a scalar context, it returns a string denoting the time:
print "".localtime(time);
Gives:
Thu Sep 24 16:09:33 2015
But note - that might vary somewhat depending on your current locale. That's probably why str2time is doing odd things - because it makes certain assumptions about formats that don't always apply. The big gotcha is this:
When both the month and the date are specified in the date as numbers they are always parsed assuming that the month number comes before the date. This is the usual format used in American dates.
You would probably be better off instead using Time::Piece and strftime to get a fixed format:
e.g.
use Time::Piece;
print localtime(time) -> strftime ( "%Y-%m-%d %H:%M:%S" );
Note - Time::Piece overloads localtime so you can actually use it (fairly) transparently. Of course, then you can also do:
print localtime(time) -> epoch;
And do without all the fuss of converting back and forth.
You have missed requesting localtime to produce scalar (string) instead of array.
use Date::Parse;
my $current = str2time (scalar(localtime(time)));
print $current, "\n";
print scalar(localtime($current)),"\n";
perldoc -f localtime
Converts a time as returned by the time function to a 9-element
list with the time analyzed for the local time zone. Typically
used as follows:
# 0 1 2 3 4 5 6 7 8
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
...
In scalar context, "localtime()" returns the ctime(3) value:
$now_string = localtime; # e.g., "Thu Oct 13 04:54:34 1994"

Perl: Setting $day, $month and $year

I've managed to cobble together a script that reads through thousands of log entries and creates a summary of them. All good so far. What I also want to be able to do is for it to create a separate summary of the entries from just the last 90 days.
A single entry in the log looks like the following, with newer entries always being added to the bottom of the file :
Serial No: 10123407
Date: 14/08/15
Time: 12:58
Cycle type: 134 U
Hold time: 0180
Cycle No: 1357
Dry Time: 00 mins.
Cycle Start
12:58.35
Hold Time 0000 Secs
Cycle: Failed
User_Message 13
Ref.to User Manual
Cycle End
13:01.32
The code I am using to return the current date and the date 90 days ago is:
use POSIX qw(strftime);
use Time::Local qw(timegm);
my ($d,$m,$y) = (localtime())[3,4,5];
print OUT (strftime("%d/%m/%y - ", gmtime(timegm(0,0,0,$d,$m,$y)-90*24*60*60)));
print OUT (strftime("%d/%m/%y\n", gmtime(timegm(0,0,0,$d,$m,$y))));
I'm doing it like this because it's producing my dates in the format I want, the same as in the logs dd/mm/yy and always zero padded.
Using this I get the following output:
11/05/15 - 09/08/15
So if I can print it, how can I store the data as the variables: $day90, $month90, $year90, $day, $month and $year. If I can do that then I think I can do the logical operations necessary to decide if the log entry is within the last 90 days and then create my summary as I want it.
I don't have any preconceived ideas as to how this is done so any and all solutions will be very much appreciated.
One of the best ways to compare dates is by converting them to %Y%m%d format (or %Y-%m-%d if you want something more readable) and then you can compare them as text strings
You can use the core module Time::Piece to do the formatting for you
Here's an example. It defines the input format and the comparison format as $dmy_format and $ymd_format respectively
The strings for today's date and 90 days earlier are defined and stored as state variables the first time in_range is called, and so never need to be calculated again. (You will need Perl 5 version 10 or better for the state keyword. If that's not available then just use my instead and move those definitions outside and immediately before the subroutine)
The passed parameter is the date in DD/MM/YY format. It is parsed and reformatted as YYYY-MM-DD and the subroutine returns the result of comparing it with the two boundary dates
use strict;
use warnings;
use v5.10; # for 'state' variables
use Time::Piece;
use Time::Seconds 'ONE_DAY';
for my $month ( 1 .. 12 ) {
my $date = sprintf '14/%02d/15', $month;
printf "date %s is %s\n", $date, in_range($date) ? 'in range' : 'out of range';
}
sub in_range {
state $ymd_format = '%Y-%m-%d';
state $dmy_format = '%d/%m/%y';
state $now = localtime;
state $today = $now->strftime($ymd_format);
state $days90 = ($now - 90 * ONE_DAY)->strftime($ymd_format);
my $date = Time::Piece->strptime(shift, $dmy_format)->strftime($ymd_format);
$date le $today and $date ge $days90;
}
output
date 14/01/15 is out of range
date 14/02/15 is out of range
date 14/03/15 is out of range
date 14/04/15 is out of range
date 14/05/15 is in range
date 14/06/15 is in range
date 14/07/15 is in range
date 14/08/15 is out of range
date 14/09/15 is out of range
date 14/10/15 is out of range
date 14/11/15 is out of range
date 14/12/15 is out of range
You could use this to get dates into variable i.e split with / and store it in variables:
use strict;
use warnings;
use POSIX qw(strftime);
use Time::Local qw(timegm);
my ($d,$m,$y) = (localtime())[3,4,5];
my ($day90,$month90,$year90) = split(/\//,(strftime("%d/%m/%y", gmtime(timegm(0,0,0,$d,$m,$y)-90*24*60*60))));
my ($day,$month,$year)=split(/\//,(strftime("%d/%m/%y", gmtime(timegm(0,0,0,$d,$m,$y)))));
print "DATE(BEFORE 90 days): $day90 $month90 $year90 \n";
print "DATE(CURRENT): $day $month $year \n";
Output:
DATE(BEFORE 90 days): 11 05 15
DATE(CURRENT): 09 08 15

%-d flag with specifier not working

My platform is Linux. I want the printed date to be formatted as Apr 3, 2014, 5:28 PM when given input such as 2014-04-03T17:28:54.864Z.
My current Perl script.
#!/usr/bin/perl
use lib '/tmp/DateTime/Format';
use DateTime::Format::ISO8601;
my $date = "2014-04-03T17:28:54.864Z";
my $iso8601 = DateTime::Format::ISO8601 -> new;
my $dt = $iso8601->parse_datetime( $date );
print "Date before conversion *******-> \"$date\"\n";
my $s6 = $dt->strftime("%b %-d, %Y, %I:%M %p");
print "Date after conversion *******-> \"$s6\"\n";
It gives the output Apr %-d, 2014, 05:28 PM. When the day of the month or hour is a single digit, I do not want zero or space padding. I want the output as Apr 3, 2014, 5:28 PM when day and hour are each a single digit.
I see no %-d option listed in the valid strftime patterns.
You could use %e to get a leading space instead of a leading zero but, since you discount that, you can get the day independently, then change it and use that changed value within the format string, something like (untested, but you'll get the idea):
# Get day and remove leading zero.
my $dayofmonth = $dt->strftime("%d");
$dayofmonth =~ s/^0//;
# Construct format string from "hard-coded" day.
my $s6 = $dt->strftime("%b " . $dayofmonth . ", %Y, %I:%M %p");
Try this:
my $s6 = $dt->format_cldr('MMM d, Y, h:mm a');
It makes use of the module:
DateTime::Format::CLDR - Parse and format CLDR time patterns
Which you can find documented here.
Because there aren't any strftime patterns for days other than %d and %e, the easy solution is just to edit the result after the fact
$s6 =~ s/\s+/ /g;
This would change your script to the following:
use strict;
use warnings;
use DateTime::Format::ISO8601;
my $date = "2014-04-03T17:28:54.864Z";
my $iso8601 = DateTime::Format::ISO8601->new;
my $dt = $iso8601->parse_datetime( $date );
print qq{Date before conversion *******-> "$date"\n};
my $s6 = $dt->strftime("%b %e, %Y, %I:%M %p");
$s6 =~ s/\s+/ /g;
print qq{Date after conversion *******-> "$s6"\n};

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 do I convert datetime to a different format? [perl]

Need help parsing the datetime stamp and splitting it up by date and time.
use strict;
use warnings;
use Time::Piece;
my $string = "05:57:03 08/31/10 MDT";
print $string,"\n";
my $time = Time::Piece->strptime($string, "%H:%M:%S");
my $date = Time::Piece->strptime($string, "%Y/%m/%d");
print $time,$date,"\n";
Thanks! Also how do I figure out which day of week this is using code?
use DateTime::Format::Strptime;
my $s = DateTime::Format::Strptime->new(pattern => '%T %D %Z');
my $dt = $s->parse_datetime('05:57:03 08/31/10 MDT');
say $dt->strftime('%A'); # Tuesday
You should be able to use code like the following:
my $t = Time::Piece->strptime($string, "%H:%M:%S %m/%d/%y %Z");
However, on my system at least, I have to change the time zone MST to GMT for it to match; if I leave it as in your example, I get an error:
Perl> my $t = Time::Piece->strptime("05:57:03 08/31/10 DST", "%H:%M:%S %m/%d/%y %Z");
[!] Runtime error: Error parsing time at /usr/local/lib/perl/5.10.0/Time/Piece.pm line 469.
If it works for you, though, you'll have a Time::Piece object, on which you can call e.g. $t->day_of_week for the day of the week as a number, $t->day for e.g. 'Tue', or $t->fullday for e.g. 'Tuesday'.
See the documentation for Time::Piece for details on the methods you can call.