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

Главные вкладки

Аватар пользователя VladSavitsky VladSavitsky 28 февраля 2010 в 17:58

Вывод даты в виде "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);
        }
        else if ($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 КБ

Комментарии

Аватар пользователя PinkRabbit PinkRabbit 28 февраля 2010 в 18:22

огромное спасибо!
Но я подожду, пока включат в сам модуль Smile

Мне интересно - один я считаю, что модуль date с самого начала должен быть сделан по-иному - чтобы функцию вывода времени на каком-то языке писал разработчик, разговаривающий на этом языке.
А не извращаться с переводами - временные конструкции на разных языках совсем разные.

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

Аватар пользователя VladSavitsky VladSavitsky 1 марта 2010 в 0:13

На хабре топик удалось переправить в блог "Друпал" - спасибо всем, кто добавил карму.
Пришлось там урезать код и тот, что остался - привести в порядок (добавить специально подсветку синтаксиса).

Аватар пользователя cyberpunk cyberpunk 1 марта 2010 в 12:20

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

Аватар пользователя adubovskoy adubovskoy 1 марта 2010 в 12:36

"cyberpunk" wrote:
ак это будет заметнее для создателя модуля ;)

Поддерживаю. Отписался там. Есть практика - комментарии для модулей/патчей имеют большое значение для жизни кода.

Аватар пользователя goodboy goodboy 1 марта 2010 в 12:37

У меня была мысль использовать month-abbreviation |Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec
для месяцев в родительном падеже. Все равно аббревиатуры пропадают, у меня, по крайней мере. Но там все дело портил "May" (одинаково для аббревиатуры и полного названия месяца). Да и решение, конечно, не универсальное.

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

Влад, небольшая опечатка: !month-genitive |січеня ( січня, как указано в _calendar_list_conv_date )

Аватар пользователя VladSavitsky VladSavitsky 1 марта 2010 в 13:21

"Goodboy" wrote:
Влад, небольшая опечатка: !month-genitive |січеня ( січня, как указано в _calendar_list_conv_date )

Да уж. Правильно "січень" - сейчас исправлю везде. Спасибо.

Аватар пользователя lexaural@drupal.org lexaural@drupal.org 1 марта 2010 в 15:05

Извините а можно "левый" вопрос?
Что за тема используется в качестве примера - очень понравилась:-)

Заранее спасибо!
ЗЫ На хабре нету эккаунта так хоть тут нашел:-)

Аватар пользователя J. Bravo J. Bravo 1 марта 2010 в 23:57

Влад, у вас хоть на одном сайте есть места, где использовать названия месяцев нужно в именительном падеже? Я в свое время, когда решал эту проблему, просто заменил переводы с "март" на "марта" и т.д. и с тех пор ни разу не сталкивался с ситуацией, где этого бы не хватало.

Аватар пользователя VladSavitsky VladSavitsky 10 ноября 2015 в 11:46

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

Но на их сайте выводятся названия месяцев как в именительном, так и в родительном падеже.

Аватар пользователя RoSk0 RoSk0 10 июня 2010 в 12:03

Может не в тему
Влад, а к $submitted этот патч отношение имеет или только к сск полям?
Хочу добится того чтобы время в submitted отображалось как на http://drupaldance.com только без дня

Как добиться такого?

Аватар пользователя RoSk0 RoSk0 10 июня 2010 в 14:14

к вопросу выше: нужно в шаблоне материалы испольовать date_format_date() я правильно понимаю? справшиваю потому что при использовании

print(date_format_date($created,'custom','j F Y',$language));

ничего не происходит, а при

var_dump(date_format_date($created,'custom','j F Y',$language));

говорит: string(2) " "
пробовал без $language, результат тот же
помогите плиз

Аватар пользователя arax28@drupal.org arax28@drupal.org 21 сентября 2010 в 11:21

"<a href="mailto:darkkin@drupal.org">darkkin@drupal.org</a>" wrote:
Возможно торможу, но после патчинга в переводе не появилось строки !month-genitive*
Подскажите плиз что сделал не так.

"<a href="mailto:kervi@drupal.org">kervi@drupal.org</a>" wrote:
таже самая проблема...

Я столкнулся с той же проблемой. Решилось всё выполнением команды "переустановить модули" из devel для всех модулей date

Аватар пользователя unic@drupal.org unic@drupal.org 5 мая 2011 в 9:43

Получить корректную обработку дат для всего сайта только этим патчем не удастся. Есть еще функция format_date в common.inc, которую использует множество модулей. В том числе Views.

Мало того, исправлением format_date тоже не ограничиться, так как тот же Views обрабатывает тайтлы для каждого аргумента отдельно (пример - View для новостей с аргументами <год>/<месяц>/<день>). Печально.

Аватар пользователя greenzlat greenzlat 21 сентября 2011 в 8:24

Обновил функции в модуле, но в поиске для перевода не находит данную строку! В чем может быть проблема? Кэш чистил.

Аватар пользователя dors dors 18 октября 2011 в 12:15

нужно ещё calendar_list.zip установить в модули - тогда в поиске перевода появится !month-genitive

после проделанных процедур формат даты изменился, а вот вывод статей через views по-прежнему "18 Октябрь"

Аватар пользователя Jean-Claude Jean-Claude 15 октября 2011 в 15:28

"greenzlat" wrote:
Обновил функции в модуле, но в поиске для перевода не находит данную строку! В чем может быть проблема? Кэш чистил.

угу аналогично, добавил вручную эту строку через po файл но ничего не работает

даже указал строку 689 где это встречается в модуле Smile date_api.module:689

Аватар пользователя Никк Никк 29 июля 2015 в 12:59

Друпал 7. Теперь все названия месяцев только в родительном падеже. А на вкладке "Год" календаря, там где выводится все месяцы года, нужно именно в именительном падеже.