How to parse a string into a DateTime object in Perl? - perl

I know about the DateTime Perl module, and many of the DateTime::Format:: modules to parse specific kinds of date/time formats. However given some examples of date/time strings, how can I figure out (at coding/design time, not at runtime) which specific module should I use?
For example, I want to parse strings like: October 28, 2011 9:00 PM PDT
Is there a list somewhere of the most common date/time formats where I could look this up and find which would be the most suitable module?
I also know about some modules which try to "guess" the format for each given string at runtime and do their best. But, for sensitive applications, I would like to determine (as strictly as possible) the format first when designing an application, and then use a module which will warn me if a string does not match the specified format.
How should I go about this?

DateTime::Format::Strptime takes date/time strings and parses them into DateTime objects.
#!/usr/bin/perl
use strict;
use warnings;
use DateTime::Format::Strptime;
my $parser = DateTime::Format::Strptime->new(
pattern => '%B %d, %Y %I:%M %p %Z',
on_error => 'croak',
);
my $dt = $parser->parse_datetime('October 28, 2011 9:00 PM PDT');
print "$dt\n";
The character sequences used in the pattern are POSIX standard. See 'man strftime' for details.

I tend to use Time::Piece simply because it's part of the standard Perl module set since version 5.10.
You can't beat it's ability to parse date strings.
my $time = Time::Piece->strptime(
"October 28, 2011 9:00 PM PDT",
"%B %d, %Y %r %Z");

Have a look at the Date::Parse module, i.e. the str2time() function. It has support for most of the commonly used formats.
Example:
use Date::Parse;
use DateTime;
my $str = "Tue, 20 Sep 2011 08:51:08 -0500";
my $epoch = str2time($str);
my $datetime = DateTime->from_epoch(epoch => $epoch);

If you are looking for common date/time formats
have a look at DateTime::Locale
use DateTime::Locale
my $locale = DateTime::Locale->load('DE_de');
The following methods return strings appropriate for the DateTime->format_cldr() method:
$locale->date_format_full()
$locale->date_format_long()
$locale->date_format_medium()
$locale->date_format_short()
$locale->date_format_default()
$locale->time_format_full()
$locale->time_format_long()
$locale->time_format_medium()
$locale->time_format_short()
$locale->time_format_default()
$locale->datetime_format_full()
$locale->datetime_format_long()
$locale->datetime_format_medium()
$locale->datetime_format_short()
$locale->datetime_format_default()
You can parse Dates with the DateTime::Format::CLDR module
use DateTime::Format::CLDR;
# 1. Basic example
my $cldr = DateTime::Format::CLDR->new(
pattern => 'dd.MM.yyyy HH:mm:ss',
locale => 'de_DE',
time_zone => 'Europe/Berlin',
);
my $dt = $cldr->parse_datetime('26.06.2013 11:05:28');

DateTime::Format::Natural is also a good candidate.
Test:
perl -MDateTime::Format::Natural \
-E "my $d=DateTime::Format::Natural->new->parse_datetime('October 11, 2021'); say $d->month;"
10

Related

Perl converting formatting date to y-m-d H:M

