AceTime  0.5.1
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 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  logging::print("DateTuple(%d-%u-%uT%d'%c')",
74  yearTiny+LocalDate::kEpochYear, month, day, timeCode, modifier);
75  }
76 };
77 
79 inline bool operator<(const DateTuple& a, const DateTuple& b) {
80  if (a.yearTiny < b.yearTiny) return true;
81  if (a.yearTiny > b.yearTiny) return false;
82  if (a.month < b.month) return true;
83  if (a.month > b.month) return false;
84  if (a.day < b.day) return true;
85  if (a.day > b.day) return false;
86  if (a.timeCode < b.timeCode) return true;
87  if (a.timeCode > b.timeCode) return false;
88  return false;
89 }
90 
91 inline bool operator>=(const DateTuple& a, const DateTuple& b) {
92  return ! (a < b);
93 }
94 
95 inline bool operator<=(const DateTuple& a, const DateTuple& b) {
96  return ! (b < a);
97 }
98 
99 inline bool operator>(const DateTuple& a, const DateTuple& b) {
100  return (b < a);
101 }
102 
104 inline bool operator==(const DateTuple& a, const DateTuple& b) {
105  return a.yearTiny == b.yearTiny
106  && a.month == b.month
107  && a.day == b.day
108  && a.timeCode == b.timeCode
109  && a.modifier == b.modifier;
110 }
111 
114  int8_t yearTiny;
115  uint8_t month;
116 };
117 
122 struct ZoneMatch {
125 
128 
131 
132  void log() const {
133  logging::print("ZoneMatch(");
134  logging::print("Start:"); startDateTime.log();
135  logging::print("; Until:"); untilDateTime.log();
136  logging::print("; Era: %snull", (era.isNotNull()) ? "!" : "");
137  logging::print(")");
138  }
139 };
140 
163 struct Transition {
165  static const uint8_t kAbbrevSize = basic::Transition::kAbbrevSize;
166 
168  const ZoneMatch* match;
169 
176 
184 
185  union {
192 
198  };
199 
200  union {
207 
213  };
214 
220 
222  char abbrev[kAbbrevSize];
223 
225  char letterBuf[2];
226 
228  acetime_t startEpochSeconds;
229 
242  bool active;
243 
251  int8_t offsetCode;
252 
254  int8_t deltaCode;
255 
256  //-------------------------------------------------------------------------
257 
258  const char* format() const {
259  return match->era.format();
260  }
261 
267  const char* letter() const {
268  // RULES column is '-' or hh:mm, so return nullptr to indicate this.
269  if (rule.isNull()) {
270  return nullptr;
271  }
272 
273  // RULES point to a named rule, and LETTER is a single, printable
274  // character.
275  char letter = rule.letter();
276  if (letter >= 32) {
277  return letterBuf;
278  }
279 
280  // RULES points to a named rule, and the LETTER is a string. The
281  // rule->letter is a non-printable number < 32, which is an index into
282  // a list of strings given by match->era->zonePolicy->letters[].
283  const ZonePolicyBroker policy = match->era.zonePolicy();
284  uint8_t numLetters = policy.numLetters();
285  if (letter >= numLetters) {
286  // This should never happen unless there is a programming error. If it
287  // does, return an empty string. (createTransitionForYear() sets
288  // letterBuf to a NUL terminated empty string if rule->letter < 32)
289  return letterBuf;
290  }
291 
292  // Return the string at index 'rule->letter'.
293  return policy.letter(letter);
294  }
295 
297  void log() const {
298  logging::print("Transition(");
299  if (sizeof(acetime_t) == sizeof(int)) {
300  logging::print("sE: %d", startEpochSeconds);
301  } else {
302  logging::print("sE: %ld", startEpochSeconds);
303  }
304  logging::print("; match: %snull", (match) ? "!" : "");
305  logging::print("; era: %snull",
306  (match && match->era.isNotNull()) ? "!" : "");
307  logging::print("; oCode: %d", offsetCode);
308  logging::print("; dCode: %d", deltaCode);
309  logging::print("; tt: "); transitionTime.log();
310  if (rule.isNotNull()) {
311  logging::print("; R.fY: %d", rule.fromYearTiny());
312  logging::print("; R.tY: %d", rule.toYearTiny());
313  logging::print("; R.M: %d", rule.inMonth());
314  logging::print("; R.dow: %d", rule.onDayOfWeek());
315  logging::print("; R.dom: %d", rule.onDayOfMonth());
316  }
317  }
318 };
319 
346 template<uint8_t SIZE>
348  public:
351 
353  void init() {
354  for (uint8_t i = 0; i < SIZE; i++) {
355  mTransitions[i] = &mPool[i];
356  }
357  mIndexPrior = 0;
358  mIndexCandidates = 0;
359  mIndexFree = 0;
360  }
361 
363  Transition* getPrior() { return mTransitions[mIndexPrior]; }
364 
366  void swap(Transition** a, Transition** b) {
367  Transition* tmp = *a;
368  *a = *b;
369  *b = tmp;
370  }
371 
381  mIndexCandidates = mIndexPrior;
382  mIndexFree = mIndexPrior;
383  }
384 
385  Transition** getCandidatePoolBegin() {
386  return &mTransitions[mIndexCandidates];
387  }
388  Transition** getCandidatePoolEnd() {
389  return &mTransitions[mIndexFree];
390  }
391 
392  Transition** getActivePoolBegin() { return &mTransitions[0]; }
393  Transition** getActivePoolEnd() { return &mTransitions[mIndexFree]; }
394 
401  // Set the internal high water mark. If that index becomes SIZE,
402  // then we know we have an overflow.
403  if (mIndexFree > mHighWater) {
404  mHighWater = mIndexFree;
405  }
406 
407  if (mIndexFree < SIZE) {
408  return mTransitions[mIndexFree];
409  } else {
410  return mTransitions[SIZE - 1];
411  }
412  }
413 
422  if (mIndexFree >= SIZE) return;
423  mIndexFree++;
424  mIndexPrior = mIndexFree;
425  mIndexCandidates = mIndexFree;
426  }
427 
434  mIndexCandidates++;
435  mIndexFree++;
436  return &mTransitions[mIndexPrior];
437  }
438 
441  swap(&mTransitions[mIndexPrior], &mTransitions[mIndexFree]);
442  }
443 
450  mIndexCandidates--;
451  }
452 
460  if (mIndexFree >= SIZE) return;
461  for (uint8_t i = mIndexFree; i > mIndexCandidates; i--) {
462  Transition* curr = mTransitions[i];
463  Transition* prev = mTransitions[i - 1];
464  if (curr->transitionTime >= prev->transitionTime) break;
465  mTransitions[i] = prev;
466  mTransitions[i - 1] = curr;
467  }
468  mIndexFree++;
469  }
470 
476  if (DEBUG) logging::println("addActiveCandidatesToActivePool()");
477  uint8_t iActive = mIndexPrior;
478  uint8_t iCandidate = mIndexCandidates;
479  for (; iCandidate < mIndexFree; iCandidate++) {
480  if (mTransitions[iCandidate]->active) {
481  if (iActive != iCandidate) {
482  swap(&mTransitions[iActive], &mTransitions[iCandidate]);
483  }
484  ++iActive;
485  }
486  }
487  mIndexPrior = iActive;
488  mIndexCandidates = iActive;
489  mIndexFree = iActive;
490  }
491 
500  const Transition* findTransition(acetime_t epochSeconds) const {
501  if (DEBUG) logging::println(
502  "findTransition(): mIndexFree: %d", mIndexFree);
503 
504  const Transition* match = nullptr;
505  for (uint8_t i = 0; i < mIndexFree; i++) {
506  const Transition* candidate = mTransitions[i];
507  if (candidate->startEpochSeconds > epochSeconds) break;
508  match = candidate;
509  }
510  return match;
511  }
512 
537  const {
538  if (DEBUG) logging::println(
539  "findTransitionForDateTime(): mIndexFree: %d", mIndexFree);
540 
541  // Convert to DateTuple. If the localDateTime is not a multiple of 15
542  // minutes, the comparision (startTime < localDate) will still be valid.
543  DateTuple localDate = { ldt.yearTiny(), ldt.month(), ldt.day(),
544  (int8_t) (ldt.hour() * 4 + ldt.minute() / 15), 'w' };
545  const Transition* match = nullptr;
546  for (uint8_t i = 0; i < mIndexFree; i++) {
547  const Transition* candidate = mTransitions[i];
548  if (candidate->startDateTime > localDate) break;
549  match = candidate;
550  }
551  return match;
552  }
553 
555  void log() const {
556  logging::println("TransitionStorage:");
557  logging::println(" mIndexPrior: %d", mIndexPrior);
558  logging::println(" mIndexCandidates: %d", mIndexCandidates);
559  logging::println(" mIndexFree: %d", mIndexFree);
560  if (mIndexPrior != 0) {
561  logging::println(" Actives:");
562  for (uint8_t i = 0; i < mIndexPrior; i++) {
563  mTransitions[i]->log();
564  logging::println();
565  }
566  }
567  if (mIndexPrior != mIndexCandidates) {
568  logging::print(" Prior: ");
569  mTransitions[mIndexPrior]->log();
570  logging::println();
571  }
572  if (mIndexCandidates != mIndexFree) {
573  logging::println(" Candidates:");
574  for (uint8_t i = mIndexCandidates; i < mIndexFree; i++) {
575  mTransitions[i]->log();
576  logging::println();
577  }
578  }
579  }
580 
582  void resetHighWater() { mHighWater = 0; }
583 
589  uint8_t getHighWater() const { return mHighWater; }
590 
591  private:
592  friend class ::TransitionStorageTest_getFreeAgent;
593  friend class ::TransitionStorageTest_getFreeAgent2;
594  friend class ::TransitionStorageTest_addFreeAgentToActivePool;
595  friend class ::TransitionStorageTest_reservePrior;
596  friend class ::TransitionStorageTest_addFreeAgentToCandidatePool;
597  friend class ::TransitionStorageTest_setFreeAgentAsPrior;
598  friend class ::TransitionStorageTest_addActiveCandidatesToActivePool;
599  friend class ::TransitionStorageTest_resetCandidatePool;
600 
602  Transition* getTransition(uint8_t i) { return mTransitions[i]; }
603 
604  Transition mPool[SIZE];
605  Transition* mTransitions[SIZE];
606  uint8_t mIndexPrior;
607  uint8_t mIndexCandidates;
608  uint8_t mIndexFree;
609 
611  uint8_t mHighWater = 0;
612 };
613 
614 } // namespace extended
615 
645  public:
651  const extended::ZoneInfo* zoneInfo = nullptr):
652  ZoneProcessor(kTypeExtended),
653  mZoneInfo(zoneInfo) {}
654 
656  const void* getZoneInfo() const override {
657  return mZoneInfo.zoneInfo();
658  }
659 
660  uint32_t getZoneId() const override { return mZoneInfo.zoneId(); }
661 
662  TimeOffset getUtcOffset(acetime_t epochSeconds) const override {
663  bool success = init(epochSeconds);
664  if (!success) return TimeOffset::forError();
665  const extended::Transition* transition = findTransition(epochSeconds);
666  return (transition)
668  transition->offsetCode + transition->deltaCode)
670  }
671 
672  TimeOffset getDeltaOffset(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 TimeOffset::forOffsetCode(transition->deltaCode);
677  }
678 
679  const char* getAbbrev(acetime_t epochSeconds) const override {
680  bool success = init(epochSeconds);
681  if (!success) return "";
682  const extended::Transition* transition = findTransition(epochSeconds);
683  return transition->abbrev;
684  }
685 
686  OffsetDateTime getOffsetDateTime(const LocalDateTime& ldt) const override {
687  TimeOffset offset;
688  bool success = init(ldt.localDate());
689  if (success) {
690  const extended::Transition* transition =
691  mTransitionStorage.findTransitionForDateTime(ldt);
692  offset = (transition)
694  transition->offsetCode + transition->deltaCode)
696  } else {
697  offset = TimeOffset::forError();
698  }
699 
700  auto odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset);
701  if (offset.isError()) {
702  return odt;
703  }
704 
705  // Normalize the OffsetDateTime, causing LocalDateTime in the DST
706  // transtion gap to be shifted forward one hour. For LocalDateTime in an
707  // overlap (DST->STD transition), the earlier UTC offset is selected// by
708  // findTransitionForDateTime(). Use that to calculate the epochSeconds,
709  // then recalculate the offset. Use this final offset to determine the
710  // effective OffsetDateTime that will survive a round-trip unchanged.
711  acetime_t epochSeconds = odt.toEpochSeconds();
712  const extended::Transition* transition =
713  mTransitionStorage.findTransition(epochSeconds);
714  offset = (transition)
716  transition->offsetCode + transition->deltaCode)
718  odt = OffsetDateTime::forEpochSeconds(epochSeconds, offset);
719  return odt;
720  }
721 
722  void printTo(Print& printer) const override;
723 
724  void printShortTo(Print& printer) const override;
725 
727  void log() const {
728  logging::println("ExtendedZoneProcessor:");
729  logging::println(" mYear: %d", mYear);
730  logging::println(" mNumMatches: %d", mNumMatches);
731  for (int i = 0; i < mNumMatches; i++) {
732  logging::print(" Match %d: ", i);
733  mMatches[i].log();
734  logging::println();
735  }
736  mTransitionStorage.log();
737  }
738 
741  mTransitionStorage.resetHighWater();
742  }
743 
745  uint8_t getTransitionHighWater() const {
746  return mTransitionStorage.getHighWater();
747  }
748 
749  private:
750  friend class ::ExtendedZoneProcessorTest_compareEraToYearMonth;
751  friend class ::ExtendedZoneProcessorTest_compareEraToYearMonth2;
752  friend class ::ExtendedZoneProcessorTest_createMatch;
753  friend class ::ExtendedZoneProcessorTest_findMatches_simple;
754  friend class ::ExtendedZoneProcessorTest_findMatches_named;
755  friend class ::ExtendedZoneProcessorTest_findCandidateTransitions;
756  friend class ::ExtendedZoneProcessorTest_findTransitionsFromNamedMatch;
757  friend class ::ExtendedZoneProcessorTest_getTransitionTime;
758  friend class ::ExtendedZoneProcessorTest_createTransitionForYear;
759  friend class ::ExtendedZoneProcessorTest_normalizeDateTuple;
760  friend class ::ExtendedZoneProcessorTest_expandDateTuple;
761  friend class ::ExtendedZoneProcessorTest_calcInteriorYears;
762  friend class ::ExtendedZoneProcessorTest_getMostRecentPriorYear;
763  friend class ::ExtendedZoneProcessorTest_compareTransitionToMatchFuzzy;
764  friend class ::ExtendedZoneProcessorTest_compareTransitionToMatch;
765  friend class ::ExtendedZoneProcessorTest_processActiveTransition;
766  friend class ::ExtendedZoneProcessorTest_fixTransitionTimes_generateStartUntilTimes;
767  friend class ::ExtendedZoneProcessorTest_createAbbreviation;
768  friend class ::ExtendedZoneProcessorTest_setZoneInfo;
769 
770  template<uint8_t SIZE, uint8_t TYPE, typename ZS, typename ZI, typename ZIB>
771  friend class ZoneProcessorCacheImpl; // setZoneInfo()
772 
773  // Disable copy constructor and assignment operator.
775  ExtendedZoneProcessor& operator=(const ExtendedZoneProcessor&) = delete;
776 
781  static const uint8_t kMaxMatches = 4;
782 
790  static const uint8_t kMaxTransitions = 8;
791 
796  static const uint8_t kMaxInteriorYears = 4;
797 
799  static const extended::ZoneEra kAnchorEra;
800 
801  bool equals(const ZoneProcessor& other) const override {
802  const auto& that = (const ExtendedZoneProcessor&) other;
803  return getZoneInfo() == that.getZoneInfo();
804  }
805 
807  void setZoneInfo(const void* zoneInfo) override {
808  if (mZoneInfo.zoneInfo() == zoneInfo) return;
809 
810  mZoneInfo = extended::ZoneInfoBroker(
811  (const extended::ZoneInfo*) zoneInfo);
812  mYear = 0;
813  mIsFilled = false;
814  mNumMatches = 0;
815  }
816 
821  const extended::Transition* findTransition(acetime_t epochSeconds) const {
822  return mTransitionStorage.findTransition(epochSeconds);
823  }
824 
826  bool init(acetime_t epochSeconds) const {
827  LocalDate ld = LocalDate::forEpochSeconds(epochSeconds);
828  return init(ld);
829  }
830 
835  bool init(const LocalDate& ld) const {
836  int16_t year = ld.year();
837  if (isFilled(year)) return true;
838  if (DEBUG) logging::println("init(): %d", year);
839 
840  mYear = year;
841  mNumMatches = 0; // clear cache
842  mTransitionStorage.init();
843 
844  if (year < mZoneInfo.startYear() - 1 || mZoneInfo.untilYear() < year) {
845  return false;
846  }
847 
848  extended::YearMonthTuple startYm = {
849  (int8_t) (year - LocalDate::kEpochYear - 1), 12 };
850  extended::YearMonthTuple untilYm = {
851  (int8_t) (year - LocalDate::kEpochYear + 1), 2 };
852 
853  mNumMatches = findMatches(mZoneInfo, startYm, untilYm, mMatches,
854  kMaxMatches);
855  if (DEBUG) log();
856  findTransitions(mTransitionStorage, mMatches, mNumMatches);
857  extended::Transition** begin = mTransitionStorage.getActivePoolBegin();
858  extended::Transition** end = mTransitionStorage.getActivePoolEnd();
859  fixTransitionTimes(begin, end);
860  generateStartUntilTimes(begin, end);
861  calcAbbreviations(begin, end);
862 
863  mIsFilled = true;
864  return true;
865  }
866 
868  bool isFilled(int16_t year) const {
869  return mIsFilled && (year == mYear);
870  }
871 
879  static uint8_t findMatches(const extended::ZoneInfoBroker zoneInfo,
880  const extended::YearMonthTuple& startYm,
881  const extended::YearMonthTuple& untilYm,
882  extended::ZoneMatch* matches, uint8_t maxMatches) {
883  if (DEBUG) logging::println("findMatches()");
884  uint8_t iMatch = 0;
886  for (uint8_t iEra = 0; iEra < zoneInfo.numEras(); iEra++) {
887  const extended::ZoneEraBroker era = zoneInfo.era(iEra);
888  if (eraOverlapsInterval(prev, era, startYm, untilYm)) {
889  if (iMatch < maxMatches) {
890  matches[iMatch] = createMatch(prev, era, startYm, untilYm);
891  iMatch++;
892  }
893  }
894  prev = era;
895  }
896  return iMatch;
897  }
898 
909  static bool eraOverlapsInterval(
910  const extended::ZoneEraBroker prev,
911  const extended::ZoneEraBroker era,
912  const extended::YearMonthTuple& startYm,
913  const extended::YearMonthTuple& untilYm) {
914  return compareEraToYearMonth(prev, untilYm.yearTiny, untilYm.month) < 0
915  && compareEraToYearMonth(era, startYm.yearTiny, startYm.month) > 0;
916  }
917 
919  static int8_t compareEraToYearMonth(const extended::ZoneEraBroker era,
920  int8_t yearTiny, uint8_t month) {
921  if (era.untilYearTiny() < yearTiny) return -1;
922  if (era.untilYearTiny() > yearTiny) return 1;
923  if (era.untilMonth() < month) return -1;
924  if (era.untilMonth() > month) return 1;
925  if (era.untilDay() > 1) return 1;
926  //if (era.untilTimeCode() < 0) return -1; // never possible
927  if (era.untilTimeCode() > 0) return 1;
928  return 0;
929  }
930 
937  static extended::ZoneMatch createMatch(
938  const extended::ZoneEraBroker prev,
939  const extended::ZoneEraBroker era,
940  const extended::YearMonthTuple& startYm,
941  const extended::YearMonthTuple& untilYm) {
942  extended::DateTuple startDate = {
943  prev.untilYearTiny(), prev.untilMonth(), prev.untilDay(),
944  (int8_t) prev.untilTimeCode(), prev.untilTimeModifier()
945  };
946  extended::DateTuple lowerBound = {
947  startYm.yearTiny, startYm.month, 1, 0, 'w'
948  };
949  if (startDate < lowerBound) {
950  startDate = lowerBound;
951  }
952 
953  extended::DateTuple untilDate = {
954  era.untilYearTiny(), era.untilMonth(), era.untilDay(),
955  (int8_t) era.untilTimeCode(), era.untilTimeModifier()
956  };
957  extended::DateTuple upperBound = {
958  untilYm.yearTiny, untilYm.month, 1, 0, 'w'
959  };
960  if (upperBound < untilDate) {
961  untilDate = upperBound;
962  }
963 
964  return {startDate, untilDate, era};
965  }
966 
971  static void findTransitions(
973  extended::ZoneMatch* matches,
974  uint8_t numMatches) {
975  if (DEBUG) logging::println("findTransitions()");
976  for (uint8_t i = 0; i < numMatches; i++) {
977  findTransitionsForMatch(transitionStorage, &matches[i]);
978  }
979  }
980 
982  static void findTransitionsForMatch(
984  const extended::ZoneMatch* match) {
985  if (DEBUG) logging::println("findTransitionsForMatch()");
986  const extended::ZonePolicyBroker policy = match->era.zonePolicy();
987  if (policy.isNull()) {
988  findTransitionsFromSimpleMatch(transitionStorage, match);
989  } else {
990  findTransitionsFromNamedMatch(transitionStorage, match);
991  }
992  }
993 
994  static void findTransitionsFromSimpleMatch(
996  const extended::ZoneMatch* match) {
997  if (DEBUG) logging::println("findTransitionsFromSimpleMatch()");
998  extended::Transition* freeTransition = transitionStorage.getFreeAgent();
999  createTransitionForYear(freeTransition, 0 /*not used*/,
1000  extended::ZoneRuleBroker(nullptr) /*rule*/, match);
1001  transitionStorage.addFreeAgentToActivePool();
1002  }
1003 
1004  static void findTransitionsFromNamedMatch(
1006  const extended::ZoneMatch* match) {
1007  if (DEBUG) logging::println("findTransitionsFromNamedMatch()");
1008  transitionStorage.resetCandidatePool();
1009  if (DEBUG) { match->log(); logging::println(); }
1010  findCandidateTransitions(transitionStorage, match);
1011  if (DEBUG) { transitionStorage.log(); logging::println(); }
1012  fixTransitionTimes(
1013  transitionStorage.getCandidatePoolBegin(),
1014  transitionStorage.getCandidatePoolEnd());
1015  selectActiveTransitions(transitionStorage, match);
1016  if (DEBUG) { transitionStorage.log(); logging::println(); }
1017 
1018  transitionStorage.addActiveCandidatesToActivePool();
1019  if (DEBUG) { transitionStorage.log(); logging::println(); }
1020  }
1021 
1022  static void findCandidateTransitions(
1024  const extended::ZoneMatch* match) {
1025  if (DEBUG) {
1026  logging::print("findCandidateTransitions(): ");
1027  match->log();
1028  logging::println();
1029  }
1030  const extended::ZonePolicyBroker policy = match->era.zonePolicy();
1031  uint8_t numRules = policy.numRules();
1032  int8_t startY = match->startDateTime.yearTiny;
1033  int8_t endY = match->untilDateTime.yearTiny;
1034 
1035  extended::Transition** prior = transitionStorage.reservePrior();
1036  (*prior)->active = false; // indicates "no prior transition"
1037  for (uint8_t r = 0; r < numRules; r++) {
1038  const extended::ZoneRuleBroker rule = policy.rule(r);
1039 
1040  // Add Transitions for interior years
1041  int8_t interiorYears[kMaxInteriorYears];
1042  uint8_t numYears = calcInteriorYears(interiorYears, kMaxInteriorYears,
1043  rule.fromYearTiny(), rule.toYearTiny(), startY, endY);
1044  for (uint8_t y = 0; y < numYears; y++) {
1045  int8_t year = interiorYears[y];
1046  extended::Transition* t = transitionStorage.getFreeAgent();
1047  createTransitionForYear(t, year, rule, match);
1048  int8_t status = compareTransitionToMatchFuzzy(t, match);
1049  if (status < 0) {
1050  setAsPriorTransition(transitionStorage, t);
1051  } else if (status == 1) {
1052  transitionStorage.addFreeAgentToCandidatePool();
1053  }
1054  }
1055 
1056  // Add Transition for prior year
1057  int8_t priorYear = getMostRecentPriorYear(
1058  rule.fromYearTiny(), rule.toYearTiny(), startY, endY);
1059  if (priorYear != LocalDate::kInvalidYearTiny) {
1060  if (DEBUG) logging::println(
1061  "findCandidateTransitions(): priorYear: %d", priorYear);
1062  extended::Transition* t = transitionStorage.getFreeAgent();
1063  createTransitionForYear(t, priorYear, rule, match);
1064  setAsPriorTransition(transitionStorage, t);
1065  }
1066  }
1067 
1068  // Add the reserved prior into the Candidate pool only if 'active' is
1069  // true, meaning that a prior was found.
1070  if ((*prior)->active) {
1071  if (DEBUG) logging::println(
1072  "findCandidateTransitions(): adding prior to Candidate pool");
1073  transitionStorage.addPriorToCandidatePool();
1074  }
1075  }
1076 
1081  static uint8_t calcInteriorYears(int8_t* interiorYears,
1082  uint8_t maxInteriorYears, int8_t fromYear, int8_t toYear,
1083  int8_t startYear, int8_t endYear) {
1084  uint8_t i = 0;
1085  for (int8_t year = startYear; year <= endYear; year++) {
1086  if (fromYear <= year && year <= toYear) {
1087  interiorYears[i] = year;
1088  i++;
1089  if (i >= maxInteriorYears) break;
1090  }
1091  }
1092  return i;
1093  }
1094 
1101  static void createTransitionForYear(extended::Transition* t, int8_t year,
1102  const extended::ZoneRuleBroker rule,
1103  const extended::ZoneMatch* match) {
1104  t->match = match;
1105  t->rule = rule;
1106  t->offsetCode = match->era.offsetCode();
1107  t->letterBuf[0] = '\0';
1108 
1109  if (rule.isNotNull()) {
1110  t->transitionTime = getTransitionTime(year, rule);
1111  t->deltaCode = rule.deltaCode();
1112 
1113  char letter = rule.letter();
1114  if (letter >= 32) {
1115  // If LETTER is a '-', treat it the same as an empty string.
1116  if (letter != '-') {
1117  t->letterBuf[0] = letter;
1118  t->letterBuf[1] = '\0';
1119  }
1120  } else {
1121  // rule->letter is a long string, so is referenced as an offset index
1122  // into the ZonePolicy.letters array. The string cannot fit in
1123  // letterBuf, so will be retrieved by the letter() method below.
1124  }
1125  } else {
1126  t->transitionTime = match->startDateTime;
1127  t->deltaCode = match->era.deltaCode();
1128  }
1129  }
1130 
1136  static int8_t getMostRecentPriorYear(int8_t fromYear, int8_t toYear,
1137  int8_t startYear, int8_t /*endYear*/) {
1138  if (fromYear < startYear) {
1139  if (toYear < startYear) {
1140  return toYear;
1141  } else {
1142  return startYear - 1;
1143  }
1144  } else {
1146  }
1147  }
1148 
1149  static extended::DateTuple getTransitionTime(
1150  int8_t yearTiny, const extended::ZoneRuleBroker rule) {
1152  yearTiny + LocalDate::kEpochYear, rule.inMonth(), rule.onDayOfWeek(),
1153  rule.onDayOfMonth());
1154  return {yearTiny, monthDay.month, monthDay.day,
1155  (int8_t) rule.atTimeCode(), rule.atTimeModifier()};
1156  }
1157 
1168  static int8_t compareTransitionToMatchFuzzy(
1169  const extended::Transition* t, const extended::ZoneMatch* match) {
1170  int16_t ttMonths = t->transitionTime.yearTiny * 12
1171  + t->transitionTime.month;
1172 
1173  int16_t matchStartMonths = match->startDateTime.yearTiny * 12
1174  + match->startDateTime.month;
1175  if (ttMonths < matchStartMonths - 1) return -1;
1176 
1177  int16_t matchUntilMonths = match->untilDateTime.yearTiny * 12
1178  + match->untilDateTime.month;
1179  if (matchUntilMonths + 2 <= ttMonths) return 2;
1180 
1181  return 1;
1182  }
1183 
1185  static void setAsPriorTransition(
1187  extended::Transition* t) {
1188  if (DEBUG) logging::println("setAsPriorTransition()");
1189  extended::Transition* prior = transitionStorage.getPrior();
1190  if (prior->active) {
1191  if (prior->transitionTime < t->transitionTime) {
1192  t->active = true;
1193  transitionStorage.setFreeAgentAsPrior();
1194  }
1195  } else {
1196  t->active = true;
1197  transitionStorage.setFreeAgentAsPrior();
1198  }
1199  }
1200 
1209  static void fixTransitionTimes(
1210  extended::Transition** begin, extended::Transition** end) {
1211  if (DEBUG) logging::println("fixTransitionTimes(): #transitions: %d;",
1212  (int) (end - begin));
1213 
1214  // extend first Transition to -infinity
1215  extended::Transition* prev = *begin;
1216 
1217  for (extended::Transition** iter = begin; iter != end; ++iter) {
1218  extended::Transition* curr = *iter;
1219  if (DEBUG) {
1220  logging::println("fixTransitionTimes(): LOOP");
1221  curr->log();
1222  logging::println();
1223  }
1224  expandDateTuple(&curr->transitionTime,
1225  &curr->transitionTimeS, &curr->transitionTimeU,
1226  prev->offsetCode, prev->deltaCode);
1227  prev = curr;
1228  }
1229  if (DEBUG) logging::println("fixTransitionTimes(): END");
1230  }
1231 
1237  static void expandDateTuple(extended::DateTuple* tt,
1239  int8_t offsetCode, int8_t deltaCode) {
1240  if (DEBUG) logging::println("expandDateTuple()");
1241  if (tt->modifier == 's') {
1242  *tts = *tt;
1243  *ttu = {tt->yearTiny, tt->month, tt->day,
1244  (int8_t) (tt->timeCode - offsetCode), 'u'};
1245  *tt = {tt->yearTiny, tt->month, tt->day,
1246  (int8_t) (tt->timeCode + deltaCode), 'w'};
1247  } else if (tt->modifier == 'u') {
1248  *ttu = *tt;
1249  *tts = {tt->yearTiny, tt->month, tt->day,
1250  (int8_t) (tt->timeCode + offsetCode), 's'};
1251  *tt = {tt->yearTiny, tt->month, tt->day,
1252  (int8_t) (tt->timeCode + offsetCode + deltaCode), 'w'};
1253  } else {
1254  // Explicit set the modifier to 'w' in case it was something else.
1255  tt->modifier = 'w';
1256  *tts = {tt->yearTiny, tt->month, tt->day,
1257  (int8_t) (tt->timeCode - deltaCode), 's'};
1258  *ttu = {tt->yearTiny, tt->month, tt->day,
1259  (int8_t) (tt->timeCode - deltaCode - offsetCode), 'u'};
1260  }
1261 
1262  if (DEBUG) logging::println("expandDateTuple(): normalizeDateTuple(): 1");
1263  normalizeDateTuple(tt);
1264  if (DEBUG) logging::println("expandDateTuple(): normalizeDateTuple(): 2");
1265  normalizeDateTuple(tts);
1266  if (DEBUG) logging::println("expandDateTuple(): normalizeDateTuple(): 3");
1267  normalizeDateTuple(ttu);
1268  }
1269 
1271  static void normalizeDateTuple(extended::DateTuple* dt) {
1272  const int8_t kOneDayAsCode = 4 * 24;
1273  if (dt->timeCode <= -kOneDayAsCode) {
1275  dt->yearTiny, dt->month, dt->day);
1276  local_date_mutation::decrementOneDay(ld);
1277  dt->yearTiny = ld.yearTiny();
1278  dt->month = ld.month();
1279  dt->day = ld.day();
1280  dt->timeCode += kOneDayAsCode;
1281  } else if (kOneDayAsCode <= dt->timeCode) {
1283  dt->yearTiny, dt->month, dt->day);
1284  local_date_mutation::incrementOneDay(ld);
1285  dt->yearTiny = ld.yearTiny();
1286  dt->month = ld.month();
1287  dt->day = ld.day();
1288  dt->timeCode -= kOneDayAsCode;
1289  } else {
1290  // do nothing
1291  }
1292  }
1293 
1298  static void selectActiveTransitions(
1300  const extended::ZoneMatch* match) {
1301  extended::Transition** begin = transitionStorage.getCandidatePoolBegin();
1302  extended::Transition** end = transitionStorage.getCandidatePoolEnd();
1303  if (DEBUG) logging::println("selectActiveTransitions(): #candidates: %d",
1304  (int) (end - begin));
1305  extended::Transition* prior = nullptr;
1306  for (extended::Transition** iter = begin; iter != end; ++iter) {
1307  extended::Transition* transition = *iter;
1308  processActiveTransition(match, transition, &prior);
1309  }
1310 
1311  // If the latest prior transition is found, shift it to start at the
1312  // startDateTime of the current match.
1313  if (prior) {
1314  if (DEBUG) logging::println(
1315  "selectActiveTransitions(): found latest prior");
1316  prior->originalTransitionTime = prior->transitionTime;
1317  prior->transitionTime = match->startDateTime;
1318  }
1319  }
1320 
1328  static void processActiveTransition(
1329  const extended::ZoneMatch* match,
1330  extended::Transition* transition,
1331  extended::Transition** prior) {
1332  int8_t status = compareTransitionToMatch(transition, match);
1333  if (status == 2) {
1334  transition->active = false;
1335  } else if (status == 1) {
1336  transition->active = true;
1337  } else if (status == 0) {
1338  if (*prior) {
1339  (*prior)->active = false;
1340  }
1341  transition->active = true;
1342  (*prior) = transition;
1343  } else { // (status < 0)
1344  if (*prior) {
1345  if ((*prior)->transitionTime < transition->transitionTime) {
1346  (*prior)->active = false;
1347  transition->active = true;
1348  (*prior) = transition;
1349  }
1350  } else {
1351  transition->active = true;
1352  (*prior) = transition;
1353  }
1354  }
1355  }
1356 
1371  static int8_t compareTransitionToMatch(
1372  const extended::Transition* transition,
1373  const extended::ZoneMatch* match) {
1374  const extended::DateTuple* transitionTime;
1375 
1376  const extended::DateTuple& matchStart = match->startDateTime;
1377  if (matchStart.modifier == 's') {
1378  transitionTime = &transition->transitionTimeS;
1379  } else if (matchStart.modifier == 'u') {
1380  transitionTime = &transition->transitionTimeU;
1381  } else { // assume 'w'
1382  transitionTime = &transition->transitionTime;
1383  }
1384  if (*transitionTime < matchStart) return -1;
1385  if (*transitionTime == matchStart) return 0;
1386 
1387  const extended::DateTuple& matchUntil = match->untilDateTime;
1388  if (matchUntil.modifier == 's') {
1389  transitionTime = &transition->transitionTimeS;
1390  } else if (matchUntil.modifier == 'u') {
1391  transitionTime = &transition->transitionTimeU;
1392  } else { // assume 'w'
1393  transitionTime = &transition->transitionTime;
1394  }
1395  if (*transitionTime < matchUntil) return 1;
1396  return 2;
1397  }
1398 
1404  static void generateStartUntilTimes(
1405  extended::Transition** begin, extended::Transition** end) {
1406  if (DEBUG) logging::println(
1407  "generateStartUntilTimes(): #transitions: %d;",
1408  (int) (end - begin));
1409 
1410  extended::Transition* prev = *begin;
1411  bool isAfterFirst = false;
1412 
1413  for (extended::Transition** iter = begin; iter != end; ++iter) {
1414  extended::Transition* const t = *iter;
1415 
1416  // 1) Update the untilDateTime of the previous Transition
1417  const extended::DateTuple& tt = t->transitionTime;
1418  if (isAfterFirst) {
1419  prev->untilDateTime = tt;
1420  }
1421 
1422  // 2) Calculate the current startDateTime by shifting the
1423  // transitionTime (represented in the UTC offset of the previous
1424  // transition) into the UTC offset of the *current* transition.
1425  int8_t code = tt.timeCode - prev->offsetCode - prev->deltaCode
1426  + t->offsetCode + t->deltaCode;
1427  t->startDateTime = {tt.yearTiny, tt.month, tt.day, code, tt.modifier};
1428  normalizeDateTuple(&t->startDateTime);
1429 
1430  // 3) The epochSecond of the 'transitionTime' is determined by the
1431  // UTC offset of the *previous* Transition. However, the
1432  // transitionTime can be represented by an illegal time (e.g. 24:00).
1433  // So, it is better to use the properly normalized startDateTime
1434  // (calculated above) with the *current* UTC offset.
1435  //
1436  // NOTE: We should also be able to calculate this directly from
1437  // 'transitionTimeU' which should still be a valid field, because it
1438  // hasn't been clobbered by 'untilDateTime' yet. Not sure if this saves
1439  // any CPU time though, since we still need to mutiply by 900.
1440  const extended::DateTuple& st = t->startDateTime;
1441  const acetime_t offsetSeconds = (acetime_t) 900
1442  * (st.timeCode - t->offsetCode - t->deltaCode);
1444  st.yearTiny, st.month, st.day);
1445  t->startEpochSeconds = ld.toEpochSeconds() + offsetSeconds;
1446 
1447  prev = t;
1448  isAfterFirst = true;
1449  }
1450 
1451  // The last Transition's until time is the until time of the ZoneMatch.
1452  extended::DateTuple untilTime = prev->match->untilDateTime;
1453  extended::DateTuple untilTimeS; // needed only for expandDateTuple
1454  extended::DateTuple untilTimeU; // needed only for expandDateTuple
1455  expandDateTuple(&untilTime, &untilTimeS, &untilTimeU,
1456  prev->offsetCode, prev->deltaCode);
1457  prev->untilDateTime = untilTime;
1458  }
1459 
1463  static void calcAbbreviations(
1464  extended::Transition** begin, extended::Transition** end) {
1465  if (DEBUG) logging::println("calcAbbreviations(): #transitions: %d;",
1466  (int) (end - begin));
1467  for (extended::Transition** iter = begin; iter != end; ++iter) {
1468  extended::Transition* const t = *iter;
1469  if (DEBUG) logging::println(
1470  "calcAbbreviations(): format:%s, deltaCode:%d, letter:%s",
1471  t->format(), t->deltaCode, t->letter());
1472  createAbbreviation(t->abbrev, extended::Transition::kAbbrevSize,
1473  t->format(), t->deltaCode, t->letter());
1474  }
1475  }
1476 
1511  static void createAbbreviation(char* dest, uint8_t destSize,
1512  const char* format, uint8_t deltaCode, const char* letterString) {
1513  // Check if RULES column is empty. Ignore the deltaCode because if
1514  // letterString is nullptr, we can only just copy the whole thing.
1515  if (letterString == nullptr) {
1516  strncpy(dest, format, destSize);
1517  dest[destSize - 1] = '\0';
1518  return;
1519  }
1520 
1521  // Check if FORMAT contains a '%'.
1522  if (strchr(format, '%') != nullptr) {
1523  copyAndReplace(dest, destSize, format, '%', letterString);
1524  } else {
1525  // Check if FORMAT contains a '/'.
1526  const char* slashPos = strchr(format, '/');
1527  if (slashPos != nullptr) {
1528  if (deltaCode == 0) {
1529  uint8_t headLength = (slashPos - format);
1530  if (headLength >= destSize) headLength = destSize - 1;
1531  memcpy(dest, format, headLength);
1532  dest[headLength] = '\0';
1533  } else {
1534  uint8_t tailLength = strlen(slashPos+1);
1535  if (tailLength >= destSize) tailLength = destSize - 1;
1536  memcpy(dest, slashPos+1, tailLength);
1537  dest[tailLength] = '\0';
1538  }
1539  } else {
1540  // Just copy the FORMAT disregarding deltaCode and letterString.
1541  strncpy(dest, format, destSize);
1542  dest[destSize - 1] = '\0';
1543  }
1544  }
1545  }
1546 
1552  static void copyAndReplace(char* dst, uint8_t dstSize, const char* src,
1553  char oldChar, const char* newString) {
1554  while (*src != '\0' && dstSize > 0) {
1555  if (*src == oldChar) {
1556  while (*newString != '\0' && dstSize > 0) {
1557  *dst++ = *newString++;
1558  dstSize--;
1559  }
1560  src++;
1561  } else {
1562  *dst++ = *src++;
1563  dstSize--;
1564  }
1565  }
1566 
1567  if (dstSize == 0) {
1568  --dst;
1569  }
1570  *dst = '\0';
1571  }
1572 
1573  extended::ZoneInfoBroker mZoneInfo;
1574 
1575  mutable int16_t mYear = 0; // maybe create LocalDate::kInvalidYear?
1576  mutable bool mIsFilled = false;
1577  // NOTE: Maybe move mNumMatches and mMatches into a MatchStorage object.
1578  mutable uint8_t mNumMatches = 0; // actual number of matches
1579  mutable extended::ZoneMatch mMatches[kMaxMatches];
1580  mutable extended::TransitionStorage<kMaxTransitions> mTransitionStorage;
1581 };
1582 
1583 } // namespace ace_time
1584 
1585 #undef DEBUG
1586 
1587 #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