Perl date format transformations - perl

I'm taking 02/29/2012 from an html form, but will need to work with the ISO 2012-02-29 from then on.
I am certain that it'd be easier for me to do it with Perl without touching on the JS datepicker of which I have zero understanding.

$date = '02/29/2012';
$date =~ s#(\d+)/(\d+)/(\d+)#$3-$1-$2#;

To do your transformation, just use
s{([0-9]{2})/([0-9]{2})/([0-9]{4})}{$3-$2-$1}
But AFAIK, ISO is not YYYY-DD-MM, but YYYY-MM-DD.

You could use DateTime or Date::Manip. There are a plenty of subroutines that perform various manipulations with date. For example, using Date::Manip:
$string = '02/29/2012';
$date = ParseDate($string);
$out = UnixDate($date, '%Y-%m-%d');
Edit: as I see, a similar answer was provided while I was typing

You can use the Date::Manip::Date module.
This is a little costlier but does validation of the dates.

Using standard Perl:
#!/usr/bin/perl
use strict;
use warnings;
use 5.010;
my $date = '02/09/2012';
say join '-', (split m|/|, $date)[2,1,0];
Using DateTime:
#!/usr/bin/perl
use strict;
use warnings;
use 5.010;
use DateTime::Format::Strptime;
my $date = '02/09/2012';
my $parser = DateTime::Format::Strptime->new(
pattern => '%d/%m/%Y',
);
my $dt = $parser->parse_datetime($date);
say $dt->ymd;
If you want to deal with dates and times in Perl, then DateTime and its friends are the tools that you want.

It depends on how much work you're going to be doing on the dates, and whether you're going to be culturally aware of how different people enter dates, and whether you're going to accept month names or abbreviations as well as pure numerics, and ...
On CPAN, there's a major section on Data and Data Types which has sub-sections for Date and Time with many modules in each. Some are extremely elaborate: for example, the DateTime module is extremely thorough, but rather heavy-weight (and is listed in neither the Date nor the Time section). Others are just clever: Date::Calc and Date::Manip. The main problem at CPAN is the embarrassment of riches - what you need is probably there.
If the simple regexes in other answers will work, use them. Otherwise, consider one of Date::Calc or Date::Manip, unless you find something else that will work better for you.

The strptime function can also be leveraged from the core module, Time::Piece :
use Time::Piece;
my $date = q(02/29/2012);
my $t = Time::Piece->strptime( $date, "%m/%d/%Y" );
print $t->ymd, "\n";

As the input is not trusted (web form...) I suggest to basically use choroba's answer (with bounds checking added), but in a test that checks if the input matches:
# input and result are in $_
unless (s{^([0-9]{2})/([0-9]{2})/([0-9]{4})$}{$3-$2-$1}) {
die "invalid input";
}
# From here input is both validated and transformed

Related

Convert 12 hr time format to 24 hr format in perl?

How do I convert "11am" and "10pm" into "11:00:00" and "22:00:00"? Is there a simple way in perl to convert this?
Time::Piece has been a standard part of Perl since Perl 5.10 in 2007.
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
use Time::Piece;
for (qw[11am 10pm]) {
my $time = Time::Piece->strptime($_, '%H%p');
say $time->strftime('%H:%M:%S');
}
Time::Piece's documentation claims that the definition of %p is based on your locale. So, according to the documentation, Time::Piece's %p can't reliably be used to handle am and pm, so you shouldn't use it.
On the other hand, Time::Piece behaves differently than documented, and %p will reliably handle am and pm, so it could technically be used to solve your problem despite documentation to the contrary.
I'd personally avoid that giant mess (and all of Time::Piece's other problems) and use the following lighter, simpler and clearer code:
my ($h, $ampm) = /^([0-9]+)(am|pm)\z/;
$h = 0 if $h == 12;
$h += 12 if $ampm eq 'pm';
my $hms = sprintf("%d:00:00", $h); # or %02d if you want 00:00:00

Perl: Converting a date held in a string to a different format

