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.
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.
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
Given a date/time as an array of (year, month, day, hour, minute, second), how would you convert it to epoch time, i.e., the number of seconds since 1970-01-01 00:00:00 GMT?
Bonus question: If given the date/time as a string, how would you first parse it into the (y,m,d,h,m,s) array?
If you're using the DateTime module, you can call the epoch() method on a DateTime object, since that's what you think of as unix time.
Using DateTimes allows you to convert fairly easily from epoch, to date objects.
Alternativly, localtime and gmtime will convert an epoch into an array containing day month and year, and timelocal and timegm from the Time::Local module will do the opposite, converting an array of time elements (seconds, minutes, ..., days, months etc.) into an epoch.
This is the simplest way to get unix time:
use Time::Local;
timelocal($second,$minute,$hour,$day,$month-1,$year);
Note the reverse order of the arguments and that January is month 0.
For many more options, see the DateTime module from CPAN.
As for parsing, see the Date::Parse module from CPAN. If you really need to get fancy with date parsing, the Date::Manip may be helpful, though its own documentation warns you away from it since it carries a lot of baggage (it knows things like common business holidays, for example) and other solutions are much faster.
If you happen to know something about the format of the date/times you'll be parsing then a simple regular expression may suffice but you're probably better off using an appropriate CPAN module. For example, if you know the dates will always be in YMDHMS order, use the CPAN module DateTime::Format::ISO8601.
For my own reference, if nothing else, below is a function I use for an application where I know the dates will always be in YMDHMS order with all or part of the "HMS" part optional. It accepts any delimiters (eg, "2009-02-15" or "2009.02.15"). It returns the corresponding unix time (seconds since 1970-01-01 00:00:00 GMT) or -1 if it couldn't parse it (which means you better be sure you'll never legitimately need to parse the date 1969-12-31 23:59:59). It also presumes two-digit years XX up to "69" refer to "20XX", otherwise "19XX" (eg, "50-02-15" means 2050-02-15 but "75-02-15" means 1975-02-15).
use Time::Local;
sub parsedate {
my($s) = #_;
my($year, $month, $day, $hour, $minute, $second);
if($s =~ m{^\s*(\d{1,4})\W*0*(\d{1,2})\W*0*(\d{1,2})\W*0*
(\d{0,2})\W*0*(\d{0,2})\W*0*(\d{0,2})}x) {
$year = $1; $month = $2; $day = $3;
$hour = $4; $minute = $5; $second = $6;
$hour |= 0; $minute |= 0; $second |= 0; # defaults.
$year = ($year<100 ? ($year<70 ? 2000+$year : 1900+$year) : $year);
return timelocal($second,$minute,$hour,$day,$month-1,$year);
}
return -1;
}
To parse a date, look at Date::Parse in CPAN.
I know this is an old question, but thought I would offer another answer.
Time::Piece is core as of Perl 5.9.5
This allows parsing of time in arbitrary formats via the strptime method.
e.g.:
my $t = Time::Piece->strptime("Sunday 3rd Nov, 1943",
"%A %drd %b, %Y");
The useful part is - because it's an overloaded object, you can use it for numeric comparisons.
e.g.
if ( $t < time() ) { #do something }
Or if you access it in a string context:
print $t,"\n";
You get:
Wed Nov 3 00:00:00 1943
There's a bunch of accessor methods that allow for some assorted other useful time based transforms. https://metacpan.org/pod/Time::Piece
$ENV{TZ}="GMT";
POSIX::tzset();
$time = POSIX::mktime($s,$m,$h,$d,$mo-1,$y-1900);
Get Date::Manip from CPAN, then:
use Date::Manip;
$string = '18-Sep-2008 20:09'; # or a wide range of other date formats
$unix_time = UnixDate( ParseDate($string), "%s" );
edit:
Date::Manip is big and slow, but very flexible in parsing, and it's pure perl. Use it if you're in a hurry when you're writing code, and you know you won't be in a hurry when you're running it.
e.g. Use it to parse command line options once on start-up, but don't use it parsing large amounts of data on a busy web server.
See the authors comments.
(Thanks to the author of the first comment below)
My favorite datetime parser is DateTime::Format::ISO8601 Once you've got that working, you'll have a DateTime object, easily convertable to epoch seconds with epoch()
There are many Date manipulation modules on CPAN. My particular favourite is DateTime and you can use the strptime modules to parse dates in arbitrary formats. There are also many DateTime::Format modules on CPAN for handling specialised date formats, but strptime is the most generic.
For further reference, a one liner that can be applied in, for example, !#/bin/sh scripts.
EPOCH="`perl -e 'use Time::Local; print timelocal('${SEC}','${MIN}','${HOUR}','${DAY}','${MONTH}','${YEAR}'),\"\n\";'`"
Just remember to avoid octal values!
Possibly one of the better examples of 'There's More Than One Way To Do It", with or without the help of CPAN.
If you have control over what you get passed as a 'date/time', I'd suggest going the DateTime route, either by using a specific Date::Time::Format subclass, or using DateTime::Format::Strptime if there isn't one supporting your wacky date format (see the datetime FAQ for more details). In general, Date::Time is the way to go if you want to do anything serious with the result: few classes on CPAN are quite as anal-retentive and obsessively accurate.
If you're expecting weird freeform stuff, throw it at Date::Parse's str2time() method, which'll get you a seconds-since-epoch value you can then have your wicked way with, without the overhead of Date::Manip.
I'm using a very old O/S that I don't dare install libraries onto, so here's what I use;
%MonthMatrix=("Jan",0,"Feb",31,"Mar",59,"Apr",90,"May",120,"Jun",151,"Jul",181,"Aug",212,"Sep",243,"Oct",273,"Nov",304,"Dec",334);
$LeapYearCount=int($YearFourDigits/4);
$EpochDayNumber=$MonthMatrix{$MonthThreeLetters};
if ($LeapYearCount==($YearFourDigits/4)) { if ($EpochDayNumber<32) { $EpochDayNumber--; }}
$EpochDayNumber=($YearFourDigits-1970)*365+$LeapYearCount+$EpochDayNumber+$DayAsNumber-493;
$TimeOfDaySeconds=($HourAsNumber*3600)+($MinutesAsNumber*60)+$SecondsAsNumber;
$ActualEpochTime=($EpochDayNumber*86400)+$TimeOfDaySeconds;
The input variables are;
$MonthThreeLetters
$DayAsNumber
$YearFourDigits
$HourAsNumber
$MinutesAsNumber
$SecondsAsNumber
...which should be self-explanatory.
The input variables, of course, assume GMT (UTC). The output variable is "$ActualEpochTime". (Often, I only need $EpochDayNumber, so that's why that otherwise superfluous variable sits on its own.)
I've used this formula for years with nary an error.
Here is a quick example that uses the Perl module Time::Local
use Time::Local;
$number_of_seconds = timelocal(0,24,2, 26,3,2022);
The arguments timelocal needs are: second, minute, hour, day, month, year
A filter converting any dates in various ISO-related formats (and who'd use anything else after reading the writings of the Mighty Kuhn?) on standard input to seconds-since-the-epoch time on standard output might serve to illustrate both parts:
martind#whitewater:~$ cat `which isoToEpoch`
#!/usr/bin/perl -w
use strict;
use Time::Piece;
# sudo apt-get install libtime-piece-perl
while (<>) {
# date --iso=s:
# 2007-02-15T18:25:42-0800
# Other matched formats:
# 2007-02-15 13:50:29 (UTC-0800)
# 2007-02-15 13:50:29 (UTC-08:00)
s/(\d{4}-\d{2}-\d{2}([T ])\d{2}:\d{2}:\d{2})(?:\.\d+)? ?(?:\(UTC)?([+\-]\d{2})?:?00\)?/Time::Piece->strptime ($1, "%Y-%m-%d$2%H:%M:%S")->epoch - (defined ($3) ? $3 * 3600 : 0)/eg;
print;
}
martind#whitewater:~$
If you're just looking for a command-line utility (i.e., not something that will get called from other functions), try out this script. It assumes the existence of GNU date (present on pretty much any Linux system):
#! /usr/bin/perl -w
use strict;
$_ = (join ' ', #ARGV);
$_ ||= <STDIN>;
chomp;
if (/^[\d.]+$/) {
print scalar localtime $_;
print "\n";
}
else {
exec "date -d '$_' +%s";
}
Here's how it works:
$ Time now
1221763842
$ Time yesterday
1221677444
$ Time 1221677444
Wed Sep 17 11:50:44 2008
$ Time '12:30pm jan 4 1987'
536790600
$ Time '9am 8 weeks ago'
1216915200