БлогNot. PHP: делаем калькулятор дат и подсчёт дней по-современному

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 [10994 просмотра]


теги: html php дата

показать комментарии (1)