Convert string "20-May-07" to date and manipulate - perl

In my perl program, I am calculating warranty start date from warranty end date The difference is 3 years. Following is the chunk of code that I have written so far:
use Time::ParseDate;
use DateTime;
use POSIX;
use DateTime::Duration;
my $warranty_expiration_string = "20-May-07";
my $epoch = parsedate($warranty_expiration_string);
my $warranty_expiration = strftime "%D", localtime($epoch); # returns: 05/20/07
How do I deduct 3 years from $warranty_expiration to get $warranty_start date?
I tried,
$warranty_start->subtract(DateTime::Duration->new('years' => 3));
..but it did not work.

I don't understand all the different date/time modules being mixed. You only need some of them, not all of them. If you want to do date math using DateTime anyway, you want something like this:
use DateTime;
use DateTime::Format::Strptime;
my $dateparser = DateTime::Format::Strptime->new( pattern => '%d-%b-%y' );
my $warranty_expiration = $dateparser->parse_datetime($warranty_expiration_string);
my $warranty_start = $warranty_expiration->clone->subtract( years => 3);
Most of the DateTime::Format::* modules are meant to be used with DateTime and I prefer to use those if I can.
You may also want to read more about the ongoing DateTime project and the list of recommended modules at:
http://datetime.perl.org

$warranty_expiration isn't a DateTime, it's a string. You want to do something like:
my $warranty_expiration = DateTime->from_epoch(
epoch => $epoch,
time_zone => 'local',
);
my $warranty_start = $warranty_expiration->clone->subtract(years => 3);
and then you can use $warranty_expiration->strftime("%D") and $warranty_start->strftime("%D") as formatted strings. Also, if you use one of the DateTime::Format modules instead of Time::ParseDate, you will get back a DateTime directly from the parser instead of having to use from_epoch.

No need to instantiate DateTime::Duration for this as the calculation methods expect those parameters directly:
use DateTime;
my $dt = DateTime->now->subtract(years => 3);
print "$dt\n";
Result:
2009-08-30T14:36:27

Related

Take Longitude/Latitude and get UTC Offset in Perl

I'm trying to localize times that are in UTC when the only thing I know about the destination time is the longitude and latitude. I've come up with something that works, but feels kludgy:
# We need to get localized time for display purposes.
state $moduleGeoLocation = require Geo::Location::TimeZone;
my $gltzobj = Geo::Location::TimeZone->new();
my $tzName = $gltzobj->lookup( lon => $params->{'longitude'}, lat => $params->{'latitude'} );
say "TimeZone: " . $tzName;
So far so good. Here's where the kludge comes in. I'm parsing a time using Time::Piece's strptime but I don't have a GMT offset for the timezone, so after I parse the UTC time in Time::Piece, I'm sending it over to DateTime to do the time zone calculation. It seems rather clunky to be using both DateTime and Time::Piece:
# Get TimeZoneOffset.
state $moduleDateTime = require DateTime;
state $moduleDateTimeTimeZone = require DateTime::TimeZone;
my $timeZone = DateTime::TimeZone->new( 'name' => $tzName );
my $timePiece = Time::Piece->strptime($hour->{'time'}, '%Y-%m-%dT%H:%M:%SZ');
my $time = DateTime->from_epoch( 'epoch' => $timePiece->epoch, 'time_zone' => $timeZone );
Is there a better way to accomplish what I'm doing? I'm aiming for the fastest possible way to get to the result of a localized time.
Your question boils down to the following:
How do I create a DateTime object from a timestamp using the %Y-%m-%dT%H:%M:%S format. I have the appropriate time zone as a DateTime::TimeZone object.
To parse a date-time into a DateTime, one should first look for an appropriate DateTime::Format:: module.
DateTime::Format::Strptime would be the most similar to your current attempt.
use DateTime::Format::Strptime qw( );
my $format = DateTime::Format::Strptime->new(
pattern => '%Y-%m-%dT%H:%M:%S',
strict => 1,
time_zone => $tz,
on_error => 'croak',
);
my $dt = $format->parse_datetime($ts);
You could also use DateTime::Format::ISO8601, although you couldn't use it as a validator since it doesn't accept only the stated format.
use DateTime::Format::ISO8601 qw( );
( my $dt = DateTime::Format::ISO8601->parse_datetime($ts) )
->set_time_zone('floating')
->set_time_zone($tz);
Given that the latter solution overrides any explicitly-provided time zone, I'd use the first solution for clarity.

Subtract days from today date and compare after

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.

datetime strptime returning wrong pattern

