将999年之前的时间转换为时代

时间:2016-03-22 20:56:34

标签: perl time epoch

在Perl中,存在一个名为timelocal的函数,用于将时间转换为纪元。

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

然而,当处理过去很久(999年之前)的时间时,此功能似乎存在固有的缺陷 - 请参阅Year Value Interpretation部分。更糟糕的是,它处理2位数的方式使事情变得更加复杂......

考虑到999年之前的一段时间,我如何准确地将其转换为相应的纪元值?

3 个答案:

答案 0 :(得分:6)

  

考虑到999年之前的一段时间,我如何准确地将其转换为相应的纪元值?

你不能使用Time :: Local。 timegm timelocal使用了contains以下内容:

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

如果年份在0到100之间,它会自动转换为当前世纪的一年,如文档中所述;从100年到999年之间的年份被视为1900年的补偿。你不能在没有黑客攻击的情况下解决这个问题。

如果您的perl编译为使用64位整数*,则可以改为使用DateTime模块:

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;

输出:

-62135596800

请注意,公历直到1582年才被采用,因此DateTime使用了所谓的&#34; preleptic Gregorian calendar&#34;通过简单地从1582年向后延伸。

*对于32位整数,过去或将来过多的日期都会导致整数溢出。如果use64bitint=define出现在perl -V的输出中(使用大写&#39; V&#39;),则perl支持64位整数。

答案 1 :(得分:1)

看一下投票和评论,DateTime模块似乎是这种东西的权威,首选模块。不幸的是,它的$dt->epoch()文档附带了这些警告;

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

看起来这些是您必须在其中工作的限制。

话虽如此,这条评论可能是对

用户的明智警告
  1. 正在使用32位整数的机器;或
  2. 即使是“旧”日期也具有较低的容错率
  3. 如果您拥有32位计算机,则第一个 会出现问题。基于签名的32位时期的范围(以年为单位)约为2 ^ 31 /(3600 * 24 * 365)或(仅)68年(自1970年)(假设为unix时期)。然而,对于一个64位的int,它从1970年开始变为290,000年 - 这可能是好的,我猜想。 : - )

    只有你可以说第二个问题是否会成为一个问题。 这是对错误程度进行背面检查的结果;

    $ 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
    $
    

    注意:我不知道这个“错误”有多少是由于我正在检查它的方式 - 一年不是365.25天。事实上,让我纠正一下 - 我从here开始更好地定义了一年中的日子,我们得到了;

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

    因此,当使用约1,000bc的日期时,会出现0.2天的错误。

    简而言之,如果你有64位机器,你应该没问题。

答案 2 :(得分:1)

公历每146,097天重复一次,相当于400年。我们可以将小于1000的年份映射到周期内的同等年份。以下实现映射第三个周期中小于1000到一年的年份,例如0001映射到1201。

#!/usr/bin/perl
use strict;
use warnings;
use Time::Local qw[timegm timelocal];

use constant CYCLE_YEARS   => 400;
use constant CYCLE_DAYS    => 146097;
use constant CYCLE_SECONDS => CYCLE_DAYS * 86400;

sub mytimelocal {
    my ($sec, $min, $hour, $mday, $month, $year) = @_;

    my $adjust = 0;
    if ($year < 1000) {
        my $cycles = 3 - int($year/CYCLE_YEARS) + ($year < 0);
        $year   += $cycles * CYCLE_YEARS;
        $adjust  = $cycles * CYCLE_SECONDS;
    }
    return timelocal($sec, $min, $hour, $mday, $month, $year) - $adjust;
}


use Test::More tests => CYCLE_DAYS;

use constant STD_OFFSET => 
  timelocal(0, 0, 0, 1, 0, 1200) - timegm(0, 0, 0, 1, 0, 1200);

my $seconds = -5 * CYCLE_SECONDS; # -0030-01-01
while ($seconds < -4 * CYCLE_SECONDS) { # 0370-01-01
    my @tm  = gmtime($seconds);
       $tm[5] += 1900;
    my $got = mytimelocal(@tm);
    my $exp = $seconds + STD_OFFSET;
    is($got, $exp, scalar gmtime($seconds));
    $seconds += 86400;
}