Чтение и запись символов Unicode

Задача
Необходимо читать символы в кодировке Unicode из файла, базы данных или формы; или требуется записать символы в кодировке Unicode.

Решение
Однобайтовые символы в кодировке ISO-8859-1 преобразуются в код UTF-8 с помощью функции utf8_encode():

print utf8_encode('Kurt Gdel is swell.');


Функция utf8_decode() используется для преобразования символов в кодировке UTF-8 в однобайтовые символы в кодировке ISO-8859-1:

print utf8_decode("Kurt G\xc3\xb6del is swell.");


Обсуждение
Существует 256 ASCII-символов. Символы с кодами от 0 до 127 стандартизованы: управляющие символы, буквы и числа, символы пунк-туации. Однако существуют различные правила для символов с кодами от 128 до 255. Одна из кодировок, называемая ISO-8859-1, охватывает символы, необходимые для письма на большинстве европейских
языков, например

Добавлено: 22 Июля 2018 15:37:47 Добавил: Андрей Ковальчук

Расширение gettext

Задача
Необходима комплексная система для работы с каталогами сообщений.

Решение
Задействуйте расширение PHP gettext, позволяющее применять утилиты GNU gettext:

bindtextdomain('gnumeric','/usr/share/locale');
textdomain('gnumeric');
$languages = array('en_CA','da_DK','de_AT','fr_FR');
foreach ($languages as $language) {
setlocale(LC_ALL, $language);
print gettext(" Unknown formula")."\n";
}


Обсуждение
Расширение gettext представляет собой набор инструментов, облегчающих генерацию сообщений на разных языках в приложении. Компиляция PHP с параметром --with-gettext открывает доступ к функциям, позволяющим извлекать текст из каталогов сообщений в формате gettext; кроме того, существует множество внешних инструментов редактирования каталогов сообщений.

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

bindtextdomain('gnumeric','/usr/share/locale')


означает, что каталог сообщений для домена gnumeric в локали en_CA находится в файле /usr/share/locale/en_CA/LC_MESSAGES/gnumeric.mo.

Функция textdomain('gnumeric') устанавливает в качестве домена по умолчанию домен gnumeric. Вызов gettext() извлекает сообщение из домена по умолчанию. Существуют и другие функции, например dgettext(), позволяющие извлекать сообщения из различных доменов.

Когда вызывается функция gettext() (или dgettext()), она возвращает требуемое сообщение для текущей локали. Если в каталоге текущейлокали нет сообщения, которое соответствовало бы переданному ей аргументу, то функция gettext() (или dgettext()) просто возвращает сам аргумент. В результате программа печатает все почему-либо не переведенные сообщения на английском языке (или на любом языке, который является базовым).

Установка домена по умолчанию с помощью функции textdomain() делает каждое последующее извлечение сообщения из домена более кратким, поскольку вместо вызова dgettext('domain','Good morning') достаточно сделать вызов gettext('Good morning'). Однако если даже строка gettext('Good morning') выглядит слишком длинной, то можно воспользоваться преимуществом недокументированного синонима _() для функции gettext(). Вместо gettext('Good morning') можно написать _('Good morning').

На веб-сайте расширения gettext можно найти полезную и подробную информацию по управлению потоком данных между программистами и переводчиками и о том, как эффективно использовать расширение gettext. Сайт также содержит информацию о других инструментах, позволяющих управлять каталогами сообщений, например о специальном режиме GNU Emacs.

Добавлено: 22 Июля 2018 15:37:13 Добавил: Андрей Ковальчук

Управление ресурсами локализации

Задача
Необходимо отслеживать различные каталоги сообщений и изображения.

Решение
Есть два приема, позволяющих упростить управление ресурсами локализации. Первый заключается в создании нового объекта языка, например канадского английского, унаследованного от существующего родственного языка, такого как американский английский. Достаточно заменить слова и фразы нового объекта, отличающиеся от слов
и фраз оригинального языка.

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

Обсуждение
Программа catalog-compare.php, показанная в примере 16.2, выводит сообщения, совпадающие в обоих каталогах, а также сообщения, которые отсутствуют в одном каталоге, но присутствуют в другом.

Пример 16.2. catalog-compare.php

$base = 'pc_MC_'.$_SERVER['argv'];
$other = 'pc_MC_'.$_SERVER['argv'];require 'pc_MC_Base.php';
require "$base.php";
require "$other.php";
$base_obj = new $base;
$other_obj = new $other;
/* Проверяем сообщения другого класса, которые совпадают
* с сообщениями базового класса или они находятся
* в базовом классе, но отсутствуют в другом классе */
foreach ($base_obj->messages as $k => $v) {
if (isset($other_obj->messages[$k])) {
if ($v == $other_obj->messages[$k]) {
print "SAME: $k\n";
}
} else {
print "MISSING: $k\n";
}
}
/* Проверяем сообщения другого класса, которые
* отсутствуют в базовом классе */
foreach ($other_obj->messages as $k => $v) {
if (! isset($base_obj->messages[$k])) {
print "MISSING (BASE): $k\n";
}
}