I have a date that I have extracted from a log file in YYYY-MM-DD HH:MM:SS format.
I have it in a variable called $field0 along with another nine variables $field1 - $field9.
Using Perl I want to change the format of the string to DD-MM-YYYY HH:MM:SS and put it back in $field0
Surely there must be a really easy way to do this?
Can anyone assist please or point me somewhere that has am example?
There are two obvious approaches. The first is to use the core Time::Piece module to parse the date-time string and reformat it, and the second is to use a regex substitution to match the three date fields and reverse them
Here are both techniques used in a single program
use strict;
use warnings 'all';
use feature 'say';
use Time::Piece;
my $time0 = my $time1 = '2016-05-28 12:53:19';
$time0 = Time::Piece->strptime($time0, '%Y-%m-%d %H:%M:%S')->strftime('%d-%m-%Y %H:%M:%S');
say $time0;
$time1 =~ s/^(\d\d\d\d)-(\d\d)-(\d\d)/$3-$2-$1/;
say $time1;
output
28-05-2016 12:53:19
28-05-2016 12:53:19

Get ISO DateTime with only core modules in Perl?

I would like to get a date-time string such as 2015-06-17 10:20:34 with only core modules. The reason of this is that cpan install DateTime takes ages on my machine because of the tests and the fetch of all the dependencies.
If I give my Program to my colleagues. They will also need to install the missing modules. However, if I simply do:
my $date = `date "+%Y-%m-%d %H:%M:%S"`; chomp $date;
It takes only one line, it requires no additional modules and works on all POSIX machines.
Why should I need to install DateTime in this case?
Yet DateTime->now is the solution recommended by google. I think it shouldn't, I think I am wrong and I can't figure out why.
Perl 5.14 contains the core module Time::Piece, which has a datetime method that returns a date and time in ISO 8601.
use Time::Piece;
my $t = localtime;
say $t->datetime;
This will return 2015-06-17T10:59:15. If you don't want the T, remove it.
say localtime->datetime =~ y/T/ /r;
The Time::Piece module has been in core Perl since version 5.10, about eight years ago
It overrides the localtime core function and looks like this
use Time::Piece;
print localtime->datetime;
output
2015-06-17T12:55:36
If you really want a space instead of the standard T then, on Perl v5.14 or later you can write
print localtime->datetime =~ tr/T/ /r;
In earler versions, which don't have the /r "non-destructive" option, you would have to put the string into a variable and modify it with tr/T/ /
This is simple with the POSIX function strftime:
use POSIX qw( strftime );
print strftime "%Y-%m-%d %H:%M:%S", localtime;
POSIX has been core since 5.0.
Time::Piece gives you an object with many useful methods. As other people have pointed out, the datetime() gives you the date and time in ISO format.
use Time::Piece;
print localtime->datetime;
But if you want a different format (like the "not quite ISO" format in your question) you can use the strftime() method.
use Time::Piece;
print localtime->strftime('%Y-%m-%d %H:%M:%S');
A couple of other points:
Your DateTime installation might go quicker if you used a pre-built package (for example, yum install perl-DateTime on a RedHat derived system).
There are two reasons for avoiding shelling out to run system commands - firstly you lose cross-platform compatibility and secondly creating a new shell is a relatively slow and expensive operation.

perl to open csv file with dynamic date in it?

