AceTime 3.0.0 Date and time classes for Arduino that support timezones from the TZ Database.

BasicZoneProcessor.h
1 /*
2 * MIT License
3 * Copyright (c) 2019 Brian T. Park
4 */
5
6 #ifndef ACE_TIME_BASIC_ZONE_PROCESSOR_H
7 #define ACE_TIME_BASIC_ZONE_PROCESSOR_H
8
9 #include <stdint.h>
10 #include <AceCommon.h> // strncpy_T()
11 #include "../zoneinfo/infos.h"
12 #include "common/common.h" // kAbbrevSize
13 #include "common/logging.h"
14 #include "TimeOffset.h"
15 #include "LocalDate.h"
16 #include "OffsetDateTime.h"
17 #include "ZoneProcessor.h"
18
19 #ifndef ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG
20 #define ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG 0
21 #endif
22
23 class BasicZoneProcessorTest_priorYearOfRule;
24 class BasicZoneProcessorTest_compareRulesBeforeYear;
25 class BasicZoneProcessorTest_findLatestPriorRule;
26 class BasicZoneProcessorTest_findZoneEra;
27 class BasicZoneProcessorTest_init_primitives;
28 class BasicZoneProcessorTest_initForLocalDate;
29 class BasicZoneProcessorTest_setZoneKey;
30 class BasicZoneProcessorTest_calcRuleOffsetMinutes;
31
32 class Print;
33
34 namespace ace_time {
35 namespace basic {
36
54 template <typename D>
55 struct TransitionTemplate {
61 typename D::ZoneEraBroker era;
62
72 typename D::ZoneRuleBroker rule;
73
75 acetime_t startEpochSeconds;
76
81 int16_t offsetMinutes;
82
84 int16_t deltaMinutes;
85
87 int16_t year;
88
94 uint8_t month;
95
103 char abbrev[kAbbrevSize];
104
106 void log() const {
107 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
108 logging::printf("(%d/%d)", year, month);
109 if (sizeof(acetime_t) == sizeof(int)) {
110 logging::printf("; stEps: %d", startEpochSeconds);
111 } else {
112 logging::printf("; stEps: %ld", startEpochSeconds);
113 }
114 logging::printf("; offMin: %d", offsetMinutes);
115 logging::printf("; abbrev: %s", abbrev);
116 if (! rule.isNull()) {
117 logging::printf("; r.fromYear: %d", rule.fromYear());
118 logging::printf("; r.toYear: %d", rule.toYear());
119 logging::printf("; r.inMonth: %d", rule.inMonth());
120 logging::printf("; r.onDayOfMonth: %d", rule.onDayOfMonth());
121 }
122 logging::printf("\n");
123 }
124 }
125 };
126
128 inline int8_t compareYearMonth(int16_t aYear, uint8_t aMonth,
129 int16_t bYear, uint8_t bMonth) {
130 if (aYear < bYear) return -1;
131 if (aYear > bYear) return 1;
132 if (aMonth < bMonth) return -1;
133 if (aMonth > bMonth) return 1;
134 return 0;
135 }
136
137 } // namespace basic
138
190 template <typename D>
191 class BasicZoneProcessorTemplate: public ZoneProcessor {
192 public:
194 typedef basic::TransitionTemplate<D> Transition;
195
196 bool isLink() const override {
197 return ! mZoneInfoBroker.targetInfo().isNull();
198 }
199
200 uint32_t getZoneId() const override {
201 return mZoneInfoBroker.zoneId();
202 }
203
232 FindResult findByLocalDateTime(
233 const LocalDateTime& ldt) const override {
234 FindResult result;
235 bool success = initForLocalDate(ldt.localDate());
236 if (!success) return result;
237
238 // 0) Use the UTC epochSeconds to get intial guess of offset.
239 acetime_t epochSeconds0 = ldt.toEpochSeconds();
240 auto result0 = findByEpochSeconds(epochSeconds0);
241 if (result0.type == FindResult::kTypeNotFound) return result;
242 auto offset0 = TimeOffset::forSeconds(
243 result0.reqStdOffsetSeconds + result0.reqDstOffsetSeconds);
244
245 // 1) Use offset0 to get the next epochSeconds and offset.
246 auto odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset0);
247 acetime_t epochSeconds1 = odt.toEpochSeconds();
248 auto result1 = findByEpochSeconds(epochSeconds1);
249 if (result1.type == FindResult::kTypeNotFound) return result;
250 auto offset1 = TimeOffset::forSeconds(
251 result1.reqStdOffsetSeconds + result1.reqDstOffsetSeconds);
252
253 // 2) Use offset1 to get the next epochSeconds and offset.
254 odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset1);
255 acetime_t epochSeconds2 = odt.toEpochSeconds();
256 auto result2 = findByEpochSeconds(epochSeconds2);
257 if (result2.type == FindResult::kTypeNotFound) return result;
258 auto offset2 = TimeOffset::forSeconds(
259 result2.reqStdOffsetSeconds + result2.reqDstOffsetSeconds);
260
261 // If offset1 and offset2 are equal, then we have an equilibrium
262 // and odt(1) must equal odt(2).
263 if (offset1 == offset2) {
264 // I think this happens for kTypeExact or kTypeOverlap, but the current
265 // algorithm cannot distinguish between the two, so let's pretend that
266 // it is kTypeExact. Pick either of result1 or result2.
267 result = result1;
268 } else {
269 // If the offsets don't match, then I think we have a kTypeGap.
270 // Pick the stdOffset and dstOffset that generate the later epochSeconds
271 // (the earlier transition), but convert into the LocalDateTime of the
272 // earlier epochSeconds (the later transition).
273 if (epochSeconds1 > epochSeconds2) {
274 result = result1;
275 } else {
276 result = result2;
277 }
278 result.type = FindResult::kTypeGap;
279 }
280
281 return result;
282 }
283
284 FindResult findByEpochSeconds(acetime_t epochSeconds) const override {
285 FindResult result;
286 const Transition* transition = getTransition(epochSeconds);
287 if (!transition) return result;
288
289 result.dstOffsetSeconds = transition->deltaMinutes * kSecPerMin;
290 result.stdOffsetSeconds = transition->offsetMinutes * kSecPerMin;
291 result.reqStdOffsetSeconds = result.stdOffsetSeconds;
292 result.reqDstOffsetSeconds = result.dstOffsetSeconds;
293 result.type = FindResult::kTypeExact;
294 result.abbrev = transition->abbrev;
295
296 return result;
297 }
298
299 void printNameTo(Print& printer) const override {
300 mZoneInfoBroker.printNameTo(printer);
301 }
302
303 void printShortNameTo(Print& printer) const override {
304 mZoneInfoBroker.printShortNameTo(printer);
305 }
306
307 void printTargetNameTo(Print& printer) const override {
308 if (isLink()) {
309 mZoneInfoBroker.targetInfo().printNameTo(printer);
310 }
311 }
312
313 void setZoneKey(uintptr_t zoneKey) override {
314 if (! mZoneInfoStore) return;
315 if (mZoneInfoBroker.equals(zoneKey)) return;
316
317 mZoneInfoBroker = mZoneInfoStore->createZoneInfoBroker(zoneKey);
318 mYear = LocalDate::kInvalidYear;
319 mNumTransitions = 0;
320 }
321
322 bool equalsZoneKey(uintptr_t zoneKey) const override {
323 return mZoneInfoBroker.equals(zoneKey);
324 }
325
327 void log() const {
328 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
329 logging::printf("BasicZoneProcessor:\n");
330 logging::printf(" mEpochYear: %d\n", mEpochYear);
331 logging::printf(" mYear: %d\n", mYear);
332 logging::printf(" mNumTransitions: %d\n", mNumTransitions);
333 for (int i = 0; i < mNumTransitions; i++) {
334 logging::printf(" mT[%d]=", i);
335 mTransitions[i].log();
336 }
337 }
338 }
339
346 void setZoneInfoStore(const typename D::ZoneInfoStore* zoneInfoStore) {
347 mZoneInfoStore = zoneInfoStore;
348 }
349
350 protected:
351
363 explicit BasicZoneProcessorTemplate(
364 uint8_t type,
365 const typename D::ZoneInfoStore* zoneInfoStore /*nullable*/,
366 uintptr_t zoneKey
367 ) :
368 ZoneProcessor(type),
369 mZoneInfoStore(zoneInfoStore)
370 {
371 setZoneKey(zoneKey);
372 }
373
374 private:
375 friend class ::BasicZoneProcessorTest_priorYearOfRule;
376 friend class ::BasicZoneProcessorTest_compareRulesBeforeYear;
377 friend class ::BasicZoneProcessorTest_findLatestPriorRule;
378 friend class ::BasicZoneProcessorTest_findZoneEra;
379 friend class ::BasicZoneProcessorTest_init_primitives;
380 friend class ::BasicZoneProcessorTest_initForLocalDate;
381 friend class ::BasicZoneProcessorTest_setZoneKey;
382 friend class ::BasicZoneProcessorTest_calcRuleOffsetMinutes;
383
394 static const uint8_t kMaxCacheEntries = 5;
395
401 static const acetime_t kMinEpochSeconds = INT32_MIN + 1;
402
403 // Disable copy constructor and assignment operator.
404 BasicZoneProcessorTemplate(const BasicZoneProcessorTemplate&) = delete;
405 BasicZoneProcessorTemplate& operator=(const BasicZoneProcessorTemplate&) =
406 delete;
407
408 bool equals(const ZoneProcessor& other) const override {
409 return mZoneInfoBroker.equals(
410 ((const BasicZoneProcessorTemplate&) other).mZoneInfoBroker);
411 }
412
414 const Transition* getTransition(acetime_t epochSeconds) const {
415 bool success = initForEpochSeconds(epochSeconds);
416 return (success) ? findMatch(epochSeconds) : nullptr;
417 }
418
447 bool initForLocalDate(const LocalDate& ld) const {
448 int16_t year = ld.year();
449 if (ld.month() == 1 && ld.day() == 1) {
450 year--;
451 }
452 // Restrict to [1,9999], even though LocalDate should be able to handle
453 // [0,10000].
454 if (year <= LocalDate::kMinYear || LocalDate::kMaxYear <= year) {
455 return false;
456 }
457
458 if (isFilled(year)) return true;
459 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
460 logging::printf("initForLocalDate(): %d (new year %d)\n",
461 ld.year(), year);
462 }
463
464 mYear = year;
465 mEpochYear = Epoch::currentEpochYear();
466 mNumTransitions = 0; // clear cache
467
468 typename D::ZoneEraBroker priorEra = addTransitionPriorToYear(year);
469 typename D::ZoneEraBroker currentEra =
470 addTransitionsForYear(year, priorEra);
471 addTransitionAfterYear(year, currentEra);
472 calcTransitions();
473 calcAbbreviations();
474
475 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
476 log();
477 }
478
479 return true;
480 }
481
487 bool initForEpochSeconds(acetime_t epochSeconds) const {
488 LocalDate ld = LocalDate::forEpochSeconds(epochSeconds);
489 return initForLocalDate(ld);
490 }
491
498 typename D::ZoneEraBroker addTransitionPriorToYear(int16_t year) const {
499 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
500 logging::printf("addTransitionPriorToYear(): %d\n", year);
501 }
502
503 const typename D::ZoneEraBroker era =
504 findZoneEra(mZoneInfoBroker, year - 1);
505
506 // If the prior ZoneEra has a ZonePolicy), then find the latest rule
507 // within the ZoneEra. Otherwise, add a Transition using a rule==nullptr.
508 typename D::ZoneRuleBroker latest =
509 findLatestPriorRule(era.zonePolicy(), year);
510 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
511 logging::printf("addTransitionsPriorToYear(): adding latest prior ");
512 if (latest.isNull()) {
513 logging::printf("ZR(null)\n");
514 } else {
515 logging::printf("ZR[%d,%d]\n", latest.fromYear(), latest.toYear());
516 }
517 }
518 addTransition(year - 1, 0 /*month*/, era, latest);
519
520 return era;
521 }
522
528 static typename D::ZoneRuleBroker findLatestPriorRule(
529 const typename D::ZonePolicyBroker& zonePolicy, int16_t year) {
530 typename D::ZoneRuleBroker latest;
531 if (zonePolicy.isNull()) return latest;
532
533 uint8_t numRules = zonePolicy.numRules();
534 for (uint8_t i = 0; i < numRules; i++) {
535 const typename D::ZoneRuleBroker rule = zonePolicy.rule(i);
536 // Check if rule is effective prior to the given year
537 if (rule.fromYear() < year) {
538 if ((latest.isNull()) ||
539 compareRulesBeforeYear(year, rule, latest) > 0) {
540 latest = rule;
541 }
542 }
543 }
544
545 return latest;
546 }
547
549 static int8_t compareRulesBeforeYear(
550 int16_t year,
551 const typename D::ZoneRuleBroker& a,
552 const typename D::ZoneRuleBroker& b) {
553 return basic::compareYearMonth(
554 priorYearOfRule(year, a), a.inMonth(),
555 priorYearOfRule(year, b), b.inMonth());
556 }
557
566 static int16_t priorYearOfRule(int16_t year,
567 const typename D::ZoneRuleBroker& rule) {
568 if (rule.toYear() < year) {
569 return rule.toYear();
570 }
571 return year - 1;
572 }
573
578 typename D::ZoneEraBroker addTransitionsForYear(
579 int16_t year, const typename D::ZoneEraBroker& priorEra) const {
580 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
581 logging::printf("addTransitionsForYear(): %d\n", year);
582 }
583
584 const typename D::ZoneEraBroker era = findZoneEra(mZoneInfoBroker, year);
585
586 // If the ZonePolicy has no rules, then add a Transition which takes
587 // effect at the start time of the current year.
588 const typename D::ZonePolicyBroker zonePolicy = era.zonePolicy();
589 if (zonePolicy.isNull()) {
590 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
591 logging::printf("addTransitionsForYear(): adding ZE.untilY=%d\n",
592 era.untilYear());
593 }
594 addTransition(year, 0 /*month*/, era, typename D::ZoneRuleBroker());
595 return era;
596 }
597
598 if (! era.equals(priorEra)) {
599 // The ZoneEra has changed, so we need to find the Rule in effect at
600 // the start of the current year of the current ZoneEra. This may be a
601 // rule far in the past, but shift the rule forward to {year, 1, 1}.
602 typename D::ZoneRuleBroker latestPrior =
603 findLatestPriorRule(era.zonePolicy(), year);
604 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
605 logging::printf(
606 "addTransitionsForYear(): adding latest prior ");
607 if (latestPrior.isNull()) {
608 logging::printf("ZR(null)\n");
609 } else {
610 logging::printf("ZR[%d,%d]\n",
611 latestPrior.fromYear(), latestPrior.toYear());
612 }
613 }
614 addTransition(year, 1 /*month*/, era, latestPrior);
615 }
616
617 // Find all directly matching transitions (i.e. the [from, to] overlap
618 // with the current year) and add them to mTransitions, in sorted order
619 // according to the ZoneRule::inMonth field.
620 uint8_t numRules = zonePolicy.numRules();
621 for (uint8_t i = 0; i < numRules; i++) {
622 const typename D::ZoneRuleBroker rule = zonePolicy.rule(i);
623 if ((rule.fromYear() <= year) && (year <= rule.toYear())) {
624 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
625 logging::printf(
626 "addTransitionsForYear(): adding rule ");
627 if (rule.isNull()) {
628 logging::printf("ZR(null)\n");
629 } else {
630 logging::printf("ZR[%d,%d]\n", rule.fromYear(), rule.toYear());
631 }
632 }
633 addTransition(year, 0 /*month*/, era, rule);
634 }
635 }
636
637 return era;
638 }
639
641 void addTransitionAfterYear(
642 int16_t year, const typename D::ZoneEraBroker& currentEra) const {
643 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
644 logging::printf("addTransitionAfterYear(): %d\n", year);
645 }
646
647 const typename D::ZoneEraBroker eraAfter =
648 findZoneEra(mZoneInfoBroker, year + 1);
649
650 // If the current era is the same as the following year, then we'll just
651 // assume that the latest ZoneRule carries over to Jan 1st of the next
652 // year. tzcompiler.py guarantees no ZoneRule occurs on Jan 1st.
653 if (currentEra.equals(eraAfter)) {
654 return;
655 }
656
657 // If the ZoneEra did change, find the latest transition prior to
658 // {year + 1, 1, 1}, then shift that Transition to Jan 1st of the
659 // following year.
660 typename D::ZoneRuleBroker latest =
661 findLatestPriorRule(eraAfter.zonePolicy(), year + 1);
662 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
663 logging::printf(
664 "addTransitionsAfterYear(): adding latest prior ");
665 if (latest.isNull()) {
666 logging::printf("ZR(null)\n");
667 } else {
668 logging::printf("ZR[%d,%d]\n", latest.fromYear(), latest.toYear());
669 }
670 }
671 addTransition(year + 1, 1 /*month*/, eraAfter, latest);
672 }
673
697 void addTransition(int16_t year, uint8_t month,
698 const typename D::ZoneEraBroker& era,
699 const typename D::ZoneRuleBroker& rule) const {
700
701 // If a zone needs more transitions than kMaxCacheEntries, the check below
702 // will cause the DST transition information to be inaccurate, and it is
703 // highly likely that this situation would be caught in the
704 // AceTimeValidation tests. Since these integration tests pass, I feel
705 // confident that those zones which need more than kMaxCacheEntries are
706 // already filtered out by tzcompiler.py.
707 //
708 // Ideally, the tzcompiler.py script would explicitly remove those zones
709 // which need more than kMaxCacheEntries Transitions. But this would
710 // require a Python version of the BasicZoneProcessor, and unfortunately,
711 // zone_processor.py implements only the ExtendedZoneProcessor algorithm
712 // An early version of zone_processor.py may have implemented something
713 // close to BasicZoneProcessor, and it may be available in the git
714 // history. But it seems like too much work right now to try to dig that
715 // out, just to implement the explicit check for kMaxCacheEntries. It
716 // would mean maintaining another version of zone_processor.py.
717 if (mNumTransitions >= kMaxCacheEntries) return;
718
719 // Insert new element at the end of the list.
720 // NOTE: It is probably tempting to pass a pointer (or reference) to
721 // mTransitions[mNumTransitions] into createTransition(), instead of
722 // returning it by value. However, MemoryBenchmark shows that directly
723 // updating the Transition through the pointer increases flash memory
724 // consumption by ~110 bytes on AVR processors. It seems that creating a
725 // local copy of Transition on the stack, filling it, and then copying it
726 // by value takes fewer instructions.
727 mTransitions[mNumTransitions] = createTransition(year, month, era, rule);
728 mNumTransitions++;
729
730 // perform an insertion sort based on ZoneRule.inMonth()
731 for (uint8_t i = mNumTransitions - 1; i > 0; i--) {
732 Transition& left = mTransitions[i - 1];
733 Transition& right = mTransitions[i];
734 // assume only 1 rule per month
735 if (basic::compareYearMonth(left.year, left.month,
736 right.year, right.month) > 0) {
737 Transition tmp = left;
738 left = right;
739 right = tmp;
740 }
741 }
742 }
743
749 static Transition createTransition(
750 int16_t year,
751 uint8_t month,
752 const typename D::ZoneEraBroker& era,
753 const typename D::ZoneRuleBroker& rule) {
754
755 Transition transition;
756 int16_t deltaMinutes;
757 uint8_t mon;
758 if (rule.isNull()) {
759 mon = 1; // RULES is either '-' or 'hh:mm' so takes effect in Jan
760 deltaMinutes = era.deltaSeconds() / kSecPerMin;
761 transition.abbrev[0] = '\0';
762 } else {
763 mon = rule.inMonth();
764 deltaMinutes = rule.deltaSeconds() / kSecPerMin;
765 ace_common::strncpy_T(
766 transition.abbrev, rule.letter(), kAbbrevSize - 1);
767 transition.abbrev[kAbbrevSize - 1] = '\0';
768 }
769 // Clobber the month if specified.
770 if (month != 0) {
771 mon = month;
772 }
773 int16_t offsetMinutes = era.offsetSeconds() / kSecPerMin;
774
775 transition.era = era;
776 transition.rule = rule;
777 transition.startEpochSeconds = 0;
778 transition.offsetMinutes = offsetMinutes;
779 transition.deltaMinutes = deltaMinutes;
780 transition.year = year;
781 transition.month = mon;
782 return transition;
783 }
784
789 static typename D::ZoneEraBroker findZoneEra(
790 const typename D::ZoneInfoBroker& info,
791 int16_t year) {
792 for (uint8_t i = 0; i < info.numEras(); i++) {
793 const typename D::ZoneEraBroker era = info.era(i);
794 if (year < era.untilYear()) return era;
795 }
796 // Return the last ZoneEra if we run off the end.
797 return info.era(info.numEras() - 1);
798 }
799
807 void calcTransitions() const {
808 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
809 logging::printf("calcTransitions():\n");
810 }
811
812 // Set the initial startEpochSeconds to be -Infinity
813 Transition* prevTransition = &mTransitions[0];
814 prevTransition->startEpochSeconds = kMinEpochSeconds;
815
816 for (uint8_t i = 1; i < mNumTransitions; i++) {
817 Transition& transition = mTransitions[i];
818 const int16_t year = transition.year;
819
820 if (transition.rule.isNull()) {
821 // If the transition is simple (has no named rule), then the
822 // ZoneEra applies for the entire year (since BasicZoneProcessor
823 // supports only whole year in the UNTIL field). The whole year UNTIL
824 // field has an implied 'w' suffix on 00:00, we don't need to call
825 // calcRuleOffsetMinutes() with a 'w', we can just use the previous
826 // transition's offset to calculate the startDateTime of this
827 // transition.
828 //
829 // Also, when transition.rule == nullptr, the mNumTransitions should
830 // be 1, since only a single transition is added by
831 // addTransitionsForYear().
832 const int16_t prevTotalOffsetMinutes = prevTransition->offsetMinutes
833 + prevTransition->deltaMinutes;
834 OffsetDateTime startDateTime = OffsetDateTime::forComponents(
835 year, 1, 1, 0, 0, 0,
836 TimeOffset::forMinutes(prevTotalOffsetMinutes));
837 transition.startEpochSeconds = startDateTime.toEpochSeconds();
838 } else {
839 // In this case, the transition points to a named ZonePolicy, which
840 // means that there could be multiple ZoneRules associated with the
841 // given year. For each transition, determine the startEpochSeconds,
842 // and the effective offset time.
843
844 // Determine the start date of the rule.
845 const MonthDay monthDay = calcStartDayOfMonth(
846 year, transition.month, transition.rule.onDayOfWeek(),
847 transition.rule.onDayOfMonth());
848
849 // Determine the offset of the 'atTimeSuffix'. The 'w' suffix
850 // requires the offset of the previous transition.
851 const int16_t prevTotalOffsetMinutes = calcRuleOffsetMinutes(
852 prevTransition->offsetMinutes + prevTransition->deltaMinutes,
853 transition.era.offsetSeconds() / kSecPerMin,
854 transition.rule.atTimeSuffix());
855
856 // startDateTime
857 const uint16_t minutes = transition.rule.atTimeSeconds() / 60;
858 const uint8_t atHour = minutes / 60;
859 const uint8_t atMinute = minutes % 60;
860 OffsetDateTime startDateTime = OffsetDateTime::forComponents(
861 year, monthDay.month, monthDay.day,
862 atHour, atMinute, 0 /*second*/,
863 TimeOffset::forMinutes(prevTotalOffsetMinutes));
864 transition.startEpochSeconds = startDateTime.toEpochSeconds();
865 }
866
867 prevTransition = &transition;
868 }
869 }
870
877 static int16_t calcRuleOffsetMinutes(int16_t prevTotalOffsetMinutes,
878 int16_t currentBaseOffsetMinutes, uint8_t atSuffix) {
879 if (atSuffix == basic::Info::ZoneContext::kSuffixW) {
880 return prevTotalOffsetMinutes;
881 } else if (atSuffix == basic::Info::ZoneContext::kSuffixS) {
882 return currentBaseOffsetMinutes;
883 } else { // 'u', 'g' or 'z'
884 return 0;
885 }
886 }
887
889 void calcAbbreviations() const {
890 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
891 logging::printf("calcAbbreviations():\n");
892 }
893
894 for (uint8_t i = 0; i < mNumTransitions; i++) {
895 Transition* transition = &mTransitions[i];
896 createAbbreviation(
897 transition->abbrev,
898 kAbbrevSize,
899 transition->era.format(),
900 transition->offsetMinutes * kSecPerMin,
901 transition->deltaMinutes * kSecPerMin,
902 transition->abbrev);
903 }
904 }
905
907 const Transition* findMatch(acetime_t epochSeconds) const {
908 const Transition* closestMatch = nullptr;
909 for (uint8_t i = 0; i < mNumTransitions; i++) {
910 const Transition* m = &mTransitions[i];
911 if (closestMatch == nullptr || m->startEpochSeconds <= epochSeconds) {
912 closestMatch = m;
913 }
914 }
915 return closestMatch;
916 }
917
918 private:
919 static const int32_t kSecPerMin = 60;
920
921 const typename D::ZoneInfoStore* mZoneInfoStore; // nullable
922 typename D::ZoneInfoBroker mZoneInfoBroker;
923
924 mutable uint8_t mNumTransitions = 0;
925 mutable Transition mTransitions[kMaxCacheEntries];
926 };
927
932 class BasicZoneProcessor: public BasicZoneProcessorTemplate<basic::Info> {
933
934 public:
936 static const uint8_t kTypeBasic = 3;
937
938 explicit BasicZoneProcessor(const basic::Info::ZoneInfo* zoneInfo = nullptr)
939 : BasicZoneProcessorTemplate<basic::Info>(
940 kTypeBasic, &mZoneInfoStore, (uintptr_t) zoneInfo)
941 {}
942
943 private:
944 basic::Info::ZoneInfoStore mZoneInfoStore;
945 };
946
947 } // namespace ace_time
948
949 #endif
ace_time::BasicZoneProcessorTemplate
An implementation of ZoneProcessor that supports a subset of the zones containing in the TZ Database.
Definition: BasicZoneProcessor.h:191
ace_time::BasicZoneProcessorTemplate::findByEpochSeconds
FindResult findByEpochSeconds(acetime_t epochSeconds) const override
Return the search results at given epochSeconds.
Definition: BasicZoneProcessor.h:284
ace_time::BasicZoneProcessorTemplate::log
void log() const
Used only for debugging.
Definition: BasicZoneProcessor.h:327
ace_time::BasicZoneProcessorTemplate::setZoneInfoStore
void setZoneInfoStore(const typename D::ZoneInfoStore *zoneInfoStore)
Set the zone info store at runtime.
Definition: BasicZoneProcessor.h:346
ace_time::BasicZoneProcessorTemplate::Transition
basic::TransitionTemplate< D > Transition
Exposed only for testing purposes.
Definition: BasicZoneProcessor.h:194
ace_time::BasicZoneProcessorTemplate::equalsZoneKey
bool equalsZoneKey(uintptr_t zoneKey) const override
Return true if ZoneProcessor is associated with the given opaque zoneKey.
Definition: BasicZoneProcessor.h:322
ace_time::BasicZoneProcessorTemplate::findByLocalDateTime
FindResult findByLocalDateTime(const LocalDateTime &ldt) const override
Return the search results at given LocalDateTime.
Definition: BasicZoneProcessor.h:232
ace_time::BasicZoneProcessorTemplate::getZoneId
uint32_t getZoneId() const override
Return the unique stable zoneId.
Definition: BasicZoneProcessor.h:200
ace_time::BasicZoneProcessorTemplate::printShortNameTo
void printShortNameTo(Print &printer) const override
Print a short human-readable identifier (e.g.
Definition: BasicZoneProcessor.h:303
ace_time::BasicZoneProcessorTemplate::printTargetNameTo
void printTargetNameTo(Print &printer) const override
Print the full identifier (e.g.
Definition: BasicZoneProcessor.h:307
ace_time::BasicZoneProcessorTemplate::printNameTo
void printNameTo(Print &printer) const override
Print a human-readable identifier (e.g.
Definition: BasicZoneProcessor.h:299
ace_time::BasicZoneProcessorTemplate::BasicZoneProcessorTemplate
BasicZoneProcessorTemplate(uint8_t type, const typename D::ZoneInfoStore *zoneInfoStore, uintptr_t zoneKey)
Constructor.
Definition: BasicZoneProcessor.h:363
ace_time::BasicZoneProcessorTemplate::setZoneKey
void setZoneKey(uintptr_t zoneKey) override
Set the opaque zoneKey of this object to a new value, reseting any internally cached information.
Definition: BasicZoneProcessor.h:313
ace_time::BasicZoneProcessorTemplate::isLink
bool isLink() const override
Return true if timezone is a Link entry pointing to a Zone entry.
Definition: BasicZoneProcessor.h:196
ace_time::BasicZoneProcessor
A specific implementation of BasicZoneProcessorTemplate that uses ZoneXxxBrokers which read from zone...
Definition: BasicZoneProcessor.h:932
ace_time::BasicZoneProcessor::kTypeBasic
static const uint8_t kTypeBasic
Unique TimeZone type identifier for BasicZoneProcessor.
Definition: BasicZoneProcessor.h:936
ace_time::Epoch::currentEpochYear
static int16_t currentEpochYear()
Get the current epoch year.
Definition: Epoch.h:27
ace_time::FindResult
Result of a search for transition at a specific epochSeconds or a specific LocalDateTime.
Definition: ZoneProcessor.h:23
ace_time::FindResult::stdOffsetSeconds
int32_t stdOffsetSeconds
STD offset of the resulting OffsetDateTime.
Definition: ZoneProcessor.h:79
ace_time::FindResult::dstOffsetSeconds
int32_t dstOffsetSeconds
DST offset of the resulting OffsetDateTime.
Definition: ZoneProcessor.h:82
ace_time::FindResult::reqDstOffsetSeconds
int32_t reqDstOffsetSeconds
DST offset of the Transition which matched the epochSeconds requested by findByEpochSeconds(),...
Definition: ZoneProcessor.h:108
ace_time::FindResult::abbrev
const char * abbrev
Pointer to the abbreviation stored in the transient Transition::abbrev variable.
Definition: ZoneProcessor.h:115
ace_time::FindResult::reqStdOffsetSeconds
int32_t reqStdOffsetSeconds
STD offset of the Transition which matched the epochSeconds requested by findByEpochSeconds(),...
Definition: ZoneProcessor.h:95
ace_time::FindResult::type
uint8_t type
Result of the findByEpochSeconds() or findByLocalDateTime() search methods.
Definition: ZoneProcessor.h:65
ace_time::LocalDateTime
Class that holds the date-time as the components (year, month, day, hour, minute, second) without reg...
Definition: LocalDateTime.h:30
ace_time::LocalDateTime::localDate
const LocalDate & localDate() const
Return the LocalDate.
Definition: LocalDateTime.h:201
ace_time::LocalDateTime::toEpochSeconds
acetime_t toEpochSeconds() const
Return seconds since the current AceTime epoch defined by Epoch::currentEpochYear().
Definition: LocalDateTime.h:230
ace_time::LocalDate::kMaxYear
static const int16_t kMaxYear
The largest year that is expected to be handled by LocalDate.
Definition: LocalDate.h:78
ace_time::LocalDate::kMinYear
static const int16_t kMinYear
The smallest year that is expected to be handled by LocalDate.
Definition: LocalDate.h:69
ace_time::LocalDate::forEpochSeconds
static LocalDate forEpochSeconds(acetime_t epochSeconds)
Factory method using the number of seconds since the current epoch year given by currentEpochYear().
Definition: LocalDate.h:205
ace_time::LocalDate::kInvalidYear
static const int16_t kInvalidYear
Sentinel year which indicates one or more of the following conditions:
Definition: LocalDate.h:58
ace_time::OffsetDateTime::forLocalDateTimeAndOffset
static OffsetDateTime forLocalDateTimeAndOffset(const LocalDateTime &localDateTime, TimeOffset timeOffset)
Factory method from LocalDateTime and TimeOffset.
Definition: OffsetDateTime.h:41
ace_time::OffsetDateTime::forComponents
static OffsetDateTime forComponents(int16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second, TimeOffset timeOffset, uint8_t fold=0)
Factory method using separated date, time, and UTC offset fields.
Definition: OffsetDateTime.h:61
ace_time::TimeOffset::forSeconds
static TimeOffset forSeconds(int32_t seconds)
Create TimeOffset from seconds from 00:00.
Definition: TimeOffset.h:96
ace_time::TimeOffset::forMinutes
static TimeOffset forMinutes(int16_t minutes)
Create TimeOffset from minutes from 00:00.
Definition: TimeOffset.h:91
ace_time::ZoneInfoLow::ZoneInfoStore
A storage object that creates an ZoneInfoBroker from a key that identifies the ZoneInfo.
Definition: ZoneInfoLow.h:807
ace_time::ZoneProcessor
Base interface for ZoneProcessor classes.
Definition: ZoneProcessor.h:140
ace_time::ZoneProcessor::mYear
int16_t mYear
Year that was used to calculate the transitions in the current cache.
Definition: ZoneProcessor.h:249
ace_time::ZoneProcessor::isFilled
bool isFilled(int16_t year) const
Check if the Transition cache is filled for the given year and current epochYear.
Definition: ZoneProcessor.h:229
ace_time::ZoneProcessor::mEpochYear
int16_t mEpochYear
Epoch year that was used to calculate the transitions in the current cache.
Definition: ZoneProcessor.h:255
common.h
Identifiers used by implementation code which need to be publically exported.
ace_time::kAbbrevSize
const uint8_t kAbbrevSize
Size of the c-string buffer needed to hold a time zone abbreviation.
Definition: common.h:44
ace_time::acetime_t
int32_t acetime_t
Type for the number of seconds from epoch.
Definition: common.h:24
ace_time::ZoneInfoLow::ZoneContext::kSuffixW
static const uint8_t kSuffixW
Represents 'w' or wall time.
Definition: ZoneInfoLow.h:88
ace_time::ZoneInfoLow::ZoneContext::kSuffixS
static const uint8_t kSuffixS
Represents 's' or standard time.
Definition: ZoneInfoLow.h:91
ace_time::ZoneInfoLow::ZoneInfo
Representation of a given time zone, implemented as an array of ZoneEra records.
Definition: ZoneInfoLow.h:324
ace_time::basic::TransitionTemplate
Data structure that defines the start of a specific UTC offset as described by the matching ZoneEra a...
Definition: BasicZoneProcessor.h:55
ace_time::basic::TransitionTemplate::startEpochSeconds
acetime_t startEpochSeconds
The calculated transition time of the given rule.
Definition: BasicZoneProcessor.h:75
ace_time::basic::TransitionTemplate::month
uint8_t month
Month of the transition.
Definition: BasicZoneProcessor.h:94
ace_time::basic::TransitionTemplate::rule
D::ZoneRuleBroker rule
The Zone transition rule that matched for the the given year.
Definition: BasicZoneProcessor.h:72
ace_time::basic::TransitionTemplate::deltaMinutes
int16_t deltaMinutes
The deltaMinutes from "standard time" at the start of transition.
Definition: BasicZoneProcessor.h:84
ace_time::basic::TransitionTemplate::log
void log() const
Used only for debugging.
Definition: BasicZoneProcessor.h:106
ace_time::basic::TransitionTemplate::abbrev
char abbrev[kAbbrevSize]
The calculated effective time zone abbreviation, e.g.
Definition: BasicZoneProcessor.h:103
ace_time::basic::TransitionTemplate::offsetMinutes
int16_t offsetMinutes
The standard time offset minutes at the start of transition, not including DST offset.
Definition: BasicZoneProcessor.h:81
ace_time::basic::TransitionTemplate::year
int16_t year
Year of the Transition.
Definition: BasicZoneProcessor.h:87
ace_time::basic::TransitionTemplate::era
D::ZoneEraBroker era
The ZoneEra that matched the given year.
Definition: BasicZoneProcessor.h:61