PHP: делаем калькулятор дат и подсчёт дней по-современному
В этом старом скрипте я всё считал вручную. В современном PHP есть удобные классы для работы с датами и интервалами дат.
В качестве первого примера смоделируем скрипт по ссылке выше, не забудем и про определение дней недели.
Нам понадобятся 2 объекта класса DateTime
- $dstart
для начальной даты периода, $dend
для конечной.
Начальной датой по умолчанию будем считать сегодняшнюю, а конечной - 31 декабря текущего года. Если же сегодня и есть 31 декабря, прибавим к году единичку. Кроме того, если даты начала и конца периода уже были переданы скрипту методом $_POST
, их нужно записать в $dstart
и $dend
вместо значений по умолчанию.
Когда значения дат настроены, остаётся вычислить интервал между ними методом DateTime::diff. При этом мы вычисляем интервал абсолютным, то есть, разрешаем конечную дату делать меньше начальной.
Вот какой кусочек кода вышел:
<?php $dstart = new DateTime(); if (isset($_POST['date1'])) $dstart = new DateTime($_POST['date1']); list ($year,$month,$day) = explode ('-',$dstart->format('Y-m-d')); if ($month==12 and $day==31) $year++; $dend = new DateTime($year.'-12-31'); if (isset($_POST['date2'])) $dend = new DateTime($_POST['date2']); $interval = $dstart->diff($dend,true); ?>
Затем выведем форму, используя современную компоненту <input type="date">
, появившуюся в HTML5.
К сожалению, эту компоненту с удобным встроенным календариком и встроенной проверкой корректности данных до сих пор поддерживают не все браузеры. На данный момент всё сработает в Chrome 20+, Opera 10.1+, Android 4.4+, а вот в Internet Explorer или Firefox ничего не выйдет.
<p><form method="post"> Начальная дата: <input type="date" name="date1" value="<?php echo $dstart->format('Y-m-d'); ?>" autofocus/> Конечная дата: <input type="date" name="date2" value="<?php echo $dend->format('Y-m-d'); ?>"/> <input type="submit" value="Расчёт"/> </form></p>
Даты передаются компоненте всегда в формате ГГГГ-ММ-ДД
.
После формы основная часть скрипта использует найденный $interval
для форматирования и печати данных.
Про форматы для DateTime::format
говорится тут, они те же самые. Служебный метод get_word_form
помогает правильно склонять слова "дней", "лет" и т.п.
<?php $weekdays = Array ('Вс','Пн','Вт','Ср','Чт','Пт','Сб'); if (isset($_POST['date1']) and isset($_POST['date2'])) { echo '<p>Период: '. $dstart->format('d.m.Y').', '.$weekdays[$dstart->format('w')]. ' - '.$dend->format('d.m.Y').', '.$weekdays[$dend->format('w')].'</p>'; $a = $interval->format('%a'); echo '<p>'.$a.' '.get_word_form ($a,'д','ень','ня','ней').'; '; $y = $interval->format('%y'); echo $y.' '.get_word_form ($y,'','год','года','лет').', '; $m = $interval->format('%m'); echo $m.' '.get_word_form ($m,'месяц','','а','ев').', '; $d = $interval->format('%d'); echo $d.' '.get_word_form ($d,'д','ень','ня','ней').'</p>'; } function get_word_form ($n,$word,$o1,$o2,$o5) { //Получить нормальное окончание для слова $word и числа объектов $n. //Параметры: //$o1 - окончание для числа *1, //$o2 - окончание для *2-*4, //$o5 - окончание для чисел *5-*9,*0,*11-*19 if ($n%100>10 and $n%100<20 or $n%10==0 or $n%10>4) $word.=$o5; else if ($n%10>1 and $n%10<5) $word.=$o2; else $word.=$o1; return $word; } ?>
Этому скрипту требуется PHP версии не ниже 5.3, но надёжнее будет 5.3.13 и выше, помнится, в 5.3.5 находились баги с интервалами, а в 5.3.13 и 5.4.X вроде бы, таковых не обнаружено. Кодировка файла предполагается Юникод (UTF-8).
Кстати, если бы мы захотели напечатать вcе даты найденного периода "в столбик", это можно было бы сделать так (перед закрывающим тегом ?>
):
//Распечатать список дат $interval = new DateInterval('P1D'); $period = ($day<0 ? new DatePeriod ($dend, $interval, $dstart) : new DatePeriod ($dstart, $interval, $dend)); foreach ($period as $dt) echo '<br>'.$dt->format('d-m-Y');
Все форматы, применимые для интервалов дат, можно посмотреть тут.
С точки зрения программирования в коде также поучительно, что
объекты нельзя "просто присваивать" по типу $dend = $dstart
, присвоится по ссылке. Про клонирование объектов см., например, здесь.
Теперь напишем калькулятор, позволяющий прибавить к заданной дате положительное или отрицательное количество дней, как в этом старом скрипте.
Скрипт может принимать методом $_POST
дату и количество дней $day
, по умолчанию ставится текущая дата, а $day=0
.
Обратите внимание, что для интервала $interval
нужно в любом случае задать положительное количество дней, а "перевернуть" расчёт, считая даты от большей к меньшей, позволит свойство invert
компоненты DateInterval
.
<?php $dstart = new DateTime(); if (isset($_POST['date'])) $dstart = new DateTime( $_POST['date']); $day = ((isset($_POST['day']) and is_numeric($_POST['day'])) ? intval($_POST['day']) : 0); $dend = new DateTime($dstart->format('Y-m-d')); $interval = new DateInterval('P'.abs($day).'D'); if ($day<0) $interval->invert = 1; $dend->add ($interval); ?>
Форма, кроме компоненты <input type="date">
, будет содержать и специализированную компоненту для ввода числового значения <input type="number">
, её поддерживают также не все браузеры, хотя и побольше, чем для даты.
При этом Android для типа number
на момент написания заметки не поддерживает атрибуты step
, min
и max
.
<p><form method="post"> Начальная дата: <input type="date" name="date" value="<?php echo $dstart->format('Y-m-d'); ?>" autofocus/> <input type="number" name="day" placeholder="Сколько дней?" min="-100000" max="100000" style="width: 9em;" <?php echo $day==0?'':'value="'.$day.'"'; ?>/> <input type="submit" value="Расчёт"/> </form></p>
Часть скрипта после кода формы окажется совсем простой, мы лишь отформатируем и выведем данные из дат $dstart
и $dend
.
<?php $weekdays = Array ('Вс','Пн','Вт','Ср','Чт','Пт','Сб'); echo '<p>Период: '. $dstart->format('d.m.Y').', '.$weekdays[$dstart->format('w')]. ' - '.$dend->format('d.m.Y').', '.$weekdays[$dend->format('w')].'</p>'; ?>
26.11.2015, 14:15 [11162 просмотра]