Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Котеров Д. В., Костарев А. Ф. - PHP 5. 2-е издание (В подлиннике) - 2008

.pdf
Скачиваний:
6114
Добавлен:
29.02.2016
Размер:
11.36 Mб
Скачать

364

Часть IV. Стандартные функции PHP

Как видите, функция printf() sprintf(), кстати, тоже) округляет дробные числа

не так, как это происходит при "разворачивании" переменной в строке. Разница заметна потому, что в $time содержится очень большая величина — сотни миллионов.

В то же время, известно, что в машинной арифметике все числа хранятся прибли-

женно (числа идут с некоторой очень маленькой "гранулярностью", или шагом), и чем больше число, тем меньшая у него точность.

Вот довольно известная фраза: "В машинной арифметике между нулем и единицей находится столько же дробных чисел, сколько между единицей и бесконечностью".

Вероятно, при "разворачивании" переменной непосредственно в строку PHP выполняет с ней какие-то преобразования, в результате которых сильно падает точность (например, он может проконвертировать 8-байтовый тип double в 4-байтовый тип float). Тем не менее, в переменной $time время все же хранится с достаточной сте-

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

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

Построение строкового представления даты

string date(string $format [,int $timestamp])

Эта функция очень полезна и весьма универсальна. Она возвращает строку, отформатированную в соответствии с параметром $format и сформированную на основе

параметра $timestamp (если последний не задан — то на основе текущей даты). Строка формата может содержать обычный текст, перемежаемый одним или несколькими символами форматирования:

U — количество секунд, прошедших с полуночи 1 января 1970 года;

z — номер дня от начала года;

Y — год, 4 цифры;

y — год, 2 цифры;

F — название месяца, например, January;

m — номер месяца;

M — название месяца, трехсимвольная аббревиатура, например, Jan;

d — номер дня в месяце, всегда 2 цифры (первая может быть 0);

j — номер дня в месяце без предваряющего нуля;

w — день недели, 0 соответствует воскресенью, 1 — понедельнику, и т. д.;

l — день недели, текстовое полное название, например, Friday;

D — день недели, английское трехсимвольное сокращение, например, Fri;

a am или pm;

A AM или PM;

h — часы, 12-часовой формат;

Глава 22. Работа с датами и временем

365

H — часы, 24-часовой формат;

i — минуты;

s — секунды;

S — английский числовой суффикс (nd, th и т. д.).

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

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

выше букв.

Как видите, набор символов форматирования весьма и весьма богат. Приведем при-

мер использования функции date() — листинг 22.2.

Листинг 22.2. Файл date.php

<?php ## Вывод дат.

echo date("l dS of F Y h:i:s A")."<br>"; echo date("Сегодня d.m.Y")."<br>";

echo date("Этот файл датирован d.m.Y", filectime(__FILE__)); ?>

string strftime(string $format [,int $timestamp])

А вот и еще одна функция, предназначенная для получения текстового представления даты по значению $timestamp (если этот параметр опущен, то в качестве него берется текущее время). В отличие от date(), названия месяцев и дней недели, которые она формирует, существенно зависят от текущей выбранной локали (см. функцию setlocale(), описанную в гл. 15).

Строка $format, передаваемая этой функции, может содержать текст и спецификаторы форматирования. Однако, в отличие от функции date(), последние задаются в

виде %X, где X — одна из букв английского алфавита. Вот некоторые наиболее популярные спецификаторы форматирования:

%Y — год (например, 2004);

%y — краткое представление года (например, 04);

%m — номер месяца (от 01 до 12);

%d — число (в диапазоне от 01 до 31);

%H — часы (от 00 до 23);

%M — минуты (от 00 до 59);

%S — секунды (от 00 до 59);

%B — полное название месяца в соответствии с текущей локалью (например,

"Март");

%b — сокращенное название месяца (например, "мар");

%A — полное название дня недели (например, "понедельник");

%d — сокращенное название дня недели (например, "Пн");

366

Часть IV. Стандартные функции PHP

