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