Эта тема беспокоит меня давно, с тех пор, как в нашей газете научились отправлять электронные счета электронной почтой. В счёте ведь надо было как-то превращать цифры с слова (типа «Два миллиона сто десять тысяч рублей»). Вот и нашёл я в сети, недолго думая, небольшой скриптик – http://dn.ir2.ru/number_in_words.php. Пару лет пользовался им, постоянно что-то исправляя, потом, видимо, исправления набрали критическую массу, и ещё несколько лет всё висело в состоянии неустойчивого равновесия. Потом ещё что-то случилось (может, миллионы кончились, и на простых тысячах вылезла ошибка, может, ещё что), и я понял, что больше допиливать чужой скрипт не хочу. Сделал свой: http://dn.ir2.ru/Num2rub.php.
После почти года работы свой скрипт тоже мне разонравился. Но я об этом не знал, пока не встретил на одном форуме задачу: вывести словами фразу «До Нового Года осталось 2 месяца 14 дней». То есть задача нарисовалась более универсальная, чем запись словами банальной денежной суммы; старый скрипт просто не умел выводить ничего, кроме рублей и копеек. Ясно, что надо было основательно менять алгоритм.
Но не настолько основательно, чтобы применять известную форму plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2). Я как изначально начал работать с цифрами, а не с числами, так и привык к такой системе: число разбивается сначала на группы (по разрядам), потом на цифры, и алгоритм plural формы рассчитывается уже по цифрам (например, если вторая справа цифра "1", используем форму 3: "рублей", "копеек", "яиц"...).
Так появился скрипт http://ir2.ru/static/Propis.php.
Систему можно подключить к любому php-скрипту обычным include 'Propis.php'. Для пользователей в скрипте предназначены две функции, которые надо вызывать через имя класса и двойное двоеточие:
1) Функция Propis::get() принимает в качестве параметра строку из нескольких чисел, разделённых разделителем (любым "не-числом"): если чисел 2 (например, "12.56"), скрипт считает их рублями и копейками;
если чисел больше двух – (справа налево) секундами, минутами, часами, днями, месяцами, годами. В функцию Propis::get() не следует передавать в качестве параметра одно число, для этого предназначена следующая функция (get_one).
2) Функция Propis::get_one(число, массив) принимает первым параметром одно число, вторым – массив из трёх "классических" plural-словоформ (типа "копейка", "копейки", "копеек").
Вычисление интервала между двумя датами – задача очень нетривиальная. Её пытались решать неоднократно, с большим или меньшим успехом. Есть, например, упоминания о том, что подобная функция, встроенная в PHP 5.3, работает с ошибками.
Обычный (очевидный) способ вычисления интервала заключается в том, чтобы сначала найти разницу примитивных значений (секунд или миллисекунд), а потом полученное значение последовательно делить на 365 (получить годы), 12 (месяцы), 30 (дни) и т.д. Видно, что при таком алгоритме не учитываются високосные годы, месяцы с 28 (или 31) днями, сутки, в которых 25 часов... А учесть всё это – скрипт разбухнет, как бобы в трюме парусника.
Я попытался применить тоже достаточно очевидный (только почему-то никем не используемый) алгоритм: почему бы ни попробовать сформировать дату встроенными средствами языка? То есть и на PHP, и на Javascript есть свои объекты (или функции) Date, которым можно передать в качестве параметра полученные (милли)секунды и сформировать правильную дату.
Понятно, что втупую такой метод не сработает: например, разница дат 2011 – 2010 будет равна одному году (или, в секундах, 31536000), но если передать этот "один год" в качестве параметра в PHP, получим дату 1971-01-01 (или что-то в этом роде). То есть "арифметика" у веб-языков программирования действует достаточно забавная: 2011 – 2010 = 1971.
Это зашито в системе: PHP функция date() с параметром 0 вернёт не нулевой год, а 1970-й (или 1980?..). Примерно так же и с javascript. А теперь наш хак: если мы знаем "нулевой" год языка, то ведь можно, наверное, вычесть его из полученного "странного" значения ("1971-01-01"), и получить правильное значение года – "1"? Нам даже не надо для применения этого хака знать, какой там именно в языке год. Достаточно передать функции параметр "0":
По такому же алгоритму можно получить и правильный месяц, и день (вычтя "нулевые" значения из рассчитанных).
Это всё очень тривиально и занимает всего три строки (вместо одной). Остальные 15 строк занимает коррекция дней (29, 30 или 31 в каждом месяце), алгоритм которой я уже сам не совсем понимаю. Но как-то она работает! Код функции n_time_diff находится внизу файла примера: http://ir2.ru/static/Propis.php.