Набор PHP-функции для создания user-friendly интерфейса на русском языке

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

    Они умеют склонять существительные по числовому признаку, выводить дату с нормальными русскими названиями месяцев и выводить дату в удобочитаемом человекопонятном виде (вчера, позавчера, 2 дня 3 часа и 2 минуты назад, через 1 год и 2 месяца и т.д.).

    Использовать их можно примерно так:
    <acrnonym title="<?php print r_date($timestamp'j M Y в H:i'false); ?>"><?php print human_date($timestamp2false);?></acronym>
    <acrnonym title="<?php print r_date($timestamp'j M Y в H:i'); ?>"><?php print human_date($timestamp);?></acronym>


    Это вернет примерно такой результат:
    <acrnonym title="2 января 2009 в 23:39">1 день назад</acronym>
    <acrnonym title="2 января в 23:39">Вчера</acronym>

    (Хабр обрезает тег <acronym>)

    И вот так:
    <?php
    $count 
    10;
    printf('%d %s'$countdeclension($count, array('комментарий''комментария''комментариев')));
    ?>


    Это вернет:
    10 комментариев

    Собственно, функции:

    <?php

    /** 
    * Склонение существительных по числовому признаку
    *
    * @var integer    Число, по которому производится склонение
    * @var array    Массив форм существительного
    * @return string Существительное в нужной форме

    * Например:
    * $count = 10;
    * sprintf('%d %s', $count, declension($count, array('комментарий', 'комментария', 'комментариев')));
    *
    * Возвращает:
    * 10 комментариев
    */
    function declension($number$words) {
        
    $number abs($number);
        if (
    $number 20$number %= 10;
        if (
    $number == 1) return $words[0];
        if (
    $number >= && $number <= 4) return $words[1];
        return 
    $words[2];
    }

    /** 
    * Приводит дату к заданному формату с учетом русских названий месяцев
    *
    * В качестве параметров функция принимает все допустимые значения функции date(),
    * но символ F заменяется на русское название месяца (вне зависимости от локали),
    * а символ M — на русское название месяца в родительном падеже
    *
    * @var integer    Unix-timestamp времени 
    * @var string    Формат даты согласно спецификации для функции date() с учетом замены значения символов F и M
    * @var boolean    Флаг отсекания года, если он совпадает с текущим
    * @return string Отформатированная дата
    */
    function r_date($time ''$format 'j M Y'$cut_year true) {
        if(empty(
    $time)) $time time();
        if(
    $cut_year && date('Y') == date('Y'$time)) $format preg_replace('/o|y|Y/'''$format);
        
    $month abs(date('n'$time)-1);
        
    $rus = array('января''февраля''марта''апреля''мая''июня''июля''августа''сентября''октября''ноября''декабря');
        
    $rus2 = array('январь''февраль''март''апрель''май''июнь''июль''август''сентябрь''октябрь''ноябрь''декабрь');
        
    $format preg_replace(array("'M'""'F'"), array($rus[$month], $rus2[$month]), $format);
        return 
    date($format$time);
    }


    /** 
    * Выводит дату в приблизительном удобочитаемом виде (например, "2 часа и 13 минут назад")
    *
    * Необходимо наличие функции declension() для корректной работы
    *
    * @var integer    Unix-timestamp времени 
    * @var integer    Степень детализации
    * @var boolean    Флаг использования упрощенных названий (вчера, позавчера, послезавтра)
    * @var string    Формат даты с учетом замены значения символов F и M, если объявлена функция r_date()
    * @return string Отформатированная дата
    */
    function human_date($timestamp$granularity 1$use_terms true$default_format 'j M Y') {
        
    $curtime time();
        
    $original $timestamp;
        
    $output '';
        if(
    $curtime >= $original) {
            
    $timestamp abs($curtime $original);
            
    $tense 'past';
        } else {
            
    $timestamp abs($original $curtime);
            
    $tense 'future';
        }
        
    $units = array('years' => 31536000,
                     
    'weeks' => 604800
                     
    'days' => 86400
                     
    'hours' => 3600
                     
    'min' => 60
                     
    'sec' => 1);
        
    $titles = array('years' => array('год''года''лет'),
                     
    'weeks' => array('неделя''недели''недель'), 
                     
    'days' => array('день''дня''дней'), 
                     
    'hours' => array('час''часа''часов'), 
                     
    'min' => array('минута''минуты''минут'), 
                     
    'sec' => array('секунда''секунды''секунд'));
        foreach (
    $units as $key => $value) {
            if (
    $timestamp >= $value) {
                
    $number floor($timestamp $value);
                
    $output .= ($output ? ($granularity == ' и ' ' ') : '') . $number .' 'declension($number$titles[$key]);
                
    $timestamp %= $value;
                
    $granularity--;
            }
        }
        if(
    $tense == 'future') {
            
    $output 'Через '.$output;
        } else {
            
    $output .= ' назад';
        }
      if(
    $use_terms) {
          
    $terms = array('Через 1 день' => 'Послезавтра',
                         
    '1 день назад' => 'Вчера',
                         
    '2 дня назад' => 'Позавчера'
                         
    );
          if(isset(
    $terms[$output])) $output $terms[$output];
      }
      return 
    $output $output : (function_exists('r_date') ? r_date($original$default_format) : date($default_format$original));
    }
    ?>


    Надеюсь, кому-нибудь они пригодятся.
    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 26

      +2
      Cпасибо, пригодиться. Хотя у меня тоже есть в рукаве такие штуки :P
        –3
        хы какраз на днях примерно такое же писал)))
          +3
          Не вижу смысла гнаться за милисекундами, но имхо
          $format = str_replace(array("M", "F"), array($rus[$month], $rus2[$month]), $format);
          логичнее, чем
          $format = preg_replace(array("'M'", "'F'"), array($rus[$month], $rus2[$month]), $format);
            +1
            Согласен. Думаю, там много мест, где можно оптимизировать код. В этом месте просто раньше был регэксп, который я потом заменил, а функцию просто не переписал, потому что, видимо, недоглядел.
          • UFO just landed and posted this here
              +3
              Что юникод?
              • UFO just landed and posted this here
              0
              извините, а давно ли можно в пыхе писать
              return $words[];
              это вообще говоря fatal error
                +1
                наверно return $words[0]; имелось ввиду?
                  0
                  Да, конечно. Спасибо, что заметили. Хайлайтер или нло похитило нолик. (:
                    0
                    Видимо, все-таки НЛО, поскольку при редактировании поста в этом месте стоит <font color="#0000BB">0</font>, а после прогонки через парсер вся конструкция полностью исчезает.
                +2
                Я бы еще добавил «нет комментариев».
                  +1
                  Ну, это скорее нужно делать за пределами этой функции, т.к. она именно для склонения существительных, а не только для комментов. Обычную логическую конструкцию на проверку переменной $count перед вызовом функции сделать логичнее с точки зрения универсальности, имхо.
                    0
                    а я бы сделал все-таки внутри функции, скажем первым элементом массива «нет комментариев» (что-либо для нулевого значения), ну а дальше как положено
                  +1
                  echo human_date(strtotime('+1 day'));
                  почему послезавтра?:)
                    +1
                    Потому что косяк, да. Чуть позже вникну и поправлю. Спасибо.
                    0
                    вот бы еще таких статей сюда, только мне кажется надо в отдельный блог — «php, готовые решения»
                      0
                      лишь бы не забивать этот блог всеми функциями подряд
                      0
                      Для функции declension() я бы использовал более продвинутый вариант:

                      function declension($digit,$expr,$onlyword=false)
                      {
                          if(!is_array($expr)) $expr = array_filter(explode(' ', $expr));
                          if(empty($expr[2])) $expr[2]=$expr[1];
                          $i=preg_replace('/[^0-9]+/s','',$digit)%100; //intval не всегда корректно работает
                          if($onlyword) $digit='';
                          if($i>=5 && $i<=20) $res=$digit.' '.$expr[2];
                          else
                          {
                              $i%=10;
                              if($i==1) $res=$digit.' '.$expr[0];
                              elseif($i>=2 && $i<=4) $res=$digit.' '.$expr[1];
                              else $res=$digit.' '.$expr[2];
                          }
                          return trim($res);
                      }


                      Позволяет писать, к примеру, так:

                      echo 'Написано '.declension('<b>33</b>','комментарий комментария комментариев');

                      (выведет «написано 33 комментария»).

                      А вообще, Ваша функция (как и название) очень похожа на то, что представлено на форуме дклаб: forum.dklab.ru/viewtopic.php?t=6760
                        0
                        Кстати для подсчета правильного относительного смещения по времени тоже есть класс, который это делает:

                        forum.dklab.ru/viewtopic.php?p=35068#35068
                          0
                          Да, прямая ссылка на класс: m.forum.dklab.ru/files/date_deltarussian.zip
                            0
                            По-моему, класс для таких мелочей — это перебор. Но за ссылку спасибо, покопаюсь.
                            +1
                            Название — это слово «склонение» на английском. Ничего удивительного. (: А функция похожа только в том месте, где собственно склоняется. Это, в общем, довольно распространенный алгоритм.

                            Насчет приведенной функции согласен, обычно это удобнее. Если в склоняемых формах нет пробела. Правда регексп для выделения числа из форматированной строки — совершенно лишнее, имхо.
                            0
                            В русском языке есть небольшая нелогичность в согласовании существительных с числом:
                            1 комментарий,
                            2 комментария,
                            5 комментариев,
                            23 комментария,
                            71 комментарий…
                            Но!
                            11 комментариев,
                            12 комментариев,
                            13 комментариев,
                            14 комментариев.
                            Поэтому нужно проверять не только число единиц, но и число десятков.
                            У меня похожая функция выглядела так:

                            /* Вывод оповещения о непрочитанных сообщениях выбранного пользователя */
                            function getUserNewMessageNotice(&$DB, $userId)
                            {
                            $MESSAGE_COUNT = getUserNewMessageCount($DB, $userId);

                            if (accountIsBanned($DB, $_SESSION['userId'], $_SESSION['userStatus'])
                            || accountIsDeleted($DB, $_SESSION['userId'], $_SESSION['userStatus'])
                            || $MESSAGE_COUNT == 0)
                            { // Если новых сообщений нет или пользователь заблокирован или удалён, напоминание не выводится
                            return NULL;
                            }

                            $NOTICE = 'В Вашем поч­то­вом ящи­ке ';

                            switch ($MESSAGE_COUNT)
                            {
                            // Согласование существительного <сообщение> в нужном числе с количеством новых сообщений
                            // Одно сообщение, два сообщение, три сообщение, четыре сообщение, двадцать одно сообщение, тридцать два сообщение,
                            // НО! Одиннадцать писЕМ, двенадцать сообщений, тринадцать сообщений и четырнадцать сообщений.
                            // Для всех чисел, оканчивающихся на 1, 2, 3 и 4 окончание будет <-ий>, кроме 11, 12, 13 и 14.
                            // Для всех остальных - <ие>. Такой вот он забавный, русский язык.

                            case ((1 != floor(($MESSAGE_COUNT % 100) / 10)) && ($MESSAGE_COUNT % 10 == 1)):
                            $NOTICE .= 'на­хо­дит­ся '.$MESSAGE_COUNT.' но­вое со­об­ще­ние.';
                            break;

                            case ((1 != floor(($MESSAGE_COUNT % 100) / 10)) && (($MESSAGE_COUNT % 10 == 2) || ($MESSAGE_COUNT % 10 == 3) || ($MESSAGE_COUNT % 10 == 4))):
                            $NOTICE .= 'на­хо­дят­ся '.$MESSAGE_COUNT.' но­вых со­об­ще­ния.';
                            break;

                            default:
                            $NOTICE .= 'находятся '.$MESSAGE_COUNT.' но­вых со­об­ще­ний.';
                            break;
                            }
                            return $NOTICE;
                            }

                            Код корявый и намертво вгрызается в свои модули со вспомогательными функциями, но общий смысл, я думаю, понятен. В случае, если непрочитанных сообщений нет, это напоминание просто не выводится.
                              +1
                              <?php
                              $numbers = array(1, 2, 5, 23, 71, 11, 12, 13, 14);
                              $words = array('комментарий', 'комментария', 'комментариев');
                              foreach($numbers as $number) printf('%d %s
                              ', $number, declension($number, $words));
                              ?>

                              Выводит:
                              1 комментарий
                              2 комментария
                              5 комментариев
                              23 комментария
                              71 комментарий
                              11 комментариев
                              12 комментариев
                              13 комментариев
                              14 комментариев


                              Почитайте внимательнее приведенную мной функцию. Эта особенность там учитываются, если число больше 20. Только строчек в ней значительно меньше.
                                0
                                Да, вы совершенно правы, у меня с самого начала в голове был совсем другой алгоритм согласования, поэтому и вашем увидел совсем не то, что нужно )

                              Only users with full accounts can post comments. Log in, please.