Annotation of parser3/src/types/pa_vdate.C, revision 1.3
1.1 moko 1: /** @file
2: Parser: @b date parser class.
3:
4: Copyright (c) 2001-2012 Art. Lebedev Studio (http://www.artlebedev.com)
5: */
6:
7: #include "pa_vstateless_object.h"
8: #include "pa_vdate.h"
9: #include "pa_vint.h"
10: #include "pa_vstring.h"
11:
1.3 ! moko 12: volatile const char * IDENT_PA_PA_VDATE_C="$Id: pa_vdate.C,v 1.2 2015/08/15 22:51:17 moko Exp $" IDENT_PA_VDATE_H;
1.1 moko 13:
1.3 ! moko 14: #define ZERO_DATE (-62169984000-SECS_PER_DAY) // '0000-00-00 00:00:00' - 1 day
! 15: #define MAX_DATE (253402300799+SECS_PER_DAY) // '9999-12-31 23:59:59' + 1 day
! 16:
! 17: #define MAX_32_DATE 2147483647
! 18:
! 19: static const int DAYS_IN_MONTH[12] =
! 20: {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
! 21:
! 22: static const int DAYS_BEFORE_MONTH[12] =
! 23: {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
! 24:
! 25: #define IS_LEAP(y) (((y) % 4) == 0 && (((y) % 100) != 0 || (((y)+1900) % 400) == 0))
! 26:
! 27: int VDate::getMonthDays(int year, int month) {
! 28: return (month == 1 /* january -- 0 */ && IS_LEAP(year)) ? 29 : DAYS_IN_MONTH[month];
! 29: }
! 30:
! 31: static void pa_gmtime(pa_time_t lcltime, struct tm *res);
! 32: static pa_time_t pa_mktime(struct tm *tim_p);
! 33:
! 34: static int gmt_offset() {
! 35: #if defined(HAVE_TIMEZONE)
! 36: tzset();
! 37: return timezone;
! 38: #else
! 39: time_t t=time(0);
! 40: tm *tm=localtime(&t);
! 41: #if defined(HAVE_TM_GMTOFF)
! 42: return -tm->tm_gmtoff;
! 43: #elif defined(HAVE_TM_TZADJ)
! 44: return tm->tm_tzadj;
! 45: #else
! 46: #error neither HAVE_TIMEZONE nor HAVE_TM_GMTOFF nor HAVE_TM_TZADJ defined
! 47: #endif
! 48: #endif
! 49: }
! 50:
! 51: static void pa_set_tz(const char* ntz) {
! 52: if(ntz && *ntz){
! 53: static char temp_tz_pair[MAX_STRING];
! 54: snprintf(temp_tz_pair, sizeof(temp_tz_pair), "TZ=%s", ntz);
! 55: putenv(temp_tz_pair);
! 56: } else {
1.1 moko 57: #ifdef HAVE_UNSETENV
1.3 ! moko 58: unsetenv("TZ");
1.1 moko 59: #else
1.3 ! moko 60: putenv("TZ=");
1.1 moko 61: #endif
1.3 ! moko 62: }
1.1 moko 63: }
64:
1.3 ! moko 65: /// Auto-object used for temporarily substituting/removing timezone variable
! 66: class Temp_tz {
! 67: const char* ntz;
! 68: char saved_tz[MAX_STRING];
! 69: public:
! 70: static const char *default_tz;
! 71: public:
! 72: Temp_tz(const char *atz) : ntz(atz) {
! 73: if(!ntz)
! 74: ntz=default_tz;
! 75: if(!ntz)
! 76: return;
! 77: if(const char* ctz=getenv("TZ")){
! 78: strncpy(saved_tz, ctz, sizeof(saved_tz)-1);
! 79: } else
! 80: saved_tz[0]=0;
! 81: pa_set_tz(ntz);
! 82: }
! 83: ~Temp_tz() {
! 84: if(ntz)
! 85: pa_set_tz(saved_tz);
! 86: }
! 87: };
! 88:
! 89: const char *Temp_tz::default_tz=0;
1.1 moko 90:
1.3 ! moko 91: static tm pa_localtime(const char *tz, pa_time_t atime, struct tm &tmIn) {
! 92: Temp_tz temp_tz(tz);
! 93: #ifdef PA_DATE64
! 94: tmIn=*localtime(&atime);
! 95: #else
! 96: if(atime >= 0 && atime <= MAX_32_DATE){
! 97: time_t itime=atime;
! 98: tmIn=*localtime(&itime);
! 99: } else {
! 100: pa_gmtime(atime-gmt_offset(), &tmIn);
! 101: }
! 102: #endif
1.1 moko 103: }
104:
1.3 ! moko 105: static pa_time_t pa_mktime(const char *tz, struct tm &tmIn) {
! 106: Temp_tz temp_tz(tz);
! 107: #ifdef PA_DATE64
! 108: return mktime(&tmIn);
! 109: #else
! 110: time_t result=mktime(&tmIn);
! 111: if(result != -1)
! 112: return result;
! 113: return pa_mktime(&tmIn)+gmt_offset();
! 114: #endif
1.1 moko 115: }
116:
1.3 ! moko 117: const String* VDate::get_sql_string(sql_string_type format) {
! 118: switch(format){
! 119: case sql_string_datetime:{
! 120: static const char *format="%.4d-%.2d-%.2d %.2d:%.2d:%.2d";
! 121: static int size=4+1+2+1+2 +1+ 2+1+2+1+2 +1/*zero-teminator*/+1/*for faulty snprintfs*/;
! 122: char *buf=new(PointerFreeGC) char[size];
! 123: snprintf(buf, size, format, ftm.tm_year+1900, ftm.tm_mon+1, ftm.tm_mday, ftm.tm_hour, ftm.tm_min, ftm.tm_sec);
! 124: return new String(buf);
! 125: }
! 126: case sql_string_date:{
! 127: static const char *format="%.4d-%.2d-%.2d";
! 128: static int size=4+1+2+1+2 +1/*zero-teminator*/+1/*for faulty snprintfs*/;
! 129: char *buf=new(PointerFreeGC) char[size];
! 130: snprintf(buf, size, format, ftm.tm_year+1900, ftm.tm_mon+1, ftm.tm_mday);
! 131: return new String(buf);
! 132: }
! 133: case sql_string_time:{
! 134: static const char *format="%.2d:%.2d:%.2d";
! 135: static int size=2+1+2+1+2 +1/*zero-teminator*/+1/*for faulty snprintfs*/;
! 136: char *buf=new(PointerFreeGC) char[size];
! 137: snprintf(buf, size, format, ftm.tm_hour, ftm.tm_min, ftm.tm_sec);
! 138: return new String(buf);
! 139: }
1.1 moko 140: }
1.3 ! moko 141: }
! 142:
! 143:
! 144: const String* VDate::get_gmt_string() {
! 145: struct tm tms;
! 146: #ifdef PA_DATE64
! 147: tms=*gmtime(&ftime);
! 148: #else
! 149: pa_gmtime(ftime, &tms);
! 150: #endif
! 151:
! 152: /// http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3
! 153: static const char month_names[12][4]={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};
! 154: static const char days[7][4]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
1.1 moko 155:
1.3 ! moko 156: static int size=3+1+1+2+1+3+1+4+1+2+1+2+1+2+4 +1/*zero-teminator*/+1/*for faulty snprintfs*/;
! 157: char *buf=new(PointerFreeGC) char[size];
! 158: snprintf(buf, size, "%s, %.2d %s %.4d %.2d:%.2d:%.2d GMT",
! 159: days[tms.tm_wday], tms.tm_mday, month_names[tms.tm_mon], tms.tm_year+1900, tms.tm_hour, tms.tm_min, tms.tm_sec);
! 160: return new String(buf);
1.1 moko 161: }
162:
163: override Value* VDate::get_element(const String& aname) {
164: // $method
165: if(Value* result=VStateless_object::get_element(aname))
166: return result;
167:
168: // $TZ
1.3 ! moko 169: if(aname=="TZ")
! 170: return ftz_cstr ? new VString(*new String(ftz_cstr)): new VString();
1.1 moko 171:
172: int result;
1.3 ! moko 173: if(aname=="year") result=1900+ftm.tm_year;
! 174: else if(aname=="month") result=1+ftm.tm_mon;
! 175: else if(aname=="day") result=ftm.tm_mday;
! 176: else if(aname=="hour") result=ftm.tm_hour;
! 177: else if(aname=="minute") result=ftm.tm_min;
! 178: else if(aname=="second") result=ftm.tm_sec;
! 179: else if(aname=="weekday") result=ftm.tm_wday;
! 180: else if(aname=="yearday") result=ftm.tm_yday;
! 181: else if(aname=="daylightsaving") result=ftm.tm_isdst;
1.1 moko 182: else if(aname=="week") {
1.3 ! moko 183: yw week = CalcWeek(ftm);
1.1 moko 184: result=week.week;
185: }
186: else if(aname=="weekyear") {
1.3 ! moko 187: yw week = CalcWeek(ftm);
1.1 moko 188: result=1900+week.year;
189: } else { return bark("%s field not found", &aname); }
190: return new VInt(result);
191: }
192:
193: const String* VDate::get_json_string(Json_options& options) {
194: String* result=new String();
195: switch(options.date){
196: case Json_options::D_SQL:
197: result->append_quoted(get_sql_string());
198: break;
199: case Json_options::D_GMT:
200: result->append_quoted(get_gmt_string());
201: break;
202: case Json_options::D_TIMESTAMP:
203: *result << format((int)ftime, 0);
204: break;
205: }
206: return result;
207: }
1.2 moko 208:
1.3 ! moko 209: void VDate::validate() {
! 210: if((ftm.tm_year==-1901) && (ftm.tm_mon==10) && (ftm.tm_mday==30)){
! 211: ftm.tm_year=-1900;
! 212: ftm.tm_mon=-1;
! 213: ftm.tm_mday=0;
! 214: }
! 215: if((ftm.tm_year+1900)<0 || (ftm.tm_year+1900)>9999){
! 216: throw Exception(DATE_RANGE_EXCEPTION_TYPE, 0, "year '%d' is out of range 0..9999", ftm.tm_year+1900);
! 217: }
! 218: }
! 219:
! 220: void VDate::set_time(pa_time_t atime) {
1.2 moko 221: if(atime==-1)
222: throw Exception(DATE_RANGE_EXCEPTION_TYPE, 0, "invalid datetime");
1.3 ! moko 223: if(atime<ZERO_DATE || atime>MAX_DATE)
! 224: throw Exception(DATE_RANGE_EXCEPTION_TYPE, 0, "unix time %.15g is out of range 0..9999 year", (double)atime);
1.2 moko 225: ftime=atime;
1.3 ! moko 226: pa_localtime(ftz_cstr, ftime, ftm);
! 227: validate();
! 228: }
! 229:
! 230: void VDate::set_tm(tm &tmIn) {
! 231: ftime=pa_mktime(ftz_cstr, tmIn);
! 232: ftm=tmIn;
! 233: if(ftime==-1)
! 234: throw Exception(DATE_RANGE_EXCEPTION_TYPE, 0, "invalid datetime '%04d-%02d-%02d'", tmIn.tm_year+1900, tmIn.tm_mon+1, tmIn.tm_mday);
! 235: validate();
1.2 moko 236: }
237:
1.3 ! moko 238: void VDate::set_tz(const String* atz) {
! 239: ftz_cstr=atz && !atz->is_empty() ? atz->cstr() : 0;
! 240: pa_localtime(ftz_cstr, ftime, ftm);
! 241: validate();
! 242: }
! 243:
! 244: void VDate::set_default_tz(const String* atz) {
! 245: Temp_tz::default_tz=atz && !atz->is_empty() ? atz->cstr() : 0;
1.2 moko 246: }
247:
248: static int ISOWeekCount (int year) {
249: static const unsigned int YearWeeks[] = {
250: 52,52,52,52,53, 52,52,52,52,52,
251: 53,52,52,52,52, 52,53,52,52,52,
252: 52,53,52,52,52, 52,52,53
253: };
254: return YearWeeks[(year+1900) % 28];
255: }
256:
257: VDate::yw VDate::CalcWeek(tm& tms) {
258: yw week = {tms.tm_year, 0};
259:
260: // http://www.merlyn.demon.co.uk/weekinfo.htm
261: static const unsigned int FirstThurs[] = {7,5,4,3,2,7,6,5,4,2,1,7,6,4,3,2,1,6,5,4,3,1,7,6,5,3,2,1};
262: int diff = tms.tm_yday-(FirstThurs[(tms.tm_year+1900) % 28]-4);
263: if (diff < 0){
264: tms.tm_mday = diff;
1.3 ! moko 265: pa_mktime(0, tms); // normalize
1.2 moko 266: week = CalcWeek(tms);
267: } else {
268: week.week = 1 + diff/7;
269: if ( week.week > 52 && ISOWeekCount(week.year) < week.week ){
270: week.year++;
271: week.week = 1;
272: }
273: }
274: return week;
275: }
1.3 ! moko 276:
! 277: #ifndef PA_DATE64
! 278:
! 279: /*
! 280: * gmtime_r.c
! 281: * Original Author: Adapted from tzcode maintained by Arthur David Olson.
! 282: * Modifications:
! 283: * - Changed to mktm_r and added __tzcalc_limits - 04/10/02, Jeff Johnston
! 284: * - Fixed bug in mday computations - 08/12/04, Alex Mogilnikov <alx@intellectronika.ru>
! 285: * - Fixed bug in __tzcalc_limits - 08/12/04, Alex Mogilnikov <alx@intellectronika.ru>
! 286: * - Move code from _mktm_r() to gmtime_r() - 05/09/14, Freddie Chopin <freddie_chopin@op.pl>
! 287: * - Fixed bug in calculations for dates after year 2069 or before year 1901. Ideas for
! 288: * solution taken from musl's __secs_to_tm() - 07/12/2014, Freddie Chopin
! 289: * <freddie_chopin@op.pl>
! 290: * - Use faster algorithm from civil_from_days() by Howard Hinnant - 12/06/2014,
! 291: * Freddie Chopin <freddie_chopin@op.pl>
! 292: *
! 293: * Converts the calendar time pointed to by tim_p into a broken-down time
! 294: * expressed as local time. Returns a pointer to a structure containing the
! 295: * broken-down time.
! 296: */
! 297:
! 298: /* Move epoch from 01.01.1970 to 01.03.0000 (yes, Year 0) - this is the first
! 299: * day of a 400-year long "era", right after additional day of leap year.
! 300: * This adjustment is required only for date calculation, so instead of
! 301: * modifying time_t value (which would require 64-bit operations to work
! 302: * correctly) it's enough to adjust the calculated number of days since epoch.
! 303: */
! 304:
! 305: #define SECS_PER_HOUR 3600
! 306: #define SECS_PER_MIN 60
! 307: #define DAYS_PER_WEEK 7
! 308: #define YEAR_BASE 1900
! 309:
! 310: #define EPOCH_ADJUSTMENT_DAYS 719468L
! 311: /* year to which the adjustment was made */
! 312: #define ADJUSTED_EPOCH_YEAR 0
! 313: /* 1st March of year 0 is Wednesday */
! 314: #define ADJUSTED_EPOCH_WDAY 3
! 315: /* there are 97 leap years in 400-year periods. ((400 - 97) * 365 + 97 * 366) */
! 316: #define DAYS_PER_ERA 146097L
! 317: /* there are 24 leap years in 100-year periods. ((100 - 24) * 365 + 24 * 366) */
! 318: #define DAYS_PER_CENTURY 36524L
! 319: /* there is one leap year every 4 years */
! 320: #define DAYS_PER_4_YEARS (3 * 365 + 366)
! 321: /* number of days in a non-leap year */
! 322: #define DAYS_PER_YEAR 365
! 323: /* number of days in January */
! 324: #define DAYS_IN_JANUARY 31
! 325: /* number of days in non-leap February */
! 326: #define DAYS_IN_FEBRUARY 28
! 327: /* number of years per era */
! 328: #define YEARS_PER_ERA 400
! 329:
! 330: static void pa_gmtime(pa_time_t lcltime, struct tm *res) {
! 331: long days, rem;
! 332: int era, weekday, year;
! 333: unsigned erayear, yearday, month, day;
! 334: unsigned long eraday;
! 335:
! 336: days = lcltime / SECS_PER_DAY;
! 337: rem = lcltime - (pa_time_t)days * SECS_PER_DAY;
! 338: days += EPOCH_ADJUSTMENT_DAYS;
! 339: if (rem < 0)
! 340: {
! 341: rem += SECS_PER_DAY;
! 342: --days;
! 343: }
! 344:
! 345: /* compute hour, min, and sec */
! 346: res->tm_hour = (int) (rem / SECS_PER_HOUR);
! 347: rem %= SECS_PER_HOUR;
! 348: res->tm_min = (int) (rem / SECS_PER_MIN);
! 349: res->tm_sec = (int) (rem % SECS_PER_MIN);
! 350:
! 351: /* compute day of week */
! 352: if ((weekday = ((ADJUSTED_EPOCH_WDAY + days) % DAYS_PER_WEEK)) < 0)
! 353: weekday += DAYS_PER_WEEK;
! 354: res->tm_wday = weekday;
! 355:
! 356: /* compute year, month, day & day of year */
! 357: /* for description of this algorithm see
! 358: * http://howardhinnant.github.io/date_algorithms.html#civil_from_days */
! 359: era = (days >= 0 ? days : days - (DAYS_PER_ERA - 1)) / DAYS_PER_ERA;
! 360: eraday = days - era * DAYS_PER_ERA; /* [0, 146096] */
! 361: erayear = (eraday - eraday / (DAYS_PER_4_YEARS - 1) + eraday / DAYS_PER_CENTURY -
! 362: eraday / (DAYS_PER_ERA - 1)) / 365; /* [0, 399] */
! 363: yearday = eraday - (DAYS_PER_YEAR * erayear + erayear / 4 - erayear / 100); /* [0, 365] */
! 364: month = (5 * yearday + 2) / 153; /* [0, 11] */
! 365: day = yearday - (153 * month + 2) / 5 + 1; /* [1, 31] */
! 366: month += month < 10 ? 2 : -10;
! 367: year = ADJUSTED_EPOCH_YEAR + erayear + era * YEARS_PER_ERA + (month <= 1);
! 368:
! 369: res->tm_yday = yearday >= DAYS_PER_YEAR - DAYS_IN_JANUARY - DAYS_IN_FEBRUARY ?
! 370: yearday - (DAYS_PER_YEAR - DAYS_IN_JANUARY - DAYS_IN_FEBRUARY) :
! 371: yearday + DAYS_IN_JANUARY + DAYS_IN_FEBRUARY + IS_LEAP(erayear);
! 372: res->tm_year = year - YEAR_BASE;
! 373: res->tm_mon = month;
! 374: res->tm_mday = day;
! 375:
! 376: res->tm_isdst = 0;
! 377: }
! 378:
! 379:
! 380: /*
! 381: * mktime.c
! 382: * Original Author: G. Haley
! 383: *
! 384: * Converts the broken-down time, expressed as local time, in the structure
! 385: * pointed to by tim_p into a calendar time value. The original values of the
! 386: * tm_wday and tm_yday fields of the structure are ignored, and the original
! 387: * values of the other fields have no restrictions. On successful completion
! 388: * the fields of the structure are set to represent the specified calendar
! 389: * time. Returns the specified calendar time. If the calendar time can not be
! 390: * represented, returns the value (time_t) -1.
! 391: */
! 392:
! 393: #define _DAYS_IN_MONTH(x) ((x == 1) ? days_in_feb : DAYS_IN_MONTH[x])
! 394: #define _DAYS_IN_YEAR(year) (IS_LEAP(year) ? 366 : 365)
! 395:
! 396: static void validate_structure(struct tm *tim_p) {
! 397: div_t res;
! 398: int days_in_feb = 28;
! 399:
! 400: /* calculate time & date to account for out of range values */
! 401: if (tim_p->tm_sec < 0 || tim_p->tm_sec > 59)
! 402: {
! 403: res = div (tim_p->tm_sec, 60);
! 404: tim_p->tm_min += res.quot;
! 405: if ((tim_p->tm_sec = res.rem) < 0)
! 406: {
! 407: tim_p->tm_sec += 60;
! 408: --tim_p->tm_min;
! 409: }
! 410: }
! 411:
! 412: if (tim_p->tm_min < 0 || tim_p->tm_min > 59)
! 413: {
! 414: res = div (tim_p->tm_min, 60);
! 415: tim_p->tm_hour += res.quot;
! 416: if ((tim_p->tm_min = res.rem) < 0)
! 417: {
! 418: tim_p->tm_min += 60;
! 419: --tim_p->tm_hour;
! 420: }
! 421: }
! 422:
! 423: if (tim_p->tm_hour < 0 || tim_p->tm_hour > 23)
! 424: {
! 425: res = div (tim_p->tm_hour, 24);
! 426: tim_p->tm_mday += res.quot;
! 427: if ((tim_p->tm_hour = res.rem) < 0)
! 428: {
! 429: tim_p->tm_hour += 24;
! 430: --tim_p->tm_mday;
! 431: }
! 432: }
! 433:
! 434: if (tim_p->tm_mon < 0 || tim_p->tm_mon > 11)
! 435: {
! 436: res = div (tim_p->tm_mon, 12);
! 437: tim_p->tm_year += res.quot;
! 438: if ((tim_p->tm_mon = res.rem) < 0)
! 439: {
! 440: tim_p->tm_mon += 12;
! 441: --tim_p->tm_year;
! 442: }
! 443: }
! 444:
! 445: if (_DAYS_IN_YEAR (tim_p->tm_year) == 366)
! 446: days_in_feb = 29;
! 447:
! 448: if (tim_p->tm_mday <= 0)
! 449: {
! 450: while (tim_p->tm_mday <= 0)
! 451: {
! 452: if (--tim_p->tm_mon == -1)
! 453: {
! 454: tim_p->tm_year--;
! 455: tim_p->tm_mon = 11;
! 456: days_in_feb =
! 457: ((_DAYS_IN_YEAR (tim_p->tm_year) == 366) ?
! 458: 29 : 28);
! 459: }
! 460: tim_p->tm_mday += _DAYS_IN_MONTH (tim_p->tm_mon);
! 461: }
! 462: }
! 463: else
! 464: {
! 465: while (tim_p->tm_mday > _DAYS_IN_MONTH (tim_p->tm_mon))
! 466: {
! 467: tim_p->tm_mday -= _DAYS_IN_MONTH (tim_p->tm_mon);
! 468: if (++tim_p->tm_mon == 12)
! 469: {
! 470: tim_p->tm_year++;
! 471: tim_p->tm_mon = 0;
! 472: days_in_feb =
! 473: ((_DAYS_IN_YEAR (tim_p->tm_year) == 366) ?
! 474: 29 : 28);
! 475: }
! 476: }
! 477: }
! 478: }
! 479:
! 480: static pa_time_t pa_mktime(struct tm *tim_p) {
! 481: pa_time_t tim = 0;
! 482: long days = 0;
! 483: int year;
! 484:
! 485: /* validate structure */
! 486: validate_structure (tim_p);
! 487:
! 488: /* compute hours, minutes, seconds */
! 489: tim += tim_p->tm_sec + (tim_p->tm_min * SECS_PER_MIN) +
! 490: (tim_p->tm_hour * SECS_PER_HOUR);
! 491:
! 492: /* compute days in year */
! 493: days += tim_p->tm_mday - 1;
! 494: days += DAYS_BEFORE_MONTH[tim_p->tm_mon];
! 495: if (tim_p->tm_mon > 1 && _DAYS_IN_YEAR (tim_p->tm_year) == 366)
! 496: days++;
! 497:
! 498: /* compute day of the year */
! 499: tim_p->tm_yday = days;
! 500:
! 501: if (tim_p->tm_year > 10000 || tim_p->tm_year < -10000)
! 502: return (time_t) -1;
! 503:
! 504: /* compute days in other years */
! 505: if ((year = tim_p->tm_year) > 70)
! 506: {
! 507: for (year = 70; year < tim_p->tm_year; year++)
! 508: days += _DAYS_IN_YEAR (year);
! 509: }
! 510: else if (year < 70)
! 511: {
! 512: for (year = 69; year > tim_p->tm_year; year--)
! 513: days -= _DAYS_IN_YEAR (year);
! 514: days -= _DAYS_IN_YEAR (year);
! 515: }
! 516:
! 517: /* compute total seconds */
! 518: tim += (pa_time_t)days * SECS_PER_DAY;
! 519:
! 520: /* compute day of the week */
! 521: if ((tim_p->tm_wday = (days + 4) % 7) < 0)
! 522: tim_p->tm_wday += 7;
! 523:
! 524: return tim;
! 525: }
! 526:
! 527: #endif
E-mail: