core/timestamp.c

Go to the documentation of this file.
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) /* basis year is 1970 */
00055 #define YEAR0_ISLY (0) /* set to 1 if YEAR0 is a leap year, 1970 is not */
00056 #define DOW0 (4) /* 1/1/1970 is a Thursday (day 4) */
00057 
00058 static int tzvalid=0;
00059 static TIMESTAMP tszero[1000]={-1}; /* zero timestamp offset for each year */
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) /* need to initialize tszero array */
00083     {
00084         TIMESTAMP ts=0;
00085         int year=YEAR0;
00086         int n = (365+YEAR0_ISLY)*DAY; /* n ticks in year */
00087         while (ts<TS_MAX && year<2969)
00088         {
00089             tszero[year-YEAR0]=ts;
00090             ts += n; /* add n ticks from ts */
00091             year++; /* add to year */
00092             n = (ISLEAPYEAR(year) ? 366: 365)*DAY; /* n ticks is next year */
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         /* throw_exception("local_datetime(TIMESTAMP=%"FMT_INT64"d, DATETIME *dt={...}): unable to determine local time %"FMT_INT64"ds %s", ts, rem, tzvalid?(dt->is_dst ? tzdst : tzstd) : "GMT",sizeof(dt->tz)); */
00122         /* dt may not be initialized! -mh */
00123         /* throw_exception("local_datetime(TIMESTAMP=%"FMT_INT64"d, DATETIME *dt={...}): unable to determine local time %"FMT_INT64"ds", ts, rem); */
00124         throw_exception("local_datetime(...): unable to determine localtime!");
00125     }
00126     
00127 
00128     if (ts<TS_ZERO && ts>TS_MAX) /* timestamp out of range */
00129         return 0;
00130     
00131     /* ts is valid */
00132     dt->timestamp = ts;
00133 
00134     /* DST? */
00135     dt->is_dst = (tzvalid && isdst(ts));
00136 
00137     /* compute year */
00138     dt->year=tsyear;
00139 
00140     /* yearday and weekday */
00141     dt->yearday = (unsigned short)(rem/DAY);
00142     dt->weekday = (unsigned short)((local/DAY + DOW0+7)%7);
00143 
00144     /* compute month */
00145     dt->month = 0;
00146     n = daysinmonth[0]*DAY;
00147     while (rem>=n)
00148     {
00149         rem -= n; /* subtract n ticks from ts */
00150         dt->month++; /* add to 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++; /* Jan=1 */
00158 
00159     /* compute day */
00160     dt->day = (unsigned short)(rem/DAY + 1);
00161     rem %= DAY;
00162 
00163     /* compute hour */
00164     dt->hour = (unsigned short)(rem/HOUR);
00165     rem %= HOUR;
00166 
00167     /* compute minute */
00168     dt->minute = (unsigned short)(rem/MINUTE);
00169     rem %= MINUTE;
00170 
00171     /* compute second */
00172     dt->second = (unsigned short)rem/TS_SECOND;
00173     rem %= SECOND;
00174 
00175     /* compute microsecond */
00176     dt->microsecond = (unsigned int)rem;
00177     
00178     /* determine timezone */
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     /* start with year */
00194     timestamp_year(0,NULL); /* initializes tszero */
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     /* add month */
00200     for (n=1; n<dt->month; n++)
00201         ts += (daysinmonth[n-1]+(n==2&&ISLEAPYEAR(dt->year)?1:0))*DAY;
00202 
00203     /* add day, hour, minute, second, usecs */
00204     ts += (dt->day-1)*DAY + dt->hour*HOUR + dt->minute*MINUTE + dt->second*SECOND + dt->microsecond*MICROSECOND;
00205 
00206     /* adjust for GMT (or unspecified) */
00207     if (strcmp(dt->tz,"GMT")==0 || strcmp(dt->tz,"")==0)
00208         return ts;
00209 
00210     /* adjust to standard local time */
00211     else if (strcmp(dt->tz,tzstd)==0)
00212         return ts+tzoffset;
00213 
00214     /* adjust to daylight local time */
00215     else if (strcmp(dt->tz,tzdst)==0)
00216         return ts+tzoffset-HOUR;
00217     
00218     /* not a valid timezone */
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     /* choose best format */
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     /* check values */
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     /* calculate days */
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; /* weekday of first day of month */
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         /* wipe comments */
00383         p = strchr(buffer,';');
00384         if (p!=NULL)
00385             *p = '\0';
00386 
00387         /* remove trailing whitespace */
00388         p = buffer+strlen(buffer)-1;
00389         while (iswspace(*p) && p>buffer)
00390             *p--='\0';
00391 
00392         /* ignore blank lines or lines starting with white space*/
00393         if (buffer[0]=='\0' || iswspace(buffer[0]))
00394             continue;
00395 
00396         /* year section */
00397         if (sscanf(buffer,"[%d]",&year)==1)
00398             continue;
00399 
00400         /* TZ spec */
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         /* load only TZ requested */
00407         if (tz!=NULL && strcmp(tz_name(tzname),current_tzname)!=0)
00408             continue;
00409 
00410         if (form==1) /* no DST */
00411             set_tzspec(year,current_tzname,NULL,NULL);
00412         else if (form==11) /* full DST spec */
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     /* try date-time format */
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     {   /* timestamp format */
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 

GridLAB-DTM Version 1.0
An open-source project initiated by the US Department of Energy