54

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?

zb226
  • 9,586
  • 6
  • 49
  • 79
dreeves
  • 26,430
  • 45
  • 154
  • 229

14 Answers14

37

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.

szabgab
  • 6,202
  • 11
  • 50
  • 64
SpoonMeiser
  • 19,918
  • 8
  • 50
  • 68
27

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;
}
szabgab
  • 6,202
  • 11
  • 50
  • 64
dreeves
  • 26,430
  • 45
  • 154
  • 229
17

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

szabgab
  • 6,202
  • 11
  • 50
  • 64
bmdhacks
  • 15,841
  • 8
  • 34
  • 55
10

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

szabgab
  • 6,202
  • 11
  • 50
  • 64
Sobrique
  • 52,974
  • 7
  • 60
  • 101
8
$ENV{TZ}="GMT";
POSIX::tzset();
$time = POSIX::mktime($s,$m,$h,$d,$mo-1,$y-1900);
ysth
  • 96,171
  • 6
  • 121
  • 214
  • epoch time is already in GMT, there' no timezone. By explicitly setting TZ=GMT, aren't you parsing the time as being **from** the GMT timezone rather than local date/time? (and I think you should rather use TZ=UTC, GMT is a country's timezone) – Thomas Guyot-Sionnest Dec 21 '15 at 21:22
  • 1
    @ThomasGuyot-Sionnest my intent was to illustrate that mktime converts from local time to epoch seconds, so you need to know what local time your input is in and set TZ accordingly (or just assume TZ is already set to what you want, but that's sloppy) – ysth Dec 21 '15 at 22:02
  • I think it's safe to assume most people will want to use local time and that is what is set on the computer/server. I do understand the zone is important when working with different regions but I think showing an example setting TZ to GMT distracts from the actual answer and may even misled rookies into using the wrong timezone for time input. OTOH you get bonus point for calling tzset() after changing TZ ;) – Thomas Guyot-Sionnest Dec 23 '15 at 04:22
6

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)

szabgab
  • 6,202
  • 11
  • 50
  • 64
scottc
  • 231
  • 1
  • 2
  • 6
    No, please don’t. Date::Manip’s own documentation has a big section that tries to convince you not to use it. – Aristotle Pagaltzis Sep 19 '08 at 00:28
  • 2
    Date::Manip will betray you when you are most vulnerable. Use DateTime. :) – Kevin May 02 '12 at 18:18
  • @AristotlePagaltzis, where "tries to convince you not to use it" means only "says that it can do everything with one Calendar, but allows that multiple less-than-everythings can be performed more efficiently by multiple other modules." So, everything scottc already said, with more emphasis on Date::Manip being a one-stop solution. – Julian Fondren Jun 25 '13 at 08:34
  • @scottc it does not work for $string = '18-Sep-2008 12:09:16 PM'; how do i convert to PM to 24 hour format – Shantesh Oct 07 '16 at 11:14
2

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
  • 6,188
  • 4
  • 26
  • 31
  • 1
    Why use perl when you can use the infinitely more flexible GNU date? `date -d "STRING" +%s` allows nearly anything, from "yesterday" to "90 days" [after right now] to more traditional things like "Mar 15 2016" and can output into nearly any format as well (`+%s` is epoch, see `date --help` for tons more). (It's too bad perl `DateTime` doesn't have this functionality.) – Adam Katz Mar 15 '16 at 21:01
2

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
  • 97,646
  • 72
  • 174
  • 218
2

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.

szabgab
  • 6,202
  • 11
  • 50
  • 64
Penfold
  • 2,548
  • 16
  • 16
2

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,380
  • 3
  • 23
  • 27
0

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.

0

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

gpwr
  • 988
  • 1
  • 10
  • 21
-3

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:~$ 
Martin Dorey
  • 2,944
  • 2
  • 24
  • 16
-4

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
  • 21,344
  • 33
  • 76
  • 86
  • 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 Sep 18 '08 at 19:03
  • Why rewrite it as a shell script? It works just fine as-is and I find it extremely useful on a daily basis. – raldi Sep 19 '08 at 05:55
  • Not a perl solution if it relies on system date command. Does not work in unix or windows. – kkoolpatz Sep 23 '15 at 15:42