k001
k001
:...

April 2032
        1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30

k001 [userpic]
пыхыпыхаю

Этот простой код на PHP парсит дату в формате W3C (http://www.w3.org/TR/NOTE-datetime)

$pat = "/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(:(\d{2}))?(?:([-+])(\d{2}):?(\d{2})|(Z))?/";
if ( preg_match( $pat, $date_str, $match ) ) {
 	        list( $year, $month, $day, $hours, $minutes, $seconds) =
 	            array( $match[1], $match[2], $match[3], $match[4], $match[5], $match[6]);
}


Что называется, найдите ошибку. Комменты скринятся.
Update: таймзона там отдельно ниже обрабатывается, просто я код постить для простоты не стал.
Update2: код регекспа тоже верный, во всяком случае, для целей этого вопроса ;)
Update3: всё расскринено, результаты тут.

Comments

Может, я неправильно понял в 5 ночи, но здесь было $match[6] - ':32'

Если так - тогда $pat = "/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})?(?:([-+])(\d{2}):?(\d{2})|(Z))?/";

Твой регексп неправильный, потому как требует наличия секунд (а точнее -- наличия : после минут).

Я, кстати, в начале точно так же его поменял, а потом понял, в чём дело. Вместо $match[6] надо брать $match[7], и всё.

блин. ну нельзя же так, с утра прям... :))

У меня получилось нечто типа:
$pat = "/(\d{4})(-(\d{2}))?(-(\d{2}))?(T(\d{2}))?(:(\d{2}))?(:(\d{2}))?(\.\d{2})?((([-+])(\d{2}):(\d{2}))|(Z))?/";
if ( preg_match( $pat, $data, $match ) ) {
list( $year, $month, $day, $hours, $minutes, $seconds) =
array( $match[1], $match[3], $match[5], $match[7], $match[9], $match[11]);

Во всяком случае он у меня примеры из спецификации парсит корректно.
В оригинале меня очень сильно смущает кучок:
(?:([-+])(\d{2}):?(\d{2})|(Z))?
Зачем вначале ? и как можно здесь объединять : и [-+] ?

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

$pat = "/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):((\d{2}))?(?:([-+])(\d{2}):?(\d{2})|(Z))?/";
?

((\d{2}))

нафига двойные скобки-то?

согласна - не нужны
стормозила
обещаю исправиться

Не матчит формат без времени (2007-12-04). Секунды матчит с : (надо было 7е скобки брать). Игнорирует timezone.

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

остальное совершенно верно!

а ошибка именно одна? ;) сразу видно, что целая часть секунд на самом деле в match[7], а может быть же еще дробная часть. ну и зону выкинули нафиг, а почему? ;)

Да, оригинально мой вопрос был про $match[7] (а не $match[6]). Но это действительно не единственная ошибка в коде — скорее, это первая очевидная.

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

  1. Отвергаются даты, конкретизированные меньше чем до минут.
  2. Отвергаются даты, конкретизированные до долей секунды.
  3. Допускаются произвольные месяцы, дни, часы, минуты, секунды и смещения от UTC, а не только реально существующие в природе.
  4. Допускаются смещения, записанные без двоеточия, в нарушение стандарта (фича для совместимости с ISO 8601? Почему только в смещении?)
  5. Допускается произвольный мусор до и после даты.
  6. В поле секунд попадёт также двоеточие-разделитель (видимо, это авторский ответ).
  7. (стиль) Левую альтернативу смещения от UTC для ясности приоритетов стоило бы заскобочить.
  8. perldoc perlre и perldoc perlunicode не определяют \d достаточно чётко, чтобы можно было утверждать, что туда не попадут цифры, отличные от [0-9]. (Я параноик?)

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

убрал. не успел просто.

Проблемы конкретизации решаются добавлением скобок (с соответствующим изменением индексов подвыражений) или (?:…)? в правильных местах.

Проблема валидации полей решается дополнительным if’ом внутри. Потому что мы все знаем, что происходит, когда пытаются валидировать всё и вся только регекспом.

