Annotation of parser3/src/classes/date.C, revision 1.95
1.1 parser 1: /** @file
2: Parser: @b date parser class.
3:
1.91 moko 4: Copyright (c) 2001-2012 Art. Lebedev Studio (http://www.artlebedev.com)
1.16 paf 5: Author: Alexandr Petrosian <paf@design.ru> (http://paf.design.ru)
1.32 paf 6: */
1.1 parser 7:
8: #include "classes.h"
1.46 paf 9: #include "pa_vmethod_frame.h"
10:
1.1 parser 11: #include "pa_request.h"
12: #include "pa_vdouble.h"
13: #include "pa_vdate.h"
1.10 parser 14: #include "pa_vtable.h"
1.1 parser 15:
1.95 ! moko 16: volatile const char * IDENT_DATE_C="$Id: date.C,v 1.94 2015/09/02 21:29:44 moko Exp $" IDENT_PA_VDATE_H;
1.91 moko 17:
1.1 parser 18: // class
19:
1.46 paf 20: class MDate: public Methoded {
1.1 parser 21: public: // VStateless_class
1.87 misha 22: Value* create_new_value(Pool&) { return new VDate(0); }
1.1 parser 23:
24: public:
1.46 paf 25: MDate();
1.1 parser 26: };
27:
1.46 paf 28: // global variable
29:
30: DECLARE_CLASS_VAR(date, new MDate, 0);
31:
32: // helpers
33:
34: class Date_calendar_table_template_columns: public ArrayString {
35: public:
36: Date_calendar_table_template_columns(): ArrayString(6+2) {
1.85 misha 37: for(int i=0; i<=6; i++)
38: *this+=new String(i, "%d"); // .i column name
39:
1.46 paf 40: *this+=new String("week");
41: *this+=new String("year");
42: }
43: };
44:
45:
46: Table date_calendar_table_template(new Date_calendar_table_template_columns);
47:
1.1 parser 48: // methods
49:
1.46 paf 50: static void _now(Request& r, MethodParams& params) {
51: VDate& vdate=GET_SELF(r, VDate);
1.23 paf 52:
53: time_t t=time(0);
1.46 paf 54: if(params.count()==1) // ^now(offset)
55: t+=(time_t)round(params.as_double(0, "offset must be double", r)*SECS_PER_DAY);
1.23 paf 56:
1.46 paf 57: vdate.set_time(t);
1.1 parser 58: }
59:
1.90 misha 60: static void _today(Request& r, MethodParams&) {
61: VDate& vdate=GET_SELF(r, VDate);
62:
63: time_t t=time(0);
64:
65: tm today=*localtime(&t);
66: today.tm_hour=0;
67: today.tm_min=0;
68: today.tm_sec=0;
69:
1.94 moko 70: vdate.set_tm(today);
1.90 misha 71: }
72:
1.78 misha 73: static int to_year(int iyear) {
1.94 moko 74: if(iyear<0 || iyear>9999)
75: throw Exception(DATE_RANGE_EXCEPTION_TYPE, 0, "year '%d' is out of range 0..9999", iyear);
76: return iyear-1900;
1.78 misha 77: }
78:
79: static int to_month(int imonth) {
80: return max(1, min(imonth, 12)) -1;
1.59 paf 81: }
82:
1.95 ! moko 83: static char *skip_number(char* string, const char *valid_delim, char *delim) {
! 84: if(string) {
! 85: char *str=string;
! 86: // skipping whitespace
! 87: while(isspace(str[0])) str++;
! 88: // skipping +-
! 89: if(str[0]=='-' || str[0]=='+') str++;
! 90: // at least one digit should be present
! 91: if(!isdigit(*(str++)))
! 92: throw Exception("date.format", 0, "invalid number in date specification '%s'", string);
! 93: // skipping digits
! 94: while(isdigit(str[0])) str++;
! 95: // skipping trailing whitespace
! 96: if(!strchr(valid_delim, ' '))
! 97: while(isspace(str[0])) str++;
! 98: // delimiter check
! 99: if(char c=str[0]){
! 100: if(!strchr(valid_delim, c))
! 101: throw Exception("date.format", 0, "invalid character '%c' in date specification '%s'", c, string);
! 102: if(delim)
! 103: *delim=c;
! 104: str[0]=0;
! 105: return str+1;
! 106: }
! 107: }
! 108: if(delim)
! 109: *delim=0;
! 110: return 0;
! 111: }
! 112:
! 113: static char *skip_number(char** string_ref, const char *valid_delim, char *delim=0) {
! 114: char *result=*string_ref;
! 115: *string_ref=skip_number(*string_ref, valid_delim, delim);
! 116: return result;
! 117: }
! 118:
! 119: static char *skip_writespace(char* str) {
! 120: if(str){
! 121: while(isspace(str[0])) str++;
! 122: return str[0] ? str : 0;
! 123: }
! 124: return 0;
! 125: }
! 126:
! 127: static char *numeric_tz(char prefix, char* tz) {
! 128: char *cur=tz;
! 129: // hours
! 130: if(!isdigit(*(cur++)))
! 131: return 0;
! 132: if(isdigit(cur[0]))
! 133: cur++;
! 134: if(cur[0] == ':'){
! 135: // optional minutes
! 136: cur++;
! 137: if(!isdigit(*(cur++)))
! 138: return 0;
! 139: if(isdigit(cur[0]))
! 140: cur++;
! 141: }
! 142: // nothing more
! 143: if(skip_writespace(cur))
! 144: return 0;
! 145: // returning POSIX TZ format
! 146: size_t size=4+(cur-tz)+1/*zero-teminator*/;
! 147: char *buf=new(PointerFreeGC) char[size];
! 148: strcpy(buf, prefix=='+' ? "SUB-":"SUB+");
! 149: strncpy(buf+4, tz, cur-tz);
! 150: buf[size]=0;
! 151: return buf;
! 152: }
! 153:
! 154: // SQL 2002-04-25 18:14:00
! 155: // ISO 2002-04-25T18:14:00.45+01:00
! 156: // TIME 18:14:00
! 157: // ':' DELIMITED 2002:04:25 [+maybe time]
! 158: // not static, used in image.C
! 159: tm cstr_to_time_t(char *cstr, const char **tzOut) {
1.64 paf 160: if( !cstr || !*cstr )
1.94 moko 161: throw Exception(DATE_RANGE_EXCEPTION_TYPE, 0, "empty string is not valid datetime");
1.95 ! moko 162: if(tzOut)
! 163: *tzOut=0;
1.41 paf 164:
1.94 moko 165: tm tmIn;
166: memset(&tmIn, 0, sizeof(tmIn));
1.41 paf 167: tmIn.tm_isdst=-1;
1.95 ! moko 168:
! 169: char delim;
! 170: char *cur=cstr;
! 171:
! 172: const char *year, *month, *mday;
! 173: const char *hour, *min, *sec, *msec;
! 174:
! 175: year=skip_number(&cur, ":-", &delim);
! 176: if(delim != ':' || delim == ':' && strlen(year) >=4 ){
! 177: // year present
! 178: month=skip_number(&cur, delim == ':' ? ":" : "-");
! 179: mday=skip_number(&cur, tzOut ? " \tT":" \t", &delim);
! 180: if(delim != 'T'){
! 181: // SQL date format
! 182: cur=skip_writespace(cur);
! 183: hour=skip_number(&cur, ":");
! 184: min=skip_number(&cur, ":");
! 185: sec=skip_number(&cur, ".");
! 186: msec=skip_number(&cur, "");
! 187: } else {
! 188: // ISO date format
! 189: hour=skip_number(&cur, ":");
! 190: min=skip_number(&cur, ":+-Z", &delim);
! 191: sec=delim==':' ? skip_number(&cur, ".+-Z", &delim) : 0;
! 192: msec=delim=='.' ? skip_number(&cur, "+-Z", &delim) : 0;
! 193: // timezone specification check
! 194: const char *tz = delim == 'Z' ? (skip_writespace(cur) ? 0 : "UTC") : (cur ? numeric_tz(delim, cur) : 0);
! 195: if(!tz){
! 196: if(!delim)
! 197: throw Exception("date.format", 0, "empty timezone specification");
! 198: throw Exception("date.format", 0, "invalid timezone specification '%c%s'", cur ? cur : "");
! 199: }
! 200: *tzOut=tz;
! 201: }
! 202:
! 203: tmIn.tm_year=to_year(pa_atoi(year));
! 204: tmIn.tm_mon=month?pa_atoi(month)-1:0;
! 205: tmIn.tm_mday=mday?pa_atoi(mday):1;
! 206: } else {
! 207: // time only
! 208: hour=year;
! 209: min=skip_number(&cur, ":");
! 210: sec=skip_number(&cur, ".");
! 211: msec=skip_number(&cur, "");
! 212:
! 213: time_t t=time(0);
! 214: tm *tmNow=localtime(&t);
! 215: tmIn.tm_year=tmNow->tm_year;
! 216: tmIn.tm_mon=tmNow->tm_mon;
! 217: tmIn.tm_mday=tmNow->tm_mday;
1.93 moko 218: }
1.95 ! moko 219:
1.65 paf 220: tmIn.tm_hour=hour?pa_atoi(hour):0;
1.60 paf 221: tmIn.tm_min=min?pa_atoi(min):0;
1.64 paf 222: tmIn.tm_sec=sec?pa_atoi(sec):0;
1.66 paf 223: //tmIn.tm_[msec<<no such, waits reimplementation of the class]=f(msec);
1.95 ! moko 224:
1.65 paf 225: return tmIn;
1.41 paf 226: }
227:
1.46 paf 228: static void _create(Request& r, MethodParams& params) {
229: VDate& vdate=GET_SELF(r, VDate);
1.1 parser 230:
1.82 misha 231: if(params.count()==1){
1.92 moko 232: if(params[0].is_string()){ // ^create[2002-04-25 18:14:00] ^create[18:14:00]
1.95 ! moko 233: const char *tz;
! 234: tm tmIn=cstr_to_time_t(params[0].get_string()->cstrm(), &tz);
! 235: if(tz)
! 236: vdate.set_tz(tz);
1.94 moko 237: vdate.set_tm(tmIn);
1.82 misha 238: } else { // ^create(float days) or ^create[date object]
1.94 moko 239: vdate.set_time(round(params.as_double(0, "float days must be double", r)*SECS_PER_DAY));
1.28 paf 240: }
1.74 misha 241: } else { // ^create(y;m;d[;h[;m[;s]]])
1.75 misha 242: assert(params.count()<=6);
1.56 paf 243: tm tmIn; memset(&tmIn, 0, sizeof(tmIn));
1.1 parser 244: tmIn.tm_isdst=-1;
1.94 moko 245: tmIn.tm_year=to_year(params.as_int(0, "year must be int", r));
1.46 paf 246: tmIn.tm_mon=params.as_int(1, "month must be int", r)-1;
247: tmIn.tm_mday=params.count()>2?params.as_int(2, "mday must be int", r):1;
1.95 ! moko 248: if(params.count()>3) tmIn.tm_hour=params.as_int(3, "hour must be int", r);
1.46 paf 249: if(params.count()>4) tmIn.tm_min=params.as_int(4, "minutes must be int", r);
250: if(params.count()>5) tmIn.tm_sec=params.as_int(5, "seconds must be int", r);
1.94 moko 251: vdate.set_tm(tmIn);
1.74 misha 252: };
1.1 parser 253: }
254:
1.90 misha 255: static void _sql_string(Request& r, MethodParams& params) {
1.46 paf 256: VDate& vdate=GET_SELF(r, VDate);
1.85 misha 257:
1.90 misha 258: VDate::sql_string_type format = VDate::sql_string_datetime;
259: if(params.count() > 0) {
260: const String& what=params.as_string(0, "'type' must be string");
261: if(what.is_empty() || what == "datetime")
262: format = VDate::sql_string_datetime;
263: else if(what == "date")
264: format=VDate::sql_string_date;
265: else if(what == "time")
266: format=VDate::sql_string_time;
267: else
268: throw Exception(PARSER_RUNTIME, &what, "'type' must be 'date', 'time' or 'datetime'");
269: }
270:
271: r.write_assign_lang(*vdate.get_sql_string(format));
1.1 parser 272: }
273:
1.80 misha 274: static void _gmt_string(Request& r, MethodParams&) {
275: VDate& vdate=GET_SELF(r, VDate);
276:
1.88 misha 277: r.write_assign_lang(*vdate.get_gmt_string());
1.80 misha 278: }
279:
1.95 ! moko 280: static void _iso_string(Request& r, MethodParams&) {
! 281: VDate& vdate=GET_SELF(r, VDate);
! 282:
! 283: r.write_assign_lang(*vdate.get_iso_string());
! 284: }
! 285:
1.46 paf 286: static void _roll(Request& r, MethodParams& params) {
287: const String& what=params.as_string(0, "'what' must be string");
1.43 paf 288: int oyear=0;
289: int omonth=0;
290: int oday=0;
1.1 parser 291: int *offset;
292: if(what=="year") offset=&oyear;
293: else if(what=="month") offset=&omonth;
294: else if(what=="day") offset=&oday;
1.48 paf 295: else if(what=="TZ") {
296: const String& argument_tz=params.as_string(1, "'TZ' must be string");
1.95 ! moko 297: if(&r.get_self() == date_class){
! 298: VDate::set_default_tz(argument_tz.cstr());
! 299: } else {
! 300: VDate& vdate=GET_SELF(r, VDate);
! 301: vdate.set_tz(argument_tz.cstr());
! 302: vdate.set_time(vdate.get_time());
! 303: }
1.48 paf 304: return;
305: } else
1.94 moko 306: throw Exception(PARSER_RUNTIME, &what, "must be year|month|day|TZ");
307:
308: if(&r.get_self() == date_class)
309: throw Exception(PARSER_RUNTIME, &what, "must be TZ to be called statically");
310:
311: VDate& vdate=GET_SELF(r, VDate);
1.1 parser 312:
1.46 paf 313: *offset=params.as_int(1, "offset must be int", r);
1.1 parser 314:
1.94 moko 315: tm tmIn=vdate.get_tm();
1.13 paf 316: tm tmSaved=tmIn;
1.43 paf 317: int adjust_day=0;
318: while(true) {
319: tmIn.tm_year+=oyear;
320: tmIn.tm_mon+=omonth;
321: tmIn.tm_mday+=oday+adjust_day;
1.94 moko 322: tmIn.tm_hour=24/2;
1.43 paf 323: tmIn.tm_min=0;
324: tmIn.tm_sec=0;
1.94 moko 325: int saved_day=tmIn.tm_mday;
326: vdate.set_tm(tmIn); /* normalize */
327:
328: if(oday==0 && tmIn.tm_mday!=saved_day /* but it changed */ ) {
329: if(adjust_day <= -3 /* 31->28 max, so never, but... */ )
330: throw Exception(DATE_RANGE_EXCEPTION_TYPE, 0, "bad resulting time (day hole still with %d day adjustment)", adjust_day );
1.43 paf 331:
332: tmIn=tmSaved; // restoring
333: --adjust_day; //retrying with prev day
334: } else
1.94 moko 335: break;
1.43 paf 336: }
1.13 paf 337:
1.94 moko 338: tmIn.tm_hour=tmSaved.tm_hour;
339: tmIn.tm_min=tmSaved.tm_min;
340: tmIn.tm_sec=tmSaved.tm_sec;
341: tmIn.tm_isdst=-1;
342:
343: vdate.set_tm(tmIn);
1.1 parser 344: }
345:
1.46 paf 346: static Table& fill_month_days(Request& r, MethodParams& params, bool rus){
1.45 paf 347: Table::Action_options table_options;
1.46 paf 348: Table& result=*new Table(date_calendar_table_template, table_options);
349:
1.94 moko 350: tm tmIn;
351: memset(&tmIn, 0, sizeof(tmIn));
352: tmIn.tm_year=to_year(params.as_int(1, "year must be int", r));
353: tmIn.tm_mon=to_month(params.as_int(2, "month must be int", r));
1.55 paf 354: tmIn.tm_mday=1;
355:
1.94 moko 356: VDate t(tmIn); /* normalize */
357: int weekDay1=tmIn.tm_wday;
358:
359: if(rus)
1.10 parser 360: weekDay1=weekDay1?weekDay1-1:6; //sunday last
1.94 moko 361: int monthDays=VDate::getMonthDays(tmIn.tm_year, tmIn.tm_mon);
1.43 paf 362:
363: for(int _day=1-weekDay1; _day<=monthDays;) {
1.46 paf 364: Table::element_type row(new ArrayString(7));
1.34 paf 365: // calculating year week no [1..54]
1.85 misha 366: int weekyear=0; // surely would be assigned to, but to calm down compiler
367: int weekno=0; // same
1.34 paf 368: // 0..6 week days-cells fill with month days
1.43 paf 369: for(int wday=0; wday<7; wday++, _day++) {
1.85 misha 370: *row+=(_day>=1 && _day<=monthDays)?new String(_day, "%02d"):new String();
1.34 paf 371:
372: if(wday==(rus?3:4)/*thursday*/) {
1.57 paf 373: tm tms;
1.94 moko 374: memset(&tms, 0, sizeof(tms));
1.57 paf 375: tms.tm_mday=_day;
1.94 moko 376: tms.tm_mon=tmIn.tm_mon;
377: tms.tm_year=tmIn.tm_year;
1.57 paf 378:
1.94 moko 379: VDate ts(tms); /*normalize*/
1.36 paf 380: weekyear=tms.tm_year+1900;
1.85 misha 381: weekno=VDate::CalcWeek(tms).week;
382: }
383: }
384: // appending week no
385: *row+=new String(weekno, "%02d");
1.36 paf 386:
1.85 misha 387: // appending week year
388: *row+=new String(weekyear, "%04d");
1.34 paf 389:
1.46 paf 390: result+=row;
391: }
392:
393: return result;
1.10 parser 394: }
395:
1.46 paf 396: static Table& fill_week_days(Request& r, MethodParams& params, bool rus){
397: Table::columns_type columns(new ArrayString(4));
398: *columns+=new String("year");
399: *columns+=new String("month");
400: *columns+=new String("day");
401: *columns+=new String("weekday");
402: Table& result=*new Table(columns);
403:
1.55 paf 404: tm tmIn;
1.94 moko 405: memset(&tmIn, 0, sizeof(tmIn));
406: tmIn.tm_year=to_year(params.as_int(1, "year must be int", r));
407: tmIn.tm_mon=to_month(params.as_int(2, "month must be int", r));
408: tmIn.tm_mday=params.as_int(3, "day must be int", r);
1.55 paf 409: tmIn.tm_hour=18;
1.84 misha 410:
1.94 moko 411: VDate t(tmIn); /* normalize */
412: int baseWeekDay=tmIn.tm_wday;
413:
1.10 parser 414: if(rus)
415: baseWeekDay=baseWeekDay?baseWeekDay-1:6; //sunday last
1.46 paf 416:
1.94 moko 417: t.set_time(t.get_time()-baseWeekDay*SECS_PER_DAY);
1.46 paf 418:
1.94 moko 419: for(int curWeekDay=0; curWeekDay<7; curWeekDay++, t.set_time(t.get_time()+SECS_PER_DAY)) {
1.46 paf 420: Table::element_type row(new ArrayString(4));
1.94 moko 421:
422: tm tmOut=t.get_tm();
423: *row+=new String(1900+tmOut.tm_year, "%04d");
424: *row+=new String(1+tmOut.tm_mon, "%02d");
425: *row+=new String(tmOut.tm_mday, "%02d");
426: *row+=new String(tmOut.tm_wday, "%02d");
1.85 misha 427:
1.46 paf 428: result+=row;
429: }
430:
431: return result;
1.10 parser 432: }
433:
1.46 paf 434: static void _calendar(Request& r, MethodParams& params) {
435: const String& what=params.as_string(0, "format must be strig");
1.10 parser 436: bool rus=false;
437: if(what=="rus")
438: rus=true;
439: else if(what=="eng")
440: rus=false;
441: else
1.94 moko 442: throw Exception(PARSER_RUNTIME, &what, "must be rus|eng");
1.10 parser 443:
1.46 paf 444: Table* table;
445: if(params.count()==1+2)
446: table=&fill_month_days(r, params, rus);
1.10 parser 447: else // 1+3
1.46 paf 448: table=&fill_week_days(r, params, rus);
1.10 parser 449:
1.46 paf 450: r.write_no_lang(*new VTable(table));
1.10 parser 451: }
452:
1.49 paf 453: static void _unix_timestamp(Request& r, MethodParams& params) {
454: VDate& vdate=GET_SELF(r, VDate);
455:
456: if(params.count()==0) {
457: // ^date.unix-timestamp[]
1.94 moko 458: r.write_no_lang(*new VDouble((double)vdate.get_time()));
1.49 paf 459: } else {
1.50 paf 460: if(vdate.get_time())
1.94 moko 461: throw Exception(PARSER_RUNTIME, 0, "date object already constructed");
1.50 paf 462: // ^unix-timestamp(time_t)
1.94 moko 463: vdate.set_time(params.as_double(0, "Unix timestamp must be number", r));
1.49 paf 464: }
465: }
466:
1.79 misha 467: static void _last_day(Request& r, MethodParams& params) {
1.94 moko 468: tm tmIn;
1.78 misha 469: if(&r.get_self() == date_class) {
1.81 misha 470: if(params.count() != 2)
1.94 moko 471: throw Exception(PARSER_RUNTIME, 0, "year and month must be defined");
1.81 misha 472: // ^date:lastday(year;month)
1.94 moko 473: tmIn.tm_year=to_year(params.as_int(0, "year must be int", r));
474: tmIn.tm_mon=to_month(params.as_int(1, "month must be int", r));
1.78 misha 475: } else {
1.94 moko 476: if(params.count() != 0)
477: throw Exception(PARSER_RUNTIME, 0, "year and month must not be defined");
1.78 misha 478: // ^date.lastday[]
1.94 moko 479: tmIn=GET_SELF(r, VDate).get_tm();
1.78 misha 480: }
1.94 moko 481: r.write_no_lang(*new VInt(VDate::getMonthDays(tmIn.tm_year, tmIn.tm_mon)));
1.78 misha 482: }
483:
1.1 parser 484: // constructor
485:
1.46 paf 486: MDate::MDate(): Methoded("date") {
1.78 misha 487: // ^date::now[]
1.90 misha 488: // ^date::now(offset float days)
1.23 paf 489: add_native_method("now", Method::CT_DYNAMIC, _now, 0, 1);
1.1 parser 490:
1.90 misha 491: // ^date::today[]
492: add_native_method("today", Method::CT_DYNAMIC, _today, 0, 0);
493:
1.78 misha 494: // ^date::create(float days)
1.90 misha 495: // ^date::create[date]
496: // ^date::create(year;month;day[;hour[;minute[;sec]]])
497: // ^date::create[yyyy-mm-dd[ hh:mm:ss]]
498: // ^date::create[hh:mm:ss]
1.17 paf 499: add_native_method("create", Method::CT_DYNAMIC, _create, 1, 6);
1.28 paf 500: // old name for compatibility with <= v1.17 2002/2/18 12:13:42 paf
1.17 paf 501: add_native_method("set", Method::CT_DYNAMIC, _create, 1, 6);
1.1 parser 502:
1.78 misha 503: // ^date.sql-string[]
1.90 misha 504: add_native_method("sql-string", Method::CT_DYNAMIC, _sql_string, 0, 1);
1.1 parser 505:
1.80 misha 506: // ^date.gmt-string[]
507: add_native_method("gmt-string", Method::CT_DYNAMIC, _gmt_string, 0, 0);
508:
1.95 ! moko 509: // ^date.iso-string[]
! 510: add_native_method("iso-string", Method::CT_DYNAMIC, _iso_string, 0, 0);
! 511:
1.78 misha 512: // ^date:lastday(year;month)
513: // ^date.lastday[]
1.79 misha 514: add_native_method("last-day", Method::CT_ANY, _last_day, 0, 2);
1.78 misha 515:
1.90 misha 516: // ^date.roll[year|month|day](+/- 1)
1.94 moko 517: add_native_method("roll", Method::CT_ANY, _roll, 2, 2);
1.10 parser 518:
1.90 misha 519: // ^date:calendar[rus|eng](year;month) = table
520: // ^date:calendar[rus|eng](year;month;day) = table
1.10 parser 521: add_native_method("calendar", Method::CT_STATIC, _calendar, 3, 4);
1.1 parser 522:
1.78 misha 523: // ^date.unix-timestamp[]
1.90 misha 524: // ^date::unix-timestamp(timestamp)
1.49 paf 525: add_native_method("unix-timestamp", Method::CT_DYNAMIC, _unix_timestamp, 0, 1);
1.1 parser 526: }
E-mail: