yakoffka.ru
грабли, костыли и велосипеды php, css, html, js и прочего

Локализация функции date

Локализация функции date
			«Ходы кривые роет подземный умный крот. Нормальные герои всегда идут в обход.
			В обход идти понятно не очень-то легко. Не очень то приятно и очень далеко.
			Зато так поступают одни лишь мудрецы, Зато так наступают одни лишь храбрецы.
			И мы с пути кривого обратно не свернем. А надо будет снова пойдем другим путем!»

песня из к/ф "Айболит-66"

1
Введение

«Информация о локали модифицируется во всем процессе, а не по каждому потоку отдельно. Если вы используете PHP на многопоточном сервере, таком как IIS, HHVM или Apache под Windows, вы можете обнаружить неожиданные изменения в настройках локали во время выполнения скриптов, никогда и не вызывавших setlocale(). Это происходит из-за того, что другие скрипты, запущенные в параллельных потоках данного процесса, в то же самое время поменяли настройки локали для всего процесса с помощью setlocale()» setlocale

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

Итак, для начала поименуем нашу функцию для персонализированного вывода ошибок:

Code:
$name_function="date_ru()";

2
Определение управляющих символов

Определим управляющие символы в шаблоне, заданном строкой $format для переопределения их значений в результирующей строке ("l", "F", "D", "M", "S", "a", "A",) и запишем их в массив $arr_simbols:

Code:
$arr_simbols=array("l","F","D","M","S","a","A",);

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

3
Определяем замены

Для каждого управляющего символа (кроме 'S') запишем массив замен значений, выдаваемых функцией data():