%c — некоторое текстовое представление даты, определяемое текущей локалью

(например, 19.02.1998 13:24:18).

Полный список спецификаторов форматирования можно найти в описании функции strftime() из документации PHP.

Если вы давно собираетесь написать детективный роман, но все никак не можете придумать, с чего же начать, попробуйте запустить скрипт из листинга 22.3. Он печатает два предложения, которые вполне могут вам подойти.

Листинг 22.3. Файл strftime.php

<? ## Использование strftime().

//Активизируем текущую локаль (иначе дата будет на английском). setlocale(LC_ALL, '');

//Выводим 2 предложения.

echo strftime("%B %Y года, %d число. Был %A, часы показывали %H:%M.");

?>

Если вы пропустите вызов setlocale(), функция strftime() вернет текст с английскими вариантами названий.

Построение timestamp

int mktime([int $hour] [,int $minute] [,int $second] [,int $month] [,int $day] [,int $year])

До сих пор мы рассматривали функции, которые преобразуют формат timestamp в представление, удобное для человека. Существует функция, которая проводит об-

ратное преобразование — mktime(). Как мы видим, все ее параметры необязательны, но пропускать их можно, конечно же, только справа налево. Если какие-то параметры не заданы, на их место подставляются значения, соответствующие текущей дате. Функция возвращает значение в формате timestamp, соответствующее указанной

дате.

Правильность даты, переданной в параметрах, не проверяется. В случае некоррект-

ной даты ничего особенного не происходит — функция "делает вид", что это ее не касается, и формирует соответствующий формат timestamp. Для иллюстрации рас-

смотрим три вызова (два из них — с ошибочной датой), которые тем не менее возвращают один и тот же результат:

echo date("M-d-Y", mktime(0,0,0,1,1,2005)); // правильная дата

echo date("M-d-Y", mktime(0,0,0,12,32,2004)); // неправильная дата

echo date("M-d-Y", mktime(0,0,0,13,1,2004)); // неправильная дата

Легко убедиться, что выводятся три одинаковых даты.

Глава 22. Работа с датами и временем

367

int strtotime(string $time [,int $timestamp])

При вызове mktime() легко перепутать порядок следования параметров и, таким об-

разом, получить неверный результат. Функция strtotime() лишена этого недостатка. Она принимает строковое представление даты в свободном формате и возвращает соответствующий формат timestamp.

Насколько же свободен этот "свободный" формат? Ведь ясно, что всех текстовых представлений учесть нельзя. Ответ на данный вопрос дает страница руководства Unix под называнием "Date input formats", которая легко находится в любой поисковой системе по ее названию — например,

http://www.google.com.ru/search?q=Date+input+formats.

В листинге 22.4 приведен сценарий, проверяющий, как функция strtotime() вос-

принимает строковые представления некоторых дат. Результат выводится в виде таб-

лицы, в которой отображается timestamp, а также заново построенное по этому time- stamp-формату строковое представление даты.

Листинг 22.4. Файл strtotime.php

<?php ## Использование функции strtotime(). $check = array(

"now",

"10 September 2000", "+1 day",

"+1 week",

"+1 week 2 days 4 hours 2 seconds", "next Thursday",

"last Monday", ); ?>

<table width="100%"> <tr align="left">

<th>Входная строка</th> <th>Timestamp</th> <th>Получившаяся дата</th> <th>Сегодня</th>

</tr>

<?foreach ($check as $str) {?> <tr>

<td><?=$str?></td> <td><?=$stamp=strtotime($str)?></td> <td><?=date("Y-m-d H:i:s", $stamp)?></td> <td><?=date("Y-m-d H:i:s", time())?></td>

</tr>

<?}?>

</table>

В табл. 22.1 приведены примеры результатов работы этого скрипта.

368

 

Часть IV. Стандартные функции PHP

 

Таблица 22.1. Результаты вызова strtotime() для некоторых дат

 

 

 

 

 

 

 

Входная строка

Timestamp

Получившаяся дата

