how to compare timestamp in array values iteratively - perl

I have an array
#array = ( 'Apr 11 21:14:25',
'Apr 11 21:10:10',
'Apr 11 21:09:10',
'Apr 11 21:07:10',
);
Here I want to comapare time stamps in a array
Process:
First value in a array should compare with second value to check time difference of 2 minutes or not?
second value in a array should compare with third value and again should check time difference of 2 minutes.
Same process should go on
Any Ideas on How to acheive this in perl?

We don't really just hand out answers without you making an effort first. But it's lunchtime and I wanted a simple programming problem to work on.
#!/usr/bin/perl
use strict;
use warnings;
use 5.010;
use Time::Piece;
my #times = (
'Apr 11 21:14:25',
'Apr 11 21:10:10',
'Apr 11 21:09:10',
'Apr 11 21:07:10',
);
# Need a year for this to make sense. Let's use
# the current one.
my $year = localtime->year;
# Convert the string dates to Time::Piece objects
my #timepieces = map {
Time::Piece->strptime("$year $_", '%Y %b %d %H:%M:%S')
} #times;
for (1 .. $#times) {
say "Comparing $times[$_ - 1] with $times[$_]";
# Use abs() so we don't care which is larger.
my $seconds = abs($timepieces[$_ - 1] - $timepieces[$_]);
if ($seconds == 120) {
say 'Exactly two minutes difference';
} elsif ($seconds > 120) {
say 'More than two minutes difference';
} else {
say 'Less than two minutes difference';
}
}

Related

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.

Find week number within range of dates in Perl

I have a range of dates -- say 2012-01-30 .. 2012-04-06 -- which denote a set of weeks that fall within these dates. These weeks are numbered 1 through 10. Given today's date (for instance, 2012-02-29), I want to be able to get the week number within the range (in this case, 5). How do I achieve this in Perl?
Package Time::Piece has strptime method to parse string into a time object and week method to indicate number of the week of the time object.
use Time::Piece;
my #dates = ('2012-01-30', ..., ...., '2012-04-06');
foreach my $strdate (#dates) {
my $date = Time::Piece->strptime($strdate, "%Y-%m-%d");
print "Number of week for '$strdate' is " . $date->week . "\n";
}
All you need to do is just to count number of unique weeks in your range.
The value of the end date doesn't make any difference unless you want to incorporate some value checking. The value that you need is week(start date) - week(this date) + 1. I recommend the Date::Calc module for its tidiness and efficiency. The code looks like this
use strict;
use warnings;
use Date::Calc 'Week_Number';
sub week {
Week_Number(split /-/, $_[0]);
}
print week('2012-02-29') - week('2012-01-30') + 1, "\n";
OUTPUT
5
use DateTime qw();
my (undef, $initial_weeknumber)
= DateTime->new(year => 2012, month => 1, day => 30)->week;
my (undef, $target_weeknumber)
= DateTime->new(year => 2012, month => 2, day => 29)->week;
printf 'We are in week %d of the Jashank epoch.',
1 + $target_weeknumber - $initial_weeknumber;

Calculating a delta of years from a date