Чтобы воспользоваться этой программой, поместите каждый объект каталога сообщений в файл с тем же именем, что и имя объекта (т. е. класс pc_MC_en_US должен находиться в файле с именем pc_MC_en_US.php, а класс pc_MC_es_US – в файле pc_MC_es_US.php). Затем вызовите программу из командной строки с двумя именами локалей в качестве аргументов:
% php catalog-compare.php en_US es_US

В контексте веб-разработки целесообразным может оказаться использование отдельной локали и каталога сообщений для каждого запроса.

Локаль может поступить из броузера (в заголовке Accept-Language) или может быть установлена в явном виде сервером (для отображения одного и того же содержания на разных языках могут быть установлены разные виртуальные хосты). Если для выбора каталога сообщений для каждого запроса требуется один и тот же код, то можно реализовать класс каталога сообщений следующим образом:

$classname = "pc_MC_$locale.php";
require 'pc_MC_Base.php';
require $classname.'.php';
$MC = new $classname;

Добавлено: 22 Июля 2018 15:34:17 Добавил: Андрей Ковальчук

Локализация включаемых файлов

Задача
Необходимо включить в страницу файлы, имеющие локальные особенности.

Решение
Определив соответствующую локаль, измените include_path:

$base = '/usr/local/php-include';
$LANG = 'en_US';
$include_path = ini_get('include_path');
ini_set('include_path',"$base/$LANG:$base/global:$include_path");


Обсуждение
Переменная $base содержит имя базового каталога включаемых локализованных файлов. Файлы, не имеющие локальных особенностей, попадают в глобальный каталог, указанный в переменной $base, а файлы, имеющие такие особенности, размещаются в подкаталогах, названных в соответствии со своими локалями (т. е. en_US). Первым в числе путей, ведущих к включенным файлам, указывается каталог, соответствующий локали, а вторым – глобальный каталог; именно в эти два места обращается PHP в поисках включаемого файла. Такойпорядок указания каталогов гарантирует, что нелокализованная информация будет загружена только в том случае, если локализованные данные окажутся недоступными.

Похожий способ применяется в функции img() из рецепта 16.7. Однако в данном случае можно воспользоваться преимуществом директивы PHP include_path, позволяющей реализовать автоматический поиск каталогов. Чтобы добиться максимальной выгоды, переустановите include_path в своей программе как можно раньше, лучше всего в начале файла, загружаемого с помощью auto_prepend_file при каждом вызове.

Добавлено: 22 Июля 2018 15:33:42 Добавил: Андрей Ковальчук

Локализация изображений

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

Решение
Создайте каталог изображений для каждой локали, которую собираетесь поддерживать, а также глобальный каталог для изображений, не содержащих информации, специфической для локали. Создайте копии каждого изображения со специфической для локали информацией в соответствующем этой локали каталоге. Убедитесь, что имена файлов изображений одинаковы в различных каталогах. Не выводите изображение непосредственно по его URL, а воспользуйтесь функцией-оболочкой, подобной функции msg() из рецепта 16.4, которая печатает специфический для локали текст.

Обсуждение
Функция-оболочка img() сначала ищет версию изображения для данной локали, а затем глобальную версию. Если ни одна из версий не найдена, то она выводит сообщение в журнал ошибок:

$image_base_path = '/usr/local/www/images';
$image_base_url = '/images';
function img($f) {
global $LANG;
global $image_base_path;
global $image_base_url;
if (is_readable("$image_base_path/$LANG/$f")) {
print "$image_base_url/$LANG/$f";
} elseif (is_readable("$image_base_path/global/$f")) {
print "$image_base_url/global/$f";
} else {
error_log("l10n error: LANG: $lang, image: '$f'");
}
}


Эта функция должна знать и путь к файлу изображения в данной файловой системе ($image_base_path), и путь к изображению, отсчитываемый от базового URL вашего сайта (/images). Первый путь нужен ей для проверки возможности чтения файла, а второй – для сборки URL изображения.

Имена файлов локализованных изображений должны быть одинаковыми во всех каталогах локализации. Например, изображение, содержащее сообщение «New!» на фоне желтого звездного взрыва, должно быть названо new.gif как в каталоге images/en_US, так и в каталоге images/es_US, даже если файл images/es_US/new.gif содержит картину желтого звездного взрыва со словом « Nuevo!» на его фоне.

Не забудьте, что текст alt, указываемый в тегах изображения, также должен быть локализован. Полностью локализованный тег <img> выглядит так:

printf('<img src="%s" alt="%s">',img('cancel.png'),msg('Cancel'));


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

printf('<img src="%s" alt="%s" height="%d" width="%d">',
img('cancel.png'),msg('Cancel'),
msg('img-cancel-height'),msg('img-cancel-width'));


Локализованные сообщения для img-cancel-height и img-cancel-width – это не текстовые строки, а целые числа, описывающие размеры изображения cancel.png для каждой локали.

Добавлено: 22 Июля 2018 15:33:11 Добавил: Андрей Ковальчук

Локализация денежных значений

Задача
Необходимо показать денежную сумму в формате, соответствующем локали.

Решение
Для получения соответствующим образом отформатированной строки предназначена функция pc_format_currency(), показанная в примере 16.1. Например:

setlocale(LC_ALL,'fr_CA');
print pc_format_currency(-12345678.45);
(12 345 678,45 $)