Сегодня

 

 

 

 

 

 

 

now

1080160854

2004-03-24 23:40:54

2004-03-24 23:40:54

 

 

10 September 2000

968529600

2000-09-10 00:00:00

2004-03-24 23:40:54

 

 

+1 day

1080247254

2004-03-25 23:40:54

2004-03-24 23:40:54

 

 

+1 week

1080762054

2004-03-31 23:40:54

2004-03-24 23:40:54

 

 

+1 week 2 days 4 hours 2 seconds

1080949256

2004-04-03 03:40:56

2004-03-24 23:40:54

 

 

next Thursday

1080766800

2004-04-01 01:00:00

2004-03-24 23:40:54

 

 

last Monday

1079902800

2004-03-22 00:00:00

2004-03-24 23:40:54

 

 

 

 

 

 

 

Разбор timestamp

array getdate(int $timestamp)

Возвращает ассоциативный массив, содержащий данные об указанном времени. В массив будут помещены следующие ключи и значения:

seconds — секунды;

minutes — минуты;

hours — часы;

mday — число;

wday — день недели (0 означает воскресенье, 1 — понедельник, и т. д.);

mon — номер месяца;

year — год;

yday — номер дня с начала года;

weekday — полное название дня недели, например, Friday;

month — полное название месяца, например, January.

В общем-то, всю эту информацию можно получить и с помощью функции date(), но тут разработчики PHP предоставляют нам альтернативный способ.

Григорианский календарь

Григорианский календарь — это как раз тот самый календарь, который мы постоянно используем в своей жизни. В России он был введен Петром I в 1700 году.

Описываемые далее три функции представляют большой интерес, если вам понадо-

бится автоматически формировать календари в сценариях. Все они имеют дело с так называемым форматом Julian Day Count (JDC). Что это такое?

Каждой дате соответствует свой JDC. Ведь, фактически, JDC — это всего лишь ко-

личество дней, прошедших с определенной даты (с 4714 года до н. э.).

Глава 22. Работа с датами и временем

369

Зачем это нужно? Например, нам заданы две даты в формате дд.мм.гггг. Нужно вы-

числить количество дней между этими датами. Поставленная задача как раз легко решается через перевод обеих дат в JDC и определение разности получившихся ве-

личин.

Вы могли бы подумать, что для этих целей можно использовать и функцию mktime() — сформировать timestamp, а затем работать с ним. К сожалению, это не так. Вспомните, что timestamp содержит время в секундах, начиная с 1 января 1970 года. Получить данные за более ранние периоды времени (или, наоборот, за 3000-й год) нельзя.

int GregorianToJD(int $month, int $day, int $year)

Преобразует дату в формат JDC. Допустимые значения года для григорианского ка-

лендаря — от 4714 года до н. э. до 9999 года н. э.

string JDToGregorian(int $julianday)

Преобразует дату в формате JDC в строку, выглядящую как месяц/число/год. Навер-

няка затем вы захотите разбить эту строку на составляющие, чтобы работать с ними по отдельности. Для этого воспользуйтесь функцией explode():

$jd = GregorianToJD(10, 11, 1970); echo "$jd<br>";

$gregorian = JDToGregorian($jd); echo "$gregorian<br>";

$list = explode($gregorian, "/");

mixed JDDayOfWeek(int $julianday, int $mode=0)

Последняя функция этой серии — JDDayOfWeek() — возвращает день недели, на который приходится указанная JDC-дата. Фактически, это единственное, чего нам не

хватало бы для формирования календарика. Параметр $mode задает, в каком виде

должен быть возвращен результат:

0 — номер дня недели (0 — воскресенье, 1 — понедельник, и т. д.);

1 — английское название дня недели;

2 — сокращение английского названия дня недели.

В PHP существует еще множество функций для работы с другими календарями — в том числе с республиканским, юлианским и т. д. Если в обозримом будущем вы не собираетесь изобретать машину времени, вряд ли они вам когда-нибудь пригодятся.

Проверка даты