I am trying to figure out a way to calculate the year of birth for records when given the age to two decimals at a given date - in Perl.
To illustrate this example consider these two records:
date, age at date
25 Nov 2005, 74.23
21 Jan 2007, 75.38
What I want to do is get the year of birth based on those records - it should be, in theory, consistent. The problem is that when I try to derive it by calculating the difference between the year in the date field minus the age, I run into rounding errors making the results look wrong while they are in fact correct.
I have tried using some "clever" combination of int() or sprintf() to round things up but to not avail. I have looked at Date::Calc but cant see something I can use.
p.s. As many dates are pre-1970, I cannot not unfortunately use UNIX epoch for this.
Have you tried DateTime? It'll handle parsing as well as subtraction.
Perl's gmtime and localtime functions have no problem handling negative input and dates before 1970.
use Time::Local;
$time = timegm(0,0,0,25,11-1,2005-1900); # 25 Nov 2005
$birthtime = $time - (365.25 * 86400) * 74.23; # ~74.23 years
print scalar gmtime($birthtime); # ==> Wed Sep 2 11:49:12 1931
The actual birthdate could be different by a few days, since one one-hundredth of a year only gives you a resolution of 3-4 days.
Use DateTime and DateTime::Duration.
When you substract a DateTime::Duration from a DateTime you get an other DateTime.
use strict;
use warnings;
use DateTime::Format::Strptime;
use DateTime::Duration;
my $fmt = DateTime::Format::Strptime->new(
pattern => '%d %b %Y',
locale => 'en_US',
);
my $start = $fmt->parse_datetime($ARGV[0]);
my $age = DateTime::Duration->new(years => $ARGV[1]);
my $birth = $start - $age;
print $fmt->format_datetime($birth), "\n";
Here is an example on how to invoke it:
$ perl birth.pl "25 Nov 2005" 74.23
25 Sep 1931
$ perl birth.pl "21 Jan 2007" 75.38
21 Sep 1931
I'd second Oesor's recommendation (second time today), and reiterate mobrule's reminder that perl handles negative dates. So DateTime is preferable.
But I would like to illustrate that this can be done with POSIX::mktime:
my ( $year1, $mon1, $day1 ) = qw<1944 7 1>;
my ( $year2, $mon2, $day2 ) = qw<2006 5 4>;
my $time1 = POSIX::mktime( (0) x 3, $day1, $mon1 - 1, 72 );
my $time2 = POSIX::mktime( (0) x 3, $day2, $mon2 - 1, 72 );
my $years = $year2 - $year1 - ( $time2 < $time1 ? 1 : 0 );
# 61 years
The caveat is that perl's internal clock handles dates back to December 14th, 1902 (actually 13th, after noon and before 6 PM), before which mktime starts returning undef. So for 99% of the people alive today, this will probably do.
Pointless trivia: scalar localtime( 0x80000000 ) : 'Fri Dec 13 15:45:52 1901' <- that's the cutoff ( 0x80000000 being 2s-complement minimum integer )

How can I calculate the number of days between two dates in Perl?

