Quantcast
Channel: Блог VladSavitsky
Viewing all articles
Browse latest Browse all 52

Патч к модулю Date - показываем месяцы в родительном падеже

$
0
0

Вывод даты в виде "27 февраль 2010"это уродливо. Ни в русском, ни в украинском, ни в польском - и, я думаю, что это касается большинства других славянских языков - так не говорят и не пишут.

НЕправильные даты в выводе ноды

Понятно, что через какое-то время привыкаешь и просто не замечаешь, но есть ведь настойчивые заказчики, которые тыкают носом разработчика в эти "ляпы"и требующие исправления ошибок (по их мнению). Спасибо им за это. Это нужно, хотя и раздражает.

Вывод блока с кастомным обработчиком дат.

Итак, по требованию заказчика вывести в блоке нормальные даты я написал модуль. Модуль выводит блок, где определённым образом форматируется вывод анонсов и собственно исправляются даты. Точно такой же блок можно сделать во Views, если бы не кривые даты...

Короче говоря код модуля я приводить не буду, а только функцию, которая исправляет даты. Этот код может очень пригодится тем, кто не сможет дочитать пост до конца.

Ок. Код всего модуля приложен к статье, чтобы уменьшить соотношение код/текст, чтобы не объяснять как делается вызов фунции и в каком формате должна быть дата - изучайте сами, если это вам нужно. Даты начала события и конца хранятся в одном ССК-поле, которое называется field_event_date и имеет тип Date.

Но этот модуль можно не использовать, а применить патч для модуля Date, но об этом ниже.

// Функция преобразует даты в "правильные", то есть в родительном падеже и ещё разные штучки делает.
function _calendar_list_conv_date($a, $format='front'){
  global$language;
  $date = strtotime($a);
  switch($format){
    case'front':
      $monthes = array(
        'en' =>array('January','February','March','April','May','June','July','August','September','October','November','December'),
        'ru' =>array('января','февраля','марта','апреля','мая','июня','июля','августа','сентября','октября','ноября','декабря'),
        'uk' =>array('січня','лютого','березня','квітня','травня','червня','липня','серпня','вересня','жовтня','листопада','грудня'),
      );
      $weekday = array(
        'en' =>array('Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday'),
        'ru' =>array('Понедельник','Вторник','Среда','Четверг','Пятница','Суббота','Воскресенье'),
        'uk' =>array('Понеділок','Вівторок','Середа','Четвер','П’ятниця','Субота','Неділя'),
      );
      switch($language->language){
        case'ru':
        case'uk':
            $output = date("j",$date) .''. $monthes[$language->language][date("n",$date)-1] .', '. $weekday[$language->language][date("N",$date)-1];
            break;
        case'en':
        default:
            $output = $monthes[$language->language][date("n",$date)-1] .''. date("j",$date) .', '. $weekday[$language->language][date("N",$date)-1];
            break;
      }

      break;
    case'short':
      $output = date('d.m.Y', $date); //20.02.2010
      break;
    default:
      $output = date("j",$date).''.$monthes[date("n",$date)-1].''.date("Y",$date).''.date("H",$date).':'.date("i",$date);
      break;
  }
  return$output;
}

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

Я понял, что нужно кардинально другое решение - универсальное и системное. Очень много времени ушло на то, чтобы понять какой именно модуль и как меняет даты - возможно сказалось то, что температура была большая.
Сам код решения был написан и оттестирован где-то за полдня.

Исправленные даты - месяц в родительном падеже

Патч модуля Date, чтобы получить корректную обработку дат для всего сайта

Изменяются только 3 функции в файле date_api.moduleмодуля Date:

  • date_t
  • date_t_strings
  • date_format_date

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

/**
 * A function to translate ambiguous short date strings.
 *
 * Example: Pass in 'Monday', 'day_abbr' and get the translated
 * abbreviation for Monday.
 *
 *  param string $string
 *  param string $context
 *  param int $langcode
 *  return translated value of the string
 */

