1 /********************************************************************************
2 *                                                                               *
3 *                                T i m e   S t u f f                            *
4 *                                                                               *
5 *********************************************************************************
6 * Copyright (C) 2019,2021 by Jeroen van der Zijp.   All Rights Reserved.        *
7 *********************************************************************************
8 * This library is free software; you can redistribute it and/or modify          *
9 * it under the terms of the GNU Lesser General Public License as published by   *
10 * the Free Software Foundation; either version 3 of the License, or             *
11 * (at your option) any later version.                                           *
12 *                                                                               *
13 * This library is distributed in the hope that it will be useful,               *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of                *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                 *
16 * GNU Lesser General Public License for more details.                           *
17 *                                                                               *
18 * You should have received a copy of the GNU Lesser General Public License      *
19 * along with this program.  If not, see <http://www.gnu.org/licenses/>          *
20 ********************************************************************************/
21 #include "xincs.h"
22 #include "fxver.h"
23 #include "fxdefs.h"
24 #include "fxmath.h"
25 #include "fxascii.h"
26 #include "FXAtomic.h"
27 #include "FXElement.h"
28 #include "FXString.h"
29 #include "FXSystem.h"
30 
31 
32 /*
33   Notes:
34   - Handy functions for manipulating calendar time.
35 */
36 
37 
38 using namespace FX;
39 
40 /*******************************************************************************/
41 
42 namespace FX {
43 
44 // Time units in terms of nanoseconds
45 static const FXTime seconds=1000000000;
46 static const FXTime minutes=60*seconds;
47 static const FXTime hours=60*minutes;
48 static const FXTime days=24*hours;
49 
50 
51 // Default formatting string used for time formatting
52 const FXchar FXSystem::defaultTimeFormat[]="%m/%d/%Y %H:%M:%S";
53 
54 
55 // ISO 8601 time format (yyyy-mm-ddThh:mm:ss+hhmm) formatting string
56 const FXchar FXSystem::isoTimeFormat[]="%FT%T%z";
57 
58 
59 // Cumulative days of the year, for non-leap-years and leap-years
60 static const FXint days_of_year[2][13]={
61   {0,31,59,90,120,151,181,212,243,273,304,334,365},
62   {0,31,60,91,121,152,182,213,244,274,305,335,366}
63   };
64 
65 
66 // Is year a leap-year
is_leap(FXint year)67 static FXint is_leap(FXint year){
68   return !(year%4) && ((year%100) || !(year%400));
69   }
70 
71 
72 // Returns day of week in civil calendar [0, 6] -> [Sun, Sat],
73 // from z is number of days since 1970-01-01.
weekday_from_days(FXlong z)74 static FXuint weekday_from_days(FXlong z){
75   return (FXuint)(z>=-4 ? (z+4)%7 : (z+5)%7+6);
76   }
77 
78 
79 // From year, month (1..12), day (1..31) return year-day (1..366).
yearday_from_date(FXint y,FXint m,FXint d)80 static FXint yearday_from_date(FXint y,FXint m,FXint d){
81   return days_of_year[is_leap(y)][m-1]+d;
82   }
83 
84 /*******************************************************************************/
85 
86 // Returns number of days since civil 1970-01-01.  Negative values indicate
87 // days prior to 1970-01-01.
88 // y is year, m is month of year (1..12), d is day of month (1..31).
daysFromCivil(FXint y,FXint m,FXint d)89 FXlong FXSystem::daysFromCivil(FXint y,FXint m,FXint d){
90   y-=(m<=2);                                            // March
91   FXlong era=(y>=0?y:y-399)/400;                        // Era
92   FXuint yoe=(FXuint)(y-era*400);                       // [0, 399]
93   FXuint doy=(153*(m+(m>2?-3:9))+2)/5+d-1;              // [0, 365]
94   FXuint doe=yoe*365+yoe/4-yoe/100+doy;                 // [0, 146096]
95   return era*146097+doe-719468;                         // Relative to epoch
96   }
97 
98 
99 // Returns year/month/day in civil calendar.
100 // z is number of days since 1970-01-01. Negative values indicate
101 // days prior to 1970-01-01.
102 // y is year, m is month of year (1..12), d is day of month (1..31).
civilFromDays(FXint & y,FXint & m,FXint & d,FXlong z)103 void FXSystem::civilFromDays(FXint& y,FXint& m,FXint& d,FXlong z){
104   z+=719468;                                            // Relative to era
105   FXlong era=(z>=0?z:z-146096)/146097;                  // Era
106   FXuint doe=(FXuint)(z-era*146097);                    // [0, 146096]
107   FXuint yoe=(doe-doe/1460+doe/36524-doe/146096)/365;   // [0, 399]
108   FXuint doy=doe-(365*yoe+yoe/4-yoe/100);               // [0, 365]
109   FXuint mp=(5*doy+2)/153;                              // [0, 11]
110   y=(FXint)(era*400+yoe);
111   d=doy-(153*mp+2)/5+1;                                 // [1, 31]
112   m=mp+(mp<10?3:-9);                                    // [1, 12]
113   y+=(m<=2);                                            // March
114   }
115 
116 /*******************************************************************************/
117 
118 // Compute nanoseconds since Unix Epoch from struct tm
timeFromSystemTime(const Time & st)119 FXTime FXSystem::timeFromSystemTime(const Time& st){
120   FXint year=st.year;
121   FXint month=st.month;
122   FXint day=st.mday;
123   FXint hour=st.hour;
124   FXint min=st.min;
125   FXint sec=st.sec;
126   FXint nano=st.nano;
127   FXint leap;
128 
129   // Validate nanoseconds
130   if(nano>seconds){
131     sec+=nano/seconds;
132     nano%=seconds;
133     }
134 
135   // Validate seconds
136   if(sec>=60){
137     min+=sec/60;
138     sec%=60;
139     }
140 
141   // Validate minutes
142   if(min>=60){
143     hour+=min/60;
144     min%=60;
145     }
146 
147   // Validate days
148   if(hour>=24){
149     day+=hour/24;
150     hour%=24;
151     }
152 
153   // Validate month
154   if(month>=13){
155     year+=(month-1)/12;
156     month=(month-1)%12+1;
157     }
158 
159   // Is leap year
160   leap=is_leap(year);
161 
162   // Validate day of month
163   while(day>days_of_year[leap][month]){
164     day-=days_of_year[leap][month];
165     month+=1;
166     if(13<=month){
167       month=1;
168       year+=1;
169       leap=is_leap(year);
170       }
171     }
172 
173   // Return nanoseconds since Epoch
174   return (((daysFromCivil(year,month,day)*24+hour)*60+min)*60+sec)*seconds+nano;
175   }
176 
177 
178 // Return system time from utc in nanoseconds since Unix Epoch
systemTimeFromTime(Time & st,FXTime utc)179 void FXSystem::systemTimeFromTime(Time& st,FXTime utc){
180 
181   // Compute days from nanoseconds, rounding down
182   FXlong zz=(0<=utc ? utc : utc-(days-1))/days;
183 
184   // Compute date from seconds
185   civilFromDays(st.year,st.month,st.mday,zz);
186 
187   // Compute day of year
188   st.yday=yearday_from_date(st.year,st.month,st.mday);
189 
190   // Compute day of week
191   st.wday=weekday_from_days(zz);
192 
193   // Hours
194   utc=utc-zz*days;
195   st.hour=(FXint)(utc/hours);
196 
197   // Minutes
198   utc=utc-st.hour*hours;
199   st.min=(FXint)(utc/minutes);
200 
201   // Seconds
202   utc=utc-st.min*minutes;
203   st.sec=(FXint)(utc/seconds);
204 
205   // Nanoseconds
206   utc=utc-st.sec*seconds;
207   st.nano=(FXint)utc;
208 
209   // Offset utc
210   st.offset=0;
211   }
212 
213 /*******************************************************************************/
214 
215 
216 // Time zone variables, set only once
217 static volatile FXuint local_zone_set=0;
218 
219 #if defined(_WIN32)
220 //
221 // The Windows TIME_ZONE_INFORMATION struct contains:
222 //
223 //   struct TIME_ZONE_INFORMATION {
224 //     LONG       Bias;                 // UTC = localtime + bias (minutes)
225 //     WCHAR      StandardName[32];     // Standard time name
226 //     SYSTEMTIME StandardDate;         // Time when daylight saving to standard time occurs
227 //     LONG       StandardBias;         // Value added to Bias during standard time (most zones = 0)
228 //     WCHAR      DaylightName[32];     // Daylight savings time name
229 //     SYSTEMTIME DaylightDate;         // Time when standard time to daylight savings time occurs
230 //     LONG       DaylightBias;         // Value added to Bias during daylight savings time (most zones = -60)
231 //     };
232 //
233 // While the Windows SYSTEMTIME struct contains:
234 //
235 //   struct SYSTEMTIME {
236 //     WORD wYear;                      // Year, 1601..
237 //     WORD wMonth;                     // Month, january..december (1..12)
238 //     WORD wDayOfWeek;                 // Day of week, sunday..monday (0..7)
239 //     WORD wDay;                       // Day of month, (1..31) [SEE NOTES BELOW]
240 //     WORD wHour;                      // Hour (0..23)
241 //     WORD wMinute;                    // Minutes (0..59)
242 //     WORD wSecond;                    // Seconds (0..59)
243 //     WORD wMilliseconds;              // Milliseconds (0..999)
244 //     };
245 //
246 static TIME_ZONE_INFORMATION tzi;
247 #endif
248 
249 
250 // Call tzset() only once
setuplocaltimezone()251 static void setuplocaltimezone(){
252   if(atomicSet(&local_zone_set,1)==0){
253 #if defined(_WIN32)
254     GetTimeZoneInformation(&tzi);
255 #else
256     tzset();
257 #endif
258     }
259   }
260 
261 
262 // Return offset between standard local time zone to UTC, in nanoseconds
localTimeZoneOffset()263 FXTime FXSystem::localTimeZoneOffset(){
264   setuplocaltimezone();
265 #if defined(_WIN32)
266   return minutes*tzi.Bias;              // +minutes*tzi.StandardBias;
267 #elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
268   struct tm tmresult;
269   time_t tmp=time(&tmp);
270   struct tm* ptm=localtime_r(&tmp,&tmresult);
271   return seconds*(-ptm->tm_gmtoff + ptm->tm_isdst*3600);
272 #else
273   return seconds*timezone;
274 #endif
275   }
276 
277 
278 // Return offset daylight savings time to standard time, in nanoseconds
daylightSavingsOffset()279 FXTime FXSystem::daylightSavingsOffset(){
280   setuplocaltimezone();
281 #if defined(_WIN32)
282   return minutes*tzi.DaylightBias;      // Or difference between standard and daylight bias.
283 #elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
284   return -hours*((tzname[1][0] == ' ') ? 0 : 1);
285 #else
286   return -hours*daylight;
287 #endif
288   }
289 
290 
291 // Return time zone name (or daylight savings time time zone name)
localTimeZoneName(FXbool dst)292 FXString FXSystem::localTimeZoneName(FXbool dst){
293   setuplocaltimezone();
294 #if defined(_WIN32)
295   return FXString(dst?tzi.DaylightName:tzi.StandardName);
296 #else
297   return FXString(tzname[dst]);
298 #endif
299   }
300 
301 
302 #if defined(WIN32)
303 
304 // Convert utc (ns) since 01/01/1970 to 100ns since 01/01/1601
fxwintime(FXTime utc)305 static inline FXTime fxwintime(FXTime utc){
306   return utc/FXLONG(100)+FXLONG(116444736000000000);
307   }
308 
309 
310 // Compare incoming date cur against changeover date chg; there are
311 // two ways to compare the numbers.
312 // When chg.wYear!=0, the change over occurs only once on the indicated
313 // absolute time.
314 // If chg.wYear==0, the switch between standard and daylight savings time
315 // occurs yearly, on the nth occurrence of wDayOfWeek, where n is given
316 // in the wDay variable of the TIME_ZONE_INFORMATION StandardDate or
317 // DaylightDate struct.
compare_zone_switch_over(const SYSTEMTIME & cur,const SYSTEMTIME & chg)318 static FXint compare_zone_switch_over(const SYSTEMTIME& cur,const SYSTEMTIME& chg){
319   static const FXint month_lengths[12]={31,28,31,30,31,30,31,31,30,31,30,31};
320   FXint cursecs,chgsecs,chgday,first,last;
321 
322   // Absolute date
323   if(chg.wYear){
324     if(cur.wYear==chg.wYear){
325       if(cur.wMonth==chg.wMonth){
326         if(cur.wDay==chg.wDay){
327           cursecs=(cur.wHour*60+cur.wMinute)*60+cur.wSecond;
328           chgsecs=(chg.wHour*60+chg.wMinute)*60+chg.wSecond;
329           return cursecs-chgsecs;
330           }
331         return cur.wDay-chg.wDay;
332         }
333       return cur.wMonth-chg.wMonth;
334       }
335     return cur.wYear-chg.wYear;
336     }
337 
338   // Relative date
339   if(cur.wMonth==chg.wMonth){
340 
341     // Calculate the day of the first wDayOfWeek in the month
342     first=(6+chg.wDayOfWeek-cur.wDayOfWeek+cur.wDay)%7+1;
343 
344     // Check needed for the 5th weekday of the month
345     last=month_lengths[cur.wMonth-1]+(cur.wMonth==2 && is_leap(cur.wYear));
346 
347     // Switch at the nth occurrence (value in chg.wDay) of certain day of week
348     chgday=first+7*(chg.wDay-1);
349     if(chgday>last) chgday-=7;
350 
351     chgsecs=((chgday*24+chg.wHour)*60+chg.wMinute)*60+chg.wSecond;
352     cursecs=((cur.wDay*24+cur.wHour)*60+cur.wMinute)*60+cur.wSecond;
353     return cursecs-chgsecs;
354     }
355   return (FXint)cur.wMonth-(FXint)chg.wMonth;
356   }
357 
358 
359 // Determine if daylight savings in effect
360 // The daylight savings time switch is tied to local time, and local
361 // time's year is tied to time zone.  However, time zone is a function
362 // of the local year [time zone info can change from year to year].
363 // We assume that the time zone is relatively stable, but we may
364 // call GetTimeZoneInformationForYear() in a future revision to
365 // obtain only DST/STD time switches, which are changed more rapidly.
daylightSavingsState(FXTime utc,FXbool local)366 static FXint daylightSavingsState(FXTime utc,FXbool local){
367 
368   // Expanded date/time
369   SYSTEMTIME loc;
370 
371   // Assume local time
372   FXTime ftloc=utc;
373   FXTime ftdst=utc;
374   FXTime ftstd=utc;
375 
376   // If UTC, convert to local using
377   if(!local){
378     ftloc=ftloc-tzi.Bias*minutes;
379     ftdst=ftloc-tzi.DaylightBias*minutes;
380     ftstd=ftloc-tzi.StandardBias*minutes;
381     }
382 
383   // Convert to windows time
384   ftloc=fxwintime(ftloc);
385 
386   // Get expanded date/time
387   FileTimeToSystemTime((const FILETIME*)&ftloc,&loc);
388 
389   // If wMonth is zero then no daylight savings in effect
390   if(tzi.DaylightDate.wMonth && tzi.StandardDate.wMonth){
391 
392     // Convert UNIX Epoch to windows time
393     ftdst=fxwintime(ftdst);
394     ftstd=fxwintime(ftstd);
395 
396     // Expanded date/time
397     SYSTEMTIME dst;
398     SYSTEMTIME std;
399 
400     // Get expanded date/time
401     FileTimeToSystemTime((const FILETIME*)&ftdst,&dst);
402     FileTimeToSystemTime((const FILETIME*)&ftstd,&std);
403 
404     // Daylight savings time prior to switch to standard time
405     FXbool before_std_date=(compare_zone_switch_over(dst,tzi.StandardDate)<0);
406 
407     // Standard time after switch to daylight savings time
408     FXbool after_dst_date=(compare_zone_switch_over(std,tzi.DaylightDate)>=0);
409 
410     // Northern hemisphere
411     if(tzi.DaylightDate.wMonth<tzi.StandardDate.wMonth){
412       if(before_std_date && after_dst_date) return 1;
413       }
414 
415     // Southern hemisphere
416     else{
417       if(before_std_date || after_dst_date) return 1;
418       }
419     }
420   return 0;
421   }
422 
423 #endif
424 
425 
426 // Return 1 if daylight savings time is active at utc in nanoseconds since Unix Epoch
daylightSavingsActive(FXTime utc)427 FXTime FXSystem::daylightSavingsActive(FXTime utc){
428   setuplocaltimezone();
429 #if defined(_WIN32)
430   return daylightSavingsState(utc,false);
431 #elif defined(HAVE_LOCALTIME_R)
432   struct tm tmresult;
433   time_t tmp=(time_t)(utc/seconds);
434   struct tm* ptm=localtime_r(&tmp,&tmresult);
435   FXTRACE((100,"FXSystem::daylightSavingsActive(%lld) = %d\n",utc,ptm && ptm->tm_isdst!=0));
436   return ptm && ptm->tm_isdst!=0;
437 #else
438   time_t tmp=(time_t)(utc/seconds);
439   struct tm* ptm=localtime(&tmp);
440   FXTRACE((100,"FXSystem::daylightSavingsActive(%lld) = %d\n",utc,ptm && ptm->tm_isdst!=0));
441   return ptm && ptm->tm_isdst!=0;
442 #endif
443   }
444 
445 /*******************************************************************************/
446 
447 // Format utc in nanoseconds since Unix Epoch to date-time string using given format
universalTime(FXTime utc,const FXchar * format)448 FXString FXSystem::universalTime(FXTime utc,const FXchar *format){
449   FXSystem::Time st={0,0,0,0,0,0,0,0,0,0};
450   FXSystem::systemTimeFromTime(st,utc);
451   return FXSystem::systemTimeFormat(st,format);
452   }
453 
454 
455 // Parse utc date-time string to UTC nanoseconds since Unix Epoch using given format
456 // Assume string is time in UTC, unless a time zone offset was parsed; in that
457 // case, adjust the time according to the zone offset.
universalTime(const FXchar * string,const FXchar * format)458 FXTime FXSystem::universalTime(const FXchar* string,const FXchar* format){
459   FXSystem::Time st={0,0,0,0,0,0,0,0,0,0};
460   if(FXSystem::systemTimeParse(st,string,format)){
461     return FXSystem::timeFromSystemTime(st)+st.offset*seconds;
462     }
463   return forever;
464   }
465 
466 
467 // Parse date-time string to UTC nanoseconds since Unix Epoch using given format
universalTime(const FXString & string,const FXchar * format)468 FXTime FXSystem::universalTime(const FXString& string,const FXchar* format){
469   return FXSystem::universalTime(string.text(),format);
470   }
471 
472 /*******************************************************************************/
473 
474 // Format utc in nanoseconds since Unix Epoch to local date-time string using given format
localTime(FXTime utc,const FXchar * format)475 FXString FXSystem::localTime(FXTime utc,const FXchar *format){
476   FXTime zoneoffset=FXSystem::localTimeZoneOffset()+FXSystem::daylightSavingsOffset()*FXSystem::daylightSavingsActive(utc);
477   FXSystem::Time st={0,0,0,0,0,0,0,0,0,(FXint)(zoneoffset/seconds)};
478   FXSystem::systemTimeFromTime(st,utc-zoneoffset);
479   return FXSystem::systemTimeFormat(st,format);
480   }
481 
482 
483 // Parse local date-time string to UTC nanoseconds since Unix Epoch using given format
484 // Assume string is time in local time, unless a time zone offset was parsed; in that
485 // case, adjust the time according to the zone offset.
localTime(const FXchar * string,const FXchar * format)486 FXTime FXSystem::localTime(const FXchar* string,const FXchar* format){
487   FXSystem::Time st={0,0,0,0,0,0,0,0,0,0};
488   if(FXSystem::systemTimeParse(st,string,format)){
489     //FXTime zoneoffset=FXSystem::localTimeZoneOffset()+FXSystem::daylightSavingsOffset()*FXSystem::daylightSavingsActive(FXThread::time());
490     return FXSystem::timeFromSystemTime(st)+st.offset*seconds;
491     }
492   return forever;
493   }
494 
495 
496 // Parse local date-time string to UTC nanoseconds since Unix Epoch using given format
localTime(const FXString & string,const FXchar * format)497 FXTime FXSystem::localTime(const FXString& string,const FXchar* format){
498   return FXSystem::localTime(string.text(),format);
499   }
500 
501 }
502 
503