int checkdate(int $month, int $day, int $year)

Эта функция проверяет, существует ли дата григорианского календаря, переданная

ей в параметрах: вначале идет месяц, затем — день, и, наконец, — год.

370

Часть IV. Стандартные функции PHP

Конкретнее, checkdate() проверяет следующее:

год должен быть между 1900 и 32 767 включительно;

месяц обязан принадлежать диапазону от 1 до 12;

число должно быть допустимым для указанного месяца и года (если год високосный).

Функция очень полезна, например, при автоматическом формировании HTML-ка-

лендарика для указанного месяца и года. В самом деле, мы можем определить, какие числа в месяце "не существуют", и для них вместо номера проставить пустое место.

Календарик

Ну, мы уже столько говорили про формирование календаря, что пора бы и привести

код, который это делает.

В листинге 22.5 представлен скрипт, использующий многие функции из тех, что

были описаны выше. Он выводит в браузер календарь на текущий месяц. Программа разделена на две логические части:

функция формирования календаря за указанный месяц указанного года. Не дела-

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

вращает результат в виде двумерного массива (таблицы);

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

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

Листинг 22.5. Файл calendar.php

<?php ## Календарь на текущий месяц.

//Функция формирует двумерный массив, представляющий собой

//календарь на указанный месяц и год. Массив состоит из строк,

//соответствующих неделям. Каждая строка — массив из семи

//элементов, которые равны числам (или пустой строке, если

//данная клетка календаря пуста).

function makeCal($year, $month) {

//Получаем номер дня недели для 1 числа месяца. Корректируем

//его, чтобы воскресенье соответствовало числу 7, а не числу 0. $wday = JDDayOfWeek(GregorianToJD($month, 1, $year), 0);

if ($wday == 0) $wday = 7;

//Начинаем с этого числа в месяце (если меньше нуля

//или больше длины месяца, тогда в календаре будет пропуск).

$n = — ($wday — 2);

$cal = array();

Глава 22. Работа с датами и временем

371

// Цикл по строкам.

for ($y=0; $y<6; $y++) {

//Будущая строка. Вначале пуста. $row = array();

$notEmpty = false;

//Цикл внутри строки по дням недели. for ($x=0; $x<7; $x++, $n++) {

//Текущее число больше 0 и меньше длины месяца? if (checkdate($month, $n, $year)) {

//Да. Заполняем клетку.

$row[] = $n; $notEmpty = true;

}else {

// Нет. Клетка пуста.

$row[] = "";

}

}

//Если в данной строке нет ни одного непустого элемента,

//значит, месяц закончился.

if (!$notEmpty) break;

// Добавляем строку в массив. $cal[] = $row;

}

return $cal;

}

// Формируем календарь на текущий месяц. $now = getdate();

$cal = makeCal($now['year'], $now['mon']-1); ?> <!-- Шаблон вывода календаря. -->

<table border=1> <tr>

<td>Пн</td>

<td>Вт</td>

<td>Ср</td>

<td>Чт</td>

<td>Пт</td>

<td>Сб</td>

<td style="color:red">Вс</td> </tr>

<!-- цикл по строкам --> <?foreach ($cal as $row) {?>

<tr>

<!-- цикл по столбцам --> <?foreach ($row as $i=>$v) {?>

<!-- воскресенье — "красный" день --> <td style="<?=$i==6? 'color:red' : ''?>">

<?=$v? $v : " "?> </td>

372

Часть IV. Стандартные функции PHP

<?}?>

</tr>

<?}?>

</table>

Дата и время по Гринвичу

До сих пор мы рассматривали так называемое локальное время — его показывают часы в том часовом поясе, где работает сервер.

Представим, что вы находитесь, например, во Владивостоке, а ваш хостинг-

провайдер — в Москве. Вы заметите, что выдаваемое функциями time() и date()

время отличается от времени Владивостока на несколько часов. Это происходит потому, что скрипт ориентируется на текущее время сервера, а не на местное время

пользователя, запустившего сценарий из браузера.