Обсуждение
Функция pc_format_currency(), показанная в примере 16.1, получает информацию о форматировании значений валюты от функции localeconv(), а затем строит корректную строку с помощью функции number_format() и некоторой логики.

Пример 16.1. pc_format_currency
function pc_format_currency($amt) {
// получаем информацию форматирования значений валюты,
// соответствующую локали
$a = localeconv();
// вычисляем знак переменной $amt, а затем удаляем ее
if ($amt < 0) { $sign = -1; } else { $sign = 1; }
$amt = abs($amt);
// форматируем переменную $amt с соответствующей группировкой,
// десятичной точкой и дробными значениями
$amt = number_format($amt,$a['frac_digits'],$a['mon_decimal_point'],
$a['mon_thousands_sep']);// определяем местоположение символа валюты
// и положительного или отрицательного знаков
$currency_symbol = $a['currency_symbol'];
// is $amt >= 0 ?
if (1 == $sign) {
$sign_symbol = 'positive_sign';
$cs_precedes = 'p_cs_precedes';
$sign_posn = 'p_sign_posn';
$sep_by_space = 'p_sep_by_space';
} else {
$sign_symbol = 'negative_sign';
$cs_precedes = 'n_cs_precedes';
$sign_posn = 'n_sign_posn';
$sep_by_space = 'n_sep_by_space';
}
if ($a[$cs_precedes]) {
if (3 == $a[$sign_posn]) {
$currency_symbol = $a[$sign_symbol].$currency_symbol;
} elseif (4 == $a[$sign_posn]) {
$currency_symbol .= $a[$sign_symbol];
}
// символ валюты в начале
if ($a[$sep_by_space]) {
$amt = $currency_symbol.' '.$amt;
} else {
$amt = $currency_symbol.$amt;
}
} else {
// символ валюты после суммы
if ($a[$sep_by_space]) {
$amt .= ' '.$currency_symbol;
} else {
$amt .= $currency_symbol;
}
}
if (0 == $a[$sign_posn]) {
$amt = "($amt)";
} elseif (1 == $a[$sign_posn]) {
$amt = $a[$sign_symbol].$amt;
} elseif (2 == $a[$sign_posn]) {
$amt .= $a[$sign_symbol];
}
return $amt;


Код в функции pc_format_currency(), который ставит символ валюты и знак на правильное место, почти одинаков для положительных и отрицательных сумм; он лишь использует различные элементы массива, возвращенного функцией localeconv(). Соответствующие элементы массива, возвращаемого функцией localeconv().

В C-библиотеке есть функция с именем strfmon(), которая делает для валюты то же самое, что функция strftime() делает для дат и времени, однако в PHP она не реализована. Наша функция pc_format_currency() предоставляет большинство из этих возможностей.

Добавлено: 22 Июля 2018 15:32:34 Добавил: Андрей Ковальчук

Локализация дат и времени

Задача
Необходимо отображать даты и значения времени в соответствии с настройками локали.

Решение
Это делается при помощи строки формата %c функции strftime():

print strftime('%c');


Можно также хранить строки формата функции strftime() как сообщения в каталоге сообщений:

$MC = new pc_MC_es_US;
print strftime($MC->msg('%Y-%m-%d'));


Обсуждение
Строка формата %c функции strftime() задает возвращаемое этой функцией представление даты и времени, предпочтительное для текущей локали. Ниже показан наиболее быстрый способ форматирования строки времени в соответствии с текущими настройками:

print strftime('%c');


Этот фрагмент кода генерирует множество результатов:

Tue Aug 13 18:37:11 2002 // для локали C по умолчанию
mar 13 ago 2002 18:37:11 EDT // для локали es_US
mar 13 aoы 2002 18:37:11 EDT // для локали fr_FR


Форматированная строка времени, полученная с помощью %c, хотя и соответствует локали, не очень гибкая. Например, если требуется только время, лучше передать другую строку формата функции strftime().

Хуже то, что и сами строки задания формата варьируются от локали к локали. В некоторых локалях значения часов отображаются в диапазоне от 1 до 12 с уточнением A.M./P.M., а в других они должны изменяться от 0 до 23. Чтобы строки времени выводились корректно именно для данной локали, включите нужные элементы в массив $messages для каждого формата времени. Ключ определенного формата времени, например %H:%M, для всех локалей будет одинаков. А вот значение может изменяться, например %H:%M для 24-часовых локалей или %I:%M %P для 12-часовых локалей. Затем найдите соответствующую строку формата и передайте ее функции strftime():$MC = new pc_MC_es_US;
print strftime($MC->msg('%H:%M'));

Смена локали не изменяет часовой пояс, она меняет только форматирование показываемого результата.

Добавлено: 22 Июля 2018 15:31:55 Добавил: Андрей Ковальчук

Программа: Календарь

Функция pc_calendar(), выводит календарь на месяц, подобно UNIX-функции cal. Пример:

// печать календаря для текущего месяца
list($month,$year) = explode(',',date('m,Y'));
pc_calendar($month,$year);


Функция pc_calendar() выводит таблицу, содержащую календарь на месяц. Календарь содержит ссылки на предыдущий и последующий месяцы и выделяет текущий день.

<?php
function pc_calendar($month,$year,$opts = '') {
// установка опций по умолчанию //
if (! is_array($opts)) { $opts = array(); }
if (! isset($opts['today_color'])) { $opts['today_color'] = '#FFFF00'; }
if (! isset($opts['month_link'])) {
$opts['month_link'] =
'(a href="'.$_SERVER['PHP_SELF'].'?month=%d&year=%d">%s(/a)';
}
list($this_month,$this_year,$this_day) = split(',',strftime('%m,%Y,%d'));
$day_highlight = (($this_month == $month) && ($this_year == $year));
list($prev_month,$prev_year) =
split(',',strftime('%m,%Y',mktime(0,0,0,$month-1,1,$year)));
$prev_month_link = sprintf($opts['month_link'],
$prev_month,$prev_year,'<');
list($next_month,$next_year) =
split(',',strftime('%m,%Y',mktime(0,0,0,$month+1,1,$year)));
$next_month_link = sprintf($opts['month_link'],
$next_month,$next_year,'>');
?>
<table border="0" cellspacing="0" cellpadding="2" align="center">
<tr>
<td align="left">
<?php print $prev_month_link ?>
</td>
<td colspan="5" align="center">
<?php print strftime('%B %Y',mktime(0,0,0,$month,1,$year)); ?>
</td>
<td align="right">
<?php print $next_month_link ?>
</td>
</tr>
<?php
$totaldays = date('t',mktime(0,0,0,$month,1,$year));
// выводим дни недели
print '<tr>';
$weekdays = array('Su','Mo','Tu','We','Th','Fr','Sa');
while (list($k,$v) = each($weekdays)) {
print '<td align="center">'.$v.'</td>';
}
print '</tr><tr>';
// выравниваем первый день месяца по соответствующему дню недели
$day_offset = date("w",mktime(0, 0, 0, $month, 1, $year));
if ($day_offset > 0) {
for ($i = 0; $i < $day_offset; $i++) { print '<td> </td>'; }
}
$yesterday = time() - 86400;
// выводим дни
for ($day = 1; $day <= $totaldays; $day++) {
$day_secs = mktime(0,0,0,$month,$day,$year);
if ($day_secs >= $yesterday) {
if ($day_highlight && ($day == $this_day)) {
print sprintf('<td align="center" bgcolor="%s">%d</td>',
$opts['today_color'],$day);
} else {
print sprintf('<td align="center">%d</td>',$day);
}
} else {
print sprintf('<td align="center">%d</td>',$day);
}
$day_offset++;
// начинаем новую строку каждую неделю //
if ($day_offset == 7) {
$day_offset = 0;
print "</tr>\n";
if ($day < $totaldays) { print '<tr>'; }
}
}
// заполнение последней недели пробелами //
if ($day_offset > 0) { $day_offset = 7 - $day_offset; }
if ($day_offset > 0) {
for ($i = 0; $i < $day_offset; $i++) { print '<td> </td>'; }
}
print '</tr></table>';
}
?>


Функция pc_calendar() начинает работу с проверки параметров, переданных ей в переменной $opts. Цвет, которым выделяется текущий день, можно передать в виде RGB-значения через переменную $opts['today_color']. Значение по умолчанию равно #FFFF00, это ярко-желтый цвет. Кроме того, чтобы изменить вид вывода ссылок на предыдущий и последующий месяцы, можно передать строку форматирования в стиле функции printf() через переменную $opts['month_link']. Затем функция устанавливает значение переменной $day_highlight в true, если месяц и год календаря совпадают с текущими месяцем и годом. Ссылки на предыдущий и последующий месяцы помещаются в переменные $prev_month_link и $next_month_link с помощью строки форматирования, находящейся в переменной $opts['month_link']. После этого функция pc_calendar() выводит верхнюю часть HTML-таблицы, которая содержит календарь, и строку таблицы с сокращенными названиями дней недели.

С учетом дня недели, возвращенного функцией strftime('%w'), печатаются пустые ячейки таблицы, чтобы первый день месяца был выровнен по соответствующему дню недели. Например, если первый день месяца вторник, то надо напечатать две пустые ячейки, чтобы занять места, отведенные под воскресенье и понедельник в первой строке таблицы. После вывода этой предварительной информации функция pc_calendar() выполняет цикл по всем дням месяца. Для большинства дней она печатает обычные ячейки таблицы, а для текущего дня печатает ячейку с другим цветом фона. Когда значение переменной $day_offset достигает 7, то неделя заполнена, и надо начинать новую строку таблицы. После вывода ячеек таблицы для каждого дня месяца добавляются пустые ячейки, чтобы заполнить до конца последнюю строку таблицы. Например, если последний день месяца четверг, то добавляются две ячейки, чтобы занять пространство, отведенное под пятницу и субботу. Наконец, таблица закрывается, и календарь полностью готов.

Добавлено: 21 Июля 2018 07:04:03 Добавил: Андрей Ковальчук

Работа с негригорианскими календарями

Задача
Необходимо работать с негригорианским календарем, таким как Юлианский, Иудейский или Французский Республиканский.

Решение
Модуль PHP «календарь» обеспечивает функции преобразования для работы с Юлианским календарем, так же как и с Французским Республиканским и Иудейским календарями. Для работы с этими функциями необходимо, чтобы PHP был собран с поддержкой этого модуля. Эти функции используют юлианский счет дней (а это не то же самое, что Юлианский календарь) как промежуточный формат обмена данными между ними. Две функции, jdtogregorian() и gregoriantojd(), выполняют преобразования между юлианскими днями и теми же датами Григорианского календаря:

$jd = gregoriantojd(3,9,1876); // 9 марта 1876 года; $jd = 2406323
$gregorian = jdtogregorian($jd); // $gregorian = 3/9/1876


Допустимым диапазоном Григорианского календаря являются значения от 4714 BCE (до рождества Христова) до 9999 CE (после рождества Христова).

Обсуждение
Преобразования между юлианским представлением дат и Юлианским календарем выполняются при помощи функции jdtojulian() и juliantojd():

// 29 февраля 1900 года (негригорианский високосный год)
$jd = juliantojd(2,29,1900); // $jd = 2415092
$julian = jdtojulian($jd); // $julian = 2/29/1900
$gregorian = jdtogregorian($jd); // $gregorian = 3/13/1900


Допустимым диапазоном для Юлианского календаря являются значения от 4713 BCE до 9999 CE, но так как он был создан в 46 году до рождества Христова, то вы рискуете вызвать недовольство поклонников Юлианского календаря, если будете использовать его для дат до его создания. Преобразования между юлианским представлением дат и Французским Республиканским календарем выполняется посредством функции jdtofrench() и frenchtojd():

$jd = frenchtojd(8,13,11); // 13 floreal XI; $jd = 2379714
$french = jdtofrench($jd); // $french = 8/13/11
$gregorian = jdtofregorian($jd); // $gregorian = 5/3/1803;

дата продажи Луизианы США.

Для Французского Республиканского календаря допустимыми являются даты с сентября 1792 года до сентября 1806 года, что представляет собой небольшой интервал времени, но, поскольку календарем пользовались с 1793 года до января 1806 года, этого вполне достаточно. Для преобразования между юлианским представлением дат и Иудейским календарем применяются функции jdtojewish() и jewishtojd( ):

$jd = JewishToJD(6,14,5761); // Adar 14, 5761; $jd = 2451978
$jewish = JDToJewish($jd); // $jewish = 6/14/5761
$gregorian = JDToGregorian($jd); // $gregorian = 3/9/2001


Допустимый диапазон для Иудейского календаря начинается с 3761
BCE (первый год по Иудейскому календарю).

Добавлено: 21 Июля 2018 07:03:25 Добавил: Андрей Ковальчук

Получение интервалов времени

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

Решение
Определите начальную дату с помощью функции time() и strftime(). Если интервал имеет фиксированную длину, то можно выполнить цикл по дням этого интервала. Если нет, то необходимо проверять каждую подпоследовательность дней на принадлежность к требуемому диапазону. Например, в неделе семь дней, поэтому для выработки дней текущей недели можно выполнить фиксированный цикл:

// генерация интервала времени для этой недели
$now = time();
// Если это время до 3 AM, увеличьте $now на 1, чтобы не беспокоиться
// о DST при движении обратно к началу недели.
if (3 < strftime('%H', $now)) { $now += 7200; }
// Какой сегодня день недели?
$today = strftime('%w', $now);
// Сколько дней назад началась неделя?
$start_day = $now - (86400 * $today);
// Вывод каждого дня недели
for ($i = 0; $i < 7; $i++) {
print strftime('%c',$start_day + 86400 * $i);
}


Обсуждение
Отдельный месяц или год может иметь различное число дней, поэтому необходимо определить конец временного диапазона, учитывая особенности данного месяца или года. Чтобы выполнить цикл по всем дням месяца, определите метку времени для первого дня этого месяца и для первого дня следующего месяца. Переменная цикла $day содержит день, увеличивающийся на определенное время (86 400 секунд), пока он не превысит метку времени начала следующего месяца:

// Генерирует временной интервал для месяца
$now = time();
// Если это до 3 AM, увеличьте $now на 1 так, чтобы не беспокоиться
// о DST при движении обратно к началу недели.
if (3 < strftime('%H', $now)) { $now += 7200; }
// Какой сегодня день недели?
$this_month = strftime('%m',$now);
// Метка времени полуночи первого дня этого месяца
$day = mktime(0,0,0,$this_month,1);
// Метка времени полуночи первого дня следующего месяца
$month_end = mktime(0,0,0,$this_month+1,1);
while ($day < $month_end) {
print strftime('%c',$day);
$day += 86400;
}

Добавлено: 21 Июля 2018 07:02:36 Добавил: Андрей Ковальчук

Выработка высокоточного времени

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

Решение
Это можно сделать при помощи функции microtime():

list($microseconds,$seconds) = explode(' ',microtime());


Обсуждение
Функция microtime() возвращает строку, содержащую дробную часть времени, прошедшего с начала эпохи, с точностью до микросекунды. Например, возвращенное значение 0.41644100 1026683258 означает, что с начала эпохи прошло 1026683258,41644100 секунд. Вместо числа с двойной точностью возвращается строка, поскольку число с двойной точностью не настолько велико, чтобы хранить полное значение с микросекундной точностью. Значение времени, содержащее миросекунды, удобно для генерирования = уникальных идентификаторов. Объединение его с идентификатором текущего процесса гарантирует получение уникального значения, поскольку процесс не может генерировать более одного идентификатора за одну микросекунду:

list($microseconds,$seconds) = explode(' ',microtime());
$id = $seconds.$microseconds.getmypid();


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

Добавлено: 21 Июля 2018 07:01:54 Добавил: Андрей Ковальчук

Учет перехода на летнее время

Задача
Необходимо обеспечить корректное определение времени с учетом перехода на летнее время.

Решение
Библиотека zoneinfo производит верные вычисления с учетом DST. В UNIX-системах преимущества zoneinfo обеспечивает функция putenv():

putenv('TZ=MST7MDT');
print strftime('%c');


Если zoneinfo нельзя использовать, то можно изменить жестко запрограммированные смещения часовых поясов, с учетом наличия или отсутствия поддержки DST локальным часовым поясом. Функция localtime() позволяет определить текущий статус поддержки DST:

// Определение текущего времени UTC
$now = time();
// Калифорния на 8 часов отстает от зоны UTC
$now -= 8 * 3600;
// DST действует?
$ar = localtime($now,true);
if ($ar['tm_isdst']) { $now += 3600; }
// Используем функцию gmdate() или gmstrftime()
// для печати Калифорнийского времени
print gmstrftime('%c',$now);


Обсуждение
Замена метки времени на значение смещения часового пояса относительно зоны UTC и последующий вызов функции gmdate() или gmstrftime() для вывода на печать значений функций, соответствующих часовому поясу, – это гибкий метод, работающий в любой временной зоне, но вычисления DST не совсем точны. Для коротких интервалов времени, когда DST-статус сервера отличается от статуса целевого часового пояса, результат неверен. Например, в 3:30 A.M. EDT в первое воскресенье апреля (после перехода на летнее время) в Тихоокеанском часовом поясе время (11:30 P.M.) еще зимнее. Сервер в Восточном часовом поясе, использующий этот метод, определит, что Калифорнийское время отстает от зоны UTC на семь часов, тогда как в действительности отставание составляет восемь часов. В 6:00 A.M. EDT (3:00 A.M. PDT) и Тихоокеанское и Восточное время уже учитывают DST, поэтому вычисления снова правильные (отставание Калифорнии от зоны UTC составляет восемь часов).

Добавлено: 21 Июля 2018 07:00:58 Добавил: Андрей Ковальчук

Учет часовых поясов при определении времени

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

Решение
В случае простых вычислений можно непосредственно добавить или вычесть разность между двумя часовыми поясами:

// если локальное время – это зона EST
$time_parts = localtime();
// Калифорния (PST) на три часа раньше
$california_time_parts = localtime(time() - 3 * 3600);

В UNIX-системах, если не известно смещение между временными зо-
нами, достаточно установить значение переменной окружения рав-
ным целевому часовому поясу:
putenv('TZ=PST8PDT');
$california_time_parts = localtime();

Обсуждение
Перед тем как начать обходить все углы и закоулки часовых поясов, мы хотим передать заявление, которое военно-морская обсерватория США представляет на http://tycho.1111.navy.mil/tzones.html. А именно, что официальная информация о часовых поясах мира в некотором роде непостоянна, «поскольку нации суверенны и могут изменять и изменяют свои системы хранения времени так, как они считают нужным». Поэтому, помня о превратностях международных отношений, которые властвуют над нами, мы прибегаем к нескольким способам, позволяющим правильно работать с часовыми поясами. Для относительно простой обработки смещений между часовыми поясами в программе организуется массив, в котором хранятся различные смещения относительно пояса UTC. Определив часовой пояс пользователя, просто прибавьте это смещение к соответствующему UTC времени, и функции, которые выводят на печать UTC время (т. е. gmdate(), gmstrftime()), смогут напечатать соответствующим образом скорректированное время.

// Определяем текущее время
$now = time();
// Калифорния на 8 часов отстает от зоны UTC
$now += $pc_timezones['PST'];
// Используйте функции gmdate() или gmstrftime()
// для вывода Калифорнийского времени
print gmstrftime('%c',$now);


В вышеприведенном коде смещения относительно зоны UTC хранятся в массиве $pc_timezones:
// Из Perl Time::Timezone
$pc_timezones = array(
'GMT' => 0, // Среднее время по гринвичскому меридиану
'UTC' => 0, // Универсальное (Скоординированное) время
'WET' => 0, // Западноевропейское время
'WAT' => -1*3600, // Западноафриканское время
'AT' => -2*3600, // Время Азорских островов
'NFT' => -3*3600-1800, // Время Ньюфаундленда
'AST' => -4*3600, // Стандартное Атлантическое время
'EST' => -5*3600, // Восточное стандартное время
'CST' => -6*3600, // Центральное стандартное время
'MST' => -7*3600, // Стандартное Горное время
'PST' => -8*3600, // Стандартное Тихоокеанское время
'YST' => -9*3600, // Стандартное Юконское время
'HST' => -10*3600, // Стандартное Гавайское время
'CAT' => -10*3600, // Время Центральной Аляски
'AHST' => -10*3600, // Стандартное время Аляска-Гавайи
'NT' => -11*3600, // Время города Ном
'IDLW' => -12*3600, // К западу от международной линии смены дат
'CET' => +1*3600, // Центральноевропейское время
'MET' => +1*3600, // Среднеевропейское время
'MEWT' => +1*3600, // Зимнее Среднеевропейское время
'SWT' => +1*3600, // Зимнее Шведское время
'FWT' => +1*3600, // Зимнее Французское время
'EET' => +2*3600, // Восточноевропейское время, СССР Зона 1
'BT' => +3*3600, // Багдадское время, СССР Зона 2
'IT' => +3*3600+1800, // Иранское время
'ZP4' => +4*3600, // СССР Зона 3
'ZP5' => +5*3600, // СССР Зона 4
'IST' => +5*3600+1800, // Стандартное Индийское время
'ZP6' => +6*3600, // СССР Зона 5
'SST' => +7*3600, // Южносуматранское время, СССР Зона 6
'WAST' => +7*3600, // Стандартное Западноавстралийское время
'JT' => +7*3600+1800, // Время острова Ява
'CCT' => +8*3600, // Время Китайского побережья, СССР Зона 7
'JST' => +9*3600, // Стандартное Японское, СССР Зона 8
'CAST' => +9*3600+1800, // Стандартное Центральноавстралийское время
'EAST' => +10*3600, // Стандартное Восточноавстралийское время
'GST' => +10*3600, // Стандартное Гуамское время, СССР Зона 9
'NZT' => +12*3600, // Новозеландское время
'NZST' => +12*3600, // Стандартное Новозеландское время
'IDLE' => +12*3600 // К востоку от международной линии смены дат
);


В UNIX-системах преобразования можно выполнять при помощи библиотеки zoneinfo. Это позволяет сделать код более компактным, а обработку DST более прозрачной. Чтобы получить преимущества библиотеки zoneinfo в PHP, все вычисления с международными датами надо выполнять на основе меток времени UNIX. Последние следует генерировать из частей времени с помощью функции pc_mktime().

function pc_mktime($tz,$hr,$min,$sec,$mon,$day,$yr) {
putenv("TZ=$tz");
$a = mktime($hr,$min,$sec,$mon,$day,$yr);
putenv('TZ=EST5EDT'); // замена пояса EST5EDT на часовой пояс
вашего сервера!
return $a;
}


Вызов функции putenv() до вызова mktime() позволяет обмануть системную функцию mktime(), заставляя ее думать, что она находится в другом часовом поясе. После вызова функции mktime() необходимо восстановить истинный часовой пояс. На восточном побережье Соединеных Штатов это пояс EST5EDT. Замените его соответствующим значением географического положения вашего компьютера. Части времени преобразуются в метки времени с помощью функции pc_mktime(). Ее двойником, который превращает метки времени в форматированную строку времени и части времени, является функция pc_strftime().

function pc_strftime($tz,$format,$timestamp) {
putenv("TZ=$tz");
$a = strftime($format,$timestamp);
putenv('TZ=EST5EDT'); // замена пояса EST5EDT на часовой пояс
вашего сервера!
return $a;
}


В этом примере применен тот же способ обмана системной функции, к которому прибегает функция pc_mktime() для получения правильного результата от функции strftime(). Существенным в этих функциях является то, что не надо беспокоиться о смещениях различных временных поясов относительно UTC независимо от того, действует ли переход на летнее время или какие-либо другие особенности часовых поясов. Достаточно установить соответствующий часовой пояс, а системная библиотека сделает все остальное. Учтите, что значение переменной $tz в обеих функциях должно быть именем не часового пояса, а зоны zoneinfo. Пояса zoneinfo имеют больше особенностей, чем часовые пояса, так как они соответствуют определенным местам. Последняя колонка показывает, происходит ли в данном поясе переход на летнее время. В разных странах переход на летнее и зимнее время не происходит в один и тот же день. Чтобы вычислить время с учетом перехода на летнее время в соответствии с географическим положением пункта, выберите пояс zoneinfo, который ближе всего находится к данному пункту.

Добавлено: 21 Июля 2018 07:00:20 Добавил: Андрей Ковальчук

Сложение и вычитание дат

Задача
Необходимо добавить или вычесть интервал из даты.

Решение
В зависимости от способа представления даты и интервала, следует применять функцию strtotime() или некоторые простые арифметические функции. Если дата и интервал представлены в соответствующем формате, то проще обратиться к функции strtotime():

$birthday = 'March 10, 1975';
$whoopee_made = strtotime("$birthday - 9 months ago");


Если дата представлена в виде метки времени UNIX, а интервал можно выразить в секундах, то надо вычесть интервал из метки времени:

$birthday = 163727100;
$gestation = 36 * 7 * 86400; // 36 weeks
$whoopee_made = $birthday - $gestation;


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

$now = time();
$next_week = $now + 7 * 86400;


Однако данный способ может привести к трудностям, если границы интервала находятся по разные стороны от момента перехода на летнее время. В этом случае длина одного из дней не будет равна 86 400 секундам, а составит либо 82 800, либо 90 000 секунд, в зависимости от сезона. Если приложение работает исключительно с UTC, то об этом можно не беспокоиться. Но если необходимо учитывать местное время, то избежать трудностей при подсчете дней поможет юлианское представление дат. Преобразования между метками времени и юлианскими датами обечпечивают функции unixtojd() и jdtounix():

$now = time();
$today = unixtojd($now);
$next_week = jdtounix($today + 7);
// don't forget to add back hours, minutes, and seconds
$next_week += 3600 * date('H',$now) + 60 * date('i',$now) + date('s',$now);


Функции unixtojd() и jdtounix() входят в модуль calendar и поэтому доступны, только если PHP собран с его поддержкой (версия для Windows имеет встроенную поддержку данного расширения).

Добавлено: 21 Июля 2018 06:59:21 Добавил: Андрей Ковальчук

Выделение дат и времен из строк

Задача
Необходимо извлечь из строки дату или время в формате, пригодном для вычислений. Например, конвертировать представление даты, такое как «last Thursday» (последний четверг) в метку времени UNIX.

Решение
Проще всего анализировать строки даты или времени с помощью функции strtotime(), которая превращает множество понятных человеку строк даты и времени в метку времени UNIX:

$a = strtotime('march 10'); // по умолчанию в текущий год


Обсуждение
Грамматика функции strtotime() и сложна и обширна, поэтому лучший способ освоиться с ней состоит в том, чтобы попробовать множество различных представлений времени. Те, кому интересны ее секреты, могут обратититься к файлу ext/standard/parsedate.y из дистрибутива PHP. Функция strtotime() понимает слова, описывающие текущее время:

$a = strtotime('now');
print strftime('%c',$a);
$a = strtotime('today');
print strftime('%c',$a);
Mon Aug 12 20:35:10 2002
Mon Aug 12 20:35:10 2002


Она понимает различные способы представления времени и даты:
$a = strtotime('5/12/1994');
print strftime('%c',$a);
$a = strtotime('12 may 1994');
print strftime('%c',$a);
Thu May 12 00:00:00 1994
Thu May 12 00:00:00 1994


Она понимает относительные время и дату:
$a = strtotime('last thursday'); // 12 августа 2002 года
print strftime('%c',$a);
$a = strtotime('2001-07-12 2pm + 1 month');
print strftime('%c',$a);
Thu Aug 8 00:00:00 2002
Mon Aug 12 14:00:00 2002


Она понимает часовые пояса. Когда следующий код запускается на компьтере, находящемся в зоне восточного поясного летнего времени (Eastern Daylight Time, EDT), он выводит то же самое время:

$a = strtotime('2002-07-12 2pm edt + 1 month');
print strftime('%c',$a);
Mon Aug 12 14:00:00 2002


Однако если код, приведенный ниже, запустить на компьютере, находящемся в поясе EDT, то он выведет время в часовом поясе EDT (16:00), когда в зоне горного летнего времени (Mountain Daylight Time, MDT), расположенной на 2 часа ближе к Гринвичу, чем пояс EDT, наступает два часа пополудни (14:00):

$a = strtotime('2002-07-12 2pm mdt + 1 month');
print strftime('%c',$a);
Mon Aug 12 16:00:00 2002


Если дата и время, которые требуется выделить из строки, представлены в известном заранее формате, то можно не вызывать функцию strtotime(), а сконструировать регулярное выражение, выделяющее необходимые части даты и времени. Ниже показано, как разобрать даты формата «YYYY-MM-DD HH:MM:SS», такие как поле DATETIME в MySQL:

$date = '1974-12-03 05:12:56';
preg_match('/(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/
',$date,$date_parts);


Этот фрагмент помещает год, месяц, день, час, минуту и секунду в переменные от $date_parts до $date_parts. (Функция preg_match() помещает все выделенное выражение в переменную $date_parts[0].) Регулярные выражения позволяют извлечь дату и время из большей строки, которая может также содержать и другую информацию (из ввода пользователя или из файла), но если известно расположение даты в разбираемой строке, то вызов функции substr() может даже ускорить разбор строки:

$date_parts[0] = substr($date,0,4);
$date_parts = substr($date,5,2);
$date_parts = substr($date,8,2);
$date_parts = substr($date,11,2);
$date_parts = substr($date,14,2);
$date_parts = substr($date,17,2);


Можно также использовать функцию split();
$ar = split('[- :]',$date);
print_r($ar);
Array
(
[0] => 1974
=> 12
=> 03
=> 05
=> 12
=> 56
)


Будьте осторожны: PHP выполняет преобразование между числами и строками без какого-либо предупреждения, но числа, начинающиеся с 0, считаются восьмеричными (по основанию 8). Поэтому 03 и 05 – это 3 и 5; но 08 и 09 – это не 8 и 9. Функции preg_match() и strtotime() имеют одинаковую эффективность при анализе формата даты, такого как «YYYY-MM-DD HH:MM:SS», но функция ereg() работает почти в четыре раза медленнее, чем другие. Для отделения части строки даты функция preg_match() наиболее удобна, но функция strtotime() очевидно имеет намного большую гибкость.

Добавлено: 21 Июля 2018 06:58:37 Добавил: Андрей Ковальчук