I am running into issues converting the date Apr 9 2017 3:45:00:000AM to 2017-04-09 03:45:00
Here's what i have tried.
use DateTime::Format::Strptime;
my $strp = DateTime::Format::Strptime->new(pattern => '%h %d %Y %H:%M',);
$start_date = $strp->parse_datetime('Apr 9 2017 3:45:00:000AM');
prints 2017-04-09T03:45:00 and not 2017-04-09 03:45:00. Trying to get 24 hour clock so when i switch AM to PM the same time prints.
Update   See end for an strptime pattern to parse the shown input string format correctly
What is shown works,† and so the DateTime::Format::Strptime constructor returns a DateTime object. But when an object is simply printed then one gets the stringification that is shown.
So you need to format it for printing as desired. A general way is with strftime
say $start_date->strftime("%F %T");
where both %F and %T are shorthands, see strftime patterns
Or combine the basic ymd and hms methods
say $start_date->ymd('-') . ' ' . $start_date->hms(':');
See the docs and adjust if/as needed. I didn't understand some details.
† It works only by accident in this exact example, since the pattern given to use for parsing in new is wrong for the shown input format, and is also inconsistent with stated requirements
The shown pattern doesn't have a format specifier for the seconds, nor for the milliseconds that follow it, nor for the following AM/PM -- all expected in the input string. So in general an input in the shown form cannot be parsed correctly with the shown pattern
The %H matches 00-23 hour, so not 12-hour clock which is stated as expected and is implicit by the presence of AM. (It still matches a 12-hour-time number but it won't once the missing AM/PM format specifier is added.)
The pattern in the OP works and parses the given input correctly because 03:45... happens to be in AM, and the module uses regex to match the given pattern anywhere in the given string (by default), so %H:%M matches 03:45 and the rest of the input string doesn't matter. If we turn on strict in the constructor this'll fail. See documentation.
Assuming that the shown input is the correct part we'd need
my $strp = DateTime::Format::Strptime->new(
pattern => '%h %d %Y %I:%M:%S%3N%p'
);
Here %I is for 12-hour clock (1-12), added %S is for seconds and %3N for milliseconds (see the page in docs for patterns, linked above), and %p for AM/PM.
The rest then works as it stands, along with printing in a desired format given above.
Following code demonstrates how desired output can be achieved without any modules
use strict;
use warnings;
use feature 'say';
my $date = 'Apr 9 2017 3:45:00:000AM';
my #fields = qw/month mday year hour min sec usec/;
my %months = ( Jan => 1, Feb => 2, Mar => 3,
Apr => 4, May => 5, Jun => 6,
Jul => 7, Aug => 8, Sep => 9,
Oct => 10, Nov => 11, Dec => 12
);
my %parts;
#parts{#fields} = split "[ :]+", $date;
$parts{hour} += 12 if $parts{usec} =~ /PM/;
$parts{month} = $months{ $parts{month} };
printf "%04d-%02d-%02d %02d:%02d:%02d\n",
#parts{qw/year month mday hour min sec/};
Perl code with assistance of module
use strict;
use warnings;
use feature 'say';
use DateTime::Format::DateParse;
my($date, $dt);
$date = 'Apr 9 2017 3:45:00:000AM';
$date =~ s/:(\d{3}[AP]M)/.$1/;
$dt = DateTime::Format::DateParse->parse_datetime( $date );
$date = $dt;
$date =~ s/T/ /;
say $date;
There are two issues with your code. First, the pattern you use to parse your date is not correct: %H is used for a 24-hour format hour. Instead, you should use a combination of %i (12-hour) and %p (AM/PM). Second, to print a DateTime object, you should first format it first (using ->strftime or ->ymd() for instance).
The milliseconds in the date are, however, a bit of an issue because strptime does not have a option to match milliseconds. I suggest to first remove them from your date, and only then parse the date with strptime:
use DateTime::Format::Strptime;
my $date = 'Apr 9 2017 3:45:00:505PM';
# Removing milliseconds from date
$date =~ s/:(\d{3})(?=AM|PM)//;
my $milliseconds = $1;
my $strp = DateTime::Format::Strptime->new(pattern => '%h %d %Y %I:%M:%S%p',);
my $start_date = $strp->parse_datetime($date);
# Taking into account the milliseconds that were removed earlier
$start_date->add(seconds => 1) if $milliseconds > 500;
say $start_date->strftime("%F %T");

How to compare dates using perl?

How can i compare two dates in the format using Perl:
"dd mon yyyy hh:mm:ss GMT"
e.g.: 12 May 2013 10:10:20 GMT
I cannot install any external Perl modules.
Please advice.
If you have Perl v5.9.5 or newer, you can use Time::Piece core module. Here's a simple demonstration of relevant operations
Convert the dates from string to Time::Piece object
my $date = "12 May 2013 10:10:20 GMT";
my $tp1 = Time::Piece->strptime($date, "%d %B %Y %T %Z");
my $tp2 = ...
Find the difference between the 2 time
my $diff = $tp2 - $tp1;
which gives you a Time::Seconds object.
Finally, display the difference in units of seconds (or something else).
print $diff->seconds;
Or you could just compare the two directly (thanks stevenl)
$tp2 > $tp1
References:
Time::Seconds
Time::Piece
man strftime for format string to use with Time::Piece->strptime
man strptime for format string to use with $tp->strftime
Note that only format characters aAbBcdHIjmMpSUwWxXyYZ% are safe if you're using non-Unix system (for example, Window's Perl doesn't support the %e specifier).
Convert the dates to the format yyyymmddhhmmss (e.g. 20130512101020) and compare as strings. Handling the time zones might get tricky without modules, though.
One of the most popular date modules is DateTime. It has a FAQ which may help you get started.
sub to_comparable {
my ($date) = #_;
my ($H,$M,$S,$d,$m,$Y) = $date =~ m{^([0-9]{2}):([0-9]{2}):([0-9]{2}), ([0-9]{2})/([0-9]{2})/([0-9]{4})\z}
or die;
return "$Y$m$d$H$M$S";
}
if (to_comparable($date2) < to_comparable($date1)) {
...
} else {
...
}

How to convert text date to timestamp?

How to convert date in text format:
23.10.2011 11:35:00
to timestamp ?
Using Time::Local , you can do:
use Time::Local;
my $date = '23.10.2011 11:35:00';
my ($mday,$mon,$year,$hour,$min,$sec) = split(/[\s.:]+/, $date);
my $time = timelocal($sec,$min,$hour,$mday,$mon-1,$year);
print $time,"\n",scalar localtime $time;
Output:
1319362500
Sun Oct 23 11:35:00 2011
I'd look into DateTime and the parsing modules.
perl -MDateTime::Format::Strptime -le'$strp = DateTime::Format::Strptime->new( pattern => "%d.%m.%Y %T", time_zone => "local"); $dt = $strp->parse_datetime("23.10.2011 11:35:00"); print $dt->epoch'
1319384100 at -e line 1.
Same as above, but not a one-liner:
use DateTime::Format::Strptime;
my $strp = DateTime::Format::Strptime->new(
pattern => '%d.%m.%Y %T',
time_zone => 'local',
);
my $dt = $strp->parse_datetime('23.10.2011 11:35:00');
print $dt->epoch;
Using Date::Parse in Perl is very elegante option too.
Maybe someone will found this useful:
$gmtdate = 'Jul 12 00:12:26 2021 GMT';
my $timestamp = str2time($gmtdate);
Not sure why the Time::Piece answer was deleted. Maybe because it (Time::Piece) has its quirks. But it's more powerful than Date::Parse (although less powerful than DateTime, but some say it covers most of the needs), it's a core module, and you don't have to parse the string as with Time::Local. But datetime math is apparently not DST-safe.
Long story short:
use Time::Piece;
my $tp = Time::Piece->strptime("23.10.2011 11:35:00",
"%d.%m.%Y %H:%M:%S");
# or this way (if the timestamp is in the local time zone):
# $tp = localtime->strptime("23.10.2011 11:35:00",
# "%d.%m.%Y %H:%M:%S");
print $tp;
More on creating Time::Piece objects here.

How do I convert datetime to a different format? [perl]

Need help parsing the datetime stamp and splitting it up by date and time.
use strict;
use warnings;
use Time::Piece;
my $string = "05:57:03 08/31/10 MDT";
print $string,"\n";
my $time = Time::Piece->strptime($string, "%H:%M:%S");
my $date = Time::Piece->strptime($string, "%Y/%m/%d");
print $time,$date,"\n";
Thanks! Also how do I figure out which day of week this is using code?
use DateTime::Format::Strptime;
my $s = DateTime::Format::Strptime->new(pattern => '%T %D %Z');
my $dt = $s->parse_datetime('05:57:03 08/31/10 MDT');
say $dt->strftime('%A'); # Tuesday
You should be able to use code like the following:
my $t = Time::Piece->strptime($string, "%H:%M:%S %m/%d/%y %Z");
However, on my system at least, I have to change the time zone MST to GMT for it to match; if I leave it as in your example, I get an error:
Perl> my $t = Time::Piece->strptime("05:57:03 08/31/10 DST", "%H:%M:%S %m/%d/%y %Z");
[!] Runtime error: Error parsing time at /usr/local/lib/perl/5.10.0/Time/Piece.pm line 469.
If it works for you, though, you'll have a Time::Piece object, on which you can call e.g. $t->day_of_week for the day of the week as a number, $t->day for e.g. 'Tue', or $t->fullday for e.g. 'Tuesday'.
See the documentation for Time::Piece for details on the methods you can call.

Parsing dateTime in perl correct to micro/nano seconds

Im looking for a perl module which takes a date string like this "Nov 23 10:42:31.808381" and its format something like "%b %d ...." this and get me a dateTime object/or print it into another format specified in the same way. Time::Piece doesnt have resolution upto nano seconds. Is there any module which will help me?
Im using perl 5.8 (this doesnt have named backreferences)
DateTime::Format::Strptime has support for microseconds and nanoseconds.
Your format is slightly strange as it doesn't include a year. I've added one you make this demo code work.
#!/usr/bin/perl
use strict;
use warnings;
use DateTime::Format::Strptime;
my $strp = DateTime::Format::Strptime->new(
pattern => '%b %d %Y %H:%M:%S.%6N',
on_error => 'croak',
);
my $string = 'Nov 23 2010 10:42:31.808381';
my $dt = $strp->parse_datetime($string);
print $dt->microsecond;
"Nov 23 10:42:31.808381" -- is microseconds, not nanoseconds
If you need microseconds, you must use Time::HiRes; module and gettimeofday method in it.