How can I get date difference in minutes with Perl - perl

My example was: How to compare dates using perl?
...
# $ARG1 is specified by the user for example "10" min
my $difftime=$ARG1;
# -- Get date ---------------------------------------------------------------------
#Stores current date and time - $time min
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =localtime(time() - (60*$difftime));
$year += 1900;
#parse date format to '22.10.2021 00:00:00' -----------------------------------------------------
my $act_date="$mday.$mon.$year $hour:$min:$sec";
...
My first problem is $hour and $mon are one to low why?
My second problem is the time is with one digit "8:4:34" and not with two digits "08:04:34". Is that a problem for a date comparison?
...
#compare date
# date example for $sql_value is 06.10.2021 09:38:27
my $date_to_compare=$sql_value;
if ($act_date <= $date_to_compare)
{
print "the current date is smaller";
}
else
{
print "the current date is greater";
}
...
I get only "the current date is smaller" Why?
Additionally how can i get the time different in minutes?
thanks

Dealing with dates and times at a low level like you're trying to do is really making your life far harder than it needs to be. You should follow the advice in the answers to the example that you link to and use Time::Piece.
My first problem is $hour and $mon are one to low why?
If $hour is wrong then that's likely to be down to timezone differences. But $mon is defined to be between 0 and 11 in the documentation for localtime(). This is part of what I mean by making your life too hard by working at this level.
You really haven't made it clear what you're trying to do. But you can make Time::Piece objects from a datetime string using strptime().
my $date_str = '22.10.2021 00:00:00';
my $date = Time::Piece->strptime($date_str, '%d.%m.%y %H:%M:%S');
Two Time::Piece objects can be compared directly:
if ($dt1 > $dt2) {
# do something
} else {
# do something else
}
If you subtract two Time::Piece objects, you get the number of seconds between the two timestamps.
my $secs = $dt1 - $dt2;
And you can use the strftime() method to get your timestamp in whatever format you want.
say $dt1->strftime('%d.%m.%y %H:%M:%S');

Related

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.

How to find the difference of time in hours between local time and user-defined time?

#!/usr/bin/perl
sub parkingcharge {
sub exittime
{
($sec, $min, $hour) = localtime();
print "exit time:$hour:$min:$sec\n";
}
my $exit = exittime();
my $entry = "9:10:8";
print "\nvehicle entry time is :$entry\n";
print "\nvehicle exit time is :$exit\n";
my $parkingCharge = ($entry - $exit);
print "\ntotal parking charge is : $parkingCharge\n";
}
parkingcharge();
The output appears like this
exit time:5:46:57
vehicle entry time is :9:10:8
vehicle exit time is :1
total parking charge is : 8
I want to find the parking charge in a Perl vehicle management program. The rate is $2 per hour, so I want to find the difference between entry time and exit time in hours and multiply by 2. The code I have written produces the wrong result.
How to take the difference between times in hours?
You can use Time::Piece, which is included with Perl. It gives you a convenient way to parse dates into Time::Piece objects, which are essentially epoch timestamps with syntactic sugar. The nice thing about them is that you can use them in math and you'll get seconds.
Because you only have times, we need to have the same date for both the entry and the exit time. One way to do that would be to check today's date and use it in both variables. But it's easier to just leave it out. Time::Piece will assume it's 1970-01-01, which is fine, because we don't care. It's only important that both timestamps have the same date as long as you cannot park over night.
use strict;
use warnings;
use Time::Piece;
my $entry = Time::Piece->strptime( '9:10:8', '%H:%M:%S' );
We use the strptime method to parse the entry time. The second argument is a pattern of placeholders. %H is hours in 24 hour notation, %M is minutes and %S is seconds. This also works without the leading zeroes.
We now have an entry date of 1970-01-01 09:10:08, or Thu Jan 1 09:10:08 1970 if you just print $entry.
Next we need to get the exit time.
my ( $sec, $min, $hour ) = localtime;
my $exit = Time::Piece->strptime( "$hour:$min:$sec", '%H:%M:%S' );
Because just using localtime in scalar context would give us today's date, we have to do an extra step. Your code already got the seconds, minutes and hours of this moment. We just use that as a string in the right format and feed it into strptime the same way we did for $entry. Now we have the exit timestamp, which is Thu Jan 1 14:46:56 1970 while I write this.
Getting the duration is a simple matter of subtraction. Converting it to hours is just a division by 60 for minutes and by 60 for hours.
my $duration = $exit - $entry;
my $duration_in_hours = $duration / 60 / 60;
The $duration_in_hours is 5.61333333333333 for me right now. If you want people to pay for every started hour, you'd have to round up.
my $fee_started_hours = int( $duration_in_hours + 1 ) * $hourly_fee;
I prefer to only pay for full hours of parking, so I'd like rounding down more.
my $fee_full_hours = int( $duration_in_hours ) * $hourly_fee;

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

Perl find closest date in array

How do I get the closest date that is earlier than the input date or equal to from an array using an input date?
For example, my array would look like this.
#dates = ("200811","200905","200912","201005","201202");
and my input date is
$inputdate = "201003";
How do I get the closest date in the array which is "200912".
The format of the date is YEARMM.
Thanks
Sort the dates, select only the ones preceding the input date, take the last such one:
print ((grep $_ <= $inputdate, sort #dates)[-1]);
use List::Util qw( max );
my $date = max grep { $_ <= $inputdate } #dates;
The logic here is to go one year back and change month from January to December if the month is January, otherwise go back one month in the same year.
I don't code much in Perl, the code in PHP is:
(I'm putting it here to give you the logic. Coding it should be trivial)
$dates = array("200811","200905","200912","201005","201202");
$inputdate = "201003";
$date = $inputdate;
while ($found==0) {
if (in_array($date, $dates)) {
$found = 1;
echo "the date is " . $date;
}
if ($date%100==1) { // if it's january, we need to change to december of the previous year
$date = $date - 100 + 12;
}
else {
$date = $date - 1; //go one month back in the same year
}
}