Глаза страшат, а руки делают

Велик могучим русский языка
О программировании русских склонений в скриптах, содержащих смену даты или времени
ЧАСТЬ ПЕРВАЯ


Статья предназначена тем, кто уже немного ковырялся в JavaScript и может «сыграть» на нём хотя бы гамму до-мажор одним пальцем. Лучше — всеми.


Те, кто заглядывал ко мне во время ремонта, возможно, обратили внимание на сообщение «Ремонт идёт 2 дня» или «...5 дней» (в зависимости от того, когда заглянули). Так вот, дни ремонта считает скрипт, и он же правильно склоняет слово «дни».

Скриптов-считалок разбросано в инете великое множество, эту считалку я тоже не сам сочинил, но вот грамотных русскоязычных считалок я не встречал ни в действии, ни в исходниках (увидите — обязательно киньте ссылочку!).

Ту часть скрипта, которая отвечает за склонение «дней», написал я, чем хочу с вами и поделиться.

Вот исходник, который я скачал на сайте http://cgi.myweb.ru.

<script type="text/javascript">

<!-- Begin
var montharray=new Array("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug",
"Sep","Oct","Nov","Dec");
function countup(yr,m,d) {
var today=new Date();
var todayy=today.getYear();

if ((navigator.appName=="Microsoft Internet Explorer")&&(todayy < 2000))
todayy="19" + todayy;
if (navigator.appName == "Netscape")
todayy=1900 + todayy;

var todaym=today.getMonth();
var todayd=today.getDate();
var todaystring=montharray[todaym]+" "+todayd+", "+todayy;
var paststring=montharray[m-1]+" "+d+", "+yr;
var difference=(Math.round((Date.parse(todaystring)-Date.parse(paststring))/
(24*60*60*1000))*1);

document.write("Вот это было создано " + difference + " дней назад.");
}
countup(2000,08,29);  //Укажите здесь дату:  (year,month,day)

//  End  -->
</script>

И будь оно короче иль длинней, то лишь один мгновенно промелькнувший «дней», то двести пятьдесят четыре «дней» — всё дней, да дней, да дней....

***

Итак, мы знаем, что

1, 21, 151 и т.д. — ДЕНЬ;

2, 3, 4 и т.д. — ДНЯ;

остальное — ДНЕЙ.

Заметим, что 11, 12, 13, 14 — тоже ДНЕЙ. Это обстоятельство чуть исхитрит нашу задачу.

Как видно из строки скрипта

document.write("Вот это было создано " + difference + " дней назад.");

число дней вводится в переменную difference.

Значит, нам нужно каким-то образом отсечь и протестировать значения 2 последних цифр (предпоследняя для 11, 12 и т.д.) и в зависимости от результатов теста выводить соответствующую форму «день-дня-дней».

Для обработки строк в javascript есть много методов. Нам может понадобиться метод substr() или метод substring(), которые по указанным параметрам находят в строке подстроку (то есть указанную часть строки).

Какой из них лучше? В данном случае — всё равно. Я воспользовался первым. Давайте рассмотрим оба.

Метод substr()

Синтаксис:

строка.substr(аргумент1, аргумент2)

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

Обратите внимание: он указывает не на первый символ подстроки, а на предыдущий, так что если мы выделяем подстроку с первого символа строки, то ставим 0.

Второй аргумент указывает количество символов в подстроке, попросту говоря — на её длину (тут уже без дураков: как слышится, так и пишется).

Например, чтобы превратить «пространство» в «транс», нужно записать:

