--- parser3/src/classes/date.C 2015/09/18 00:08:12 1.95 +++ parser3/src/classes/date.C 2020/12/15 17:10:27 1.112 @@ -1,7 +1,7 @@ /** @file Parser: @b date parser class. - Copyright (c) 2001-2012 Art. Lebedev Studio (http://www.artlebedev.com) + Copyright (c) 2001-2020 Art. Lebedev Studio (http://www.artlebedev.com) Author: Alexandr Petrosian (http://paf.design.ru) */ @@ -13,7 +13,7 @@ #include "pa_vdate.h" #include "pa_vtable.h" -volatile const char * IDENT_DATE_C="$Id: date.C,v 1.95 2015/09/18 00:08:12 moko Exp $" IDENT_PA_VDATE_H; +volatile const char * IDENT_DATE_C="$Id: date.C,v 1.112 2020/12/15 17:10:27 moko Exp $" IDENT_PA_VDATE_H; // class @@ -27,7 +27,7 @@ public: // global variable -DECLARE_CLASS_VAR(date, new MDate, 0); +DECLARE_CLASS_VAR(date, new MDate); // helpers @@ -50,9 +50,9 @@ Table date_calendar_table_template(new D static void _now(Request& r, MethodParams& params) { VDate& vdate=GET_SELF(r, VDate); - time_t t=time(0); + pa_time_t t=(pa_time_t)time(0); if(params.count()==1) // ^now(offset) - t+=(time_t)round(params.as_double(0, "offset must be double", r)*SECS_PER_DAY); + t+=(pa_time_t)round(params.as_double(0, "offset must be double", r)*SECS_PER_DAY); vdate.set_time(t); } @@ -70,7 +70,7 @@ static void _today(Request& r, MethodPar vdate.set_tm(today); } -static int to_year(int iyear) { +int to_year(int iyear) { if(iyear<0 || iyear>9999) throw Exception(DATE_RANGE_EXCEPTION_TYPE, 0, "year '%d' is out of range 0..9999", iyear); return iyear-1900; @@ -80,6 +80,16 @@ static int to_month(int imonth) { return max(1, min(imonth, 12)) -1; } + +static const char *skip_number_throw(char *string, char c, const char *valid){ + if(!valid[0]) + throw Exception("date.format", 0, "invalid character '%c' after number in '%s'", c, string); + if(!strcmp(valid, "+-Z")) + throw Exception("date.format", 0, "invalid timezone character '%c' after number in '%s'", c, string); + throw Exception("date.format", 0, "number delimiter '%c'%s expected, but found '%c' in date '%s'", + valid[0], valid[strlen(valid)-1] == 'Z' ? " or timezone":"", c, string); +} + static char *skip_number(char* string, const char *valid_delim, char *delim) { if(string) { char *str=string; @@ -88,8 +98,11 @@ static char *skip_number(char* string, c // skipping +- if(str[0]=='-' || str[0]=='+') str++; // at least one digit should be present - if(!isdigit(*(str++))) - throw Exception("date.format", 0, "invalid number in date specification '%s'", string); + if(!str[0]) + throw Exception("date.format", 0, "number expected in date '%s'", string); + if(!isdigit(str[0])) + throw Exception("date.format", 0, "'%c' must be number in date '%s'", str[0], string); + str++; // skipping digits while(isdigit(str[0])) str++; // skipping trailing whitespace @@ -98,7 +111,7 @@ static char *skip_number(char* string, c // delimiter check if(char c=str[0]){ if(!strchr(valid_delim, c)) - throw Exception("date.format", 0, "invalid character '%c' in date specification '%s'", c, string); + skip_number_throw(string, c, valid_delim); if(delim) *delim=c; str[0]=0; @@ -125,29 +138,34 @@ static char *skip_writespace(char* str) } static char *numeric_tz(char prefix, char* tz) { - char *cur=tz; + // preparing POSIX TZ format + char *buf=new(PointerFreeGC) char[4+5+1/*zero-teminator*/]; + strcpy(buf, prefix=='+' ? "SUB-":"SUB+"); + char *cur=buf+4; + // hours - if(!isdigit(*(cur++))) + if(!isdigit(*(cur++)=*(tz++))) return 0; - if(isdigit(cur[0])) - cur++; - if(cur[0] == ':'){ - // optional minutes - cur++; - if(!isdigit(*(cur++))) + if(isdigit(tz[0])) + *(cur++)=*(tz++); + + if(tz[0] == ':'){ + // HH:mm format + *(cur++)=*(tz++); + if(!isdigit(*(cur++)=*(tz++))) + return 0; + if(isdigit(tz[0])) + *(cur++)=*(tz++); + } else if(isdigit(tz[0])){ + // HHmm format + *(cur++)=':'; + if(!isdigit(*(cur++)=*(tz++)) || !isdigit(*(cur++)=*(tz++))) return 0; - if(isdigit(cur[0])) - cur++; } // nothing more - if(skip_writespace(cur)) + if(skip_writespace(tz)) return 0; - // returning POSIX TZ format - size_t size=4+(cur-tz)+1/*zero-teminator*/; - char *buf=new(PointerFreeGC) char[size]; - strcpy(buf, prefix=='+' ? "SUB-":"SUB+"); - strncpy(buf+4, tz, cur-tz); - buf[size]=0; + *cur=0; return buf; } @@ -170,9 +188,9 @@ tm cstr_to_time_t(char *cstr, const char char *cur=cstr; const char *year, *month, *mday; - const char *hour, *min, *sec, *msec; + const char *hour, *min, *sec, *msec PA_ATTR_UNUSED; - year=skip_number(&cur, ":-", &delim); + year=skip_number(&cur, "-:", &delim); if(delim != ':' || delim == ':' && strlen(year) >=4 ){ // year present month=skip_number(&cur, delim == ':' ? ":" : "-"); @@ -194,8 +212,8 @@ tm cstr_to_time_t(char *cstr, const char const char *tz = delim == 'Z' ? (skip_writespace(cur) ? 0 : "UTC") : (cur ? numeric_tz(delim, cur) : 0); if(!tz){ if(!delim) - throw Exception("date.format", 0, "empty timezone specification"); - throw Exception("date.format", 0, "invalid timezone specification '%c%s'", cur ? cur : ""); + throw Exception("date.format", 0, "empty timezone"); + throw Exception("date.format", 0, "invalid timezone '%c%s'", delim, cur ? cur : ""); } *tzOut=tz; } @@ -217,9 +235,9 @@ tm cstr_to_time_t(char *cstr, const char tmIn.tm_mday=tmNow->tm_mday; } - tmIn.tm_hour=hour?pa_atoi(hour):0; - tmIn.tm_min=min?pa_atoi(min):0; - tmIn.tm_sec=sec?pa_atoi(sec):0; + tmIn.tm_hour=pa_atoi(hour); + tmIn.tm_min=pa_atoi(min); + tmIn.tm_sec=pa_atoi(sec); //tmIn.tm_[msec<(adate)->get_tz()); vdate.set_time(round(params.as_double(0, "float days must be double", r)*SECS_PER_DAY)); } - } else { // ^create(y;m;d[;h[;m[;s]]]) - assert(params.count()<=6); + } else { // ^create(y;m;d[;h[;m[;s[;TZ]]]]) tm tmIn; memset(&tmIn, 0, sizeof(tmIn)); tmIn.tm_isdst=-1; tmIn.tm_year=to_year(params.as_int(0, "year must be int", r)); @@ -248,6 +267,7 @@ static void _create(Request& r, MethodPa if(params.count()>3) tmIn.tm_hour=params.as_int(3, "hour must be int", r); if(params.count()>4) tmIn.tm_min=params.as_int(4, "minutes must be int", r); if(params.count()>5) tmIn.tm_sec=params.as_int(5, "seconds must be int", r); + if(params.count()>6) vdate.set_tz(params.as_string(6, "TZ must be string").cstr()); vdate.set_tm(tmIn); }; } @@ -268,19 +288,43 @@ static void _sql_string(Request& r, Meth throw Exception(PARSER_RUNTIME, &what, "'type' must be 'date', 'time' or 'datetime'"); } - r.write_assign_lang(*vdate.get_sql_string(format)); + r.write(*vdate.get_sql_string(format)); } static void _gmt_string(Request& r, MethodParams&) { VDate& vdate=GET_SELF(r, VDate); - r.write_assign_lang(*vdate.get_gmt_string()); + r.write(*vdate.get_gmt_string()); } -static void _iso_string(Request& r, MethodParams&) { +static void _iso_string(Request& r, MethodParams& params) { VDate& vdate=GET_SELF(r, VDate); - r.write_assign_lang(*vdate.get_iso_string()); + VDate::iso_string_type format=VDate::iso_string_default; + + if(params.count()>0) + if(HashStringValue* options=params.as_hash(0)){ + int valid_options=0; + if(Value* vshow_ms=options->get("ms")){ + if(r.process(*vshow_ms).as_bool()) + format=VDate::iso_string_type(format|VDate::iso_string_ms); + valid_options++; + } + if(Value* vshow_colon=options->get("colon")){ + if(!r.process(*vshow_colon).as_bool()) + format=VDate::iso_string_type(format|VDate::iso_string_no_colon); + valid_options++; + } + if(Value* vshow_z=options->get("z")){ + if(!r.process(*vshow_z).as_bool()) + format=VDate::iso_string_type(format|VDate::iso_string_no_z); + valid_options++; + } + if(valid_options != options->count()) + throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION); + } + + r.write(*vdate.get_iso_string(format)); } static void _roll(Request& r, MethodParams& params) { @@ -432,7 +476,7 @@ static Table& fill_week_days(Request& r, } static void _calendar(Request& r, MethodParams& params) { - const String& what=params.as_string(0, "format must be strig"); + const String& what=params.as_string(0, "format must be string"); bool rus=false; if(what=="rus") rus=true; @@ -447,7 +491,7 @@ static void _calendar(Request& r, Method else // 1+3 table=&fill_week_days(r, params, rus); - r.write_no_lang(*new VTable(table)); + r.write(*new VTable(table)); } static void _unix_timestamp(Request& r, MethodParams& params) { @@ -455,7 +499,7 @@ static void _unix_timestamp(Request& r, if(params.count()==0) { // ^date.unix-timestamp[] - r.write_no_lang(*new VDouble((double)vdate.get_time())); + r.write(*new VDouble((double)vdate.get_time())); } else { if(vdate.get_time()) throw Exception(PARSER_RUNTIME, 0, "date object already constructed"); @@ -478,7 +522,7 @@ static void _last_day(Request& r, Method // ^date.lastday[] tmIn=GET_SELF(r, VDate).get_tm(); } - r.write_no_lang(*new VInt(VDate::getMonthDays(tmIn.tm_year, tmIn.tm_mon))); + r.write(*new VInt(VDate::getMonthDays(tmIn.tm_year, tmIn.tm_mon))); } // constructor @@ -493,12 +537,12 @@ MDate::MDate(): Methoded("date") { // ^date::create(float days) // ^date::create[date] - // ^date::create(year;month;day[;hour[;minute[;sec]]]) + // ^date::create(year;month;day[;hour[;minute[;sec[;TZ]]]]) // ^date::create[yyyy-mm-dd[ hh:mm:ss]] // ^date::create[hh:mm:ss] - add_native_method("create", Method::CT_DYNAMIC, _create, 1, 6); + add_native_method("create", Method::CT_DYNAMIC, _create, 1, 7); // old name for compatibility with <= v1.17 2002/2/18 12:13:42 paf - add_native_method("set", Method::CT_DYNAMIC, _create, 1, 6); + add_native_method("set", Method::CT_DYNAMIC, _create, 1, 7); // ^date.sql-string[] add_native_method("sql-string", Method::CT_DYNAMIC, _sql_string, 0, 1); @@ -506,8 +550,8 @@ MDate::MDate(): Methoded("date") { // ^date.gmt-string[] add_native_method("gmt-string", Method::CT_DYNAMIC, _gmt_string, 0, 0); - // ^date.iso-string[] - add_native_method("iso-string", Method::CT_DYNAMIC, _iso_string, 0, 0); + // ^date.iso-string[$.colon(true) $.z(true) $.ms(false)] + add_native_method("iso-string", Method::CT_DYNAMIC, _iso_string, 0, 1); // ^date:lastday(year;month) // ^date.lastday[]