function date_t($string, $context, $langcode = NULL){
  //static $replace = array();

  if(empty($replace[$langcode])){
    // The function to create the date string arrays is kept separate
    // so those arrays can be directly accessed by other functions.
    date_t_strings($replace, $langcode);
  }
  switch($context){
    case'day_name':
    case'day_abbr':
    case'day_abbr1':
    case'day_abbr2':  
      $untranslated = array_flip(date_week_days_untranslated());
      break;
    case'month_name':
    case'month_genitive': //Добавляем в обработку новый контекст
    case'month_abbr':
      $untranslated = array_flip(date_month_names_untranslated());
      break;  
    case'ampm':
      $untranslated = array_flip(array('am', 'pm', 'AM', 'PM'));  
      break;
    case'datetime':
      $untranslated = array_flip(array('Year', 'Month', 'Day', 'Week', 'Hour', 'Minute', 'Second', 'All Day', 'All day'));  
      break;
    case'datetime_plural':
      $untranslated = array_flip(array('Years', 'Months', 'Days', 'Weeks', 'Hours', 'Minutes', 'Seconds'));  
      break;
    case'date_order':
      $untranslated = array_flip(array('Every', 'First', 'Second', 'Third', 'Fourth', 'Fifth'));  
      break;
    case'date_order_reverse':
      $untranslated = array_flip(array('', 'Last', 'Next to last', 'Third from last', 'Fourth from last', 'Fifth from last'));  
      break;
    case'date_nav':
      $untranslated = array_flip(array('Prev', 'Next', 'Today'));  
      break;
  }
  $pos = $untranslated[$string];
  return$replace[$langcode][$context][$pos];
}

/**
 * Construct translation arrays from pipe-delimited strings.
 *
 * Combining these strings into a single t() gives them the context needed
 * for better translation.
 */

function date_t_strings(&$replace, $langcode){
  $replace[$langcode]['day_name'] = explode('|', trim(t('!day-name Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday', array('!day-name' =>''), $langcode)));
  $replace[$langcode]['day_abbr'] = explode('|', trim(t('!day-abbreviation Sun|Mon|Tue|Wed|Thu|Fri|Sat', array('!day-abbreviation' =>''), $langcode)));
  $replace[$langcode]['day_abbr1'] = explode('|', trim(t('!day-abbreviation S|M|T|W|T|F|S', array('!day-abbreviation' =>''), $langcode)));
  $replace[$langcode]['day_abbr2'] = explode('|', trim(t('!day-abbreviation SU|MO|TU|WE|TH|FR|SA', array('!day-abbreviation' =>''), $langcode)));
  $replace[$langcode]['ampm'] = explode('|', trim(t('!ampm-abbreviation am|pm|AM|PM', array('!ampm-abbreviation' =>''), $langcode)));
  $replace[$langcode]['datetime'] = explode('|', trim(t('!datetime Year|Month|Day|Week|Hour|Minute|Second|All Day|All day', array('!datetime' =>''), $langcode)));
  $replace[$langcode]['datetime_plural'] = explode('|', trim(t('!datetime_plural Years|Months|Days|Weeks|Hours|Minutes|Seconds', array('!datetime_plural' =>''), $langcode)));
  $replace[$langcode]['date_order'] = explode('|', trim(t('!date_order Every|First|Second|Third|Fourth|Fifth', array('!date_order' =>''), $langcode)));
  $replace[$langcode]['date_order_reverse'] = explode('|', trim(t('!date_order |Last|Next to last|Third from last|Fourth from last|Fifth from last', array('!date_order' =>''), $langcode)));
  $replace[$langcode]['date_nav'] = explode('|', trim(t('!date_nav Prev|Next|Today', array('!date_nav' =>''), $langcode)));
 
  // These start with a pipe so the January value will be in position 1 instead of position 0.
  $replace[$langcode]['month_name'] = explode('|', trim(t('!month-name |January|February|March|April|May|June|July|August|September|October|November|December', array('!month-name' =>''), $langcode)));
  //Строка с названиями месяцев, которую ещё нужно будет перевести на нужные языки:
  $replace[$langcode]['month_genitive'] = explode('|', trim(t('!month-genitive |January|February|March|April|May|June|July|August|September|October|November|December', array('!month-name' =>''), $langcode)));
  $replace[$langcode]['month_abbr'] = explode('|', trim(t('!month-abbreviation |Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec', array('!month-abbreviation' =>''), $langcode)));

}

/**
 * Reworked from Drupal's format_date function to handle pre-1970 and
 * post-2038 dates and accept a date object instead of a timestamp as input.
 *
 * Translates formatted date results, unlike PHP function date_format().
 *
 *  param $oject
 *   A date object, could be created by date_make_date().
 *  param $type
 *   The format to use. Can be "small", "medium" or "large" for the preconfigured
 *   date formats. If "custom" is specified, then $format is required as well.
 *  param $format
 *   A PHP date format string as required by date(). A backslash should be used
 *   before a character to avoid interpreting the character as part of a date
 *   format.
 *  return
 *   A translated date string in the requested format.
 */