Code:
$arr_l=array(// массив замен полного наименования дня недели "Sunday"=>"Воскресенье", "Monday"=>"Понедельник", "Tuesday"=>"Вторник", "Wednesday"=>"Среда", "Thursday"=>"Четверг", "Friday"=>"Пятница", "Saturday"=>"Суббота", ); $arr_F=array(// массив замен полного наименования месяца "January"=>"января", "February"=>"февраля", "March"=>"марта", "April"=>"апреля", "May"=>"мая", "June"=>"июня", "July"=>"июля", "August"=>"августа", "September"=>"сентября", "October"=>"октября", "November"=>"ноября", "December"=>"декабря", ); $arr_D=array(// массив замен cокращенного представления дня недели, 3 символа "Sun"=>"Вс", "Mon"=>"Пн", "Tue"=>"Вт", "Wed"=>"Ср", "Thu"=>"Чт", "Fri"=>"Пт", "Sat"=>"Сб", ); $arr_M=array(// массив замен cокращенного наименования месяца, 3 символа "Jan"=>"янв", "Feb"=>"фев", "Mar"=>"мар", "Apr"=>"апр", "May"=>"май", "Jun"=>"июн", "Jul"=>"июл", "Aug"=>"авг", "Sep"=>"сен", "Oct"=>"окт", "Nov"=>"ноя", "Dec"=>"дек", ); $arr_a=array(// до/после полудня "am"=>"до полудня", "pm"=>"после полудня", ); $arr_A=array(// ДО/ПОСЛЕ ПОЛУДНЯ "AM"=>"ДО ПОЛУДНЯ", "PM"=>"ПОСЛЕ ПОЛУДНЯ", );

4
Получение оригинальной строки

Получаем системную дату/время с помощью оригинальной функции:

Code:
$res=date($format,$timestamp);

5
Замены подстрок

Для начала следует определить наличие управляющего символа в строке $format, учитывая комментирование. Найдём $c - количество вхождений незакомментированного символа $s:

Code:
$c=substr_count($format,$s)-substr_count($format,"\".$s);//количество вхождений незаэкранированных искомых символов

Производим необходимые замены, уменьшая значение переменной $c на количество произведенных замен.

Code:
foreach(${"arr_".$s} as $search=>$replace){ $res=str_replace($search,$replace,$res,$count); $c=$c-$count; }unset($search,$replace);

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

Code:
if($c===0){ unset($c); }else{ $res=$res."\n<p class='error'>error#".__line__." in $name_function: \$s='$s'; \$c='$c'.</p>\n"; }unset($s,$c);

6
Замена суффиксов

Здесь дело обстоит немногим сложнее: массив замен значений будет состоять из двух подмассивов - для суффиксов и окончаний.

Code:
$arr_S=array(// суффикс порядкового числительного дня месяца, 2 символа; array("th","st","rd","nd",),// массив суффиксов в порядке убывания частоты употребления в диапазоне от 1 до 31 array(// массив окончаний числительных в порядке убывания частоты употребления в диапазоне от 1 до 31 (что-то ) "01"=>"-ое", "02"=>"-ое", "03"=>"-ье", "04"=>"-ое", "05"=>"-ое", "06"=>"-ое", "07"=>"-ое", "08"=>"-ое", "09"=>"-ое", "10"=>"-ое", "11"=>"-ое", "12"=>"-ое", "13"=>"-ое", "14"=>"-ое", "15"=>"-ое", "16"=>"-ое", "17"=>"-ое", "18"=>"-ое", "19"=>"-ое", "20"=>"-ое", "21"=>"-ое", "22"=>"-ое", "23"=>"-ье", "24"=>"-ое", "25"=>"-ое", "26"=>"-ое", "27"=>"-ое", "28"=>"-ое", "29"=>"-ое", "30"=>"-ое", "31"=>"-ое", ), );

Я не особо силён в правиле буквенных наращиваний после цифр, так что здесь до проверки правильности пока оставлю так.

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

Code:
foreach(${"arr_".$s}[0] as $search){ $replace=${"arr_".$s}[1][date('d',$timestamp)]; $res=str_replace($search,$replace,$res,$count); $c=$c-$count; }unset($search,$replace);

7
Добавление циклов

Собственно теперь собираем всё вместе, добавив циклы и условия, и радуемся.

Финальная версия скрипта:

Code:
// 2018_09_06 вывод системной даты/времени на русском языке (кроме 'r' Дата в формате RFC 2822) //================================================================================================== function date_ru($format,$timestamp=FALSE){ // 1 поименуем функцию для персонализированного вывода ошибок $name_function="date_ru()"; // 2 определим символы в шаблоне результирующей строки, значение которых необходимо переопределить // порядок ВАЖЕН!!! сначала полные названия, затем сокращенные! $arr_simbols=array("l","F","D","M","S","a","A",); // 3 определяем замены для каждого символа $arr_l=array(// Полное наименование дня недели "Sunday"=>"Воскресенье", "Monday"=>"Понедельник", "Tuesday"=>"Вторник", "Wednesday"=>"Среда", "Thursday"=>"Четверг", "Friday"=>"Пятница", "Saturday"=>"Суббота", ); $arr_F=array(// Полное наименование месяца "January"=>"января", "February"=>"февраля", "March"=>"марта", "April"=>"апреля", "May"=>"мая", "June"=>"июня", "July"=>"июля", "August"=>"августа", "September"=>"сентября", "October"=>"октября", "November"=>"ноября", "December"=>"декабря", ); $arr_D=array(// Сокращенное представление дня недели, 3 символа "Sun"=>"Вс", "Mon"=>"Пн", "Tue"=>"Вт", "Wed"=>"Ср", "Thu"=>"Чт", "Fri"=>"Пт", "Sat"=>"Сб", ); $arr_M=array(// Сокращенное наименование месяца, 3 символа "Jan"=>"янв", "Feb"=>"фев", "Mar"=>"мар", "Apr"=>"апр", "May"=>"май", "Jun"=>"июн", "Jul"=>"июл", "Aug"=>"авг", "Sep"=>"сен", "Oct"=>"окт", "Nov"=>"ноя", "Dec"=>"дек", ); $arr_S=array(// суффикс порядкового числительного дня месяца, 2 символа; array("th","st","rd","nd",),// массив суффиксов в порядке убывания частоты употребления в диапазоне от 1 до 31 array(// массив окончаний числительных в порядке убывания частоты употребления в диапазоне от 1 до 31 (что-то я не особо силён в окончаниях числительных, так что здесь до проверки заказчиком оставлю так) "01"=>"-ое", "02"=>"-ое", "03"=>"-ье", "04"=>"-ое", "05"=>"-ое", "06"=>"-ое", "07"=>"-ое", "08"=>"-ое", "09"=>"-ое", "10"=>"-ое", "11"=>"-ое", "12"=>"-ое", "13"=>"-ое", "14"=>"-ое", "15"=>"-ое", "16"=>"-ое", "17"=>"-ое", "18"=>"-ое", "19"=>"-ое", "20"=>"-ое", "21"=>"-ое", "22"=>"-ое", "23"=>"-ье", "24"=>"-ое", "25"=>"-ое", "26"=>"-ое", "27"=>"-ое", "28"=>"-ое", "29"=>"-ое", "30"=>"-ое", "31"=>"-ое", ), ); $arr_a=array(// до/после полудня "am"=>"до полудня", "pm"=>"после полудня", ); $arr_A=array(// ДО/ПОСЛЕ ПОЛУДНЯ "AM"=>"ДО ПОЛУДНЯ", "PM"=>"ПОСЛЕ ПОЛУДНЯ", ); // 4 получаем системную дату/время на англицком: $res=date($format,$timestamp); // 5 смотрим содержимое переменной $format на предмет наличия 'D l j S F M' foreach($arr_simbols as $s){ $c=substr_count($format,$s)-substr_count($format,"\".$s);//количество вхождений незаэкранированных искомых символов if($c>0){// незаэкранированный искомый символ найден! // 6/1 производим замену, если символ - не суффикс (с ним у нас будет разговор попозже) if($s!=="S"){ foreach(${"arr_".$s} as $search=>$replace){ $res=str_replace($search,$replace,$res,$count); $c=$c-$count; }unset($search,$replace); // 6/2 производим замену для суффикса }else{ foreach(${"arr_".$s}[0] as $search){ $replace=${"arr_".$s}[1][date('d',$timestamp)]; $res=str_replace($search,$replace,$res,$count); $c=$c-$count; }unset($search,$replace); } // 7 проверка количества произведенных замен if($c!==0){$res=$res."\n<p class='error'>error#".__line__." in $name_function: \$s='$s'; \$c='$c'.</p>\n";} unset($s,$c); } } // выводим результат return $res; }

8
Проверка работы функции

Заключительный этап - проверка правильности работы функции.

Code:
$arr_test=array( array("полного наименования дня недели","l",(60*60*24),7), array("сокращенного наименования дня недели","D",(60*60*24),7), array("полного наименования месяца","F",(60*60*24*31),12), array("сокращенного наименования месяца","M",(60*60*24*31),12), array("Ante/Post Meridiem","A",(60*60*12),2), array("ante/post meridiem","a",(60*60*12),2), array("суффикса/окончания","S",(60*60*24),31), array("неверного параметра \$timestamp='string'","S","string",1), array("неверного параметра: дата выше 19 января 2038","S",(60*60*24*31*200),10), array("неверного параметра: дата ниже 13 декабря 1901","S",(-60*60*24*31*200),10), array("полноформатной даты","D, jS F Y, h:i a",(60*60*36),7), ); foreach($arr_test as $arr_test){ $timestamp=1536322980;// Пт, 7-ое сентября 2018, 03:23 после полудня if($arr_test[1]=="S"){$b="j";}else{$b="";}// дописываем день месяца для проверки окончаний echo "\t<table class='blue_table'>\n<caption>проверка преобразования ".$arr_test[0]."</caption>"; echo "\t\t\t<tr><th>№</th><th>timestamp</th><th>date('Y.m.d')</th><th>date('$b".$arr_test[1]."')</th><th>date_ru('$b".$arr_test[1]."')</th></tr>\n"; for($i=0;$i<$arr_test[3];++$i){ echo "\t\t\t<tr><td>".($i+1)."</td><td>$timestamp</td><td>".date('Y.m.d',$timestamp)."</td><td>".date($b.$arr_test[1],$timestamp)."$ae</td><td>".date_ru($b.$arr_test[1],$timestamp)." $ar</td></tr>\n"; $timestamp=$timestamp+$arr_test[2]; } echo "\t\t</table>\n"; }unset($arr_test);
проверка преобразования полного наименования дня недели
timestampdate('Y.m.d')date('l')date_ru('l')
115363229802018.09.07FridayПятница
215364093802018.09.08SaturdayСуббота
315364957802018.09.09SundayВоскресенье
415365821802018.09.10MondayПонедельник
515366685802018.09.11TuesdayВторник
615367549802018.09.12WednesdayСреда
715368413802018.09.13ThursdayЧетверг
проверка преобразования сокращенного наименования дня недели
timestampdate('Y.m.d')date('D')date_ru('D')
115363229802018.09.07FriПт
215364093802018.09.08SatСб
315364957802018.09.09SunВс
415365821802018.09.10MonПн
515366685802018.09.11TueВт
615367549802018.09.12WedСр
715368413802018.09.13ThuЧт
проверка преобразования полного наименования месяца
timestampdate('Y.m.d')date('F')date_ru('F')
115363229802018.09.07Septemberсентября
215390013802018.10.08Octoberоктября
315416797802018.11.08Novemberноября
415443581802018.12.09Decemberдекабря
515470365802019.01.09Januaryянваря
615497149802019.02.09Februaryфевраля
715523933802019.03.12Marchмарта
815550717802019.04.12Aprilапреля
915577501802019.05.13Mayмая
1015604285802019.06.13Juneиюня
1115631069802019.07.14Julyиюля
1215657853802019.08.14Augustавгуста
проверка преобразования сокращенного наименования месяца
timestampdate('Y.m.d')date('M')date_ru('M')
115363229802018.09.07Sepсен
215390013802018.10.08Octокт
315416797802018.11.08Novноя
415443581802018.12.09Decдек
515470365802019.01.09Janянв
615497149802019.02.09Febфев
715523933802019.03.12Marмар
815550717802019.04.12Aprапр
915577501802019.05.13Mayмай
1015604285802019.06.13Junиюн
1115631069802019.07.14Julиюл
1215657853802019.08.14Augавг
проверка преобразования Ante/Post Meridiem
timestampdate('Y.m.d')date('A')date_ru('A')
115363229802018.09.07PMПОСЛЕ ПОЛУДНЯ
215363661802018.09.08AMДО ПОЛУДНЯ
проверка преобразования ante/post meridiem
timestampdate('Y.m.d')date('a')date_ru('a')
115363229802018.09.07pmпосле полудня
215363661802018.09.08amдо полудня
проверка преобразования суффикса/окончания
timestampdate('Y.m.d')date('jS')date_ru('jS')
115363229802018.09.077th7-ое
215364093802018.09.088th8-ое
315364957802018.09.099th9-ое
415365821802018.09.1010th10-ое
515366685802018.09.1111th11-ое
615367549802018.09.1212th12-ое
715368413802018.09.1313th13-ое
815369277802018.09.1414th14-ое
915370141802018.09.1515th15-ое
1015371005802018.09.1616th16-ое
1115371869802018.09.1717th17-ое
1215372733802018.09.1818th18-ое
1315373597802018.09.1919th19-ое
1415374461802018.09.2020th20-ое
1515375325802018.09.2121st21-ое
1615376189802018.09.2222nd22-ое
1715377053802018.09.2323rd23-ье
1815377917802018.09.2424th24-ое
1915378781802018.09.2525th25-ое
2015379645802018.09.2626th26-ое
2115380509802018.09.2727th27-ое
2215381373802018.09.2828th28-ое
2315382237802018.09.2929th29-ое
2415383101802018.09.3030th30-ое
2515383965802018.10.011st1-ое
2615384829802018.10.022nd2-ое
2715385693802018.10.033rd3-ье
2815386557802018.10.044th4-ое
2915387421802018.10.055th5-ое
3015388285802018.10.066th6-ое
3115389149802018.10.077th7-ое
проверка преобразования неверного параметра: дата позже 19 января 2038
timestampdate('Y.m.d')date('jS')date_ru('jS')
115363229802018.09.077th7-ое
220720029802035.08.2929th29-ое
326076829802052.08.1919th19-ое
431433629802069.08.1010th10-ое
536790429802086.08.011st1-ое
642147229802103.07.2424th24-ое
747504029802120.07.1414th14-ое
852860829802137.07.055th5-ое
958217629802154.06.2626th26-ое
1063574429802171.06.1717th17-ое
проверка преобразования неверного параметра: дата раньше 13 декабря 1901
timestampdate('Y.m.d')date('jS')date_ru('jS')
115363229802018.09.077th7-ое
210006429802001.09.1616th16-ое
34649629801984.09.2525th25-ое
4-707170201967.10.055th5-ое
5-6063970201950.10.1414th14-ое
6-11420770201933.10.2323rd23-ье
7-16777570201916.11.011st1-ое
8-22134370201899.11.1010th10-ое
9-27491170201882.11.1919th19-ое
10-32847970201865.11.2828th28-ое
проверка преобразования полноформатной даты
timestampdate('Y.m.d')date('D, jS F Y, h:i a')date_ru('D, jS F Y, h:i a')
115363229802018.09.07Fri, 7th September 2018, 03:23 pmПт, 7-ое сентября 2018, 03:23 после полудня
215364525802018.09.09Sun, 9th September 2018, 03:23 amВс, 9-ое сентября 2018, 03:23 до полудня
315365821802018.09.10Mon, 10th September 2018, 03:23 pmПн, 10-ое сентября 2018, 03:23 после полудня
415367117802018.09.12Wed, 12th September 2018, 03:23 amСр, 12-ое сентября 2018, 03:23 до полудня
515368413802018.09.13Thu, 13th September 2018, 03:23 pmЧт, 13-ое сентября 2018, 03:23 после полудня
615369709802018.09.15Sat, 15th September 2018, 03:23 amСб, 15-ое сентября 2018, 03:23 до полудня
715371005802018.09.16Sun, 16th September 2018, 03:23 pmВс, 16-ое сентября 2018, 03:23 после полудня

9
Заключение

Получена функция, локализирующая результаты вывода встроенной функции date(). Возвращает отформатированную строку с датой на русском языке. Для чего я это сделал? специально для hh, Капустин Яков (Пятница, 7-ое сентября 2018, 15:23)