Расширенная проверка даты с помощью PHP - PullRequest
5 голосов
/ 20 июля 2009

Мне нужно подтвердить множество дат моего текущего проекта. К сожалению, эти даты могут сильно отличаться. Примеры включают в себя:

  1. 1983-07-10 (после 1970)
  2. 1492-10-11 (до 1970 года Unix Timestamps - это устраняет strtotime () в некоторых системах)
  3. 200 г. до н.э. (Действительно старый ...)

Даты не будут превышать 9999 г. до н. Э., И при этом они не будут будущими (за пределами «сегодня»). Каков наилучший способ проверить, что представленные значения действительно являются датами и при этом правильными датами?

Обновление ...

Все даты должны быть отсортированы в их глобальном списке. Значения дат 1 и 3, приведенные выше, должны быть сопоставимы друг с другом и отсортированы по ASC или DESC.

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

Ответы [ 7 ]

5 голосов
/ 20 июля 2009

Как насчет серии тщательно написанных регулярных выражений, которые распознают каждый возможный формат. Когда вы знаете формат, вы можете проверить и, возможно, поместить его в единое представление (например, 64-битное time_t).

например.,

/(\d{4})-(\d{2})-(\d{2})/
/(\d+)(bc|b.c.|bce|b.c.e)/i
etc.

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

Обновление:

Все даты должны быть отсортированы в их глобальном списке.

Мне кажется, что для того, чтобы иметь возможность сортировать даты, которые появляются в разных форматах, вам нужно единое представление для каждой из них, как я упоминал ранее. Например, используйте многоключевой словарь (std :: multimap в C ++, не уверен насчет PHP) для хранения отображений (единообразное представление) -> (входное представление). В зависимости от реализации контейнера, вы можете получить обратный поиск или заказать ключ бесплатно.

2 голосов
/ 20 июля 2009

Как насчет использования Zend_Date. Библиотека данных Zend - очень хорошая библиотека утилит даты. Он может работать автономно или с другими библиотеками Zend и может работать с date_default_timezone_set (), поэтому даты автоматически анализируются для установленного часового пояса, и он будет работать для дат вне диапазона отметок времени Unix. Иногда это может быть немного скучно, но его сильные стороны значительно перевешивают его слабые стороны.

Возможно, вам придется реализовать собственный анализ для BC / AD, так как я не уверен, что это сработает, но стоит попробовать.

У Pear также есть библиотека дат , на которую, возможно, стоит взглянуть, однако я не использовал ее и слышал от многих людей, что они предпочитают Zend_Date пакету Pear's Date.

Вы всегда можете написать свое, но зачем изобретать велосипед. Если он не катится так, как вы хотите, возьмите его и улучшите его;)

1 голос
/ 20 июля 2009

Так как вы управляете интерфейсом ввода, без ограничения общности мы можем предположить, что будут отдельные целые числа года / месяца / дня (правильная проверка для ... целочисленного значения :). Скажем, этот год будет отрицательным для обозначения БК.

Итак, прежде всего ... очевидный (частичный) ответ: checkdate () . Это хорошо для лет> = 1, как сказано в документации к функции.

Поэтому вы застряли с проблемой того, что делать, если год <= 0. </p>

Давайте сделаем side-trek здесь и посмотрим, почему это может быть БОЛЬШОЙ проблемой ...

Согласно ссылке на Википедию выше, юлианский календарь вступил в силу в 45 г. до н. Этот календарь для всех практических целей идентичен григорианскому календарю, который мы используем сегодня. Разница в том, что между ними существует десятидневное смещение; последний день юлианского календаря был четверг, 4 октября 1582 года, после чего последовал первый день григорианского календаря, пятница, 15 октября 1582 года (цикл рабочих дней не был затронут).

Это уже означает, что даты в диапазоне от 5 октября 1582 до 14 октября 1582 (включительно) недействительны , если вы следуете григорианскому календарю ; их никогда не было.

Возвращаясь оттуда, вы хороши до 45 г. до н. Начиная с 46 г. до н. Э. Вместо юлианского использовался римский календарь .

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

Если можно предположить, что никто в здравом уме не будет на самом деле знать дату до нашей эры или знать, как правильно ее указать, даже если они и сделали, вы можете произвольно предположить, что все даты до нашей эры имеют форму 1 / 1 ГОД. Поэтому ваш интерфейс может отключить элементы управления месяц / день, если установлен флажок «BC», иметь отдельные групповые поля для BC и AD или что-либо еще подходящее.