How can I open a CSV file with a date that changes each day, where the date format is yyyy for year, dd for day and mmm for a 3 letter month.
This is as far as I've got
#!/usr/bin/perl
use strict;
use warnings;
#Set-up Input Files
#Inputfile
$INFILE = "C:\\DBR_%yyyy\\%b\\Failures_input%d%h\\.csv";
#Open input file for reading
open (INPUT,"$INFILE") or die " cannot open $INFILE ";
It is very unclear what you are asking for, and you don't mention %d and %h in your pattern.
If you want to open the latest CSV file then you need to do a nested search of the path, finding the latest date and time.
Here is something that may help. This code generates the path to the file that would be created for the current date and time. It uses the Time::Piece module, which is part of core Perl and shouldn't need installing. By default it overloads the localtime operator so that it returns a Time::Piece object in scalar context. That allows the module's utility methods to be applied directly.
use strict;
use warnings;
use Time::Piece;
my $failures_file = localtime->strftime('C:\DBR_%Y\%b\Failures_input%d%b.csv');
my $invoices_file = localtime->strftime('C:\%Y\%b\invoices%d%b.csv');
print $failures_file, "\n";
print $invoices_file, "\n";
output
C:\DBR_2014\Mar\Failures_input17Mar.csv
C:\2014\Mar\invoices17Mar.csv
However I think it is more likely that you want the name of the latest file with a path of that form, which is a little more complex (and a dreadful system design). Please verify your requirement and we will be able to help you further.
I wanted to write an answer about the localtime Perl function, but its documentation is so complete that it even includes stuff about the three-letter abbreviations.
A few remarks on your string literal for the filename:
You can use single forward slashes, even on Windows
You don't have to escape the dot with a backslash
You can "interpolate" variables into strings: "DBR_$yyyy" if $yyyy is a variable.
I second SzG's general recommendations.
The following shows how to add the date formatting to your filename using two core modules.
Using Time::Piece (in Perl core since version 5.8):
use Time::Piece;
use strict;
use warnings;
my $infile = localtime->strftime('C:\DBR_%Y\%b\Failures_input%d.csv');
Or using POSIX and strftime if you're working on an ancient box. To find the available specifiers, just google strftime:
use POSIX qw(strftime);
use strict;
use warnings;
my $infile = strftime 'C:\DBR_%Y\%b\Failures_input%d.csv', localtime;
Outputs:
C:\DBR_2014\Mar\Failures_input16.csv

Perl - store output of localtime into hash

In perl I am able to get current seconds using this sequence of commands:
my #time = ($sec,$min,$hour,$day,$mon,$year_1900,$wday,$yday,$isdst)=localtime;
print $time[0]
Is there any equivalent of this but using hashes? So one can type something like this:
print $time{"sec"}
I have tried:
my %time= ("sec","min","hour","day","mon","year_1900","wday","yday","isdst")=localtime;
print $time{"sec"}
But it ended with following error:
Can't modify constant item in list assignment at -e line 1, near "localtime;"
Execution of -e aborted due to compilation errors.
Thanks
Instead of storing everything in a hash, you can store it in an object using Time::Piece:
#!/usr/bin/perl
use strict;
use warnings;
use Time::Piece;
my $t = localtime;
print $t->sec;
Time::Piece can do date math, comparisons, parsing, and output, making it more flexible than a simple hash.
You can use hash slice
my %time;
#time{"sec","min","hour","day","mon","year_1900","wday","yday","isdst"} = localtime;
# or shorter
# #time{qw(sec min hour day mon year_1900 wday yday isdst)} = localtime;
print $time{"sec"};
There are a few standard Perl modules that can help you.
The first is Time::localtime. Thie replaces the internal localtime command with a by name methods for accessing the various parts of the time:
use strict; # Always
use warnings; # Always
use feature qw(say); # An improved version of the print command
use Time::localtime
my $time = localtime; # A Time::Tm object, not your standard localtime command
say $time->sec; # Prints the seconds
This isn't quite the hash you've requested, but you can see that it greatly improves the access to the various pieces provided by localtime in a way that's almost like a hash.
You can also see the use of say which is like print except that you don't need that pesky \n on the end (like you forgot in your example).
Taking things to the next step...
Another nice standard module is called Time::Piece. It provides even easier ways to parse time and to display it.
use strict; # Always
use warnings; # Always
use feature qw(say); # An improved version of the print command
use Time::Piece
my $time = localtime;
say $time->sec; # Prints the seconds. Looks pretty much the same
#
# But now look!
#
say $time->ymd; # Prints the time as YYYY-MM-DD
say $time->ymd("/"); # Prints the time as YYYY/MM/DD
say $time->mdy("/"); # Prints the time as MM/DD/YYYY
say $time->month; # Prints the name of the month
say $time->cdate; # Prints date and time as a string
I prefer Time::Piece because of its flexibility and the ease of initializing a date that's not the current time. If you have a string for the date/time, and you can describe it, you can easily create a new Time::Piece date/time object that can be manipulated.
One of the many things Perl programmers miss is the cornucopia of standard Perl modules that are packed into almost every Perl distribution. These modules do not require downloading or installation. They're there and available on almost any computer that has that particular Perl release.
You can use File::Copy for that missing file copy command. You can use File::Basename for that missing basename and dirname command. You can use the hundreds of modules that come with Perl to make your life easier. Take a tour of the standard Perl documentation and play around with it.