Time Shield Library
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_TIME_ZONE_CONVERSIONS_HPP_INCLUDED
4#define _TIME_SHIELD_TIME_ZONE_CONVERSIONS_HPP_INCLUDED
5
9
10#include "date_time_struct.hpp"
11#include "time_conversions.hpp"
12#include "time_zone_offset.hpp"
14
15namespace time_shield {
16
19
20 ts_t zone_to_gmt(ts_t local, TimeZone zone);
21 ts_t gmt_to_zone(ts_t gmt, TimeZone zone);
22 ts_ms_t zone_to_gmt_ms(ts_ms_t local_ms, TimeZone zone);
24
32
39
46
53
54 namespace detail {
55
58 int max_days = num_days_in_month(dt.year, dt.mon);
59 const int OLD_START_SUMMER_HOUR = 2;
60 const int OLD_STOP_SUMMER_HOUR = 3;
61 const int NEW_SUMMER_HOUR = 1;
62
63 if(dt.year < 2002) {
64 if(dt.mon > MAR && dt.mon < OCT) {
65 return cet - SEC_PER_HOUR * 2;
66 } else if(dt.mon == MAR) {
67 for(int d = max_days; d >= dt.day; --d) {
68 if(day_of_week_date(dt.year, MAR, d) == SUN) {
69 if(d == dt.day) {
70 if(dt.hour >= OLD_START_SUMMER_HOUR) {
71 return cet - SEC_PER_HOUR * 2;
72 }
73 return cet - SEC_PER_HOUR;
74 }
75 return cet - SEC_PER_HOUR;
76 }
77 }
78 return cet - SEC_PER_HOUR * 2;
79 } else if(dt.mon == OCT) {
80 for(int d = max_days; d >= dt.day; --d) {
81 if(day_of_week_date(dt.year, OCT, d) == SUN) {
82 if(d == dt.day) {
83 if(dt.hour >= OLD_STOP_SUMMER_HOUR) {
84 return cet - SEC_PER_HOUR;
85 }
86 return cet - SEC_PER_HOUR;
87 }
88 return cet - SEC_PER_HOUR * 2;
89 }
90 }
91 return cet - SEC_PER_HOUR;
92 }
93 return cet - SEC_PER_HOUR;
94 }
95
96 if(dt.mon > MAR && dt.mon < OCT) {
97 return cet - SEC_PER_HOUR * 2;
98 }
99 if(dt.mon == MAR) {
100 for(int d = max_days; d >= dt.day; --d) {
101 if(day_of_week_date(dt.year, MAR, d) == SUN) {
102 if(d == dt.day) {
103 if(dt.hour >= (NEW_SUMMER_HOUR + 2)) {
104 return cet - SEC_PER_HOUR * 2;
105 }
106 return cet - SEC_PER_HOUR;
107 }
108 return cet - SEC_PER_HOUR;
109 }
110 }
111 return cet - SEC_PER_HOUR * 2;
112 }
113 if(dt.mon == OCT) {
114 for(int d = max_days; d >= dt.day; --d) {
115 if(day_of_week_date(dt.year, OCT, d) == SUN) {
116 if(d == dt.day) {
117 if(dt.hour >= (NEW_SUMMER_HOUR + 1)) {
118 return cet - SEC_PER_HOUR;
119 }
120 return cet - SEC_PER_HOUR * 2;
121 }
122 return cet - SEC_PER_HOUR * 2;
123 }
124 }
125 }
126 return cet - SEC_PER_HOUR;
127 }
128
131 const int SWITCH_HOUR = 1;
132
133 if(dt.mon > MAR && dt.mon < OCT) {
134 return gmt + SEC_PER_HOUR * 2;
135 }
136 if(dt.mon == MAR) {
137 int last = last_sunday_month_day(dt.year, MAR);
138 if(dt.day > last) {
139 return gmt + SEC_PER_HOUR * 2;
140 }
141 if(dt.day < last) {
142 return gmt + SEC_PER_HOUR;
143 }
144 if(dt.hour >= SWITCH_HOUR) {
145 return gmt + SEC_PER_HOUR * 2;
146 }
147 return gmt + SEC_PER_HOUR;
148 }
149 if(dt.mon == OCT) {
150 int last = last_sunday_month_day(dt.year, OCT);
151 if(dt.day > last) {
152 return gmt + SEC_PER_HOUR;
153 }
154 if(dt.day < last) {
155 return gmt + SEC_PER_HOUR * 2;
156 }
157 if(dt.hour >= SWITCH_HOUR) {
158 return gmt + SEC_PER_HOUR;
159 }
160 return gmt + SEC_PER_HOUR * 2;
161 }
162 return gmt + SEC_PER_HOUR;
163 }
164
165 inline ts_t european_local_to_gmt(ts_t local, int standard_offset_hours) {
166 return cet_to_gmt_impl(local - SEC_PER_HOUR * (standard_offset_hours - 1));
167 }
168
169 inline ts_t gmt_to_european_local(ts_t gmt, int standard_offset_hours) {
170 return gmt_to_cet_impl(gmt) + SEC_PER_HOUR * (standard_offset_hours - 1);
171 }
172
174 const int SWITCH_HOUR = 2;
175 int start_day = 0;
176 int end_day = 0;
177 int start_month = 0;
178 int end_month = 0;
179
180 if(dt.year >= 2007) {
181 start_month = MAR;
182 end_month = NOV;
183 int first_sunday_march = static_cast<int>(
185 start_day = first_sunday_march + 7;
186 end_day = static_cast<int>(
188 } else {
189 start_month = APR;
190 end_month = OCT;
191 start_day = static_cast<int>(
193 end_day = last_sunday_month_day(dt.year, OCT);
194 }
195
196 if(dt.mon > start_month && dt.mon < end_month) {
197 return true;
198 }
199 if(dt.mon < start_month || dt.mon > end_month) {
200 return false;
201 }
202 if(dt.mon == start_month) {
203 if(dt.day > start_day) {
204 return true;
205 }
206 if(dt.day < start_day) {
207 return false;
208 }
209 return dt.hour >= SWITCH_HOUR;
210 }
211 if(dt.mon == end_month) {
212 if(dt.day < end_day) {
213 return true;
214 }
215 if(dt.day > end_day) {
216 return false;
217 }
218 return dt.hour < SWITCH_HOUR;
219 }
220 return false;
221 }
222
223 inline bool fixed_zone_offset(TimeZone zone, tz_t& utc_offset) {
224 switch(zone) {
225 case GMT:
226 case UTC:
227 case WET:
228 utc_offset = 0;
229 return true;
230 case WEST:
231 utc_offset = static_cast<tz_t>(SEC_PER_HOUR);
232 return true;
233 case CET:
234 utc_offset = static_cast<tz_t>(SEC_PER_HOUR);
235 return true;
236 case CEST:
237 utc_offset = static_cast<tz_t>(SEC_PER_HOUR * 2);
238 return true;
239 case EET:
240 utc_offset = static_cast<tz_t>(SEC_PER_HOUR * 2);
241 return true;
242 case EEST:
243 utc_offset = static_cast<tz_t>(SEC_PER_HOUR * 3);
244 return true;
245 case IST:
246 utc_offset = static_cast<tz_t>(SEC_PER_HOUR * 5 + SEC_PER_MIN * 30);
247 return true;
248 case MYT:
249 case WITA:
250 case SGT:
251 case PHT:
252 case HKT:
253 utc_offset = static_cast<tz_t>(SEC_PER_HOUR * 8);
254 return true;
255 case WIB:
256 case ICT:
257 utc_offset = static_cast<tz_t>(SEC_PER_HOUR * 7);
258 return true;
259 case WIT:
260 case JST:
261 case KST:
262 utc_offset = static_cast<tz_t>(SEC_PER_HOUR * 9);
263 return true;
264 case KZT:
265 utc_offset = static_cast<tz_t>(SEC_PER_HOUR * 5);
266 return true;
267 case TRT:
268 case BYT:
269 utc_offset = static_cast<tz_t>(SEC_PER_HOUR * 3);
270 return true;
271 case GST:
272 utc_offset = static_cast<tz_t>(SEC_PER_HOUR * 4);
273 return true;
274 default:
275 utc_offset = 0;
276 return false;
277 }
278 }
279
281 if(local_ms == ERROR_TIMESTAMP) {
282 return ERROR_TIMESTAMP;
283 }
284 const ts_t local_sec = ms_to_sec<ts_t>(local_ms);
285 const ts_ms_t remainder_ms = local_ms - sec_to_ms<ts_ms_t>(local_sec);
286 const ts_t gmt_sec = zone_to_gmt(local_sec, zone);
287 if(gmt_sec == ERROR_TIMESTAMP) {
288 return ERROR_TIMESTAMP;
289 }
290 return sec_to_ms<ts_ms_t>(gmt_sec) + remainder_ms;
291 }
292
294 if(gmt_ms == ERROR_TIMESTAMP) {
295 return ERROR_TIMESTAMP;
296 }
297 const ts_t gmt_sec = ms_to_sec<ts_t>(gmt_ms);
298 const ts_ms_t remainder_ms = gmt_ms - sec_to_ms<ts_ms_t>(gmt_sec);
299 const ts_t local_sec = gmt_to_zone(gmt_sec, zone);
300 if(local_sec == ERROR_TIMESTAMP) {
301 return ERROR_TIMESTAMP;
302 }
303 return sec_to_ms<ts_ms_t>(local_sec) + remainder_ms;
304 }
305
306 } // namespace detail
307
311 inline ts_t cet_to_gmt(ts_t cet) {
312 return detail::cet_to_gmt_impl(cet);
313 }
314
318 inline ts_t eet_to_gmt(ts_t eet) {
319 return detail::european_local_to_gmt(eet, 2);
320 }
321
327 }
328
332 inline ts_t et_to_gmt(ts_t et) {
334 bool is_dst = detail::is_us_eastern_dst_local(dt);
335 return et + SEC_PER_HOUR * (is_dst ? 4 : 5);
336 }
337
341 inline ts_t gmt_to_et(ts_t gmt) {
342 ts_t et_standard = gmt - SEC_PER_HOUR * 5;
343 DateTimeStruct dt_local = to_date_time(et_standard);
344 bool is_dst = detail::is_us_eastern_dst_local(dt_local);
345 return gmt - SEC_PER_HOUR * (is_dst ? 4 : 5);
346 }
347
351 inline ts_t ny_to_gmt(ts_t ny) {
352 return et_to_gmt(ny);
353 }
354
358 inline ts_t gmt_to_ny(ts_t gmt) {
359 return gmt_to_et(gmt);
360 }
361
365 inline ts_t ct_to_gmt(ts_t ct) {
366 return et_to_gmt(ct + SEC_PER_HOUR);
367 }
368
372 inline ts_t gmt_to_ct(ts_t gmt) {
373 return gmt_to_et(gmt) - SEC_PER_HOUR;
374 }
375
379 inline ts_t gmt_to_cet(ts_t gmt) {
380 return detail::gmt_to_cet_impl(gmt);
381 }
382
386 inline ts_t gmt_to_eet(ts_t gmt) {
387 return detail::gmt_to_european_local(gmt, 2);
388 }
389
394 inline ts_t zone_to_gmt(ts_t local, TimeZone zone) {
395 if(local == ERROR_TIMESTAMP) {
396 return ERROR_TIMESTAMP;
397 }
398
399 tz_t utc_offset = 0;
400 switch(zone) {
401 case GMT:
402 case UTC:
403 return local;
404 case WET:
405 return detail::european_local_to_gmt(local, 0);
406 case CET:
407 return cet_to_gmt(local);
408 case EET:
409 return eet_to_gmt(local);
410 case WEST:
411 case CEST:
412 case EEST:
413 detail::fixed_zone_offset(zone, utc_offset);
414 return to_utc(local, utc_offset);
415 case ET:
416 return et_to_gmt(local);
417 case CT:
418 return ct_to_gmt(local);
419 case UNKNOWN:
420 return ERROR_TIMESTAMP;
421 default:
422 if(detail::fixed_zone_offset(zone, utc_offset)) {
423 return to_utc(local, utc_offset);
424 }
425 return ERROR_TIMESTAMP;
426 }
427 }
428
433 inline ts_t gmt_to_zone(ts_t gmt, TimeZone zone) {
434 if(gmt == ERROR_TIMESTAMP) {
435 return ERROR_TIMESTAMP;
436 }
437
438 tz_t utc_offset = 0;
439 switch(zone) {
440 case GMT:
441 case UTC:
442 return gmt;
443 case WET:
444 return detail::gmt_to_european_local(gmt, 0);
445 case CET:
446 return gmt_to_cet(gmt);
447 case EET:
448 return gmt_to_eet(gmt);
449 case WEST:
450 case CEST:
451 case EEST:
452 detail::fixed_zone_offset(zone, utc_offset);
453 return to_local(gmt, utc_offset);
454 case ET:
455 return gmt_to_et(gmt);
456 case CT:
457 return gmt_to_ct(gmt);
458 case UNKNOWN:
459 return ERROR_TIMESTAMP;
460 default:
461 if(detail::fixed_zone_offset(zone, utc_offset)) {
462 return to_local(gmt, utc_offset);
463 }
464 return ERROR_TIMESTAMP;
465 }
466 }
467
473 inline ts_t convert_time_zone(ts_t local, TimeZone from, TimeZone to) {
474 ts_t gmt = zone_to_gmt(local, from);
475 return gmt == ERROR_TIMESTAMP ? ERROR_TIMESTAMP
476 : gmt_to_zone(gmt, to);
477 }
478
483 inline ts_ms_t zone_to_gmt_ms(ts_ms_t local_ms, TimeZone zone) {
484 if(local_ms == ERROR_TIMESTAMP) {
485 return ERROR_TIMESTAMP;
486 }
487
488 tz_t utc_offset = 0;
489 switch(zone) {
490 case GMT:
491 case UTC:
492 return local_ms;
493 case WET:
494 case CET:
495 case EET:
496 case ET:
497 case CT:
498 return detail::zone_to_gmt_ms_by_seconds(local_ms, zone);
499 case WEST:
500 case CEST:
501 case EEST:
502 detail::fixed_zone_offset(zone, utc_offset);
503 return to_utc_ms(local_ms, utc_offset);
504 case UNKNOWN:
505 return ERROR_TIMESTAMP;
506 default:
507 if(detail::fixed_zone_offset(zone, utc_offset)) {
508 return to_utc_ms(local_ms, utc_offset);
509 }
510 return ERROR_TIMESTAMP;
511 }
512 }
513
518 inline ts_ms_t gmt_to_zone_ms(ts_ms_t gmt_ms, TimeZone zone) {
519 if(gmt_ms == ERROR_TIMESTAMP) {
520 return ERROR_TIMESTAMP;
521 }
522
523 tz_t utc_offset = 0;
524 switch(zone) {
525 case GMT:
526 case UTC:
527 return gmt_ms;
528 case WET:
529 case CET:
530 case EET:
531 case ET:
532 case CT:
533 return detail::gmt_to_zone_ms_by_seconds(gmt_ms, zone);
534 case WEST:
535 case CEST:
536 case EEST:
537 detail::fixed_zone_offset(zone, utc_offset);
538 return to_local_ms(gmt_ms, utc_offset);
539 case UNKNOWN:
540 return ERROR_TIMESTAMP;
541 default:
542 if(detail::fixed_zone_offset(zone, utc_offset)) {
543 return to_local_ms(gmt_ms, utc_offset);
544 }
545 return ERROR_TIMESTAMP;
546 }
547 }
548
555 ts_ms_t gmt_ms = zone_to_gmt_ms(local_ms, from);
556 return gmt_ms == ERROR_TIMESTAMP ? ERROR_TIMESTAMP
557 : gmt_to_zone_ms(gmt_ms, to);
558 }
559
560 namespace detail {
561
563 LocalTimeStatus status,
564 ts_ms_t first_utc_ms = ERROR_TIMESTAMP,
565 ts_ms_t second_utc_ms = ERROR_TIMESTAMP) {
566 LocalTimeResolution result = {status, first_utc_ms, second_utc_ms};
567 return result;
568 }
569
571 tz_t& first_offset,
572 tz_t& second_offset) {
573 switch(zone) {
574 case WET:
575 first_offset = 0;
576 second_offset = static_cast<tz_t>(SEC_PER_HOUR);
577 return true;
578 case CET:
579 first_offset = static_cast<tz_t>(SEC_PER_HOUR);
580 second_offset = static_cast<tz_t>(SEC_PER_HOUR * 2);
581 return true;
582 case EET:
583 first_offset = static_cast<tz_t>(SEC_PER_HOUR * 2);
584 second_offset = static_cast<tz_t>(SEC_PER_HOUR * 3);
585 return true;
586 case ET:
587 first_offset = static_cast<tz_t>(-SEC_PER_HOUR * 5);
588 second_offset = static_cast<tz_t>(-SEC_PER_HOUR * 4);
589 return true;
590 case CT:
591 first_offset = static_cast<tz_t>(-SEC_PER_HOUR * 6);
592 second_offset = static_cast<tz_t>(-SEC_PER_HOUR * 5);
593 return true;
594 default:
595 first_offset = 0;
596 second_offset = 0;
597 return false;
598 }
599 }
600
602 switch(zone) {
603 case WET:
604 return 0;
605 case CET:
606 return static_cast<tz_t>(SEC_PER_HOUR);
607 case EET:
608 return static_cast<tz_t>(SEC_PER_HOUR * 2);
609 case ET:
610 return static_cast<tz_t>(-SEC_PER_HOUR * 5);
611 case CT:
612 return static_cast<tz_t>(-SEC_PER_HOUR * 6);
613 default:
614 return 0;
615 }
616 }
617
619 return zone == WET || zone == CET || zone == EET;
620 }
621
622 inline bool is_us_dynamic_zone(TimeZone zone) {
623 return zone == ET || zone == CT;
624 }
625
626 inline bool european_dst_at_utc_ms(ts_ms_t utc_ms) {
627 const DateTimeStruct dt = to_date_time(ms_to_sec<ts_t>(utc_ms));
628 const int start_day = last_sunday_month_day(dt.year, MAR);
629 const int end_day = last_sunday_month_day(dt.year, OCT);
630 const ts_ms_t start_ms =
631 to_timestamp_ms(dt.year, int(MAR), start_day, 1, 0, 0);
632 const ts_ms_t end_ms =
633 to_timestamp_ms(dt.year, int(OCT), end_day, 1, 0, 0);
634 return utc_ms >= start_ms && utc_ms < end_ms;
635 }
636
637 inline int first_sunday_month_day(int year, int month) {
638 return static_cast<int>(
639 1 + (DAYS_PER_WEEK - day_of_week_date(year, month, 1)) %
641 }
642
643 inline bool us_dst_at_utc_ms(TimeZone zone, ts_ms_t utc_ms) {
644 const DateTimeStruct dt = to_date_time(ms_to_sec<ts_t>(utc_ms));
645 const int year = static_cast<int>(dt.year);
646 int start_month = MAR;
647 int end_month = NOV;
648 int start_day = first_sunday_month_day(year, MAR) + 7;
649 int end_day = first_sunday_month_day(year, NOV);
650
651 if(dt.year < 2007) {
652 start_month = APR;
653 end_month = OCT;
654 start_day = first_sunday_month_day(year, APR);
655 end_day = last_sunday_month_day(year, OCT);
656 }
657
658 const tz_t standard_offset = dynamic_zone_standard_offset(zone);
659 const tz_t daylight_offset =
660 static_cast<tz_t>(standard_offset + SEC_PER_HOUR);
661 const ts_ms_t start_local =
662 to_timestamp_ms(dt.year, start_month, start_day, 2, 0, 0);
663 const ts_ms_t end_local =
664 to_timestamp_ms(dt.year, end_month, end_day, 2, 0, 0);
665 const ts_ms_t start_utc = to_utc_ms(start_local, standard_offset);
666 const ts_ms_t end_utc = to_utc_ms(end_local, daylight_offset);
667 return utc_ms >= start_utc && utc_ms < end_utc;
668 }
669
671 ts_ms_t utc_ms,
672 tz_t offset) {
673 if(is_european_dynamic_zone(zone)) {
674 const tz_t standard_offset = dynamic_zone_standard_offset(zone);
675 const tz_t expected =
676 static_cast<tz_t>(standard_offset +
679 : 0));
680 return offset == expected;
681 }
682
683 if(is_us_dynamic_zone(zone)) {
684 const tz_t standard_offset = dynamic_zone_standard_offset(zone);
685 const tz_t expected =
686 static_cast<tz_t>(standard_offset +
687 (us_dst_at_utc_ms(zone, utc_ms)
689 : 0));
690 return offset == expected;
691 }
692
693 return false;
694 }
695
697 ts_ms_t local_ms,
698 TimeZone zone,
699 tz_t offset) {
700 const ts_ms_t candidate = to_utc_ms(local_ms, offset);
701 if(candidate == ERROR_TIMESTAMP ||
702 to_local_ms(candidate, offset) != local_ms ||
703 !dynamic_offset_applies_at_utc_ms(zone, candidate, offset) ||
704 result.first_utc_ms == candidate ||
705 result.second_utc_ms == candidate) {
706 return;
707 }
708
709 if(result.first_utc_ms == ERROR_TIMESTAMP) {
710 result.first_utc_ms = candidate;
711 return;
712 }
713
714 if(result.second_utc_ms == ERROR_TIMESTAMP) {
715 result.second_utc_ms = candidate;
716 }
717 }
718
720 ts_ms_t local_ms,
721 TimeZone zone,
722 tz_t first_offset,
723 tz_t second_offset) {
724 LocalTimeResolution result =
726
727 add_local_time_candidate(result, local_ms, zone, first_offset);
728 add_local_time_candidate(result, local_ms, zone, second_offset);
729
730 if(result.first_utc_ms == ERROR_TIMESTAMP) {
731 return result;
732 }
733
734 if(result.second_utc_ms == ERROR_TIMESTAMP) {
736 return result;
737 }
738
739 if(result.second_utc_ms < result.first_utc_ms) {
740 const ts_ms_t tmp = result.first_utc_ms;
741 result.first_utc_ms = result.second_utc_ms;
742 result.second_utc_ms = tmp;
743 }
744
746 return result;
747 }
748
750 return status == LocalTimeStatus::valid ||
752 }
753
755 const LocalTimeResolution& resolution,
756 AmbiguousTimePolicy ambiguous_policy) {
757 if(resolution.status == LocalTimeStatus::valid) {
758 return resolution.first_utc_ms;
759 }
760
761 if(resolution.status == LocalTimeStatus::ambiguous) {
762 switch(ambiguous_policy) {
764 return resolution.first_utc_ms;
766 return resolution.second_utc_ms;
768 default:
769 return ERROR_TIMESTAMP;
770 }
771 }
772
773 return ERROR_TIMESTAMP;
774 }
775
776 } // namespace detail
777
783 inline bool zone_offset_at_utc_ms(ts_ms_t utc_ms,
784 TimeZone zone,
785 tz_t& out) noexcept {
786 if(utc_ms == ERROR_TIMESTAMP || zone == UNKNOWN) {
787 return false;
788 }
789
790 if(zone == GMT || zone == UTC) {
791 out = 0;
792 return true;
793 }
794
796 const tz_t standard_offset = detail::dynamic_zone_standard_offset(zone);
797 out = static_cast<tz_t>(standard_offset +
800 : 0));
801 return true;
802 }
803
805 const tz_t standard_offset = detail::dynamic_zone_standard_offset(zone);
806 out = static_cast<tz_t>(standard_offset +
807 (detail::us_dst_at_utc_ms(zone, utc_ms)
809 : 0));
810 return true;
811 }
812
813 tz_t utc_offset = 0;
814 if(detail::fixed_zone_offset(zone, utc_offset)) {
815 out = utc_offset;
816 return true;
817 }
818
819 return false;
820 }
821
827 inline bool zone_offset_at_utc(ts_t utc,
828 TimeZone zone,
829 tz_t& out) noexcept {
830 return utc == ERROR_TIMESTAMP
831 ? false
832 : zone_offset_at_utc_ms(sec_to_ms<ts_ms_t>(utc), zone, out);
833 }
834
840 TimeZone zone) {
841 if(local_ms == ERROR_TIMESTAMP || zone == UNKNOWN) {
844 }
845
846 if(zone == GMT || zone == UTC) {
848 local_ms);
849 }
850
851 tz_t first_offset = 0;
852 tz_t second_offset = 0;
853 if(detail::dynamic_dst_zone_offsets(zone, first_offset, second_offset)) {
855 zone,
856 first_offset,
857 second_offset);
858 }
859
860 tz_t utc_offset = 0;
861 if(detail::fixed_zone_offset(zone, utc_offset)) {
864 to_utc_ms(local_ms, utc_offset));
865 }
866
868 }
869
879
880 namespace detail {
881
883 TimeZone zone,
884 int direction) {
885 const ts_ms_t window = static_cast<ts_ms_t>(MS_PER_DAY) * 2;
886 const bool forward = direction >= 0;
887 ts_ms_t low = forward ? local_ms : local_ms - window;
888 ts_ms_t high = forward ? local_ms + window : local_ms;
889
891 resolve_local_time_ms(forward ? high : low, zone);
893 return ERROR_TIMESTAMP;
894 }
895
896 while(high - low > 1) {
897 const ts_ms_t mid = low + (high - low) / 2;
898 const LocalTimeResolution resolution =
899 resolve_local_time_ms(mid, zone);
900 if(local_time_status_has_utc(resolution.status)) {
901 if(forward) {
902 high = mid;
903 } else {
904 low = mid;
905 }
906 } else if(forward) {
907 low = mid;
908 } else {
909 high = mid;
910 }
911 }
912
913 const LocalTimeResolution shifted =
914 resolve_local_time_ms(forward ? high : low, zone);
916 shifted,
918 }
919
920 } // namespace detail
921
924 TimeZone zone,
925 AmbiguousTimePolicy ambiguous_policy,
926 NonexistentTimePolicy nonexistent_policy) {
927 const LocalTimeResolution resolution =
928 resolve_local_time_ms(local_ms, zone);
929
930 if(resolution.status == LocalTimeStatus::nonexistent) {
931 switch(nonexistent_policy) {
934 local_ms,
935 zone,
936 1);
939 local_ms,
940 zone,
941 -1);
943 default:
944 return ERROR_TIMESTAMP;
945 }
946 }
947
948 return detail::local_time_resolution_to_utc(resolution,
949 ambiguous_policy);
950 }
951
953 inline ts_t zone_to_gmt(ts_t local,
954 TimeZone zone,
955 AmbiguousTimePolicy ambiguous_policy,
956 NonexistentTimePolicy nonexistent_policy) {
957 const ts_ms_t utc_ms = local == ERROR_TIMESTAMP
960 zone,
961 ambiguous_policy,
962 nonexistent_policy);
963 return utc_ms == ERROR_TIMESTAMP ? ERROR_TIMESTAMP
964 : ms_to_sec<ts_t>(utc_ms);
965 }
966
969 return zone_to_gmt_ms(local_ms,
970 zone,
973 }
974
976 inline ts_t zone_to_gmt_strict(ts_t local, TimeZone zone) {
977 return zone_to_gmt(local,
978 zone,
981 }
982
985 ts_ms_t local_ms,
986 TimeZone from,
987 TimeZone to,
988 AmbiguousTimePolicy ambiguous_policy,
989 NonexistentTimePolicy nonexistent_policy) {
990 const ts_ms_t gmt_ms = zone_to_gmt_ms(local_ms,
991 from,
992 ambiguous_policy,
993 nonexistent_policy);
994 return gmt_ms == ERROR_TIMESTAMP ? ERROR_TIMESTAMP
995 : gmt_to_zone_ms(gmt_ms, to);
996 }
997
1000 ts_t local,
1001 TimeZone from,
1002 TimeZone to,
1003 AmbiguousTimePolicy ambiguous_policy,
1004 NonexistentTimePolicy nonexistent_policy) {
1005 const ts_t gmt = zone_to_gmt(local,
1006 from,
1007 ambiguous_policy,
1008 nonexistent_policy);
1009 return gmt == ERROR_TIMESTAMP ? ERROR_TIMESTAMP
1010 : gmt_to_zone(gmt, to);
1011 }
1012
1013 inline ts_t ist_to_gmt(ts_t ist) { return zone_to_gmt(ist, IST); }
1014 inline ts_t gmt_to_ist(ts_t gmt) { return gmt_to_zone(gmt, IST); }
1015
1016 inline ts_t myt_to_gmt(ts_t myt) { return zone_to_gmt(myt, MYT); }
1017 inline ts_t gmt_to_myt(ts_t gmt) { return gmt_to_zone(gmt, MYT); }
1018
1019 inline ts_t wib_to_gmt(ts_t wib) { return zone_to_gmt(wib, WIB); }
1020 inline ts_t gmt_to_wib(ts_t gmt) { return gmt_to_zone(gmt, WIB); }
1021
1022 inline ts_t wita_to_gmt(ts_t wita) { return zone_to_gmt(wita, WITA); }
1023 inline ts_t gmt_to_wita(ts_t gmt) { return gmt_to_zone(gmt, WITA); }
1024
1025 inline ts_t wit_to_gmt(ts_t wit) { return zone_to_gmt(wit, WIT); }
1026 inline ts_t gmt_to_wit(ts_t gmt) { return gmt_to_zone(gmt, WIT); }
1027
1028 inline ts_t kzt_to_gmt(ts_t kzt) { return zone_to_gmt(kzt, KZT); }
1029 inline ts_t gmt_to_kzt(ts_t gmt) { return gmt_to_zone(gmt, KZT); }
1030
1031 inline ts_t trt_to_gmt(ts_t trt) { return zone_to_gmt(trt, TRT); }
1032 inline ts_t gmt_to_trt(ts_t gmt) { return gmt_to_zone(gmt, TRT); }
1033
1034 inline ts_t byt_to_gmt(ts_t byt) { return zone_to_gmt(byt, BYT); }
1035 inline ts_t gmt_to_byt(ts_t gmt) { return gmt_to_zone(gmt, BYT); }
1036
1037 inline ts_t sgt_to_gmt(ts_t sgt) { return zone_to_gmt(sgt, SGT); }
1038 inline ts_t gmt_to_sgt(ts_t gmt) { return gmt_to_zone(gmt, SGT); }
1039
1040 inline ts_t ict_to_gmt(ts_t ict) { return zone_to_gmt(ict, ICT); }
1041 inline ts_t gmt_to_ict(ts_t gmt) { return gmt_to_zone(gmt, ICT); }
1042
1043 inline ts_t pht_to_gmt(ts_t pht) { return zone_to_gmt(pht, PHT); }
1044 inline ts_t gmt_to_pht(ts_t gmt) { return gmt_to_zone(gmt, PHT); }
1045
1046 inline ts_t gst_to_gmt(ts_t gst) { return zone_to_gmt(gst, GST); }
1047 inline ts_t gmt_to_gst(ts_t gmt) { return gmt_to_zone(gmt, GST); }
1048
1049 inline ts_t hkt_to_gmt(ts_t hkt) { return zone_to_gmt(hkt, HKT); }
1050 inline ts_t gmt_to_hkt(ts_t gmt) { return gmt_to_zone(gmt, HKT); }
1051
1052 inline ts_t jst_to_gmt(ts_t jst) { return zone_to_gmt(jst, JST); }
1053 inline ts_t gmt_to_jst(ts_t gmt) { return gmt_to_zone(gmt, JST); }
1054
1055 inline ts_t kst_to_gmt(ts_t kst) { return zone_to_gmt(kst, KST); }
1056 inline ts_t gmt_to_kst(ts_t gmt) { return gmt_to_zone(gmt, KST); }
1057
1059 inline ts_t kyiv_to_gmt(ts_t kyiv) { return eet_to_gmt(kyiv); }
1060
1062 inline ts_t gmt_to_kyiv(ts_t gmt) { return gmt_to_eet(gmt); }
1063
1065
1066} // namespace time_shield
1067
1068#endif // _TIME_SHIELD_TIME_ZONE_CONVERSIONS_HPP_INCLUDED
Header for date and time structure and related functions.
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:49
int32_t tz_t
Time zone offset in minutes from UTC (e.g., +180 = UTC+3).
Definition types.hpp:61
int64_t ts_ms_t
Unix timestamp in milliseconds since epoch.
Definition types.hpp:50
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.
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.
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.
Umbrella header for time conversion functions.
Helper functions for unit conversions between seconds, minutes, hours, and milliseconds.
UTC offset arithmetic helpers (UTC <-> local) and TimeZoneStruct offset extraction.