AceTime  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
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>
11 #include "common/compat.h"
12 #include "internal/ZonePolicy.h"
13 #include "internal/ZoneInfo.h"
14 #include "common/logging.h"
15 #include "TimeOffset.h"
16 #include "LocalDate.h"
17 #include "OffsetDateTime.h"
18 #include "ZoneProcessor.h"
19 #include "BasicZoneProcessor.h"
20 #include "local_date_mutation.h"
21 
22 #define ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG 0
23 
24 class ExtendedZoneProcessorTest_compareEraToYearMonth;
25 class ExtendedZoneProcessorTest_compareEraToYearMonth2;
26 class ExtendedZoneProcessorTest_createMatch;
27 class ExtendedZoneProcessorTest_findMatches_simple;
28 class ExtendedZoneProcessorTest_findMatches_named;
29 class ExtendedZoneProcessorTest_findCandidateTransitions;
30 class ExtendedZoneProcessorTest_findTransitionsFromNamedMatch;
31 class ExtendedZoneProcessorTest_getTransitionTime;
32 class ExtendedZoneProcessorTest_createTransitionForYear;
33 class ExtendedZoneProcessorTest_normalizeDateTuple;
34 class ExtendedZoneProcessorTest_expandDateTuple;
35 class ExtendedZoneProcessorTest_calcInteriorYears;
36 class ExtendedZoneProcessorTest_getMostRecentPriorYear;
37 class ExtendedZoneProcessorTest_compareTransitionToMatchFuzzy;
38 class ExtendedZoneProcessorTest_compareTransitionToMatch;
39 class ExtendedZoneProcessorTest_processActiveTransition;
40 class ExtendedZoneProcessorTest_fixTransitionTimes_generateStartUntilTimes;
41 class ExtendedZoneProcessorTest_createAbbreviation;
42 class ExtendedZoneProcessorTest_setZoneInfo;
43 class TransitionStorageTest_getFreeAgent;
44 class TransitionStorageTest_getFreeAgent2;
45 class TransitionStorageTest_addFreeAgentToActivePool;
46 class TransitionStorageTest_reservePrior;
47 class TransitionStorageTest_addFreeAgentToCandidatePool;
48 class TransitionStorageTest_setFreeAgentAsPrior;
49 class TransitionStorageTest_addActiveCandidatesToActivePool;
50 class TransitionStorageTest_resetCandidatePool;
51 
52 namespace ace_time {
53 
54 template<uint8_t SIZE, uint8_t TYPE, typename ZS, typename ZI, typename ZIB>
55 class ZoneProcessorCacheImpl;
56 
57 namespace extended {
58 
59 // NOTE: Consider compressing 'modifier' into 'month' field
64 struct DateTuple {
65  int8_t yearTiny; // [-127, 126], 127 will cause bugs
66  uint8_t month; // [1-12]
67  uint8_t day; // [1-31]
68  int8_t timeCode; // 15-min intervals, negative values allowed
69  uint8_t modifier; // 's', 'w', 'u'
70 
72  void log() const {
73  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
74  logging::print("DateTuple(%d-%u-%uT%d'%c')",
75  yearTiny+LocalDate::kEpochYear, month, day, timeCode, modifier);
76  }
77  }
78 };
79 
81 inline bool operator<(const DateTuple& a, const DateTuple& b) {
82  if (a.yearTiny < b.yearTiny) return true;
83  if (a.yearTiny > b.yearTiny) return false;
84  if (a.month < b.month) return true;
85  if (a.month > b.month) return false;
86  if (a.day < b.day) return true;
87  if (a.day > b.day) return false;
88  if (a.timeCode < b.timeCode) return true;
89  if (a.timeCode > b.timeCode) return false;
90  return false;
91 }
92 
93 inline bool operator>=(const DateTuple& a, const DateTuple& b) {
94  return ! (a < b);
95 }
96 
97 inline bool operator<=(const DateTuple& a, const DateTuple& b) {
98  return ! (b < a);
99 }
100 
101 inline bool operator>(const DateTuple& a, const DateTuple& b) {
102  return (b < a);
103 }
104 
106 inline bool operator==(const DateTuple& a, const DateTuple& b) {
107  return a.yearTiny == b.yearTiny
108  && a.month == b.month
109  && a.day == b.day
110  && a.timeCode == b.timeCode
111  && a.modifier == b.modifier;
112 }
113 
116  int8_t yearTiny;
117  uint8_t month;
118 };
119 
124 struct ZoneMatch {
127 
130 
133 
134  void log() const {
135  logging::print("ZoneMatch(");
136  logging::print("Start:"); startDateTime.log();
137  logging::print("; Until:"); untilDateTime.log();
138  logging::print("; Era: %snull", (era.isNotNull()) ? "!" : "");
139  logging::print(")");
140  }
141 };
142 
168 struct Transition {
170  static const uint8_t kAbbrevSize = basic::Transition::kAbbrevSize;
171 
173  const ZoneMatch* match;
174 
181 
189 
190  union {
197 
203  };
204 
205  union {
212 
218  };
219 
225 
227  acetime_t startEpochSeconds;
228 
230  char abbrev[kAbbrevSize];
231 
233  char letterBuf[2];
234 
247  bool active;
248 
256  int8_t offsetCode;
257 
259  int8_t deltaCode;
260 
261  //-------------------------------------------------------------------------
262 
263  const char* format() const {
264  return match->era.format();
265  }
266 
272  const char* letter() const {
273  // RULES column is '-' or hh:mm, so return nullptr to indicate this.
274  if (rule.isNull()) {
275  return nullptr;
276  }
277 
278  // RULES point to a named rule, and LETTER is a single, printable
279  // character.
280  char letter = rule.letter();
281  if (letter >= 32) {
282  return letterBuf;
283  }
284 
285  // RULES points to a named rule, and the LETTER is a string. The
286  // rule->letter is a non-printable number < 32, which is an index into
287  // a list of strings given by match->era->zonePolicy->letters[].
288  const ZonePolicyBroker policy = match->era.zonePolicy();
289  uint8_t numLetters = policy.numLetters();
290  if (letter >= numLetters) {
291  // This should never happen unless there is a programming error. If it
292  // does, return an empty string. (createTransitionForYear() sets
293  // letterBuf to a NUL terminated empty string if rule->letter < 32)
294  return letterBuf;
295  }
296 
297  // Return the string at index 'rule->letter'.
298  return policy.letter(letter);
299  }
300 
302  void log() const {
303  logging::print("Transition(");
304  if (sizeof(acetime_t) == sizeof(int)) {
305  logging::print("sE: %d", startEpochSeconds);
306  } else {
307  logging::print("sE: %ld", startEpochSeconds);
308  }
309  logging::print("; match: %snull", (match) ? "!" : "");
310  logging::print("; era: %snull",
311  (match && match->era.isNotNull()) ? "!" : "");
312  logging::print("; oCode: %d", offsetCode);
313  logging::print("; dCode: %d", deltaCode);
314  logging::print("; tt: "); transitionTime.log();
315  if (rule.isNotNull()) {
316  logging::print("; R.fY: %d", rule.fromYearTiny());
317  logging::print("; R.tY: %d", rule.toYearTiny());
318  logging::print("; R.M: %d", rule.inMonth());
319  logging::print("; R.dow: %d", rule.onDayOfWeek());
320  logging::print("; R.dom: %d", rule.onDayOfMonth());
321  }
322  }
323 };
324 
351 template<uint8_t SIZE>
353  public:
356 
358  void init() {
359  for (uint8_t i = 0; i < SIZE; i++) {
360  mTransitions[i] = &mPool[i];
361  }
362  mIndexPrior = 0;
363  mIndexCandidates = 0;
364  mIndexFree = 0;
365  }
366 
368  Transition* getPrior() { return mTransitions[mIndexPrior]; }
369 
371  void swap(Transition** a, Transition** b) {
372  Transition* tmp = *a;
373  *a = *b;
374  *b = tmp;
375  }
376 
386  mIndexCandidates = mIndexPrior;
387  mIndexFree = mIndexPrior;
388  }
389 
390  Transition** getCandidatePoolBegin() {
391  return &mTransitions[mIndexCandidates];
392  }
393  Transition** getCandidatePoolEnd() {
394  return &mTransitions[mIndexFree];
395  }
396 
397  Transition** getActivePoolBegin() { return &mTransitions[0]; }
398  Transition** getActivePoolEnd() { return &mTransitions[mIndexFree]; }
399 
406  // Set the internal high water mark. If that index becomes SIZE,
407  // then we know we have an overflow.
408  if (mIndexFree > mHighWater) {
409  mHighWater = mIndexFree;
410  }
411 
412  if (mIndexFree < SIZE) {
413  return mTransitions[mIndexFree];
414  } else {
415  return mTransitions[SIZE - 1];
416  }
417  }
418 
427  if (mIndexFree >= SIZE) return;
428  mIndexFree++;
429  mIndexPrior = mIndexFree;
430  mIndexCandidates = mIndexFree;
431  }
432 
439  mIndexCandidates++;
440  mIndexFree++;
441  return &mTransitions[mIndexPrior];
442  }
443 
446  swap(&mTransitions[mIndexPrior], &mTransitions[mIndexFree]);
447  }
448 
455  mIndexCandidates--;
456  }
457 
465  if (mIndexFree >= SIZE) return;
466  for (uint8_t i = mIndexFree; i > mIndexCandidates; i--) {
467  Transition* curr = mTransitions[i];
468  Transition* prev = mTransitions[i - 1];
469  if (curr->transitionTime >= prev->transitionTime) break;
470  mTransitions[i] = prev;
471  mTransitions[i - 1] = curr;
472  }
473  mIndexFree++;
474  }
475 
481  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
482  logging::println("addActiveCandidatesToActivePool()");
483  }
484  uint8_t iActive = mIndexPrior;
485  uint8_t iCandidate = mIndexCandidates;
486  for (; iCandidate < mIndexFree; iCandidate++) {
487  if (mTransitions[iCandidate]->active) {
488  if (iActive != iCandidate) {
489  swap(&mTransitions[iActive], &mTransitions[iCandidate]);
490  }
491  ++iActive;
492  }
493  }
494  mIndexPrior = iActive;
495  mIndexCandidates = iActive;
496  mIndexFree = iActive;
497  }
498 
507  const Transition* findTransition(acetime_t epochSeconds) const {
508  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
509  logging::println( "findTransition(): mIndexFree: %d", mIndexFree);
510  }
511 
512  const Transition* match = nullptr;
513  for (uint8_t i = 0; i < mIndexFree; i++) {
514  const Transition* candidate = mTransitions[i];
515  if (candidate->startEpochSeconds > epochSeconds) break;
516  match = candidate;
517  }
518  return match;
519  }
520 
545  const {
546  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
547  logging::println(
548  "findTransitionForDateTime(): mIndexFree: %d", mIndexFree);
549  }
550 
551  // Convert to DateTuple. If the localDateTime is not a multiple of 15
552  // minutes, the comparision (startTime < localDate) will still be valid.
553  DateTuple localDate = { ldt.yearTiny(), ldt.month(), ldt.day(),
554  (int8_t) (ldt.hour() * 4 + ldt.minute() / 15), 'w' };
555  const Transition* match = nullptr;
556  for (uint8_t i = 0; i < mIndexFree; i++) {
557  const Transition* candidate = mTransitions[i];
558  if (candidate->startDateTime > localDate) break;
559  match = candidate;
560  }
561  return match;
562  }
563 
565  void log() const {
566  logging::println("TransitionStorage:");
567  logging::println(" mIndexPrior: %d", mIndexPrior);
568  logging::println(" mIndexCandidates: %d", mIndexCandidates);
569  logging::println(" mIndexFree: %d", mIndexFree);
570  if (mIndexPrior != 0) {
571  logging::println(" Actives:");
572  for (uint8_t i = 0; i < mIndexPrior; i++) {
573  mTransitions[i]->log();
574  logging::println();
575  }
576  }
577  if (mIndexPrior != mIndexCandidates) {
578  logging::print(" Prior: ");
579  mTransitions[mIndexPrior]->log();
580  logging::println();
581  }
582  if (mIndexCandidates != mIndexFree) {
583  logging::println(" Candidates:");
584  for (uint8_t i = mIndexCandidates; i < mIndexFree; i++) {
585  mTransitions[i]->log();
586  logging::println();
587  }
588  }
589  }
590 
592  void resetHighWater() { mHighWater = 0; }
593 
599  uint8_t getHighWater() const { return mHighWater; }
600 
601  private:
602  friend class ::TransitionStorageTest_getFreeAgent;
603  friend class ::TransitionStorageTest_getFreeAgent2;
604  friend class ::TransitionStorageTest_addFreeAgentToActivePool;
605  friend class ::TransitionStorageTest_reservePrior;
606  friend class ::TransitionStorageTest_addFreeAgentToCandidatePool;
607  friend class ::TransitionStorageTest_setFreeAgentAsPrior;
608  friend class ::TransitionStorageTest_addActiveCandidatesToActivePool;
609  friend class ::TransitionStorageTest_resetCandidatePool;
610 
612  Transition* getTransition(uint8_t i) { return mTransitions[i]; }
613 
614  Transition mPool[SIZE];
615  Transition* mTransitions[SIZE];
616  uint8_t mIndexPrior;
617  uint8_t mIndexCandidates;
618  uint8_t mIndexFree;
619 
621  uint8_t mHighWater = 0;
622 };
623 
624 } // namespace extended
625 
655  public:
661  const extended::ZoneInfo* zoneInfo = nullptr):
662  ZoneProcessor(kTypeExtended),
663  mZoneInfo(zoneInfo) {}
664 
666  const void* getZoneInfo() const override {
667  return mZoneInfo.zoneInfo();
668  }
669 
670  uint32_t getZoneId() const override { return mZoneInfo.zoneId(); }
671 
672  TimeOffset getUtcOffset(acetime_t epochSeconds) const override {
673  bool success = init(epochSeconds);
674  if (!success) return TimeOffset::forError();
675  const extended::Transition* transition = findTransition(epochSeconds);
676  return (transition)
678  transition->offsetCode + transition->deltaCode)
680  }
681 
682  TimeOffset getDeltaOffset(acetime_t epochSeconds) const override {
683  bool success = init(epochSeconds);
684  if (!success) return TimeOffset::forError();
685  const extended::Transition* transition = findTransition(epochSeconds);
686  return TimeOffset::forOffsetCode(transition->deltaCode);
687  }
688 
689  const char* getAbbrev(acetime_t epochSeconds) const override {
690  bool success = init(epochSeconds);
691  if (!success) return "";
692  const extended::Transition* transition = findTransition(epochSeconds);
693  return transition->abbrev;
694  }
695 
696  OffsetDateTime getOffsetDateTime(const LocalDateTime& ldt) const override {
697  TimeOffset offset;
698  bool success = init(ldt.localDate());
699  if (success) {
700  const extended::Transition* transition =
701  mTransitionStorage.findTransitionForDateTime(ldt);
702  offset = (transition)
704  transition->offsetCode + transition->deltaCode)
706  } else {
707  offset = TimeOffset::forError();
708  }
709 
710  auto odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset);
711  if (offset.isError()) {
712  return odt;
713  }
714 
715  // Normalize the OffsetDateTime, causing LocalDateTime in the DST
716  // transtion gap to be shifted forward one hour. For LocalDateTime in an
717  // overlap (DST->STD transition), the earlier UTC offset is selected// by
718  // findTransitionForDateTime(). Use that to calculate the epochSeconds,
719  // then recalculate the offset. Use this final offset to determine the
720  // effective OffsetDateTime that will survive a round-trip unchanged.
721  acetime_t epochSeconds = odt.toEpochSeconds();
722  const extended::Transition* transition =
723  mTransitionStorage.findTransition(epochSeconds);
724  offset = (transition)
726  transition->offsetCode + transition->deltaCode)
728  odt = OffsetDateTime::forEpochSeconds(epochSeconds, offset);
729  return odt;
730  }
731 
732  void printTo(Print& printer) const override;
733 
734  void printShortTo(Print& printer) const override;
735 
737  void log() const {
738  logging::println("ExtendedZoneProcessor:");
739  logging::println(" mYear: %d", mYear);
740  logging::println(" mNumMatches: %d", mNumMatches);
741  for (int i = 0; i < mNumMatches; i++) {
742  logging::print(" Match %d: ", i);
743  mMatches[i].log();
744  logging::println();
745  }
746  mTransitionStorage.log();
747  }
748 
751  mTransitionStorage.resetHighWater();
752  }
753 
755  uint8_t getTransitionHighWater() const {
756  return mTransitionStorage.getHighWater();
757  }
758 
759  private:
760  friend class ::ExtendedZoneProcessorTest_compareEraToYearMonth;
761  friend class ::ExtendedZoneProcessorTest_compareEraToYearMonth2;
762  friend class ::ExtendedZoneProcessorTest_createMatch;
763  friend class ::ExtendedZoneProcessorTest_findMatches_simple;
764  friend class ::ExtendedZoneProcessorTest_findMatches_named;
765  friend class ::ExtendedZoneProcessorTest_findCandidateTransitions;
766  friend class ::ExtendedZoneProcessorTest_findTransitionsFromNamedMatch;
767  friend class ::ExtendedZoneProcessorTest_getTransitionTime;
768  friend class ::ExtendedZoneProcessorTest_createTransitionForYear;
769  friend class ::ExtendedZoneProcessorTest_normalizeDateTuple;
770  friend class ::ExtendedZoneProcessorTest_expandDateTuple;
771  friend class ::ExtendedZoneProcessorTest_calcInteriorYears;
772  friend class ::ExtendedZoneProcessorTest_getMostRecentPriorYear;
773  friend class ::ExtendedZoneProcessorTest_compareTransitionToMatchFuzzy;
774  friend class ::ExtendedZoneProcessorTest_compareTransitionToMatch;
775  friend class ::ExtendedZoneProcessorTest_processActiveTransition;
776  friend class ::ExtendedZoneProcessorTest_fixTransitionTimes_generateStartUntilTimes;
777  friend class ::ExtendedZoneProcessorTest_createAbbreviation;
778  friend class ::ExtendedZoneProcessorTest_setZoneInfo;
779 
780  template<uint8_t SIZE, uint8_t TYPE, typename ZS, typename ZI, typename ZIB>
781  friend class ZoneProcessorCacheImpl; // setZoneInfo()
782 
783  // Disable copy constructor and assignment operator.
785  ExtendedZoneProcessor& operator=(const ExtendedZoneProcessor&) = delete;
786 
791  static const uint8_t kMaxMatches = 4;
792 
800  static const uint8_t kMaxTransitions = 8;
801 
806  static const uint8_t kMaxInteriorYears = 4;
807 
809  static const extended::ZoneEra kAnchorEra;
810 
811  bool equals(const ZoneProcessor& other) const override {
812  const auto& that = (const ExtendedZoneProcessor&) other;
813  return getZoneInfo() == that.getZoneInfo();
814  }
815 
817  void setZoneInfo(const void* zoneInfo) override {
818  if (mZoneInfo.zoneInfo() == zoneInfo) return;
819 
820  mZoneInfo = extended::ZoneInfoBroker(
821  (const extended::ZoneInfo*) zoneInfo);
822  mYear = 0;
823  mIsFilled = false;
824  mNumMatches = 0;
825  }
826 
831  const extended::Transition* findTransition(acetime_t epochSeconds) const {
832  return mTransitionStorage.findTransition(epochSeconds);
833  }
834 
836  bool init(acetime_t epochSeconds) const {
837  LocalDate ld = LocalDate::forEpochSeconds(epochSeconds);
838  return init(ld);
839  }
840 
845  bool init(const LocalDate& ld) const {
846  int16_t year = ld.year();
847  if (isFilled(year)) return true;
848  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
849  logging::println("init(): %d", year);
850  }
851 
852  mYear = year;
853  mNumMatches = 0; // clear cache
854  mTransitionStorage.init();
855 
856  if (year < mZoneInfo.startYear() - 1 || mZoneInfo.untilYear() < year) {
857  return false;
858  }
859 
860  extended::YearMonthTuple startYm = {
861  (int8_t) (year - LocalDate::kEpochYear - 1), 12 };
862  extended::YearMonthTuple untilYm = {
863  (int8_t) (year - LocalDate::kEpochYear + 1), 2 };
864 
865  mNumMatches = findMatches(mZoneInfo, startYm, untilYm, mMatches,
866  kMaxMatches);
867  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
868  log();
869  }
870  findTransitions(mTransitionStorage, mMatches, mNumMatches);
871  extended::Transition** begin = mTransitionStorage.getActivePoolBegin();
872  extended::Transition** end = mTransitionStorage.getActivePoolEnd();
873  fixTransitionTimes(begin, end);
874  generateStartUntilTimes(begin, end);
875  calcAbbreviations(begin, end);
876 
877  mIsFilled = true;
878  return true;
879  }
880 
882  bool isFilled(int16_t year) const {
883  return mIsFilled && (year == mYear);
884  }
885 
893  static uint8_t findMatches(const extended::ZoneInfoBroker zoneInfo,
894  const extended::YearMonthTuple& startYm,
895  const extended::YearMonthTuple& untilYm,
896  extended::ZoneMatch* matches, uint8_t maxMatches) {
897  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
898  logging::println("findMatches()");
899  }
900  uint8_t iMatch = 0;
902  for (uint8_t iEra = 0; iEra < zoneInfo.numEras(); iEra++) {
903  const extended::ZoneEraBroker era = zoneInfo.era(iEra);
904  if (eraOverlapsInterval(prev, era, startYm, untilYm)) {
905  if (iMatch < maxMatches) {
906  matches[iMatch] = createMatch(prev, era, startYm, untilYm);
907  iMatch++;
908  }
909  }
910  prev = era;
911  }
912  return iMatch;
913  }
914 
925  static bool eraOverlapsInterval(
926  const extended::ZoneEraBroker prev,
927  const extended::ZoneEraBroker era,
928  const extended::YearMonthTuple& startYm,
929  const extended::YearMonthTuple& untilYm) {
930  return compareEraToYearMonth(prev, untilYm.yearTiny, untilYm.month) < 0
931  && compareEraToYearMonth(era, startYm.yearTiny, startYm.month) > 0;
932  }
933 
935  static int8_t compareEraToYearMonth(const extended::ZoneEraBroker era,
936  int8_t yearTiny, uint8_t month) {
937  if (era.untilYearTiny() < yearTiny) return -1;
938  if (era.untilYearTiny() > yearTiny) return 1;
939  if (era.untilMonth() < month) return -1;
940  if (era.untilMonth() > month) return 1;
941  if (era.untilDay() > 1) return 1;
942  //if (era.untilTimeCode() < 0) return -1; // never possible
943  if (era.untilTimeCode() > 0) return 1;
944  return 0;
945  }
946 
953  static extended::ZoneMatch createMatch(
954  const extended::ZoneEraBroker prev,
955  const extended::ZoneEraBroker era,
956  const extended::YearMonthTuple& startYm,
957  const extended::YearMonthTuple& untilYm) {
958  extended::DateTuple startDate = {
959  prev.untilYearTiny(), prev.untilMonth(), prev.untilDay(),
960  (int8_t) prev.untilTimeCode(), prev.untilTimeModifier()
961  };
962  extended::DateTuple lowerBound = {
963  startYm.yearTiny, startYm.month, 1, 0, 'w'
964  };
965  if (startDate < lowerBound) {
966  startDate = lowerBound;
967  }
968 
969  extended::DateTuple untilDate = {
970  era.untilYearTiny(), era.untilMonth(), era.untilDay(),
971  (int8_t) era.untilTimeCode(), era.untilTimeModifier()
972  };
973  extended::DateTuple upperBound = {
974  untilYm.yearTiny, untilYm.month, 1, 0, 'w'
975  };
976  if (upperBound < untilDate) {
977  untilDate = upperBound;
978  }
979 
980  return {startDate, untilDate, era};
981  }
982 
987  static void findTransitions(
989  extended::ZoneMatch* matches,
990  uint8_t numMatches) {
991  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
992  logging::println("findTransitions()");
993  }
994  for (uint8_t i = 0; i < numMatches; i++) {
995  findTransitionsForMatch(transitionStorage, &matches[i]);
996  }
997  }
998 
1000  static void findTransitionsForMatch(
1002  const extended::ZoneMatch* match) {
1003  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1004  logging::println("findTransitionsForMatch()");
1005  }
1006  const extended::ZonePolicyBroker policy = match->era.zonePolicy();
1007  if (policy.isNull()) {
1008  findTransitionsFromSimpleMatch(transitionStorage, match);
1009  } else {
1010  findTransitionsFromNamedMatch(transitionStorage, match);
1011  }
1012  }
1013 
1014  static void findTransitionsFromSimpleMatch(
1016  const extended::ZoneMatch* match) {
1017  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1018  logging::println("findTransitionsFromSimpleMatch()");
1019  }
1020  extended::Transition* freeTransition = transitionStorage.getFreeAgent();
1021  createTransitionForYear(freeTransition, 0 /*not used*/,
1022  extended::ZoneRuleBroker(nullptr) /*rule*/, match);
1023  transitionStorage.addFreeAgentToActivePool();
1024  }
1025 
1026  static void findTransitionsFromNamedMatch(
1028  const extended::ZoneMatch* match) {
1029  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1030  logging::println("findTransitionsFromNamedMatch()");
1031  }
1032  transitionStorage.resetCandidatePool();
1033  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1034  match->log(); logging::println();
1035  }
1036  findCandidateTransitions(transitionStorage, match);
1037  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1038  transitionStorage.log(); logging::println();
1039  }
1040  fixTransitionTimes(
1041  transitionStorage.getCandidatePoolBegin(),
1042  transitionStorage.getCandidatePoolEnd());
1043  selectActiveTransitions(transitionStorage, match);
1044  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1045  transitionStorage.log(); logging::println();
1046  }
1047 
1048  transitionStorage.addActiveCandidatesToActivePool();
1049  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1050  transitionStorage.log(); logging::println();
1051  }
1052  }
1053 
1054  static void findCandidateTransitions(
1056  const extended::ZoneMatch* match) {
1057  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1058  logging::print("findCandidateTransitions(): ");
1059  match->log();
1060  logging::println();
1061  }
1062  const extended::ZonePolicyBroker policy = match->era.zonePolicy();
1063  uint8_t numRules = policy.numRules();
1064  int8_t startY = match->startDateTime.yearTiny;
1065  int8_t endY = match->untilDateTime.yearTiny;
1066 
1067  extended::Transition** prior = transitionStorage.reservePrior();
1068  (*prior)->active = false; // indicates "no prior transition"
1069  for (uint8_t r = 0; r < numRules; r++) {
1070  const extended::ZoneRuleBroker rule = policy.rule(r);
1071 
1072  // Add Transitions for interior years
1073  int8_t interiorYears[kMaxInteriorYears];
1074  uint8_t numYears = calcInteriorYears(interiorYears, kMaxInteriorYears,
1075  rule.fromYearTiny(), rule.toYearTiny(), startY, endY);
1076  for (uint8_t y = 0; y < numYears; y++) {
1077  int8_t year = interiorYears[y];
1078  extended::Transition* t = transitionStorage.getFreeAgent();
1079  createTransitionForYear(t, year, rule, match);
1080  int8_t status = compareTransitionToMatchFuzzy(t, match);
1081  if (status < 0) {
1082  setAsPriorTransition(transitionStorage, t);
1083  } else if (status == 1) {
1084  transitionStorage.addFreeAgentToCandidatePool();
1085  }
1086  }
1087 
1088  // Add Transition for prior year
1089  int8_t priorYear = getMostRecentPriorYear(
1090  rule.fromYearTiny(), rule.toYearTiny(), startY, endY);
1091  if (priorYear != LocalDate::kInvalidYearTiny) {
1092  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1093  logging::println(
1094  "findCandidateTransitions(): priorYear: %d", priorYear);
1095  }
1096  extended::Transition* t = transitionStorage.getFreeAgent();
1097  createTransitionForYear(t, priorYear, rule, match);
1098  setAsPriorTransition(transitionStorage, t);
1099  }
1100  }
1101 
1102  // Add the reserved prior into the Candidate pool only if 'active' is
1103  // true, meaning that a prior was found.
1104  if ((*prior)->active) {
1105  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1106  logging::println(
1107  "findCandidateTransitions(): adding prior to Candidate pool");
1108  }
1109  transitionStorage.addPriorToCandidatePool();
1110  }
1111  }
1112 
1117  static uint8_t calcInteriorYears(int8_t* interiorYears,
1118  uint8_t maxInteriorYears, int8_t fromYear, int8_t toYear,
1119  int8_t startYear, int8_t endYear) {
1120  uint8_t i = 0;
1121  for (int8_t year = startYear; year <= endYear; year++) {
1122  if (fromYear <= year && year <= toYear) {
1123  interiorYears[i] = year;
1124  i++;
1125  if (i >= maxInteriorYears) break;
1126  }
1127  }
1128  return i;
1129  }
1130 
1137  static void createTransitionForYear(extended::Transition* t, int8_t year,
1138  const extended::ZoneRuleBroker rule,
1139  const extended::ZoneMatch* match) {
1140  t->match = match;
1141  t->rule = rule;
1142  t->offsetCode = match->era.offsetCode();
1143  t->letterBuf[0] = '\0';
1144 
1145  if (rule.isNotNull()) {
1146  t->transitionTime = getTransitionTime(year, rule);
1147  t->deltaCode = rule.deltaCode();
1148 
1149  char letter = rule.letter();
1150  if (letter >= 32) {
1151  // If LETTER is a '-', treat it the same as an empty string.
1152  if (letter != '-') {
1153  t->letterBuf[0] = letter;
1154  t->letterBuf[1] = '\0';
1155  }
1156  } else {
1157  // rule->letter is a long string, so is referenced as an offset index
1158  // into the ZonePolicy.letters array. The string cannot fit in
1159  // letterBuf, so will be retrieved by the letter() method below.
1160  }
1161  } else {
1162  t->transitionTime = match->startDateTime;
1163  t->deltaCode = match->era.deltaCode();
1164  }
1165  }
1166 
1172  static int8_t getMostRecentPriorYear(int8_t fromYear, int8_t toYear,
1173  int8_t startYear, int8_t /*endYear*/) {
1174  if (fromYear < startYear) {
1175  if (toYear < startYear) {
1176  return toYear;
1177  } else {
1178  return startYear - 1;
1179  }
1180  } else {
1182  }
1183  }
1184 
1185  static extended::DateTuple getTransitionTime(
1186  int8_t yearTiny, const extended::ZoneRuleBroker rule) {
1188  yearTiny + LocalDate::kEpochYear, rule.inMonth(), rule.onDayOfWeek(),
1189  rule.onDayOfMonth());
1190  return {yearTiny, monthDay.month, monthDay.day,
1191  (int8_t) rule.atTimeCode(), rule.atTimeModifier()};
1192  }
1193 
1204  static int8_t compareTransitionToMatchFuzzy(
1205  const extended::Transition* t, const extended::ZoneMatch* match) {
1206  int16_t ttMonths = t->transitionTime.yearTiny * 12
1207  + t->transitionTime.month;
1208 
1209  int16_t matchStartMonths = match->startDateTime.yearTiny * 12
1210  + match->startDateTime.month;
1211  if (ttMonths < matchStartMonths - 1) return -1;
1212 
1213  int16_t matchUntilMonths = match->untilDateTime.yearTiny * 12
1214  + match->untilDateTime.month;
1215  if (matchUntilMonths + 2 <= ttMonths) return 2;
1216 
1217  return 1;
1218  }
1219 
1221  static void setAsPriorTransition(
1223  extended::Transition* t) {
1224  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1225  logging::println("setAsPriorTransition()");
1226  }
1227  extended::Transition* prior = transitionStorage.getPrior();
1228  if (prior->active) {
1229  if (prior->transitionTime < t->transitionTime) {
1230  t->active = true;
1231  transitionStorage.setFreeAgentAsPrior();
1232  }
1233  } else {
1234  t->active = true;
1235  transitionStorage.setFreeAgentAsPrior();
1236  }
1237  }
1238 
1247  static void fixTransitionTimes(
1248  extended::Transition** begin, extended::Transition** end) {
1249  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1250  logging::println("fixTransitionTimes(): #transitions: %d;",
1251  (int) (end - begin));
1252  }
1253 
1254  // extend first Transition to -infinity
1255  extended::Transition* prev = *begin;
1256 
1257  for (extended::Transition** iter = begin; iter != end; ++iter) {
1258  extended::Transition* curr = *iter;
1259  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1260  logging::println("fixTransitionTimes(): LOOP");
1261  curr->log();
1262  logging::println();
1263  }
1264  expandDateTuple(&curr->transitionTime,
1265  &curr->transitionTimeS, &curr->transitionTimeU,
1266  prev->offsetCode, prev->deltaCode);
1267  prev = curr;
1268  }
1269  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1270  logging::println("fixTransitionTimes(): END");
1271  }
1272  }
1273 
1279  static void expandDateTuple(extended::DateTuple* tt,
1281  int8_t offsetCode, int8_t deltaCode) {
1282  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1283  logging::println("expandDateTuple()");
1284  }
1285  if (tt->modifier == 's') {
1286  *tts = *tt;
1287  *ttu = {tt->yearTiny, tt->month, tt->day,
1288  (int8_t) (tt->timeCode - offsetCode), 'u'};
1289  *tt = {tt->yearTiny, tt->month, tt->day,
1290  (int8_t) (tt->timeCode + deltaCode), 'w'};
1291  } else if (tt->modifier == 'u') {
1292  *ttu = *tt;
1293  *tts = {tt->yearTiny, tt->month, tt->day,
1294  (int8_t) (tt->timeCode + offsetCode), 's'};
1295  *tt = {tt->yearTiny, tt->month, tt->day,
1296  (int8_t) (tt->timeCode + offsetCode + deltaCode), 'w'};
1297  } else {
1298  // Explicit set the modifier to 'w' in case it was something else.
1299  tt->modifier = 'w';
1300  *tts = {tt->yearTiny, tt->month, tt->day,
1301  (int8_t) (tt->timeCode - deltaCode), 's'};
1302  *ttu = {tt->yearTiny, tt->month, tt->day,
1303  (int8_t) (tt->timeCode - deltaCode - offsetCode), 'u'};
1304  }
1305 
1306  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1307  logging::println("expandDateTuple(): normalizeDateTuple(): 1");
1308  }
1309  normalizeDateTuple(tt);
1310  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1311  logging::println("expandDateTuple(): normalizeDateTuple(): 2");
1312  }
1313  normalizeDateTuple(tts);
1314  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1315  logging::println("expandDateTuple(): normalizeDateTuple(): 3");
1316  }
1317  normalizeDateTuple(ttu);
1318  }
1319 
1321  static void normalizeDateTuple(extended::DateTuple* dt) {
1322  const int8_t kOneDayAsCode = 4 * 24;
1323  if (dt->timeCode <= -kOneDayAsCode) {
1325  dt->yearTiny, dt->month, dt->day);
1326  local_date_mutation::decrementOneDay(ld);
1327  dt->yearTiny = ld.yearTiny();
1328  dt->month = ld.month();
1329  dt->day = ld.day();
1330  dt->timeCode += kOneDayAsCode;
1331  } else if (kOneDayAsCode <= dt->timeCode) {
1333  dt->yearTiny, dt->month, dt->day);
1334  local_date_mutation::incrementOneDay(ld);
1335  dt->yearTiny = ld.yearTiny();
1336  dt->month = ld.month();
1337  dt->day = ld.day();
1338  dt->timeCode -= kOneDayAsCode;
1339  } else {
1340  // do nothing
1341  }
1342  }
1343 
1348  static void selectActiveTransitions(
1350  const extended::ZoneMatch* match) {
1351  extended::Transition** begin = transitionStorage.getCandidatePoolBegin();
1352  extended::Transition** end = transitionStorage.getCandidatePoolEnd();
1353  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1354  logging::println("selectActiveTransitions(): #candidates: %d",
1355  (int) (end - begin));
1356  }
1357  extended::Transition* prior = nullptr;
1358  for (extended::Transition** iter = begin; iter != end; ++iter) {
1359  extended::Transition* transition = *iter;
1360  processActiveTransition(match, transition, &prior);
1361  }
1362 
1363  // If the latest prior transition is found, shift it to start at the
1364  // startDateTime of the current match.
1365  if (prior) {
1366  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1367  logging::println(
1368  "selectActiveTransitions(): found latest prior");
1369  }
1370  prior->originalTransitionTime = prior->transitionTime;
1371  prior->transitionTime = match->startDateTime;
1372  }
1373  }
1374 
1382  static void processActiveTransition(
1383  const extended::ZoneMatch* match,
1384  extended::Transition* transition,
1385  extended::Transition** prior) {
1386  int8_t status = compareTransitionToMatch(transition, match);
1387  if (status == 2) {
1388  transition->active = false;
1389  } else if (status == 1) {
1390  transition->active = true;
1391  } else if (status == 0) {
1392  if (*prior) {
1393  (*prior)->active = false;
1394  }
1395  transition->active = true;
1396  (*prior) = transition;
1397  } else { // (status < 0)
1398  if (*prior) {
1399  if ((*prior)->transitionTime < transition->transitionTime) {
1400  (*prior)->active = false;
1401  transition->active = true;
1402  (*prior) = transition;
1403  }
1404  } else {
1405  transition->active = true;
1406  (*prior) = transition;
1407  }
1408  }
1409  }
1410 
1425  static int8_t compareTransitionToMatch(
1426  const extended::Transition* transition,
1427  const extended::ZoneMatch* match) {
1428  const extended::DateTuple* transitionTime;
1429 
1430  const extended::DateTuple& matchStart = match->startDateTime;
1431  if (matchStart.modifier == 's') {
1432  transitionTime = &transition->transitionTimeS;
1433  } else if (matchStart.modifier == 'u') {
1434  transitionTime = &transition->transitionTimeU;
1435  } else { // assume 'w'
1436  transitionTime = &transition->transitionTime;
1437  }
1438  if (*transitionTime < matchStart) return -1;
1439  if (*transitionTime == matchStart) return 0;
1440 
1441  const extended::DateTuple& matchUntil = match->untilDateTime;
1442  if (matchUntil.modifier == 's') {
1443  transitionTime = &transition->transitionTimeS;
1444  } else if (matchUntil.modifier == 'u') {
1445  transitionTime = &transition->transitionTimeU;
1446  } else { // assume 'w'
1447  transitionTime = &transition->transitionTime;
1448  }
1449  if (*transitionTime < matchUntil) return 1;
1450  return 2;
1451  }
1452 
1458  static void generateStartUntilTimes(
1459  extended::Transition** begin, extended::Transition** end) {
1460  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1461  logging::println(
1462  "generateStartUntilTimes(): #transitions: %d;",
1463  (int) (end - begin));
1464  }
1465 
1466  extended::Transition* prev = *begin;
1467  bool isAfterFirst = false;
1468 
1469  for (extended::Transition** iter = begin; iter != end; ++iter) {
1470  extended::Transition* const t = *iter;
1471 
1472  // 1) Update the untilDateTime of the previous Transition
1473  const extended::DateTuple& tt = t->transitionTime;
1474  if (isAfterFirst) {
1475  prev->untilDateTime = tt;
1476  }
1477 
1478  // 2) Calculate the current startDateTime by shifting the
1479  // transitionTime (represented in the UTC offset of the previous
1480  // transition) into the UTC offset of the *current* transition.
1481  int8_t code = tt.timeCode - prev->offsetCode - prev->deltaCode
1482  + t->offsetCode + t->deltaCode;
1483  t->startDateTime = {tt.yearTiny, tt.month, tt.day, code, tt.modifier};
1484  normalizeDateTuple(&t->startDateTime);
1485 
1486  // 3) The epochSecond of the 'transitionTime' is determined by the
1487  // UTC offset of the *previous* Transition. However, the
1488  // transitionTime can be represented by an illegal time (e.g. 24:00).
1489  // So, it is better to use the properly normalized startDateTime
1490  // (calculated above) with the *current* UTC offset.
1491  //
1492  // NOTE: We should also be able to calculate this directly from
1493  // 'transitionTimeU' which should still be a valid field, because it
1494  // hasn't been clobbered by 'untilDateTime' yet. Not sure if this saves
1495  // any CPU time though, since we still need to mutiply by 900.
1496  const extended::DateTuple& st = t->startDateTime;
1497  const acetime_t offsetSeconds = (acetime_t) 900
1498  * (st.timeCode - t->offsetCode - t->deltaCode);
1500  st.yearTiny, st.month, st.day);
1501  t->startEpochSeconds = ld.toEpochSeconds() + offsetSeconds;
1502 
1503  prev = t;
1504  isAfterFirst = true;
1505  }
1506 
1507  // The last Transition's until time is the until time of the ZoneMatch.
1508  extended::DateTuple untilTime = prev->match->untilDateTime;
1509  extended::DateTuple untilTimeS; // needed only for expandDateTuple
1510  extended::DateTuple untilTimeU; // needed only for expandDateTuple
1511  expandDateTuple(&untilTime, &untilTimeS, &untilTimeU,
1512  prev->offsetCode, prev->deltaCode);
1513  prev->untilDateTime = untilTime;
1514  }
1515 
1519  static void calcAbbreviations(
1520  extended::Transition** begin, extended::Transition** end) {
1521  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1522  logging::println("calcAbbreviations(): #transitions: %d;",
1523  (int) (end - begin));
1524  }
1525  for (extended::Transition** iter = begin; iter != end; ++iter) {
1526  extended::Transition* const t = *iter;
1527  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1528  logging::println(
1529  "calcAbbreviations(): format:%s, deltaCode:%d, letter:%s",
1530  t->format(), t->deltaCode, t->letter());
1531  }
1532  createAbbreviation(t->abbrev, extended::Transition::kAbbrevSize,
1533  t->format(), t->deltaCode, t->letter());
1534  }
1535  }
1536 
1571  static void createAbbreviation(char* dest, uint8_t destSize,
1572  const char* format, uint8_t deltaCode, const char* letterString) {
1573  // Check if RULES column is empty. Ignore the deltaCode because if
1574  // letterString is nullptr, we can only just copy the whole thing.
1575  if (letterString == nullptr) {
1576  strncpy(dest, format, destSize);
1577  dest[destSize - 1] = '\0';
1578  return;
1579  }
1580 
1581  // Check if FORMAT contains a '%'.
1582  if (strchr(format, '%') != nullptr) {
1583  copyAndReplace(dest, destSize, format, '%', letterString);
1584  } else {
1585  // Check if FORMAT contains a '/'.
1586  const char* slashPos = strchr(format, '/');
1587  if (slashPos != nullptr) {
1588  if (deltaCode == 0) {
1589  uint8_t headLength = (slashPos - format);
1590  if (headLength >= destSize) headLength = destSize - 1;
1591  memcpy(dest, format, headLength);
1592  dest[headLength] = '\0';
1593  } else {
1594  uint8_t tailLength = strlen(slashPos+1);
1595  if (tailLength >= destSize) tailLength = destSize - 1;
1596  memcpy(dest, slashPos+1, tailLength);
1597  dest[tailLength] = '\0';
1598  }
1599  } else {
1600  // Just copy the FORMAT disregarding deltaCode and letterString.
1601  strncpy(dest, format, destSize);
1602  dest[destSize - 1] = '\0';
1603  }
1604  }
1605  }
1606 
1612  static void copyAndReplace(char* dst, uint8_t dstSize, const char* src,
1613  char oldChar, const char* newString) {
1614  while (*src != '\0' && dstSize > 0) {
1615  if (*src == oldChar) {
1616  while (*newString != '\0' && dstSize > 0) {
1617  *dst++ = *newString++;
1618  dstSize--;
1619  }
1620  src++;
1621  } else {
1622  *dst++ = *src++;
1623  dstSize--;
1624  }
1625  }
1626 
1627  if (dstSize == 0) {
1628  --dst;
1629  }
1630  *dst = '\0';
1631  }
1632 
1633  extended::ZoneInfoBroker mZoneInfo;
1634 
1635  mutable int16_t mYear = 0; // maybe create LocalDate::kInvalidYear?
1636  mutable bool mIsFilled = false;
1637  // NOTE: Maybe move mNumMatches and mMatches into a MatchStorage object.
1638  mutable uint8_t mNumMatches = 0; // actual number of matches
1639  mutable extended::ZoneMatch mMatches[kMaxMatches];
1640  mutable extended::TransitionStorage<kMaxTransitions> mTransitionStorage;
1641 };
1642 
1643 } // namespace ace_time
1644 
1645 #endif
void addFreeAgentToActivePool()
Immediately add the free agent Transition at index mIndexFree to the Active pool. ...
static TimeOffset forError()
Return an error indicator.
Definition: TimeOffset.h:110
Base interface for ZoneProcessor classes.
Definition: ZoneProcessor.h:45
ZoneEraBroker era
The ZoneEra that matched the given year.
A heap manager which is specialized and tuned to manage a collection of Transitions, keeping track of unused, used, and active states, using a fixed array of Transitions.
uint8_t minute() const
Return the minute.
void addActiveCandidatesToActivePool()
Add active candidates into the Active pool, and collapse the Candidate pool.
DateTuple transitionTimeU
Version of transitionTime in &#39;u&#39; mode, using the UTC offset of the previous transition.
void resetCandidatePool()
Empty the Candidate pool by resetting the various indexes.
char letterBuf[2]
Storage for the single letter &#39;letter&#39; field if &#39;rule&#39; is not null.
int8_t yearTiny() const
Return the single-byte year offset from year 2000.
Definition: LocalDate.h:225
DateTuple startDateTime
The effective start time of the matching ZoneEra.
Transition * getFreeAgent()
Return a pointer to the first Transition in the free pool.
A cache of ZoneProcessors that provides a ZoneProcessor to the TimeZone upon request.
static const uint8_t kAbbrevSize
Longest abbreviation currently seems to be 5 characters (https://www.timeanddate.com/time/zones/) but...
const ZoneMatch * match
The match which generated this Transition.
uint8_t getHighWater() const
Return the high water mark.
bool isError() const
Return true if this TimeOffset represents an error.
Definition: TimeOffset.h:155
static OffsetDateTime forLocalDateTimeAndOffset(const LocalDateTime &localDateTime, TimeOffset timeOffset)
Factory method from LocalDateTime and TimeOffset.
DateTuple originalTransitionTime
If the transition is shifted to the beginning of a ZoneMatch, this is set to the transitionTime for d...
uint8_t day() const
Return the day of the month.
int8_t yearTiny() const
Return the single-byte year offset from year 2000.
The result of calcStartDayOfMonth().
virtual const void * getZoneInfo() const =0
Return the opaque zoneInfo.
Represents an interval of time where the time zone obeyed a certain UTC offset and DST delta...
void log() const
Verify that the indexes are valid.
const void * getZoneInfo() const override
Return the underlying ZoneInfo.
void addPriorToCandidatePool()
Add the current prior into the Candidates pool.
const char * getAbbrev(acetime_t epochSeconds) const override
Return the time zone abbreviation at epochSeconds.
Representation of a given time zone, implemented as an array of ZoneEra records.
Definition: ZoneInfo.h:77
void setFreeAgentAsPrior()
Swap the Free agrent transition with the current Prior transition.
Transition * getPrior()
Return the current prior transition.
static LocalDate forTinyComponents(int8_t yearTiny, uint8_t month, uint8_t day)
Factory method using components with an int8_t yearTiny.
Definition: LocalDate.h:101
TimeOffset getUtcOffset(acetime_t epochSeconds) const override
Return the total UTC offset at epochSeconds, including DST offset.
char abbrev[kAbbrevSize]
The calculated effective time zone abbreviation, e.g.
const char * letter() const
Return the letter string.
An entry in ZoneInfo which describes which ZonePolicy was being followed during a particular time per...
Definition: ZoneInfo.h:20
uint8_t day() const
Return the day of the month.
Definition: LocalDate.h:237
uint8_t month() const
Return the month with January=1, December=12.
static OffsetDateTime forEpochSeconds(acetime_t epochSeconds, TimeOffset timeOffset)
Factory method.
void addFreeAgentToCandidatePool()
Add the free agent Transition at index mIndexFree to the Candidate pool, sorted by transitionTime...
DateTuple transitionTime
The original transition time, usually &#39;w&#39; but sometimes &#39;s&#39; or &#39;u&#39;.
acetime_t toEpochSeconds() const
Return the number of seconds since AceTime epoch (2000-01-01 00:00:00).
Definition: LocalDate.h:320
const LocalDate & localDate() const
Return the LocalDate.
static const uint8_t kAbbrevSize
Size of the timezone abbreviation.
An implementation of ZoneProcessor that works for all zones defined by the TZ Database (with some zon...
const Transition * findTransitionForDateTime(const LocalDateTime &ldt) const
Return the Transition matching the given dateTime.
uint8_t hour() const
Return the hour.
TimeOffset getDeltaOffset(acetime_t epochSeconds) const override
Return the DST delta offset at epochSeconds.
DateTuple transitionTimeS
Version of transitionTime in &#39;s&#39; mode, using the UTC offset of the previous Transition.
acetime_t startEpochSeconds
The calculated transition time of the given rule.
A simple tuple to represent a year/month pair.
static LocalDate forEpochSeconds(acetime_t epochSeconds)
Factory method using the number of seconds since AceTime epoch of 2000-01-01.
Definition: LocalDate.h:145
static TimeOffset forOffsetCode(int8_t offsetCode)
Create TimeOffset from the offset code.
Definition: TimeOffset.h:117
void resetTransitionHighWater()
Reset the TransitionStorage high water mark.
bool active
Flag used for 2 slightly different meanings at different stages of init() processing.
uint32_t getZoneId() const override
Return the unique stable zoneId.
The date (year, month, day), time (hour, minute, second) and offset from UTC (timeOffset).
void log() const
Used only for debugging.
Transition ** reservePrior()
Allocate one Transition just after the Active pool, but before the Candidate pool, to keep the most recent prior Transition.
static const int8_t kInvalidYearTiny
Sentinel yearTiny which indicates an error condition or sometimes a year that &#39;does not exist&#39;...
Definition: LocalDate.h:45
void resetHighWater()
Reset the high water mark.
Macros and definitions that provide a consistency layer among the various Arduino boards for compatib...
void log() const
Used only for debugging.
A thin wrapper that represents a time offset from a reference point, usually 00:00 at UTC...
Definition: TimeOffset.h:58
int8_t deltaCode
The DST delta code.
void swap(Transition **a, Transition **b)
Swap 2 transitions.
The date (year, month, day) representing the date without regards to time zone.
Definition: LocalDate.h:36
DateTuple untilDateTime
Until time expressed using the UTC offset of the current Transition.
void log() const
Used only for debugging.
ExtendedZoneProcessor(const extended::ZoneInfo *zoneInfo=nullptr)
Constructor.
uint8_t getTransitionHighWater() const
Get the TransitionStorage high water mark.
static const int16_t kEpochYear
Base year of epoch.
Definition: LocalDate.h:39
DateTuple untilDateTime
The effective until time of the matching ZoneEra.
DateTuple startDateTime
Start time expressed using the UTC offset of the current Transition.
uint8_t month() const
Return the month with January=1, December=12.
Definition: LocalDate.h:231
int16_t year() const
Return the full year instead of just the last 2 digits.
Definition: LocalDate.h:219
static basic::MonthDay calcStartDayOfMonth(int16_t year, uint8_t month, uint8_t onDayOfWeek, int8_t onDayOfMonth)
Calculate the actual (month, day) of the expresssion (onDayOfWeek >= onDayOfMonth) or (onDayOfWeek <=...
A tuple that represents a date and time, using a timeCode that tracks the time component using 15-min...
Data broker for accessing ZonePolicy in PROGMEM.
Definition: Brokers.h:297
const Transition * findTransition(acetime_t epochSeconds) const
Return the Transition matching the given epochSeconds.
OffsetDateTime getOffsetDateTime(const LocalDateTime &ldt) const override
Return the best estimate of the OffsetDateTime at the given LocalDateTime for the timezone of the cur...
int8_t offsetCode
The base offset code, not the total effective UTC offset.
ZoneRuleBroker rule
The Zone transition rule that matched for the the given year.
Data structure that captures the matching ZoneEra and its ZoneRule transitions for a given year...
Class that holds the date-time as the components (year, month, day, hour, minute, second) without reg...
Definition: LocalDateTime.h:27