Subtract days from today date and compare after - perl

Following situation.
I have a file named 2018_12_03_FileName.log. Now I get the date from the file (2018_12_03).
I want to convert the string to a DateTime object, which works too.
$chars =~s/_//g;
$chars = Time::Piece->strptime("$chars", "%Y%m%d");
$chars = $chars->strftime("%d/%m/%Y");
Output
03/12/2018
After that I want to get the date today - 14 days. But here is one of my two problems. I tried many things, but couldn't find any real solution working for me.
my $day14 = DateTime->now();
$day14 -= (2 * ONE_WEEK);
Error:
Cannot subtract 1209600 from a DateTime object (DateTime=HASH(0x6f2d84)). Only a DateTime::Duration or DateTime object can be subtracted from a DateTime object.
Now the second problem is, I want to compare these two dates and look if the file date is in range or not.
my $cmp = DateTime->compare($chars, $day14);
Error:
Argument "15/07/2019" isn't numeric in numeric eq (==) at
A DateTime object can only be compared to another DateTime object (03/12/2018, 15/07/2019).
So how can I subtract 14 days from the today date and how can I compare these two dates after?

You're slightly muddling up two Date/Time ecosystems that don't work well together.
You can do this using Time::Piece and Time::Seconds.
use feature 'say';
use Time::Piece;
use Time::Seconds;
my $chars = '2018_12_03';
my $tp = Time::Piece->strptime($chars, '%Y_%m_%d');
my $date14 = $tp - (2 * ONE_WEEK);
say $tp->strftime('%d/%m/%Y');
say $date14->strftime('%d/%m/%Y');
Output:
03/12/2018
19/11/2018
Or you can do it using DateTime and friends.
use feature 'say';
use DateTime;
use DateTime::Format::Strptime;
my $date_parser = DateTime::Format::Strptime->new(
pattern => '%Y_%m_%d',
on_error => 'croak',
);
my $chars = '2018_12_03';
my $dt = $date_parser->parse_datetime($chars);
my $date14 = $dt->clone->subtract( weeks => 2 );
say $dt->strftime('%d/%m/%Y');
say $date14->strftime('%d/%m/%Y');
Output:
03/12/2018
19/11/2018
As for your last question, you can compare either Time::Piece objects or DateTime objects using the standard Perl comparison operators (<, ==, >=, etc). But you have to compare two objects of the same type.

Just another option, Time::Moment makes date math really simple:
use strict;
use warnings;
use Time::Moment;
my $chars = Time::Moment->from_object(Time::Piece->strptime("$chars", "%Y%m%d"));
my $day14 = Time::Moment->now_utc->minus_days(14)->at_midnight;
print $day14->strftime('%d/%m/%Y');
These objects can be compared with normal operators like with DateTime and Time::Piece. Just keep in mind that, since you only care about the day and not time of day, all math should be done according to the same time zone, of which the easiest is of course UTC.

Related

How to subtract dates in perl and convert it in minutes and hours?

