2016-03-22 5 views
4

В Perl существует функция, называемая timelocal, для преобразования времени в эпоху.Преобразование времени до года 999 в эпоху

пример:my $epoch = timelocal($sec, $min, $hour, $mday, $mon, $year)

Эта функция, однако, как представляется, по своей сути недостатки при работе с временами очень далеко в прошлом (до 999 года) - см раздел о Year Value Interpretation. Чтобы ухудшить ситуацию, способ, которым он обрабатывает 2-значные годы, еще более усложняет ситуацию ...

Учитывая время до 999 года, как я могу точно преобразовать его в соответствующее значение эпохи?

+3

Это не недостаток, это DWIM. – mob

ответ

6

Предоставлено время до 999 года, как я могу точно преобразовать его в соответствующее значение эпохи?

Вы не можете со временем: по местному времени. 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 

Обратите внимание, что григорианский календарь WASN 't даже принят до 1582 года, поэтому DateTime использует так называемый «пролепический григорианский календарь», просто расширяя его назад от 1582.


* С 32-битовыми целыми числами, даты слишком далеко в прошлом или будущем будут вызывать целочисленное переполнение. Ваш perl поддерживает 64-битные целые числа, если use64bitint=define появляется на выходе до perl -V (с капиталом «V»).

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. имеют низкую толерантность к ошибке, даже для «старого» датирует

Первого это собирается быть проблемой, если у вас есть 32-битная машина.Диапазон (в годах) для подписанной 32-разрядной эпохи составляет около 2^31/(3600 * 24 * 365) или (только) 68 лет до 1970 года (предполагая эпоху unix). Тем не менее, для 64-битного набора это составляет 290 000 лет к 1970 году, и я думаю, что это будет нормально. :-)

Только вы можете сказать, будет ли вторая проблема проблемой. Присутствуют результаты обходного рассмотрения степени ошибки;

$ 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 

Таким образом, ошибка примерно 0,2 дней при работе с датами около 1000bc.

Короче говоря, если у вас 64-разрядная машина, вы должны быть в порядке.

+0

Теперь это интересно. Код для расчета эпохи: '($ self -> {utc_rd_days} - 719163) * SECONDS_PER_DAY + $ self -> {utc_rd_secs}', где 'utc_rd_days' - количество дней с 1 января, года 1. С 32- бит int, 719163 * 86400 будет переполняться. – ThisSuitIsBlackNot

+0

Кроме того, ваши расчеты отключены. Продолжительность года в григорианском календаре составляет 365,2425 дней. В вашем последнем вычислении вы используете длину тропического года. – ThisSuitIsBlackNot

+0

Спасибо, что указали это. Это все немного относительное - вы видите, что я начал использовать 365.25. Идея, конечно же, состоит в том, чтобы понять, как можно игнорировать такие вещи, как игнорирование секунд прыжка. Оказывается, ИМО очень точен. Я рад, что кто-то посчитал это интересным - я потратил слишком много времени на это, и в то же время появились еще два ответа. – Marty

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; 
} 
Смежные вопросы