Время по GMT

Для того чтобы не путаться в часовых поясах, придумали специальный формат вре-

мени — гринвичский (Greenwich Mean Time, GMT; еще одна аббревиатура, обозначающая то же самое — UTC). Время по Гринвичу — это то время, которое в на-

стоящий момент показывают часы в г. Гринвич (это в Англии). Там же проходит

знаменитый "нулевой меридиан".

Для обозначения времени в других часовых поясах принята запись GMT +ЧЧ00 или GMT +ЧЧ:00, где ЧЧ — разница времени в часах. Например, обозначение Москвы —

GMT +0300. Это означает, что время в Москве на 3 часа отличается от времени нуле-

вого меридиана (в большую сторону).

Выше мы рассматривали функции, "ничего не знающие" о текущем часовом поясе. Эти функции всегда работают так, будто бы мы находимся в г. Гринвич, и не делают

поправки на разность времени.

В PHP существует ряд функций, которые принимают в параметрах локальное время и

возвращают различным образом оформленные даты, которые в текущий момент актуальны на нулевом меридиане. Это, например, функция gmdate(), предназначенная

для получения строкового представления даты по GMT, или функция gmmktime(), создающая timestamp-формат по указанной ей локальной дате.

Хранение абсолютного времени

К сожалению, все эти функции на практике оказываются неудобными. Чтобы это

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

При этом в базе данных сохраняется текущее время и введенный текст. Так как пользователи заходят на сервер из разных стран, в базе нужно хранить timestamp-

формат по GMT-формату, а не локальный timestamp. При выводе же даты в браузер

пользователя следует учитывать его часовой пояс и проводить соответствующую корректировку времени.

Глава 22. Работа с датами и временем

373

Пусть, например, сервер расположен в Москве. Скрипт для добавления сообщения

был запущен кем-то в 4 часа ночи. Так как Москва — это GMT +03:00, в базе данных

сохранится отметка: текст добавлен в 01 час ночи по Гринвичу.

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

Через некоторое время на форум-сервер заглянул пользователь из Токио (GMT +09:00). Скрипт вывода сообщения определяет его временное смещение, извлекает из базы данных время по GMT (а это 1 час ночи, напоминаем) и добавляет 9 часов разницы. Получается 10 часов утра. Эта дата и выводится в браузер.

Рассмотрим, какие действия нам нужно совершить для реализации этого алгоритма:

1.Получение текущего timestamp-формата по GMT. Именно этот timestamp в настоящий момент "показывают часы" в Гринвиче. Мы будем сохранять это время

вбазе данных.

2.Получение текстового представления даты, если известен GMT-timestamp и це-

левая часовая зона (которая, конечно, отлична от GMT). Оно будет распечатано

вбраузере пользователя.

Ксожалению, ни одна стандартная функция PHP не может справиться одновременно с обеими задачами! Действительно, функции gmtime() (по аналогии c time()) не существует. Функция gmdate(), хоть и имеет префикс gm, выполняет обратную операцию: возвращает текстовое представление даты по GMT, зная локальное время (а там нужно — наоборот).

Перевод времени

Будем решать проблемы по мере их поступления. Для начала посмотрим, как же можно получить время по GMT, зная только локальный timestamp. Напишем для этого функцию local2gm(), приведенную в листинге 22.6.

Листинг 22.6. Файл gm.php

<?php ## Работа с временем по GMT.

//Вычисляет timestamp в Гринвиче, который соответствует

//локальному timestamp-значению.

function local2gm($localStamp=false) {

if ($localStamp === false) $localStamp = time();

//Получаем смещение часовой зоны в секундах. $tzOffset = date("Z", $localStamp);

//Вычитаем разницу — получаем время по GMT. return $localStamp — $tzOffset;

}

//Вычисляет локальный timestamp в Гринвиче, который

//соответствует timestamp-значению по GMT.

Тут вы можете оставить комментарий к выбранному абзацу или сообщить об ошибке.

Оставленные комментарии видны всем.