Написал класс для работы с датами расширенного диапазона. Если кому нужно - пользуйтесь на здоровье. Буду очень рад замечаниям. Но по хорошему в конструктор нужно вставить регулярное выражение, проверяющее правильность ввода даты и разбивающее дату на годы, дни, часы и т.д. Изучать RCPE пока некогда... если у кого есть, может поделитесь? Варианты строки с датой как в . Код класса:
#==========================================================================================# Класс для работы с датами расширенного диапазона. (v1.0) Автор: С. Лифшиц (serlif@mail.ru)#==========================================================================================## Метод print и массивы локальных названий месяцев взяты из класса dtf.p# (http://www.parser.ru/examples/date/) Михаила Петрушина (http://misha.design.ru/).# Алгоритмы работы с модифицированными юлианскими датами взяты на http://alglib.manual.ru/## Диапазон класса: 17.11.1858 - 23.12.54078.## Верхний предел занижен, так как за ним начинаются ошибки в вычислениях (связанные# видимо с переполнением int). Некогда разбираться... Дальше наверное нужно только астрономам. :)## Класс в первую очередь быд предназначен для обработки дат, хранящихся# в СУБД и выходящих за стандартный диапазон. Поэтому я обошелся без регулярного выражения# в конструкторе.## Поля класса аналогичны Parser-овскому (www.parser.ru/docs/lang/datefields.htm):# day, month, year, hour, minute, second, weekday, yearday, daylightsaving## Дополнительные поля: millisecond - миллисекунды, julian - дата в модифицированном# юлианском формате (количество дней, прошедших с 17.11.1858), russian - дата по русски# в виде "ДД Месяц ГГГГ, ДеньНедели", timestamp- количество секунд, прошедших с начала# текущих суток## Примечание: Поскольку зимнее и летнее время в любом случае порождает# неопределенности (попробуйте сказать какое время 2004-10-31 02:00:01),# то поле daylightsaving вычисляется и для параметра без времени, говоря# в этом случае какое в этот день используется время (здесь неопределенности для# последнего воскресения марта и октября)## Методы:## @create[sql-date] - конструктор## @roll[target;offset] - сдвигает дату на заданное количество единиц (в target: year, month, day)## @print[sql-date;format;locale] - вывод даты, используя форматную строку и локальные названия месяцев## @dayofweek[year;month;day] - определение дня недели для введенной даты## @daysbetween[year1;month1;day1;year2;month2;day2] - вычисление количества дней между датами## @getjulian[year;month;day] - переводит дату в модифицированную юлианскую## @fromjulian[julian] - переводит дату из модифицированной юлианской в обычную##==========================================================================================@CLASS
cdate
#==========================================================================================# Конструктор класса#==========================================================================================@create[sql-date][date;cdate][tmp]$count[^sql-date.split[]]$date[^sql-date.split[ ;h]]# Заполняем поля, относящиеся к дате$cdate[^date.0.split[-;h]]$year($cdate.0)$month($cdate.1)$day($cdate.2)$hour(0)$minute(0)$second(0)$millisecond(0)# Переводим дату в юлианскую$julian(^getjulian[$year;$month;$day])# Если вышли за диапазон класса - выдаем ошибку^if($julian<0 || $julian>19073000){^throw[bad.command;cdate:create;date '${year}-${month}-${day}' is out of valid range]}# Корректируем значения для ошибочных дат (ex. 2000-02-31)$tmp[^fromjulian[$julian]]$year($tmp.year)$month($tmp.month)$day($tmp.day)# Если в параметре есть время - заполняем поля^if(^count.count[]>1){$time[^date.1.split[.;h]]$count[^date.1.split[.]]$ctime[^time.0.split[:;h]]$timestamp(^ctime.2.int(0)+^ctime.1.int(0)*60+^ctime.0.int(0)*3600)^if(^count.count[]>1){$timestamp($timestamp+^time.1.int(0)\1000+(^time.1.int(0)%1000)/1000)}# Корректируем значения для ошибочного времени (ex. 26:69:84 = Сутки + 3:10:24)$julian($julian+$timestamp\86400)$timestamp($timestamp%86400)$tmp[^fromjulian[$julian]]$year($tmp.year)$month($tmp.month)$day($tmp.day)# Заполняем время$hour($timestamp\3600)$minute(($timestamp%3600)\60)$second(^math:trunc(($timestamp%3600)%60))^if(^count.count[]>1){$millisecond(^math:frac($timestamp)*1000)}}^advancedfields[]#==========================================================================================# Вычисляет расширенные поля: день недели, день года и т.д.#==========================================================================================@advancedfields[][marth;october]$weekday[^dayofweek[$year;$month;$day]]$yearday[^daysbetween[$year;1;1;$year;$month;$day]]# Вычисляем принадлежит ли день периоду летнего времени$marth(32-^dayofweek[$year;03;25])$marth[^daysbetween[$year;1;1;$year;3;$marth]]$october(32-^dayofweek[$year;10;25])$october[^daysbetween[$year;1;1;$year;10;$october]]$daylightsaving(^if($yearday>=$marth && $yearday<=$october){1}{0})# Уточнение для летнего и зимнего времени^if($yearday==$marth && $hour==2){^hour.dec[]}^if($daylightsaving && def $time){^if($yearday==$marth && $hour<2){$daylightsaving(0)}^if($yearday==$october && $hour>=2){$daylightsaving(0)}}^leapyear[]$russian[^print[;%e %h %Y, %A]]#==========================================================================================# Сдвигает дату на заданное количество единиц#==========================================================================================@roll[target;offset][tmp]# Годы^if($target eq year){$year($year+^offset.int(0))}# Месяцы^if($target eq month){$month($month+^offset.int(0)%12)$year($year+^offset.int(0)\12)^if($month<=0){$month(12+$month)^year.dec[]}^if($month>12){$month($month-12)^year.inc[]}}# Дни^if($target eq day){$julian($julian+^offset.int(0))$tmp[^fromjulian[$julian]]$year($tmp.year)$month($tmp.month)$day($tmp.day)}# Перевычисляем доп. поля для обновленной даты^leapyear[]^if($day>$dayinmonth.$month){$day($dayinmonth.$month)}$julian[^getjulian[$year;$month;$day]]# Если вышли за диапазон класса - выдаем ошибку^if($julian<0 || $julian>19073000){^throw[rolled.out;cdate:roll;bad resulting date '${year}-${month}-${day}'(rolled out of valid date range)]}^advancedfields[]#==========================================================================================# Коррекция количества дней в феврале для високосных лет#==========================================================================================@leapyear[]^if($year%4==0 && ($year%100!=0 || $year%400==0)){$dayinmonth.2[29]}{$dayinmonth.2[28]}#==========================================================================================# Вычисляет день недели#==========================================================================================@dayofweek[y;m;d][jul]$jul(^getjulian[$y;$m;$d])$result(($jul%7+3)%7)#==========================================================================================# Вычисляет количество дней между датами#==========================================================================================@daysbetween[y1;m1;d1;y2;m2;d2][date1;date2]$result(^getjulian[$y2;$m2;$d2]-^getjulian[$y1;$m1;$d1])#==========================================================================================# Переводит дату в модифицированную юлианскую#==========================================================================================@getjulian[y;m;d][a;b]$y(^y.int(0))$m(^m.int(0))$d(^d.int(0))$a(10000*$y+100*$m+$d)^if($m<=2){^m.inc(12)^y.dec(1)}^if($a<=15821004){$b(-2+($y+4716)\4-1179)}{$b($y\400-$y\100+$y\4)}$a(365*$y-679004)$result($a+$b+306001*($m+1)\10000+$d)#==========================================================================================# Переводит дату из модифицированной юлианской в обычную#==========================================================================================@fromjulian[mjd][jd;b;c;d;e;f;da;mo;ye]$jd($mjd+2400001)^if($jd<2299161){$b(0)$c($jd+1524)}{$b(($jd*100-186721625)\3652425)$c($jd+($b-$b\4)+1525)}$d((100*$c-12210)\36525)$e(365*$d+$d\4)$f(($c-$e)*10000\306001)$da(^math:trunc($c-$e+0.5)-^math:trunc($f*30.6001))$mo($f-1-12*($f\14))$ye($d-4715-(7+$mo)\10)$result[$.day($da)$.month($mo)$.year($ye)]#==========================================================================================# Вывод даты, используя форматную строку.# Формат строки понятен из вариантов switch#==========================================================================================@print[sql-date;format;locale][tmp;temp;year;month;day;hour;minute;second]# Если дата не задана - печатаем для текущего объекта^if(!def ${sql-date}){$year($self.year)$month($self.month)$day($self.day)$hour($self.hour)$minute($self.minute)$second($self.second)$millisecond($self.millisecond)}{$temp[^cdate::create[$sql-date]]$year($temp.year)$month($temp.month)$day($temp.day)$hour($temp.hour)$minute($temp.minute)$second($temp.second)$millisecond($temp.millisecond)}^if(!def $locale){$locale[$rr-locale]}$result[^format.match[%(.)][g]{^switch[$match.1]{^case[%]{%}^case[e]{$day}^case[d]{^day.format[%02d]}^case[c]{$month}^case[m]{^month.format[%02d]}^case[h]{$locale.month.[$month]}^case[Y]{$year}^case[y]{$tmp($year % 100)^tmp.format[%02d]}^case[w]{$weekday}^case[a;A]{$locale.weekday.[$weekday]}^case[D]{^month.format[%02d]/^day.format[%02d]/$year}^case[H]{^hour.format[%02d]}^case[M]{^minute.format[%02d]}^case[i]{^minute.format[%02d]}^case[S]{^second.format[%02d]}^case[T]{^hour.format[%02d]:^minute.format[%02d]:^second.format[%02d]}}}]#==========================================================================================# Вспомогательные переменные#==========================================================================================@auto[]$dayinmonth[$.1[31]$.2[28]$.3[31]$.4[30]$.5[31]$.6[30]$.7[31]$.8[31]$.9[30]$.10[31]$.11[30]$.12[31]]$rr-locale[$.month[$.1[Января]$.2[Февраля]$.3[Марта]$.4[Апреля]$.5[Мая]$.6[Июня]$.7[Июля]$.8[Августа]$.9[Сентября]$.10[Октября]$.11[Ноября]$.12[Декабря]]$.weekday[$.0[Воскресенье]$.1[Понедельник]$.2[Вторник]$.3[Среда]$.4[Четверг]$.5[Пятница]$.6[Суббота]$.7[Воскресенье]]]$ri-locale[$.month[$.1[Январь]$.2[Февраль]$.3[Март]$.4[Апрель]$.5[Май]$.6[Июнь]$.7[Июль]$.8[Август]$.9[Сентябрь]$.10[Октябрь]$.11[Ноябрь]$.12[Декабрь]]$.weekday[$.0[Воскресенье]$.1[Понедельник]$.2[Вторник]$.3[Среда]$.4[Четверг]$.5[Пятница]$.6[Суббота]$.7[Воскресенье]]]$rs-locale[$.month[$.1[Янв]$.2[Фев]$.3[Мар]$.4[Апр]$.5[Май]$.6[Июн]$.7[Июл]$.8[Авг]$.9[Сен]$.10[Окт]$.11[Ноя]$.12[Дек]]$.weekday[$.0[Вс]$.1[Пн]$.2[Вт]$.3[Ср]$.4[Чт]$.5[Пт]$.6[Сб]$.7[Вс]]]# английская locale$es-locale[$.month[$.1[Jan]$.2[Feb]$.3[Mar]$.4[Apr]$.5[May]$.6[Jun]$.7[Jul]$.8[Aug]$.9[Sep]$.10[Oct]$.11[Nov]$.12[Dec]]$.weekday[$.0[Sun]$.1[Mon]$.2[Tue]$.3[Wed]$.4[Thu]$.5[Fri]$.6[Sat]$.7[Sun]]]$ei-locale[$.month[$.1[January]$.2[Febrary]$.3[March]$.4[April]$.5[May]$.6[June]$.7[July]$.8[August]$.9[September]$.10[October]$.11[November]$.12[December]]$.weekday[$.0[Sunday]$.1[Monday]$.2[Tuesday]$.3[Wednsday]$.4[Thusday]$.5[Friday]$.6[Satarday]$.7[Sunday]]]# украинская locale$us-locale[$.month[$.1[Сiч]$.2[Лют]$.3[Бер]$.4[Квi]$.5[Тра]$.6[Чер]$.7[Лип]$.8[Сер]$.9[Вер]$.10[Жов]$.11[Лис]$.12[Гру]]$.weekday[$.0[Нед]$.1[Пон]$.2[Вiв]$.3[Сер]$.4[Чет]$.5[П'я]$.6[Суб]$.7[Нед]]]$ui-locale[$.month[$.1[Сiчень]$.2[Лютий]$.3[Березень]$.4[Квiтень]$.5[Травень]$.6[Червень]$.7[Липень]$.8[Серпень]$.9[Вересень]$.10[Жовтень]$.11[Листопад]$.12[Грудень]]$.weekday[$.0[Недiля]$.1[Понедiлок]$.2[Вiвторок]$.3[Середа]$.4[Четвер]$.5[П'ятниця]$.6[Субота]$.7[Недiля]]]
Единственно я не уверен, что для стандартного английского и украинского варианта подходит форматная строка %e %h %Y, %A. Насколько я знаю в английском принято писать типа "НазваниеМесяца ДД, ГГГГ (ДеньНедели)" - т.е %h %e, %Y (%A)? В украинском вроде как и у нас. И кстати мне помогли просклонять названия в украинском.
по моему основное назначение класса - делать ^cdate:print[$from_sql.datetime;формат;локаль]
поэтому незачем при каждом create (который вызывается из print) делать мало кому обычно нужные телодвижения.
возможно стоит сделать как у меня в dtf: установку текущей locale. если мы делаем на сайт английском, то выполнили один раз set locale и у нас по умолчанию все на английском, в ком числе в переменной где строка с полной датой (только тогда её надо обозвать не $russian).
- исправлены ошибки написания дней недели и месяцев в английском варианте - конструктор в качестве входного параметра теперь также понимает объект класса cdate, int, double - УДАЛЕНО создание внутренней переменной $local (с текстовым представлением даты) при конструировании объекта - при ^obj.print[;формат] теперь не происходит ещё одного, внутреннего, создания объекта cdate - ускорена работа конструктора в случае ^cdate::create[date-object] - для разбора строковых дат используется единожды созданные внутренний объект класса regex (нет множественной компиляций pcre-шаблона в случае парсинга большого количества дат) - добавлен метод sql-string - мелкие правки