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

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

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

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

1
Введение

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

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

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

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

Code:
0
  if(true){} $arr_simbols=array("l""F""D""M""S""a""A",);

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

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

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

Code:
00
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
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
  $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:
0
  $res=date($format,$timestamp);

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

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

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

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

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

Code:
0
1
2
3
4
5
6

  
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);
  

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

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

Code:
00
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
32
33
34
35
36
37
  $arr_S=array(
    
// массив суффиксов в порядке убывания частоты употребления 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"=>"-ое",
    ),
  );

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

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

Code:
0
1
2
3
4
5
  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:
00
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
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
  // 2018_09_06 вывод системной даты/времени на русском языке (кроме 'r' Дата в формате RFC 2822)
  //==================================================================================================
  
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<class='error'>error#".__line__." in ".__function__.": \$s='$s'; \$c='$c'.</p>\n";
        
}
        unset(
$s,$c);
      }
    }

    
// выводим результат
    
return $res;
  }
  

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

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

Code:
00
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
  $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(). Возвращает отформатированную строку с датой на русском языке. Капустин Яков (Пятница, 7-ое сентября 2018, 15:23)