Локализация функции date
«Ходы кривые роет подземный умный крот. Нормальные герои всегда идут в обход. В обход идти понятно не очень-то легко. Не очень то приятно и очень далеко. Зато так поступают одни лишь мудрецы, Зато так наступают одни лишь храбрецы. И мы с пути кривого обратно не свернем. А надо будет снова пойдем другим путем!»песня из к/ф "Айболит-66"
01Введение
«Информация о локали модифицируется во всем процессе, а не по каждому потоку отдельно. Если вы используете PHP на многопоточном сервере, таком как IIS, HHVM или Apache под Windows, вы можете обнаружить неожиданные изменения в настройках локали во время выполнения скриптов, никогда и не вызывавших setlocale(). Это происходит из-за того, что другие скрипты, запущенные в параллельных потоках данного процесса, в то же самое время поменяли настройки локали для всего процесса с помощью setlocale()» setlocale
Для того, чтобы внезапно не обнаружить неожиданные изменения в работе скриптов, и показать себя настоящим героем, завсегда идущим в обход, я не стал менять настройки локали, а в очередной раз сел за изобретение велосипеда.
02Определение управляющих символов
Определим управляющие символы в шаблоне, заданном строкой $format для переопределения их значений в результирующей строке ("l", "F", "D", "M", "S", "a", "A",) и запишем их в массив $arr_simbols:
Важно соблюсти правильную последовательность: сначала производим замену полных наименований, затем сокращений, затем суффиксов.
03Определяем замены
Для каждого управляющего символа (кроме 'S') запишем массив замен значений, выдаваемых функцией data():
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
"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"=>"ПОСЛЕ ПОЛУДНЯ",
);
04Получение оригинальной строки
Получаем системную дату/время с помощью оригинальной функции:
05Замены подстрок
Для начала следует определить наличие управляющих символов в строке $format, учитывая возможность встречи закомментированных.
Производим необходимые замены, уменьшая значение переменной $c на количество произведенных замен.
2
3
4
5
$res=str_replace($search,$replace,$res,$count);
$c=$c-$count;
}unset($search,$replace);
Выполняем проверку: при корректной работе скрипта значение $c должно быть равным нулю.
2
3
4
5
6
7
if($c===0){
unset($c);
}else{
$res=$res."\n<p class='error'>error#".__line__." in ".__function__.": \$s='$s'; \$c='$c'.</p>\n";
}unset($s,$c);
06Замена суффиксов
Здесь дело обстоит немногим сложнее: массив замен значений будет состоять из двух подмассивов - для суффиксов и окончаний.
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
32
33
34
35
36
37
38
// массив суффиксов в порядке убывания частоты употребления 2 символа
array("th", "st", "rd", "nd",),
// массив окончаний числительных в диапазоне от 1 до 31
array(
"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"=>"-ое",
),
);
Я не особо силён в правиле буквенных наращиваний после цифр, так что здесь до проверки правильности пока оставлю так.
Так как связь между английскими суффиксами и русскими окончаниями числительных косвенная, соответствие будем проводить через число месяца:
2
3
4
5
6
$replace=${"arr_".$s}[1][date('d',$timestamp)];
$res=str_replace($search,$replace,$res,$count);
$c=$c-$count;
}unset($search,$replace);
07Добавление циклов
Собственно теперь собираем всё вместе, добавив циклы и условия, и радуемся.
Финальная версия скрипта:
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
//==================================================================================================
function date_ru($format,$timestamp=FALSE){
// 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 до 31666
array(
"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 ".__function__.": \$s='$s'; \$c='$c'.</p>\n";
}
unset($s,$c);
}
}
// выводим результат
return $res;
}
08Проверка работы функции
Заключительный этап - проверка правильности работы функции.
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
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);
№ | timestamp | date('Y.m.d') | date('l') | date_ru('l') |
---|---|---|---|---|
1 | 1536322980 | 2018.09.07 | Friday | Пятница |
2 | 1536409380 | 2018.09.08 | Saturday | Суббота |
3 | 1536495780 | 2018.09.09 | Sunday | Воскресенье |
4 | 1536582180 | 2018.09.10 | Monday | Понедельник |
5 | 1536668580 | 2018.09.11 | Tuesday | Вторник |
6 | 1536754980 | 2018.09.12 | Wednesday | Среда |
7 | 1536841380 | 2018.09.13 | Thursday | Четверг |
№ | timestamp | date('Y.m.d') | date('D') | date_ru('D') |
---|---|---|---|---|
1 | 1536322980 | 2018.09.07 | Fri | Пт |
2 | 1536409380 | 2018.09.08 | Sat | Сб |
3 | 1536495780 | 2018.09.09 | Sun | Вс |
4 | 1536582180 | 2018.09.10 | Mon | Пн |
5 | 1536668580 | 2018.09.11 | Tue | Вт |
6 | 1536754980 | 2018.09.12 | Wed | Ср |
7 | 1536841380 | 2018.09.13 | Thu | Чт |
№ | timestamp | date('Y.m.d') | date('F') | date_ru('F') |
---|---|---|---|---|
1 | 1536322980 | 2018.09.07 | September | сентября |
2 | 1539001380 | 2018.10.08 | October | октября |
3 | 1541679780 | 2018.11.08 | November | ноября |
4 | 1544358180 | 2018.12.09 | December | декабря |
5 | 1547036580 | 2019.01.09 | January | января |
6 | 1549714980 | 2019.02.09 | February | февраля |
7 | 1552393380 | 2019.03.12 | March | марта |
8 | 1555071780 | 2019.04.12 | April | апреля |
9 | 1557750180 | 2019.05.13 | May | мая |
10 | 1560428580 | 2019.06.13 | June | июня |
11 | 1563106980 | 2019.07.14 | July | июля |
12 | 1565785380 | 2019.08.14 | August | августа |
№ | timestamp | date('Y.m.d') | date('M') | date_ru('M') |
---|---|---|---|---|
1 | 1536322980 | 2018.09.07 | Sep | сен |
2 | 1539001380 | 2018.10.08 | Oct | окт |
3 | 1541679780 | 2018.11.08 | Nov | ноя |
4 | 1544358180 | 2018.12.09 | Dec | дек |
5 | 1547036580 | 2019.01.09 | Jan | янв |
6 | 1549714980 | 2019.02.09 | Feb | фев |
7 | 1552393380 | 2019.03.12 | Mar | мар |
8 | 1555071780 | 2019.04.12 | Apr | апр |
9 | 1557750180 | 2019.05.13 | May | май |
10 | 1560428580 | 2019.06.13 | Jun | июн |
11 | 1563106980 | 2019.07.14 | Jul | июл |
12 | 1565785380 | 2019.08.14 | Aug | авг |
№ | timestamp | date('Y.m.d') | date('A') | date_ru('A') |
---|---|---|---|---|
1 | 1536322980 | 2018.09.07 | PM | ПОСЛЕ ПОЛУДНЯ |
2 | 1536366180 | 2018.09.08 | AM | ДО ПОЛУДНЯ |
№ | timestamp | date('Y.m.d') | date('a') | date_ru('a') |
---|---|---|---|---|
1 | 1536322980 | 2018.09.07 | pm | после полудня |
2 | 1536366180 | 2018.09.08 | am | до полудня |
№ | timestamp | date('Y.m.d') | date('jS') | date_ru('jS') |
---|---|---|---|---|
1 | 1536322980 | 2018.09.07 | 7th | 7-ое |
2 | 1536409380 | 2018.09.08 | 8th | 8-ое |
3 | 1536495780 | 2018.09.09 | 9th | 9-ое |
4 | 1536582180 | 2018.09.10 | 10th | 10-ое |
5 | 1536668580 | 2018.09.11 | 11th | 11-ое |
6 | 1536754980 | 2018.09.12 | 12th | 12-ое |
7 | 1536841380 | 2018.09.13 | 13th | 13-ое |
8 | 1536927780 | 2018.09.14 | 14th | 14-ое |
9 | 1537014180 | 2018.09.15 | 15th | 15-ое |
10 | 1537100580 | 2018.09.16 | 16th | 16-ое |
11 | 1537186980 | 2018.09.17 | 17th | 17-ое |
12 | 1537273380 | 2018.09.18 | 18th | 18-ое |
13 | 1537359780 | 2018.09.19 | 19th | 19-ое |
14 | 1537446180 | 2018.09.20 | 20th | 20-ое |
15 | 1537532580 | 2018.09.21 | 21st | 21-ое |
16 | 1537618980 | 2018.09.22 | 22nd | 22-ое |
17 | 1537705380 | 2018.09.23 | 23rd | 23-ье |
18 | 1537791780 | 2018.09.24 | 24th | 24-ое |
19 | 1537878180 | 2018.09.25 | 25th | 25-ое |
20 | 1537964580 | 2018.09.26 | 26th | 26-ое |
21 | 1538050980 | 2018.09.27 | 27th | 27-ое |
22 | 1538137380 | 2018.09.28 | 28th | 28-ое |
23 | 1538223780 | 2018.09.29 | 29th | 29-ое |
24 | 1538310180 | 2018.09.30 | 30th | 30-ое |
25 | 1538396580 | 2018.10.01 | 1st | 1-ое |
26 | 1538482980 | 2018.10.02 | 2nd | 2-ое |
27 | 1538569380 | 2018.10.03 | 3rd | 3-ье |
28 | 1538655780 | 2018.10.04 | 4th | 4-ое |
29 | 1538742180 | 2018.10.05 | 5th | 5-ое |
30 | 1538828580 | 2018.10.06 | 6th | 6-ое |
31 | 1538914980 | 2018.10.07 | 7th | 7-ое |
№ | timestamp | date('Y.m.d') | date('jS') | date_ru('jS') |
---|---|---|---|---|
1 | 1536322980 | 2018.09.07 | 7th | 7-ое |
2 | 2072002980 | 2035.08.29 | 29th | 29-ое |
3 | 2607682980 | 2052.08.19 | 19th | 19-ое |
4 | 3143362980 | 2069.08.10 | 10th | 10-ое |
5 | 3679042980 | 2086.08.01 | 1st | 1-ое |
6 | 4214722980 | 2103.07.24 | 24th | 24-ое |
7 | 4750402980 | 2120.07.14 | 14th | 14-ое |
8 | 5286082980 | 2137.07.05 | 5th | 5-ое |
9 | 5821762980 | 2154.06.26 | 26th | 26-ое |
10 | 6357442980 | 2171.06.17 | 17th | 17-ое |
№ | timestamp | date('Y.m.d') | date('jS') | date_ru('jS') |
---|---|---|---|---|
1 | 1536322980 | 2018.09.07 | 7th | 7-ое |
2 | 1000642980 | 2001.09.16 | 16th | 16-ое |
3 | 464962980 | 1984.09.25 | 25th | 25-ое |
4 | -70717020 | 1967.10.05 | 5th | 5-ое |
5 | -606397020 | 1950.10.14 | 14th | 14-ое |
6 | -1142077020 | 1933.10.23 | 23rd | 23-ье |
7 | -1677757020 | 1916.11.01 | 1st | 1-ое |
8 | -2213437020 | 1899.11.10 | 10th | 10-ое |
9 | -2749117020 | 1882.11.19 | 19th | 19-ое |
10 | -3284797020 | 1865.11.28 | 28th | 28-ое |
№ | timestamp | date('Y.m.d') | date('D, jS F Y, h:i a') | date_ru('D, jS F Y, h:i a') |
---|---|---|---|---|
1 | 1536322980 | 2018.09.07 | Fri, 7th September 2018, 03:23 pm | Пт, 7-ое сентября 2018, 03:23 после полудня |
2 | 1536452580 | 2018.09.09 | Sun, 9th September 2018, 03:23 am | Вс, 9-ое сентября 2018, 03:23 до полудня |
3 | 1536582180 | 2018.09.10 | Mon, 10th September 2018, 03:23 pm | Пн, 10-ое сентября 2018, 03:23 после полудня |
4 | 1536711780 | 2018.09.12 | Wed, 12th September 2018, 03:23 am | Ср, 12-ое сентября 2018, 03:23 до полудня |
5 | 1536841380 | 2018.09.13 | Thu, 13th September 2018, 03:23 pm | Чт, 13-ое сентября 2018, 03:23 после полудня |
6 | 1536970980 | 2018.09.15 | Sat, 15th September 2018, 03:23 am | Сб, 15-ое сентября 2018, 03:23 до полудня |
7 | 1537100580 | 2018.09.16 | Sun, 16th September 2018, 03:23 pm | Вс, 16-ое сентября 2018, 03:23 после полудня |
09Заключение
Получена функция, локализирующая результаты вывода встроенной функции date(). Возвращает отформатированную строку с датой на русском языке. Капустин Яков (Пятница, 7-ое сентября 2018, 15:23)
Капустин Яков (2018.09.07 15:23)