I pull a date from a file and I'm creating a datetime object by using this command and then adding 1 to it to get the next date.
my $parseit = DateTime::Format::Strptime->new(pattern => '%Y%m%d');
my $lastdate = "20190115";
my $tempdate = $parseit->parse_datetime($lastdate);
my $date_up1 = $tempdate->add(days => 1);
but in printing out the variable $date_up1 I always get it in the form %Y-%m-%d. How can I just get it returned in the pattern that I selected.
strptime and thus DateTime::Format::Strptime by default only dictates how you parse the input into a DateTime object. DateTime objects default to a specific stringification, which you are seeing. In order to stringify it in a certain way, you can use its strftime method.
print $date_up1->strftime('%Y%m%d');
While DateTime::Format::Strptime can be used to both parse and format date times, it doesn't set itself as the default formatter for the DateTime objects it creates as you expect. You can do that explicitly by adding the following:
$tempdate->set_formatter($parseit);
After cleaning up your code, it looks like this:
my $date = "20190115";
my $format = DateTime::Format::Strptime->new(
pattern => '%Y%m%d',
on_error => 'croak',
);
my $dt = $format->parse_datetime($date);
$dt->set_formatter($format);
$dt->add( days => 1 );
say $dt;
Alternatively, all of the following work without setting the formatter:
$format->format_datetime($dt)
$dt->strftime("%Y%m%d") (Most flexible, but introduces duplicate code in this case)
$dt->ymd("") (Simplest in this case)

How can I display the current datetime in the given format including microseconds in Perl?

I want Perl to display current date and time in the following format:
2010-02-11 13:12:34.87876
I wrote this code:
use Time::localtime;
my $tm = localtime;
printf("It is now %04d-%02d-%02d %02d:%02d:%02d\n", $tm->year+1900,
($tm->mon)+1, $tm->mday, $tm->hour, $tm->min, $tm->sec);
and got:
2011-11-17 17:22:59
I do not know how to get the remaining .87876 in the output. Could someone please help me?
use DateTime::HiRes qw();
DateTime::HiRes->now(time_zone => 'local')->strftime('%F %T.%5N')
# returns '2011-11-17 17:42:42.21719'
DateTime::HiRes
strftime in DateTime
Using DateTime and Time::HiRes:
use DateTime;
use Time::HiRes;
my $dt = DateTime->from_epoch(
epoch => Time::HiRes::time,
time_zone => 'local',
);
print $dt->strftime("%Y-%m-%d %H:%M:%S.%5N"); # 2011-11-17 21:03:46.74562
This is exactly how DateTime::HiRes creates DateTime objects with sub-second resolution.
You might be looking for %5N. See DateTime::Format

How to make DateTime::Duration output only in days?

This code finds the difference between today and a fixed date.
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
use DateTime ();
use DateTime::Duration ();
use DateTime::Format::Strptime ();
my $date = "23/05-2022";
my $parser = DateTime::Format::Strptime->new(
pattern => '%d/%m-%Y',
time_zone => 'local',
);
$date = $parser->parse_datetime($date);
my $today = DateTime->today(time_zone=>'local');
my $d = DateTime::Duration->new($today - $date);
print Dumper $d->delta_days;
The problem is that is only outputs -22 days.
If I do print Dumper $d; I can see the -130 months as well.
$VAR1 = bless( {
'seconds' => 0,
'minutes' => 0,
'end_of_month' => 'preserve',
'nanoseconds' => 0,
'days' => -22,
'months' => -130
}, 'DateTime::Duration' );
How do I get it to output the result in days?
Doing
print Dumper $d->delta_days + $d->delta_months*30;
doesn't seam like an elegant solution.
At first you need to do the correct subtraction. There exists delta_md, delta_days, delta_ms and subtract_datetime_absolute. Depending on which unit you later want, you need to pick the right subtraction. The problem is that not every unit is convertible later without time_zone information. Thats the reason why you need to pick the correct delta method.
For example a day can have 23 Hours or 24 or 25 Hours, depending on the time zone. Because of that, you need to specify how the subtraction should work. Because you want the days later, the subtraction need to focus on days, rather focus on hours. Don't use the overload feature, because it only does a best fit.
That means you need to do a delta_days subtraction.
my $dur = $date->delta_days($today);
Now $dur is a DateTime::Duration object. You need to knew that it always tries to best fit the days, weeks, years, months if possible. That means your days will split in weeks and days. Because this conversion is always a constant.
If you don't want this "best fit" you need to call the in_units method and convert it only to days.
my $days = $dur->in_units('days');
But like i said before in_units only can do a conversion where it is possible. A call with in_units('hours') will not work on this object and just return a zero because you cant convert days to hours. If you want hours for example, you need to do a delta_ms, and on this object you can call in_units('hours')
The complete example:
#!/usr/bin/env perl
use 5.010;
use strict;
use warnings;
use DateTime;
use DateTime::Format::Strptime;
my $date = "23/05-2022";
my $parser = DateTime::Format::Strptime->new(
pattern => '%d/%m-%Y',
time_zone => 'local',
);
$date = $parser->parse_datetime($date);
my $today = DateTime->new(
day => 1,
month => 7,
year => 2011,
time_zone => 'local'
);
my $dur = $date->delta_days($today);
say "Weeks: ", $dur->weeks;
say "Days: ", $dur->days;
say "Absolute Days: ", $dur->in_units('days');
say "Absolute Hours: ", $date->delta_ms($today)->in_units('hours');
The output of this program is:
Weeks: 568
Days: 3
Absolute Days: 3979
Absolute Hours: 95496
And just for info:
1) You don't need to load DateTime::Duration its get loaded with DateTime.
2) You dont need (). These modules are OOP and don't export/import anything.
From a quick read of the DateTime module doc, I don't believe that
DateTime::Duration->new($today - $date)
will do what you expect. I believe you need to use
$dur = $today->subtract_datetime($date)
The type of $dur is not immediately clear from the docs, however.