Проблема двоеточия в смещении — решается или не решается после анализа тех данных, которые будут через это прогоняться. Если данных без двоеточия много, оставляем как есть. Если мало — добавляем в образец (:)? и warning. Если доказано, что их быть не должно — втыкаем в образец двоеточие.

Проблема мусора решается заключением образца в ^…$.

Проблема двоеточия в секундах решается заменой $match[6] на $match[7] или шестых скобок (…) на скобки без захвата (?:…).

Пункты 7–8 содержат решение прямым текстом. Rationale к пункту 7 — разные движки регулярных выражений имеют разные понятия, что приоритетнее, альтернатива или конкатенация.

В коде сформулирую чуть позже.

function is_leap_year($year)
{
    return (($year % 400) == 0) || ((($year % 100) != 0) && (($year % 4) == 0));
}

$month_days = array(0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
function month_days($year, $month)
{
    global $month_days;
    return $month_days[$month] + (($month == 2 && is_leap_year($year)) ? 1 : 0);
}

// returns false if $s is not a valid W3C date/time,
// or an array of date/time elements:
//   0  year    int 0 to 9999
//   1  month   NULL or int 1 to 12
//   2  day     NULL or int 1 to 31
//   3  hour    NULL or int 0 to 23
//   4  minute  NULL or int 0 to 59
//   5  second  NULL or float [0, 60)
//   6  zone    NULL or int offset from UTC in minutes
function parse_datetime($s)
{
    //            1             2             3
    $pattern = "/^([0-9]{4})(?:-([0-9]{2})(?:-([0-9]{2})" .
    //       4          5             6
        "(?:T([0-9]{2}):([0-9]{2})(?::([0-9]{2}(?:\.[0-9]+)?))?" .
    //   7     89               10
        "(Z|(?:(([-+])[0-9]{2}):([0-9]{2}))))?)?)?$/";
    if (!preg_match($pattern, $s, $matches)) return false;

    array_shift($matches);
    list($year, $month, $day, 
        $hour, $minute, $second, 
        $zone, $zone_sign, $zone_hours, $zone_minutes) = $matches;

    $result = array(NULL, NULL, NULL, NULL, NULL, NULL, NULL);

    $year = intval($year);
    $result[0] = $year;

    if (!isset($month)) return $result;
    $month = intval($month);
    if ($month < 1 || $month > 12) return false;
    $result[1] = $month;

    if (!isset($day)) return $result;
    $day = intval($day);
    if ($day < 1 || $day > month_days($year, $month)) return false;
    $result[2] = $day;

    if (!isset($hour)) return $result;
    $hour = intval($hour);
    if ($hour < 0 || $hour > 23) return false;
    $minute = intval($minute);
    if ($minute < 0 || $minute > 59) return false;
    $zone_hours = intval($zone_hours);
    $zone_minutes = intval($zone_minutes);
    // Insert zone validation here -- IMO matching with a static list is too strict,
    // and even checking that -12 <= $zone_hours <= +14 is not entirely correct
    if ($zone_minutes < 0 || $zone_minutes > 59) return false;

    $result[3] = $hour;
    $result[4] = $minute;
    $result[6] = $zone_hours * 60 + intval($zone_sign . $zone_minutes);

    if (!isset($second)) return $result;
    $second = (float)($second);
    if ($second < 0 || $second >= 60) return false;
    $result[5] = $second;

    return $result;
}

Писать регексп так, чтоб не пропускал 2007-02-29, но пропускал 2007-02-28 смысла не вижу. Это проще проверить потом.

Разницы же между 36м января и 29м февраля 2007 года нету.

list( $year, $month, $day, $hours, $minutes, $seconds) =
array( $match[1], $match[2], $match[3], $match[4], $match[5], $match[7]);
1 либо меня регексп чтобы $match[6] хранила секунды без ":"
2 либо использовать в list $match[7] - чтобы получить значение секунд

теперь правильно :)

Ознакомился с комментариями.
Мозг взорван, спасибо.
:)