2010-01-08 3 views
1

Я столкнулся с странным timewarp, выполняя некоторую математику со временем, и это оставило меня в тупике. Или, ну, я нашел причину (или, по крайней мере, правдоподобную), но не знаю, что с этим делать, или если есть что-то, что можно сделать.Проблемы с добавлением времени

Проблема в простых словах заключается в том, что при добавлении времени в более крупных единицах, чем за 1 неделю (исключая множители), представляется невозможным быть точным. И, точнее, я имею в виду, что когда я добавляю 1 год в секунду, чтобы СЕЙЧАС, я получаю 1 год и несколько часов от СЕЙЧАС.

Я могу понять, что добавление 1 (или более) месяцев будет делать это, так как длина месяца варьируется, но год должен быть более или менее одинаковым, не так ли?

Теперь я знаю, что вы хотите знать, как я это делаю, так что здесь следует (pseudoish) Код:

class My_DateInterval { 
    public $length; // Interval length in seconds 

    public function __construct($interval) { 
     $this->length = 0; 

     preg_match(
      '/P(((?<years>([0-9]{1,}))Y)|((?<months>([0-9]{1,}))M)|((?<weeks>([0-9]{1,}))W)|((?<days>([0-9]{1,}))D)){0,}(T((?<hours>([0-9]{1,2})){1}H){0,1}((?<minutes>([0-9]{1,2}){1})M){0,1}((?<seconds>([0-9]{1,2}){1})S){0,1}){0,1}/', 
      $interval, $timeparts 
     ); 

     if (is_numeric($timeparts['years'])) $this->length += intval($timeparts['years']) * 31556926; // 1 year in seconds 
     if (is_numeric($timeparts['months'])) $this->length += intval($timeparts['months']) * 2629743.83; // 1 month in seconds 
     if (is_numeric($timeparts['weeks'])) $this->length += intval($timeparts['weeks']) * 604800; // 1 week in seconds 
     if (is_numeric($timeparts['days'])) $this->length += intval($timeparts['days']) * 86400; // 1 day in seconds 
     if (is_numeric($timeparts['hours'])) $this->length += intval($timeparts['hours']) * 3600; // 1 hour in seconds 
     if (is_numeric($timeparts['minutes'])) $this->length += intval($timeparts['minutes']) * 60; // 1 minute in seconds 
     if (is_numeric($timeparts['seconds'])) $this->length += intval($timeparts['seconds']); 

     $this->length = round($this->length); 
    } 
} 

class My_DateTime extends DateTime { 
    public function __construct($time, $tz = null) { 
     parent::__contruct($time, $tz); 
    } 

    public function add(My_DateInterval $i) { 
     $this->modify($i->length . ' seconds'); 
    } 
} 

$d = new My_DateTime(); 
$i = new My_DateInterval('P1Y'); 
$d->add($i); 

Выполнение некоторых отладочных распечаток длины интервала и до/после значения показывают, что все это хорошо, в том смысле, что «он работает так, как ожидалось, и все проверяет», но проблема, о которой говорилось выше, все еще стоит: есть аномалия в точности всего этого, что я бы очень хотел получить, если это возможно.

Во многих словах: как сделать точную математику с единицами времени более 1 недели. (I.e. 1 месяц/1 год).

PS. Для тех, кто задается вопросом, почему я использую классы My_ *, так это потому, что PHP 5.3 просто недостаточно распространен, но я пытаюсь сохранить что-то таким образом, что переход на встроенные классы полезности будет максимально гладким.

ответ

2

A год 365.25 дней, грубо говоря. Следовательно, у нас високосные годы.

Месяцы имеют переменную длину.

Следовательно, семантика того, что добавление года и добавление месяца, вероятно, не соответствует добавлению фиксированного количества секунд.

Например, добавление месяца к 14 февраля 2007 года, вероятно, ожидается 14 марта 2007 года и 14 февраля 2008 года будет 14 марта 2008 года, добавив 28 или 29 дней соответственно.

Этот материал становится грубым, особенно когда мы добавляем в разные календари, большая часть мира даже не имеют в феврале! Затем добавьте «Семь рабочих дней» - вам нужно учитывать календарь праздничного дня.

Нет библиотек, которые вы можете найти для этого?

+0

Да, это в значительной степени то, что я подозревал. Я полагаю, что для этого могут быть библиотеки, но мне они действительно не нужны, мне просто нужно пересмотреть мой подход. Спасибо! –

