Perl convert localtime to unix (epoch) time - perl

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"

Related

Perl converting formatting date to y-m-d H:M

I am running into issues converting the date Apr 9 2017 3:45:00:000AM to 2017-04-09 03:45:00
Here's what i have tried.
use DateTime::Format::Strptime;
my $strp = DateTime::Format::Strptime->new(pattern => '%h %d %Y %H:%M',);
$start_date = $strp->parse_datetime('Apr 9 2017 3:45:00:000AM');
prints 2017-04-09T03:45:00 and not 2017-04-09 03:45:00. Trying to get 24 hour clock so when i switch AM to PM the same time prints.
Update   See end for an strptime pattern to parse the shown input string format correctly
What is shown works,† and so the DateTime::Format::Strptime constructor returns a DateTime object. But when an object is simply printed then one gets the stringification that is shown.
So you need to format it for printing as desired. A general way is with strftime
say $start_date->strftime("%F %T");
where both %F and %T are shorthands, see strftime patterns
Or combine the basic ymd and hms methods
say $start_date->ymd('-') . ' ' . $start_date->hms(':');
See the docs and adjust if/as needed. I didn't understand some details.
† It works only by accident in this exact example, since the pattern given to use for parsing in new is wrong for the shown input format, and is also inconsistent with stated requirements
The shown pattern doesn't have a format specifier for the seconds, nor for the milliseconds that follow it, nor for the following AM/PM -- all expected in the input string. So in general an input in the shown form cannot be parsed correctly with the shown pattern
The %H matches 00-23 hour, so not 12-hour clock which is stated as expected and is implicit by the presence of AM. (It still matches a 12-hour-time number but it won't once the missing AM/PM format specifier is added.)
The pattern in the OP works and parses the given input correctly because 03:45... happens to be in AM, and the module uses regex to match the given pattern anywhere in the given string (by default), so %H:%M matches 03:45 and the rest of the input string doesn't matter. If we turn on strict in the constructor this'll fail. See documentation.
Assuming that the shown input is the correct part we'd need
my $strp = DateTime::Format::Strptime->new(
pattern => '%h %d %Y %I:%M:%S%3N%p'
);
Here %I is for 12-hour clock (1-12), added %S is for seconds and %3N for milliseconds (see the page in docs for patterns, linked above), and %p for AM/PM.
The rest then works as it stands, along with printing in a desired format given above.
Following code demonstrates how desired output can be achieved without any modules
use strict;
use warnings;
use feature 'say';
my $date = 'Apr 9 2017 3:45:00:000AM';
my #fields = qw/month mday year hour min sec usec/;
my %months = ( Jan => 1, Feb => 2, Mar => 3,
Apr => 4, May => 5, Jun => 6,
Jul => 7, Aug => 8, Sep => 9,
Oct => 10, Nov => 11, Dec => 12
);
my %parts;
#parts{#fields} = split "[ :]+", $date;
$parts{hour} += 12 if $parts{usec} =~ /PM/;
$parts{month} = $months{ $parts{month} };
printf "%04d-%02d-%02d %02d:%02d:%02d\n",
#parts{qw/year month mday hour min sec/};
Perl code with assistance of module
use strict;
use warnings;
use feature 'say';
use DateTime::Format::DateParse;
my($date, $dt);
$date = 'Apr 9 2017 3:45:00:000AM';
$date =~ s/:(\d{3}[AP]M)/.$1/;
$dt = DateTime::Format::DateParse->parse_datetime( $date );
$date = $dt;
$date =~ s/T/ /;
say $date;
There are two issues with your code. First, the pattern you use to parse your date is not correct: %H is used for a 24-hour format hour. Instead, you should use a combination of %i (12-hour) and %p (AM/PM). Second, to print a DateTime object, you should first format it first (using ->strftime or ->ymd() for instance).
The milliseconds in the date are, however, a bit of an issue because strptime does not have a option to match milliseconds. I suggest to first remove them from your date, and only then parse the date with strptime:
use DateTime::Format::Strptime;
my $date = 'Apr 9 2017 3:45:00:505PM';
# Removing milliseconds from date
$date =~ s/:(\d{3})(?=AM|PM)//;
my $milliseconds = $1;
my $strp = DateTime::Format::Strptime->new(pattern => '%h %d %Y %I:%M:%S%p',);
my $start_date = $strp->parse_datetime($date);
# Taking into account the milliseconds that were removed earlier
$start_date->add(seconds => 1) if $milliseconds > 500;
say $start_date->strftime("%F %T");

How can I convert the epoch time to YMD in Perl?

I am trying to convert a date from epoch to year month day and get the correct date.
my $day = 18322;
my ($y, $m, $d) = (gmtime 86400*$day)[5,4,3];
The epoch date is 1583020800 The conversion is as follows $y is 120 $m is 2 $d is 1
I guess I have to add $y = $y+1900 I get the correct year, I can add 1 to $m to get the correct month the day $d I don't have to add anything to. Is this correct. I am taking over code for someone but I have no idea what [5,4,3] does.
Epoch time 1583020800 is Sun Mar 1 00:00:00 2020.
You can use gmtime, but it's awkward. It returns an array of values and they need to be converted. The year is the number of years since 1900 and the month starts at 0. This is because it is a thin wrapper around struct tm from the C programming language Perl is written in.
my($y,$m,$d) = (gmtime(1583020800))[5,4,3];
$y += 1900;
$m += 1;
printf "%04d-%02d-%02d\n", $y, $m, $d;
Instead, use the built in Time::Piece.
use v5.10;
use Time::Piece;
my $time = Time::Piece->gmtime(1583020800);
say $time->ymd;
Or the more powerful DateTime.
use v5.10;
use DateTime;
my $dt = DateTime->from_epoch(epoch => 1583020800);
say $dt->ymd;
The (...)[5,4,3] is a literal list slice. The thing inside the parens creates a list, but this selects only elements 5, 4, and 3.
The gmtime docs point to localtime, which shows you the position of each thing in its list:
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
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
localtime(time);
I would use Time::Piece as in Schwern's answer. But just to cover all bases, you can use the strftime() function from POSIX.pm as well.
use feature 'say';
use POSIX qw[strftime];
say strftime('%Y-%m-%d', gmtime(1583020800));
Output:
2020-03-01
You can pass different format strings to strftime().

Perl - calculate the difference in days between two dates on MS Windows and Linux

My script calculates the difference in days between two dates. However, all the time I encounter errors. The solution must work for all OS. It is advisable to do it in UNIX epoch time, but if it is impossible then there may be another solution.
I tried:
Time::ParseDate - does not work on MS Windows
Time::Local - does not work on dates from the 31st of the month
Sample code:
#!/usr/bin/perl -w
use strict;
use Time::Local;
use POSIX;
sub toepoch {
my #a = split /[- :]/, $_[0];
$a[0] =~ s/^.{2}//;
if (! defined $a[5]) {
$a[5] = 00
}
my $b = timelocal($a[5], $a[4], $a[3], $a[2], $a[1], $a[0]);
return $b;
}
my $days = sprintf("%d",(&toepoch('2018-03-31 11:00') - &toepoch('2018-04-02 11:00') / 86400));
print $days;
Output: Day '31' out of range 1..30 at epoch.pl line 12.
What module should I check in next? I remind you that the solution must work on UNIX and MS Windows systems.
From the documentation for Time::Local:
It is worth drawing particular attention to the expected ranges for
the values provided. The value for the day of the month is the actual
day (ie 1..31), while the month is the number of months since January
(0..11). This is consistent with the values returned from
"localtime()" and "gmtime()".
So by supplying timelocal the array (0, 00, 11, 31, 03, 18) you're trying to use day 31 of month 4, which doesn't work since April only ever has 30 days. If only the error message included the month it's assuming!
When doing the conversion, you need to mind to keep month values within 0..11 and adjust the year accordingly.
(Alternately you can use timelocal_nocheck() to be allowed to input month -1 and have the function do the conversion to the previous year. Although if you did use that function, you'd have had a bug that was a lot harder to track down, since it would have automatically converted 31st of April to 1st of May and you'd have no idea why your time difference is only 1 day.)
Secondly, you have a misplaced parenthesis on the calculation line, so you divide only the latter time by 86400.
My edited code:
use strict;
use warnings;
use Time::Local;
use POSIX;
sub toepoch {
my #a = split /[- :]/, $_[0];
$a[0] =~ s/^.{2}//;
if (! defined $a[5]) {
$a[5] = 00
}
--$a[1];
if ($a[1] < 0) {
--$a[0];
$a[1] += 12;
}
my $b = timelocal($a[5], $a[4], $a[3], $a[2], $a[1], $a[0]);
return $b;
}
my $days = sprintf("%d",(&toepoch('2018-03-31 11:00') - &toepoch('2018-04-02 11:00')) / 86400);
print $days;
Output:
-2
EDIT:
I assume you know what you're doing when using format %d for the value - it truncates the value down to the next whole number, meaning if you had dates
2018-03-31 11:00
2018-04-02 10:59
that is, just 1 minute short of 2 days, your program would report the time difference as "-1".
To round to nearest whole number, use the format %.0f instead.

Compare date time zone with time() in perl

I am trying to compare a file creation time which is in the format: 08-07-2016 08:16:26 GMT with the current time using time() in perl.
Since time() returns epoch time, I am not sure how to find the time difference between these two different time formats.
I tried something like below and for obvious reasons, I get an error saying: "Argument 08-07-2016 08:16:26 GMT" isn't numeric in subtraction".
my $current_time = time();
my $time_diff = $creation_time - $current_time;
if ($time_diff > 10) { #compare if the difference is greater than 10hours
# do something...
}
Some of the questions I have:
Since I want to compare only the hour difference, how can I extract just the hours from both these time formats?
I am unsure if the comparison of $time_diff > 10 is right. How to represent 10hours? 10*60?
OR is there a way to at least convert any given time format into epoch using DateTime or Time::Local?
How can I pass a a date parameter to a DateTime constructor?
my $dt1 = DateTime-> new (
year =>'1998',
month =>'4',
day =>'4',
hour =>'21',
time_zone =>'local'
);
Instead can we do something like
my $date = '08-07-2016 08:16:26 GMT';
my $dt1 = DateTime->new($date); # how can i pass a parameter to the constructor
print Dumper($dt1->epoch);
Thanks in advance for any help.
Time::Piece has been a standard part of Perl since 2007.
#!/usr/bin/perl
use strict;
use warnings;
use 5.010;
use Time::Piece;
use Time::Seconds;
my $creation_string = '08-07-2016 08:16:26 GMT';
my $creation_time = Time::Piece->strptime($creation_string, '%d-%m-%Y %H:%M:%S %Z');
my $current_time = gmtime;
my $diff = $current_time - $creation_time;
say $diff; # Difference in seconds
say $diff->pretty;

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