AceTime  1.11.7
Date and time classes for Arduino that support timezones from the TZ Database.
ExtendedZoneProcessor.h
1 /*
2  * MIT License
3  * Copyright (c) 2019 Brian T. Park
4  */
5 
6 #ifndef ACE_TIME_EXTENDED_ZONE_PROCESSOR_H
7 #define ACE_TIME_EXTENDED_ZONE_PROCESSOR_H
8 
9 #include <string.h> // memcpy()
10 #include <stdint.h> // uintptr_t
11 #include <AceCommon.h> // copyReplaceString()
12 #include "common/compat.h"
13 #include "internal/ZonePolicy.h"
14 #include "internal/ZoneInfo.h"
16 #include "common/logging.h"
17 #include "TimeOffset.h"
18 #include "LocalDate.h"
19 #include "OffsetDateTime.h"
20 #include "ZoneProcessor.h"
21 #include "local_date_mutation.h"
22 
23 #ifndef ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG
24 #define ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG 0
25 #endif
26 
27 class ExtendedZoneProcessorTest_compareEraToYearMonth;
28 class ExtendedZoneProcessorTest_compareEraToYearMonth2;
29 class ExtendedZoneProcessorTest_createMatchingEra;
30 class ExtendedZoneProcessorTest_findMatches_simple;
31 class ExtendedZoneProcessorTest_findMatches_named;
32 class ExtendedZoneProcessorTest_findCandidateTransitions;
33 class ExtendedZoneProcessorTest_createTransitionsFromNamedMatch;
34 class ExtendedZoneProcessorTest_getTransitionTime;
35 class ExtendedZoneProcessorTest_createTransitionForYearTiny;
36 class ExtendedZoneProcessorTest_normalizeDateTuple;
37 class ExtendedZoneProcessorTest_expandDateTuple;
38 class ExtendedZoneProcessorTest_calcInteriorYears;
39 class ExtendedZoneProcessorTest_getMostRecentPriorYear;
40 class ExtendedZoneProcessorTest_compareTransitionToMatchFuzzy;
41 class ExtendedZoneProcessorTest_compareTransitionToMatch;
42 class ExtendedZoneProcessorTest_processTransitionMatchStatus;
43 class ExtendedZoneProcessorTest_fixTransitionTimes_generateStartUntilTimes;
44 class ExtendedZoneProcessorTest_createAbbreviation;
45 class ExtendedZoneProcessorTest_setZoneKey;
46 class TransitionStorageTest_getFreeAgent;
47 class TransitionStorageTest_getFreeAgent2;
48 class TransitionStorageTest_addFreeAgentToActivePool;
49 class TransitionStorageTest_reservePrior;
50 class TransitionStorageTest_addPriorToCandidatePool;
51 class TransitionStorageTest_addFreeAgentToCandidatePool;
52 class TransitionStorageTest_setFreeAgentAsPriorIfValid;
53 class TransitionStorageTest_addActiveCandidatesToActivePool;
54 class TransitionStorageTest_findTransitionForDateTime;
55 class TransitionStorageTest_resetCandidatePool;
56 class TransitionValidation;
57 
58 class Print;
59 
60 namespace ace_time {
61 namespace extended {
62 
63 //---------------------------------------------------------------------------
64 
69 struct DateTuple {
70  DateTuple() = default;
71 
72  DateTuple(int8_t y, uint8_t mon, uint8_t d, int16_t min, uint8_t mod):
73  yearTiny(y), month(mon), day(d), suffix(mod), minutes(min) {}
74 
75  int8_t yearTiny; // [-127, 126], 127 will cause bugs
76  uint8_t month; // [1-12]
77  uint8_t day; // [1-31]
78  uint8_t suffix; // kSuffixS, kSuffixW, kSuffixU
79  int16_t minutes; // negative values allowed
80 
82  void log() const {
83  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
84  int hour = minutes / 60;
85  int minute = minutes - hour * 60;
86  char c = "wsu"[(suffix>>4)];
87  logging::printf("%04d-%02u-%02uT%02d:%02d%c",
88  yearTiny+LocalDate::kEpochYear, month, day, hour, minute, c);
89  }
90  }
91 };
92 
94 inline bool operator<(const DateTuple& a, const DateTuple& b) {
95  if (a.yearTiny < b.yearTiny) return true;
96  if (a.yearTiny > b.yearTiny) return false;
97  if (a.month < b.month) return true;
98  if (a.month > b.month) return false;
99  if (a.day < b.day) return true;
100  if (a.day > b.day) return false;
101  if (a.minutes < b.minutes) return true;
102  if (a.minutes > b.minutes) return false;
103  return false;
104 }
105 
106 inline bool operator>=(const DateTuple& a, const DateTuple& b) {
107  return ! (a < b);
108 }
109 
110 inline bool operator<=(const DateTuple& a, const DateTuple& b) {
111  return ! (b < a);
112 }
113 
114 inline bool operator>(const DateTuple& a, const DateTuple& b) {
115  return (b < a);
116 }
117 
119 inline bool operator==(const DateTuple& a, const DateTuple& b) {
120  return a.yearTiny == b.yearTiny
121  && a.month == b.month
122  && a.day == b.day
123  && a.minutes == b.minutes
124  && a.suffix == b.suffix;
125 }
126 
128 inline void normalizeDateTuple(DateTuple* dt) {
129  const int16_t kOneDayAsMinutes = 60 * 24;
130  if (dt->minutes <= -kOneDayAsMinutes) {
131  LocalDate ld = LocalDate::forTinyComponents(
132  dt->yearTiny, dt->month, dt->day);
133  local_date_mutation::decrementOneDay(ld);
134  dt->yearTiny = ld.yearTiny();
135  dt->month = ld.month();
136  dt->day = ld.day();
137  dt->minutes += kOneDayAsMinutes;
138  } else if (kOneDayAsMinutes <= dt->minutes) {
139  LocalDate ld = LocalDate::forTinyComponents(
140  dt->yearTiny, dt->month, dt->day);
141  local_date_mutation::incrementOneDay(ld);
142  dt->yearTiny = ld.yearTiny();
143  dt->month = ld.month();
144  dt->day = ld.day();
145  dt->minutes -= kOneDayAsMinutes;
146  } else {
147  // do nothing
148  }
149 }
150 
152 inline acetime_t subtractDateTuple(const DateTuple& a, const DateTuple& b) {
153  int32_t epochDaysA = LocalDate::forTinyComponents(
154  a.yearTiny, a.month, a.day).toEpochDays();
155  int32_t epochSecondsA = epochDaysA * 86400 + a.minutes * 60;
156 
157  int32_t epochDaysB = LocalDate::forTinyComponents(
158  b.yearTiny, b.month, b.day).toEpochDays();
159  int32_t epochSecondsB = epochDaysB * 86400 + b.minutes * 60;
160 
161  return epochSecondsA - epochSecondsB;
162 }
163 
164 //---------------------------------------------------------------------------
165 
168  int8_t yearTiny;
169  uint8_t month;
170 };
171 
178 template<typename ZEB>
185 
188 
190  ZEB era;
191 
194 
197 
200 
201  void log() const {
202  logging::printf("MatchingEra(");
203  logging::printf("start="); startDateTime.log();
204  logging::printf("; until="); untilDateTime.log();
205  logging::printf("; era=%c", (era.isNull()) ? '-' : '*');
206  logging::printf("; prevMatch=%c", (prevMatch) ? '*' : '-');
207  logging::printf(")");
208  }
209 };
210 
212 template <typename T>
213 void swap(T& a, T& b) {
214  T tmp = a;
215  a = b;
216  b = tmp;
217 }
218 
223 enum class MatchStatus : uint8_t {
224  kFarPast, // 0
225  kPrior, // 1
226  kExactMatch, // 2
227  kWithinMatch, // 3
228  kFarFuture, // 4
229 };
230 
231 inline bool isMatchStatusActive(MatchStatus status) {
232  return status == MatchStatus::kExactMatch
233  || status == MatchStatus::kWithinMatch
234  || status == MatchStatus::kPrior;
235 }
236 
237 //---------------------------------------------------------------------------
238 
271 template <typename ZEB, typename ZPB, typename ZRB>
273 
276 
282  ZRB rule;
283 
291 
292  union {
299 
305  };
306 
307  union {
314 
320  };
321 
322 #if ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG
327  DateTuple originalTransitionTime;
328 #endif
329 
331  acetime_t startEpochSeconds;
332 
340  int16_t offsetMinutes;
341 
343  int16_t deltaMinutes;
344 
346  char abbrev[internal::kAbbrevSize];
347 
349  char letterBuf[2];
350 
351  union {
359 
364  MatchStatus matchStatus;
365  };
366 
367  //-------------------------------------------------------------------------
368 
369  const char* format() const {
370  return match->era.format();
371  }
372 
378  const char* letter() const {
379  // RULES column is '-' or hh:mm, so return nullptr to indicate this.
380  if (rule.isNull()) {
381  return nullptr;
382  }
383 
384  // RULES point to a named rule, and LETTER is a single, printable character.
385  // Return the letterBuf which contains a NUL-terminated string containing
386  // the single character, as initialized in createTransitionForYearTiny().
387  char letter = rule.letter();
388  if (letter >= 32) {
389  return letterBuf;
390  }
391 
392  // RULES points to a named rule, and the LETTER is a string. The
393  // rule->letter is a non-printable number < 32, which is an index into
394  // a list of strings given by match->era->zonePolicy->letters[].
395  const ZPB policy = match->era.zonePolicy();
396  uint8_t numLetters = policy.numLetters();
397  if (letter >= numLetters) {
398  // This should never happen unless there is a programming error. If it
399  // does, return an empty string. (createTransitionForYearTiny() sets
400  // letterBuf to a NUL terminated empty string if rule->letter < 32)
401  return letterBuf;
402  }
403 
404  // Return the string at index 'rule->letter'.
405  return policy.letter(letter);
406  }
407 
409  void log() const {
410  logging::printf("Transition(");
411  if (sizeof(acetime_t) <= sizeof(int)) {
412  logging::printf("start=%d", startEpochSeconds);
413  } else {
414  logging::printf("start=%ld", startEpochSeconds);
415  }
416  logging::printf("; status=%d", matchStatus);
417  logging::printf("; UTC");
420  logging::printf("; tt="); transitionTime.log();
421  logging::printf("; tts="); transitionTimeS.log();
422  logging::printf("; ttu="); transitionTimeU.log();
423  if (rule.isNull()) {
424  logging::printf("; rule=-");
425  } else {
426  logging::printf("; rule=");
427  logging::printf("[%d,%d]",
428  rule.fromYearTiny() + LocalDate::kEpochYear,
429  rule.toYearTiny() + LocalDate::kEpochYear);
430  }
431  }
432 
434  static void logHourMinute(int16_t minutes) {
435  char sign;
436  if (minutes < 0) {
437  sign = '-';
438  minutes = -minutes;
439  } else {
440  sign = '+';
441  }
442  uint8_t hour = minutes / 60;
443  uint8_t minute = minutes - hour * 60;
444  logging::printf("%c%02u:%02u", sign, (unsigned) hour, (unsigned) minute);
445  }
446 };
447 
453 template <typename ZEB, typename ZPB, typename ZRB>
455  const TransitionTemplate<ZEB, ZPB, ZRB>* transition;
456  uint8_t fold; // 1 if in the overlap, otherwise 0
457 };
458 
464 template <typename ZEB, typename ZPB, typename ZRB>
466  static constexpr int8_t kStatusGap = 0; // no exact match
467  static constexpr int8_t kStatusExact = 1; // one exact match
468  static constexpr int8_t kStatusOverlap = 2; // two matches
469 
470  const TransitionTemplate<ZEB, ZPB, ZRB>* transition0; // fold==0
471  const TransitionTemplate<ZEB, ZPB, ZRB>* transition1; // fold==1
472  int8_t searchStatus;
473 };
474 
506 template<uint8_t SIZE, typename ZEB, typename ZPB, typename ZRB>
508  public:
514 
521 
528 
531 
538  void init() {
539  for (uint8_t i = 0; i < SIZE; i++) {
540  mTransitions[i] = &mPool[i];
541  }
542  mIndexPrior = 0;
543  mIndexCandidates = 0;
544  mIndexFree = 0;
545  }
546 
549  return mTransitions[mIndexPrior];
550  }
551 
561  mIndexCandidates = mIndexPrior;
562  mIndexFree = mIndexPrior;
563  }
564 
565  Transition** getCandidatePoolBegin() {
566  return &mTransitions[mIndexCandidates];
567  }
568  Transition** getCandidatePoolEnd() {
569  return &mTransitions[mIndexFree];
570  }
571 
572  Transition** getActivePoolBegin() {
573  return &mTransitions[0];
574  }
575  Transition** getActivePoolEnd() {
576  return &mTransitions[mIndexFree];
577  }
578 
585  if (mIndexFree < SIZE) {
586  // Allocate a free transition.
587  if (mIndexFree >= mAllocSize) {
588  mAllocSize = mIndexFree + 1;
589  }
590  return mTransitions[mIndexFree];
591  } else {
592  // No more transition available in the buffer, so just return the last
593  // one. This will probably cause a bug in the timezone calculations, but
594  // I think this is better than triggering undefined behavior by running
595  // off the end of the mTransitions buffer.
596  return mTransitions[SIZE - 1];
597  }
598  }
599 
608  if (mIndexFree >= SIZE) return;
609  mIndexFree++;
610  mIndexPrior = mIndexFree;
611  mIndexCandidates = mIndexFree;
612  }
613 
623  getFreeAgent(); // allocate a new Transition
624 
625  mIndexCandidates++;
626  mIndexFree++;
627  return &mTransitions[mIndexPrior];
628  }
629 
632  Transition* ft = mTransitions[mIndexFree];
633  Transition* prior = mTransitions[mIndexPrior];
634  if ((prior->isValidPrior && prior->transitionTime < ft->transitionTime)
635  || !prior->isValidPrior) {
636  ft->isValidPrior = true;
637  prior->isValidPrior = false;
638  swap(mTransitions[mIndexPrior], mTransitions[mIndexFree]);
639  }
640  }
641 
648  mIndexCandidates--;
649  }
650 
658  if (mIndexFree >= SIZE) return;
659 
660  // This implementation makes pair-wise swaps to shift the current
661  // Transition leftwards into its correctly sorted position. At first
662  // glance, this seem inefficient compared to the alternative
663  // implementation where we save the current Transition, then slide all the
664  // elements to the left by one position rightwards. However,
665  // MemoryBenchmark shows that this implementation is 46 bytes smaller on
666  // an AVR processor.
667  for (uint8_t i = mIndexFree; i > mIndexCandidates; i--) {
668  Transition* curr = mTransitions[i];
669  Transition* prev = mTransitions[i - 1];
670  if (curr->transitionTime >= prev->transitionTime) break;
671  mTransitions[i] = prev;
672  mTransitions[i - 1] = curr;
673  }
674  mIndexFree++;
675  }
676 
684  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
685  logging::printf("addActiveCandidatesToActivePool()\n");
686  }
687 
688  // Shift active candidates to the left into the Active pool.
689  uint8_t iActive = mIndexPrior;
690  uint8_t iCandidate = mIndexCandidates;
691  for (; iCandidate < mIndexFree; iCandidate++) {
692  if (isMatchStatusActive(mTransitions[iCandidate]->matchStatus)) {
693  if (iActive != iCandidate) {
694  // Must use swap(), because we are moving pointers instead of the
695  // actual Transition objects.
696  swap(mTransitions[iActive], mTransitions[iCandidate]);
697  }
698  ++iActive;
699  }
700  }
701 
702  mIndexPrior = iActive;
703  mIndexCandidates = iActive;
704  mIndexFree = iActive;
705 
706  return mTransitions[iActive - 1];
707  }
708 
718  const {
719  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
720  logging::printf(
721  "findTransitionForSeconds(): mIndexFree: %d\n", mIndexFree);
722  }
723 
724  const Transition* prevMatch = nullptr;
725  const Transition* match = nullptr;
726  for (uint8_t i = 0; i < mIndexFree; i++) {
727  const Transition* candidate = mTransitions[i];
728  if (candidate->startEpochSeconds > epochSeconds) break;
729  prevMatch = match;
730  match = candidate;
731  }
732  uint8_t fold = calculateFold(epochSeconds, match, prevMatch);
733  return MatchingTransition{ match, fold };
734  }
735 
736  static uint8_t calculateFold(
737  acetime_t epochSeconds,
738  const Transition* match,
739  const Transition* prevMatch) {
740 
741  if (match == nullptr) return 0;
742  if (prevMatch == nullptr) return 0;
743 
744  // Check if epochSeconds occurs during a "fall back" DST transition.
745  acetime_t overlapSeconds = subtractDateTuple(
746  prevMatch->untilDateTime, match->startDateTime);
747  if (overlapSeconds <= 0) return 0;
748  acetime_t secondsFromTransitionStart =
749  epochSeconds - match->startEpochSeconds;
750  if (secondsFromTransitionStart >= overlapSeconds) return 0;
751 
752  // EpochSeconds occurs within the "fall back" overlap.
753  return 1;
754  }
755 
762  const {
763  // Convert LocalDateTime to DateTuple.
764  DateTuple localDate{
765  ldt.yearTiny(),
766  ldt.month(),
767  ldt.day(),
768  (int16_t) (ldt.hour() * 60 + ldt.minute()),
770  };
771 
772  // Examine adjacent pairs of Transitions, looking for an exact match, gap,
773  // or overlap.
774  const Transition* prevCandidate = nullptr;
775  const Transition* candidate = nullptr;
776  int8_t searchStatus = TransitionResult::kStatusGap;
777  for (uint8_t i = 0; i < mIndexFree; i++) {
778  candidate = mTransitions[i];
779 
780  const DateTuple& startDateTime = candidate->startDateTime;
781  const DateTuple& untilDateTime = candidate->untilDateTime;
782  bool isExactMatch = (startDateTime <= localDate)
783  && (localDate < untilDateTime);
784 
785  if (isExactMatch) {
786  // Check for a previous exact match to detect an overlap.
787  if (searchStatus == TransitionResult::kStatusExact) {
788  searchStatus = TransitionResult::kStatusOverlap;
789  break;
790  }
791 
792  // Loop again to detect an overlap.
793  searchStatus = TransitionResult::kStatusExact;
794 
795  } else if (startDateTime > localDate) {
796  // Exit loop since no more candidate transition.
797  break;
798  }
799 
800  prevCandidate = candidate;
801 
802  // Set nullptr so that if the loop runs off the end of the list of
803  // Transitions, the candidate is marked as nullptr.
804  candidate = nullptr;
805  }
806 
807  // Check if the prev was an exact match, and clear the current to
808  // avoid confusion.
809  if (searchStatus == TransitionResult::kStatusExact) {
810  candidate = nullptr;
811  }
812 
813  // This should get optimized by RVO.
814  return TransitionResult{
815  prevCandidate,
816  candidate,
817  searchStatus,
818  };
819  }
820 
822  void log() const {
823  logging::printf("TransitionStorage: ");
824  logging::printf("nActives=%d", mIndexPrior);
825  logging::printf(", nPrior=%d", mIndexCandidates - mIndexPrior);
826  logging::printf(", nCandidates=%d", mIndexFree - mIndexCandidates);
827  logging::printf(", nFree=%d", SIZE - mIndexFree);
828  logging::printf("\n");
829 
830  if (mIndexPrior != 0) {
831  logging::printf(" Actives:\n");
832  for (uint8_t i = 0; i < mIndexPrior; i++) {
833  logging::printf(" ");
834  mTransitions[i]->log();
835  logging::printf("\n");
836  }
837  }
838  if (mIndexPrior != mIndexCandidates) {
839  logging::printf(" Prior: \n");
840  logging::printf(" ");
841  mTransitions[mIndexPrior]->log();
842  logging::printf("\n");
843  }
844  if (mIndexCandidates != mIndexFree) {
845  logging::printf(" Candidates:\n");
846  for (uint8_t i = mIndexCandidates; i < mIndexFree; i++) {
847  logging::printf(" ");
848  mTransitions[i]->log();
849  logging::printf("\n");
850  }
851  }
852  }
853 
855  void resetAllocSize() { mAllocSize = 0; }
856 
862  uint8_t getAllocSize() const { return mAllocSize; }
863 
864  private:
865  friend class ::TransitionStorageTest_getFreeAgent;
866  friend class ::TransitionStorageTest_getFreeAgent2;
867  friend class ::TransitionStorageTest_addFreeAgentToActivePool;
868  friend class ::TransitionStorageTest_reservePrior;
869  friend class ::TransitionStorageTest_addPriorToCandidatePool;
870  friend class ::TransitionStorageTest_addFreeAgentToCandidatePool;
871  friend class ::TransitionStorageTest_setFreeAgentAsPriorIfValid;
872  friend class ::TransitionStorageTest_addActiveCandidatesToActivePool;
873  friend class ::TransitionStorageTest_findTransitionForDateTime;
874  friend class ::TransitionStorageTest_resetCandidatePool;
875 
877  Transition* getTransition(uint8_t i) {
878  return mTransitions[i];
879  }
880 
881  Transition mPool[SIZE];
882  Transition* mTransitions[SIZE];
883  uint8_t mIndexPrior;
884  uint8_t mIndexCandidates;
885  uint8_t mIndexFree;
886 
888  uint8_t mAllocSize = 0;
889 };
890 
891 } // namespace extended
892 
924 template <typename BF, typename ZIB, typename ZEB, typename ZPB, typename ZRB>
926  public:
937  static const uint8_t kMaxTransitions = 8;
938 
941 
945 
949 
952 
956 
957  bool isLink() const override { return mZoneInfoBroker.isLink(); }
958 
959  uint32_t getZoneId(bool followLink = false) const override {
960  ZIB zib = (isLink() && followLink)
961  ? mZoneInfoBroker.targetZoneInfo()
962  : mZoneInfoBroker;
963  return zib.zoneId();
964  }
965 
966  TimeOffset getUtcOffset(acetime_t epochSeconds) const override {
967  bool success = initForEpochSeconds(epochSeconds);
968  if (!success) return TimeOffset::forError();
969  MatchingTransition matchingTransition =
970  mTransitionStorage.findTransitionForSeconds(epochSeconds);
971  const Transition* transition = matchingTransition.transition;
972  return (transition)
974  transition->offsetMinutes + transition->deltaMinutes)
976  }
977 
978  TimeOffset getDeltaOffset(acetime_t epochSeconds) const override {
979  bool success = initForEpochSeconds(epochSeconds);
980  if (!success) return TimeOffset::forError();
981  MatchingTransition matchingTransition =
982  mTransitionStorage.findTransitionForSeconds(epochSeconds);
983  const Transition* transition = matchingTransition.transition;
984  return (transition)
985  ? TimeOffset::forMinutes(transition->deltaMinutes)
987  }
988 
989  const char* getAbbrev(acetime_t epochSeconds) const override {
990  bool success = initForEpochSeconds(epochSeconds);
991  if (!success) return "";
992  MatchingTransition matchingTransition =
993  mTransitionStorage.findTransitionForSeconds(epochSeconds);
994  const Transition* transition = matchingTransition.transition;
995  return (transition) ? transition->abbrev : "";
996  }
997 
1017  OffsetDateTime getOffsetDateTime(const LocalDateTime& ldt) const override {
1018  bool success = initForYear(ldt.year());
1019  if (! success) {
1020  return OffsetDateTime::forError();
1021  }
1022 
1023  // Find the Transition(s) in the gap or overlap.
1024  TransitionResult result =
1025  mTransitionStorage.findTransitionForDateTime(ldt);
1026 
1027  // Extract the appropriate Transition, depending on the request ldt.fold
1028  // and the result.searchStatus.
1029  bool needsNormalization = false;
1030  const Transition* transition;
1031  if (result.searchStatus == TransitionResult::kStatusExact) {
1032  transition = result.transition0;
1033  } else {
1034  if (result.transition0 == nullptr || result.transition1 == nullptr) {
1035  // ldt was far past or far future, and didn't match anything.
1036  transition = nullptr;
1037  } else {
1038  needsNormalization =
1039  (result.searchStatus == TransitionResult::kStatusGap);
1040  transition = (ldt.fold() == 0)
1041  ? result.transition0
1042  : result.transition1;
1043  }
1044  }
1045 
1046  if (! transition) {
1047  return OffsetDateTime::forError();
1048  }
1049 
1051  transition->offsetMinutes + transition->deltaMinutes);
1052  auto odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset);
1053 
1054  if (needsNormalization) {
1055  acetime_t epochSeconds = odt.toEpochSeconds();
1056 
1057  // If in the gap, normalization means that we convert to epochSeconds
1058  // then perform another search through the Transitions, then use
1059  // that new Transition to convert the epochSeconds to OffsetDateTime. It
1060  // turns out that this process identical to just using the other
1061  // Transition returned in TransitionResult above.
1062  const Transition* otherTransition = (ldt.fold() == 0)
1063  ? result.transition1
1064  : result.transition0;
1065  TimeOffset otherOffset = TimeOffset::forMinutes(
1066  otherTransition->offsetMinutes + otherTransition->deltaMinutes);
1067  odt = OffsetDateTime::forEpochSeconds(epochSeconds, otherOffset);
1068 
1069  // Invert the fold.
1070  // 1) The normalization process causes the LocalDateTime to jump to the
1071  // other transition.
1072  // 2) Provides a user-accessible indicator that a gap normalization was
1073  // performed.
1074  odt.fold(1 - ldt.fold());
1075  }
1076 
1077  return odt;
1078  }
1079 
1088  OffsetDateTime getOffsetDateTime(acetime_t epochSeconds) const override {
1089  bool success = initForEpochSeconds(epochSeconds);
1090  if (!success) return OffsetDateTime::forError();
1091 
1092  MatchingTransition matchingTransition =
1093  mTransitionStorage.findTransitionForSeconds(epochSeconds);
1094  const Transition* transition = matchingTransition.transition;
1095  TimeOffset timeOffset = (transition)
1097  transition->offsetMinutes + transition->deltaMinutes)
1100  epochSeconds, timeOffset, matchingTransition.fold);
1101  }
1102 
1103  void printNameTo(Print& printer, bool followLink = false) const override {
1104  ZIB zib = (isLink() && followLink)
1105  ? mZoneInfoBroker.targetZoneInfo()
1106  : mZoneInfoBroker;
1107  zib.printNameTo(printer);
1108  }
1109 
1110  void printShortNameTo(Print& printer, bool followLink = false)
1111  const override {
1112  ZIB zib = (isLink() && followLink)
1113  ? mZoneInfoBroker.targetZoneInfo()
1114  : mZoneInfoBroker;
1115  zib.printShortNameTo(printer);
1116  }
1117 
1119  void log() const {
1120  logging::printf("ExtendedZoneProcessor\n");
1121  logging::printf(" mYear: %d\n", mYear);
1122  logging::printf(" mNumMatches: %d\n", mNumMatches);
1123  for (int i = 0; i < mNumMatches; i++) {
1124  logging::printf(" Match %d: ", i);
1125  mMatches[i].log();
1126  logging::printf("\n");
1127  }
1128  mTransitionStorage.log();
1129  }
1130 
1133  mTransitionStorage.resetAllocSize();
1134  }
1135 
1137  uint8_t getTransitionAllocSize() const {
1138  return mTransitionStorage.getAllocSize();
1139  }
1140 
1141  void setZoneKey(uintptr_t zoneKey) override {
1142  if (! mBrokerFactory) return;
1143  if (mZoneInfoBroker.equals(zoneKey)) return;
1144 
1145  mZoneInfoBroker = mBrokerFactory->createZoneInfoBroker(zoneKey);
1146  mYear = 0;
1147  mIsFilled = false;
1148  mNumMatches = 0;
1149  resetTransitionAllocSize(); // clear the alloc size for new zone
1150  }
1151 
1152  bool equalsZoneKey(uintptr_t zoneKey) const override {
1153  return mZoneInfoBroker.equals(zoneKey);
1154  }
1155 
1162  void setBrokerFactory(const BF* brokerFactory) {
1163  mBrokerFactory = brokerFactory;
1164  }
1165 
1171  bool initForEpochSeconds(acetime_t epochSeconds) const {
1172  LocalDate ld = LocalDate::forEpochSeconds(epochSeconds);
1173  return initForYear(ld.year());
1174  }
1175 
1181  bool initForYear(int16_t year) const {
1182  if (isFilled(year)) return true;
1183  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1184  logging::printf("initForYear(): %d\n", year);
1185  }
1186 
1187  mYear = year;
1188  mNumMatches = 0; // clear cache
1189  mTransitionStorage.init();
1190 
1191  if (year < mZoneInfoBroker.zoneContext()->startYear - 1
1192  || mZoneInfoBroker.zoneContext()->untilYear < year) {
1193  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1194  logging::printf(
1195  "initForYear(): Year %d out of valid range [%d, %d)\n",
1196  year,
1197  mZoneInfoBroker.zoneContext()->startYear,
1198  mZoneInfoBroker.zoneContext()->untilYear);
1199  }
1200  return false;
1201  }
1202 
1203  extended::YearMonthTuple startYm = {
1204  (int8_t) (year - LocalDate::kEpochYear - 1), 12 };
1205  extended::YearMonthTuple untilYm = {
1206  (int8_t) (year - LocalDate::kEpochYear + 1), 2 };
1207 
1208  // Step 1. The equivalent steps for the Python version are in the
1209  // AceTimePython project, under
1210  // zone_processor.ZoneProcessor.init_for_year().
1211  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1212  logging::printf("==== Step 1: findMatches()\n");
1213  }
1214  mNumMatches = findMatches(mZoneInfoBroker, startYm, untilYm, mMatches,
1215  kMaxMatches);
1216  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
1217 
1218  // Step 2
1219  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1220  logging::printf("==== Step 2: createTransitions()\n");
1221  }
1222  createTransitions(mTransitionStorage, mMatches, mNumMatches);
1223  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
1224 
1225  // Step 3
1226  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1227  logging::printf("==== Step 3: fixTransitionTimes()\n");
1228  }
1229  Transition** begin = mTransitionStorage.getActivePoolBegin();
1230  Transition** end = mTransitionStorage.getActivePoolEnd();
1231  fixTransitionTimes(begin, end);
1232  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
1233 
1234  // Step 4
1235  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1236  logging::printf("==== Step 4: generateStartUntilTimes()\n");
1237  }
1238  generateStartUntilTimes(begin, end);
1239  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
1240 
1241  // Step 5
1242  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1243  logging::printf("==== Step 5: calcAbbreviations()\n");
1244  }
1245  calcAbbreviations(begin, end);
1246  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
1247 
1248  mIsFilled = true;
1249  return true;
1250  }
1251 
1252  protected:
1263  uint8_t type,
1264  const BF* brokerFactory /*nullable*/,
1265  uintptr_t zoneKey
1266  ) :
1267  ZoneProcessor(type),
1268  mBrokerFactory(brokerFactory)
1269  {
1270  setZoneKey(zoneKey);
1271  }
1272 
1273  private:
1274  friend class ::ExtendedZoneProcessorTest_compareEraToYearMonth;
1275  friend class ::ExtendedZoneProcessorTest_compareEraToYearMonth2;
1276  friend class ::ExtendedZoneProcessorTest_createMatchingEra;
1277  friend class ::ExtendedZoneProcessorTest_findMatches_simple;
1278  friend class ::ExtendedZoneProcessorTest_findMatches_named;
1279  friend class ::ExtendedZoneProcessorTest_findCandidateTransitions;
1280  friend class ::ExtendedZoneProcessorTest_createTransitionsFromNamedMatch;
1281  friend class ::ExtendedZoneProcessorTest_getTransitionTime;
1282  friend class ::ExtendedZoneProcessorTest_createTransitionForYearTiny;
1283  friend class ::ExtendedZoneProcessorTest_normalizeDateTuple;
1284  friend class ::ExtendedZoneProcessorTest_expandDateTuple;
1285  friend class ::ExtendedZoneProcessorTest_calcInteriorYears;
1286  friend class ::ExtendedZoneProcessorTest_getMostRecentPriorYear;
1287  friend class ::ExtendedZoneProcessorTest_compareTransitionToMatchFuzzy;
1288  friend class ::ExtendedZoneProcessorTest_compareTransitionToMatch;
1289  friend class ::ExtendedZoneProcessorTest_processTransitionMatchStatus;
1290  friend class ::ExtendedZoneProcessorTest_fixTransitionTimes_generateStartUntilTimes;
1291  friend class ::ExtendedZoneProcessorTest_createAbbreviation;
1292  friend class ::ExtendedZoneProcessorTest_setZoneKey;
1293  friend class ::TransitionStorageTest_findTransitionForDateTime;
1294  friend class ::TransitionValidation;
1295 
1296  // Disable copy constructor and assignment operator.
1298  const ExtendedZoneProcessorTemplate&) = delete;
1299  ExtendedZoneProcessorTemplate& operator=(
1300  const ExtendedZoneProcessorTemplate&) = delete;
1301 
1306  static const uint8_t kMaxMatches = 4;
1307 
1312  static const uint8_t kMaxInteriorYears = 4;
1313 
1314  bool equals(const ZoneProcessor& other) const override {
1315  return mZoneInfoBroker.equals(
1316  ((const ExtendedZoneProcessorTemplate&) other).mZoneInfoBroker);
1317  }
1318 
1320  bool isFilled(int16_t year) const {
1321  return mIsFilled && (year == mYear);
1322  }
1323 
1331  static uint8_t findMatches(
1332  const ZIB& zoneInfo,
1333  const extended::YearMonthTuple& startYm,
1334  const extended::YearMonthTuple& untilYm,
1335  MatchingEra* matches,
1336  uint8_t maxMatches
1337  ) {
1338  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1339  logging::printf("findMatches()\n");
1340  }
1341  uint8_t iMatch = 0;
1342  MatchingEra* prevMatch = nullptr;
1343  for (uint8_t iEra = 0; iEra < zoneInfo.numEras(); iEra++) {
1344  const ZEB era = zoneInfo.era(iEra);
1345  if (eraOverlapsInterval(prevMatch, era, startYm, untilYm)) {
1346  if (iMatch < maxMatches) {
1347  matches[iMatch] = createMatchingEra(
1348  prevMatch, era, startYm, untilYm);
1349  prevMatch = &matches[iMatch];
1350  iMatch++;
1351  }
1352  }
1353  }
1354  return iMatch;
1355  }
1356 
1368  static bool eraOverlapsInterval(
1369  const MatchingEra* prevMatch,
1370  const ZEB& era,
1371  const extended::YearMonthTuple& startYm,
1372  const extended::YearMonthTuple& untilYm) {
1373  return (prevMatch == nullptr || compareEraToYearMonth(
1374  prevMatch->era, untilYm.yearTiny, untilYm.month) < 0)
1375  && compareEraToYearMonth(era, startYm.yearTiny, startYm.month) > 0;
1376  }
1377 
1379  static int8_t compareEraToYearMonth(const ZEB& era,
1380  int8_t yearTiny, uint8_t month) {
1381  if (era.untilYearTiny() < yearTiny) return -1;
1382  if (era.untilYearTiny() > yearTiny) return 1;
1383  if (era.untilMonth() < month) return -1;
1384  if (era.untilMonth() > month) return 1;
1385  if (era.untilDay() > 1) return 1;
1386  //if (era.untilTimeMinutes() < 0) return -1; // never possible
1387  if (era.untilTimeMinutes() > 0) return 1;
1388  return 0;
1389  }
1390 
1397  static MatchingEra createMatchingEra(
1398  MatchingEra* prevMatch,
1399  const ZEB& era,
1400  const extended::YearMonthTuple& startYm,
1401  const extended::YearMonthTuple& untilYm) {
1402 
1403  // If prevMatch is null, set startDate to be earlier than all valid
1404  // ZoneEra.
1405  extended::DateTuple startDate = (prevMatch == nullptr)
1406  ? extended::DateTuple{
1408  1,
1409  1,
1410  0,
1412  }
1413  : extended::DateTuple{
1414  prevMatch->era.untilYearTiny(),
1415  prevMatch->era.untilMonth(),
1416  prevMatch->era.untilDay(),
1417  (int16_t) prevMatch->era.untilTimeMinutes(),
1418  prevMatch->era.untilTimeSuffix()
1419  };
1420  extended::DateTuple lowerBound{
1421  startYm.yearTiny,
1422  startYm.month,
1423  1,
1424  0,
1426  };
1427  if (startDate < lowerBound) {
1428  startDate = lowerBound;
1429  }
1430 
1431  extended::DateTuple untilDate{
1432  era.untilYearTiny(),
1433  era.untilMonth(),
1434  era.untilDay(),
1435  (int16_t) era.untilTimeMinutes(),
1436  era.untilTimeSuffix()
1437  };
1438  extended::DateTuple upperBound{
1439  untilYm.yearTiny,
1440  untilYm.month,
1441  1,
1442  0,
1444  };
1445  if (upperBound < untilDate) {
1446  untilDate = upperBound;
1447  }
1448 
1449  return {startDate, untilDate, era, prevMatch, 0, 0};
1450  }
1451 
1456  static void createTransitions(
1457  TransitionStorage& transitionStorage,
1458  MatchingEra* matches,
1459  uint8_t numMatches) {
1460  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1461  logging::printf("createTransitions()\n");
1462  }
1463 
1464  for (uint8_t i = 0; i < numMatches; i++) {
1465  createTransitionsForMatch(transitionStorage, &matches[i]);
1466  }
1467  }
1468 
1470  static void createTransitionsForMatch(
1471  TransitionStorage& transitionStorage,
1472  MatchingEra* match) {
1473  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1474  logging::printf("== createTransitionsForMatch()\n");
1475  }
1476  const ZPB policy = match->era.zonePolicy();
1477  if (policy.isNull()) {
1478  createTransitionsFromSimpleMatch(transitionStorage, match);
1479  } else {
1480  createTransitionsFromNamedMatch(transitionStorage, match);
1481  }
1482  }
1483 
1484  // Step 2A
1485  static void createTransitionsFromSimpleMatch(
1486  TransitionStorage& transitionStorage,
1487  MatchingEra* match) {
1488  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1489  logging::printf("== createTransitionsFromSimpleMatch()\n");
1490  }
1491 
1492  Transition* freeTransition = transitionStorage.getFreeAgent();
1493  createTransitionForYearTiny(freeTransition, 0 /*not used*/,
1494  ZRB() /*rule*/, match);
1495  freeTransition->matchStatus = extended::MatchStatus::kExactMatch;
1496  match->lastOffsetMinutes = freeTransition->offsetMinutes;
1497  match->lastDeltaMinutes = freeTransition->deltaMinutes;
1498  transitionStorage.addFreeAgentToActivePool();
1499  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1500  freeTransition->log();
1501  logging::printf("\n");
1502  }
1503  }
1504 
1505  // Step 2B
1506  static void createTransitionsFromNamedMatch(
1507  TransitionStorage& transitionStorage,
1508  MatchingEra* match) {
1509  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1510  logging::printf("== createTransitionsFromNamedMatch()\n");
1511  }
1512 
1513  transitionStorage.resetCandidatePool();
1514  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1515  match->log(); logging::printf("\n");
1516  }
1517 
1518  // Pass 1: Find candidate transitions using whole years.
1519  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1520  logging::printf("---- Pass 1: findCandidateTransitions()\n");
1521  }
1522  findCandidateTransitions(transitionStorage, match);
1523  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1524  transitionStorage.log();
1525  }
1526 
1527  // Pass 2: Fix the transitions times, converting 's' and 'u' into 'w'
1528  // uniformly.
1529  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1530  logging::printf("---- Pass 2: fixTransitionTimes()\n");
1531  }
1532  fixTransitionTimes(
1533  transitionStorage.getCandidatePoolBegin(),
1534  transitionStorage.getCandidatePoolEnd());
1535 
1536  // Pass 3: Select only those Transitions which overlap with the actual
1537  // start and until times of the MatchingEra.
1538  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1539  logging::printf("---- Pass 3: selectActiveTransitions()\n");
1540  }
1541  selectActiveTransitions(
1542  transitionStorage.getCandidatePoolBegin(),
1543  transitionStorage.getCandidatePoolEnd());
1544  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1545  transitionStorage.log();
1546  }
1547  Transition* lastTransition =
1548  transitionStorage.addActiveCandidatesToActivePool();
1549  match->lastOffsetMinutes = lastTransition->offsetMinutes;
1550  match->lastDeltaMinutes = lastTransition->deltaMinutes;
1551  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1552  transitionStorage.log();
1553  }
1554  }
1555 
1556  // Step 2B: Pass 1
1557  static void findCandidateTransitions(
1558  TransitionStorage& transitionStorage,
1559  const MatchingEra* match) {
1560  using extended::MatchStatus;
1561 
1562  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1563  logging::printf("findCandidateTransitions(): \n");
1564  match->log();
1565  logging::printf("\n");
1566  }
1567  const ZPB policy = match->era.zonePolicy();
1568  uint8_t numRules = policy.numRules();
1569  int8_t startY = match->startDateTime.yearTiny;
1570  int8_t endY = match->untilDateTime.yearTiny;
1571 
1572  // The prior is referenced through a handle (i.e. pointer to pointer)
1573  // because the actual pointer to the prior could change through the
1574  // transitionStorage.setFreeAgentAsPriorIfValid() method.
1575  Transition** prior = transitionStorage.reservePrior();
1576  (*prior)->isValidPrior = false; // indicates "no prior transition"
1577  for (uint8_t r = 0; r < numRules; r++) {
1578  const ZRB rule = policy.rule(r);
1579 
1580  // Add Transitions for interior years
1581  int8_t interiorYears[kMaxInteriorYears];
1582  uint8_t numYears = calcInteriorYears(interiorYears, kMaxInteriorYears,
1583  rule.fromYearTiny(), rule.toYearTiny(), startY, endY);
1584  for (uint8_t y = 0; y < numYears; y++) {
1585  int8_t yearTiny = interiorYears[y];
1586  Transition* t = transitionStorage.getFreeAgent();
1587  createTransitionForYearTiny(t, yearTiny, rule, match);
1588  MatchStatus status = compareTransitionToMatchFuzzy(t, match);
1589  if (status == MatchStatus::kPrior) {
1590  transitionStorage.setFreeAgentAsPriorIfValid();
1591  } else if (status == MatchStatus::kWithinMatch) {
1592  transitionStorage.addFreeAgentToCandidatePool();
1593  } else {
1594  // Must be kFarFuture.
1595  // Do nothing, allowing the free agent to be reused.
1596  }
1597  }
1598 
1599  // Add Transition for prior year
1600  int8_t priorYearTiny = getMostRecentPriorYear(
1601  rule.fromYearTiny(), rule.toYearTiny(), startY, endY);
1602  if (priorYearTiny != LocalDate::kInvalidYearTiny) {
1603  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1604  logging::printf(
1605  "findCandidateTransitions(): priorYear: %d\n",
1606  priorYearTiny + LocalDate::kEpochYear);
1607  }
1608  Transition* t = transitionStorage.getFreeAgent();
1609  createTransitionForYearTiny(t, priorYearTiny, rule, match);
1610  transitionStorage.setFreeAgentAsPriorIfValid();
1611  }
1612  }
1613 
1614  // Add the reserved prior into the Candidate pool only if 'isValidPrior'
1615  // is true.
1616  if ((*prior)->isValidPrior) {
1617  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1618  logging::printf(
1619  "findCandidateTransitions(): adding prior to Candidate pool\n");
1620  (*prior)->log();
1621  logging::printf("\n");
1622  }
1623  transitionStorage.addPriorToCandidatePool();
1624  }
1625  }
1626 
1646  static uint8_t calcInteriorYears(
1647  int8_t* interiorYears,
1648  uint8_t maxInteriorYears,
1649  int8_t fromYear, int8_t toYear,
1650  int8_t startYear, int8_t endYear) {
1651  uint8_t i = 0;
1652  for (int8_t year = startYear; year <= endYear; year++) {
1653  if (fromYear <= year && year <= toYear) {
1654  interiorYears[i] = year;
1655  i++;
1656  if (i >= maxInteriorYears) break;
1657  }
1658  }
1659  return i;
1660  }
1661 
1668  static void createTransitionForYearTiny(
1669  Transition* t,
1670  int8_t yearTiny,
1671  const ZRB& rule,
1672  const MatchingEra* match) {
1673  t->match = match;
1674  t->rule = rule;
1675  t->offsetMinutes = match->era.offsetMinutes();
1676  t->letterBuf[0] = '\0';
1677 
1678  if (! rule.isNull()) {
1679  t->transitionTime = getTransitionTime(yearTiny, rule);
1680  t->deltaMinutes = rule.deltaMinutes();
1681 
1682  char letter = rule.letter();
1683  if (letter >= 32) {
1684  // If LETTER is a '-', treat it the same as an empty string.
1685  if (letter != '-') {
1686  t->letterBuf[0] = letter;
1687  t->letterBuf[1] = '\0';
1688  }
1689  } else {
1690  // rule->letter is a long string, so is referenced as an offset index
1691  // into the ZonePolicy.letters array. The string cannot fit in
1692  // letterBuf, so will be retrieved by the letter() method below.
1693  }
1694  } else {
1695  // Create a Transition using the MatchingEra for the transitionTime.
1696  // Used for simple MatchingEra.
1697  t->transitionTime = match->startDateTime;
1698  t->deltaMinutes = match->era.deltaMinutes();
1699  }
1700  }
1701 
1714  static int8_t getMostRecentPriorYear(
1715  int8_t fromYear, int8_t toYear,
1716  int8_t startYear, int8_t endYear) {
1717 
1718  (void) endYear; // disable compiler warnings
1719 
1720  if (fromYear < startYear) {
1721  if (toYear < startYear) {
1722  return toYear;
1723  } else {
1724  return startYear - 1;
1725  }
1726  } else {
1728  }
1729  }
1730 
1735  static extended::DateTuple getTransitionTime(
1736  int8_t yearTiny, const ZRB& rule) {
1737 
1738  internal::MonthDay monthDay = internal::calcStartDayOfMonth(
1739  yearTiny + LocalDate::kEpochYear,
1740  rule.inMonth(),
1741  rule.onDayOfWeek(),
1742  rule.onDayOfMonth());
1743  return {
1744  yearTiny,
1745  monthDay.month,
1746  monthDay.day,
1747  (int16_t) rule.atTimeMinutes(),
1748  rule.atTimeSuffix()
1749  };
1750  }
1751 
1762  static extended::MatchStatus compareTransitionToMatchFuzzy(
1763  const Transition* t, const MatchingEra* match) {
1764  using extended::MatchStatus;
1765 
1766  int16_t ttMonths = t->transitionTime.yearTiny * 12
1767  + t->transitionTime.month;
1768 
1769  int16_t matchStartMonths = match->startDateTime.yearTiny * 12
1770  + match->startDateTime.month;
1771  if (ttMonths < matchStartMonths - 1) return MatchStatus::kPrior;
1772 
1773  int16_t matchUntilMonths = match->untilDateTime.yearTiny * 12
1774  + match->untilDateTime.month;
1775  if (matchUntilMonths + 2 <= ttMonths) return MatchStatus::kFarFuture;
1776 
1777  return MatchStatus::kWithinMatch;
1778  }
1779 
1788  static void fixTransitionTimes(Transition** begin, Transition** end) {
1789  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1790  logging::printf("fixTransitionTimes(): START; #transitions=%d\n",
1791  (int) (end - begin));
1792  printTransitions(begin, end);
1793  }
1794 
1795  // extend first Transition to -infinity
1796  Transition* prev = *begin;
1797 
1798  for (Transition** iter = begin; iter != end; ++iter) {
1799  Transition* curr = *iter;
1800  expandDateTuple(
1801  &curr->transitionTime,
1802  prev->offsetMinutes,
1803  prev->deltaMinutes,
1804  &curr->transitionTime,
1805  &curr->transitionTimeS,
1806  &curr->transitionTimeU);
1807  prev = curr;
1808  }
1809  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1810  logging::printf("fixTransitionTimes(): FIXED\n");
1811  printTransitions(begin, end);
1812  logging::printf("fixTransitionTimes(): END\n");
1813  }
1814  }
1815 
1816  #ifdef ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG
1817  static void printTransitions(Transition** begin, Transition** end) {
1818  for (Transition** iter = begin; iter != end; ++iter) {
1819  (*iter)->log();
1820  logging::printf("\n");
1821  }
1822  }
1823  #endif
1824 
1830  static void expandDateTuple(
1831  const extended::DateTuple* tt,
1832  int16_t offsetMinutes,
1833  int16_t deltaMinutes,
1834  extended::DateTuple* ttw,
1835  extended::DateTuple* tts,
1836  extended::DateTuple* ttu) {
1837 
1838  if (tt->suffix == internal::ZoneContext::kSuffixS) {
1839  *tts = *tt;
1840  *ttu = {tt->yearTiny, tt->month, tt->day,
1841  (int16_t) (tt->minutes - offsetMinutes),
1843  *ttw = {tt->yearTiny, tt->month, tt->day,
1844  (int16_t) (tt->minutes + deltaMinutes),
1846  } else if (tt->suffix == internal::ZoneContext::kSuffixU) {
1847  *ttu = *tt;
1848  *tts = {tt->yearTiny, tt->month, tt->day,
1849  (int16_t) (tt->minutes + offsetMinutes),
1851  *ttw = {tt->yearTiny, tt->month, tt->day,
1852  (int16_t) (tt->minutes + (offsetMinutes + deltaMinutes)),
1854  } else {
1855  // Explicit set the suffix to 'w' in case it was something else.
1856  *ttw = *tt;
1857  ttw->suffix = internal::ZoneContext::kSuffixW;
1858  *tts = {tt->yearTiny, tt->month, tt->day,
1859  (int16_t) (tt->minutes - deltaMinutes),
1861  *ttu = {tt->yearTiny, tt->month, tt->day,
1862  (int16_t) (tt->minutes - (deltaMinutes + offsetMinutes)),
1864  }
1865 
1866  extended::normalizeDateTuple(ttw);
1867  extended::normalizeDateTuple(tts);
1868  extended::normalizeDateTuple(ttu);
1869  }
1870 
1875  static void selectActiveTransitions(Transition** begin, Transition** end) {
1876  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1877  logging::printf("selectActiveTransitions(): #candidates: %d\n",
1878  (int) (end - begin));
1879  }
1880 
1881  Transition* prior = nullptr;
1882  for (Transition** iter = begin; iter != end; ++iter) {
1883  Transition* transition = *iter;
1884  processTransitionMatchStatus(transition, &prior);
1885  }
1886 
1887  // If the latest prior transition is found, shift it to start at the
1888  // startDateTime of the current match.
1889  if (prior) {
1890  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1891  logging::printf(
1892  "selectActiveTransitions(): found latest prior\n");
1893  }
1894  #if ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG
1895  prior->originalTransitionTime = prior->transitionTime;
1896  #endif
1897  prior->transitionTime = prior->match->startDateTime;
1898  }
1899  }
1900 
1907  static void processTransitionMatchStatus(
1908  Transition* transition,
1909  Transition** prior) {
1910  using extended::MatchStatus;
1911 
1912  MatchStatus status = compareTransitionToMatch(
1913  transition, transition->match);
1914  transition->matchStatus = status;
1915 
1916  if (status == MatchStatus::kExactMatch) {
1917  if (*prior) {
1918  (*prior)->matchStatus = MatchStatus::kFarPast;
1919  }
1920  (*prior) = transition;
1921  } else if (status == MatchStatus::kPrior) {
1922  if (*prior) {
1923  if ((*prior)->transitionTimeU <= transition->transitionTimeU) {
1924  (*prior)->matchStatus = MatchStatus::kFarPast;
1925  (*prior) = transition;
1926  } else {
1927  transition->matchStatus = MatchStatus::kFarPast;
1928  }
1929  } else {
1930  (*prior) = transition;
1931  }
1932  }
1933  }
1934 
1943  static extended::MatchStatus compareTransitionToMatch(
1944  const Transition* transition,
1945  const MatchingEra* match) {
1946 
1947  // Find the previous Match offsets.
1948  int16_t prevMatchOffsetMinutes;
1949  int16_t prevMatchDeltaMinutes;
1950  if (match->prevMatch) {
1951  prevMatchOffsetMinutes = match->prevMatch->lastOffsetMinutes;
1952  prevMatchDeltaMinutes = match->prevMatch->lastDeltaMinutes;
1953  } else {
1954  prevMatchOffsetMinutes = match->era.offsetMinutes();
1955  prevMatchDeltaMinutes = 0;
1956  }
1957 
1958  // Expand start times.
1959  extended::DateTuple stw;
1960  extended::DateTuple sts;
1961  extended::DateTuple stu;
1962  expandDateTuple(
1963  &match->startDateTime,
1964  prevMatchOffsetMinutes,
1965  prevMatchDeltaMinutes,
1966  &stw,
1967  &sts,
1968  &stu);
1969 
1970  // Transition times.
1971  const extended::DateTuple& ttw = transition->transitionTime;
1972  const extended::DateTuple& tts = transition->transitionTimeS;
1973  const extended::DateTuple& ttu = transition->transitionTimeU;
1974 
1975  // Compare Transition to Match, where equality is assumed if *any* of the
1976  // 'w', 's', or 'u' versions of the DateTuple are equal. This prevents
1977  // duplicate Transition instances from being created in a few cases.
1978  if (ttw == stw || tts == sts || ttu == stu) {
1979  return extended::MatchStatus::kExactMatch;
1980  }
1981 
1982  if (ttu < stu) {
1983  return extended::MatchStatus::kPrior;
1984  }
1985 
1986  // Now check if the transition occurs after the given match. The
1987  // untilDateTime of the current match uses the same UTC offsets as the
1988  // transitionTime of the current transition, so no complicated adjustments
1989  // are needed. We just make sure we compare 'w' with 'w', 's' with 's',
1990  // and 'u' with 'u'.
1991  const extended::DateTuple& matchUntil = match->untilDateTime;
1992  const extended::DateTuple* transitionTime;
1993  if (matchUntil.suffix == internal::ZoneContext::kSuffixS) {
1994  transitionTime = &tts;
1995  } else if (matchUntil.suffix == internal::ZoneContext::kSuffixU) {
1996  transitionTime = &ttu;
1997  } else { // assume 'w'
1998  transitionTime = &ttw;
1999  }
2000  if (*transitionTime < matchUntil) {
2001  return extended::MatchStatus::kWithinMatch;
2002  }
2003  return extended::MatchStatus::kFarFuture;
2004  }
2005 
2011  static void generateStartUntilTimes(Transition** begin, Transition** end) {
2012  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
2013  logging::printf(
2014  "generateStartUntilTimes(): #transitions=%d\n",
2015  (int) (end - begin));
2016  }
2017 
2018  Transition* prev = *begin;
2019  bool isAfterFirst = false;
2020 
2021  for (Transition** iter = begin; iter != end; ++iter) {
2022  Transition* const t = *iter;
2023 
2024  // 1) Update the untilDateTime of the previous Transition
2025  const extended::DateTuple& tt = t->transitionTime;
2026  if (isAfterFirst) {
2027  prev->untilDateTime = tt;
2028  }
2029 
2030  // 2) Calculate the current startDateTime by shifting the
2031  // transitionTime (represented in the UTC offset of the previous
2032  // transition) into the UTC offset of the *current* transition.
2033  int16_t minutes = tt.minutes + (
2034  - prev->offsetMinutes - prev->deltaMinutes
2035  + t->offsetMinutes + t->deltaMinutes);
2036  t->startDateTime = {tt.yearTiny, tt.month, tt.day, minutes,
2037  tt.suffix};
2038  extended::normalizeDateTuple(&t->startDateTime);
2039 
2040  // 3) The epochSecond of the 'transitionTime' is determined by the
2041  // UTC offset of the *previous* Transition. However, the
2042  // transitionTime can be represented by an illegal time (e.g. 24:00).
2043  // So, it is better to use the properly normalized startDateTime
2044  // (calculated above) with the *current* UTC offset.
2045  //
2046  // NOTE: We should also be able to calculate this directly from
2047  // 'transitionTimeU' which should still be a valid field, because it
2048  // hasn't been clobbered by 'untilDateTime' yet. Not sure if this saves
2049  // any CPU time though, since we still need to mutiply by 900.
2050  const extended::DateTuple& st = t->startDateTime;
2051  const acetime_t offsetSeconds = (acetime_t) 60
2052  * (st.minutes - (t->offsetMinutes + t->deltaMinutes));
2053  LocalDate ld = LocalDate::forTinyComponents(
2054  st.yearTiny, st.month, st.day);
2055  t->startEpochSeconds = ld.toEpochSeconds() + offsetSeconds;
2056 
2057  prev = t;
2058  isAfterFirst = true;
2059  }
2060 
2061  // The last Transition's until time is the until time of the MatchingEra.
2062  extended::DateTuple untilTimeW;
2063  extended::DateTuple untilTimeS;
2064  extended::DateTuple untilTimeU;
2065  expandDateTuple(
2066  &prev->match->untilDateTime,
2067  prev->offsetMinutes,
2068  prev->deltaMinutes,
2069  &untilTimeW,
2070  &untilTimeS,
2071  &untilTimeU);
2072  prev->untilDateTime = untilTimeW;
2073  }
2074 
2078  static void calcAbbreviations(Transition** begin, Transition** end) {
2079  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
2080  logging::printf("calcAbbreviations(): #transitions: %d\n",
2081  (int) (end - begin));
2082  }
2083  for (Transition** iter = begin; iter != end; ++iter) {
2084  Transition* const t = *iter;
2085  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
2086  logging::printf(
2087  "calcAbbreviations(): format:%s, deltaMinutes:%d, letter:%s\n",
2088  t->format(), t->deltaMinutes, t->letter());
2089  }
2090  createAbbreviation(
2091  t->abbrev,
2092  internal::kAbbrevSize,
2093  t->format(),
2094  t->deltaMinutes,
2095  t->letter());
2096  }
2097  }
2098 
2107  static void createAbbreviation(
2108  char* dest,
2109  uint8_t destSize,
2110  const char* format,
2111  uint16_t deltaMinutes,
2112  const char* letterString) {
2113 
2114  // Check if FORMAT contains a '%'.
2115  if (strchr(format, '%') != nullptr) {
2116  // Check if RULES column empty, therefore no 'letter'
2117  if (letterString == nullptr) {
2118  strncpy(dest, format, destSize - 1);
2119  dest[destSize - 1] = '\0';
2120  } else {
2121  ace_common::copyReplaceString(
2122  dest, destSize, format, '%', letterString);
2123  }
2124  } else {
2125  // Check if FORMAT contains a '/'.
2126  const char* slashPos = strchr(format, '/');
2127  if (slashPos != nullptr) {
2128  if (deltaMinutes == 0) {
2129  uint8_t headLength = (slashPos - format);
2130  if (headLength >= destSize) headLength = destSize - 1;
2131  memcpy(dest, format, headLength);
2132  dest[headLength] = '\0';
2133  } else {
2134  uint8_t tailLength = strlen(slashPos+1);
2135  if (tailLength >= destSize) tailLength = destSize - 1;
2136  memcpy(dest, slashPos+1, tailLength);
2137  dest[tailLength] = '\0';
2138  }
2139  } else {
2140  // Just copy the FORMAT disregarding deltaMinutes and letterString.
2141  strncpy(dest, format, destSize);
2142  dest[destSize - 1] = '\0';
2143  }
2144  }
2145  }
2146 
2147  const BF* mBrokerFactory; // nullable
2148  ZIB mZoneInfoBroker;
2149 
2150  mutable int16_t mYear = 0; // maybe create LocalDate::kInvalidYear?
2151  mutable bool mIsFilled = false;
2152  // NOTE: Maybe move mNumMatches and mMatches into a MatchStorage object.
2153  mutable uint8_t mNumMatches = 0; // actual number of matches
2154  mutable MatchingEra mMatches[kMaxMatches];
2155  mutable TransitionStorage mTransitionStorage;
2156 };
2157 
2158 
2164  extended::BrokerFactory,
2165  extended::ZoneInfoBroker,
2166  extended::ZoneEraBroker,
2167  extended::ZonePolicyBroker,
2168  extended::ZoneRuleBroker> {
2169 
2170  public:
2172  static const uint8_t kTypeExtended = 4;
2173 
2174  explicit ExtendedZoneProcessor(const extended::ZoneInfo* zoneInfo = nullptr)
2176  extended::BrokerFactory,
2177  extended::ZoneInfoBroker,
2178  extended::ZoneEraBroker,
2179  extended::ZonePolicyBroker,
2180  extended::ZoneRuleBroker>(
2181  kTypeExtended, &mBrokerFactory, (uintptr_t) zoneInfo)
2182  {}
2183 
2184  private:
2185  extended::BrokerFactory mBrokerFactory;
2186 };
2187 
2188 } // namespace ace_time
2189 
2190 #endif
The classes provide a thin layer of indirection for accessing the zoneinfo files stored in the zonedb...
An implementation of ZoneProcessor that supports for all zones defined by the TZ Database.
bool initForEpochSeconds(acetime_t epochSeconds) const
Initialize using the epochSeconds.
void setBrokerFactory(const BF *brokerFactory)
Set the broker factory at runtime.
const char * getAbbrev(acetime_t epochSeconds) const override
Return the time zone abbreviation at epochSeconds.
extended::MatchingTransitionTemplate< ZEB, ZPB, ZRB > MatchingTransition
Exposed only for testing purposes.
extended::TransitionStorageTemplate< kMaxTransitions, ZEB, ZPB, ZRB > TransitionStorage
Exposed only for testing purposes.
OffsetDateTime getOffsetDateTime(acetime_t epochSeconds) const override
void printNameTo(Print &printer, bool followLink=false) const override
Print a human-readable identifier (e.g.
bool initForYear(int16_t year) const
Initialize the zone rules cache, keyed by the "current" year.
void printShortNameTo(Print &printer, bool followLink=false) const override
Print a short human-readable identifier (e.g.
bool equalsZoneKey(uintptr_t zoneKey) const override
Return true if ZoneProcessor is associated with the given opaque zoneKey.
OffsetDateTime getOffsetDateTime(const LocalDateTime &ldt) const override
bool isLink() const override
Return true if timezone is a Link entry pointing to a Zone entry.
TimeOffset getDeltaOffset(acetime_t epochSeconds) const override
Return the DST delta offset at epochSeconds.
void resetTransitionAllocSize()
Reset the TransitionStorage high water mark.
TimeOffset getUtcOffset(acetime_t epochSeconds) const override
Return the total UTC offset at epochSeconds, including DST offset.
extended::TransitionResultTemplate< ZEB, ZPB, ZRB > TransitionResult
Exposed only for testing purposes.
static const uint8_t kMaxTransitions
Max number of Transitions required for all Zones supported by this class.
extended::TransitionTemplate< ZEB, ZPB, ZRB > Transition
Exposed only for testing purposes.
void setZoneKey(uintptr_t zoneKey) override
Set the opaque zoneKey of this object to a new value, reseting any internally cached information.
ExtendedZoneProcessorTemplate(uint8_t type, const BF *brokerFactory, uintptr_t zoneKey)
Constructor.
void log() const
Used only for debugging.
uint32_t getZoneId(bool followLink=false) const override
Return the unique stable zoneId.
uint8_t getTransitionAllocSize() const
Get the largest allocation size of TransitionStorage.
extended::MatchingEraTemplate< ZEB > MatchingEra
Exposed only for testing purposes.
A specific implementation of ExtendedZoneProcessorTemplate that uses ZoneXxxBrokers which read from z...
static const uint8_t kTypeExtended
Unique TimeZone type identifier for ExtendedZoneProcessor.
Class that holds the date-time as the components (year, month, day, hour, minute, second) without reg...
Definition: LocalDateTime.h:31
int8_t yearTiny() const
Return the single-byte year offset from year 2000.
uint8_t day() const
Return the day of the month.
uint8_t month() const
Return the month with January=1, December=12.
uint8_t fold() const
Return the fold.
uint8_t minute() const
Return the minute.
uint8_t hour() const
Return the hour.
int16_t year() const
Return the year.
The date (year, month, day) representing the date without regards to time zone.
Definition: LocalDate.h:36
static const int16_t kEpochYear
Base year of epoch.
Definition: LocalDate.h:39
int16_t year() const
Return the full year instead of just the last 2 digits.
Definition: LocalDate.h:269
static LocalDate forEpochSeconds(acetime_t epochSeconds)
Factory method using the number of seconds since AceTime epoch of 2000-01-01.
Definition: LocalDate.h:175
int32_t toEpochDays() const
Return number of days since AceTime epoch (2000-01-01 00:00:00Z).
Definition: LocalDate.h:338
static const int8_t kInvalidYearTiny
Sentinel yearTiny which indicates an error condition or sometimes a year that 'does not exist'.
Definition: LocalDate.h:51
static LocalDate forTinyComponents(int8_t yearTiny, uint8_t month, uint8_t day)
Factory method using components with an int8_t yearTiny.
Definition: LocalDate.h:131
The date (year, month, day), time (hour, minute, second) and offset from UTC (timeOffset).
static OffsetDateTime forLocalDateTimeAndOffset(const LocalDateTime &localDateTime, TimeOffset timeOffset)
Factory method from LocalDateTime and TimeOffset.
static OffsetDateTime forError()
Factory method that returns an instance whose isError() is true.
static OffsetDateTime forEpochSeconds(acetime_t epochSeconds, TimeOffset timeOffset, uint8_t fold=0)
Factory method.
A thin wrapper that represents a time offset from a reference point, usually 00:00 at UTC,...
Definition: TimeOffset.h:56
static TimeOffset forError()
Return an error indicator.
Definition: TimeOffset.h:105
static TimeOffset forMinutes(int16_t minutes)
Create TimeOffset from minutes from 00:00.
Definition: TimeOffset.h:83
Base interface for ZoneProcessor classes.
Definition: ZoneProcessor.h:41
A factory that creates a basic::ZoneInfoBroker.
A heap manager which is specialized and tuned to manage a collection of Transitions,...
void resetAllocSize()
Reset the current allocation size.
void addFreeAgentToActivePool()
Immediately add the free agent Transition at index mIndexFree to the Active pool.
MatchingTransitionTemplate< ZEB, ZPB, ZRB > MatchingTransition
Template instantiation of MatchingTransitiontemplate used by this class.
void init()
Initialize all pools to 0 size, usually when a new year is initialized.
void setFreeAgentAsPriorIfValid()
Set the free agent transition as the most recent prior.
Transition * getFreeAgent()
Return a pointer to the first Transition in the free pool.
TransitionTemplate< ZEB, ZPB, ZRB > Transition
Template instantiation of TransitionTemplate used by this class.
void resetCandidatePool()
Empty the Candidate pool by resetting the various indexes.
TransitionResult findTransitionForDateTime(const LocalDateTime &ldt) const
Return the candidate Transitions matching the given dateTime.
uint8_t getAllocSize() const
Return the maximum number of transitions which was allocated.
void addFreeAgentToCandidatePool()
Add the free agent Transition at index mIndexFree to the Candidate pool, sorted by transitionTime.
Transition * getPrior()
Return the current prior transition.
Transition * addActiveCandidatesToActivePool()
Add active candidates into the Active pool, and collapse the Candidate pool.
TransitionResultTemplate< ZEB, ZPB, ZRB > TransitionResult
Template instantiation of TransitionResultTemplate used by this class.
void addPriorToCandidatePool()
Add the current prior into the Candidates pool.
MatchingTransition findTransitionForSeconds(acetime_t epochSeconds) const
Return the Transition matching the given epochSeconds.
Transition ** reservePrior()
Allocate a free Transition then add it to the Prior pool.
void log() const
Verify that the indexes are valid.
Macros and definitions that provide a consistency layer among the various Arduino boards for compatib...
A tuple that represents a date and time.
void log() const
Used only for debugging.
Data structure that captures the matching ZoneEra and its ZoneRule transitions for a given year.
MatchingEraTemplate * prevMatch
The previous MatchingEra, needed to interpret startDateTime.
int16_t lastDeltaMinutes
The DST offset of the last Transition in this MatchingEra.
ZEB era
The ZoneEra that matched the given year.
DateTuple untilDateTime
The effective until time of the matching ZoneEra.
DateTuple startDateTime
The effective start time of the matching ZoneEra, which uses the UTC offsets of the previous matching...
int16_t lastOffsetMinutes
The STD offset of the last Transition in this MatchingEra.
Tuple of a matching Transition and its 'fold'.
The result of the findTransitionForDateTime(const LocalDatetime&) method which can return 2 possible ...
Represents an interval of time where the time zone obeyed a certain UTC offset and DST delta.
ZRB rule
The Zone transition rule that matched for the the given year.
bool isValidPrior
During findCandidateTransitions(), this flag indicates whether the current transition is a valid "pri...
DateTuple transitionTime
The original transition time, usually 'w' but sometimes 's' or 'u'.
acetime_t startEpochSeconds
The calculated transition time of the given rule.
DateTuple transitionTimeS
Version of transitionTime in 's' mode, using the UTC offset of the previous Transition.
DateTuple untilDateTime
Until time expressed using the UTC offset of the current Transition.
const char * letter() const
Return the letter string.
int16_t offsetMinutes
The base offset minutes, not the total effective UTC offset.
void log() const
Used only for debugging.
char letterBuf[2]
Storage for the single letter 'letter' field if 'rule' is not null.
DateTuple transitionTimeU
Version of transitionTime in 'u' mode, using the UTC offset of the previous transition.
static void logHourMinute(int16_t minutes)
Print minutes as [+/-]hh:mm.
MatchStatus matchStatus
During processTransitionMatchStatus(), this flag indicates how the transition falls within the time i...
DateTuple startDateTime
Start time expressed using the UTC offset of the current Transition.
char abbrev[internal::kAbbrevSize]
The calculated effective time zone abbreviation, e.g.
int16_t deltaMinutes
The DST delta minutes.
const MatchingEraTemplate< ZEB > * match
The match which generated this Transition.
A simple tuple to represent a year/month pair.
static const uint8_t kSuffixW
Represents 'w' or wall time.
Definition: ZoneContext.h:18
static const uint8_t kSuffixS
Represents 's' or standard time.
Definition: ZoneContext.h:21
static const uint8_t kSuffixU
Represents 'u' or UTC time.
Definition: ZoneContext.h:24