1

Я могу понять, что добавление 1 (или более) месяцев будет делать это, поскольку длина месяцев варьируется, но год должен быть более или менее одинаковым, не так ли?

Выше, когда вы добавляете стоит год секунд, вы начинаете с количеством дней (то, что я думаю, есть) и средней года (то есть, 365,2421 .. * 24 * 60 * 60). Таким образом, ваш расчет неявно определяет год как определенное количество дней.

С этим определением 31 декабря осталось немного меньше 6 часов. Таким образом, ваши «часы» идут с 31 декабря 23:59 до 29:59 (независимо от того, что есть), прежде чем переходить на 00:00 1 января. Нечто похожее будет происходить с месяцами, так как вы также определяете их как определенное количество секунд вместо 28 - 31 дней.

Если целью вашего расчета было время разницы между событиями в общем «среднем» календаре, тогда ваш метод будет работать нормально. Но если вам нужна переписка с реальным календарем, это будет отключено.

Самый простой способ сделать это - использовать фальшивый юлианский календарь. Следите за каждым разделом времени (год, месяц, день и т. Д.). Определите дни, чтобы быть точно 24 часа. Определите год как 365 дней. Добавьте 1 день, если год делится на 4, но не тогда, когда он делится на 100, если он также не делится на 400.

Если вы хотите добавить или вычесть, выполните «математику» вручную ... каждый раз, когда вы увеличиваете , проверьте «переполнение». Если вы переполнили день месяца, сбросьте его и увеличьте месяц (затем проверьте месяц на переполнение и т. Д. И т. Д.).

Ваш календарь сможет точно соответствовать реальному календарю, практически для всего.

Большинство реализаций компьютерных версий делают в основном это (в разной степени сложности). В JavaScript, например, (потому что мой веб-инспектор удобно):

> new Date(2010,0,1,0,0,0) - new Date(2009,0,1,0,0,0) 
31536000000 

Как вы можете видеть, что это ровно 365 дней стоит миллисекунд. Никакой беспорядок, никакой суеты.

Путь к ним, получение времени HARD. Математика находится в базе 60. Это все особые случаи (високосные годы, летнее время, часовые пояса).

Удачи.

+1

Да, спасибо. Я действительно не заинтересован в том, чтобы делать собственные расчеты по календарю, так как они уже сделаны (лучше, чем я мог). Я просто пытался решить проблему глупо, но, как всегда, это требовало публичной публикации, прежде чем ее реализовать. –

+0

Программирование глупости лучше всего при совместном использовании :) – Seth

0

На всякий случай кто-то заинтересован, рабочее решение было настолько простым, что (как обычно) заставляет меня чувствовать себя глупо. Вместо перевода длины интервала в секунды версия Worder корректно работает с внутренними компонентами PHP.

class My_DateInterval { 
    public $length; // strtotime supported string format 
    public $years; 
    public $months; 
    public $weeks; 
    public $days; 
    public $hours; 
    public $minutes; 
    public $seconds; 

    public function __construct($interval) { 
     $this->length = 0; 

     preg_match(
      '/P(((?<years>([0-9]{1,}))Y)|((?<months>([0-9]{1,}))M)|((?<weeks>([0-9]{1,}))W)|((?<days>([0-9]{1,}))D)){0,}(T((?<hours>([0-9]{1,2})){1}H){0,1}((?<minutes>([0-9]{1,2}){1})M){0,1}((?<seconds>([0-9]{1,2}){1})S){0,1}){0,1}/', 
      $interval, $timeparts 
     ); 

     $this->years = intval($timeparts['years']); 
     $this->months = intval($timeparts['months']); 
     $this->weeks = intval($timeparts['weeks']); 
     $this->days = intval($timeparts['days']); 
     $this->hours = intval($timeparts['hours']); 
     $this->minutes = intval($timeparts['minutes']); 
     $this->seconds = intval($timeparts['seconds']); 

     $length = $this->toString(); 
    } 

    public function toString() { 
     if (empty($this->length)) { 
      $this->length = sprintf('%d years %d months %d weeks %d days %d hours %d minutes %d seconds', 
       $this->years, 
       $this->months, 
       $this->weeks, 
       $this->days, 
       $this->hours, 
       $this->minutes, 
       $this->seconds 
      ); 
     } 

     return $this->length; 
    } 
} 
Смежные вопросы