Every time I tried to find the difference of these date strings, there is an error. I wonder if you could help me this.
my $datecreated = '2021-09-06 04:52:38';
my $dateresolved = '2021-09-06 04:52:48';
my $time_elapsed= $dateresolved - $datecreated;
print $time_elapsed;
And I want to convert the result into minutes and hours.
These two timestamps are mere strings. In order to get the duration between these two moments in time ("subtract" them) one needs to build date-time objects from them, in a library that knows how to then find duration between them. One good choice is DateTime
use warnings;
use strict;
use feature 'say';
use DateTime;
use DateTime::Format::Strptime;
my ($ts1, $ts2) = (#ARGV == 2)
? #ARGV : ('2021-09-05 04:52:38', '2021-09-01 04:52:48');
my $strp = DateTime::Format::Strptime->new(
pattern => '%F %T', time_zone => 'floating', on_error => 'croak'
);
my ($dt1, $dt2) = map { $strp->parse_datetime($_) } $ts1, $ts2;
# Get difference in hours and minutes (seconds discarded per question)
my ($hrs, $min) = delta_hm($dt1, $dt2);
say "$hrs hours and $min minutes";
# Or (time-stamp hh:mm in scalar context)
my $ts_hm = delta_hm($dt1, $dt2);
say $ts_hm;
# To get wanted units (hours+minutes here) best use a delta_X
sub delta_hm {
my ($dt1, $dt2) = #_;
my ($min, $sec) = $dt1->delta_ms($dt2)->in_units('minutes', 'seconds');
my $hrs = int( $min / 60 );
$min = $min % ($hrs*60) if $hrs;
return (wantarray) # discard seconds
? ($hrs, $min)
: join ':', map { sprintf "%02d", $_ } $hrs, $min;
}
The hard-coded input time-stamps here are different than the ones in the question; those would make an hour+minute difference a zero, since they differ only in seconds! (Is that intended?) One can also submit two time-stamp strings as input to this program.
Note that a generic duration object makes it harder to convert to any particular desired units
One cannot in general convert between seconds, minutes, days, and months, so this class will never do so. Instead, create the duration with the desired units to begin with, for example by calling the appropriate subtraction/delta method on a DateTime.pm object.
So above I use delta_ms since minutes are easily converted to hours+minutes. Seconds are discarded as the question implies (if that is in fact unintended add them in the routine).
For more general uses one can do
use DateTime::Duration;
my $dur = $dt1->subtract_datetime($dt2);
# Easy to extract parts (components) of the duration
say "Hours: ", $dur->hours, " and minutes: ", $dur->minutes; # NOT conversion
Can do this with the core Time::Piece as well
use warnings;
use strict;
use feature 'say';
use Time::Piece;
my ($ts1, $ts2) = (#ARGV)
? #ARGV : ('2021-09-05 04:52:38', '2021-09-01 04:52:48');
my ($dt1, $dt2) = map { Time::Piece->strptime($_, "%Y-%m-%d %T") } $ts1, $ts2;
# In older module versions the format specifier `%F` (`%Y-%m-%d`) may fail
# so I spell it out here; the %T (for %H:%M:%S) should always be good
# For local times (not UTC) better use Time::Piece::localtime->strptime
my $delta = $dt1 - $dt2;
# say $delta->pretty;
my $hrs = int( $delta->hours );
my $min = int($delta->minutes) - ($hrs//=0)*60;
say "$hrs:$min";
This is much simpler, but watch out for occasional tricky (error-inducing) API of Time::Piece.
Note, while Time::Piece is core, succinct, and much lighter (and correct!), the DateTime is far more rounded and powerful, also with an ecosystem of extensions.
Use Time::Piece which is a standard part of the Perl library since 2007.
#!/usr/bin/perl
use strict;
use warnings;
use Time::Piece;
# Define the format of your inputs
my $format = '%Y-%m-%d %H:%M:%S';
# Convert your date strings into Time::Piece objects
my $datecreated = Time::Piece->strptime('2021-09-06 04:52:38', $format);
my $dateresolved = Time::Piece->strptime('2021-09-06 04:52:48', $format);
# Time::Piece objects can be subtracted from each other.
# This gives the elapsed time in seconds.
my $time_elapsed = $dateresolved - $datecreated;
# Do the calculations to displace the elapsed time in hours,
# minutes and seconds.
printf "%02dh:%02dm:%02ds\n",
$time_elapsed->hours,
$time_elapsed->minutes % 60,
$time_elapsed->seconds % 60;

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().

Converting ISO8601 duration (P3Y6M...) to seconds in perl

I'm trying to convert an ISO 8601 time duration format, P3Y6M4DT12H30M5S, to seconds in perl. Is there a library that does this? I've tried searching, but I've only found a js library. Looking through DateTime and Time::Moment didn't provide a solution either.
There is DateTime::Format::Duration::ISO8601, which can convert the ISO 8601 duration string into a DateTime::Duration object. You can then convert that to seconds.
use DateTime::Duration;
use DateTime::Format::Duration::ISO8601;
my $format = DateTime::Format::Duration::ISO8601->new;
my $d = $format->parse_duration('P3Y6M4DT12H30M5S');
However, the DateTime::Duration format cannot be used to convert a year to seconds, as explained in the docs here.
The last example demonstrates that there will not be any conversion
between units which don't have a fixed conversion rate. The only
conversions possible are:
years <=> months
weeks <=> days
hours <=> minutes
seconds <=> nanoseconds
For the explanation of why this is the case, please see the How
DateTime Math Works section of the DateTime.pm documentation
You can use DateTime::Format::Duration with the %s pattern to circumvent that. A complete implementation might look like this.
use DateTime::Duration;
use DateTime::Format::Duration::ISO8601;
use DateTime::Format::Duration;
my $format = DateTime::Format::Duration::ISO8601->new;
my $d = $format->parse_duration('P3Y6M4DT12H30M5S');
my $output_format = DateTime::Format::Duration->new( pattern => '%s' );
print $output_format->format_duration($d);
Or, in short if you only need it once.
use DateTime::Duration;
use DateTime::Format::Duration::ISO8601;
use DateTime::Format::Duration;
print DateTime::Format::Duration->new( pattern => '%s' )
->format_duration(
DateTime::Format::Duration::ISO8601->new->parse_duration('P3Y6M4DT12H30M5S') );
Both of these will print
109254605

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;

Convert YYDDD to YY/MM/DD

I'm looking for a way to convert a date in format YYDDD to YY/MM/DD.
i.e. 12212 becomes 12/07/30.
An example in php can be found at http://www.longpelaexpertise.com.au/toolsJulian.php and you can find a DDD calendar at http://landweb.nascom.nasa.gov/browse/calendar.html
I'd appreciate any guidance both with and without perl modules.
Thanks!
edit: I'm not looking for a way to convert php2perl or anything like that. I'm simply looking for a way to convert YYDDD to YY/MM/DD using perl. I would prefer a way without using any additional perl modules however if that is the only way to do it, then I'll welcome examples using perl modules.
The nicest way would be to use Time::Piece to parse the date and reformat it, like this
Time::Piece->strptime('12212', '%y%j')->strftime('%y/%m/%d')
unfortunately however, the module doesn't accept %j (day of year) in its strptime format (although it is fine in strftime).
The second best option is strftime from the POSIX module. The string has to be split into year and day first, and the day of year is zero-based so one must be subtracted, but then the conversion is straightforward. It is probably best packaged as a subroutine, like this
use strict;
use warnings;
use POSIX 'strftime';
sub yj2ymd {
my ($year, $yday) = $_[0] =~ /(\d\d)(\d\d\d)/;
strftime('%y/%m/%d', 0, 0, 0, 0, 0, 2000+$year, 0, $yday-1);
}
print yj2ymd('12212');
output
12/07/30
You will have to do something fancier with the year if you expect any dates from last century.
I like Time::Piece simply because it comes with Perl since, I believe revision 5.10. You'll find dozens of date/time modules and everyone has their favorites, but with Time::Piece becoming part of the official distribution, it's time to switch to that.
To use Time::Piece, you need to know about strptime (STRing Parse TIME) and strftime (STRing Format TIME) both which uses %x letter formats to represent certain aspects of your time string. The reason is that Time::Piece uses the same %x format characters to both convert your time string to a time object and sometimes to format that time object into your string.
From strftime's manpage:
%j
The day of the year as a decimal number (range 001 to 366).
And:
%y
The year as a decimal number without a century (range 00 to 99).
Now to convert your date:
use Time::Piece;
my $old_time = "12212"; #YYddd
my $time = Time::Piece->strptime( $old_time, "%y%j" );
my $new_time = $time->ymd("/"); Now in YY/MM/DD format
Whoops
Hang on — strptime does not grok %j. This will give the wrong answer.
I didn't test this because I have used Time::Piece so often that I can do it in my sleep. I never used %j before in Time::Piece. The perldoc mentions nothing about %j not working, and I don't get any sort of error. That's not nice.
New strategy. I can parse the YYddd string into year and days. Then, I can get the beginning of the year as 01/01/$year. After that, I can take the days, and add it to the year. However, to do this correctly, I need a constant from Time::Seconds:
use strict;
use warnings;
use feature qw(say);
use Time::Piece;
use Time::Seconds;
my $old_date = "12212";
$old_date =~ /(..)(.*)/;
my $year = $1;
my $days = $2;
my $time = Time::Piece->strptime("01/01/$year", "%m/%d/%y");
$time += ( ( $days - 1 ) * ONE_DAY); #01/01/$year is day 1 and not 0
say $time->strftime("%y/%m/%d");
That gives 12/07/30 as the answer.
Announcement
The owner of the Time::Piece module fixed the error in the module. Version 1.23 now works:
use warnings;
use strict;
use autodie;
use feature qw(say);
use Data::Dumper;
use Time::Piece;
my $old_time = "12212"; #YYddd
my $time = Time::Piece->strptime( $old_time, "%y%j" );
my $new_time = $time->ymd("/"); #Now in YY/MM/DD format
say "Version: $Time::Piece::VERSION";
say $new_time;
This prints out:
Version: 1.23
2012/07/30
Here's a short and sweet way to do what you want:
#!/usr/bin/perl
use strict;
use Date::Calc qw(Add_Delta_Days);
my $dt = '12212';
my $startYr = 2000 + substr($dt, 0, 2);
my $daysToAdd = substr($dt, 2) - 1;
my ($newYr, $newMo, $newDay) = Add_Delta_Days($startYr, 1, 1, $daysToAdd);
printf("%02d/%02d/%02d\n", $newYr % 100, $newMo, $newDay);