Единственная остающаяся проблема после всего этого, на мой взгляд, это проверка дат високосных лет. Они были введены с юлианским календарем, но на самом деле не были правильно реализованы до 8 н. Э. .

Последняя ссылка выше подтверждает, что в течение 45 г. до н.э. - 4 г. н.э. (включительно) високосные годы не были правильно рассчитаны. Функция скачка на год, которая учитывает это несоответствие, плюс переключатель julian / gregorian будет:

define('YEAR_JULIAN_CALENDAR_INTRODUCED', -45);
define('YEAR_JULIAN_CALENDAR_LEAP_IMPLEMENTED_CORRECTLY', 8);
define('YEAR_GREGORIAN_CALENDAR_INTRODUCED', 1582);

function is_leap_year($year) {
    if($year < YEAR_JULIAN_CALENDAR_INTRODUCED) {
        return false; // or good luck :)
    }
    if($year < YEAR_JULIAN_CALENDAR_LEAP_IMPLEMENTED_CORRECTLY) {
        return $year <= -9 && $year % 3 == 0;
    }
    if($year < YEAR_GREGORIAN_CALENDAR_INTRODUCED) {
        return $year % 4 == 0;
    }
    // Otherwise, Gregorian is in effect
    return $year % 4 == 0 && ($year % 100 != 0 || $year % 400 == 0);
}

Вооружившись этим, вы могли бы написать функцию, которая правильно сообщает вам, сколько дней в году. Тогда на этом можно построить вычитание / сложение даты.

После всего этого обсуждения (я восхищаюсь мужеством любого, кто прочитал это далеко :) Я должен спросить:

Сколько точности вам на самом деле нужно?

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

Если вам не нужно этого делать, просто притворитесь, что вы никогда не читали все это. :)

1 голос
/ 20 июля 2009

Вы можете рассмотреть возможность реализации собственного класса типов DateTime. Я не уверен, каковы все ваши требования, но я мог видеть, что у него есть свойства для BC / AD, форматирования и т. Д. Если немного подумать, это не должно быть намного сложнее, чем реализация класса типа Money, если он вам знаком. .

Причина, по которой я предлагаю это, состоит в том, что 200 г. до н.э. и 1492-10-07 сильно различаются, даже в формате. Если говорить о манжете, если вы лечите BC <0 <AD, вы также сможете получить необходимые вам расчеты. </p>

0 голосов
/ 20 июля 2009

Второй ответ после обновления вопроса Джонатана:

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

Вы можете просто указать время как количество секунд с 1/1/10000 до н.э. (сверните свою эпоху); 64 бита будет более чем достаточно для этого. Для этого вам нужно решить одну или две задачи.

A. Как сделать 64-битные целые в PHP.

PHP гарантированно предоставит 31 бит для целых чисел. Поэтому вы можете выполнить одно из следующих действий:

  1. Напишите свой собственный класс 62-битных целых, который хранит биты в двух закрытых целочисленных членах. 62 бита также более чем достаточно.

    Это было бы больно и, вероятно, быстро. Основное преимущество: вы не зависите от расширения PHP.

  2. Используйте BCMath или GMP , чтобы делать целые числа произвольной точности.

    Сначала я попробую, если переносимость не обязательна. Это может оказаться медленнее, чем приемлемо, хотя. Основное преимущество: вы не рискуете ошибиться с ошибочным кодом.

Имея класс 60-или-бит-целое число (поддерживающий сложение / вычитание / сравнение с помощью соответствующих методов или вспомогательных функций), вы можете написать класс CustomDateTime, который поддерживает всю необходимую логику. Этот класс будет включать весь код "date-to-int" и наоборот (например, конструкция); все операции, связанные с выполнением чисто внутренних операций (например, сравнение), будут просто перенаправлены в ваш целочисленный класс.

B. Как сделать 64-битные целые в базе данных.

Все базы данных делают это без проблем. Вы почти наверняка должны идти по этому пути, потому что, например, MySQL не поддерживает даты до 1000 года нашей эры. Не знаю о других продавцах.

0 голосов
/ 20 июля 2009

Мне кажется, что наиболее важно перечислить все возможности (или как-то сгруппировать их) и подготовить регулярные выражения для каждого варианта - и на этой основе определить и обработать его.

0 голосов
/ 20 июля 2009

как насчет strtotime ()?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...