views:

7462

answers:

11

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?

+2  A: 

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;
}
dreeves
What advantage does Time::Local give you that the more common POSIX(strftime) doesn't?
Paul Tomblin
strftime is for outputting time, timelocal is to 'input' them.
Leon Timmermans
+25  A: 

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.

SpoonMeiser
It's always better to use a library function to do a common operation than to roll your own (buggy) solution.
Nighthawk
A: 

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
raldi
That is the kind of thing that either should be a shell script, or it should be made to use a proper Perl module to do the work IMHO.
Leon Timmermans
Why rewrite it as a shell script? It works just fine as-is and I find it extremely useful on a daily basis.
raldi
+11  A: 

To parse a date, look at Date::Parse in CPAN.

bmdhacks
A: 

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:~$
+1  A: 

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

skiphoppy
+3  A: 

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)

scottc
No, please don’t. Date::Manip’s own documentation has a big section that tries to convince you not to use it.
Aristotle Pagaltzis
+1  A: 

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 Date::Time route, either by using a specific Date::Time::Format subclass, or using Date::Time::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.

Penfold
It's DateTime, not Date::Time.
Dave Rolsky
+2  A: 

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.

Shlomi Fish
+4  A: 
$ENV{TZ}="GMT";
$time = POSIX::mktime($s,$m,$h,$d,$mo-1,$y-1900);
ysth
+2  A: 

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!

Anders