<script type="text/javascript">
document.write("пространство".substr(4, 5);
</script>

Можете скопировать этот код в «Блокнот», сохранить его как (именно «как», или «as»), допустим, test1.htm или test1.html, потом открыть интернет-браузером и посмотреть, что вышло. Можно ещё поиграться — поменять строку, цифирки и почувствовать себя почти крутым программером.

Метод substring()

Синтаксис такой же:

строка.substring(аргумент1, аргумент2)

Первый аргумент идентичен первому аргументу предыдущего метода (и тоже с нуля).

Второй аргумент указывает последний символ выбранной подстроки (и тоже с единицы).

А наш «транс» будет выглядеть так:

<script type="text/javascript">
document.write("пространство".substring(4, 9);
</script>

Ну хорошо, но ведь в нашй величине всё время разное число символов. Как же мы определим, какие аргументы нам нужны?

На ту беду у строк существуют не только методы, но и свойства. Например, свойство length, то есть, как понял, думаю, любой ёжик, — длина.

Не нужно быть великим математиком, чтобы понять, что последний символ любой строки равен её длине, предпоследний — её длине минус 1 и т.д.

Помним, что первый аргумент на единицу меньше реальной величины.

Значит, последний символ нашей переменной будет

difference.substr(difference.length - 1, 1)

или

difference.substring(difference.length - 1, difference.length),

предпоследний —

difference.substr(difference.length - 2, 1)

или

difference.substring(difference.length - 2, difference.length - 1)...

...Вот так и я сгоряча подумал. Но умный браузер сообщил мне, что «тип не поддерживает метод». Если вы немного разбираетесь в языке — вчитайтесь в исходник, и поймёте, что difference содержит не строковое, а числовое значение. А у них другие свойства и методы...

...И среди этих методов есть один, который называется toString(), и он конвертирует число to string, то есть в строку. Что нам и надо.

***

Приступаем к доработке скрипта. Нам будут нужны следующие значения:

  • Строка, конвертированная из difference;
  • последний символ этой строки;
  • предпоследний символ этой строки;
  • переменная, выводящая нужное склонение слова «дней».

Итого — 4 переменных. Давайте их объявим. Грамотнее всего вставить это куда-нибудь в начало скрипта, среди других объявлений (грубо говоря — «туды, где var». Не мудрствуя лукаво, обзовём их:

var a, b, c, d

Примечание 14.05.2006: В некоторых случаях (например, когда я использовал функцию как файл.js) обнаружился баг, связанный с тем, что d уже используется как аргумент функции countup(). Я просто переименовал его в dy. Или можно дать другое имя нашей переменной d.

Как вы заметили, переменная difference объявлена уже с конкретным значением. Вот после этой объявы добавим такую строчку:

d = difference.toString(10)

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

Далее назначаем на переменные нужные подстроки d:

b = d.substr(d.length - 1, 1)   //последняя цифра
c = d.substr(d.length - 2, 1)   //предпоследняя цифра

Кстати, поняли, почему я выбрал первый метод? Короче выходит, а мне печатать лениво...

Всё, теперь можно приступать к самому важному — созданию тела функции, которая обучит компьютер изъясняться на нормальном русском языке.

Тут я немного поскрипел мозгами, комбинируя, и в результате пришёл к такой последовательности (можно, наверно, и поизящнее, но и эта работает). Сначала приведу её «в переводе на русский»:

Если c = 1 и длина d не равна 1, то a = " дней" //искл. для 11, 12 и т.д.
    в противном случае
    если b = 1, то a = " день".
        в противном случае
        если b = 2 или 3 или 4, то a = " дня"
            в противном случае  //т.е. во всех остальных случаях
            a = " дней"

Почему в первом случае нужно указать, что длина d не равна 1? Потому что, если она равна 1, то несуществующий предпоследний символ javascript приравнивает к последнему, и в результате — «1 дней», «2 дней» и т.п.

Итак, переведём с русского на javascript:

if (c == 1 && d.length != 1){a = " дней"}
    else {
    if (b == 1){a = " день"}
        else {
        if (b == 2 || b == 3 || b == 4){a = " дня"}
            else {
            a = " дней"
                 }
             }
         }

Вот как выглядит дополненный скрипт:

<script type="text/javascript">

<!-- Begin
var montharray=new Array("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug",
"Sep","Oct","Nov","Dec");
function countup(yr,m,d) {
var today=new Date();
var todayy=today.getYear();

var a, b, c, d

if ((navigator.appName == "Microsoft Internet Explorer") && (todayy < 2000
))		
todayy="19" + todayy;
if (navigator.appName == "Netscape")
todayy=1900 + todayy;

var todaym=today.getMonth();
var todayd=today.getDate();
var todaystring=montharray[todaym]+" "+todayd+", "+todayy;
var paststring=montharray[m-1]+" "+d+", "+yr;
var difference=(Math.round((Date.parse(todaystring)-Date.parse(paststring))/
(24*60*60*1000))*1);

d = difference.toString(10)
b = d.substr(d.length - 1, 1)   //последняя
c = d.substr(d.length - 2, 1)   //предпоследняя

if (c == 1 && d.length != 1){a = " дней"} //искл. для 11, 12 и т.д.
    else {
    if (b == 1){a = " день"}
        else {
        if (b == 2 || b == 3 || b == 4){a = " дня"}
            else {
            a = " дней"
                 }
             }
         }

document.write("Вот это было создано " + d + a + " назад.");
}
countup(2000,08,29);  //Укажите здесь дату:  (year,month,day)

//  End  -->
</script>

А зачем там два куска цветом выделены?

Ещё одна деталь, не имеющая отношения к теме статьи, но помогающая улучшить скрипт.

Исходник старый, и в нём использован «кривой» метод getYear(), который глючит в большинстве браузеров со значениями после 2000 года. В строках с розовым фоном автор искусственно «выпрямляет» результаты метода (не упоминая, однако, браузер «Opera», которого тогда, возможно, ещё не было).

Но теперь есть другой метод — getFullYear(), который не нуждается в поправках.

Предлагаю:

  • В зелёной строке написать var todayy=today.getFullYear();
  • розовый фрагмент в таком случае убрать за ненадобностью.

***

Теперь осталось отредактировать document.write() под собственные нужды и указать в countup своё стартовое время.

Если использовать именно эту форму скрипта, то нужно

  • изменить текст (кроме переменных) в document.write() сообразно своим надобностям;
  • заменить значения countup() в последней строке;
  • вставить этот скрипт нужно не в «голову» документа, а в то место, где этот текст должен появиться на странице.

И так — в каждом конкретном случае.

Если подойти к этому делу с головой, то можно сделать скрипт более удобным и универсальным.

Как он устроен? В нём 2 части. Основная часть — от строки function countup(yr,m,d) { до последней нижней закрывающей фигурной скобки — это функция countup, которая считает заданные параметры. Строка countup(2000,08,29); — это выполнение функции в теле документа. Сюда и подставляются нужные вам аргументы.

Весь код можно разбить на два скрипта. Первый — это весь код без последней строки. Его можно вставить в «голову» документа или же записать в отдельный файл с расширением .js. В document.write() можно оставить только

document.write(d + a);

Это даст нам текст «х дней» (в зависимости от значения х — «день» или «дня»).

Последняя строка вставляется отдельным скриптом в нужный участок тела документа. На моей «ремонтной» странице в <head> стоит ссылка на файл скрипта с функцией:

<script src="daystartru.js" type="text/javascript"></script>

А в теле документа — следующее:

<h3><i>Ремонт идёт <script type="text/javascript">countup(2006,04,16);
</script>.</i></h3>

На другой странице ссылка на тот же файл, а в контенте —

<h3><span class="red">Напоминаю, что сайт на реконструкции; упомянутые
страницы скоро вернутся на место в новом облике. Ремонт начался 
<script type="text/javascript">countup(2006,04,16);</script> назад.</span>
</h3>

Маленькая инструкция по установке:

  • Скопируйте полный текст дополненного скрипта в любой примитивный текстовый редактор;
  • удалите из него строчку
    «countup(2000,08,29); //Укажите здесь дату: (year,month,day)»;
  • сохраните текстовый файл с каким-нибудь именем и расширением .js;
  • вставьте ссылку на него в <head> своей web-страницы (как указано в третьем от конца примере);
  • воткните скрипт со строчкой countup(ХХХХ,ХХ,ХХ); в нужное место на web-странице (как указано в двух последних примерах), замените крестики нужными параметрами (год, месяц, дата), комментарий (то, что за двумя слэшами) лучше убрать вообще;
  • enjoy!

Ну вот, теперь можете пользоваться. Если возникнут проблемы — пишите, обязательно отвечу.

P.S. Те, у кого руки не кривые, могут приспособить этот код и для других подобных считалок.

Скачать скрипт (скачано )


 [an error occurred while processing this directive]