AceTime  1.11.1
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_createMatch;
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_createTransitionForYear;
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_addFreeAgentToCandidatePool;
51 class TransitionStorageTest_setFreeAgentAsPriorIfValid;
52 class TransitionStorageTest_addActiveCandidatesToActivePool;
53 class TransitionStorageTest_findTransitionForDateTime;
54 class TransitionStorageTest_resetCandidatePool;
55 class TransitionValidation;
56 
57 class Print;
58 
59 namespace ace_time {
60 namespace extended {
61 
62 //---------------------------------------------------------------------------
63 
68 struct DateTuple {
69  DateTuple() = default;
70 
71  DateTuple(int8_t y, uint8_t mon, uint8_t d, int16_t min, uint8_t mod):
72  yearTiny(y), month(mon), day(d), suffix(mod), minutes(min) {}
73 
74  int8_t yearTiny; // [-127, 126], 127 will cause bugs
75  uint8_t month; // [1-12]
76  uint8_t day; // [1-31]
77  uint8_t suffix; // kSuffixS, kSuffixW, kSuffixU
78  int16_t minutes; // negative values allowed
79 
81  void log() const {
82  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
83  int hour = minutes / 60;
84  int minute = minutes - hour * 60;
85  char c = "wsu"[(suffix>>4)];
86  logging::printf("%04d-%02u-%02uT%02d:%02d%c",
87  yearTiny+LocalDate::kEpochYear, month, day, hour, minute, c);
88  }
89  }
90 };
91 
93 inline bool operator<(const DateTuple& a, const DateTuple& b) {
94  if (a.yearTiny < b.yearTiny) return true;
95  if (a.yearTiny > b.yearTiny) return false;
96  if (a.month < b.month) return true;
97  if (a.month > b.month) return false;
98  if (a.day < b.day) return true;
99  if (a.day > b.day) return false;
100  if (a.minutes < b.minutes) return true;
101  if (a.minutes > b.minutes) return false;
102  return false;
103 }
104 
105 inline bool operator>=(const DateTuple& a, const DateTuple& b) {
106  return ! (a < b);
107 }
108 
109 inline bool operator<=(const DateTuple& a, const DateTuple& b) {
110  return ! (b < a);
111 }
112 
113 inline bool operator>(const DateTuple& a, const DateTuple& b) {
114  return (b < a);
115 }
116 
118 inline bool operator==(const DateTuple& a, const DateTuple& b) {
119  return a.yearTiny == b.yearTiny
120  && a.month == b.month
121  && a.day == b.day
122  && a.minutes == b.minutes
123  && a.suffix == b.suffix;
124 }
125 
127 inline void normalizeDateTuple(DateTuple* dt) {
128  const int16_t kOneDayAsMinutes = 60 * 24;
129  if (dt->minutes <= -kOneDayAsMinutes) {
130  LocalDate ld = LocalDate::forTinyComponents(
131  dt->yearTiny, dt->month, dt->day);
132  local_date_mutation::decrementOneDay(ld);
133  dt->yearTiny = ld.yearTiny();
134  dt->month = ld.month();
135  dt->day = ld.day();
136  dt->minutes += kOneDayAsMinutes;
137  } else if (kOneDayAsMinutes <= dt->minutes) {
138  LocalDate ld = LocalDate::forTinyComponents(
139  dt->yearTiny, dt->month, dt->day);
140  local_date_mutation::incrementOneDay(ld);
141  dt->yearTiny = ld.yearTiny();
142  dt->month = ld.month();
143  dt->day = ld.day();
144  dt->minutes -= kOneDayAsMinutes;
145  } else {
146  // do nothing
147  }
148 }
149 
151 inline acetime_t subtractDateTuple(const DateTuple& a, const DateTuple& b) {
152  int32_t epochDaysA = LocalDate::forTinyComponents(
153  a.yearTiny, a.month, a.day).toEpochDays();
154  int32_t epochSecondsA = epochDaysA * 86400 + a.minutes * 60;
155 
156  int32_t epochDaysB = LocalDate::forTinyComponents(
157  b.yearTiny, b.month, b.day).toEpochDays();
158  int32_t epochSecondsB = epochDaysB * 86400 + b.minutes * 60;
159 
160  return epochSecondsA - epochSecondsB;
161 }
162 
163 //---------------------------------------------------------------------------
164 
167  int8_t yearTiny;
168  uint8_t month;
169 };
170 
177 template<typename ZEB>
184 
187 
189  ZEB era;
190 
193 
196 
199 
200  void log() const {
201  logging::printf("MatchingEra(");
202  logging::printf("start="); startDateTime.log();
203  logging::printf("; until="); untilDateTime.log();
204  logging::printf("; era=%c", (era.isNull()) ? '-' : '*');
205  logging::printf("; prevMatch=%c", (prevMatch) ? '*' : '-');
206  logging::printf(")");
207  }
208 };
209 
211 template <typename T>
212 void swap(T& a, T& b) {
213  T tmp = a;
214  a = b;
215  b = tmp;
216 }
217 
222 enum class MatchStatus : uint8_t {
223  kFarPast, // 0
224  kPrior, // 1
225  kExactMatch, // 2
226  kWithinMatch, // 3
227  kFarFuture, // 4
228 };
229 
230 inline bool isMatchStatusActive(MatchStatus status) {
231  return status == MatchStatus::kExactMatch
232  || status == MatchStatus::kWithinMatch
233  || status == MatchStatus::kPrior;
234 }
235 
236 //---------------------------------------------------------------------------
237 
270 template <typename ZEB, typename ZPB, typename ZRB>
272 
275 
281  ZRB rule;
282 
290 
291  union {
298 
304  };
305 
306  union {
313 
319  };
320 
321 #if ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG
322 
326  DateTuple originalTransitionTime;
327 #endif
328 
330  acetime_t startEpochSeconds;
331 
339  int16_t offsetMinutes;
340 
342  int16_t deltaMinutes;
343 
345  char abbrev[internal::kAbbrevSize];
346 
348  char letterBuf[2];
349 
350  union {
358 
363  MatchStatus matchStatus;
364  };
365 
366  //-------------------------------------------------------------------------
367 
368  const char* format() const {
369  return match->era.format();
370  }
371 
377  const char* letter() const {
378  // RULES column is '-' or hh:mm, so return nullptr to indicate this.
379  if (rule.isNull()) {
380  return nullptr;
381  }
382 
383  // RULES point to a named rule, and LETTER is a single, printable
384  // character.
385  char letter = rule.letter();
386  if (letter >= 32) {
387  return letterBuf;
388  }
389 
390  // RULES points to a named rule, and the LETTER is a string. The
391  // rule->letter is a non-printable number < 32, which is an index into
392  // a list of strings given by match->era->zonePolicy->letters[].
393  const ZPB policy = match->era.zonePolicy();
394  uint8_t numLetters = policy.numLetters();
395  if (letter >= numLetters) {
396  // This should never happen unless there is a programming error. If it
397  // does, return an empty string. (createTransitionForYear() sets
398  // letterBuf to a NUL terminated empty string if rule->letter < 32)
399  return letterBuf;
400  }
401 
402  // Return the string at index 'rule->letter'.
403  return policy.letter(letter);
404  }
405 
407  void log() const {
408  logging::printf("Transition(");
409  if (sizeof(acetime_t) <= sizeof(int)) {
410  logging::printf("start=%d", startEpochSeconds);
411  } else {
412  logging::printf("start=%ld", startEpochSeconds);
413  }
414  logging::printf("; status=%d", matchStatus);
415  logging::printf("; UTC");
418  logging::printf("; tt="); transitionTime.log();
419  logging::printf("; tts="); transitionTimeS.log();
420  logging::printf("; ttu="); transitionTimeU.log();
421  if (rule.isNull()) {
422  logging::printf("; rule=-");
423  } else {
424  logging::printf("; rule=");
425  logging::printf("[%d,%d]",
426  rule.fromYearTiny() + LocalDate::kEpochYear,
427  rule.toYearTiny() + LocalDate::kEpochYear);
428  }
429  }
430 
432  static void logHourMinute(int16_t minutes) {
433  char sign;
434  if (minutes < 0) {
435  sign = '-';
436  minutes = -minutes;
437  } else {
438  sign = '+';
439  }
440  uint8_t hour = minutes / 60;
441  uint8_t minute = minutes - hour * 60;
442  logging::printf("%c%02u:%02u", sign, (unsigned) hour, (unsigned) minute);
443  }
444 };
445 
451 template <typename ZEB, typename ZPB, typename ZRB>
453  const TransitionTemplate<ZEB, ZPB, ZRB>* transition;
454  uint8_t fold; // 1 if in the overlap, otherwise 0
455 };
456 
462 template <typename ZEB, typename ZPB, typename ZRB>
464  static constexpr int8_t kStatusGap = 0; // no exact match
465  static constexpr int8_t kStatusExact = 1; // one exact match
466  static constexpr int8_t kStatusOverlap = 2; // two matches
467 
468  const TransitionTemplate<ZEB, ZPB, ZRB>* transition0; // fold==0
469  const TransitionTemplate<ZEB, ZPB, ZRB>* transition1; // fold==1
470  int8_t searchStatus;
471 };
472 
504 template<uint8_t SIZE, typename ZEB, typename ZPB, typename ZRB>
506  public:
512 
519 
526 
529 
536  void init() {
537  for (uint8_t i = 0; i < SIZE; i++) {
538  mTransitions[i] = &mPool[i];
539  }
540  mIndexPrior = 0;
541  mIndexCandidates = 0;
542  mIndexFree = 0;
543  }
544 
547  return mTransitions[mIndexPrior];
548  }
549 
559  mIndexCandidates = mIndexPrior;
560  mIndexFree = mIndexPrior;
561  }
562 
563  Transition** getCandidatePoolBegin() {
564  return &mTransitions[mIndexCandidates];
565  }
566  Transition** getCandidatePoolEnd() {
567  return &mTransitions[mIndexFree];
568  }
569 
570  Transition** getActivePoolBegin() {
571  return &mTransitions[0];
572  }
573  Transition** getActivePoolEnd() {
574  return &mTransitions[mIndexFree];
575  }
576 
583  if (mIndexFree < SIZE) {
584  // Allocate a free transition.
585  if (mIndexFree >= mAllocSize) {
586  mAllocSize = mIndexFree + 1;
587  }
588  return mTransitions[mIndexFree];
589  } else {
590  // No more transition available in the buffer, so just return the last
591  // one. This will probably cause a bug in the timezone calculations, but
592  // I think this is better than triggering undefined behavior by running
593  // off the end of the mTransitions buffer.
594  return mTransitions[SIZE - 1];
595  }
596  }
597 
606  if (mIndexFree >= SIZE) return;
607  mIndexFree++;
608  mIndexPrior = mIndexFree;
609  mIndexCandidates = mIndexFree;
610  }
611 
621  getFreeAgent(); // allocate a new Transition
622 
623  mIndexCandidates++;
624  mIndexFree++;
625  return &mTransitions[mIndexPrior];
626  }
627 
630  Transition* ft = mTransitions[mIndexFree];
631  Transition* prior = mTransitions[mIndexPrior];
632  if ((prior->isValidPrior && prior->transitionTime < ft->transitionTime)
633  || !prior->isValidPrior) {
634  ft->isValidPrior = true;
635  prior->isValidPrior = false;
636  swap(mTransitions[mIndexPrior], mTransitions[mIndexFree]);
637  }
638  }
639 
646  mIndexCandidates--;
647  }
648 
656  if (mIndexFree >= SIZE) return;
657 
658  // This implementation makes pair-wise swaps to shift the current
659  // Transition leftwards into its correctly sorted position. At first
660  // glance, this seem inefficient compared to the alternative
661  // implementation where we save the current Transition, then slide all the
662  // elements to the left by one position rightwards. However,
663  // MemoryBenchmark shows that this implementation is 46 bytes smaller on
664  // an AVR processor.
665  for (uint8_t i = mIndexFree; i > mIndexCandidates; i--) {
666  Transition* curr = mTransitions[i];
667  Transition* prev = mTransitions[i - 1];
668  if (curr->transitionTime >= prev->transitionTime) break;
669  mTransitions[i] = prev;
670  mTransitions[i - 1] = curr;
671  }
672  mIndexFree++;
673  }
674 
682  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
683  logging::printf("addActiveCandidatesToActivePool()\n");
684  }
685 
686  // Shift active candidates to the left into the Active pool.
687  uint8_t iActive = mIndexPrior;
688  uint8_t iCandidate = mIndexCandidates;
689  for (; iCandidate < mIndexFree; iCandidate++) {
690  if (isMatchStatusActive(mTransitions[iCandidate]->matchStatus)) {
691  if (iActive != iCandidate) {
692  // Must use swap(), because we are moving pointers instead of the
693  // actual Transition objects.
694  swap(mTransitions[iActive], mTransitions[iCandidate]);
695  }
696  ++iActive;
697  }
698  }
699 
700  mIndexPrior = iActive;
701  mIndexCandidates = iActive;
702  mIndexFree = iActive;
703 
704  return mTransitions[iActive - 1];
705  }
706 
716  const {
717  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
718  logging::printf(
719  "findTransitionForSeconds(): mIndexFree: %d\n", mIndexFree);
720  }
721 
722  const Transition* prevMatch = nullptr;
723  const Transition* match = nullptr;
724  for (uint8_t i = 0; i < mIndexFree; i++) {
725  const Transition* candidate = mTransitions[i];
726  if (candidate->startEpochSeconds > epochSeconds) break;
727  prevMatch = match;
728  match = candidate;
729  }
730  uint8_t fold = calculateFold(epochSeconds, match, prevMatch);
731  return MatchingTransition{ match, fold };
732  }
733 
734  static uint8_t calculateFold(
735  acetime_t epochSeconds,
736  const Transition* match,
737  const Transition* prevMatch) {
738 
739  if (match == nullptr) return 0;
740  if (prevMatch == nullptr) return 0;
741 
742  // Check if epochSeconds occurs during a "fall back" DST transition.
743  acetime_t overlapSeconds = subtractDateTuple(
744  prevMatch->untilDateTime, match->startDateTime);
745  if (overlapSeconds <= 0) return 0;
746  acetime_t secondsFromTransitionStart =
747  epochSeconds - match->startEpochSeconds;
748  if (secondsFromTransitionStart >= overlapSeconds) return 0;
749 
750  // EpochSeconds occurs within the "fall back" overlap.
751  return 1;
752  }
753 
760  const {
761  // Convert LocalDateTime to DateTuple.
762  DateTuple localDate{
763  ldt.yearTiny(),
764  ldt.month(),
765  ldt.day(),
766  (int16_t) (ldt.hour() * 60 + ldt.minute()),
768  };
769 
770  // Examine adjacent pairs of Transitions, looking for an exact match, gap,
771  // or overlap.
772  const Transition* prevCandidate = nullptr;
773  const Transition* candidate = nullptr;
774  int8_t searchStatus = TransitionResult::kStatusGap;
775  for (uint8_t i = 0; i < mIndexFree; i++) {
776  candidate = mTransitions[i];
777 
778  DateTuple startDateTime = candidate->startDateTime;
779  DateTuple untilDateTime = candidate->untilDateTime;
780  bool isExactMatch = (startDateTime <= localDate)
781  && (localDate < untilDateTime);
782 
783  if (isExactMatch) {
784  // Check for a previous exact match to detect an overlap.
785  if (searchStatus == TransitionResult::kStatusExact) {
786  searchStatus = TransitionResult::kStatusOverlap;
787  break;
788  }
789 
790  // Loop again to detect an overlap.
791  searchStatus = TransitionResult::kStatusExact;
792 
793  } else if (startDateTime > localDate) {
794  // Exit loop since no more candidate transition.
795  break;
796  }
797 
798  prevCandidate = candidate;
799 
800  // Set nullptr so that if the loop runs off the end of the list of
801  // Transitions, the candidate is marked as nullptr.
802  candidate = nullptr;
803  }
804 
805  // Check if the prev was an exact match, and clear the current to
806  // avoid confusion.
807  if (searchStatus == TransitionResult::kStatusExact) {
808  candidate = nullptr;
809  }
810 
811  // This should get optimized by RVO.
812  return TransitionResult{
813  prevCandidate,
814  candidate,
815  searchStatus,
816  };
817  }
818 
820  void log() const {
821  logging::printf("TransitionStorage: ");
822  logging::printf("nActives=%d", mIndexPrior);
823  logging::printf(", nPrior=%d", mIndexCandidates - mIndexPrior);
824  logging::printf(", nCandidates=%d", mIndexFree - mIndexCandidates);
825  logging::printf(", nFree=%d", SIZE - mIndexFree);
826  logging::printf("\n");
827 
828  if (mIndexPrior != 0) {
829  logging::printf(" Actives:\n");
830  for (uint8_t i = 0; i < mIndexPrior; i++) {
831  logging::printf(" ");
832  mTransitions[i]->log();
833  logging::printf("\n");
834  }
835  }
836  if (mIndexPrior != mIndexCandidates) {
837  logging::printf(" Prior: \n");
838  logging::printf(" ");
839  mTransitions[mIndexPrior]->log();
840  logging::printf("\n");
841  }
842  if (mIndexCandidates != mIndexFree) {
843  logging::printf(" Candidates:\n");
844  for (uint8_t i = mIndexCandidates; i < mIndexFree; i++) {
845  logging::printf(" ");
846  mTransitions[i]->log();
847  logging::printf("\n");
848  }
849  }
850  }
851 
853  void resetAllocSize() { mAllocSize = 0; }
854 
860  uint8_t getAllocSize() const { return mAllocSize; }
861 
862  private:
863  friend class ::TransitionStorageTest_getFreeAgent;
864  friend class ::TransitionStorageTest_getFreeAgent2;
865  friend class ::TransitionStorageTest_addFreeAgentToActivePool;
866  friend class ::TransitionStorageTest_reservePrior;
867  friend class ::TransitionStorageTest_addFreeAgentToCandidatePool;
868  friend class ::TransitionStorageTest_setFreeAgentAsPriorIfValid;
869  friend class ::TransitionStorageTest_addActiveCandidatesToActivePool;
870  friend class ::TransitionStorageTest_findTransitionForDateTime;
871  friend class ::TransitionStorageTest_resetCandidatePool;
872 
874  Transition* getTransition(uint8_t i) {
875  return mTransitions[i];
876  }
877 
878  Transition mPool[SIZE];
879  Transition* mTransitions[SIZE];
880  uint8_t mIndexPrior;
881  uint8_t mIndexCandidates;
882  uint8_t mIndexFree;
883 
885  uint8_t mAllocSize = 0;
886 };
887 
888 } // namespace extended
889 
921 template <typename BF, typename ZIB, typename ZEB, typename ZPB, typename ZRB>
923  public:
934  static const uint8_t kMaxTransitions = 8;
935 
938 
942 
946 
949 
953 
954  bool isLink() const override { return mZoneInfoBroker.isLink(); }
955 
956  uint32_t getZoneId(bool followLink = false) const override {
957  ZIB zib = (isLink() && followLink)
958  ? mZoneInfoBroker.targetZoneInfo()
959  : mZoneInfoBroker;
960  return zib.zoneId();
961  }
962 
963  TimeOffset getUtcOffset(acetime_t epochSeconds) const override {
964  bool success = initForEpochSeconds(epochSeconds);
965  if (!success) return TimeOffset::forError();
966  MatchingTransition matchingTransition =
967  mTransitionStorage.findTransitionForSeconds(epochSeconds);
968  const Transition* transition = matchingTransition.transition;
969  return (transition)
971  transition->offsetMinutes + transition->deltaMinutes)
973  }
974 
975  TimeOffset getDeltaOffset(acetime_t epochSeconds) const override {
976  bool success = initForEpochSeconds(epochSeconds);
977  if (!success) return TimeOffset::forError();
978  MatchingTransition matchingTransition =
979  mTransitionStorage.findTransitionForSeconds(epochSeconds);
980  const Transition* transition = matchingTransition.transition;
981  return (transition)
982  ? TimeOffset::forMinutes(transition->deltaMinutes)
984  }
985 
986  const char* getAbbrev(acetime_t epochSeconds) const override {
987  bool success = initForEpochSeconds(epochSeconds);
988  if (!success) return "";
989  MatchingTransition matchingTransition =
990  mTransitionStorage.findTransitionForSeconds(epochSeconds);
991  const Transition* transition = matchingTransition.transition;
992  return (transition) ? transition->abbrev : "";
993  }
994 
1014  OffsetDateTime getOffsetDateTime(const LocalDateTime& ldt) const override {
1015  bool success = initForYear(ldt.year());
1016  if (! success) {
1017  return OffsetDateTime::forError();
1018  }
1019 
1020  // Find the Transition(s) in the gap or overlap.
1021  TransitionResult result =
1022  mTransitionStorage.findTransitionForDateTime(ldt);
1023 
1024  // Extract the appropriate Transition, depending on the request ldt.fold
1025  // and the result.searchStatus.
1026  bool needsNormalization = false;
1027  const Transition* transition;
1028  if (result.searchStatus == TransitionResult::kStatusExact) {
1029  transition = result.transition0;
1030  } else {
1031  if (result.transition0 == nullptr || result.transition1 == nullptr) {
1032  // ldt was far past or far future, and didn't match anything.
1033  transition = nullptr;
1034  } else {
1035  needsNormalization =
1036  (result.searchStatus == TransitionResult::kStatusGap);
1037  transition = (ldt.fold() == 0)
1038  ? result.transition0
1039  : result.transition1;
1040  }
1041  }
1042 
1043  if (! transition) {
1044  return OffsetDateTime::forError();
1045  }
1046 
1048  transition->offsetMinutes + transition->deltaMinutes);
1049  auto odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset);
1050 
1051  if (needsNormalization) {
1052  acetime_t epochSeconds = odt.toEpochSeconds();
1053 
1054  // If in the gap, normalization means that we convert to epochSeconds
1055  // then perform another search through the Transitions, then use
1056  // that new Transition to convert the epochSeconds to OffsetDateTime. It
1057  // turns out that this process identical to just using the other
1058  // Transition returned in TransitionResult above.
1059  const Transition* otherTransition = (ldt.fold() == 0)
1060  ? result.transition1
1061  : result.transition0;
1062  TimeOffset otherOffset = TimeOffset::forMinutes(
1063  otherTransition->offsetMinutes + otherTransition->deltaMinutes);
1064  odt = OffsetDateTime::forEpochSeconds(epochSeconds, otherOffset);
1065 
1066  // Invert the fold.
1067  // 1) The normalization process causes the LocalDateTime to jump to the
1068  // other transition.
1069  // 2) Provides a user-accessible indicator that a gap normalization was
1070  // performed.
1071  odt.fold(1 - ldt.fold());
1072  }
1073 
1074  return odt;
1075  }
1076 
1085  OffsetDateTime getOffsetDateTime(acetime_t epochSeconds) const override {
1086  bool success = initForEpochSeconds(epochSeconds);
1087  if (!success) return OffsetDateTime::forError();
1088 
1089  MatchingTransition matchingTransition =
1090  mTransitionStorage.findTransitionForSeconds(epochSeconds);
1091  const Transition* transition = matchingTransition.transition;
1092  TimeOffset timeOffset = (transition)
1094  transition->offsetMinutes + transition->deltaMinutes)
1097  epochSeconds, timeOffset, matchingTransition.fold);
1098  }
1099 
1100  void printNameTo(Print& printer, bool followLink = false) const override {
1101  ZIB zib = (isLink() && followLink)
1102  ? mZoneInfoBroker.targetZoneInfo()
1103  : mZoneInfoBroker;
1104  zib.printNameTo(printer);
1105  }
1106 
1107  void printShortNameTo(Print& printer, bool followLink = false)
1108  const override {
1109  ZIB zib = (isLink() && followLink)
1110  ? mZoneInfoBroker.targetZoneInfo()
1111  : mZoneInfoBroker;
1112  zib.printShortNameTo(printer);
1113  }
1114 
1116  void log() const {
1117  logging::printf("ExtendedZoneProcessor\n");
1118  logging::printf(" mYear: %d\n", mYear);
1119  logging::printf(" mNumMatches: %d\n", mNumMatches);
1120  for (int i = 0; i < mNumMatches; i++) {
1121  logging::printf(" Match %d: ", i);
1122  mMatches[i].log();
1123  logging::printf("\n");
1124  }
1125  mTransitionStorage.log();
1126  }
1127 
1130  mTransitionStorage.resetAllocSize();
1131  }
1132 
1134  uint8_t getTransitionAllocSize() const {
1135  return mTransitionStorage.getAllocSize();
1136  }
1137 
1138  void setZoneKey(uintptr_t zoneKey) override {
1139  if (mZoneInfoBroker.equals(zoneKey)) return;
1140 
1141  mZoneInfoBroker = mBrokerFactory->createZoneInfoBroker(zoneKey);
1142  mYear = 0;
1143  mIsFilled = false;
1144  mNumMatches = 0;
1145  resetTransitionAllocSize(); // clear the alloc size for new zone
1146  }
1147 
1148  bool equalsZoneKey(uintptr_t zoneKey) const override {
1149  return mZoneInfoBroker.equals(zoneKey);
1150  }
1151 
1158  void setBrokerFactory(const BF* brokerFactory) {
1159  mBrokerFactory = brokerFactory;
1160  }
1161 
1167  bool initForEpochSeconds(acetime_t epochSeconds) const {
1168  LocalDate ld = LocalDate::forEpochSeconds(epochSeconds);
1169  return initForYear(ld.year());
1170  }
1171 
1177  bool initForYear(int16_t year) const {
1178  if (isFilled(year)) return true;
1179  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1180  logging::printf("initForYear(): %d\n", year);
1181  }
1182 
1183  mYear = year;
1184  mNumMatches = 0; // clear cache
1185  mTransitionStorage.init();
1186 
1187  if (year < mZoneInfoBroker.zoneContext()->startYear - 1
1188  || mZoneInfoBroker.zoneContext()->untilYear < year) {
1189  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1190  logging::printf(
1191  "initForYear(): Year %d out of valid range [%d, %d)\n",
1192  year,
1193  mZoneInfoBroker.zoneContext()->startYear,
1194  mZoneInfoBroker.zoneContext()->untilYear);
1195  }
1196  return false;
1197  }
1198 
1199  extended::YearMonthTuple startYm = {
1200  (int8_t) (year - LocalDate::kEpochYear - 1), 12 };
1201  extended::YearMonthTuple untilYm = {
1202  (int8_t) (year - LocalDate::kEpochYear + 1), 2 };
1203 
1204  // Step 1. The equivalent steps for the Python version are in the
1205  // AceTimePython project, under
1206  // zone_processor.ZoneProcessor.init_for_year().
1207  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1208  logging::printf("==== Step 1: findMatches()\n");
1209  }
1210  mNumMatches = findMatches(mZoneInfoBroker, startYm, untilYm, mMatches,
1211  kMaxMatches);
1212  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
1213 
1214  // Step 2
1215  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1216  logging::printf("==== Step 2: createTransitions()\n");
1217  }
1218  createTransitions(mTransitionStorage, mMatches, mNumMatches);
1219  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
1220 
1221  // Step 3
1222  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1223  logging::printf("==== Step 3: fixTransitionTimes()\n");
1224  }
1225  Transition** begin = mTransitionStorage.getActivePoolBegin();
1226  Transition** end = mTransitionStorage.getActivePoolEnd();
1227  fixTransitionTimes(begin, end);
1228  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
1229 
1230  // Step 4
1231  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1232  logging::printf("==== Step 4: generateStartUntilTimes()\n");
1233  }
1234  generateStartUntilTimes(begin, end);
1235  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
1236 
1237  // Step 5
1238  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1239  logging::printf("==== Step 5: calcAbbreviations()\n");
1240  }
1241  calcAbbreviations(begin, end);
1242  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
1243 
1244  mIsFilled = true;
1245  return true;
1246  }
1247 
1248  protected:
1256  uint8_t type,
1257  const BF* brokerFactory,
1258  uintptr_t zoneKey
1259  ) :
1260  ZoneProcessor(type),
1261  mBrokerFactory(brokerFactory)
1262  {
1263  setZoneKey(zoneKey);
1264  }
1265 
1266  private:
1267  friend class ::ExtendedZoneProcessorTest_compareEraToYearMonth;
1268  friend class ::ExtendedZoneProcessorTest_compareEraToYearMonth2;
1269  friend class ::ExtendedZoneProcessorTest_createMatch;
1270  friend class ::ExtendedZoneProcessorTest_findMatches_simple;
1271  friend class ::ExtendedZoneProcessorTest_findMatches_named;
1272  friend class ::ExtendedZoneProcessorTest_findCandidateTransitions;
1273  friend class ::ExtendedZoneProcessorTest_createTransitionsFromNamedMatch;
1274  friend class ::ExtendedZoneProcessorTest_getTransitionTime;
1275  friend class ::ExtendedZoneProcessorTest_createTransitionForYear;
1276  friend class ::ExtendedZoneProcessorTest_normalizeDateTuple;
1277  friend class ::ExtendedZoneProcessorTest_expandDateTuple;
1278  friend class ::ExtendedZoneProcessorTest_calcInteriorYears;
1279  friend class ::ExtendedZoneProcessorTest_getMostRecentPriorYear;
1280  friend class ::ExtendedZoneProcessorTest_compareTransitionToMatchFuzzy;
1281  friend class ::ExtendedZoneProcessorTest_compareTransitionToMatch;
1282  friend class ::ExtendedZoneProcessorTest_processTransitionMatchStatus;
1283  friend class ::ExtendedZoneProcessorTest_fixTransitionTimes_generateStartUntilTimes;
1284  friend class ::ExtendedZoneProcessorTest_createAbbreviation;
1285  friend class ::ExtendedZoneProcessorTest_setZoneKey;
1286  friend class ::TransitionStorageTest_findTransitionForDateTime;
1287  friend class ::TransitionValidation;
1288 
1289  // Disable copy constructor and assignment operator.
1291  const ExtendedZoneProcessorTemplate&) = delete;
1292  ExtendedZoneProcessorTemplate& operator=(
1293  const ExtendedZoneProcessorTemplate&) = delete;
1294 
1299  static const uint8_t kMaxMatches = 4;
1300 
1305  static const uint8_t kMaxInteriorYears = 4;
1306 
1307  bool equals(const ZoneProcessor& other) const override {
1308  return mZoneInfoBroker.equals(
1309  ((const ExtendedZoneProcessorTemplate&) other).mZoneInfoBroker);
1310  }
1311 
1313  bool isFilled(int16_t year) const {
1314  return mIsFilled && (year == mYear);
1315  }
1316 
1324  static uint8_t findMatches(
1325  const ZIB& zoneInfo,
1326  const extended::YearMonthTuple& startYm,
1327  const extended::YearMonthTuple& untilYm,
1328  MatchingEra* matches,
1329  uint8_t maxMatches
1330  ) {
1331  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1332  logging::printf("findMatches()\n");
1333  }
1334  uint8_t iMatch = 0;
1335  MatchingEra* prevMatch = nullptr;
1336  for (uint8_t iEra = 0; iEra < zoneInfo.numEras(); iEra++) {
1337  const ZEB era = zoneInfo.era(iEra);
1338  if (eraOverlapsInterval(prevMatch, era, startYm, untilYm)) {
1339  if (iMatch < maxMatches) {
1340  matches[iMatch] = createMatch(prevMatch, era, startYm, untilYm);
1341  prevMatch = &matches[iMatch];
1342  iMatch++;
1343  }
1344  }
1345  }
1346  return iMatch;
1347  }
1348 
1360  static bool eraOverlapsInterval(
1361  const MatchingEra* prevMatch,
1362  const ZEB& era,
1363  const extended::YearMonthTuple& startYm,
1364  const extended::YearMonthTuple& untilYm) {
1365  return (prevMatch == nullptr || compareEraToYearMonth(
1366  prevMatch->era, untilYm.yearTiny, untilYm.month) < 0)
1367  && compareEraToYearMonth(era, startYm.yearTiny, startYm.month) > 0;
1368  }
1369 
1371  static int8_t compareEraToYearMonth(const ZEB& era,
1372  int8_t yearTiny, uint8_t month) {
1373  if (era.untilYearTiny() < yearTiny) return -1;
1374  if (era.untilYearTiny() > yearTiny) return 1;
1375  if (era.untilMonth() < month) return -1;
1376  if (era.untilMonth() > month) return 1;
1377  if (era.untilDay() > 1) return 1;
1378  //if (era.untilTimeMinutes() < 0) return -1; // never possible
1379  if (era.untilTimeMinutes() > 0) return 1;
1380  return 0;
1381  }
1382 
1389  static MatchingEra createMatch(
1390  MatchingEra* prevMatch,
1391  const ZEB& era,
1392  const extended::YearMonthTuple& startYm,
1393  const extended::YearMonthTuple& untilYm) {
1394 
1395  // If prevMatch is null, set startDate to be earlier than all valid
1396  // ZoneEra.
1397  extended::DateTuple startDate = (prevMatch == nullptr)
1398  ? extended::DateTuple{
1400  1,
1401  1,
1402  0,
1404  }
1405  : extended::DateTuple{
1406  prevMatch->era.untilYearTiny(),
1407  prevMatch->era.untilMonth(),
1408  prevMatch->era.untilDay(),
1409  (int16_t) prevMatch->era.untilTimeMinutes(),
1410  prevMatch->era.untilTimeSuffix()
1411  };
1412  extended::DateTuple lowerBound{
1413  startYm.yearTiny,
1414  startYm.month,
1415  1,
1416  0,
1418  };
1419  if (startDate < lowerBound) {
1420  startDate = lowerBound;
1421  }
1422 
1423  extended::DateTuple untilDate{
1424  era.untilYearTiny(),
1425  era.untilMonth(),
1426  era.untilDay(),
1427  (int16_t) era.untilTimeMinutes(),
1428  era.untilTimeSuffix()
1429  };
1430  extended::DateTuple upperBound{
1431  untilYm.yearTiny,
1432  untilYm.month,
1433  1,
1434  0,
1436  };
1437  if (upperBound < untilDate) {
1438  untilDate = upperBound;
1439  }
1440 
1441  return {startDate, untilDate, era, prevMatch, 0, 0};
1442  }
1443 
1448  static void createTransitions(
1449  TransitionStorage& transitionStorage,
1450  MatchingEra* matches,
1451  uint8_t numMatches) {
1452  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1453  logging::printf("createTransitions()\n");
1454  }
1455 
1456  for (uint8_t i = 0; i < numMatches; i++) {
1457  createTransitionsForMatch(transitionStorage, &matches[i]);
1458  }
1459  }
1460 
1462  static void createTransitionsForMatch(
1463  TransitionStorage& transitionStorage,
1464  MatchingEra* match) {
1465  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1466  logging::printf("== createTransitionsForMatch()\n");
1467  }
1468  const ZPB policy = match->era.zonePolicy();
1469  if (policy.isNull()) {
1470  createTransitionsFromSimpleMatch(transitionStorage, match);
1471  } else {
1472  createTransitionsFromNamedMatch(transitionStorage, match);
1473  }
1474  }
1475 
1476  static void createTransitionsFromSimpleMatch(
1477  TransitionStorage& transitionStorage,
1478  MatchingEra* match) {
1479  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1480  logging::printf("== createTransitionsFromSimpleMatch()\n");
1481  }
1482 
1483  Transition* freeTransition = transitionStorage.getFreeAgent();
1484  createTransitionForYear(freeTransition, 0 /*not used*/,
1485  ZRB() /*rule*/, match);
1486  freeTransition->matchStatus = extended::MatchStatus::kExactMatch;
1487  match->lastOffsetMinutes = freeTransition->offsetMinutes;
1488  match->lastDeltaMinutes = freeTransition->deltaMinutes;
1489  transitionStorage.addFreeAgentToActivePool();
1490  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1491  freeTransition->log();
1492  logging::printf("\n");
1493  }
1494  }
1495 
1496  static void createTransitionsFromNamedMatch(
1497  TransitionStorage& transitionStorage,
1498  MatchingEra* match) {
1499  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1500  logging::printf("== createTransitionsFromNamedMatch()\n");
1501  }
1502 
1503  transitionStorage.resetCandidatePool();
1504  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1505  match->log(); logging::printf("\n");
1506  }
1507 
1508  // Pass 1: Find candidate transitions using whole years.
1509  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1510  logging::printf("---- Pass 1: findCandidateTransitions()\n");
1511  }
1512  findCandidateTransitions(transitionStorage, match);
1513  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1514  transitionStorage.log();
1515  }
1516 
1517  // Pass 2: Fix the transitions times, converting 's' and 'u' into 'w'
1518  // uniformly.
1519  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1520  logging::printf("---- Pass 2: fixTransitionTimes()\n");
1521  }
1522  fixTransitionTimes(
1523  transitionStorage.getCandidatePoolBegin(),
1524  transitionStorage.getCandidatePoolEnd());
1525 
1526  // Pass 3: Select only those Transitions which overlap with the actual
1527  // start and until times of the MatchingEra.
1528  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1529  logging::printf("---- Pass 3: selectActiveTransitions()\n");
1530  }
1531  selectActiveTransitions(
1532  transitionStorage.getCandidatePoolBegin(),
1533  transitionStorage.getCandidatePoolEnd());
1534  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1535  transitionStorage.log();
1536  }
1537  Transition* lastTransition =
1538  transitionStorage.addActiveCandidatesToActivePool();
1539  match->lastOffsetMinutes = lastTransition->offsetMinutes;
1540  match->lastDeltaMinutes = lastTransition->deltaMinutes;
1541  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1542  transitionStorage.log();
1543  }
1544  }
1545 
1546  static void findCandidateTransitions(
1547  TransitionStorage& transitionStorage,
1548  const MatchingEra* match) {
1549  using extended::MatchStatus;
1550 
1551  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1552  logging::printf("findCandidateTransitions(): \n");
1553  match->log();
1554  logging::printf("\n");
1555  }
1556  const ZPB policy = match->era.zonePolicy();
1557  uint8_t numRules = policy.numRules();
1558  int8_t startY = match->startDateTime.yearTiny;
1559  int8_t endY = match->untilDateTime.yearTiny;
1560 
1561  // The prior is referenced through a handle (i.e. pointer to pointer)
1562  // because the actual pointer to the prior could change through the
1563  // transitionStorage.setFreeAgentAsPriorIfValid() method.
1564  Transition** prior = transitionStorage.reservePrior();
1565  (*prior)->isValidPrior = false; // indicates "no prior transition"
1566  for (uint8_t r = 0; r < numRules; r++) {
1567  const ZRB rule = policy.rule(r);
1568 
1569  // Add Transitions for interior years
1570  int8_t interiorYears[kMaxInteriorYears];
1571  uint8_t numYears = calcInteriorYears(interiorYears, kMaxInteriorYears,
1572  rule.fromYearTiny(), rule.toYearTiny(), startY, endY);
1573  for (uint8_t y = 0; y < numYears; y++) {
1574  int8_t year = interiorYears[y];
1575  Transition* t = transitionStorage.getFreeAgent();
1576  createTransitionForYear(t, year, rule, match);
1577  MatchStatus status = compareTransitionToMatchFuzzy(t, match);
1578  if (status == MatchStatus::kPrior) {
1579  transitionStorage.setFreeAgentAsPriorIfValid();
1580  } else if (status == MatchStatus::kWithinMatch) {
1581  transitionStorage.addFreeAgentToCandidatePool();
1582  } else {
1583  // Must be kFarFuture.
1584  // Do nothing, allowing the free agent to be reused.
1585  }
1586  }
1587 
1588  // Add Transition for prior year
1589  int8_t priorYear = getMostRecentPriorYear(
1590  rule.fromYearTiny(), rule.toYearTiny(), startY, endY);
1591  if (priorYear != LocalDate::kInvalidYearTiny) {
1592  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1593  logging::printf(
1594  "findCandidateTransitions(): priorYear: %d\n",
1595  priorYear + LocalDate::kEpochYear);
1596  }
1597  Transition* t = transitionStorage.getFreeAgent();
1598  createTransitionForYear(t, priorYear, rule, match);
1599  transitionStorage.setFreeAgentAsPriorIfValid();
1600  }
1601  }
1602 
1603  // Add the reserved prior into the Candidate pool only if 'isValidPrior'
1604  // is true.
1605  if ((*prior)->isValidPrior) {
1606  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1607  logging::printf(
1608  "findCandidateTransitions(): adding prior to Candidate pool\n");
1609  (*prior)->log();
1610  logging::printf("\n");
1611  }
1612  transitionStorage.addPriorToCandidatePool();
1613  }
1614  }
1615 
1635  static uint8_t calcInteriorYears(
1636  int8_t* interiorYears,
1637  uint8_t maxInteriorYears,
1638  int8_t fromYear, int8_t toYear,
1639  int8_t startYear, int8_t endYear) {
1640  uint8_t i = 0;
1641  for (int8_t year = startYear; year <= endYear; year++) {
1642  if (fromYear <= year && year <= toYear) {
1643  interiorYears[i] = year;
1644  i++;
1645  if (i >= maxInteriorYears) break;
1646  }
1647  }
1648  return i;
1649  }
1650 
1657  static void createTransitionForYear(
1658  Transition* t,
1659  int8_t year,
1660  const ZRB& rule,
1661  const MatchingEra* match) {
1662  t->match = match;
1663  t->rule = rule;
1664  t->offsetMinutes = match->era.offsetMinutes();
1665  t->letterBuf[0] = '\0';
1666 
1667  if (! rule.isNull()) {
1668  t->transitionTime = getTransitionTime(year, rule);
1669  t->deltaMinutes = rule.deltaMinutes();
1670 
1671  char letter = rule.letter();
1672  if (letter >= 32) {
1673  // If LETTER is a '-', treat it the same as an empty string.
1674  if (letter != '-') {
1675  t->letterBuf[0] = letter;
1676  t->letterBuf[1] = '\0';
1677  }
1678  } else {
1679  // rule->letter is a long string, so is referenced as an offset index
1680  // into the ZonePolicy.letters array. The string cannot fit in
1681  // letterBuf, so will be retrieved by the letter() method below.
1682  }
1683  } else {
1684  // Create a Transition using the MatchingEra for the transitionTime.
1685  // Used for simple MatchingEra.
1686  t->transitionTime = match->startDateTime;
1687  t->deltaMinutes = match->era.deltaMinutes();
1688  }
1689  }
1690 
1703  static int8_t getMostRecentPriorYear(
1704  int8_t fromYear, int8_t toYear,
1705  int8_t startYear, int8_t endYear) {
1706 
1707  (void) endYear; // disable compiler warnings
1708 
1709  if (fromYear < startYear) {
1710  if (toYear < startYear) {
1711  return toYear;
1712  } else {
1713  return startYear - 1;
1714  }
1715  } else {
1717  }
1718  }
1719 
1724  static extended::DateTuple getTransitionTime(
1725  int8_t yearTiny, const ZRB& rule) {
1726 
1727  internal::MonthDay monthDay = internal::calcStartDayOfMonth(
1728  yearTiny + LocalDate::kEpochYear,
1729  rule.inMonth(),
1730  rule.onDayOfWeek(),
1731  rule.onDayOfMonth());
1732  return {
1733  yearTiny,
1734  monthDay.month,
1735  monthDay.day,
1736  (int16_t) rule.atTimeMinutes(),
1737  rule.atTimeSuffix()
1738  };
1739  }
1740 
1751  static extended::MatchStatus compareTransitionToMatchFuzzy(
1752  const Transition* t, const MatchingEra* match) {
1753  using extended::MatchStatus;
1754 
1755  int16_t ttMonths = t->transitionTime.yearTiny * 12
1756  + t->transitionTime.month;
1757 
1758  int16_t matchStartMonths = match->startDateTime.yearTiny * 12
1759  + match->startDateTime.month;
1760  if (ttMonths < matchStartMonths - 1) return MatchStatus::kPrior;
1761 
1762  int16_t matchUntilMonths = match->untilDateTime.yearTiny * 12
1763  + match->untilDateTime.month;
1764  if (matchUntilMonths + 2 <= ttMonths) return MatchStatus::kFarFuture;
1765 
1766  return MatchStatus::kWithinMatch;
1767  }
1768 
1777  static void fixTransitionTimes(Transition** begin, Transition** end) {
1778  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1779  logging::printf("fixTransitionTimes(): START; #transitions=%d\n",
1780  (int) (end - begin));
1781  printTransitions(begin, end);
1782  }
1783 
1784  // extend first Transition to -infinity
1785  Transition* prev = *begin;
1786 
1787  for (Transition** iter = begin; iter != end; ++iter) {
1788  Transition* curr = *iter;
1789  expandDateTuple(
1790  &curr->transitionTime,
1791  prev->offsetMinutes,
1792  prev->deltaMinutes,
1793  &curr->transitionTime,
1794  &curr->transitionTimeS,
1795  &curr->transitionTimeU);
1796  prev = curr;
1797  }
1798  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1799  logging::printf("fixTransitionTimes(): FIXED\n");
1800  printTransitions(begin, end);
1801  logging::printf("fixTransitionTimes(): END\n");
1802  }
1803  }
1804 
1805  #ifdef ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG
1806  static void printTransitions(Transition** begin, Transition** end) {
1807  for (Transition** iter = begin; iter != end; ++iter) {
1808  (*iter)->log();
1809  logging::printf("\n");
1810  }
1811  }
1812  #endif
1813 
1819  static void expandDateTuple(
1820  const extended::DateTuple* tt,
1821  int16_t offsetMinutes,
1822  int16_t deltaMinutes,
1823  extended::DateTuple* ttw,
1824  extended::DateTuple* tts,
1825  extended::DateTuple* ttu) {
1826 
1827  if (tt->suffix == internal::ZoneContext::kSuffixS) {
1828  *tts = *tt;
1829  *ttu = {tt->yearTiny, tt->month, tt->day,
1830  (int16_t) (tt->minutes - offsetMinutes),
1832  *ttw = {tt->yearTiny, tt->month, tt->day,
1833  (int16_t) (tt->minutes + deltaMinutes),
1835  } else if (tt->suffix == internal::ZoneContext::kSuffixU) {
1836  *ttu = *tt;
1837  *tts = {tt->yearTiny, tt->month, tt->day,
1838  (int16_t) (tt->minutes + offsetMinutes),
1840  *ttw = {tt->yearTiny, tt->month, tt->day,
1841  (int16_t) (tt->minutes + (offsetMinutes + deltaMinutes)),
1843  } else {
1844  // Explicit set the suffix to 'w' in case it was something else.
1845  *ttw = *tt;
1846  ttw->suffix = internal::ZoneContext::kSuffixW;
1847  *tts = {tt->yearTiny, tt->month, tt->day,
1848  (int16_t) (tt->minutes - deltaMinutes),
1850  *ttu = {tt->yearTiny, tt->month, tt->day,
1851  (int16_t) (tt->minutes - (deltaMinutes + offsetMinutes)),
1853  }
1854 
1855  extended::normalizeDateTuple(ttw);
1856  extended::normalizeDateTuple(tts);
1857  extended::normalizeDateTuple(ttu);
1858  }
1859 
1864  static void selectActiveTransitions(Transition** begin, Transition** end) {
1865  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1866  logging::printf("selectActiveTransitions(): #candidates: %d\n",
1867  (int) (end - begin));
1868  }
1869 
1870  Transition* prior = nullptr;
1871  for (Transition** iter = begin; iter != end; ++iter) {
1872  Transition* transition = *iter;
1873  processTransitionMatchStatus(transition, &prior);
1874  }
1875 
1876  // If the latest prior transition is found, shift it to start at the
1877  // startDateTime of the current match.
1878  if (prior) {
1879  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1880  logging::printf(
1881  "selectActiveTransitions(): found latest prior\n");
1882  }
1883  #if ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG
1884  prior->originalTransitionTime = prior->transitionTime;
1885  #endif
1886  prior->transitionTime = prior->match->startDateTime;
1887  }
1888  }
1889 
1896  static void processTransitionMatchStatus(
1897  Transition* transition,
1898  Transition** prior) {
1899  using extended::MatchStatus;
1900 
1901  MatchStatus status = compareTransitionToMatch(
1902  transition, transition->match);
1903  transition->matchStatus = status;
1904 
1905  if (status == MatchStatus::kExactMatch) {
1906  if (*prior) {
1907  (*prior)->matchStatus = MatchStatus::kFarPast;
1908  }
1909  (*prior) = transition;
1910  } else if (status == MatchStatus::kPrior) {
1911  if (*prior) {
1912  if ((*prior)->transitionTimeU <= transition->transitionTimeU) {
1913  (*prior)->matchStatus = MatchStatus::kFarPast;
1914  (*prior) = transition;
1915  } else {
1916  transition->matchStatus = MatchStatus::kFarPast;
1917  }
1918  } else {
1919  (*prior) = transition;
1920  }
1921  }
1922  }
1923 
1932  static extended::MatchStatus compareTransitionToMatch(
1933  const Transition* transition,
1934  const MatchingEra* match) {
1935 
1936  // Find the previous Match offsets.
1937  int16_t prevMatchOffsetMinutes;
1938  int16_t prevMatchDeltaMinutes;
1939  if (match->prevMatch) {
1940  prevMatchOffsetMinutes = match->prevMatch->lastOffsetMinutes;
1941  prevMatchDeltaMinutes = match->prevMatch->lastDeltaMinutes;
1942  } else {
1943  prevMatchOffsetMinutes = match->era.offsetMinutes();
1944  prevMatchDeltaMinutes = 0;
1945  }
1946 
1947  // Expand start times.
1948  extended::DateTuple stw;
1949  extended::DateTuple sts;
1950  extended::DateTuple stu;
1951  expandDateTuple(
1952  &match->startDateTime,
1953  prevMatchOffsetMinutes,
1954  prevMatchDeltaMinutes,
1955  &stw,
1956  &sts,
1957  &stu);
1958 
1959  // Transition times.
1960  const extended::DateTuple& ttw = transition->transitionTime;
1961  const extended::DateTuple& tts = transition->transitionTimeS;
1962  const extended::DateTuple& ttu = transition->transitionTimeU;
1963 
1964  // Compare Transition to Match, where equality is assumed if *any* of the
1965  // 'w', 's', or 'u' versions of the DateTuple are equal. This prevents
1966  // duplicate Transition instances from being created in a few cases.
1967  if (ttw == stw || tts == sts || ttu == stu) {
1968  return extended::MatchStatus::kExactMatch;
1969  }
1970 
1971  if (ttu < stu) {
1972  return extended::MatchStatus::kPrior;
1973  }
1974 
1975  // Now check if the transition occurs after the given match. The
1976  // untilDateTime of the current match uses the same UTC offsets as the
1977  // transitionTime of the current transition, so no complicated adjustments
1978  // are needed. We just make sure we compare 'w' with 'w', 's' with 's',
1979  // and 'u' with 'u'.
1980  const extended::DateTuple& matchUntil = match->untilDateTime;
1981  const extended::DateTuple* transitionTime;
1982  if (matchUntil.suffix == internal::ZoneContext::kSuffixS) {
1983  transitionTime = &tts;
1984  } else if (matchUntil.suffix == internal::ZoneContext::kSuffixU) {
1985  transitionTime = &ttu;
1986  } else { // assume 'w'
1987  transitionTime = &ttw;
1988  }
1989  if (*transitionTime < matchUntil) {
1990  return extended::MatchStatus::kWithinMatch;
1991  }
1992  return extended::MatchStatus::kFarFuture;
1993  }
1994 
2000  static void generateStartUntilTimes(Transition** begin, Transition** end) {
2001  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
2002  logging::printf(
2003  "generateStartUntilTimes(): #transitions=%d\n",
2004  (int) (end - begin));
2005  }
2006 
2007  Transition* prev = *begin;
2008  bool isAfterFirst = false;
2009 
2010  for (Transition** iter = begin; iter != end; ++iter) {
2011  Transition* const t = *iter;
2012 
2013  // 1) Update the untilDateTime of the previous Transition
2014  const extended::DateTuple& tt = t->transitionTime;
2015  if (isAfterFirst) {
2016  prev->untilDateTime = tt;
2017  }
2018 
2019  // 2) Calculate the current startDateTime by shifting the
2020  // transitionTime (represented in the UTC offset of the previous
2021  // transition) into the UTC offset of the *current* transition.
2022  int16_t minutes = tt.minutes + (
2023  - prev->offsetMinutes - prev->deltaMinutes
2024  + t->offsetMinutes + t->deltaMinutes);
2025  t->startDateTime = {tt.yearTiny, tt.month, tt.day, minutes,
2026  tt.suffix};
2027  extended::normalizeDateTuple(&t->startDateTime);
2028 
2029  // 3) The epochSecond of the 'transitionTime' is determined by the
2030  // UTC offset of the *previous* Transition. However, the
2031  // transitionTime can be represented by an illegal time (e.g. 24:00).
2032  // So, it is better to use the properly normalized startDateTime
2033  // (calculated above) with the *current* UTC offset.
2034  //
2035  // NOTE: We should also be able to calculate this directly from
2036  // 'transitionTimeU' which should still be a valid field, because it
2037  // hasn't been clobbered by 'untilDateTime' yet. Not sure if this saves
2038  // any CPU time though, since we still need to mutiply by 900.
2039  const extended::DateTuple& st = t->startDateTime;
2040  const acetime_t offsetSeconds = (acetime_t) 60
2041  * (st.minutes - (t->offsetMinutes + t->deltaMinutes));
2042  LocalDate ld = LocalDate::forTinyComponents(
2043  st.yearTiny, st.month, st.day);
2044  t->startEpochSeconds = ld.toEpochSeconds() + offsetSeconds;
2045 
2046  prev = t;
2047  isAfterFirst = true;
2048  }
2049 
2050  // The last Transition's until time is the until time of the MatchingEra.
2051  extended::DateTuple untilTimeW;
2052  extended::DateTuple untilTimeS;
2053  extended::DateTuple untilTimeU;
2054  expandDateTuple(
2055  &prev->match->untilDateTime,
2056  prev->offsetMinutes,
2057  prev->deltaMinutes,
2058  &untilTimeW,
2059  &untilTimeS,
2060  &untilTimeU);
2061  prev->untilDateTime = untilTimeW;
2062  }
2063 
2067  static void calcAbbreviations(Transition** begin, Transition** end) {
2068  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
2069  logging::printf("calcAbbreviations(): #transitions: %d\n",
2070  (int) (end - begin));
2071  }
2072  for (Transition** iter = begin; iter != end; ++iter) {
2073  Transition* const t = *iter;
2074  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
2075  logging::printf(
2076  "calcAbbreviations(): format:%s, deltaMinutes:%d, letter:%s\n",
2077  t->format(), t->deltaMinutes, t->letter());
2078  }
2079  createAbbreviation(
2080  t->abbrev,
2081  internal::kAbbrevSize,
2082  t->format(),
2083  t->deltaMinutes,
2084  t->letter());
2085  }
2086  }
2087 
2096  static void createAbbreviation(
2097  char* dest,
2098  uint8_t destSize,
2099  const char* format,
2100  uint16_t deltaMinutes,
2101  const char* letterString) {
2102 
2103  // Check if FORMAT contains a '%'.
2104  if (strchr(format, '%') != nullptr) {
2105  // Check if RULES column empty, therefore no 'letter'
2106  if (letterString == nullptr) {
2107  strncpy(dest, format, destSize - 1);
2108  dest[destSize - 1] = '\0';
2109  } else {
2110  ace_common::copyReplaceString(
2111  dest, destSize, format, '%', letterString);
2112  }
2113  } else {
2114  // Check if FORMAT contains a '/'.
2115  const char* slashPos = strchr(format, '/');
2116  if (slashPos != nullptr) {
2117  if (deltaMinutes == 0) {
2118  uint8_t headLength = (slashPos - format);
2119  if (headLength >= destSize) headLength = destSize - 1;
2120  memcpy(dest, format, headLength);
2121  dest[headLength] = '\0';
2122  } else {
2123  uint8_t tailLength = strlen(slashPos+1);
2124  if (tailLength >= destSize) tailLength = destSize - 1;
2125  memcpy(dest, slashPos+1, tailLength);
2126  dest[tailLength] = '\0';
2127  }
2128  } else {
2129  // Just copy the FORMAT disregarding deltaMinutes and letterString.
2130  strncpy(dest, format, destSize);
2131  dest[destSize - 1] = '\0';
2132  }
2133  }
2134  }
2135 
2136  const BF* mBrokerFactory;
2137  ZIB mZoneInfoBroker;
2138 
2139  mutable int16_t mYear = 0; // maybe create LocalDate::kInvalidYear?
2140  mutable bool mIsFilled = false;
2141  // NOTE: Maybe move mNumMatches and mMatches into a MatchStorage object.
2142  mutable uint8_t mNumMatches = 0; // actual number of matches
2143  mutable MatchingEra mMatches[kMaxMatches];
2144  mutable TransitionStorage mTransitionStorage;
2145 };
2146 
2147 
2153  extended::BrokerFactory,
2154  extended::ZoneInfoBroker,
2155  extended::ZoneEraBroker,
2156  extended::ZonePolicyBroker,
2157  extended::ZoneRuleBroker> {
2158 
2159  public:
2161  static const uint8_t kTypeExtended = 4;
2162 
2163  explicit ExtendedZoneProcessor(const extended::ZoneInfo* zoneInfo = nullptr)
2165  extended::BrokerFactory,
2166  extended::ZoneInfoBroker,
2167  extended::ZoneEraBroker,
2168  extended::ZonePolicyBroker,
2169  extended::ZoneRuleBroker>(
2170  kTypeExtended, &mBrokerFactory, (uintptr_t) zoneInfo)
2171  {}
2172 
2173  private:
2174  extended::BrokerFactory mBrokerFactory;
2175 };
2176 
2177 } // namespace ace_time
2178 
2179 #endif
ace_time::ExtendedZoneProcessorTemplate::printShortNameTo
void printShortNameTo(Print &printer, bool followLink=false) const override
Print a short human-readable identifier (e.g.
Definition: ExtendedZoneProcessor.h:1107
ace_time::extended::TransitionTemplate::match
const MatchingEraTemplate< ZEB > * match
The match which generated this Transition.
Definition: ExtendedZoneProcessor.h:274
ace_time::extended::TransitionTemplate::untilDateTime
DateTuple untilDateTime
Until time expressed using the UTC offset of the current Transition.
Definition: ExtendedZoneProcessor.h:318
ace_time::ExtendedZoneProcessorTemplate::initForYear
bool initForYear(int16_t year) const
Initialize the zone rules cache, keyed by the "current" year.
Definition: ExtendedZoneProcessor.h:1177
ace_time::extended::TransitionStorageTemplate::findTransitionForSeconds
MatchingTransition findTransitionForSeconds(acetime_t epochSeconds) const
Return the Transition matching the given epochSeconds.
Definition: ExtendedZoneProcessor.h:715
ace_time::extended::TransitionStorageTemplate::init
void init()
Initialize all pools to 0 size, usually when a new year is initialized.
Definition: ExtendedZoneProcessor.h:536
ace_time::extended::TransitionStorageTemplate::getPrior
Transition * getPrior()
Return the current prior transition.
Definition: ExtendedZoneProcessor.h:546
ace_time::extended::TransitionStorageTemplate::reservePrior
Transition ** reservePrior()
Allocate a free Transition then add it to the Prior pool.
Definition: ExtendedZoneProcessor.h:620
ace_time::extended::TransitionTemplate::isValidPrior
bool isValidPrior
During findCandidateTransitions(), this flag indicates whether the current transition is a valid "pri...
Definition: ExtendedZoneProcessor.h:357
ace_time::extended::TransitionTemplate::letterBuf
char letterBuf[2]
Storage for the single letter 'letter' field if 'rule' is not null.
Definition: ExtendedZoneProcessor.h:348
ace_time::LocalDateTime::hour
uint8_t hour() const
Return the hour.
Definition: LocalDateTime.h:223
ace_time::LocalDateTime
Class that holds the date-time as the components (year, month, day, hour, minute, second) without reg...
Definition: LocalDateTime.h:31
ace_time::TimeOffset::forError
static TimeOffset forError()
Return an error indicator.
Definition: TimeOffset.h:105
ace_time::extended::TransitionTemplate::rule
ZRB rule
The Zone transition rule that matched for the the given year.
Definition: ExtendedZoneProcessor.h:281
ace_time::extended::TransitionStorageTemplate::addPriorToCandidatePool
void addPriorToCandidatePool()
Add the current prior into the Candidates pool.
Definition: ExtendedZoneProcessor.h:645
ace_time::extended::TransitionTemplate::offsetMinutes
int16_t offsetMinutes
The base offset minutes, not the total effective UTC offset.
Definition: ExtendedZoneProcessor.h:339
ace_time::extended::TransitionStorageTemplate::getFreeAgent
Transition * getFreeAgent()
Return a pointer to the first Transition in the free pool.
Definition: ExtendedZoneProcessor.h:582
ace_time::extended::TransitionStorageTemplate::TransitionStorageTemplate
TransitionStorageTemplate()
Constructor.
Definition: ExtendedZoneProcessor.h:528
ace_time::extended::TransitionTemplate::letter
const char * letter() const
Return the letter string.
Definition: ExtendedZoneProcessor.h:377
ace_time::TimeOffset
A thin wrapper that represents a time offset from a reference point, usually 00:00 at UTC,...
Definition: TimeOffset.h:56
ace_time::ExtendedZoneProcessorTemplate::isLink
bool isLink() const override
Return true if timezone is a Link entry pointing to a Zone entry.
Definition: ExtendedZoneProcessor.h:954
ace_time::ExtendedZoneProcessorTemplate::TransitionResult
extended::TransitionResultTemplate< ZEB, ZPB, ZRB > TransitionResult
Exposed only for testing purposes.
Definition: ExtendedZoneProcessor.h:945
ace_time::extended::MatchingEraTemplate::lastDeltaMinutes
uint16_t lastDeltaMinutes
The DST offset of the last Transition in this MatchingEra.
Definition: ExtendedZoneProcessor.h:198
ace_time::ExtendedZoneProcessorTemplate::MatchingTransition
extended::MatchingTransitionTemplate< ZEB, ZPB, ZRB > MatchingTransition
Exposed only for testing purposes.
Definition: ExtendedZoneProcessor.h:941
ace_time::extended::MatchingTransitionTemplate
Tuple of a matching Transition and its 'fold'.
Definition: ExtendedZoneProcessor.h:452
ace_time::internal::ZoneContext::kSuffixS
static const uint8_t kSuffixS
Represents 's' or standard time.
Definition: ZoneContext.h:21
ace_time::LocalDate
The date (year, month, day) representing the date without regards to time zone.
Definition: LocalDate.h:36
ace_time::ExtendedZoneProcessor::kTypeExtended
static const uint8_t kTypeExtended
Unique TimeZone type identifier for ExtendedZoneProcessor.
Definition: ExtendedZoneProcessor.h:2161
ace_time::extended::TransitionTemplate::transitionTimeU
DateTuple transitionTimeU
Version of transitionTime in 'u' mode, using the UTC offset of the previous transition.
Definition: ExtendedZoneProcessor.h:312
ace_time::ExtendedZoneProcessorTemplate::getTransitionAllocSize
uint8_t getTransitionAllocSize() const
Get the largest allocation size of TransitionStorage.
Definition: ExtendedZoneProcessor.h:1134
ace_time::LocalDate::forEpochSeconds
static LocalDate forEpochSeconds(acetime_t epochSeconds)
Factory method using the number of seconds since AceTime epoch of 2000-01-01.
Definition: LocalDate.h:175
ace_time::ExtendedZoneProcessor
A specific implementation of ExtendedZoneProcessorTemplate that uses ZoneXxxBrokers which read from z...
Definition: ExtendedZoneProcessor.h:2152
ace_time::extended::TransitionStorageTemplate::resetCandidatePool
void resetCandidatePool()
Empty the Candidate pool by resetting the various indexes.
Definition: ExtendedZoneProcessor.h:558
ace_time::ExtendedZoneProcessorTemplate::TransitionStorage
extended::TransitionStorageTemplate< kMaxTransitions, ZEB, ZPB, ZRB > TransitionStorage
Exposed only for testing purposes.
Definition: ExtendedZoneProcessor.h:952
ace_time::extended::MatchingEraTemplate::lastOffsetMinutes
uint16_t lastOffsetMinutes
The STD offset of the last Transition in this MatchingEra.
Definition: ExtendedZoneProcessor.h:195
ace_time::ExtendedZoneProcessorTemplate::getAbbrev
const char * getAbbrev(acetime_t epochSeconds) const override
Return the time zone abbreviation at epochSeconds.
Definition: ExtendedZoneProcessor.h:986
ace_time::extended::TransitionStorageTemplate::MatchingTransition
MatchingTransitionTemplate< ZEB, ZPB, ZRB > MatchingTransition
Template instantiation of MatchingTransitiontemplate used by this class.
Definition: ExtendedZoneProcessor.h:518
ace_time::ExtendedZoneProcessorTemplate::MatchingEra
extended::MatchingEraTemplate< ZEB > MatchingEra
Exposed only for testing purposes.
Definition: ExtendedZoneProcessor.h:948
ace_time::LocalDateTime::month
uint8_t month() const
Return the month with January=1, December=12.
Definition: LocalDateTime.h:211
ace_time::OffsetDateTime
The date (year, month, day), time (hour, minute, second) and offset from UTC (timeOffset).
Definition: OffsetDateTime.h:33
ace_time::ExtendedZoneProcessorTemplate::ExtendedZoneProcessorTemplate
ExtendedZoneProcessorTemplate(uint8_t type, const BF *brokerFactory, uintptr_t zoneKey)
Constructor.
Definition: ExtendedZoneProcessor.h:1255
compat.h
ace_time::ZoneProcessor
Base interface for ZoneProcessor classes.
Definition: ZoneProcessor.h:41
ace_time::LocalDate::kInvalidYearTiny
static const int8_t kInvalidYearTiny
Sentinel yearTiny which indicates an error condition or sometimes a year that 'does not exist'.
Definition: LocalDate.h:51
ace_time::ExtendedZoneProcessorTemplate::getUtcOffset
TimeOffset getUtcOffset(acetime_t epochSeconds) const override
Return the total UTC offset at epochSeconds, including DST offset.
Definition: ExtendedZoneProcessor.h:963
ace_time::OffsetDateTime::forError
static OffsetDateTime forError()
Factory method that returns an instance whose isError() is true.
Definition: OffsetDateTime.h:179
ace_time::extended::TransitionTemplate::matchStatus
MatchStatus matchStatus
During processTransitionMatchStatus(), this flag indicates how the transition falls within the time i...
Definition: ExtendedZoneProcessor.h:363
ace_time::extended::TransitionTemplate::abbrev
char abbrev[internal::kAbbrevSize]
The calculated effective time zone abbreviation, e.g.
Definition: ExtendedZoneProcessor.h:345
ace_time::extended::TransitionTemplate::startDateTime
DateTuple startDateTime
Start time expressed using the UTC offset of the current Transition.
Definition: ExtendedZoneProcessor.h:303
ace_time::extended::DateTuple
A tuple that represents a date and time.
Definition: ExtendedZoneProcessor.h:68
ace_time::ExtendedZoneProcessorTemplate::kMaxTransitions
static const uint8_t kMaxTransitions
Max number of Transitions required for all Zones supported by this class.
Definition: ExtendedZoneProcessor.h:934
ace_time::extended::MatchingEraTemplate::prevMatch
MatchingEraTemplate * prevMatch
The previous MatchingEra, needed to interpret startDateTime.
Definition: ExtendedZoneProcessor.h:192
ace_time::extended::TransitionStorageTemplate::findTransitionForDateTime
TransitionResult findTransitionForDateTime(const LocalDateTime &ldt) const
Return the candidate Transitions matching the given dateTime.
Definition: ExtendedZoneProcessor.h:759
ace_time::ExtendedZoneProcessorTemplate::getOffsetDateTime
OffsetDateTime getOffsetDateTime(const LocalDateTime &ldt) const override
Definition: ExtendedZoneProcessor.h:1014
ace_time::LocalDateTime::minute
uint8_t minute() const
Return the minute.
Definition: LocalDateTime.h:229
ace_time::extended::BrokerFactory
A factory that creates a basic::ZoneInfoBroker.
Definition: ExtendedBrokers.h:501
ace_time::TimeOffset::forMinutes
static TimeOffset forMinutes(int16_t minutes)
Create TimeOffset from minutes from 00:00.
Definition: TimeOffset.h:83
ace_time::extended::TransitionStorageTemplate
A heap manager which is specialized and tuned to manage a collection of Transitions,...
Definition: ExtendedZoneProcessor.h:505
ace_time::LocalDate::forTinyComponents
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
ace_time::extended::TransitionStorageTemplate::TransitionResult
TransitionResultTemplate< ZEB, ZPB, ZRB > TransitionResult
Template instantiation of TransitionResultTemplate used by this class.
Definition: ExtendedZoneProcessor.h:525
ace_time::ExtendedZoneProcessorTemplate::setBrokerFactory
void setBrokerFactory(const BF *brokerFactory)
Set the broker factory at runtime.
Definition: ExtendedZoneProcessor.h:1158
ace_time::extended::TransitionStorageTemplate::addActiveCandidatesToActivePool
Transition * addActiveCandidatesToActivePool()
Add active candidates into the Active pool, and collapse the Candidate pool.
Definition: ExtendedZoneProcessor.h:681
ace_time::ExtendedZoneProcessorTemplate::initForEpochSeconds
bool initForEpochSeconds(acetime_t epochSeconds) const
Initialize using the epochSeconds.
Definition: ExtendedZoneProcessor.h:1167
ace_time::LocalDate::year
int16_t year() const
Return the full year instead of just the last 2 digits.
Definition: LocalDate.h:269
ace_time::extended::TransitionStorageTemplate::resetAllocSize
void resetAllocSize()
Reset the current allocation size.
Definition: ExtendedZoneProcessor.h:853
ace_time::LocalDate::kEpochYear
static const int16_t kEpochYear
Base year of epoch.
Definition: LocalDate.h:39
ace_time::LocalDateTime::year
int16_t year() const
Return the year.
Definition: LocalDateTime.h:191
ace_time::extended::TransitionTemplate::deltaMinutes
int16_t deltaMinutes
The DST delta minutes.
Definition: ExtendedZoneProcessor.h:342
ace_time::extended::TransitionTemplate::transitionTimeS
DateTuple transitionTimeS
Version of transitionTime in 's' mode, using the UTC offset of the previous Transition.
Definition: ExtendedZoneProcessor.h:297
ace_time::extended::TransitionStorageTemplate::setFreeAgentAsPriorIfValid
void setFreeAgentAsPriorIfValid()
Set the free agent transition as the most recent prior.
Definition: ExtendedZoneProcessor.h:629
ace_time::extended::MatchingEraTemplate::untilDateTime
DateTuple untilDateTime
The effective until time of the matching ZoneEra.
Definition: ExtendedZoneProcessor.h:186
ace_time::extended::MatchingEraTemplate
Data structure that captures the matching ZoneEra and its ZoneRule transitions for a given year.
Definition: ExtendedZoneProcessor.h:178
ace_time::LocalDateTime::yearTiny
int8_t yearTiny() const
Return the single-byte year offset from year 2000.
Definition: LocalDateTime.h:201
ace_time::extended::MatchingEraTemplate::startDateTime
DateTuple startDateTime
The effective start time of the matching ZoneEra, which uses the UTC offsets of the previous matching...
Definition: ExtendedZoneProcessor.h:183
ace_time::extended::TransitionResultTemplate
The result of the findTransitionForDateTime(const LocalDatetime&) method which can return 2 possible ...
Definition: ExtendedZoneProcessor.h:463
ExtendedBrokers.h
ace_time::ExtendedZoneProcessorTemplate::getZoneId
uint32_t getZoneId(bool followLink=false) const override
Return the unique stable zoneId.
Definition: ExtendedZoneProcessor.h:956
ace_time::internal::ZoneContext::kSuffixU
static const uint8_t kSuffixU
Represents 'u' or UTC time.
Definition: ZoneContext.h:24
ace_time::ExtendedZoneProcessorTemplate::setZoneKey
void setZoneKey(uintptr_t zoneKey) override
Set the opaque zoneKey of this object to a new value, reseting any internally cached information.
Definition: ExtendedZoneProcessor.h:1138
ace_time::ExtendedZoneProcessorTemplate::equalsZoneKey
bool equalsZoneKey(uintptr_t zoneKey) const override
Return true if ZoneProcessor is associated with the given opaque zoneKey.
Definition: ExtendedZoneProcessor.h:1148
ace_time::ExtendedZoneProcessorTemplate::printNameTo
void printNameTo(Print &printer, bool followLink=false) const override
Print a human-readable identifier (e.g.
Definition: ExtendedZoneProcessor.h:1100
ace_time::extended::TransitionStorageTemplate::Transition
TransitionTemplate< ZEB, ZPB, ZRB > Transition
Template instantiation of TransitionTemplate used by this class.
Definition: ExtendedZoneProcessor.h:511
ace_time::extended::TransitionTemplate::startEpochSeconds
acetime_t startEpochSeconds
The calculated transition time of the given rule.
Definition: ExtendedZoneProcessor.h:330
ace_time::ExtendedZoneProcessorTemplate::log
void log() const
Used only for debugging.
Definition: ExtendedZoneProcessor.h:1116
ace_time::extended::TransitionStorageTemplate::addFreeAgentToActivePool
void addFreeAgentToActivePool()
Immediately add the free agent Transition at index mIndexFree to the Active pool.
Definition: ExtendedZoneProcessor.h:605
ace_time::LocalDateTime::day
uint8_t day() const
Return the day of the month.
Definition: LocalDateTime.h:217
ace_time::extended::TransitionTemplate
Represents an interval of time where the time zone obeyed a certain UTC offset and DST delta.
Definition: ExtendedZoneProcessor.h:271
ace_time::ExtendedZoneProcessorTemplate::getOffsetDateTime
OffsetDateTime getOffsetDateTime(acetime_t epochSeconds) const override
Definition: ExtendedZoneProcessor.h:1085
ace_time::OffsetDateTime::forLocalDateTimeAndOffset
static OffsetDateTime forLocalDateTimeAndOffset(const LocalDateTime &localDateTime, TimeOffset timeOffset)
Factory method from LocalDateTime and TimeOffset.
Definition: OffsetDateTime.h:37
ace_time::extended::YearMonthTuple
A simple tuple to represent a year/month pair.
Definition: ExtendedZoneProcessor.h:166
ace_time::ExtendedZoneProcessorTemplate::getDeltaOffset
TimeOffset getDeltaOffset(acetime_t epochSeconds) const override
Return the DST delta offset at epochSeconds.
Definition: ExtendedZoneProcessor.h:975
ace_time::extended::DateTuple::log
void log() const
Used only for debugging.
Definition: ExtendedZoneProcessor.h:81
ace_time::extended::MatchingEraTemplate::era
ZEB era
The ZoneEra that matched the given year.
Definition: ExtendedZoneProcessor.h:189
ace_time::extended::TransitionTemplate::transitionTime
DateTuple transitionTime
The original transition time, usually 'w' but sometimes 's' or 'u'.
Definition: ExtendedZoneProcessor.h:289
ace_time::internal::ZoneContext::kSuffixW
static const uint8_t kSuffixW
Represents 'w' or wall time.
Definition: ZoneContext.h:18
ace_time::extended::TransitionStorageTemplate::getAllocSize
uint8_t getAllocSize() const
Return the maximum number of transitions which was allocated.
Definition: ExtendedZoneProcessor.h:860
ace_time::LocalDateTime::fold
uint8_t fold() const
Return the fold.
Definition: LocalDateTime.h:241
ace_time::ExtendedZoneProcessorTemplate::Transition
extended::TransitionTemplate< ZEB, ZPB, ZRB > Transition
Exposed only for testing purposes.
Definition: ExtendedZoneProcessor.h:937
ace_time::extended::TransitionStorageTemplate::addFreeAgentToCandidatePool
void addFreeAgentToCandidatePool()
Add the free agent Transition at index mIndexFree to the Candidate pool, sorted by transitionTime.
Definition: ExtendedZoneProcessor.h:655
ace_time::extended::TransitionTemplate::log
void log() const
Used only for debugging.
Definition: ExtendedZoneProcessor.h:407
ace_time::ExtendedZoneProcessorTemplate
An implementation of ZoneProcessor that supports for all zones defined by the TZ Database.
Definition: ExtendedZoneProcessor.h:922
ace_time::extended::TransitionTemplate::logHourMinute
static void logHourMinute(int16_t minutes)
Print minutes as [+/-]hh:mm.
Definition: ExtendedZoneProcessor.h:432
ace_time::LocalDate::toEpochDays
int32_t toEpochDays() const
Return number of days since AceTime epoch (2000-01-01 00:00:00Z).
Definition: LocalDate.h:338
ace_time::ExtendedZoneProcessorTemplate::resetTransitionAllocSize
void resetTransitionAllocSize()
Reset the TransitionStorage high water mark.
Definition: ExtendedZoneProcessor.h:1129
ace_time::extended::TransitionStorageTemplate::log
void log() const
Verify that the indexes are valid.
Definition: ExtendedZoneProcessor.h:820
ace_time::OffsetDateTime::forEpochSeconds
static OffsetDateTime forEpochSeconds(acetime_t epochSeconds, TimeOffset timeOffset, uint8_t fold=0)
Factory method.
Definition: OffsetDateTime.h:75