00001
00024 #include <stdlib.h>
00025 #include <string.h>
00026 #include <stdio.h>
00027 #include <math.h>
00028 #include <time.h>
00029 #include <ctype.h>
00030 #include "platform.h"
00031 #include "timestamp.h"
00032 #include "exception.h"
00033 #include "find.h"
00034 #include "output.h"
00035 #include "globals.h"
00036
00037 #ifndef WIN32
00038 #define _tzname tzname
00039 #define _timezone timezone
00040 #endif
00041
00042 #define TZFILE "tzinfo.txt"
00043
00044 #define DAY (86400*TS_SECOND)
00045 #define HOUR (3600*TS_SECOND)
00046 #define MINUTE (60*TS_SECOND)
00047 #define SECOND (TS_SECOND)
00048 #define MICROSECOND (TS_SECOND/1000000)
00050 typedef struct {int month, nth, day, hour, minute;} SPEC;
00051 static daysinmonth[] = {31,28,31,30,31,30,31,31,30,31,30,31};
00052 static char *dow[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
00053 #define ISLEAPYEAR(Y) ((Y)%4==0 && ((Y)%100!=0 || (Y)%400==0))
00054 #define YEAR0 (1970)
00055 #define YEAR0_ISLY (0)
00056 #define DOW0 (4)
00057
00058 static int tzvalid=0;
00059 static TIMESTAMP tszero[1000]={-1};
00060 static TIMESTAMP dststart[1000], dstend[1000];
00061 static TIMESTAMP tzoffset;
00062 static char current_tzname[32], tzstd[5], tzdst[5];
00063
00064 #define LOCALTIME(T) ((T)-tzoffset+(isdst((T))?3600:0))
00065 #define GMTIME(T) ((T)+tzoffset-(isdst((T)+tzoffset)?3600:0))
00066
00070 char *timestamp_current_timezone(void)
00071 {
00072 return current_tzname;
00073 }
00074
00078 int timestamp_year(TIMESTAMP ts, TIMESTAMP *remainder)
00079 {
00080 static int year = 0;
00081 int tsyear=0;
00082 if (tszero[0]==-1)
00083 {
00084 TIMESTAMP ts=0;
00085 int year=YEAR0;
00086 int n = (365+YEAR0_ISLY)*DAY;
00087 while (ts<TS_MAX && year<2969)
00088 {
00089 tszero[year-YEAR0]=ts;
00090 ts += n;
00091 year++;
00092 n = (ISLEAPYEAR(year) ? 366: 365)*DAY;
00093 }
00094 }
00095 while (year>0 && ts<tszero[year]) year--;
00096 while (year<MAXYEAR-YEAR0-1 && ts>tszero[year+1]) year++;
00097 if (remainder) *remainder = ts-tszero[year];
00098 return year+YEAR0;
00099 }
00100
00103 int isdst(TIMESTAMP t)
00104 {
00105 int year = timestamp_year(t+tzoffset,NULL) - YEAR0;
00106 return dststart[year]<=t && t<dstend[year];
00107 }
00108
00112 int local_datetime(TIMESTAMP ts, DATETIME *dt)
00113 {
00114 int n;
00115 TIMESTAMP rem;
00116 TIMESTAMP local = LOCALTIME(ts);
00117 int tsyear = timestamp_year(local,&rem);
00118 if(dt == NULL) return 0;
00119
00120 if (rem<0){
00121
00122
00123
00124 throw_exception("local_datetime(...): unable to determine localtime!");
00125 }
00126
00127
00128 if (ts<TS_ZERO && ts>TS_MAX)
00129 return 0;
00130
00131
00132 dt->timestamp = ts;
00133
00134
00135 dt->is_dst = (tzvalid && isdst(ts));
00136
00137
00138 dt->year=tsyear;
00139
00140
00141 dt->yearday = (unsigned short)(rem/DAY);
00142 dt->weekday = (unsigned short)((local/DAY + DOW0+7)%7);
00143
00144
00145 dt->month = 0;
00146 n = daysinmonth[0]*DAY;
00147 while (rem>=n)
00148 {
00149 rem -= n;
00150 dt->month++;
00151 n = (daysinmonth[dt->month] + (dt->month==1 && ISLEAPYEAR(dt->year) ? 1:0)) * 86400 * TS_SECOND;
00152 if(n < 86400 * 28){
00153 output_fatal("Breaking an infinite loop in local_datetime! (ts = %"FMT_INT64"ds", ts);
00154 return 0;
00155 }
00156 }
00157 dt->month++;
00158
00159
00160 dt->day = (unsigned short)(rem/DAY + 1);
00161 rem %= DAY;
00162
00163
00164 dt->hour = (unsigned short)(rem/HOUR);
00165 rem %= HOUR;
00166
00167
00168 dt->minute = (unsigned short)(rem/MINUTE);
00169 rem %= MINUTE;
00170
00171
00172 dt->second = (unsigned short)rem/TS_SECOND;
00173 rem %= SECOND;
00174
00175
00176 dt->microsecond = (unsigned int)rem;
00177
00178
00179 strncpy(dt->tz, tzvalid?(dt->is_dst ? tzdst : tzstd) : "GMT",sizeof(dt->tz));
00180
00181 return 1;
00182 }
00183
00186 TIMESTAMP mkdatetime(DATETIME *dt)
00187 {
00188 TIMESTAMP ts;
00189 int n;
00190
00191 if(dt == NULL) return TS_INVALID;
00192
00193
00194 timestamp_year(0,NULL);
00195 if (dt->year<YEAR0 || dt->year>=YEAR0+sizeof(tszero)/sizeof(tszero[0]))
00196 return TS_INVALID;
00197 ts = tszero[dt->year-YEAR0];
00198
00199
00200 for (n=1; n<dt->month; n++)
00201 ts += (daysinmonth[n-1]+(n==2&&ISLEAPYEAR(dt->year)?1:0))*DAY;
00202
00203
00204 ts += (dt->day-1)*DAY + dt->hour*HOUR + dt->minute*MINUTE + dt->second*SECOND + dt->microsecond*MICROSECOND;
00205
00206
00207 if (strcmp(dt->tz,"GMT")==0 || strcmp(dt->tz,"")==0)
00208 return ts;
00209
00210
00211 else if (strcmp(dt->tz,tzstd)==0)
00212 return ts+tzoffset;
00213
00214
00215 else if (strcmp(dt->tz,tzdst)==0)
00216 return ts+tzoffset-HOUR;
00217
00218
00219 return TS_INVALID;
00220 }
00221
00224 int strdatetime(DATETIME *t, char *buffer, int size)
00225 {
00226 int len;
00227 char tbuffer[1024];
00228
00229 if(t == NULL) return 0;
00230 if(buffer == NULL) return 0;
00231
00232
00233 if (t->microsecond!=0)
00234 len = sprintf(tbuffer,"%04d-%02d-%02d %02d:%02d:%02d.%06d %s",
00235 t->year,t->month,t->day,t->hour,t->minute,t->second,t->microsecond,t->tz);
00236 else
00237 len = sprintf(tbuffer,"%04d-%02d-%02d %02d:%02d:%02d %s",
00238 t->year,t->month,t->day,t->hour,t->minute,t->second,t->tz);
00239
00240 if(len < size){
00241 strncpy(buffer, tbuffer, len+1);
00242 return len;
00243 }
00244 return 0;
00245 }
00246
00250 TIMESTAMP compute_dstevent(int year, SPEC *spec, time_t offset)
00251 {
00252 TIMESTAMP t = TS_INVALID;
00253 int y, m, d, ndays=0, day1;
00254
00255 if(spec == NULL) return -1;
00256
00257 if (spec->day<0 || spec->day>7
00258 || spec->hour<0 || spec->hour>23
00259 || spec->minute<0 || spec->minute>59
00260 || spec->month<0 || spec->month>11
00261 || spec->nth<0 || spec->nth>5)
00262 return -1;
00263
00264
00265 for (y=YEAR0; y<year; y++)
00266 ndays += 365 + (ISLEAPYEAR(y)?1:0);
00267 for (m=0; m<spec->month-1; m++)
00268 ndays += daysinmonth[m] + ((m==1&&ISLEAPYEAR(y))?1:0);
00269 day1 = (ndays+DOW0+7)%7;
00270 d = ((8-day1)+(spec->nth-1)*7);
00271 while (d>daysinmonth[m]+((m==1&&ISLEAPYEAR(y))?1:0))
00272 d -= 7;
00273 ndays += d-1;
00274 t = (ndays*86400 + spec->hour*3600 + spec->minute*60);
00275 return t*TS_SECOND+tzoffset;
00276 }
00277
00280 int tz_info(char *tzspec, char *tzname, char *std, char *dst, time_t *offset)
00281 {
00282 int hours=0, minutes=0;
00283 char buf1[32], buf2[32];
00284 if ((strchr(tzspec,':')!=NULL && sscanf(tzspec,"%[A-Z]%d:%d%[A-Z]",buf1,&hours,&minutes,buf2)<3)
00285 || sscanf(tzspec,"%[A-Z]%d%[A-Z]",buf1,&hours,buf2)<2)
00286 return 0;
00287 if (hours<-12 || hours>12)
00288 return 0;
00289 if (minutes<0 || minutes>59)
00290 return 0;
00291 if (std) strcpy(std,buf1);
00292 if (dst) strcpy(dst,buf2);
00293 if (minutes==0)
00294 {
00295 if (tzname) sprintf(tzname,"%s%d%s",buf1,hours,buf2);
00296 if (offset) *offset=hours*3600;
00297 return 1;
00298 }
00299 else
00300 {
00301 if (tzname) sprintf(tzname,"%s%d:%02d%s",buf1,hours,minutes,buf2);
00302 if (offset) *offset=hours*3600+minutes*60;
00303 return 2;
00304 }
00305 }
00306
00310 char *tz_name(char *tzspec)
00311 {
00312 static char name[32]="GMT";
00313 return tz_info(tzspec,name,NULL,NULL,NULL)?name:NULL;
00314 }
00315
00318 time_t tz_offset(char *tzspec)
00319 {
00320 time_t offset;
00321 return tz_info(tzspec,NULL,NULL,NULL,&offset)?offset:-1;
00322 }
00323
00326 char *tz_std(char *tzspec)
00327 {
00328 static char std[32]="GMT";
00329 return tz_info(tzspec,NULL,std,NULL,NULL)?std:"GMT";
00330 }
00331
00334 char *tz_dst(char *tzspec)
00335 {
00336 static char dst[32]="GMT";
00337 return tz_info(tzspec,NULL,NULL,dst,NULL)?dst:"GMT";
00338 }
00339
00342 void set_tzspec(int year,char *tzname,SPEC *pStart, SPEC *pEnd)
00343 {
00344 int y;
00345 for (y=year-YEAR0; y<sizeof(tszero)/sizeof(tszero[0]); y++)
00346 {
00347 dststart[y] = compute_dstevent(y+YEAR0,pStart,tzoffset);
00348 dstend[y] = compute_dstevent(y+YEAR0,pEnd,tzoffset);
00349 }
00350 }
00351
00354 void load_tzspecs(char *tz)
00355 {
00356 char *filepath = find_file(TZFILE,NULL,FF_READ);
00357 FILE *fp;
00358 char buffer[1024];
00359 int linenum=0;
00360 int year=YEAR0;
00361 tzvalid=0;
00362 strncpy(current_tzname,tz_name(tz),sizeof(current_tzname));
00363 tzoffset = tz_offset(current_tzname);
00364 strncpy(tzstd,tz_std(current_tzname),sizeof(tzstd));
00365 strncpy(tzdst,tz_dst(current_tzname),sizeof(tzdst));
00366
00367 if (filepath==NULL)
00368 throw_exception("timezone specification file %s not found in GLPATH=%s: %s", TZFILE, getenv("GLPATH"), strerror(errno));
00369
00370 fp = fopen(filepath,"r");
00371 if (fp==NULL)
00372 throw_exception("%s: access denied: %s", filepath, strerror(errno));
00373
00374 while (fgets(buffer,sizeof(buffer),fp))
00375 {
00376 char *p;
00377 char tzname[32];
00378 SPEC start, end;
00379 int form;
00380 linenum++;
00381
00382
00383 p = strchr(buffer,';');
00384 if (p!=NULL)
00385 *p = '\0';
00386
00387
00388 p = buffer+strlen(buffer)-1;
00389 while (iswspace(*p) && p>buffer)
00390 *p--='\0';
00391
00392
00393 if (buffer[0]=='\0' || iswspace(buffer[0]))
00394 continue;
00395
00396
00397 if (sscanf(buffer,"[%d]",&year)==1)
00398 continue;
00399
00400
00401 form = sscanf(buffer,"%[^,],M%d.%d.%d/%d:%d,M%d.%d.%d/%d:%d",
00402 tzname,
00403 &start.month,&start.nth,&start.day,&start.hour,&start.minute,
00404 &end.month,&end.nth,&end.day,&end.hour,&end.minute);
00405
00406
00407 if (tz!=NULL && strcmp(tz_name(tzname),current_tzname)!=0)
00408 continue;
00409
00410 if (form==1)
00411 set_tzspec(year,current_tzname,NULL,NULL);
00412 else if (form==11)
00413 set_tzspec(year,current_tzname,&start,&end);
00414 else
00415 throw_exception("%s(%d): %s is not a valid timezone spec", filepath,linenum,buffer);
00416 }
00417
00418 if (ferror(fp))
00419 output_error("%s(%d): %s", filepath, linenum, strerror(errno));
00420 else
00421 output_verbose("%s loaded ok", filepath);
00422
00423 fclose(fp);
00424 tzvalid=1;
00425 }
00426
00430 char *timestamp_set_tz(char *tz_name)
00431 {
00432 static char guess[64];
00433 if (tz_name==NULL)
00434 tz_name=getenv("TZ");
00435 if (tz_name==NULL)
00436 {
00437 if (strcmp(_tzname[0],"")==0)
00438 throw_exception("timezone not identified");
00439 if (_timezone%60==0)
00440 sprintf(guess,"%s%d%s", _tzname[0], _timezone/3600, _tzname[1]);
00441 else
00442 sprintf(guess,"%s%d:%d%s", _tzname[0], _timezone/3600, _timezone/60, _tzname[1]);
00443 tz_name = guess;
00444 }
00445 load_tzspecs(tz_name);
00446 return current_tzname;
00447 }
00448
00451 int convert_from_timestamp(TIMESTAMP ts, char *buffer, int size)
00452 {
00453 char temp[64]="INVALID";
00454 int len=(int)strlen(temp);
00455 if (ts>=365*DAY)
00456 { DATETIME t;
00457 if (ts>=0)
00458 {
00459 if (ts<TS_NEVER)
00460 {
00461 if (local_datetime(ts,&t))
00462 len = strdatetime(&t,temp,sizeof(temp));
00463 else
00464 throw_exception("%"FMT_INT64"d is an invalid timestamp", ts);
00465 }
00466 else
00467 len=sprintf(temp,"%s","NEVER");
00468 }
00469 }
00470 else if (ts>=DAY)
00471 len=sprintf(temp,"%lfd",(double)ts/DAY);
00472 else if (ts>=HOUR)
00473 len=sprintf(temp,"%lfh",(double)ts/HOUR);
00474 else if (ts>=MINUTE)
00475 len=sprintf(temp,"%lfm",(double)ts/MINUTE);
00476 else if (ts>=SECOND)
00477 len=sprintf(temp,"%lfs",(double)ts/SECOND);
00478 else if (ts==0)
00479 len=sprintf(temp,"%s","INIT");
00480 else
00481 len=sprintf(temp,"%"FMT_INT64"d",ts);
00482 if (len<size)
00483 {
00484 if(ts == TS_NEVER){
00485 strcpy(buffer, "TS_NEVER");
00486 return (int)strlen("TS_NEVER");
00487 }
00488 strcpy(buffer,temp);
00489 return len;
00490 }
00491 else
00492 return 0;
00493 }
00494
00497 TIMESTAMP convert_to_timestamp(char *value)
00498 {
00499
00500 int Y=0,m=0,d=0,H=0,M=0,S=0;
00501 char tz[5]="";
00502 if (*value=='\'' || *value=='"') value++;
00503 if (sscanf(value,"%d-%d-%d %d:%d:%d %[-+:A-Za-z0-9]",&Y,&m,&d,&H,&M,&S,tz)>=3)
00504 {
00505 int isdst = (strcmp(tz,tzdst)==0) ? 1 : 0;
00506 DATETIME dt = {Y,m,d,H,M,S,0,isdst};
00507 strncpy(dt.tz,tz,sizeof(dt.tz));
00508 return mkdatetime(&dt);
00509 }
00510 else if (strcmp(value,"INIT")==0)
00511 return 0;
00512 else if (strcmp(value, "TS_NEVER")==0)
00513 return TS_NEVER;
00514 else if (strcmp(value, "NOW") == 0)
00515 return global_clock;
00516 else if (isdigit(value[0]))
00517 {
00518 double t = atof(value);
00519 char *p=value;
00520 while (isdigit(*p) || *p=='.') p++;
00521 switch (*p) {
00522 case 's':
00523 case 'S':
00524 t *= SECOND;
00525 break;
00526 case 'm':
00527 case 'M':
00528 t *= MINUTE;
00529 break;
00530 case 'h':
00531 case 'H':
00532 t *= HOUR;
00533 break;
00534 case 'd':
00535 case 'D':
00536 t *= DAY;
00537 break;
00538 default:
00539 return TS_NEVER;
00540 break;
00541 }
00542 return (TIMESTAMP)(t+0.5);
00543 }
00544 else
00545 return TS_NEVER;
00546 }
00547
00548 double timestamp_to_days(TIMESTAMP t)
00549 {
00550 return (double)t/DAY;
00551 }
00552
00553 double timestamp_to_hours(TIMESTAMP t)
00554 {
00555 return (double)t/HOUR;
00556 }
00557
00558 double timestamp_to_minutes(TIMESTAMP t)
00559 {
00560 return (double)t/MINUTE;
00561 }
00562
00563 double timestamp_to_seconds(TIMESTAMP t)
00564 {
00565 return (double)t/SECOND;
00566 }
00567
00571 int timestamp_test(void)
00572 {
00573 #define NYEARS 50
00574 int year;
00575 static DATETIME last_t;
00576 TIMESTAMP step = SECOND;
00577 TIMESTAMP ts;
00578 char buf1[64], buf2[64];
00579 char steptxt[32];
00580 TIMESTAMP *event[]={dststart,dstend};
00581 int failed=0, succeeded=0;
00582
00583 output_verbose("performing daylight saving time tests");
00584 output_test("BEGIN: daylight saving time event test for TZ=%s...", current_tzname);
00585 convert_from_timestamp(step,steptxt,sizeof(steptxt));
00586 for (year=0; year<NYEARS; year++)
00587 {
00588 int test;
00589 for (test=0; test<2; test++)
00590 {
00591 for (ts=(event[test])[year]-2*step; ts<(event[test])[year]+2*step;ts+=step)
00592 {
00593 DATETIME t;
00594 if (local_datetime(ts,&t))
00595 {
00596 if (last_t.is_dst!=t.is_dst)
00597 output_test("%s + %s = %s", strdatetime(&last_t,buf1,sizeof(buf1))?buf1:"(invalid)", steptxt, strdatetime(&t,buf2,sizeof(buf2))?buf2:"(invalid)");
00598 last_t = t;
00599 succeeded++;
00600 }
00601 else
00602 {
00603 output_test("FAILED: unable to convert ts=%"FMT_INT64"d to local time", ts);
00604 failed++;
00605 }
00606 }
00607 }
00608 }
00609 output_test("END: daylight saving time event test");
00610
00611 step=HOUR;
00612 convert_from_timestamp(step,steptxt,sizeof(steptxt));
00613 output_test("BEGIN: round robin test at %s timesteps",steptxt);
00614 for (ts=DAY+tzoffset; ts<DAY*365*NYEARS; ts+=step)
00615 {
00616 DATETIME t;
00617 if (local_datetime(ts,&t))
00618 {
00619 TIMESTAMP tt = mkdatetime(&t);
00620 convert_from_timestamp(ts,buf1,sizeof(buf1));
00621 convert_from_timestamp(tt,buf2,sizeof(buf2));
00622 if (tt==TS_INVALID)
00623 {
00624 output_test("FAILED: unable to extract %04d-%02d-%02d %02d:%02d:%02d %s (dow=%s, doy=%d)", t.year,t.month,t.day,t.hour,t.minute,t.second,t.tz,dow[t.weekday],t.yearday);
00625 failed++;
00626 }
00627 else if (tt!=ts)
00628 {
00629 output_test("FAILED: unable to match %04d-%02d-%02d %02d:%02d:%02d %s (dow=%s, doy=%d)\n from=%s, to=%s", t.year,t.month,t.day,t.hour,t.minute,t.second,t.tz,dow[t.weekday],t.yearday,buf1,buf2);
00630 failed++;
00631 }
00632 else if (convert_to_timestamp(buf1)!=ts)
00633 {
00634 output_test("FAILED: unable to convert %04d-%02d-%02d %02d:%02d:%02d %s (dow=%s, doy=%d) back to a timestamp\n from=%s, to=%s", t.year,t.month,t.day,t.hour,t.minute,t.second,t.tz,dow[t.weekday],t.yearday,buf1,buf2);
00635 output_test(" expected %" FMT_INT64 "d but got %" FMT_INT64 "d", ts, convert_to_timestamp(buf1));
00636 failed++;
00637 }
00638 else
00639 succeeded++;
00640 }
00641 else
00642 {
00643 output_test("FAILED: timestamp_test: unable to convert ts=%"FMT_INT64"d to local time", ts);
00644 failed++;
00645 }
00646 }
00647 output_test("END: round robin test",steptxt);
00648 output_test("END: daylight saving time tests for %d to %d", YEAR0, YEAR0+NYEARS);
00649 output_debug("daylight saving time tests: %d succeeded, %d failed (see '%s' for details)", succeeded, failed, global_testoutputfile);
00650 return failed;
00651 }
00652