Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Converting times before the year 999 to epoch

Tags:

time

epoch

perl

In Perl a function called timelocal exists to convert times to epoch.

example: my $epoch = timelocal($sec, $min, $hour, $mday, $mon, $year)

This function however appears to be inherently flawed when dealing with times very far in the past (before the year 999) - see the section on Year Value Interpretation. To make things worse, the way it processes 2 digit years complicates things even more...

Given a time before the year 999 how can I accurately convert it to its corresponding epoch value?

like image 433
tjwrona1992 Avatar asked Feb 08 '23 10:02

tjwrona1992


2 Answers

Given a time before the year 999 how can I accurately convert it to its corresponding epoch value?

You can't with Time::Local. timegm (which is used by timelocal) contains the following:

if ( $year >= 1000 ) {
    $year -= 1900;
}
elsif ( $year < 100 and $year >= 0 ) {
    $year += ( $year > $Breakpoint ) ? $Century : $NextCentury;
}

If the year is between 0 and 100, it's automatically converted to a year in the current century as described in the documentation; years between 100 and 999 are treated as offsets from 1900. You can't get around that without hacking the source.


If your perl was compiled to use 64-bit integers*, you can use the DateTime module instead:

use strict;
use warnings 'all';
use 5.010;

use DateTime;

my $dt = DateTime->new(
    year       => 1,
    month      => 1,
    day        => 1,
    hour       => 0,
    minute     => 0,
    second     => 0,
    time_zone  => 'UTC'
);

say $dt->epoch;

Output:

-62135596800

Note that the Gregorian calendar wasn't even adopted until 1582, so DateTime uses what's called the "proleptic Gregorian calendar" by simply extending it backwards from 1582.


* With 32-bit integers, dates too far in the past or future will cause integer overflow. Your perl supports 64-bit integers if use64bitint=define appears in the output to perl -V (with a capital 'V').

like image 192
ThisSuitIsBlackNot Avatar answered Mar 07 '23 09:03

ThisSuitIsBlackNot


Looking at the votes and reviews, the DateTime module would seem to be the authoritative, go-to module for this sort of stuff. Unfortunately its $dt->epoch() documentation comes with these caveats;

Since the epoch does not account for leap seconds, the epoch time for 
1972-12-31T23:59:60 (UTC) is exactly the same as that for 1973-01-01T00:00:00.

This module uses Time::Local to calculate the epoch, which may or may not 
handle epochs before 1904 or after 2038 (depending on the size of your system's 
integers, and whether or not Perl was compiled with 64-bit int support).

It would appear these are the limits you going to have to work within.

Having said that, this comment is probably a sensible warning for users who

  1. Are using a machine with 32-bit ints; or
  2. Have a low error tolerance even for "old" dates

The first is going to be a problem if you have a 32-bit machine. The range (in years) for a signed 32-bit based epoch is around 2^31 / (3600*24*365) or (only) 68 years to/from 1970 (presuming a unix epoch). For a 64 bit int however, it becomes 290,000 years to/from 1970 - which would be ok, I presume. :-)

Only you can say if the second issue is going to be a problem. Herewith are the results of a back-of-the-envelope examination of the degree of error;

$ perl -MDateTime -E 'say DateTime->new( year => 0 )->epoch / (365.25 * 24 * 3600)'
-1969.96030116359  # Year 0ad is 1969.96 years before 1970
$ perl -MDateTime -E 'say DateTime->new( year => -1000 )->epoch / (365.25*24*3600)'
-2969.93839835729  # year 1000bc is 2969.94 years before 1970
$ perl -MDateTime -E 'say ((DateTime->new( year => -1000 )->epoch - DateTime->new( year => 0 )->epoch ) / (365.25*24*3600))'
-999.978097193703  # 1,000bc has an error of 0.022 years
$ perl -MDateTime -E 'say 1000*365.25 + ((DateTime->new( year => -1000 )->epoch - DateTime->new( year => 0 )->epoch ) / (24 * 3600))'
8  # ... or 8 days
$

NOTE: I don't know how much of this "error" is due to the way I'm examining it - a year is not 365.25 days. In fact, let me correct that - I took a better definition of days in a year from here and we get;

$ perl -MDateTime -E 'say 1000*365.242189  + ((DateTime->new( year => -1000 )->epoch - DateTime->new( year => 0 )->epoch ) / (24 * 3600))'
0.189000000013039

So, an error of something-like 0.2 days when working with dates around 1,000bc.

In short, if you have 64 bit machine, you should be fine.

like image 34
Marty Avatar answered Mar 07 '23 09:03

Marty