I want to calculate (using the default Perl installation only) the number of days between two dates. The format of both the dates are like so 04-MAY-09. (DD-MMM-YY)
I couldn't find any tutorials that discussed that date format. Should I be building a custom date checker for this format? Further reading of the Date::Calc on CPAN it looks unlikely that this format is supported.
There seems to be quite a bit of confusion because, depending on what you are trying to accomplish, “the number of days between two dates” can mean at least two different things:
The calendar distance between the two dates.
The absolute distance between the two dates.
As an example and to note the difference, assume that you have two DateTime objects constructed as follows:
use DateTime;
sub iso8601_date {
die unless $_[0] =~ m/^(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)Z$/;
return DateTime->new(year => $1, month => $2, day => $3,
hour => $4, minute => $5, second => $6, time_zone => 'UTC');
}
my $dt1 = iso8601_date('2014-11-04T23:35:42Z');
my $dt2 = iso8601_date('2014-11-07T01:15:18Z');
Note that $dt1 is quite late on a Tuesday, while $dt2 is very early on the following Friday.
If you want the calendar distance use:
my $days = $dt2->delta_days($dt1)->delta_days();
print "$days\n" # -> 3
Indeed, between, Tuesday and Friday there are 3 days. A calendar distance of 1 means “tomorrow” and a distance of -1 means “yesterday”. The “time” part of the DateTime objects is mostly irrelevant (except perhaps if the two dates fall on different time zones, then you would have to decide what “the calendar distance” between those two dates should mean).
If you want the absolute distance then instead use:
my $days = $dt2->subtract_datetime_absolute($dt1)->delta_seconds / (24*60*60);
print "$days\n"; # -> 2.06916666666667
Indeed, if you want to split the time between the two dates in 24-hour chunks, there are only about 2.07 days between them. Depending on your application, you might want to truncate or round this number. The “time” part of the DateTime objects is very relevant, and the expected result is well defined even for dates on different time zones.
If you care about accuracy, keep in mind that not all days have 86400 seconds. Any solution based on that assumption will not be correct for some cases.
Here's a snippet I keep around to calculate and display date/time differences a few different ways using the DateTime library. The last answer printed is the one you want, I think.
#!/usr/bin/perl -w
use strict;
use DateTime;
use DateTime::Format::Duration;
# XXX: Create your two dates here
my $d1 = DateTime->new(...);
my $d2 = DateTime->new(...);
my $dur = ($d1 > $d2 ? ($d1->subtract_datetime_absolute($d2)) :
($d2->subtract_datetime_absolute($d1)));
my $f = DateTime::Format::Duration->new(pattern =>
'%Y years, %m months, %e days, %H hours, %M minutes, %S seconds');
print $f->format_duration($dur), "\n";
$dur = $d1->delta_md($d2);
my $dy = int($dur->delta_months / 12);
my $dm = $dur->delta_months % 12;
print "$dy years $dm months ", $dur->delta_days, " days\n";
print $dur->delta_months, " months ", $dur->delta_days, " days\n";
print $d1->delta_days($d2)->delta_days, " days\n";
Time::ParseDate will handle that format just fine:
use Time::ParseDate qw(parsedate);
$d1="04-MAR-09";
$d2="06-MAR-09";
printf "%d days difference\n", (parsedate($d2) - parsedate($d1)) / (60 * 60 * 24);
Date::Calc has Decode_Date_EU (and US etc)
#!/usr/bin/perl
use Date::Calc qw(Delta_Days Decode_Date_EU);
($year1,$month1,$day1) = Decode_Date_EU('02-MAY-09');
($year2,$month2,$day2) = Decode_Date_EU('04-MAY-09');
print "Diff = " . Delta_Days($year1,$month1,$day1, $year2,$month2,$day2);
This question already has a nice answer, but I want to provide a answer showing why calculating the difference in seconds is WRONG (when we're using formatted/local dates rather than floating dates).
I find it distressing how many suggestions tell people to subtract seconds. (This question was the first Google hit for my search, so I don't care how old it is.)
I've made that mistake myself and wondered why the application would suddenly (over the weekend) show incorrent times. So I'm hoping this code will help people (who may be facing such an issue) understand why this approach is wrong and help them avoid that mistake.
Here is a complete example, one that doesn't contain "..." at some crucial point (because if you insert two dates in the same time zone, you may not see an error).
#!/usr/bin/env perl
use strict;
use warnings;
use Data::Dumper;
use DateTime;
# Friday, Oct 31
my $dt1 = DateTime->new(
time_zone => "America/Chicago",
year => 2014,
month => 10,
day => 31,
);
my $date1 = $dt1->strftime("%Y-%m-%d (%Z %z)");
# Monday, Nov 01
my $dt2 = $dt1->clone->set(month => 11, day => 3);
my $date2 = $dt2->strftime("%Y-%m-%d (%Z %z)");
# Friday, Mar 06
my $dt3 = DateTime->new(
time_zone => "America/Chicago",
year => 2015,
month => 3,
day => 6,
);
my $date3 = $dt3->strftime("%Y-%m-%d (%Z %z)");
# Monday, Mar 09
my $dt4 = $dt3->clone->set(day => 9);
my $date4 = $dt4->strftime("%Y-%m-%d (%Z %z)");
# CDT -> CST
print "dt1:\t$dt1 ($date1):\t".$dt1->epoch."\n";
print "dt2:\t$dt2 ($date2):\t".$dt2->epoch."\n";
my $diff1_duration = $dt2->subtract_datetime_absolute($dt1);
my $diff1_seconds = $diff1_duration->seconds;
my $diff1_seconds_days = $diff1_seconds / 86400;
print "diff:\t$diff1_seconds seconds = $diff1_seconds_days days (WRONG)\n";
my $diff1_seconds_days_int = int($diff1_seconds_days);
print "int:\t$diff1_seconds_days_int days (RIGHT in this case)\n";
print "days\t".$dt2->delta_days($dt1)->days." days (RIGHT)\n";
print "\n";
# CST -> CDT
print "dt3:\t$dt3 ($date3):\t".$dt3->epoch."\n";
print "dt4:\t$dt4 ($date4):\t".$dt4->epoch."\n";
my $diff3_duration = $dt4->subtract_datetime_absolute($dt3);
my $diff3_seconds = $diff3_duration->seconds;
my $diff3_seconds_days = $diff3_seconds / 86400;
print "diff:\t$diff3_seconds seconds = $diff3_seconds_days days (WRONG)\n";
my $diff3_seconds_days_int = int($diff3_seconds_days);
print "int:\t$diff3_seconds_days_int days (WRONG!!)\n";
print "days\t".$dt4->delta_days($dt3)->days." days (RIGHT)\n";
print "\n";
Output:
dt1: 2014-10-31T00:00:00 (2014-10-31 (CDT -0500)): 1414731600
dt2: 2014-11-03T00:00:00 (2014-11-03 (CST -0600)): 1414994400
diff: 262800 seconds = 3.04166666666667 days (WRONG)
int: 3 days (RIGHT in this case)
days 3 days (RIGHT)
dt3: 2015-03-06T00:00:00 (2015-03-06 (CST -0600)): 1425621600
dt4: 2015-03-09T00:00:00 (2015-03-09 (CDT -0500)): 1425877200
diff: 255600 seconds = 2.95833333333333 days (WRONG)
int: 2 days (WRONG!!)
days 3 days (RIGHT)
Notes:
Again, I'm using local dates. If you use floating dates, you won't have that problem - simply because your dates stay in the same time zone.
Both time ranges in my example go from friday to monday, so the difference in days is 3, not 3.04... and of course not 2.95...
Turning the float into an integer using int() (as suggested in an answer) is just wrong, as shown in the example.
I do realize that rounding the difference in seconds would also return correct results in my example, but I feel like it's still wrong. You'd be calculating a day difference of 2 (for a large value of 2) and, because it is a large value of 2, turn it into a 3. So as long as DateTime provides the functionality, use DateTime.
Quoting the documentation (delta_days() vs subtract_datetime()):
date vs datetime math
If you only care about the date (calendar) portion of a datetime, you
should use either delta_md() or delta_days(), not subtract_datetime().
This will give predictable, unsurprising results, free from
DST-related complications.
Bottom line: Don't diff seconds if you're using DateTime. If you're not sure what date framework to use, use DateTime, it's awesome.
You could convert the dates into the long integer format, which is the number of seconds since the epoch (some date in 1970 I think). You then have two variables that are the dates in seconds; subtract the smaller from the larger. Now you have a time span in seconds; divide it by the number of seconds in 24 hours.
Convert the two dates to seconds and then do the math:
#!/usr/bin/perl
use strict;
use warnings;
use POSIX qw/mktime/;
{
my %mon = (
JAN => 0,
FEB => 1,
MAR => 2,
APR => 3,
MAY => 4,
JUN => 5,
JUL => 6,
AUG => 7,
SEP => 8,
OCT => 9,
NOV => 10,
DEC => 11,
);
sub date_to_seconds {
my $date = shift;
my ($day, $month, $year) = split /-/, $date;
$month = $mon{$month};
if ($year < 50) { #or whatever your cutoff is
$year += 100; #make it 20??
}
#return midnight on the day in question in
#seconds since the epoch
return mktime 0, 0, 0, $day, $month, $year;
}
}
my $d1 = "04-MAY-99";
my $d2 = "04-MAY-00";
my $s1 = date_to_seconds $d1;
my $s2 = date_to_seconds $d2;
my $days = int(($s2 - $s1)/(24*60*60));
print "there are $days days between $d1 and $d2\n";