AceTime  0.1
Date and time classes for Arduino that supports the TZ DAtabase, and a system clock synchronized from an NTP server or an RTC chip.
ExtendedZoneSpecifier.h
1 #ifndef ACE_TIME_EXTENDED_ZONE_SPECIFIER_H
2 #define ACE_TIME_EXTENDED_ZONE_SPECIFIER_H
3 
4 #include <Arduino.h>
5 #include <string.h> // memcpy()
6 #include <stdint.h>
7 #include "common/ZonePolicy.h"
8 #include "common/ZoneInfo.h"
9 #include "common/logger.h"
10 #include "TimeOffset.h"
11 #include "LocalDate.h"
12 #include "OffsetDateTime.h"
13 #include "ZoneSpecifier.h"
14 #include "BasicZoneSpecifier.h"
15 #include "local_date_mutation.h"
16 
17 #define DEBUG 0
18 
19 class ExtendedZoneSpecifierTest_compareEraToYearMonth;
20 class ExtendedZoneSpecifierTest_createMatch;
21 class ExtendedZoneSpecifierTest_findMatches_simple;
22 class ExtendedZoneSpecifierTest_findMatches_named;
23 class ExtendedZoneSpecifierTest_findCandidateTransitions;
24 class ExtendedZoneSpecifierTest_findTransitionsFromNamedMatch;
25 class ExtendedZoneSpecifierTest_getTransitionTime;
26 class ExtendedZoneSpecifierTest_createTransitionForYear;
27 class ExtendedZoneSpecifierTest_normalizeDateTuple;
28 class ExtendedZoneSpecifierTest_expandDateTuple;
29 class ExtendedZoneSpecifierTest_calcInteriorYears;
30 class ExtendedZoneSpecifierTest_getMostRecentPriorYear;
31 class ExtendedZoneSpecifierTest_compareTransitionToMatchFuzzy;
32 class ExtendedZoneSpecifierTest_compareTransitionToMatch;
33 class ExtendedZoneSpecifierTest_processActiveTransition;
34 class ExtendedZoneSpecifierTest_fixTransitionTimes_generateStartUntilTimes;
35 class ExtendedZoneSpecifierTest_createAbbreviation;
36 class TransitionStorageTest_getFreeAgent;
37 class TransitionStorageTest_getFreeAgent2;
38 class TransitionStorageTest_addFreeAgentToActivePool;
39 class TransitionStorageTest_reservePrior;
40 class TransitionStorageTest_addFreeAgentToCandidatePool;
41 class TransitionStorageTest_setFreeAgentAsPrior;
42 class TransitionStorageTest_addActiveCandidatesToActivePool;
43 class TransitionStorageTest_resetCandidatePool;
44 
45 namespace ace_time {
46 
47 namespace extended {
48 
49 // NOTE: Consider compressing 'modifier' into 'month' field
54 struct DateTuple {
55  int8_t yearTiny; // [-127, 126], 127 will cause bugs
56  uint8_t month; // [1-12]
57  uint8_t day; // [1-31]
58  int8_t timeCode; // 15-min intervals, negative values allowed
59  uint8_t modifier; // 's', 'w', 'u'
60 
62  void log() const {
63  logging::print("DateTuple(%d-%u-%uT%d'%c')",
64  yearTiny+LocalDate::kEpochYear, month, day, timeCode, modifier);
65  }
66 };
67 
69 inline bool operator<(const DateTuple& a, const DateTuple& b) {
70  if (a.yearTiny < b.yearTiny) return true;
71  if (a.yearTiny > b.yearTiny) return false;
72  if (a.month < b.month) return true;
73  if (a.month > b.month) return false;
74  if (a.day < b.day) return true;
75  if (a.day > b.day) return false;
76  if (a.timeCode < b.timeCode) return true;
77  if (a.timeCode > b.timeCode) return false;
78  return false;
79 }
80 
81 inline bool operator>=(const DateTuple& a, const DateTuple& b) {
82  return ! (a < b);
83 }
84 
85 inline bool operator<=(const DateTuple& a, const DateTuple& b) {
86  return ! (b < a);
87 }
88 
89 inline bool operator>(const DateTuple& a, const DateTuple& b) {
90  return (b < a);
91 }
92 
94 inline bool operator==(const DateTuple& a, const DateTuple& b) {
95  return a.yearTiny == b.yearTiny
96  && a.month == b.month
97  && a.day == b.day
98  && a.timeCode == b.timeCode
99  && a.modifier == b.modifier;
100 }
101 
104  int8_t yearTiny;
105  uint8_t month;
106 };
107 
112 struct ZoneMatch {
115 
118 
121 
122  void log() const {
123  logging::print("ZoneMatch(");
124  logging::print("Start:"); startDateTime.log();
125  logging::print("; Until:"); untilDateTime.log();
126  logging::print("; Era: %snull", (era) ? "!" : "");
127  logging::print(")");
128  }
129 };
130 
146 struct Transition {
148  static const uint8_t kAbbrevSize = basic::Transition::kAbbrevSize;
149 
151  const ZoneMatch* match;
152 
159 
167 
168  union {
175 
181  };
182 
183  union {
190 
196  };
197 
203 
205  char abbrev[kAbbrevSize];
206 
207 
209  acetime_t startEpochSeconds;
210 
212  bool active;
213 
214  //-------------------------------------------------------------------------
215 
216  const char* format() const {
217  return match->era->format;
218  }
219 
224  int8_t offsetCode() const {
225  return match->era->offsetCode;
226  }
227 
237  const char* letter() const {
238  // Char buffer to allow a single letter to be returned as a (char*).
239  // Not thread-safe.
240  static char letterBuf[2];
241 
242  // RULES column is '-' or hh:mm, so return nullptr to indicate this.
243  if (!rule) {
244  return nullptr;
245  }
246 
247  // RULES point to a named rule, and LETTER is a single, printable
248  // character. However, if it's a '-', convert into an empty string "".
249  if (rule->letter >= 32) {
250  if (rule->letter == '-') {
251  letterBuf[0] = '\0';
252  } else {
253  letterBuf[0] = rule->letter;
254  letterBuf[1] = '\0';
255  }
256  return letterBuf;
257  }
258 
259  // RULES points to a named rule, and the LETTER is a string. The
260  // rule->letter is a non-printable number < 32, which is an index into
261  // a list of strings given by match->era->zonePolicy->letters[].
262  const ZonePolicy* policy = match->era->zonePolicy;
263  uint8_t numLetters = policy->numLetters;
264  if (rule->letter >= numLetters) {
265  // This should never happen unless there is a programming error.
266  // If it does, return an empty string.
267  letterBuf[0] = '\0';
268  return letterBuf;
269  }
270 
271  // Return the string at index 'rule->letter'.
272  return policy->letters[rule->letter];
273  }
274 
276  int8_t deltaCode() const {
277  return (rule) ? rule->deltaCode : match->era->deltaCode;
278  }
279 
281  void log() const {
282  logging::print("Transition(");
283  if (sizeof(acetime_t) == sizeof(int)) {
284  logging::print("sE: %d", startEpochSeconds);
285  } else {
286  logging::print("sE: %ld", startEpochSeconds);
287  }
288  logging::print("; match: %snull", (match) ? "!" : "");
289  logging::print("; era: %snull", (match && match->era) ? "!" : "");
290  logging::print("; oCode: %d", offsetCode());
291  logging::print("; dCode: %d", deltaCode());
292  logging::print("; tt: "); transitionTime.log();
293  if (rule != nullptr) {
294  logging::print("; R.fY: %d", rule->fromYearTiny);
295  logging::print("; R.tY: %d", rule->toYearTiny);
296  logging::print("; R.M: %d", rule->inMonth);
297  logging::print("; R.dow: %d", rule->onDayOfWeek);
298  logging::print("; R.dom: %d", rule->onDayOfMonth);
299  }
300  }
301 };
302 
329 template<uint8_t SIZE>
331  public:
334 
336  void init() {
337  for (uint8_t i = 0; i < SIZE; i++) {
338  mTransitions[i] = &mPool[i];
339  }
340  mIndexPrior = 0;
341  mIndexCandidates = 0;
342  mIndexFree = 0;
343  }
344 
346  Transition* getPrior() { return mTransitions[mIndexPrior]; }
347 
349  void swap(Transition** a, Transition** b) {
350  Transition* tmp = *a;
351  *a = *b;
352  *b = tmp;
353  }
354 
364  mIndexCandidates = mIndexPrior;
365  mIndexFree = mIndexPrior;
366  }
367 
368  Transition** getCandidatePoolBegin() {
369  return &mTransitions[mIndexCandidates];
370  }
371  Transition** getCandidatePoolEnd() {
372  return &mTransitions[mIndexFree];
373  }
374 
375  Transition** getActivePoolBegin() { return &mTransitions[0]; }
376  Transition** getActivePoolEnd() { return &mTransitions[mIndexFree]; }
377 
384  // Set the internal high water mark. If that index becomes SIZE,
385  // then we know we have an overflow.
386  if (mIndexFree > mHighWater) {
387  mHighWater = mIndexFree;
388  }
389 
390  if (mIndexFree < SIZE) {
391  return mTransitions[mIndexFree];
392  } else {
393  return mTransitions[SIZE - 1];
394  }
395  }
396 
405  if (mIndexFree >= SIZE) return;
406  mIndexFree++;
407  mIndexPrior = mIndexFree;
408  mIndexCandidates = mIndexFree;
409  }
410 
417  mIndexCandidates++;
418  mIndexFree++;
419  return &mTransitions[mIndexPrior];
420  }
421 
424  swap(&mTransitions[mIndexPrior], &mTransitions[mIndexFree]);
425  }
426 
433  mIndexCandidates--;
434  }
435 
442  if (mIndexFree >= SIZE) return;
443  for (uint8_t i = mIndexFree; i > mIndexCandidates; i--) {
444  Transition* curr = mTransitions[i];
445  Transition* prev = mTransitions[i - 1];
446  if (curr->transitionTime < prev->transitionTime) {
447  mTransitions[i] = prev;
448  mTransitions[i - 1] = curr;
449  } else {
450  break;
451  }
452  }
453  mIndexFree++;
454  }
455 
461  if (DEBUG) logging::println("addActiveCandidatesToActivePool()");
462  uint8_t iActive = mIndexPrior;
463  uint8_t iCandidate = mIndexCandidates;
464  for (; iCandidate < mIndexFree; iCandidate++) {
465  if (mTransitions[iCandidate]->active) {
466  if (iActive != iCandidate) {
467  swap(&mTransitions[iActive], &mTransitions[iCandidate]);
468  }
469  ++iActive;
470  }
471  }
472  mIndexPrior = iActive;
473  mIndexCandidates = iActive;
474  mIndexFree = iActive;
475  }
476 
481  const Transition* findTransition(acetime_t epochSeconds) const {
482  if (DEBUG) logging::println(
483  "findTransition(): mIndexFree: %d", mIndexFree);
484 
485  const Transition* match = nullptr;
486  for (uint8_t i = 0; i < mIndexFree; i++) {
487  const Transition* candidate = mTransitions[i];
488  if (candidate->startEpochSeconds <= epochSeconds) {
489  match = candidate;
490  } else if (candidate->startEpochSeconds > epochSeconds) {
491  break;
492  }
493  }
494  return match;
495  }
496 
521  const {
522  if (DEBUG) logging::println(
523  "findTransitionForDateTime(): mIndexFree: %d", mIndexFree);
524 
525  // Convert to DateTuple. If the localDateTime is not a multiple of 15
526  // minutes, the comparision (startTime < localDate) will still be valid.
527  DateTuple localDate = { ldt.yearTiny(), ldt.month(), ldt.day(),
528  (int8_t) (ldt.hour() * 4 + ldt.minute() / 15), 'w' };
529  const Transition* match = nullptr;
530  for (uint8_t i = 0; i < mIndexFree; i++) {
531  const Transition* candidate = mTransitions[i];
532  if (candidate->startDateTime <= localDate) {
533  match = candidate;
534  } else if (candidate->startDateTime > localDate) {
535  break;
536  }
537  }
538  return match;
539  }
540 
542  void log() const {
543  logging::println("TransitionStorage:");
544  logging::println(" mIndexPrior: %d", mIndexPrior);
545  logging::println(" mIndexCandidates: %d", mIndexCandidates);
546  logging::println(" mIndexFree: %d", mIndexFree);
547  if (mIndexPrior != 0) {
548  logging::println(" Actives:");
549  for (uint8_t i = 0; i < mIndexPrior; i++) {
550  mTransitions[i]->log();
551  logging::println();
552  }
553  }
554  if (mIndexPrior != mIndexCandidates) {
555  logging::print(" Prior: ");
556  mTransitions[mIndexPrior]->log();
557  logging::println();
558  }
559  if (mIndexCandidates != mIndexFree) {
560  logging::println(" Candidates:");
561  for (uint8_t i = mIndexCandidates; i < mIndexFree; i++) {
562  mTransitions[i]->log();
563  logging::println();
564  }
565  }
566  }
567 
569  void resetHighWater() { mHighWater = 0; }
570 
576  uint8_t getHighWater() const { return mHighWater; }
577 
578  private:
579  friend class ::TransitionStorageTest_getFreeAgent;
580  friend class ::TransitionStorageTest_getFreeAgent2;
581  friend class ::TransitionStorageTest_addFreeAgentToActivePool;
582  friend class ::TransitionStorageTest_reservePrior;
583  friend class ::TransitionStorageTest_addFreeAgentToCandidatePool;
584  friend class ::TransitionStorageTest_setFreeAgentAsPrior;
585  friend class ::TransitionStorageTest_addActiveCandidatesToActivePool;
586  friend class ::TransitionStorageTest_resetCandidatePool;
587 
589  Transition* getTransition(uint8_t i) { return mTransitions[i]; }
590 
591  Transition mPool[SIZE];
592  Transition* mTransitions[SIZE];
593  uint8_t mIndexPrior;
594  uint8_t mIndexCandidates;
595  uint8_t mIndexFree;
596 
598  uint8_t mHighWater = 0;
599 };
600 
601 } // namespace extended
602 
632  public:
637  explicit ExtendedZoneSpecifier(const extended::ZoneInfo* zoneInfo):
638  ZoneSpecifier(kTypeExtended),
639  mZoneInfo(zoneInfo) {}
640 
642  const extended::ZoneInfo* getZoneInfo() const { return mZoneInfo; }
643 
644  TimeOffset getUtcOffset(acetime_t epochSeconds) const override {
645  init(epochSeconds);
646  if (mIsOutOfBounds) return TimeOffset::forError();
647  const extended::Transition* transition = findTransition(epochSeconds);
648  return (transition)
650  transition->offsetCode() + transition->deltaCode())
652  }
653 
654  TimeOffset getDeltaOffset(acetime_t epochSeconds) const override {
655  init(epochSeconds);
656  if (mIsOutOfBounds) return TimeOffset::forError();
657  const extended::Transition* transition = findTransition(epochSeconds);
658  return TimeOffset::forOffsetCode(transition->deltaCode());
659  }
660 
661  const char* getAbbrev(acetime_t epochSeconds) const override {
662  init(epochSeconds);
663  if (mIsOutOfBounds) return "";
664  const extended::Transition* transition = findTransition(epochSeconds);
665  return transition->abbrev;
666  }
667 
669  const override {
670  init(ldt.getLocalDate());
671  if (mIsOutOfBounds) return TimeOffset::forError();
672  const extended::Transition* transition =
673  mTransitionStorage.findTransitionForDateTime(ldt);
674  return (transition)
676  transition->offsetCode() + transition->deltaCode())
678  }
679 
680  void printTo(Print& printer) const override;
681 
683  void log() const {
684  logging::println("ExtendedZoneSpecifier:");
685  logging::println(" mYear: %d", mYear);
686  logging::println(" mNumMatches: %d", mNumMatches);
687  for (int i = 0; i < mNumMatches; i++) {
688  logging::print(" Match %d: ", i);
689  mMatches[i].log();
690  logging::println();
691  }
692  mTransitionStorage.log();
693  }
694 
697  mTransitionStorage.resetHighWater();
698  }
699 
701  uint8_t getTransitionHighWater() const {
702  return mTransitionStorage.getHighWater();
703  }
704 
705  private:
706  friend class ::ExtendedZoneSpecifierTest_compareEraToYearMonth;
707  friend class ::ExtendedZoneSpecifierTest_createMatch;
708  friend class ::ExtendedZoneSpecifierTest_findMatches_simple;
709  friend class ::ExtendedZoneSpecifierTest_findMatches_named;
710  friend class ::ExtendedZoneSpecifierTest_findCandidateTransitions;
711  friend class ::ExtendedZoneSpecifierTest_findTransitionsFromNamedMatch;
712  friend class ::ExtendedZoneSpecifierTest_getTransitionTime;
713  friend class ::ExtendedZoneSpecifierTest_createTransitionForYear;
714  friend class ::ExtendedZoneSpecifierTest_normalizeDateTuple;
715  friend class ::ExtendedZoneSpecifierTest_expandDateTuple;
716  friend class ::ExtendedZoneSpecifierTest_calcInteriorYears;
717  friend class ::ExtendedZoneSpecifierTest_getMostRecentPriorYear;
718  friend class ::ExtendedZoneSpecifierTest_compareTransitionToMatchFuzzy;
719  friend class ::ExtendedZoneSpecifierTest_compareTransitionToMatch;
720  friend class ::ExtendedZoneSpecifierTest_processActiveTransition;
721  friend class ::ExtendedZoneSpecifierTest_fixTransitionTimes_generateStartUntilTimes;
722  friend class ::ExtendedZoneSpecifierTest_createAbbreviation;
723 
728  static const uint8_t kMaxMatches = 4;
729 
736  static const uint8_t kMaxTransitions = 8;
737 
742  static const uint8_t kMaxInteriorYears = 4;
743 
745  static const extended::ZoneEra kAnchorEra;
746 
747  // Disable copy constructor and assignment operator.
749  ExtendedZoneSpecifier& operator=(const ExtendedZoneSpecifier&) = delete;
750 
751  bool equals(const ZoneSpecifier& other) const override {
752  const auto& that = (const ExtendedZoneSpecifier&) other;
753  return getZoneInfo() == that.getZoneInfo();
754  }
755 
757  const extended::Transition* findTransition(acetime_t epochSeconds) const {
758  return mTransitionStorage.findTransition(epochSeconds);
759  }
760 
762  void init(acetime_t epochSeconds) const {
763  LocalDate ld = LocalDate::forEpochSeconds(epochSeconds);
764  init(ld);
765  }
766 
768  void init(const LocalDate& ld) const {
769  int16_t year = ld.year();
770  if (isFilled(year)) return;
771  if (DEBUG) logging::println("init(): %d", year);
772 
773  mYear = year;
774  mNumMatches = 0; // clear cache
775  mTransitionStorage.init();
776 
777  if (year < mZoneInfo->zoneContext->startYear - 1
778  || mZoneInfo->zoneContext->untilYear < year) {
779  mIsOutOfBounds = true;
780  return;
781  }
782 
783  extended::YearMonthTuple startYm = {
784  (int8_t) (year - LocalDate::kEpochYear - 1), 12 };
785  extended::YearMonthTuple untilYm = {
786  (int8_t) (year - LocalDate::kEpochYear + 1), 2 };
787 
788  mNumMatches = findMatches(mZoneInfo, startYm, untilYm, mMatches,
789  kMaxMatches);
790  if (DEBUG) log();
791  findTransitions(mTransitionStorage, mMatches, mNumMatches);
792  extended::Transition** begin = mTransitionStorage.getActivePoolBegin();
793  extended::Transition** end = mTransitionStorage.getActivePoolEnd();
794  fixTransitionTimes(begin, end);
795  generateStartUntilTimes(begin, end);
796  calcAbbreviations(begin, end);
797 
798  mIsFilled = true;
799  mIsOutOfBounds = false;
800  }
801 
803  bool isFilled(int16_t year) const {
804  return mIsFilled && (year == mYear);
805  }
806 
814  static uint8_t findMatches(const extended::ZoneInfo* zoneInfo,
815  const extended::YearMonthTuple& startYm,
816  const extended::YearMonthTuple& untilYm,
817  extended::ZoneMatch* matches, uint8_t maxMatches) {
818  if (DEBUG) logging::println("findMatches()");
819  uint8_t iMatch = 0;
820  const extended::ZoneEra* prev = &kAnchorEra;
821  for (uint8_t iEra = 0; iEra < zoneInfo->numEras; iEra++) {
822  const extended::ZoneEra* era = &zoneInfo->eras[iEra];
823  if (eraOverlapsInterval(prev, era, startYm, untilYm)) {
824  if (iMatch < maxMatches) {
825  matches[iMatch] = createMatch(prev, era, startYm, untilYm);
826  iMatch++;
827  }
828  }
829  prev = era;
830  }
831  return iMatch;
832  }
833 
844  static bool eraOverlapsInterval(
845  const extended::ZoneEra* prev,
846  const extended::ZoneEra* era,
847  const extended::YearMonthTuple& startYm,
848  const extended::YearMonthTuple& untilYm) {
849  return compareEraToYearMonth(prev, untilYm.yearTiny, untilYm.month) < 0
850  && compareEraToYearMonth(era, startYm.yearTiny, startYm.month) > 0;
851  }
852 
854  static int8_t compareEraToYearMonth(const extended::ZoneEra* era,
855  int8_t yearTiny, uint8_t month) {
856  if (era->untilYearTiny < yearTiny) return -1;
857  if (era->untilYearTiny > yearTiny) return 1;
858  if (era->untilMonth < month) return -1;
859  if (era->untilMonth > month) return 1;
860  if (era->untilDay > 1) return 1;
861  //if (era->untilTimeCode < 0) return -1; // never possible
862  if (era->untilTimeCode > 0) return 1;
863  return 0;
864  }
865 
872  static extended::ZoneMatch createMatch(
873  const extended::ZoneEra* prev,
874  const extended::ZoneEra* era,
875  const extended::YearMonthTuple& startYm,
876  const extended::YearMonthTuple& untilYm) {
877  extended::DateTuple startDate = {
878  prev->untilYearTiny, prev->untilMonth, prev->untilDay,
879  (int8_t) prev->untilTimeCode, prev->untilTimeModifier
880  };
881  extended::DateTuple lowerBound = {
882  startYm.yearTiny, startYm.month, 1, 0, 'w'
883  };
884  if (startDate < lowerBound) {
885  startDate = lowerBound;
886  }
887 
888  extended::DateTuple untilDate = {
889  era->untilYearTiny, era->untilMonth, era->untilDay,
890  (int8_t) era->untilTimeCode, era->untilTimeModifier
891  };
892  extended::DateTuple upperBound = {
893  untilYm.yearTiny, untilYm.month, 1, 0, 'w'
894  };
895  if (upperBound < untilDate) {
896  untilDate = upperBound;
897  }
898 
899  return {startDate, untilDate, era};
900  }
901 
906  static void findTransitions(
908  extended::ZoneMatch* matches,
909  uint8_t numMatches) {
910  if (DEBUG) logging::println("findTransitions()");
911  for (uint8_t i = 0; i < numMatches; i++) {
912  findTransitionsForMatch(transitionStorage, &matches[i]);
913  }
914  }
915 
917  static void findTransitionsForMatch(
919  const extended::ZoneMatch* match) {
920  if (DEBUG) logging::println("findTransitionsForMatch()");
921  const extended::ZonePolicy* policy = match->era->zonePolicy;
922  if (policy == nullptr) {
923  findTransitionsFromSimpleMatch(transitionStorage, match);
924  } else {
925  findTransitionsFromNamedMatch(transitionStorage, match);
926  }
927  }
928 
929  static void findTransitionsFromSimpleMatch(
931  const extended::ZoneMatch* match) {
932  if (DEBUG) logging::println("findTransitionsFromSimpleMatch()");
933  extended::Transition* freeTransition = transitionStorage.getFreeAgent();
934  freeTransition->match = match;
935  freeTransition->rule = nullptr;
936  freeTransition->transitionTime = match->startDateTime;
937 
938  transitionStorage.addFreeAgentToActivePool();
939  }
940 
941  static void findTransitionsFromNamedMatch(
943  const extended::ZoneMatch* match) {
944  if (DEBUG) logging::println("findTransitionsFromNamedMatch()");
945  transitionStorage.resetCandidatePool();
946  if (DEBUG) { match->log(); logging::println(); }
947  findCandidateTransitions(transitionStorage, match);
948  if (DEBUG) { transitionStorage.log(); logging::println(); }
949  fixTransitionTimes(
950  transitionStorage.getCandidatePoolBegin(),
951  transitionStorage.getCandidatePoolEnd());
952  selectActiveTransitions(transitionStorage, match);
953  if (DEBUG) { transitionStorage.log(); logging::println(); }
954 
955  transitionStorage.addActiveCandidatesToActivePool();
956  if (DEBUG) { transitionStorage.log(); logging::println(); }
957  }
958 
959  static void findCandidateTransitions(
961  const extended::ZoneMatch* match) {
962  if (DEBUG) {
963  logging::print("findCandidateTransitions(): ");
964  match->log();
965  logging::println();
966  }
967  const extended::ZonePolicy* policy = match->era->zonePolicy;
968  uint8_t numRules = policy->numRules;
969  const extended::ZoneRule* rules = policy->rules;
970  int8_t startY = match->startDateTime.yearTiny;
971  int8_t endY = match->untilDateTime.yearTiny;
972 
973  extended::Transition** prior = transitionStorage.reservePrior();
974  (*prior)->active = false; // indicates "no prior transition"
975  for (uint8_t r = 0; r < numRules; r++) {
976  const extended::ZoneRule* const rule = &rules[r];
977 
978  // Add Transitions for interior years
979  int8_t interiorYears[kMaxInteriorYears];
980  uint8_t numYears = calcInteriorYears(interiorYears, kMaxInteriorYears,
981  rule->fromYearTiny, rule->toYearTiny, startY, endY);
982  for (uint8_t y = 0; y < numYears; y++) {
983  int8_t year = interiorYears[y];
984  extended::Transition* t = transitionStorage.getFreeAgent();
985  createTransitionForYear(t, year, rule, match);
986  int8_t status = compareTransitionToMatchFuzzy(t, match);
987  if (status < 0) {
988  setAsPriorTransition(transitionStorage, t);
989  } else if (status == 1) {
990  transitionStorage.addFreeAgentToCandidatePool();
991  }
992  }
993 
994  // Add Transition for prior year
995  int8_t priorYear = getMostRecentPriorYear(
996  rule->fromYearTiny, rule->toYearTiny, startY, endY);
997  if (priorYear != LocalDate::kInvalidYearTiny) {
998  if (DEBUG) logging::println(
999  "findCandidateTransitions(): priorYear: %d", priorYear);
1000  extended::Transition* t = transitionStorage.getFreeAgent();
1001  createTransitionForYear(t, priorYear, rule, match);
1002  setAsPriorTransition(transitionStorage, t);
1003  }
1004  }
1005 
1006  // Add the reserved prior into the Candidate pool only if 'active' is
1007  // true, meaning that a prior was found.
1008  if ((*prior)->active) {
1009  if (DEBUG) logging::println(
1010  "findCandidateTransitions(): adding prior to Candidate pool");
1011  transitionStorage.addPriorToCandidatePool();
1012  }
1013  }
1014 
1019  static uint8_t calcInteriorYears(int8_t* interiorYears,
1020  uint8_t maxInteriorYears, int8_t fromYear, int8_t toYear,
1021  int8_t startYear, int8_t endYear) {
1022  uint8_t i = 0;
1023  for (int8_t year = startYear; year <= endYear; year++) {
1024  if (fromYear <= year && year <= toYear) {
1025  interiorYears[i] = year;
1026  i++;
1027  if (i >= maxInteriorYears) break;
1028  }
1029  }
1030  return i;
1031  }
1032 
1034  static void createTransitionForYear(extended::Transition* t, int8_t year,
1035  const extended::ZoneRule* rule,
1036  const extended::ZoneMatch* match) {
1037  t->match = match;
1038  t->transitionTime = getTransitionTime(year, rule);
1039  t->rule = rule;
1040  }
1041 
1047  static int8_t getMostRecentPriorYear(int8_t fromYear, int8_t toYear,
1048  int8_t startYear, int8_t /*endYear*/) {
1049  if (fromYear < startYear) {
1050  if (toYear < startYear) {
1051  return toYear;
1052  } else {
1053  return startYear - 1;
1054  }
1055  } else {
1057  }
1058  }
1059 
1060  static extended::DateTuple getTransitionTime(
1061  int8_t yearTiny, const extended::ZoneRule* rule) {
1062  uint8_t dayOfMonth = BasicZoneSpecifier::calcStartDayOfMonth(
1063  yearTiny + LocalDate::kEpochYear, rule->inMonth, rule->onDayOfWeek,
1064  rule->onDayOfMonth);
1065  return {yearTiny, rule->inMonth, dayOfMonth,
1066  (int8_t) rule->atTimeCode, rule->atTimeModifier};
1067  }
1068 
1079  static int8_t compareTransitionToMatchFuzzy(
1080  const extended::Transition* t, const extended::ZoneMatch* match) {
1081  int16_t ttMonths = t->transitionTime.yearTiny * 12
1082  + t->transitionTime.month;
1083 
1084  int16_t matchStartMonths = match->startDateTime.yearTiny * 12
1085  + match->startDateTime.month;
1086  if (ttMonths < matchStartMonths - 1) return -1;
1087 
1088  int16_t matchUntilMonths = match->untilDateTime.yearTiny * 12
1089  + match->untilDateTime.month;
1090  if (matchUntilMonths + 2 <= ttMonths) return 2;
1091 
1092  return 1;
1093  }
1094 
1096  static void setAsPriorTransition(
1098  extended::Transition* t) {
1099  if (DEBUG) logging::println("setAsPriorTransition()");
1100  extended::Transition* prior = transitionStorage.getPrior();
1101  if (prior->active) {
1102  if (prior->transitionTime < t->transitionTime) {
1103  t->active = true;
1104  transitionStorage.setFreeAgentAsPrior();
1105  }
1106  } else {
1107  t->active = true;
1108  transitionStorage.setFreeAgentAsPrior();
1109  }
1110  }
1111 
1120  static void fixTransitionTimes(
1121  extended::Transition** begin, extended::Transition** end) {
1122  if (DEBUG) logging::println("fixTransitionTimes(): #transitions: %d;",
1123  (int) (end - begin));
1124  // extend first Transition to -infinity
1125  extended::Transition* prev = *begin;
1126  for (extended::Transition** iter = begin; iter != end; ++iter) {
1127  extended::Transition* curr = *iter;
1128  if (DEBUG) {
1129  logging::println("fixTransitionTimes(): LOOP");
1130  curr->log();
1131  logging::println();
1132  }
1133  expandDateTuple(&curr->transitionTime,
1134  &curr->transitionTimeS, &curr->transitionTimeU,
1135  prev->offsetCode(), prev->deltaCode());
1136  prev = curr;
1137  }
1138  if (DEBUG) logging::println("fixTransitionTimes(): END");
1139  }
1140 
1146  static void expandDateTuple(extended::DateTuple* tt,
1148  int8_t offsetCode, int8_t deltaCode) {
1149  if (DEBUG) logging::println("expandDateTuple()");
1150  if (tt->modifier == 's') {
1151  *tts = *tt;
1152  *ttu = {tt->yearTiny, tt->month, tt->day,
1153  (int8_t) (tt->timeCode - offsetCode), 'u'};
1154  *tt = {tt->yearTiny, tt->month, tt->day,
1155  (int8_t) (tt->timeCode + deltaCode), 'w'};
1156  } else if (tt->modifier == 'u') {
1157  *ttu = *tt;
1158  *tts = {tt->yearTiny, tt->month, tt->day,
1159  (int8_t) (tt->timeCode + offsetCode), 's'};
1160  *tt = {tt->yearTiny, tt->month, tt->day,
1161  (int8_t) (tt->timeCode + offsetCode + deltaCode), 'w'};
1162  } else {
1163  // Explicit set the modifier to 'w' in case it was something else.
1164  tt->modifier = 'w';
1165  *tts = {tt->yearTiny, tt->month, tt->day,
1166  (int8_t) (tt->timeCode - deltaCode), 's'};
1167  *ttu = {tt->yearTiny, tt->month, tt->day,
1168  (int8_t) (tt->timeCode - deltaCode - offsetCode), 'u'};
1169  }
1170 
1171  if (DEBUG) logging::println("expandDateTuple(): normalizeDateTuple(): 1");
1172  normalizeDateTuple(tt);
1173  if (DEBUG) logging::println("expandDateTuple(): normalizeDateTuple(): 2");
1174  normalizeDateTuple(tts);
1175  if (DEBUG) logging::println("expandDateTuple(): normalizeDateTuple(): 3");
1176  normalizeDateTuple(ttu);
1177  }
1178 
1180  static void normalizeDateTuple(extended::DateTuple* dt) {
1181  const int8_t kOneDayAsCode = 4 * 24;
1182  if (dt->timeCode <= -kOneDayAsCode) {
1183  LocalDate ld(dt->yearTiny, dt->month, dt->day);
1184  local_date_mutation::decrementOneDay(ld);
1185  dt->yearTiny = ld.yearTiny();
1186  dt->month = ld.month();
1187  dt->day = ld.day();
1188  dt->timeCode += kOneDayAsCode;
1189  } else if (kOneDayAsCode <= dt->timeCode) {
1190  LocalDate ld(dt->yearTiny, dt->month, dt->day);
1191  local_date_mutation::incrementOneDay(ld);
1192  dt->yearTiny = ld.yearTiny();
1193  dt->month = ld.month();
1194  dt->day = ld.day();
1195  dt->timeCode -= kOneDayAsCode;
1196  } else {
1197  // do nothing
1198  }
1199  }
1200 
1205  static void selectActiveTransitions(
1207  const extended::ZoneMatch* match) {
1208  extended::Transition** begin = transitionStorage.getCandidatePoolBegin();
1209  extended::Transition** end = transitionStorage.getCandidatePoolEnd();
1210  if (DEBUG) logging::println("selectActiveTransitions(): #candidates: %d",
1211  (int) (end - begin));
1212  extended::Transition* prior = nullptr;
1213  for (extended::Transition** iter = begin; iter != end; ++iter) {
1214  extended::Transition* transition = *iter;
1215  processActiveTransition(match, transition, &prior);
1216  }
1217 
1218  // If the latest prior transition is found, shift it to start at the
1219  // startDateTime of the current match.
1220  if (prior) {
1221  if (DEBUG) logging::println(
1222  "selectActiveTransitions(): found latest prior");
1223  prior->originalTransitionTime = prior->transitionTime;
1224  prior->transitionTime = match->startDateTime;
1225  }
1226  }
1227 
1235  static void processActiveTransition(
1236  const extended::ZoneMatch* match,
1237  extended::Transition* transition,
1238  extended::Transition** prior) {
1239  int8_t status = compareTransitionToMatch(transition, match);
1240  if (status == 2) {
1241  transition->active = false;
1242  } else if (status == 1) {
1243  transition->active = true;
1244  } else if (status == 0) {
1245  if (*prior) {
1246  (*prior)->active = false;
1247  }
1248  transition->active = true;
1249  (*prior) = transition;
1250  } else { // (status < 0)
1251  if (*prior) {
1252  if ((*prior)->transitionTime < transition->transitionTime) {
1253  (*prior)->active = false;
1254  transition->active = true;
1255  (*prior) = transition;
1256  }
1257  } else {
1258  transition->active = true;
1259  (*prior) = transition;
1260  }
1261  }
1262  }
1263 
1278  static int8_t compareTransitionToMatch(
1279  const extended::Transition* transition,
1280  const extended::ZoneMatch* match) {
1281  const extended::DateTuple* transitionTime;
1282 
1283  const extended::DateTuple& matchStart = match->startDateTime;
1284  if (matchStart.modifier == 's') {
1285  transitionTime = &transition->transitionTimeS;
1286  } else if (matchStart.modifier == 'u') {
1287  transitionTime = &transition->transitionTimeU;
1288  } else { // assume 'w'
1289  transitionTime = &transition->transitionTime;
1290  }
1291  if (*transitionTime < matchStart) return -1;
1292  if (*transitionTime == matchStart) return 0;
1293 
1294  const extended::DateTuple& matchUntil = match->untilDateTime;
1295  if (matchUntil.modifier == 's') {
1296  transitionTime = &transition->transitionTimeS;
1297  } else if (matchUntil.modifier == 'u') {
1298  transitionTime = &transition->transitionTimeU;
1299  } else { // assume 'w'
1300  transitionTime = &transition->transitionTime;
1301  }
1302  if (*transitionTime < matchUntil) return 1;
1303  return 2;
1304  }
1305 
1311  static void generateStartUntilTimes(
1312  extended::Transition** begin, extended::Transition** end) {
1313  if (DEBUG) logging::println(
1314  "generateStartUntilTimes(): #transitions: %d;",
1315  (int) (end - begin));
1316 
1317  extended::Transition* prev = *begin;
1318  bool isAfterFirst = false;
1319  for (extended::Transition** iter = begin; iter != end; ++iter) {
1320  extended::Transition* const t = *iter;
1321  const extended::DateTuple& tt = t->transitionTime;
1322 
1323  // 1) Update the untilDateTime of the previous Transition
1324  if (isAfterFirst) {
1325  prev->untilDateTime = tt;
1326  }
1327 
1328  // 2) Calculate the current startDateTime by shifting the
1329  // transitionTime (represented in the UTC offset of the previous
1330  // transition) into the UTC offset of the *current* transition.
1331  int8_t code = tt.timeCode - prev->offsetCode() - prev->deltaCode()
1332  + t->offsetCode() + t->deltaCode();
1333  t->startDateTime = {tt.yearTiny, tt.month, tt.day, code, tt.modifier};
1334  normalizeDateTuple(&t->startDateTime);
1335 
1336  // 3) The epochSecond of the 'transitionTime' is determined by the
1337  // UTC offset of the *previous* Transition. However, the
1338  // transitionTime can be represented by an illegal time (e.g. 24:00).
1339  // So, it is better to use the properly normalized startDateTime
1340  // (calculated above) with the *current* UTC offset.
1341  //
1342  // NOTE: We should also be able to calculate this directly from
1343  // 'transitionTimeU' which should still be a valid field, because it
1344  // hasn't been clobbered by 'untilDateTime' yet. Not sure if this saves
1345  // any CPU time though, since we still need to mutiply by 900.
1346  const extended::DateTuple& st = t->startDateTime;
1347  const acetime_t offsetSeconds = (acetime_t) 900
1348  * (st.timeCode - t->offsetCode() - t->deltaCode());
1349  LocalDate ld(st.yearTiny, st.month, st.day);
1350  t->startEpochSeconds = ld.toEpochSeconds() + offsetSeconds;
1351 
1352  prev = t;
1353  isAfterFirst = true;
1354  }
1355 
1356  // The last Transition's until time is the until time of the ZoneMatch.
1357  extended::DateTuple untilTime = prev->match->untilDateTime;
1358  extended::DateTuple untilTimeS; // needed only for expandDateTuple
1359  extended::DateTuple untilTimeU; // needed only for expandDateTuple
1360  expandDateTuple(&untilTime, &untilTimeS, &untilTimeU,
1361  prev->offsetCode(), prev->deltaCode());
1362  prev->untilDateTime = untilTime;
1363  }
1364 
1368  static void calcAbbreviations(
1369  extended::Transition** begin, extended::Transition** end) {
1370  if (DEBUG) logging::println("calcAbbreviations(): #transitions: %d;",
1371  (int) (end - begin));
1372  for (extended::Transition** iter = begin; iter != end; ++iter) {
1373  extended::Transition* const t = *iter;
1374  const char* format = t->format();
1375  int8_t deltaCode = t->deltaCode();
1376  const char* letter = t->letter();
1377  createAbbreviation(t->abbrev, extended::Transition::kAbbrevSize,
1378  format, deltaCode, letter);
1379  }
1380  }
1381 
1416  static void createAbbreviation(char* dest, uint8_t destSize,
1417  const char* format, uint8_t deltaCode, const char* letterString) {
1418  // Check if RULES column is empty. Ignore the deltaCode because if
1419  // letterString is nullptr, we can only just copy the whole thing.
1420  if (letterString == nullptr) {
1421  strncpy(dest, format, destSize);
1422  dest[destSize - 1] = '\0';
1423  return;
1424  }
1425 
1426  // Check if FORMAT contains a '%'.
1427  if (strchr(format, '%') != nullptr) {
1428  copyAndReplace(dest, destSize, format, '%', letterString);
1429  } else {
1430  // Check if FORMAT contains a '/'.
1431  const char* slashPos = strchr(format, '/');
1432  if (slashPos != nullptr) {
1433  if (deltaCode == 0) {
1434  uint8_t headLength = (slashPos - format);
1435  if (headLength >= destSize) headLength = destSize - 1;
1436  memcpy(dest, format, headLength);
1437  dest[headLength] = '\0';
1438  } else {
1439  uint8_t tailLength = strlen(slashPos+1);
1440  if (tailLength >= destSize) tailLength = destSize - 1;
1441  memcpy(dest, slashPos+1, tailLength);
1442  dest[tailLength] = '\0';
1443  }
1444  } else {
1445  // Just copy the FORMAT disregarding deltaCode and letterString.
1446  strncpy(dest, format, destSize);
1447  dest[destSize - 1] = '\0';
1448  }
1449  }
1450  }
1451 
1457  static void copyAndReplace(char* dst, uint8_t dstSize, const char* src,
1458  char oldChar, const char* newString) {
1459  while (*src != '\0' && dstSize > 0) {
1460  if (*src == oldChar) {
1461  while (*newString != '\0' && dstSize > 0) {
1462  *dst++ = *newString++;
1463  dstSize--;
1464  }
1465  src++;
1466  } else {
1467  *dst++ = *src++;
1468  dstSize--;
1469  }
1470  }
1471 
1472  if (dstSize == 0) {
1473  --dst;
1474  }
1475  *dst = '\0';
1476  }
1477 
1478  const extended::ZoneInfo* const mZoneInfo;
1479 
1480  mutable int16_t mYear = 0;
1481  mutable bool mIsFilled = false;
1482  mutable bool mIsOutOfBounds = false; // year is too early or late
1483  // NOTE: Maybe move mNumMatches and mMatches into a MatchStorage object.
1484  mutable uint8_t mNumMatches = 0; // actual number of matches
1485  mutable extended::ZoneMatch mMatches[kMaxMatches];
1486  mutable extended::TransitionStorage<kMaxTransitions> mTransitionStorage;
1487 };
1488 
1489 } // namespace ace_time
1490 
1491 #undef DEBUG
1492 
1493 #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:90
uint8_t hour() const
Return the hour.
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.
void addActiveCandidatesToActivePool()
Add active candidates into the Active pool, and collapse the Candidate pool.
int8_t offsetCode() const
The base offset code.
uint8_t month() const
Return the month with January=1, December=12.
uint8_t const untilMonth
The month field in UNTIL (1-12).
Definition: ZoneInfo.h:49
DateTuple transitionTimeU
Version of transitionTime in &#39;u&#39; mode, using the UTC offset of the previous transition.
int8_t const toYearTiny
TO year as an offset from year 2000 stored as a single byte.
Definition: ZonePolicy.h:18
uint8_t getHighWater() const
Return the high water mark.
void resetCandidatePool()
Empty the Candidate pool by resetting the various indexes.
const char * getAbbrev(acetime_t epochSeconds) const override
Return the time zone abbreviation at epochSeconds.
acetime_t toEpochSeconds() const
Return the number of seconds since AceTime epoch (2000-01-01 00:00:00).
Definition: LocalDate.h:303
uint8_t const onDayOfMonth
Determined by the ON column.
Definition: ZonePolicy.h:41
DateTuple startDateTime
The effective start time of the matching ZoneEra.
Transition * getFreeAgent()
Return a pointer to the first Transition in the free pool.
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 month() const
Return the month with January=1, December=12.
Definition: LocalDate.h:215
uint8_t minute() const
Return the minute.
DateTuple originalTransitionTime
If the transition is shifted to the beginning of a ZoneMatch, this is set to the transitionTime for d...
int8_t yearTiny() const
Return the single-byte year offset from year 2000.
An implementation of ZoneSpecifier that works for all zones defined by the TZ Database (with some zon...
const Transition * findTransition(acetime_t epochSeconds) const
Return the Transition matching the given epochSeconds.
A collection of transition rules which describe the DST rules of a given administrative region...
Definition: ZonePolicy.h:91
const extended::ZoneRule * rule
The Zone transition rule that matched for the the given year.
int8_t const offsetCode
UTC offset in 15 min increments.
Definition: ZoneInfo.h:20
Represents an interval of time where the time zone obeyed a certain UTC offset and DST delta...
void addPriorToCandidatePool()
Add the current prior into the Candidates pool.
Representation of a given time zone, implemented as an array of ZoneEra records.
Definition: ZoneInfo.h:72
const ZonePolicy *const zonePolicy
Zone policy, determined by the RULES column.
Definition: ZoneInfo.h:26
void setFreeAgentAsPrior()
Swap the Free agrent transition with the current Prior transition.
void log() const
Used only for debugging.
Transition * getPrior()
Return the current prior transition.
void log() const
Verify that the indexes are valid.
char abbrev[kAbbrevSize]
The calculated effective time zone abbreviation, e.g.
void resetTransitionHighWater()
Reset the TransitionStorage high water mark.
An entry in ZoneInfo which describes which ZonePolicy was being followed during a particular time per...
Definition: ZoneInfo.h:15
uint8_t const untilTimeModifier
UNTIL time modifier suffix: &#39;w&#39;, &#39;s&#39; or &#39;u&#39;.
Definition: ZoneInfo.h:65
void addFreeAgentToCandidatePool()
Add the free agent Transition at index mIndexFree to the Candidate pool, sorted by transitionTime...
Base interface for ZoneSpecifier classes.
Definition: ZoneSpecifier.h:39
DateTuple transitionTime
The original transition time, usually &#39;w&#39; but sometimes &#39;s&#39; or &#39;u&#39;.
uint8_t const untilTimeCode
The time field of UNTIL field in 15-minute increments.
Definition: ZoneInfo.h:62
A time zone transition rule.
Definition: ZonePolicy.h:7
uint8_t day() const
Return the day of the month.
static const uint8_t kAbbrevSize
Size of the timezone abbreviation.
int16_t year() const
Return the full year instead of just the last 2 digits.
Definition: LocalDate.h:203
uint8_t const onDayOfWeek
Determined by the ON column.
Definition: ZonePolicy.h:35
uint8_t const numEras
Number of ZoneEra entries.
Definition: ZoneInfo.h:89
uint8_t const untilDay
The day field in UNTIL (1-31).
Definition: ZoneInfo.h:56
int8_t deltaCode() const
The DST offset code.
DateTuple transitionTimeS
Version of transitionTime in &#39;s&#39; mode, using the UTC offset of the previous Transition.
int8_t const untilYearTiny
Era is valid until currentTime < untilYear.
Definition: ZoneInfo.h:46
uint8_t day() const
Return the day of the month.
Definition: LocalDate.h:221
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:97
const Transition * findTransitionForDateTime(const LocalDateTime &ldt) const
Return the Transition matching the given dateTime.
void log() const
Used only for debugging.
const extended::ZoneEra * era
The ZoneEra that matched the given year.
bool active
Determines if this transition is valid.
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:41
ExtendedZoneSpecifier(const extended::ZoneInfo *zoneInfo)
Constructor.
void resetHighWater()
Reset the high water mark.
TimeOffset getDeltaOffset(acetime_t epochSeconds) const override
Return the DST delta offset at epochSeconds.
A thin wrapper that represents a time offset from a reference point, usually 00:00 at UTC...
Definition: TimeOffset.h:53
uint8_t const inMonth
Determined by the IN column.
Definition: ZonePolicy.h:21
uint8_t const atTimeCode
Determined by the AT column in units of 15-minutes from 00:00.
Definition: ZonePolicy.h:47
TimeOffset getUtcOffsetForDateTime(const LocalDateTime &ldt) const override
Return the UTC offset matching the given the date/time components.
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:32
DateTuple untilDateTime
Until time expressed using the UTC offset of the current Transition.
int8_t const fromYearTiny
FROM year as an offset from year 2000 stored as a single byte.
Definition: ZonePolicy.h:15
uint8_t getTransitionHighWater() const
Get the TransitionStorage high water mark.
uint8_t const atTimeModifier
Determined by the suffix in the AT column: &#39;w&#39;=wall; &#39;s&#39;=standard; &#39;u&#39;=meridian (&#39;g&#39; and &#39;z&#39; mean the...
Definition: ZonePolicy.h:54
static const int16_t kEpochYear
Base year of epoch.
Definition: LocalDate.h:35
DateTuple untilDateTime
The effective until time of the matching ZoneEra.
const char * letter() const
Return the letter string.
const extended::ZoneInfo * getZoneInfo() const
Return the underlying ZoneInfo.
uint8_t const letter
Determined by the LETTER column.
Definition: ZonePolicy.h:78
DateTuple startDateTime
Start time expressed using the UTC offset of the current Transition.
const ZoneEra *const eras
ZoneEra entries in increasing order of UNTIL time.
Definition: ZoneInfo.h:92
A tuple that represents a date and time, using a timeCode that tracks the time component using 15-min...
int8_t yearTiny() const
Return the single-byte year offset from year 2000.
Definition: LocalDate.h:209
TimeOffset getUtcOffset(acetime_t epochSeconds) const override
Return the total UTC offset at epochSeconds, including DST offset.
int8_t const deltaCode
Determined by the SAVE column, containing the offset from UTC, in 15-min increments.
Definition: ZonePolicy.h:60
const char *const format
Zone abbreviations (e.g.
Definition: ZoneInfo.h:40
Data structure that captures the matching ZoneEra and its ZoneRule transitions for a given year...
int8_t const deltaCode
If zonePolicy is nullptr, then this indicates the DST offset in 15 minute increments.
Definition: ZoneInfo.h:32
void log() const
Used only for debugging.