@@ -212,23 +212,28 @@ static int64_t systemtime_to_unix_time(const SYSTEMTIME& systime)
212
212
SECS_BETWEEN_1601_1970;
213
213
}
214
214
215
+ struct TRANSITIONS_INFO {
216
+ TIME_ZONE_INFORMATION tzi;
217
+ SYSTEMTIME standard_local;
218
+ SYSTEMTIME daylight_local;
219
+ };
220
+
215
221
/* Checks whether the daylight saving time is in effect at the given time.
216
222
`tzi` could be calculated here, but is passed along to avoid recomputing
217
223
it. */
218
224
static bool is_daylight_time (
219
225
const DYNAMIC_TIME_ZONE_INFORMATION& dtzi,
220
- const TIME_ZONE_INFORMATION& tzi ,
226
+ TRANSITIONS_INFO& trans ,
221
227
const SYSTEMTIME& time)
222
228
{
223
229
// it means that daylight saving time is not supported at all
224
- if (tzi.StandardDate .wMonth == 0 ) {
230
+ if (trans. tzi .StandardDate .wMonth == 0 ) {
225
231
return false ;
226
232
}
227
233
/* translate the "date" values stored in `tzi` into real dates of
228
234
transitions to and from the daylight saving time. */
229
- SYSTEMTIME standard_local, daylight_local;
230
- get_transition_date (time.wYear , tzi.StandardDate , standard_local);
231
- get_transition_date (time.wYear , tzi.DaylightDate , daylight_local);
235
+ get_transition_date (time.wYear , trans.tzi .StandardDate , trans.standard_local );
236
+ get_transition_date (time.wYear , trans.tzi .DaylightDate , trans.daylight_local );
232
237
/* Two things happen here:
233
238
* All the relevant dates are converted to a number of ticks an some
234
239
unified scale, counted in seconds. This is done so that we are able
@@ -239,10 +244,10 @@ static bool is_daylight_time(
239
244
time, as seen by a person that is currently on the daylight saving
240
245
time. So, in order for the dates to be on the same scale, the biases
241
246
that are assumed to be currently active are negated. */
242
- int64_t standard = systemtime_to_ticks (standard_local) /
243
- WINDOWS_TICKS_PER_SEC + (tzi.Bias + tzi.DaylightBias ) * 60 ;
244
- int64_t daylight = systemtime_to_ticks (daylight_local) /
245
- WINDOWS_TICKS_PER_SEC + (tzi.Bias + tzi.StandardBias ) * 60 ;
247
+ int64_t standard = systemtime_to_ticks (trans. standard_local ) /
248
+ WINDOWS_TICKS_PER_SEC + (trans. tzi .Bias + trans. tzi .DaylightBias ) * 60 ;
249
+ int64_t daylight = systemtime_to_ticks (trans. daylight_local ) /
250
+ WINDOWS_TICKS_PER_SEC + (trans. tzi .Bias + trans. tzi .StandardBias ) * 60 ;
246
251
int64_t time_secs = systemtime_to_ticks (time) /
247
252
WINDOWS_TICKS_PER_SEC;
248
253
/* Maybe `else` is never hit, but I've seen no indication of that assumption
@@ -258,18 +263,18 @@ static bool is_daylight_time(
258
263
259
264
// Get the UTC offset for a given timezone at a given time.
260
265
static int offset_at_systime (DYNAMIC_TIME_ZONE_INFORMATION& dtzi,
266
+ TRANSITIONS_INFO& ts,
261
267
const SYSTEMTIME& systime)
262
268
{
263
- TIME_ZONE_INFORMATION tzi{};
264
- bool result = GetTimeZoneInformationForYear (systime.wYear , &dtzi, &tzi);
269
+ bool result = GetTimeZoneInformationForYear (systime.wYear , &dtzi, &ts.tzi );
265
270
if (!result) {
266
271
return INT_MAX;
267
272
}
268
- auto bias = tzi.Bias ;
269
- if (is_daylight_time (dtzi, tzi , systime)) {
270
- bias += tzi.DaylightBias ;
273
+ auto bias = ts. tzi .Bias ;
274
+ if (is_daylight_time (dtzi, ts , systime)) {
275
+ bias += ts. tzi .DaylightBias ;
271
276
} else {
272
- bias += tzi.StandardBias ;
277
+ bias += ts. tzi .StandardBias ;
273
278
}
274
279
return -bias * 60 ;
275
280
}
@@ -330,7 +335,8 @@ int offset_at_instant(TZID zone_id, int64_t epoch_sec)
330
335
}
331
336
SYSTEMTIME systime;
332
337
unix_time_to_systemtime (epoch_sec, systime);
333
- return offset_at_systime (dtzi, systime);
338
+ TRANSITIONS_INFO ts{};
339
+ return offset_at_systime (dtzi, ts, systime);
334
340
}
335
341
336
342
TZID timezone_by_name (const char *zone_name)
@@ -344,7 +350,8 @@ TZID timezone_by_name(const char *zone_name)
344
350
}
345
351
}
346
352
347
- int offset_at_datetime (TZID zone_id, int64_t epoch_sec, int *offset)
353
+ static int offset_at_datetime_impl (TZID zone_id, int64_t epoch_sec, int *offset,
354
+ GAP_HANDLING gap_handling)
348
355
{
349
356
DYNAMIC_TIME_ZONE_INFORMATION dtzi{};
350
357
bool result = time_zone_by_id (zone_id, dtzi);
@@ -354,7 +361,8 @@ int offset_at_datetime(TZID zone_id, int64_t epoch_sec, int *offset)
354
361
SYSTEMTIME localtime, utctime, adjusted;
355
362
unix_time_to_systemtime (epoch_sec, localtime);
356
363
TzSpecificLocalTimeToSystemTimeEx (&dtzi, &localtime, &utctime);
357
- *offset = offset_at_systime (dtzi, utctime);
364
+ TRANSITIONS_INFO trans{};
365
+ *offset = offset_at_systime (dtzi, trans, utctime);
358
366
SystemTimeToTzSpecificLocalTimeEx (&dtzi, &utctime, &adjusted);
359
367
/* We don't use `epoch_sec` instead of `systemtime_to_unix_time(localtime)
360
368
because `unix_time_to_systemtime(epoch_sec, localtime)` above could
@@ -364,8 +372,55 @@ int offset_at_datetime(TZID zone_id, int64_t epoch_sec, int *offset)
364
372
result to return: timezone database information outside of [1970; current
365
373
time) is not accurate anyway, and WinAPI supports dates in years [1601;
366
374
30827], which should be enough for all practical purposes. */
367
- return (int )(systemtime_to_unix_time (adjusted) -
375
+ const auto transition_duration = (int )(systemtime_to_unix_time (adjusted) -
368
376
systemtime_to_unix_time (localtime));
377
+ if (transition_duration == 0 )
378
+ return 0 ;
379
+ switch (gap_handling) {
380
+ case GAP_HANDLING_MOVE_FORWARD:
381
+ return transition_duration;
382
+ case GAP_HANDLING_NEXT_CORRECT:
383
+ /* Let x, y in {daylight, standard}
384
+ If a gap happened, then
385
+ xEnd + xOffset < utctime < yBegin + yOffset
386
+ What we need to return is
387
+ yBegin + yOffset - epoch_sec
388
+ To learn whether we crossed from daylight to standard or vice versa:
389
+ xEnd = yBegin - epsilon => yOffset + epsilon > xOffset
390
+ Thus, we crossed from the lower offset to the bigger one. So,
391
+ return (daylight.offset > standard.offset ?
392
+ daylight.begin + daylight.offset :
393
+ standard.begin + standard.offset) - epoch_sec */
394
+ if (trans.tzi .DaylightBias < trans.tzi .StandardBias ) {
395
+ return systemtime_to_unix_time (trans.daylight_local )
396
+ + trans.tzi .StandardBias - trans.tzi .DaylightBias
397
+ - epoch_sec + 1 ;
398
+ } else {
399
+ return systemtime_to_unix_time (trans.standard_local )
400
+ + trans.tzi .DaylightBias - trans.tzi .StandardBias
401
+ - epoch_sec + 1 ;
402
+ }
403
+ default :
404
+ // impossible
405
+ *offset = INT_MAX;
406
+ return 0 ;
407
+ }
408
+ }
409
+
410
+ int offset_at_datetime (TZID zone_id, int64_t epoch_sec, int *offset)
411
+ {
412
+ return offset_at_datetime_impl (zone_id, epoch_sec, offset,
413
+ GAP_HANDLING_MOVE_FORWARD);
414
+ }
415
+
416
+ int64_t at_start_of_day (TZID zone_id, int64_t epoch_sec)
417
+ {
418
+ int offset = 0 ;
419
+ int trans = offset_at_datetime_impl (zone_id, epoch_sec, &offset,
420
+ GAP_HANDLING_NEXT_CORRECT);
421
+ if (offset == INT_MAX)
422
+ return LONG_MAX;
423
+ return epoch_sec - offset + trans;
369
424
}
370
425
371
426
}
0 commit comments