Time Shield Library 2.0.0
C++ library for working with time
Loading...
Searching...
No Matches
time_zone_conversions.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: MIT
2#pragma once
3#ifndef TIME_SHIELD_HEADER_TIMEZONE_TIME_ZONE_CONVERSIONS_HPP_INCLUDED
4#define TIME_SHIELD_HEADER_TIMEZONE_TIME_ZONE_CONVERSIONS_HPP_INCLUDED
5
9
10#include <time_shield/core.hpp>
12
13namespace time_shield {
14
17
18 ts_t zone_to_gmt(ts_t local, TimeZone zone);
19 ts_t gmt_to_zone(ts_t gmt, TimeZone zone);
20 ts_ms_t zone_to_gmt_ms(ts_ms_t local_ms, TimeZone zone);
22
24 enum class LocalTimeStatus {
25 valid,
27 ambiguous,
29 };
30
37
44
51
52 namespace detail {
53
56 int max_days = num_days_in_month(dt.year, dt.mon);
57 const int OLD_START_SUMMER_HOUR = 2;
58 const int OLD_STOP_SUMMER_HOUR = 3;
59 const int NEW_SUMMER_HOUR = 1;
60
61 if(dt.year < 2002) {
62 if(dt.mon > MAR && dt.mon < OCT) {
63 return cet - SEC_PER_HOUR * 2;
64 } else if(dt.mon == MAR) {
65 for(int d = max_days; d >= dt.day; --d) {
66 if(day_of_week_date(dt.year, MAR, d) == SUN) {
67 if(d == dt.day) {
68 if(dt.hour >= OLD_START_SUMMER_HOUR) {
69 return cet - SEC_PER_HOUR * 2;
70 }
71 return cet - SEC_PER_HOUR;
72 }
73 return cet - SEC_PER_HOUR;
74 }
75 }
76 return cet - SEC_PER_HOUR * 2;
77 } else if(dt.mon == OCT) {
78 for(int d = max_days; d >= dt.day; --d) {
79 if(day_of_week_date(dt.year, OCT, d) == SUN) {
80 if(d == dt.day) {
81 if(dt.hour >= OLD_STOP_SUMMER_HOUR) {
82 return cet - SEC_PER_HOUR;
83 }
84 return cet - SEC_PER_HOUR;
85 }
86 return cet - SEC_PER_HOUR * 2;
87 }
88 }
89 return cet - SEC_PER_HOUR;
90 }
91 return cet - SEC_PER_HOUR;
92 }
93
94 if(dt.mon > MAR && dt.mon < OCT) {
95 return cet - SEC_PER_HOUR * 2;
96 }
97 if(dt.mon == MAR) {
98 for(int d = max_days; d >= dt.day; --d) {
99 if(day_of_week_date(dt.year, MAR, d) == SUN) {
100 if(d == dt.day) {
101 if(dt.hour >= (NEW_SUMMER_HOUR + 2)) {
102 return cet - SEC_PER_HOUR * 2;
103 }
104 return cet - SEC_PER_HOUR;
105 }
106 return cet - SEC_PER_HOUR;
107 }
108 }
109 return cet - SEC_PER_HOUR * 2;
110 }
111 if(dt.mon == OCT) {
112 for(int d = max_days; d >= dt.day; --d) {
113 if(day_of_week_date(dt.year, OCT, d) == SUN) {
114 if(d == dt.day) {
115 if(dt.hour >= (NEW_SUMMER_HOUR + 1)) {
116 return cet - SEC_PER_HOUR;
117 }
118 return cet - SEC_PER_HOUR * 2;
119 }
120 return cet - SEC_PER_HOUR * 2;
121 }
122 }
123 }
124 return cet - SEC_PER_HOUR;
125 }
126
129 const int SWITCH_HOUR = 1;
130
131 if(dt.mon > MAR && dt.mon < OCT) {
132 return gmt + SEC_PER_HOUR * 2;
133 }
134 if(dt.mon == MAR) {
135 int last = last_sunday_month_day(dt.year, MAR);
136 if(dt.day > last) {
137 return gmt + SEC_PER_HOUR * 2;
138 }
139 if(dt.day < last) {
140 return gmt + SEC_PER_HOUR;
141 }
142 if(dt.hour >= SWITCH_HOUR) {
143 return gmt + SEC_PER_HOUR * 2;
144 }
145 return gmt + SEC_PER_HOUR;
146 }
147 if(dt.mon == OCT) {
148 int last = last_sunday_month_day(dt.year, OCT);
149 if(dt.day > last) {
150 return gmt + SEC_PER_HOUR;
151 }
152 if(dt.day < last) {
153 return gmt + SEC_PER_HOUR * 2;
154 }
155 if(dt.hour >= SWITCH_HOUR) {
156 return gmt + SEC_PER_HOUR;
157 }
158 return gmt + SEC_PER_HOUR * 2;
159 }
160 return gmt + SEC_PER_HOUR;
161 }
162
163 inline ts_t european_local_to_gmt(ts_t local, int standard_offset_hours) {
164 return cet_to_gmt_impl(local - SEC_PER_HOUR * (standard_offset_hours - 1));
165 }
166
167 inline ts_t gmt_to_european_local(ts_t gmt, int standard_offset_hours) {
168 return gmt_to_cet_impl(gmt) + SEC_PER_HOUR * (standard_offset_hours - 1);
169 }
170
172 const int SWITCH_HOUR = 2;
173 int start_day = 0;
174 int end_day = 0;
175 int start_month = 0;
176 int end_month = 0;
177
178 if(dt.year >= 2007) {
179 start_month = MAR;
180 end_month = NOV;
181 int first_sunday_march = static_cast<int>(
183 start_day = first_sunday_march + 7;
184 end_day = static_cast<int>(
186 } else {
187 start_month = APR;
188 end_month = OCT;
189 start_day = static_cast<int>(
191 end_day = last_sunday_month_day(dt.year, OCT);
192 }
193
194 if(dt.mon > start_month && dt.mon < end_month) {
195 return true;
196 }
197 if(dt.mon < start_month || dt.mon > end_month) {
198 return false;
199 }
200 if(dt.mon == start_month) {
201 if(dt.day > start_day) {
202 return true;
203 }
204 if(dt.day < start_day) {
205 return false;
206 }
207 return dt.hour >= SWITCH_HOUR;
208 }
209 if(dt.mon == end_month) {
210 if(dt.day < end_day) {
211 return true;
212 }
213 if(dt.day > end_day) {
214 return false;
215 }
216 return dt.hour < SWITCH_HOUR;
217 }
218 return false;
219 }
220
221 inline bool fixed_zone_offset(TimeZone zone, tz_t& utc_offset) {
222 switch(zone) {
223 case GMT:
224 case UTC:
225 case WET:
226 utc_offset = 0;
227 return true;
228 case WEST:
229 utc_offset = static_cast<tz_t>(SEC_PER_HOUR);
230 return true;
231 case CET:
232 utc_offset = static_cast<tz_t>(SEC_PER_HOUR);
233 return true;
234 case CEST:
235 utc_offset = static_cast<tz_t>(SEC_PER_HOUR * 2);
236 return true;
237 case EET:
238 utc_offset = static_cast<tz_t>(SEC_PER_HOUR * 2);
239 return true;
240 case EEST:
241 utc_offset = static_cast<tz_t>(SEC_PER_HOUR * 3);
242 return true;
243 case IST:
244 utc_offset = static_cast<tz_t>(SEC_PER_HOUR * 5 + SEC_PER_MIN * 30);
245 return true;
246 case MYT:
247 case WITA:
248 case SGT:
249 case PHT:
250 case HKT:
251 utc_offset = static_cast<tz_t>(SEC_PER_HOUR * 8);
252 return true;
253 case WIB:
254 case ICT:
255 utc_offset = static_cast<tz_t>(SEC_PER_HOUR * 7);
256 return true;
257 case WIT:
258 case JST:
259 case KST:
260 utc_offset = static_cast<tz_t>(SEC_PER_HOUR * 9);
261 return true;
262 case KZT:
263 utc_offset = static_cast<tz_t>(SEC_PER_HOUR * 5);
264 return true;
265 case TRT:
266 case BYT:
267 utc_offset = static_cast<tz_t>(SEC_PER_HOUR * 3);
268 return true;
269 case GST:
270 utc_offset = static_cast<tz_t>(SEC_PER_HOUR * 4);
271 return true;
272 default:
273 utc_offset = 0;
274 return false;
275 }
276 }
277
279 if(local_ms == ERROR_TIMESTAMP) {
280 return ERROR_TIMESTAMP;
281 }
282 const ts_t local_sec = ms_to_sec<ts_t>(local_ms);
283 const ts_ms_t remainder_ms = local_ms - sec_to_ms<ts_ms_t>(local_sec);
284 const ts_t gmt_sec = zone_to_gmt(local_sec, zone);
285 if(gmt_sec == ERROR_TIMESTAMP) {
286 return ERROR_TIMESTAMP;
287 }
288 return sec_to_ms<ts_ms_t>(gmt_sec) + remainder_ms;
289 }
290
292 if(gmt_ms == ERROR_TIMESTAMP) {
293 return ERROR_TIMESTAMP;
294 }
295 const ts_t gmt_sec = ms_to_sec<ts_t>(gmt_ms);
296 const ts_ms_t remainder_ms = gmt_ms - sec_to_ms<ts_ms_t>(gmt_sec);
297 const ts_t local_sec = gmt_to_zone(gmt_sec, zone);
298 if(local_sec == ERROR_TIMESTAMP) {
299 return ERROR_TIMESTAMP;
300 }
301 return sec_to_ms<ts_ms_t>(local_sec) + remainder_ms;
302 }
303
304 } // namespace detail
305
309 inline ts_t cet_to_gmt(ts_t cet) {
310 return detail::cet_to_gmt_impl(cet);
311 }
312
316 inline ts_t eet_to_gmt(ts_t eet) {
317 return detail::european_local_to_gmt(eet, 2);
318 }
319
325 }
326
330 inline ts_t et_to_gmt(ts_t et) {
332 bool is_dst = detail::is_us_eastern_dst_local(dt);
333 return et + SEC_PER_HOUR * (is_dst ? 4 : 5);
334 }
335
339 inline ts_t gmt_to_et(ts_t gmt) {
340 ts_t et_standard = gmt - SEC_PER_HOUR * 5;
341 DateTimeStruct dt_local = to_date_time(et_standard);
342 bool is_dst = detail::is_us_eastern_dst_local(dt_local);
343 return gmt - SEC_PER_HOUR * (is_dst ? 4 : 5);
344 }
345
349 inline ts_t ny_to_gmt(ts_t ny) {
350 return et_to_gmt(ny);
351 }
352
356 inline ts_t gmt_to_ny(ts_t gmt) {
357 return gmt_to_et(gmt);
358 }
359
363 inline ts_t ct_to_gmt(ts_t ct) {
364 return et_to_gmt(ct + SEC_PER_HOUR);
365 }
366
370 inline ts_t gmt_to_ct(ts_t gmt) {
371 return gmt_to_et(gmt) - SEC_PER_HOUR;
372 }
373
377 inline ts_t gmt_to_cet(ts_t gmt) {
378 return detail::gmt_to_cet_impl(gmt);
379 }
380
384 inline ts_t gmt_to_eet(ts_t gmt) {
385 return detail::gmt_to_european_local(gmt, 2);
386 }
387
392 inline ts_t zone_to_gmt(ts_t local, TimeZone zone) {
393 if(local == ERROR_TIMESTAMP) {
394 return ERROR_TIMESTAMP;
395 }
396
397 tz_t utc_offset = 0;
398 switch(zone) {
399 case GMT:
400 case UTC:
401 return local;
402 case WET:
403 return detail::european_local_to_gmt(local, 0);
404 case CET:
405 return cet_to_gmt(local);
406 case EET:
407 return eet_to_gmt(local);
408 case WEST:
409 case CEST:
410 case EEST:
411 detail::fixed_zone_offset(zone, utc_offset);
412 return to_utc(local, utc_offset);
413 case ET:
414 return et_to_gmt(local);
415 case CT:
416 return ct_to_gmt(local);
417 case UNKNOWN:
418 return ERROR_TIMESTAMP;
419 default:
420 if(detail::fixed_zone_offset(zone, utc_offset)) {
421 return to_utc(local, utc_offset);
422 }
423 return ERROR_TIMESTAMP;
424 }
425 }
426
431 inline ts_t gmt_to_zone(ts_t gmt, TimeZone zone) {
432 if(gmt == ERROR_TIMESTAMP) {
433 return ERROR_TIMESTAMP;
434 }
435
436 tz_t utc_offset = 0;
437 switch(zone) {
438 case GMT:
439 case UTC:
440 return gmt;
441 case WET:
442 return detail::gmt_to_european_local(gmt, 0);
443 case CET:
444 return gmt_to_cet(gmt);
445 case EET:
446 return gmt_to_eet(gmt);
447 case WEST:
448 case CEST:
449 case EEST:
450 detail::fixed_zone_offset(zone, utc_offset);
451 return to_local(gmt, utc_offset);
452 case ET:
453 return gmt_to_et(gmt);
454 case CT:
455 return gmt_to_ct(gmt);
456 case UNKNOWN:
457 return ERROR_TIMESTAMP;
458 default:
459 if(detail::fixed_zone_offset(zone, utc_offset)) {
460 return to_local(gmt, utc_offset);
461 }
462 return ERROR_TIMESTAMP;
463 }
464 }
465
471 inline ts_t convert_time_zone(ts_t local, TimeZone from, TimeZone to) {
472 ts_t gmt = zone_to_gmt(local, from);
473 return gmt == ERROR_TIMESTAMP ? ERROR_TIMESTAMP
474 : gmt_to_zone(gmt, to);
475 }
476
481 inline ts_ms_t zone_to_gmt_ms(ts_ms_t local_ms, TimeZone zone) {
482 if(local_ms == ERROR_TIMESTAMP) {
483 return ERROR_TIMESTAMP;
484 }
485
486 tz_t utc_offset = 0;
487 switch(zone) {
488 case GMT:
489 case UTC:
490 return local_ms;
491 case WET:
492 case CET:
493 case EET:
494 case ET:
495 case CT:
496 return detail::zone_to_gmt_ms_by_seconds(local_ms, zone);
497 case WEST:
498 case CEST:
499 case EEST:
500 detail::fixed_zone_offset(zone, utc_offset);
501 return to_utc_ms(local_ms, utc_offset);
502 case UNKNOWN:
503 return ERROR_TIMESTAMP;
504 default:
505 if(detail::fixed_zone_offset(zone, utc_offset)) {
506 return to_utc_ms(local_ms, utc_offset);
507 }
508 return ERROR_TIMESTAMP;
509 }
510 }
511
516 inline ts_ms_t gmt_to_zone_ms(ts_ms_t gmt_ms, TimeZone zone) {
517 if(gmt_ms == ERROR_TIMESTAMP) {
518 return ERROR_TIMESTAMP;
519 }
520
521 tz_t utc_offset = 0;
522 switch(zone) {
523 case GMT:
524 case UTC:
525 return gmt_ms;
526 case WET:
527 case CET:
528 case EET:
529 case ET:
530 case CT:
531 return detail::gmt_to_zone_ms_by_seconds(gmt_ms, zone);
532 case WEST:
533 case CEST:
534 case EEST:
535 detail::fixed_zone_offset(zone, utc_offset);
536 return to_local_ms(gmt_ms, utc_offset);
537 case UNKNOWN:
538 return ERROR_TIMESTAMP;
539 default:
540 if(detail::fixed_zone_offset(zone, utc_offset)) {
541 return to_local_ms(gmt_ms, utc_offset);
542 }
543 return ERROR_TIMESTAMP;
544 }
545 }
546
553 ts_ms_t gmt_ms = zone_to_gmt_ms(local_ms, from);
554 return gmt_ms == ERROR_TIMESTAMP ? ERROR_TIMESTAMP
555 : gmt_to_zone_ms(gmt_ms, to);
556 }
557
558 namespace detail {
559
561 LocalTimeStatus status,
562 ts_ms_t first_utc_ms = ERROR_TIMESTAMP,
563 ts_ms_t second_utc_ms = ERROR_TIMESTAMP) {
564 LocalTimeResolution result = {status, first_utc_ms, second_utc_ms};
565 return result;
566 }
567
569 tz_t& first_offset,
570 tz_t& second_offset) {
571 switch(zone) {
572 case WET:
573 first_offset = 0;
574 second_offset = static_cast<tz_t>(SEC_PER_HOUR);
575 return true;
576 case CET:
577 first_offset = static_cast<tz_t>(SEC_PER_HOUR);
578 second_offset = static_cast<tz_t>(SEC_PER_HOUR * 2);
579 return true;
580 case EET:
581 first_offset = static_cast<tz_t>(SEC_PER_HOUR * 2);
582 second_offset = static_cast<tz_t>(SEC_PER_HOUR * 3);
583 return true;
584 case ET:
585 first_offset = static_cast<tz_t>(-SEC_PER_HOUR * 5);
586 second_offset = static_cast<tz_t>(-SEC_PER_HOUR * 4);
587 return true;
588 case CT:
589 first_offset = static_cast<tz_t>(-SEC_PER_HOUR * 6);
590 second_offset = static_cast<tz_t>(-SEC_PER_HOUR * 5);
591 return true;
592 default:
593 first_offset = 0;
594 second_offset = 0;
595 return false;
596 }
597 }
598
600 switch(zone) {
601 case WET:
602 return 0;
603 case CET:
604 return static_cast<tz_t>(SEC_PER_HOUR);
605 case EET:
606 return static_cast<tz_t>(SEC_PER_HOUR * 2);
607 case ET:
608 return static_cast<tz_t>(-SEC_PER_HOUR * 5);
609 case CT:
610 return static_cast<tz_t>(-SEC_PER_HOUR * 6);
611 default:
612 return 0;
613 }
614 }
615
617 return zone == WET || zone == CET || zone == EET;
618 }
619
620 inline bool is_us_dynamic_zone(TimeZone zone) {
621 return zone == ET || zone == CT;
622 }
623
624 inline bool european_dst_at_utc_ms(ts_ms_t utc_ms) {
625 const DateTimeStruct dt = to_date_time(ms_to_sec<ts_t>(utc_ms));
626 const int start_day = last_sunday_month_day(dt.year, MAR);
627 const int end_day = last_sunday_month_day(dt.year, OCT);
628 const ts_ms_t start_ms =
629 to_timestamp_ms(dt.year, int(MAR), start_day, 1, 0, 0);
630 const ts_ms_t end_ms =
631 to_timestamp_ms(dt.year, int(OCT), end_day, 1, 0, 0);
632 return utc_ms >= start_ms && utc_ms < end_ms;
633 }
634
635 inline int first_sunday_month_day(int year, int month) {
636 return static_cast<int>(
637 1 + (DAYS_PER_WEEK - day_of_week_date(year, month, 1)) %
639 }
640
641 inline bool us_dst_at_utc_ms(TimeZone zone, ts_ms_t utc_ms) {
642 const DateTimeStruct dt = to_date_time(ms_to_sec<ts_t>(utc_ms));
643 const int year = static_cast<int>(dt.year);
644 int start_month = MAR;
645 int end_month = NOV;
646 int start_day = first_sunday_month_day(year, MAR) + 7;
647 int end_day = first_sunday_month_day(year, NOV);
648
649 if(dt.year < 2007) {
650 start_month = APR;
651 end_month = OCT;
652 start_day = first_sunday_month_day(year, APR);
653 end_day = last_sunday_month_day(year, OCT);
654 }
655
656 const tz_t standard_offset = dynamic_zone_standard_offset(zone);
657 const tz_t daylight_offset =
658 static_cast<tz_t>(standard_offset + SEC_PER_HOUR);
659 const ts_ms_t start_local =
660 to_timestamp_ms(dt.year, start_month, start_day, 2, 0, 0);
661 const ts_ms_t end_local =
662 to_timestamp_ms(dt.year, end_month, end_day, 2, 0, 0);
663 const ts_ms_t start_utc = to_utc_ms(start_local, standard_offset);
664 const ts_ms_t end_utc = to_utc_ms(end_local, daylight_offset);
665 return utc_ms >= start_utc && utc_ms < end_utc;
666 }
667
669 ts_ms_t utc_ms,
670 tz_t offset) {
671 if(is_european_dynamic_zone(zone)) {
672 const tz_t standard_offset = dynamic_zone_standard_offset(zone);
673 const tz_t expected =
674 static_cast<tz_t>(standard_offset +
677 : 0));
678 return offset == expected;
679 }
680
681 if(is_us_dynamic_zone(zone)) {
682 const tz_t standard_offset = dynamic_zone_standard_offset(zone);
683 const tz_t expected =
684 static_cast<tz_t>(standard_offset +
685 (us_dst_at_utc_ms(zone, utc_ms)
687 : 0));
688 return offset == expected;
689 }
690
691 return false;
692 }
693
695 ts_ms_t local_ms,
696 TimeZone zone,
697 tz_t offset) {
698 const ts_ms_t candidate = to_utc_ms(local_ms, offset);
699 if(candidate == ERROR_TIMESTAMP ||
700 to_local_ms(candidate, offset) != local_ms ||
701 !dynamic_offset_applies_at_utc_ms(zone, candidate, offset) ||
702 result.first_utc_ms == candidate ||
703 result.second_utc_ms == candidate) {
704 return;
705 }
706
707 if(result.first_utc_ms == ERROR_TIMESTAMP) {
708 result.first_utc_ms = candidate;
709 return;
710 }
711
712 if(result.second_utc_ms == ERROR_TIMESTAMP) {
713 result.second_utc_ms = candidate;
714 }
715 }
716
718 ts_ms_t local_ms,
719 TimeZone zone,
720 tz_t first_offset,
721 tz_t second_offset) {
722 LocalTimeResolution result =
724
725 add_local_time_candidate(result, local_ms, zone, first_offset);
726 add_local_time_candidate(result, local_ms, zone, second_offset);
727
728 if(result.first_utc_ms == ERROR_TIMESTAMP) {
729 return result;
730 }
731
732 if(result.second_utc_ms == ERROR_TIMESTAMP) {
734 return result;
735 }
736
737 if(result.second_utc_ms < result.first_utc_ms) {
738 const ts_ms_t tmp = result.first_utc_ms;
739 result.first_utc_ms = result.second_utc_ms;
740 result.second_utc_ms = tmp;
741 }
742
744 return result;
745 }
746
748 return status == LocalTimeStatus::valid ||
750 }
751
753 const LocalTimeResolution& resolution,
754 AmbiguousTimePolicy ambiguous_policy) {
755 if(resolution.status == LocalTimeStatus::valid) {
756 return resolution.first_utc_ms;
757 }
758
759 if(resolution.status == LocalTimeStatus::ambiguous) {
760 switch(ambiguous_policy) {
762 return resolution.first_utc_ms;
764 return resolution.second_utc_ms;
766 default:
767 return ERROR_TIMESTAMP;
768 }
769 }
770
771 return ERROR_TIMESTAMP;
772 }
773
774 } // namespace detail
775
781 inline bool zone_offset_at_utc_ms(ts_ms_t utc_ms,
782 TimeZone zone,
783 tz_t& out) noexcept {
784 if(utc_ms == ERROR_TIMESTAMP || zone == UNKNOWN) {
785 return false;
786 }
787
788 if(zone == GMT || zone == UTC) {
789 out = 0;
790 return true;
791 }
792
794 const tz_t standard_offset = detail::dynamic_zone_standard_offset(zone);
795 out = static_cast<tz_t>(standard_offset +
798 : 0));
799 return true;
800 }
801
803 const tz_t standard_offset = detail::dynamic_zone_standard_offset(zone);
804 out = static_cast<tz_t>(standard_offset +
805 (detail::us_dst_at_utc_ms(zone, utc_ms)
807 : 0));
808 return true;
809 }
810
811 tz_t utc_offset = 0;
812 if(detail::fixed_zone_offset(zone, utc_offset)) {
813 out = utc_offset;
814 return true;
815 }
816
817 return false;
818 }
819
825 inline bool zone_offset_at_utc(ts_t utc,
826 TimeZone zone,
827 tz_t& out) noexcept {
828 return utc == ERROR_TIMESTAMP
829 ? false
830 : zone_offset_at_utc_ms(sec_to_ms<ts_ms_t>(utc), zone, out);
831 }
832
838 TimeZone zone) {
839 if(local_ms == ERROR_TIMESTAMP || zone == UNKNOWN) {
842 }
843
844 if(zone == GMT || zone == UTC) {
846 local_ms);
847 }
848
849 tz_t first_offset = 0;
850 tz_t second_offset = 0;
851 if(detail::dynamic_dst_zone_offsets(zone, first_offset, second_offset)) {
853 zone,
854 first_offset,
855 second_offset);
856 }
857
858 tz_t utc_offset = 0;
859 if(detail::fixed_zone_offset(zone, utc_offset)) {
862 to_utc_ms(local_ms, utc_offset));
863 }
864
866 }
867
877
878 namespace detail {
879
881 TimeZone zone,
882 int direction) {
883 const ts_ms_t window = static_cast<ts_ms_t>(MS_PER_DAY) * 2;
884 const bool forward = direction >= 0;
885 ts_ms_t low = forward ? local_ms : local_ms - window;
886 ts_ms_t high = forward ? local_ms + window : local_ms;
887
889 resolve_local_time_ms(forward ? high : low, zone);
891 return ERROR_TIMESTAMP;
892 }
893
894 while(high - low > 1) {
895 const ts_ms_t mid = low + (high - low) / 2;
896 const LocalTimeResolution resolution =
897 resolve_local_time_ms(mid, zone);
898 if(local_time_status_has_utc(resolution.status)) {
899 if(forward) {
900 high = mid;
901 } else {
902 low = mid;
903 }
904 } else if(forward) {
905 low = mid;
906 } else {
907 high = mid;
908 }
909 }
910
911 const LocalTimeResolution shifted =
912 resolve_local_time_ms(forward ? high : low, zone);
914 shifted,
916 }
917
918 } // namespace detail
919
922 TimeZone zone,
923 AmbiguousTimePolicy ambiguous_policy,
924 NonexistentTimePolicy nonexistent_policy) {
925 const LocalTimeResolution resolution =
926 resolve_local_time_ms(local_ms, zone);
927
928 if(resolution.status == LocalTimeStatus::nonexistent) {
929 switch(nonexistent_policy) {
932 local_ms,
933 zone,
934 1);
937 local_ms,
938 zone,
939 -1);
941 default:
942 return ERROR_TIMESTAMP;
943 }
944 }
945
946 return detail::local_time_resolution_to_utc(resolution,
947 ambiguous_policy);
948 }
949
951 inline ts_t zone_to_gmt(ts_t local,
952 TimeZone zone,
953 AmbiguousTimePolicy ambiguous_policy,
954 NonexistentTimePolicy nonexistent_policy) {
955 const ts_ms_t utc_ms = local == ERROR_TIMESTAMP
958 zone,
959 ambiguous_policy,
960 nonexistent_policy);
961 return utc_ms == ERROR_TIMESTAMP ? ERROR_TIMESTAMP
962 : ms_to_sec<ts_t>(utc_ms);
963 }
964
967 return zone_to_gmt_ms(local_ms,
968 zone,
971 }
972
974 inline ts_t zone_to_gmt_strict(ts_t local, TimeZone zone) {
975 return zone_to_gmt(local,
976 zone,
979 }
980
983 ts_ms_t local_ms,
984 TimeZone from,
985 TimeZone to,
986 AmbiguousTimePolicy ambiguous_policy,
987 NonexistentTimePolicy nonexistent_policy) {
988 const ts_ms_t gmt_ms = zone_to_gmt_ms(local_ms,
989 from,
990 ambiguous_policy,
991 nonexistent_policy);
992 return gmt_ms == ERROR_TIMESTAMP ? ERROR_TIMESTAMP
993 : gmt_to_zone_ms(gmt_ms, to);
994 }
995
998 ts_t local,
999 TimeZone from,
1000 TimeZone to,
1001 AmbiguousTimePolicy ambiguous_policy,
1002 NonexistentTimePolicy nonexistent_policy) {
1003 const ts_t gmt = zone_to_gmt(local,
1004 from,
1005 ambiguous_policy,
1006 nonexistent_policy);
1007 return gmt == ERROR_TIMESTAMP ? ERROR_TIMESTAMP
1008 : gmt_to_zone(gmt, to);
1009 }
1010
1011 inline ts_t ist_to_gmt(ts_t ist) { return zone_to_gmt(ist, IST); }
1012 inline ts_t gmt_to_ist(ts_t gmt) { return gmt_to_zone(gmt, IST); }
1013
1014 inline ts_t myt_to_gmt(ts_t myt) { return zone_to_gmt(myt, MYT); }
1015 inline ts_t gmt_to_myt(ts_t gmt) { return gmt_to_zone(gmt, MYT); }
1016
1017 inline ts_t wib_to_gmt(ts_t wib) { return zone_to_gmt(wib, WIB); }
1018 inline ts_t gmt_to_wib(ts_t gmt) { return gmt_to_zone(gmt, WIB); }
1019
1020 inline ts_t wita_to_gmt(ts_t wita) { return zone_to_gmt(wita, WITA); }
1021 inline ts_t gmt_to_wita(ts_t gmt) { return gmt_to_zone(gmt, WITA); }
1022
1023 inline ts_t wit_to_gmt(ts_t wit) { return zone_to_gmt(wit, WIT); }
1024 inline ts_t gmt_to_wit(ts_t gmt) { return gmt_to_zone(gmt, WIT); }
1025
1026 inline ts_t kzt_to_gmt(ts_t kzt) { return zone_to_gmt(kzt, KZT); }
1027 inline ts_t gmt_to_kzt(ts_t gmt) { return gmt_to_zone(gmt, KZT); }
1028
1029 inline ts_t trt_to_gmt(ts_t trt) { return zone_to_gmt(trt, TRT); }
1030 inline ts_t gmt_to_trt(ts_t gmt) { return gmt_to_zone(gmt, TRT); }
1031
1032 inline ts_t byt_to_gmt(ts_t byt) { return zone_to_gmt(byt, BYT); }
1033 inline ts_t gmt_to_byt(ts_t gmt) { return gmt_to_zone(gmt, BYT); }
1034
1035 inline ts_t sgt_to_gmt(ts_t sgt) { return zone_to_gmt(sgt, SGT); }
1036 inline ts_t gmt_to_sgt(ts_t gmt) { return gmt_to_zone(gmt, SGT); }
1037
1038 inline ts_t ict_to_gmt(ts_t ict) { return zone_to_gmt(ict, ICT); }
1039 inline ts_t gmt_to_ict(ts_t gmt) { return gmt_to_zone(gmt, ICT); }
1040
1041 inline ts_t pht_to_gmt(ts_t pht) { return zone_to_gmt(pht, PHT); }
1042 inline ts_t gmt_to_pht(ts_t gmt) { return gmt_to_zone(gmt, PHT); }
1043
1044 inline ts_t gst_to_gmt(ts_t gst) { return zone_to_gmt(gst, GST); }
1045 inline ts_t gmt_to_gst(ts_t gmt) { return gmt_to_zone(gmt, GST); }
1046
1047 inline ts_t hkt_to_gmt(ts_t hkt) { return zone_to_gmt(hkt, HKT); }
1048 inline ts_t gmt_to_hkt(ts_t gmt) { return gmt_to_zone(gmt, HKT); }
1049
1050 inline ts_t jst_to_gmt(ts_t jst) { return zone_to_gmt(jst, JST); }
1051 inline ts_t gmt_to_jst(ts_t gmt) { return gmt_to_zone(gmt, JST); }
1052
1053 inline ts_t kst_to_gmt(ts_t kst) { return zone_to_gmt(kst, KST); }
1054 inline ts_t gmt_to_kst(ts_t gmt) { return gmt_to_zone(gmt, KST); }
1055
1057 inline ts_t kyiv_to_gmt(ts_t kyiv) { return eet_to_gmt(kyiv); }
1058
1060 inline ts_t gmt_to_kyiv(ts_t gmt) { return gmt_to_eet(gmt); }
1061
1063
1064} // namespace time_shield
1065
1066#endif // TIME_SHIELD_HEADER_TIMEZONE_TIME_ZONE_CONVERSIONS_HPP_INCLUDED
constexpr int64_t ERROR_TIMESTAMP
Error timestamp value.
constexpr int64_t DAYS_PER_WEEK
Days per week.
constexpr int64_t SEC_PER_HOUR
Seconds per hour.
constexpr int64_t MS_PER_DAY
Milliseconds per day.
Definition constants.hpp:97
constexpr int64_t SEC_PER_MIN
Seconds per minute.
TIME_SHIELD_CONSTEXPR ts_t to_utc(ts_t local, tz_t utc_offset) noexcept
Convert local timestamp (seconds) to UTC using UTC offset.
TIME_SHIELD_CONSTEXPR ts_t to_local(ts_t utc, tz_t utc_offset) noexcept
Convert UTC timestamp (seconds) to local time using UTC offset.
TIME_SHIELD_CONSTEXPR ts_ms_t to_local_ms(ts_ms_t utc_ms, tz_t utc_offset) noexcept
Convert UTC timestamp (milliseconds) to local time using UTC offset.
ts_t zone_to_gmt(ts_t local, TimeZone zone)
Convert supported local civil time to GMT (UTC).
TIME_SHIELD_CONSTEXPR ts_ms_t to_utc_ms(ts_ms_t local_ms, tz_t utc_offset) noexcept
Convert local timestamp (milliseconds) to UTC using UTC offset.
TIME_SHIELD_CONSTEXPR T1 ms_to_sec(T2 ts_ms) noexcept
Converts a timestamp from milliseconds to seconds.
TIME_SHIELD_CONSTEXPR T1 day_of_week_date(T2 year, T3 month, T4 day)
Get the day of the week.
TIME_SHIELD_CONSTEXPR T year(ts_t ts=time_shield::ts())
Alias for year_of function.
TIME_SHIELD_CONSTEXPR T1 sec_to_ms(T2 ts) noexcept
Converts a timestamp from seconds to milliseconds.
@ OCT
October.
Definition enums.hpp:103
@ NOV
November.
Definition enums.hpp:104
@ MAR
March.
Definition enums.hpp:96
@ APR
April.
Definition enums.hpp:97
@ JST
Japan Standard Time.
Definition enums.hpp:200
@ GST
Gulf Standard Time.
Definition enums.hpp:198
@ PHT
Philippine Time.
Definition enums.hpp:197
@ BYT
Belarus Time.
Definition enums.hpp:194
@ KST
Korea Standard Time.
Definition enums.hpp:201
@ ET
US Eastern Time.
Definition enums.hpp:185
@ EET
Eastern European Time.
Definition enums.hpp:179
@ CEST
Central European Summer Time.
Definition enums.hpp:183
@ WIT
Eastern Indonesia Time.
Definition enums.hpp:191
@ WEST
Western European Summer Time.
Definition enums.hpp:184
@ WITA
Central Indonesia Time.
Definition enums.hpp:190
@ MYT
Malaysia Time.
Definition enums.hpp:188
@ HKT
Hong Kong Time.
Definition enums.hpp:199
@ WET
Western European Time.
Definition enums.hpp:181
@ KZT
Kazakhstan Time.
Definition enums.hpp:192
@ UNKNOWN
Unknown Time Zone.
Definition enums.hpp:202
@ TRT
Turkey Time.
Definition enums.hpp:193
@ UTC
Coordinated Universal Time.
Definition enums.hpp:178
@ ICT
Indochina Time.
Definition enums.hpp:196
@ GMT
Greenwich Mean Time.
Definition enums.hpp:177
@ SGT
Singapore Time.
Definition enums.hpp:195
@ WIB
Western Indonesia Time.
Definition enums.hpp:189
@ EEST
Eastern European Summer Time.
Definition enums.hpp:182
@ CT
US Central Time.
Definition enums.hpp:186
@ CET
Central European Time.
Definition enums.hpp:180
@ IST
India Standard Time.
Definition enums.hpp:187
@ SUN
Sunday.
Definition enums.hpp:28
TIME_SHIELD_CONSTEXPR T1 last_sunday_month_day(T2 year, T3 month)
Get the day of the last Sunday of the given month and year.
T1 to_date_time(T2 ts)
Converts a timestamp to a date-time structure.
TIME_SHIELD_CONSTEXPR T1 num_days_in_month(T2 year, T3 month) noexcept
Get the number of days in a month.
TIME_SHIELD_CONSTEXPR ts_ms_t to_timestamp_ms(T1 year, T2 month, T2 day, T2 hour=0, T2 min=0, T2 sec=0, T2 ms=0)
Converts a date-time structure to a timestamp in milliseconds.
int64_t ts_t
Unix timestamp in seconds since 1970‑01‑01T00:00:00Z.
Definition types.hpp:48
int32_t tz_t
Time zone offset in minutes from UTC (e.g., +180 = UTC+3).
Definition types.hpp:60
int64_t ts_ms_t
Unix timestamp in milliseconds since epoch.
Definition types.hpp:49
void add_local_time_candidate(LocalTimeResolution &result, ts_ms_t local_ms, TimeZone zone, tz_t offset)
bool is_us_dynamic_zone(TimeZone zone)
bool local_time_status_has_utc(LocalTimeStatus status)
bool fixed_zone_offset(TimeZone zone, tz_t &utc_offset)
tz_t dynamic_zone_standard_offset(TimeZone zone)
LocalTimeResolution resolve_with_dynamic_offsets(ts_ms_t local_ms, TimeZone zone, tz_t first_offset, tz_t second_offset)
LocalTimeResolution make_local_time_resolution(LocalTimeStatus status, ts_ms_t first_utc_ms=ERROR_TIMESTAMP, ts_ms_t second_utc_ms=ERROR_TIMESTAMP)
int first_sunday_month_day(int year, int month)
ts_t gmt_to_european_local(ts_t gmt, int standard_offset_hours)
bool is_us_eastern_dst_local(const DateTimeStruct &dt)
ts_ms_t gmt_to_zone_ms_by_seconds(ts_ms_t gmt_ms, TimeZone zone)
bool european_dst_at_utc_ms(ts_ms_t utc_ms)
bool dynamic_dst_zone_offsets(TimeZone zone, tz_t &first_offset, tz_t &second_offset)
bool is_european_dynamic_zone(TimeZone zone)
ts_ms_t local_time_resolution_to_utc(const LocalTimeResolution &resolution, AmbiguousTimePolicy ambiguous_policy)
ts_ms_t shift_nonexistent_local_time_ms(ts_ms_t local_ms, TimeZone zone, int direction)
ts_t european_local_to_gmt(ts_t local, int standard_offset_hours)
bool dynamic_offset_applies_at_utc_ms(TimeZone zone, ts_ms_t utc_ms, tz_t offset)
ts_ms_t zone_to_gmt_ms_by_seconds(ts_ms_t local_ms, TimeZone zone)
bool us_dst_at_utc_ms(TimeZone zone, ts_ms_t utc_ms)
Main namespace for the Time Shield library.
ts_t gmt_to_et(ts_t gmt)
Convert GMT (UTC) to US Eastern Time (New York, EST/EDT).
ts_t kzt_to_gmt(ts_t kzt)
ts_t eet_to_gmt(ts_t eet)
Convert Eastern European Time to Greenwich Mean Time.
ts_t gmt_to_gst(ts_t gmt)
ts_t gmt_to_hkt(ts_t gmt)
ts_t gmt_to_eet(ts_t gmt)
Convert Greenwich Mean Time to Eastern European Time.
ts_t gst_to_gmt(ts_t gst)
ts_t byt_to_gmt(ts_t byt)
ts_ms_t zone_to_gmt_ms(ts_ms_t local_ms, TimeZone zone)
Convert supported local civil time in milliseconds to GMT (UTC).
ts_t gmt_to_kyiv(ts_t gmt)
Convert GMT to Kyiv civil time using the EET/EEST rules.
ts_t gmt_to_ist(ts_t gmt)
ts_t ct_to_gmt(ts_t ct)
Convert US Central Time (America/Chicago, CST/CDT) to GMT (UTC).
ts_t gmt_to_jst(ts_t gmt)
bool is_us_eastern_dst_local(const DateTimeStruct &dt)
Check if local US Eastern time uses DST.
ts_t ict_to_gmt(ts_t ict)
ts_t gmt_to_wit(ts_t gmt)
ts_t gmt_to_kzt(ts_t gmt)
ts_ms_t zone_to_gmt_ms_strict(ts_ms_t local_ms, TimeZone zone)
Convert only unambiguous existing local time to UTC.
ts_t kyiv_to_gmt(ts_t kyiv)
Convert Kyiv civil time to GMT using the EET/EEST rules.
ts_t wita_to_gmt(ts_t wita)
ts_t cet_to_gmt(ts_t cet)
Convert Central European Time to Greenwich Mean Time.
ts_t convert_time_zone(ts_t local, TimeZone from, TimeZone to)
Convert a timestamp between two supported local civil time zones.
LocalTimeResolution resolve_local_time(ts_t local, TimeZone zone)
Resolve local civil time given in seconds.
ts_t gmt_to_byt(ts_t gmt)
ts_ms_t gmt_to_zone_ms(ts_ms_t gmt_ms, TimeZone zone)
Convert GMT (UTC) in milliseconds to a supported local civil time zone.
ts_t gmt_to_pht(ts_t gmt)
LocalTimeStatus
Classification of a local civil timestamp in a time zone.
@ ambiguous
Local time maps to more than one UTC timestamp.
@ nonexistent
Local time falls into a DST forward gap.
@ unsupported
Zone or timestamp cannot be resolved.
@ valid
Local time maps to exactly one UTC timestamp.
ts_t gmt_to_ny(ts_t gmt)
Convert GMT (UTC) to New York Time.
ts_t gmt_to_cet(ts_t gmt)
Convert Greenwich Mean Time to Central European Time.
ts_t gmt_to_sgt(ts_t gmt)
ts_t kst_to_gmt(ts_t kst)
bool zone_offset_at_utc_ms(ts_ms_t utc_ms, TimeZone zone, tz_t &out) noexcept
Resolve the effective UTC offset for a UTC millisecond instant.
ts_t gmt_to_trt(ts_t gmt)
ts_t wit_to_gmt(ts_t wit)
ts_t hkt_to_gmt(ts_t hkt)
ts_t gmt_to_ict(ts_t gmt)
bool zone_offset_at_utc(ts_t utc, TimeZone zone, tz_t &out) noexcept
Resolve the effective UTC offset for a UTC second instant.
ts_t ist_to_gmt(ts_t ist)
ts_t gmt_to_wib(ts_t gmt)
ts_t gmt_to_myt(ts_t gmt)
ts_t wib_to_gmt(ts_t wib)
ts_t trt_to_gmt(ts_t trt)
ts_t gmt_to_zone(ts_t gmt, TimeZone zone)
Convert GMT (UTC) to a supported local civil time zone.
ts_t pht_to_gmt(ts_t pht)
AmbiguousTimePolicy
Policy for ambiguous local civil timestamps.
@ second_occurrence
Use the latest UTC occurrence.
@ first_occurrence
Use the earliest UTC occurrence.
@ error
Return ERROR_TIMESTAMP.
ts_t sgt_to_gmt(ts_t sgt)
ts_t zone_to_gmt_strict(ts_t local, TimeZone zone)
Convert only unambiguous existing local time in seconds to UTC.
ts_t ny_to_gmt(ts_t ny)
Convert New York Time to GMT (UTC).
ts_t gmt_to_wita(ts_t gmt)
ts_t et_to_gmt(ts_t et)
Convert US Eastern Time (New York, EST/EDT) to GMT (UTC).
LocalTimeResolution resolve_local_time_ms(ts_ms_t local_ms, TimeZone zone)
Resolve local civil time to zero, one, or two UTC candidates.
ts_ms_t convert_time_zone_ms(ts_ms_t local_ms, TimeZone from, TimeZone to)
Convert a millisecond timestamp between two supported local civil time zones.
ts_t gmt_to_ct(ts_t gmt)
Convert GMT (UTC) to US Central Time (America/Chicago, CST/CDT).
NonexistentTimePolicy
Policy for nonexistent local civil timestamps.
@ shift_forward
Use the earliest valid local instant after the gap.
@ shift_backward
Use the latest valid local instant before the gap.
@ error
Return ERROR_TIMESTAMP.
ts_t gmt_to_kst(ts_t gmt)
ts_t jst_to_gmt(ts_t jst)
ts_t myt_to_gmt(ts_t myt)
Structure to represent date and time.
int hour
Hour component of time (0-23)
int64_t year
Year component of the date.
int day
Day component of the date (1-31).
int mon
Month component of the date (1-12).
Result of explicit local-time resolution.