function date_format_date($date, $type = 'medium', $format = '', $langcode = NULL){
  if(empty($date)){
    return'';
  }
  switch($type){
    case'small':
    case'short':
      $format = variable_get('date_format_short', 'm/d/Y - H:i');
      break;
    case'large':
    case'long':
      $format = variable_get('date_format_long', 'l, F j, Y - H:i');
      break;
    case'custom':
      $format = $format;
      break;
    case'medium':
    default:
      $format = variable_get('date_format_medium', 'D, m/d/Y - H:i');
  }
  $max = strlen($format);
  $datestring = '';
  for($i = 0; $i<$max; $i++){
    $c = $format[$i];
    switch($c){
      // Use date_t() for ambiguous short strings that need translation.
      // We send long day and month names to date_t(), along with context.
      case'l':
        $datestring .= date_t(date_format($date, 'l'), 'day_name', $langcode);
        break;
      case'D':
        $datestring .= date_t(date_format($date, 'l'), 'day_abbr', $langcode);
        break;
      case'F':
        //Собственно логика вывода названия месяца в зависимости от ситуации:
        if((strpos($format, 'd') === FALSE)&&(strpos($format, 'j') === FALSE)){
          $datestring .= date_t(date_format($date, 'F'), 'month_name', $langcode);
        }
        else{
          $datestring .= date_t(date_format($date, 'F'), 'month_genitive', $langcode);
        }
        break;
      case'M':
        $datestring .= date_t(date_format($date, 'F'), 'month_abbr', $langcode);
        break;  
      case'A':
      case'a':
        $datestring .= date_t(date_format($date, $c), 'ampm', $langcode);
        break;  
      // The timezone name translations can use t().  
      case'e':
      case'T':
        $datestring .= t(date_format($date, $c));
        break;
      // Remaining date parts need no translation.
      case'O':
        $datestring .= sprintf('%s%02d%02d', (date_offset_get($date)<0 ? '-' : '+'), abs(date_offset_get($date)/3600), abs(date_offset_get($date)%3600)/60);
        break;
      case'P':
        $datestring .= sprintf('%s%02d:%02d', (date_offset_get($date)<0 ? '-' : '+'), abs(date_offset_get($date)/3600), abs(date_offset_get($date)%3600)/60);
        break;
      case'Z':
        $datestring .= date_offset_get($date);
        break;              
      case'\\':
        $datestring .= $format[++$i];
        break;
      default:
        if(strpos('BdcgGhHiIjLmnsStTUwWYyz', $c)!== FALSE){
          $datestring .= date_format($date, $c);
        }
        elseif($c == 'r'){
          $datestring .= format_date($date, 'custom', 'D, d M Y H:i:s O', $langcode);
        }
        else{
          $datestring .= $c;
        }
    }
  }
  return$datestring;
}

Я добавил комменты в код. Идея в том, что название месяца в родительном падеже должно выводиться только, если выводится рядом с числоммесяца. Во всех остальных случаях - именительный падеж.

Пример:

23 февраля 2010, а не 23 февраль 2010.
Но март 2010, а не марта 2010

Как заставить все это работать?

  • Применить патч, который приложен к статье или скопировать уже изменённые функции вместо старых. Рецепт о том, как применить патч (есть варианты для Линукс, и для Винды): Применение заплат (patch)
  • Открыть страницу "Перевод интерфейса" (admin/build/translate/search) и найти строку:
    !month-genitive |January|February|March|April|May|June|July|August|September|October|November|December
  • Сделать её перевод на нужный язык. Учтите, что для английского названия месяцев обычно пишутся с заглавной, а у нас - нет. Названия
    месяцев должны быть в родильном падеже. Для русского и украинского языков это выглядит так:
    • Русский:
      !month-genitive |января|февраля|марта|апреля|мая|июня|июля|августа|сентября|октября|ноября|декабря
    • Украинский:
      !month-genitive |січня|лютого|березня|квітня|травня|червня|липня|серпня|вересня|жовтня|листопада|грудня
  • Сохранить переводы и проверить отображение дат.

Будущее патча

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

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

ВложениеРазмер
Иконка пакетаcalendar_list.zip2.25 КБ
Файлdate_api.module.patch1.62 КБ
Версия Drupal:
0 Thanks

Viewing all articles
Browse latest Browse all 52

Trending Articles