AceTime  0.3
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.
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 
227  int8_t offsetCode() const {
228  return match->era->offsetCode;
229  }
230 
240  const char* letter() const {
241  // Char buffer to allow a single letter to be returned as a (char*).
242  // Not thread-safe.
243  static char letterBuf[2];
244 
245  // RULES column is '-' or hh:mm, so return nullptr to indicate this.
246  if (!rule) {
247  return nullptr;
248  }
249 
250  // RULES point to a named rule, and LETTER is a single, printable
251  // character. However, if it's a '-', convert into an empty string "".
252  if (rule->letter >= 32) {
253  if (rule->letter == '-') {
254  letterBuf[0] = '\0';
255  } else {
256  letterBuf[0] = rule->letter;
257  letterBuf[1] = '\0';
258  }
259  return letterBuf;
260  }
261 
262  // RULES points to a named rule, and the LETTER is a string. The
263  // rule->letter is a non-printable number < 32, which is an index into
264  // a list of strings given by match->era->zonePolicy->letters[].
265  const ZonePolicy* policy = match->era->zonePolicy;
266  uint8_t numLetters = policy->numLetters;
267  if (rule->letter >= numLetters) {
268  // This should never happen unless there is a programming error.
269  // If it does, return an empty string.
270  letterBuf[0] = '\0';
271  return letterBuf;
272  }
273 
274  // Return the string at index 'rule->letter'.
275  return policy->letters[rule->letter];
276  }
277 
279  int8_t deltaCode() const {
280  return (rule) ? rule->deltaCode : match->era->deltaCode;
281  }
282 
284  void log() const {
285  logging::print("Transition(");
286  if (sizeof(acetime_t) == sizeof(int)) {
287  logging::print("sE: %d", startEpochSeconds);
288  } else {
289  logging::print("sE: %ld", startEpochSeconds);
290  }
291  logging::print("; match: %snull", (match) ? "!" : "");
292  logging::print("; era: %snull", (match && match->era) ? "!" : "");
293  logging::print("; oCode: %d", offsetCode());
294  logging::print("; dCode: %d", deltaCode());
295  logging::print("; tt: "); transitionTime.log();
296  if (rule != nullptr) {
297  logging::print("; R.fY: %d", rule->fromYearTiny);
298  logging::print("; R.tY: %d", rule->toYearTiny);
299  logging::print("; R.M: %d", rule->inMonth);
300  logging::print("; R.dow: %d", rule->onDayOfWeek);
301  logging::print("; R.dom: %d", rule->onDayOfMonth);
302  }
303  }
304 };
305 
332 template<uint8_t SIZE>
334  public:
337 
339  void init() {
340  for (uint8_t i = 0; i < SIZE; i++) {
341  mTransitions[i] = &mPool[i];
342  }
343  mIndexPrior = 0;
344  mIndexCandidates = 0;
345  mIndexFree = 0;
346  }
347 
349  Transition* getPrior() { return mTransitions[mIndexPrior]; }
350 
352  void swap(Transition** a, Transition** b) {
353  Transition* tmp = *a;
354  *a = *b;
355  *b = tmp;
356  }
357 
367  mIndexCandidates = mIndexPrior;
368  mIndexFree = mIndexPrior;
369  }
370 
371  Transition** getCandidatePoolBegin() {
372  return &mTransitions[mIndexCandidates];
373  }
374  Transition** getCandidatePoolEnd() {
375  return &mTransitions[mIndexFree];
376  }
377 
378  Transition** getActivePoolBegin() { return &mTransitions[0]; }
379  Transition** getActivePoolEnd() { return &mTransitions[mIndexFree]; }
380 
387  // Set the internal high water mark. If that index becomes SIZE,
388  // then we know we have an overflow.
389  if (mIndexFree > mHighWater) {
390  mHighWater = mIndexFree;
391  }
392 
393  if (mIndexFree < SIZE) {
394  return mTransitions[mIndexFree];
395  } else {
396  return mTransitions[SIZE - 1];
397  }
398  }
399 
408  if (mIndexFree >= SIZE) return;
409  mIndexFree++;
410  mIndexPrior = mIndexFree;
411  mIndexCandidates = mIndexFree;
412  }
413 
420  mIndexCandidates++;
421  mIndexFree++;
422  return &mTransitions[mIndexPrior];
423  }
424 
427  swap(&mTransitions[mIndexPrior], &mTransitions[mIndexFree]);
428  }
429 
436  mIndexCandidates--;
437  }
438 
446  if (mIndexFree >= SIZE) return;
447  for (uint8_t i = mIndexFree; i > mIndexCandidates; i--) {
448  Transition* curr = mTransitions[i];
449  Transition* prev = mTransitions[i - 1];
450  if (curr->transitionTime >= prev->transitionTime) break;
451  mTransitions[i] = prev;
452  mTransitions[i - 1] = curr;
453  }
454  mIndexFree++;
455  }
456 
462  if (DEBUG) logging::println("addActiveCandidatesToActivePool()");
463  uint8_t iActive = mIndexPrior;
464  uint8_t iCandidate = mIndexCandidates;
465  for (; iCandidate < mIndexFree; iCandidate++) {
466  if (mTransitions[iCandidate]->active) {
467  if (iActive != iCandidate) {
468  swap(&mTransitions[iActive], &mTransitions[iCandidate]);
469  }
470  ++iActive;
471  }
472  }
473  mIndexPrior = iActive;
474  mIndexCandidates = iActive;
475  mIndexFree = iActive;
476  }
477 
486  const Transition* findTransition(acetime_t epochSeconds) const {
487  if (DEBUG) logging::println(
488  "findTransition(): mIndexFree: %d", mIndexFree);
489 
490  const Transition* match = nullptr;
491  for (uint8_t i = 0; i < mIndexFree; i++) {
492  const Transition* candidate = mTransitions[i];
493  if (candidate->startEpochSeconds > epochSeconds) break;
494  match = candidate;
495  }
496  return match;
497  }
498 
523  const {
524  if (DEBUG) logging::println(
525  "findTransitionForDateTime(): mIndexFree: %d", mIndexFree);
526 
527  // Convert to DateTuple. If the localDateTime is not a multiple of 15
528  // minutes, the comparision (startTime < localDate) will still be valid.
529  DateTuple localDate = { ldt.yearTiny(), ldt.month(), ldt.day(),
530  (int8_t) (ldt.hour() * 4 + ldt.minute() / 15), 'w' };
531  const Transition* match = nullptr;
532  for (uint8_t i = 0; i < mIndexFree; i++) {
533  const Transition* candidate = mTransitions[i];
534  if (candidate->startDateTime > localDate) break;
535  match = candidate;
536  }
537  return match;
538  }
539 
541  void log() const {
542  logging::println("TransitionStorage:");
543  logging::println(" mIndexPrior: %d", mIndexPrior);
544  logging::println(" mIndexCandidates: %d", mIndexCandidates);
545  logging::println(" mIndexFree: %d", mIndexFree);
546  if (mIndexPrior != 0) {
547  logging::println(" Actives:");
548  for (uint8_t i = 0; i < mIndexPrior; i++) {
549  mTransitions[i]->log();
550  logging::println();
551  }
552  }
553  if (mIndexPrior != mIndexCandidates) {
554  logging::print(" Prior: ");
555  mTransitions[mIndexPrior]->log();
556  logging::println();
557  }
558  if (mIndexCandidates != mIndexFree) {
559  logging::println(" Candidates:");
560  for (uint8_t i = mIndexCandidates; i < mIndexFree; i++) {
561  mTransitions[i]->log();
562  logging::println();
563  }
564  }
565  }
566 
568  void resetHighWater() { mHighWater = 0; }
569 
575  uint8_t getHighWater() const { return mHighWater; }
576 
577  private:
578  friend class ::TransitionStorageTest_getFreeAgent;
579  friend class ::TransitionStorageTest_getFreeAgent2;
580  friend class ::TransitionStorageTest_addFreeAgentToActivePool;
581  friend class ::TransitionStorageTest_reservePrior;
582  friend class ::TransitionStorageTest_addFreeAgentToCandidatePool;
583  friend class ::TransitionStorageTest_setFreeAgentAsPrior;
584  friend class ::TransitionStorageTest_addActiveCandidatesToActivePool;
585  friend class ::TransitionStorageTest_resetCandidatePool;
586 
588  Transition* getTransition(uint8_t i) { return mTransitions[i]; }
589 
590  Transition mPool[SIZE];
591  Transition* mTransitions[SIZE];
592  uint8_t mIndexPrior;
593  uint8_t mIndexCandidates;
594  uint8_t mIndexFree;
595 
597  uint8_t mHighWater = 0;
598 };
599 
600 } // namespace extended
601 
631  public:
636  explicit ExtendedZoneSpecifier(const extended::ZoneInfo* zoneInfo):
637  ZoneSpecifier(kTypeExtended),
638  mZoneInfo(zoneInfo) {}
639 
641  const extended::ZoneInfo* getZoneInfo() const { return mZoneInfo; }
642 
643  TimeOffset getUtcOffset(acetime_t epochSeconds) const override {
644  bool success = init(epochSeconds);
645  if (!success) return TimeOffset::forError();
646  const extended::Transition* transition = findTransition(epochSeconds);
647  return (transition)
649  transition->offsetCode() + transition->deltaCode())
651  }
652 
653  TimeOffset getDeltaOffset(acetime_t epochSeconds) const override {
654  bool success = init(epochSeconds);
655  if (!success) return TimeOffset::forError();
656  const extended::Transition* transition = findTransition(epochSeconds);
657  return TimeOffset::forOffsetCode(transition->deltaCode());
658  }
659 
660  const char* getAbbrev(acetime_t epochSeconds) const override {
661  bool success = init(epochSeconds);
662  if (!success) return "";
663  const extended::Transition* transition = findTransition(epochSeconds);
664  return transition->abbrev;
665  }
666 
667  OffsetDateTime getOffsetDateTime(const LocalDateTime& ldt) const override {
668  TimeOffset offset;
669  bool success = init(ldt.localDate());
670  if (success) {
671  const extended::Transition* transition =
672  mTransitionStorage.findTransitionForDateTime(ldt);
673  offset = (transition)
675  transition->offsetCode() + transition->deltaCode())
677  } else {
678  offset = TimeOffset::forError();
679  }
680 
681  auto odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset);
682  if (offset.isError()) {
683  return odt;
684  }
685 
686  // Normalize the OffsetDateTime, causing LocalDateTime in the DST
687  // transtion gap to be shifted forward one hour. For LocalDateTime in an
688  // overlap (DST->STD transition), the earlier UTC offset is selected// by
689  // findTransitionForDateTime(). Use that to calculate the epochSeconds,
690  // then recalculate the offset. Use this final offset to determine the
691  // effective OffsetDateTime that will survive a round-trip unchanged.
692  acetime_t epochSeconds = odt.toEpochSeconds();
693  const extended::Transition* transition =
694  mTransitionStorage.findTransition(epochSeconds);
695  offset = (transition)
697  transition->offsetCode() + transition->deltaCode())
699  odt = OffsetDateTime::forEpochSeconds(epochSeconds, offset);
700  return odt;
701  }
702 
704  void printTo(Print& printer) const override;
705 
707  void log() const {
708  logging::println("ExtendedZoneSpecifier:");
709  logging::println(" mYear: %d", mYear);
710  logging::println(" mNumMatches: %d", mNumMatches);
711  for (int i = 0; i < mNumMatches; i++) {
712  logging::print(" Match %d: ", i);
713  mMatches[i].log();
714  logging::println();
715  }
716  mTransitionStorage.log();
717  }
718 
721  mTransitionStorage.resetHighWater();
722  }
723 
725  uint8_t getTransitionHighWater() const {
726  return mTransitionStorage.getHighWater();
727  }
728 
729  private:
730  friend class ::ExtendedZoneSpecifierTest_compareEraToYearMonth;
731  friend class ::ExtendedZoneSpecifierTest_createMatch;
732  friend class ::ExtendedZoneSpecifierTest_findMatches_simple;
733  friend class ::ExtendedZoneSpecifierTest_findMatches_named;
734  friend class ::ExtendedZoneSpecifierTest_findCandidateTransitions;
735  friend class ::ExtendedZoneSpecifierTest_findTransitionsFromNamedMatch;
736  friend class ::ExtendedZoneSpecifierTest_getTransitionTime;
737  friend class ::ExtendedZoneSpecifierTest_createTransitionForYear;
738  friend class ::ExtendedZoneSpecifierTest_normalizeDateTuple;
739  friend class ::ExtendedZoneSpecifierTest_expandDateTuple;
740  friend class ::ExtendedZoneSpecifierTest_calcInteriorYears;
741  friend class ::ExtendedZoneSpecifierTest_getMostRecentPriorYear;
742  friend class ::ExtendedZoneSpecifierTest_compareTransitionToMatchFuzzy;
743  friend class ::ExtendedZoneSpecifierTest_compareTransitionToMatch;
744  friend class ::ExtendedZoneSpecifierTest_processActiveTransition;
745  friend class ::ExtendedZoneSpecifierTest_fixTransitionTimes_generateStartUntilTimes;
746  friend class ::ExtendedZoneSpecifierTest_createAbbreviation;
747 
752  static const uint8_t kMaxMatches = 4;
753 
761  static const uint8_t kMaxTransitions = 8;
762 
767  static const uint8_t kMaxInteriorYears = 4;
768 
770  static const extended::ZoneEra kAnchorEra;
771 
772  // Disable copy constructor and assignment operator.
774  ExtendedZoneSpecifier& operator=(const ExtendedZoneSpecifier&) = delete;
775 
776  bool equals(const ZoneSpecifier& other) const override {
777  const auto& that = (const ExtendedZoneSpecifier&) other;
778  return getZoneInfo() == that.getZoneInfo();
779  }
780 
785  const extended::Transition* findTransition(acetime_t epochSeconds) const {
786  return mTransitionStorage.findTransition(epochSeconds);
787  }
788 
790  bool init(acetime_t epochSeconds) const {
791  LocalDate ld = LocalDate::forEpochSeconds(epochSeconds);
792  return init(ld);
793  }
794 
799  bool init(const LocalDate& ld) const {
800  int16_t year = ld.year();
801  if (isFilled(year)) return true;
802  if (DEBUG) logging::println("init(): %d", year);
803 
804  mYear = year;
805  mNumMatches = 0; // clear cache
806  mTransitionStorage.init();
807 
808  if (year < mZoneInfo->zoneContext->startYear - 1
809  || mZoneInfo->zoneContext->untilYear < year) {
810  return false;
811  }
812 
813  extended::YearMonthTuple startYm = {
814  (int8_t) (year - LocalDate::kEpochYear - 1), 12 };
815  extended::YearMonthTuple untilYm = {
816  (int8_t) (year - LocalDate::kEpochYear + 1), 2 };
817 
818  mNumMatches = findMatches(mZoneInfo, startYm, untilYm, mMatches,
819  kMaxMatches);
820  if (DEBUG) log();
821  findTransitions(mTransitionStorage, mMatches, mNumMatches);
822  extended::Transition** begin = mTransitionStorage.getActivePoolBegin();
823  extended::Transition** end = mTransitionStorage.getActivePoolEnd();
824  fixTransitionTimes(begin, end);
825  generateStartUntilTimes(begin, end);
826  calcAbbreviations(begin, end);
827 
828  mIsFilled = true;
829  return true;
830  }
831 
833  bool isFilled(int16_t year) const {
834  return mIsFilled && (year == mYear);
835  }
836 
844  static uint8_t findMatches(const extended::ZoneInfo* zoneInfo,
845  const extended::YearMonthTuple& startYm,
846  const extended::YearMonthTuple& untilYm,
847  extended::ZoneMatch* matches, uint8_t maxMatches) {
848  if (DEBUG) logging::println("findMatches()");
849  uint8_t iMatch = 0;
850  const extended::ZoneEra* prev = &kAnchorEra;
851  for (uint8_t iEra = 0; iEra < zoneInfo->numEras; iEra++) {
852  const extended::ZoneEra* era = &zoneInfo->eras[iEra];
853  if (eraOverlapsInterval(prev, era, startYm, untilYm)) {
854  if (iMatch < maxMatches) {
855  matches[iMatch] = createMatch(prev, era, startYm, untilYm);
856  iMatch++;
857  }
858  }
859  prev = era;
860  }
861  return iMatch;
862  }
863 
874  static bool eraOverlapsInterval(
875  const extended::ZoneEra* prev,
876  const extended::ZoneEra* era,
877  const extended::YearMonthTuple& startYm,
878  const extended::YearMonthTuple& untilYm) {
879  return compareEraToYearMonth(prev, untilYm.yearTiny, untilYm.month) < 0
880  && compareEraToYearMonth(era, startYm.yearTiny, startYm.month) > 0;
881  }
882 
884  static int8_t compareEraToYearMonth(const extended::ZoneEra* era,
885  int8_t yearTiny, uint8_t month) {
886  if (era->untilYearTiny < yearTiny) return -1;
887  if (era->untilYearTiny > yearTiny) return 1;
888  if (era->untilMonth < month) return -1;
889  if (era->untilMonth > month) return 1;
890  if (era->untilDay > 1) return 1;
891  //if (era->untilTimeCode < 0) return -1; // never possible
892  if (era->untilTimeCode > 0) return 1;
893  return 0;
894  }
895 
902  static extended::ZoneMatch createMatch(
903  const extended::ZoneEra* prev,
904  const extended::ZoneEra* era,
905  const extended::YearMonthTuple& startYm,
906  const extended::YearMonthTuple& untilYm) {
907  extended::DateTuple startDate = {
908  prev->untilYearTiny, prev->untilMonth, prev->untilDay,
909  (int8_t) prev->untilTimeCode, prev->untilTimeModifier
910  };
911  extended::DateTuple lowerBound = {
912  startYm.yearTiny, startYm.month, 1, 0, 'w'
913  };
914  if (startDate < lowerBound) {
915  startDate = lowerBound;
916  }
917 
918  extended::DateTuple untilDate = {
919  era->untilYearTiny, era->untilMonth, era->untilDay,
920  (int8_t) era->untilTimeCode, era->untilTimeModifier
921  };
922  extended::DateTuple upperBound = {
923  untilYm.yearTiny, untilYm.month, 1, 0, 'w'
924  };
925  if (upperBound < untilDate) {
926  untilDate = upperBound;
927  }
928 
929  return {startDate, untilDate, era};
930  }
931 
936  static void findTransitions(
938  extended::ZoneMatch* matches,
939  uint8_t numMatches) {
940  if (DEBUG) logging::println("findTransitions()");
941  for (uint8_t i = 0; i < numMatches; i++) {
942  findTransitionsForMatch(transitionStorage, &matches[i]);
943  }
944  }
945 
947  static void findTransitionsForMatch(
949  const extended::ZoneMatch* match) {
950  if (DEBUG) logging::println("findTransitionsForMatch()");
951  const extended::ZonePolicy* policy = match->era->zonePolicy;
952  if (policy == nullptr) {
953  findTransitionsFromSimpleMatch(transitionStorage, match);
954  } else {
955  findTransitionsFromNamedMatch(transitionStorage, match);
956  }
957  }
958 
959  static void findTransitionsFromSimpleMatch(
961  const extended::ZoneMatch* match) {
962  if (DEBUG) logging::println("findTransitionsFromSimpleMatch()");
963  extended::Transition* freeTransition = transitionStorage.getFreeAgent();
964  freeTransition->match = match;
965  freeTransition->rule = nullptr;
966  freeTransition->transitionTime = match->startDateTime;
967 
968  transitionStorage.addFreeAgentToActivePool();
969  }
970 
971  static void findTransitionsFromNamedMatch(
973  const extended::ZoneMatch* match) {
974  if (DEBUG) logging::println("findTransitionsFromNamedMatch()");
975  transitionStorage.resetCandidatePool();
976  if (DEBUG) { match->log(); logging::println(); }
977  findCandidateTransitions(transitionStorage, match);
978  if (DEBUG) { transitionStorage.log(); logging::println(); }
979  fixTransitionTimes(
980  transitionStorage.getCandidatePoolBegin(),
981  transitionStorage.getCandidatePoolEnd());
982  selectActiveTransitions(transitionStorage, match);
983  if (DEBUG) { transitionStorage.log(); logging::println(); }
984 
985  transitionStorage.addActiveCandidatesToActivePool();
986  if (DEBUG) { transitionStorage.log(); logging::println(); }
987  }
988 
989  static void findCandidateTransitions(
991  const extended::ZoneMatch* match) {
992  if (DEBUG) {
993  logging::print("findCandidateTransitions(): ");
994  match->log();
995  logging::println();
996  }
997  const extended::ZonePolicy* policy = match->era->zonePolicy;
998  uint8_t numRules = policy->numRules;
999  const extended::ZoneRule* rules = policy->rules;
1000  int8_t startY = match->startDateTime.yearTiny;
1001  int8_t endY = match->untilDateTime.yearTiny;
1002 
1003  extended::Transition** prior = transitionStorage.reservePrior();
1004  (*prior)->active = false; // indicates "no prior transition"
1005  for (uint8_t r = 0; r < numRules; r++) {
1006  const extended::ZoneRule* const rule = &rules[r];
1007 
1008  // Add Transitions for interior years
1009  int8_t interiorYears[kMaxInteriorYears];
1010  uint8_t numYears = calcInteriorYears(interiorYears, kMaxInteriorYears,
1011  rule->fromYearTiny, rule->toYearTiny, startY, endY);
1012  for (uint8_t y = 0; y < numYears; y++) {
1013  int8_t year = interiorYears[y];
1014  extended::Transition* t = transitionStorage.getFreeAgent();
1015  createTransitionForYear(t, year, rule, match);
1016  int8_t status = compareTransitionToMatchFuzzy(t, match);
1017  if (status < 0) {
1018  setAsPriorTransition(transitionStorage, t);
1019  } else if (status == 1) {
1020  transitionStorage.addFreeAgentToCandidatePool();
1021  }
1022  }
1023 
1024  // Add Transition for prior year
1025  int8_t priorYear = getMostRecentPriorYear(
1026  rule->fromYearTiny, rule->toYearTiny, startY, endY);
1027  if (priorYear != LocalDate::kInvalidYearTiny) {
1028  if (DEBUG) logging::println(
1029  "findCandidateTransitions(): priorYear: %d", priorYear);
1030  extended::Transition* t = transitionStorage.getFreeAgent();
1031  createTransitionForYear(t, priorYear, rule, match);
1032  setAsPriorTransition(transitionStorage, t);
1033  }
1034  }
1035 
1036  // Add the reserved prior into the Candidate pool only if 'active' is
1037  // true, meaning that a prior was found.
1038  if ((*prior)->active) {
1039  if (DEBUG) logging::println(
1040  "findCandidateTransitions(): adding prior to Candidate pool");
1041  transitionStorage.addPriorToCandidatePool();
1042  }
1043  }
1044 
1049  static uint8_t calcInteriorYears(int8_t* interiorYears,
1050  uint8_t maxInteriorYears, int8_t fromYear, int8_t toYear,
1051  int8_t startYear, int8_t endYear) {
1052  uint8_t i = 0;
1053  for (int8_t year = startYear; year <= endYear; year++) {
1054  if (fromYear <= year && year <= toYear) {
1055  interiorYears[i] = year;
1056  i++;
1057  if (i >= maxInteriorYears) break;
1058  }
1059  }
1060  return i;
1061  }
1062 
1064  static void createTransitionForYear(extended::Transition* t, int8_t year,
1065  const extended::ZoneRule* rule,
1066  const extended::ZoneMatch* match) {
1067  t->match = match;
1068  t->transitionTime = getTransitionTime(year, rule);
1069  t->rule = rule;
1070  }
1071 
1077  static int8_t getMostRecentPriorYear(int8_t fromYear, int8_t toYear,
1078  int8_t startYear, int8_t /*endYear*/) {
1079  if (fromYear < startYear) {
1080  if (toYear < startYear) {
1081  return toYear;
1082  } else {
1083  return startYear - 1;
1084  }
1085  } else {
1087  }
1088  }
1089 
1090  static extended::DateTuple getTransitionTime(
1091  int8_t yearTiny, const extended::ZoneRule* rule) {
1092  uint8_t dayOfMonth = BasicZoneSpecifier::calcStartDayOfMonth(
1093  yearTiny + LocalDate::kEpochYear, rule->inMonth, rule->onDayOfWeek,
1094  rule->onDayOfMonth);
1095  return {yearTiny, rule->inMonth, dayOfMonth,
1096  (int8_t) rule->atTimeCode, rule->atTimeModifier};
1097  }
1098 
1109  static int8_t compareTransitionToMatchFuzzy(
1110  const extended::Transition* t, const extended::ZoneMatch* match) {
1111  int16_t ttMonths = t->transitionTime.yearTiny * 12
1112  + t->transitionTime.month;
1113 
1114  int16_t matchStartMonths = match->startDateTime.yearTiny * 12
1115  + match->startDateTime.month;
1116  if (ttMonths < matchStartMonths - 1) return -1;
1117 
1118  int16_t matchUntilMonths = match->untilDateTime.yearTiny * 12
1119  + match->untilDateTime.month;
1120  if (matchUntilMonths + 2 <= ttMonths) return 2;
1121 
1122  return 1;
1123  }
1124 
1126  static void setAsPriorTransition(
1128  extended::Transition* t) {
1129  if (DEBUG) logging::println("setAsPriorTransition()");
1130  extended::Transition* prior = transitionStorage.getPrior();
1131  if (prior->active) {
1132  if (prior->transitionTime < t->transitionTime) {
1133  t->active = true;
1134  transitionStorage.setFreeAgentAsPrior();
1135  }
1136  } else {
1137  t->active = true;
1138  transitionStorage.setFreeAgentAsPrior();
1139  }
1140  }
1141 
1150  static void fixTransitionTimes(
1151  extended::Transition** begin, extended::Transition** end) {
1152  if (DEBUG) logging::println("fixTransitionTimes(): #transitions: %d;",
1153  (int) (end - begin));
1154  // extend first Transition to -infinity
1155  extended::Transition* prev = *begin;
1156  for (extended::Transition** iter = begin; iter != end; ++iter) {
1157  extended::Transition* curr = *iter;
1158  if (DEBUG) {
1159  logging::println("fixTransitionTimes(): LOOP");
1160  curr->log();
1161  logging::println();
1162  }
1163  expandDateTuple(&curr->transitionTime,
1164  &curr->transitionTimeS, &curr->transitionTimeU,
1165  prev->offsetCode(), prev->deltaCode());
1166  prev = curr;
1167  }
1168  if (DEBUG) logging::println("fixTransitionTimes(): END");
1169  }
1170 
1176  static void expandDateTuple(extended::DateTuple* tt,
1178  int8_t offsetCode, int8_t deltaCode) {
1179  if (DEBUG) logging::println("expandDateTuple()");
1180  if (tt->modifier == 's') {
1181  *tts = *tt;
1182  *ttu = {tt->yearTiny, tt->month, tt->day,
1183  (int8_t) (tt->timeCode - offsetCode), 'u'};
1184  *tt = {tt->yearTiny, tt->month, tt->day,
1185  (int8_t) (tt->timeCode + deltaCode), 'w'};
1186  } else if (tt->modifier == 'u') {
1187  *ttu = *tt;
1188  *tts = {tt->yearTiny, tt->month, tt->day,
1189  (int8_t) (tt->timeCode + offsetCode), 's'};
1190  *tt = {tt->yearTiny, tt->month, tt->day,
1191  (int8_t) (tt->timeCode + offsetCode + deltaCode), 'w'};
1192  } else {
1193  // Explicit set the modifier to 'w' in case it was something else.
1194  tt->modifier = 'w';
1195  *tts = {tt->yearTiny, tt->month, tt->day,
1196  (int8_t) (tt->timeCode - deltaCode), 's'};
1197  *ttu = {tt->yearTiny, tt->month, tt->day,
1198  (int8_t) (tt->timeCode - deltaCode - offsetCode), 'u'};
1199  }
1200 
1201  if (DEBUG) logging::println("expandDateTuple(): normalizeDateTuple(): 1");
1202  normalizeDateTuple(tt);
1203  if (DEBUG) logging::println("expandDateTuple(): normalizeDateTuple(): 2");
1204  normalizeDateTuple(tts);
1205  if (DEBUG) logging::println("expandDateTuple(): normalizeDateTuple(): 3");
1206  normalizeDateTuple(ttu);
1207  }
1208 
1210  static void normalizeDateTuple(extended::DateTuple* dt) {
1211  const int8_t kOneDayAsCode = 4 * 24;
1212  if (dt->timeCode <= -kOneDayAsCode) {
1214  dt->yearTiny, dt->month, dt->day);
1215  local_date_mutation::decrementOneDay(ld);
1216  dt->yearTiny = ld.yearTiny();
1217  dt->month = ld.month();
1218  dt->day = ld.day();
1219  dt->timeCode += kOneDayAsCode;
1220  } else if (kOneDayAsCode <= dt->timeCode) {
1222  dt->yearTiny, dt->month, dt->day);
1223  local_date_mutation::incrementOneDay(ld);
1224  dt->yearTiny = ld.yearTiny();
1225  dt->month = ld.month();
1226  dt->day = ld.day();
1227  dt->timeCode -= kOneDayAsCode;
1228  } else {
1229  // do nothing
1230  }
1231  }
1232 
1237  static void selectActiveTransitions(
1239  const extended::ZoneMatch* match) {
1240  extended::Transition** begin = transitionStorage.getCandidatePoolBegin();
1241  extended::Transition** end = transitionStorage.getCandidatePoolEnd();
1242  if (DEBUG) logging::println("selectActiveTransitions(): #candidates: %d",
1243  (int) (end - begin));
1244  extended::Transition* prior = nullptr;
1245  for (extended::Transition** iter = begin; iter != end; ++iter) {
1246  extended::Transition* transition = *iter;
1247  processActiveTransition(match, transition, &prior);
1248  }
1249 
1250  // If the latest prior transition is found, shift it to start at the
1251  // startDateTime of the current match.
1252  if (prior) {
1253  if (DEBUG) logging::println(
1254  "selectActiveTransitions(): found latest prior");
1255  prior->originalTransitionTime = prior->transitionTime;
1256  prior->transitionTime = match->startDateTime;
1257  }
1258  }
1259 
1267  static void processActiveTransition(
1268  const extended::ZoneMatch* match,
1269  extended::Transition* transition,
1270  extended::Transition** prior) {
1271  int8_t status = compareTransitionToMatch(transition, match);
1272  if (status == 2) {
1273  transition->active = false;
1274  } else if (status == 1) {
1275  transition->active = true;
1276  } else if (status == 0) {
1277  if (*prior) {
1278  (*prior)->active = false;
1279  }
1280  transition->active = true;
1281  (*prior) = transition;
1282  } else { // (status < 0)
1283  if (*prior) {
1284  if ((*prior)->transitionTime < transition->transitionTime) {
1285  (*prior)->active = false;
1286  transition->active = true;
1287  (*prior) = transition;
1288  }
1289  } else {
1290  transition->active = true;
1291  (*prior) = transition;
1292  }
1293  }
1294  }
1295 
1310  static int8_t compareTransitionToMatch(
1311  const extended::Transition* transition,
1312  const extended::ZoneMatch* match) {
1313  const extended::DateTuple* transitionTime;
1314 
1315  const extended::DateTuple& matchStart = match->startDateTime;
1316  if (matchStart.modifier == 's') {
1317  transitionTime = &transition->transitionTimeS;
1318  } else if (matchStart.modifier == 'u') {
1319  transitionTime = &transition->transitionTimeU;
1320  } else { // assume 'w'
1321  transitionTime = &transition->transitionTime;
1322  }
1323  if (*transitionTime < matchStart) return -1;
1324  if (*transitionTime == matchStart) return 0;
1325 
1326  const extended::DateTuple& matchUntil = match->untilDateTime;
1327  if (matchUntil.modifier == 's') {
1328  transitionTime = &transition->transitionTimeS;
1329  } else if (matchUntil.modifier == 'u') {
1330  transitionTime = &transition->transitionTimeU;
1331  } else { // assume 'w'
1332  transitionTime = &transition->transitionTime;
1333  }
1334  if (*transitionTime < matchUntil) return 1;
1335  return 2;
1336  }
1337 
1343  static void generateStartUntilTimes(
1344  extended::Transition** begin, extended::Transition** end) {
1345  if (DEBUG) logging::println(
1346  "generateStartUntilTimes(): #transitions: %d;",
1347  (int) (end - begin));
1348 
1349  extended::Transition* prev = *begin;
1350  bool isAfterFirst = false;
1351  for (extended::Transition** iter = begin; iter != end; ++iter) {
1352  extended::Transition* const t = *iter;
1353  const extended::DateTuple& tt = t->transitionTime;
1354 
1355  // 1) Update the untilDateTime of the previous Transition
1356  if (isAfterFirst) {
1357  prev->untilDateTime = tt;
1358  }
1359 
1360  // 2) Calculate the current startDateTime by shifting the
1361  // transitionTime (represented in the UTC offset of the previous
1362  // transition) into the UTC offset of the *current* transition.
1363  int8_t code = tt.timeCode - prev->offsetCode() - prev->deltaCode()
1364  + t->offsetCode() + t->deltaCode();
1365  t->startDateTime = {tt.yearTiny, tt.month, tt.day, code, tt.modifier};
1366  normalizeDateTuple(&t->startDateTime);
1367 
1368  // 3) The epochSecond of the 'transitionTime' is determined by the
1369  // UTC offset of the *previous* Transition. However, the
1370  // transitionTime can be represented by an illegal time (e.g. 24:00).
1371  // So, it is better to use the properly normalized startDateTime
1372  // (calculated above) with the *current* UTC offset.
1373  //
1374  // NOTE: We should also be able to calculate this directly from
1375  // 'transitionTimeU' which should still be a valid field, because it
1376  // hasn't been clobbered by 'untilDateTime' yet. Not sure if this saves
1377  // any CPU time though, since we still need to mutiply by 900.
1378  const extended::DateTuple& st = t->startDateTime;
1379  const acetime_t offsetSeconds = (acetime_t) 900
1380  * (st.timeCode - t->offsetCode() - t->deltaCode());
1382  st.yearTiny, st.month, st.day);
1383  t->startEpochSeconds = ld.toEpochSeconds() + offsetSeconds;
1384 
1385  prev = t;
1386  isAfterFirst = true;
1387  }
1388 
1389  // The last Transition's until time is the until time of the ZoneMatch.
1390  extended::DateTuple untilTime = prev->match->untilDateTime;
1391  extended::DateTuple untilTimeS; // needed only for expandDateTuple
1392  extended::DateTuple untilTimeU; // needed only for expandDateTuple
1393  expandDateTuple(&untilTime, &untilTimeS, &untilTimeU,
1394  prev->offsetCode(), prev->deltaCode());
1395  prev->untilDateTime = untilTime;
1396  }
1397 
1401  static void calcAbbreviations(
1402  extended::Transition** begin, extended::Transition** end) {
1403  if (DEBUG) logging::println("calcAbbreviations(): #transitions: %d;",
1404  (int) (end - begin));
1405  for (extended::Transition** iter = begin; iter != end; ++iter) {
1406  extended::Transition* const t = *iter;
1407  const char* format = t->format();
1408  int8_t deltaCode = t->deltaCode();
1409  const char* letter = t->letter();
1410  createAbbreviation(t->abbrev, extended::Transition::kAbbrevSize,
1411  format, deltaCode, letter);
1412  }
1413  }
1414 
1449  static void createAbbreviation(char* dest, uint8_t destSize,
1450  const char* format, uint8_t deltaCode, const char* letterString) {
1451  // Check if RULES column is empty. Ignore the deltaCode because if
1452  // letterString is nullptr, we can only just copy the whole thing.
1453  if (letterString == nullptr) {
1454  strncpy(dest, format, destSize);
1455  dest[destSize - 1] = '\0';
1456  return;
1457  }
1458 
1459  // Check if FORMAT contains a '%'.
1460  if (strchr(format, '%') != nullptr) {
1461  copyAndReplace(dest, destSize, format, '%', letterString);
1462  } else {
1463  // Check if FORMAT contains a '/'.
1464  const char* slashPos = strchr(format, '/');
1465  if (slashPos != nullptr) {
1466  if (deltaCode == 0) {
1467  uint8_t headLength = (slashPos - format);
1468  if (headLength >= destSize) headLength = destSize - 1;
1469  memcpy(dest, format, headLength);
1470  dest[headLength] = '\0';
1471  } else {
1472  uint8_t tailLength = strlen(slashPos+1);
1473  if (tailLength >= destSize) tailLength = destSize - 1;
1474  memcpy(dest, slashPos+1, tailLength);
1475  dest[tailLength] = '\0';
1476  }
1477  } else {
1478  // Just copy the FORMAT disregarding deltaCode and letterString.
1479  strncpy(dest, format, destSize);
1480  dest[destSize - 1] = '\0';
1481  }
1482  }
1483  }
1484 
1490  static void copyAndReplace(char* dst, uint8_t dstSize, const char* src,
1491  char oldChar, const char* newString) {
1492  while (*src != '\0' && dstSize > 0) {
1493  if (*src == oldChar) {
1494  while (*newString != '\0' && dstSize > 0) {
1495  *dst++ = *newString++;
1496  dstSize--;
1497  }
1498  src++;
1499  } else {
1500  *dst++ = *src++;
1501  dstSize--;
1502  }
1503  }
1504 
1505  if (dstSize == 0) {
1506  --dst;
1507  }
1508  *dst = '\0';
1509  }
1510 
1511  const extended::ZoneInfo* const mZoneInfo;
1512 
1513  mutable int16_t mYear = 0;
1514  mutable bool mIsFilled = false;
1515  // NOTE: Maybe move mNumMatches and mMatches into a MatchStorage object.
1516  mutable uint8_t mNumMatches = 0; // actual number of matches
1517  mutable extended::ZoneMatch mMatches[kMaxMatches];
1518  mutable extended::TransitionStorage<kMaxTransitions> mTransitionStorage;
1519 };
1520 
1521 } // namespace ace_time
1522 
1523 #undef DEBUG
1524 
1525 #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:105
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.
static uint8_t calcStartDayOfMonth(int16_t year, uint8_t month, uint8_t onDayOfWeek, uint8_t onDayOfMonth)
Calculate the actual dayOfMonth of the expresssion (onDayOfWeek >= onDayOfMonth). ...
uint8_t minute() const
Return the minute.
void addActiveCandidatesToActivePool()
Add active candidates into the Active pool, and collapse the Candidate pool.
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
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.
int8_t yearTiny() const
Return the single-byte year offset from year 2000.
Definition: LocalDate.h:219
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 getHighWater() const
Return the high water mark.
bool isError() const
Return true if this TimeOffset represents an error.
Definition: TimeOffset.h:150
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...
An implementation of ZoneSpecifier that works for all zones defined by the TZ Database (with some zon...
uint8_t day() const
Return the day of the month.
A collection of transition rules which describe the DST rules of a given administrative region...
Definition: ZonePolicy.h:91
int8_t yearTiny() const
Return the single-byte year offset from year 2000.
void log() const
Used only for debugging.
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 log() const
Verify that the indexes are valid.
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.
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:95
char abbrev[kAbbrevSize]
The calculated effective time zone abbreviation, e.g.
const char * letter() const
Return the letter string.
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 day() const
Return the day of the month.
Definition: LocalDate.h:231
uint8_t month() const
Return the month with January=1, December=12.
static OffsetDateTime forEpochSeconds(acetime_t epochSeconds, TimeOffset timeOffset)
Factory method.
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:40
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:314
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
const LocalDate & localDate() const
Return the LocalDate.
static const uint8_t kAbbrevSize
Size of the timezone abbreviation.
const Transition * findTransitionForDateTime(const LocalDateTime &ldt) const
Return the Transition matching the given dateTime.
uint8_t const onDayOfWeek
Determined by the ON column.
Definition: ZonePolicy.h:35
uint8_t hour() const
Return the hour.
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
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
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:139
static TimeOffset forOffsetCode(int8_t offsetCode)
Create TimeOffset from the offset code.
Definition: TimeOffset.h:112
const extended::ZoneEra * era
The ZoneEra that matched the given year.
bool active
Determines if this transition is valid.
The date (year, month, day) and time (hour, minute, second) fields representing the time with an offs...
uint8_t getTransitionHighWater() const
Get the TransitionStorage high water mark.
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:39
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
int8_t offsetCode() const
The base offset code, not the total effective UTC offset.
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:30
DateTuple untilDateTime
Until time expressed using the UTC offset of the current Transition.
const extended::ZoneInfo * getZoneInfo() const
Return the underlying ZoneInfo.
void log() const
Used only for debugging.
int8_t const fromYearTiny
FROM year as an offset from year 2000 stored as a single byte.
Definition: ZonePolicy.h:15
OffsetDateTime getOffsetDateTime(const LocalDateTime &ldt) const override
Return the best estimate of the OffsetDateTime at the given LocalDateTime for the timezone of the cur...
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:33
DateTuple untilDateTime
The effective until time of the matching ZoneEra.
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.
uint8_t month() const
Return the month with January=1, December=12.
Definition: LocalDate.h:225
int16_t year() const
Return the full year instead of just the last 2 digits.
Definition: LocalDate.h:213
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...
const Transition * findTransition(acetime_t epochSeconds) const
Return the Transition matching the given epochSeconds.
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
int8_t deltaCode() const
The DST offset code.
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