AceTime  0.7
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
ExtendedZoneProcessor.h
1 /*
2  * MIT License
3  * Copyright (c) 2019 Brian T. Park
4  */
5 
6 #ifndef ACE_TIME_EXTENDED_ZONE_PROCESSOR_H
7 #define ACE_TIME_EXTENDED_ZONE_PROCESSOR_H
8 
9 #include <string.h> // memcpy()
10 #include <stdint.h>
11 #include "common/compat.h"
12 #include "internal/ZonePolicy.h"
13 #include "internal/ZoneInfo.h"
14 #include "common/logging.h"
15 #include "TimeOffset.h"
16 #include "LocalDate.h"
17 #include "OffsetDateTime.h"
18 #include "ZoneProcessor.h"
19 #include "BasicZoneProcessor.h"
20 #include "local_date_mutation.h"
21 
22 #define ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG 0
23 
24 class ExtendedZoneProcessorTest_compareEraToYearMonth;
25 class ExtendedZoneProcessorTest_compareEraToYearMonth2;
26 class ExtendedZoneProcessorTest_createMatch;
27 class ExtendedZoneProcessorTest_findMatches_simple;
28 class ExtendedZoneProcessorTest_findMatches_named;
29 class ExtendedZoneProcessorTest_findCandidateTransitions;
30 class ExtendedZoneProcessorTest_findTransitionsFromNamedMatch;
31 class ExtendedZoneProcessorTest_getTransitionTime;
32 class ExtendedZoneProcessorTest_createTransitionForYear;
33 class ExtendedZoneProcessorTest_normalizeDateTuple;
34 class ExtendedZoneProcessorTest_expandDateTuple;
35 class ExtendedZoneProcessorTest_calcInteriorYears;
36 class ExtendedZoneProcessorTest_getMostRecentPriorYear;
37 class ExtendedZoneProcessorTest_compareTransitionToMatchFuzzy;
38 class ExtendedZoneProcessorTest_compareTransitionToMatch;
39 class ExtendedZoneProcessorTest_processActiveTransition;
40 class ExtendedZoneProcessorTest_fixTransitionTimes_generateStartUntilTimes;
41 class ExtendedZoneProcessorTest_createAbbreviation;
42 class ExtendedZoneProcessorTest_setZoneInfo;
43 class TransitionStorageTest_getFreeAgent;
44 class TransitionStorageTest_getFreeAgent2;
45 class TransitionStorageTest_addFreeAgentToActivePool;
46 class TransitionStorageTest_reservePrior;
47 class TransitionStorageTest_addFreeAgentToCandidatePool;
48 class TransitionStorageTest_setFreeAgentAsPrior;
49 class TransitionStorageTest_addActiveCandidatesToActivePool;
50 class TransitionStorageTest_resetCandidatePool;
51 class TransitionStorageTest_findTransitionForDateTime;
52 
53 namespace ace_time {
54 
55 template<uint8_t SIZE, uint8_t TYPE, typename ZS, typename ZI, typename ZIB>
56 class ZoneProcessorCacheImpl;
57 
58 namespace extended {
59 
64 struct DateTuple {
65  DateTuple() = default;
66 
67  DateTuple(int8_t y, uint8_t mon, uint8_t d, int16_t min, uint8_t mod):
68  yearTiny(y), month(mon), day(d), modifier(mod), minutes(min) {}
69 
70  int8_t yearTiny; // [-127, 126], 127 will cause bugs
71  uint8_t month; // [1-12]
72  uint8_t day; // [1-31]
73  uint8_t modifier; // TIME_MODIFIER_S, TIME_MODIFIER_W, TIME_MODIFIER_U
74  int16_t minutes; // negative values allowed
75 
77  void log() const {
78  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
79  char c = "wsu"[(modifier>>4)];
80  logging::printf("DateTuple(%04d-%02u-%02uT%d'%c')",
81  yearTiny+LocalDate::kEpochYear, month, day, minutes, c);
82  }
83  }
84 };
85 
87 inline bool operator<(const DateTuple& a, const DateTuple& b) {
88  if (a.yearTiny < b.yearTiny) return true;
89  if (a.yearTiny > b.yearTiny) return false;
90  if (a.month < b.month) return true;
91  if (a.month > b.month) return false;
92  if (a.day < b.day) return true;
93  if (a.day > b.day) return false;
94  if (a.minutes < b.minutes) return true;
95  if (a.minutes > b.minutes) return false;
96  return false;
97 }
98 
99 inline bool operator>=(const DateTuple& a, const DateTuple& b) {
100  return ! (a < b);
101 }
102 
103 inline bool operator<=(const DateTuple& a, const DateTuple& b) {
104  return ! (b < a);
105 }
106 
107 inline bool operator>(const DateTuple& a, const DateTuple& b) {
108  return (b < a);
109 }
110 
112 inline bool operator==(const DateTuple& a, const DateTuple& b) {
113  return a.yearTiny == b.yearTiny
114  && a.month == b.month
115  && a.day == b.day
116  && a.minutes == b.minutes
117  && a.modifier == b.modifier;
118 }
119 
122  int8_t yearTiny;
123  uint8_t month;
124 };
125 
130 struct ZoneMatch {
133 
136 
139 
140  void log() const {
141  logging::printf("ZoneMatch(");
142  logging::printf("Start:"); startDateTime.log();
143  logging::printf("; Until:"); untilDateTime.log();
144  logging::printf("; Era: %snull", (era.isNotNull()) ? "!" : "");
145  logging::printf(")");
146  }
147 };
148 
174 struct Transition {
176  static const uint8_t kAbbrevSize = basic::Transition::kAbbrevSize;
177 
179  const ZoneMatch* match;
180 
187 
195 
196  union {
203 
209  };
210 
211  union {
218 
224  };
225 
231 
233  acetime_t startEpochSeconds;
234 
236  char abbrev[kAbbrevSize];
237 
239  char letterBuf[2];
240 
253  bool active;
254 
262  int8_t offsetCode;
263 
265  int8_t deltaCode;
266 
267  //-------------------------------------------------------------------------
268 
269  const char* format() const {
270  return match->era.format();
271  }
272 
278  const char* letter() const {
279  // RULES column is '-' or hh:mm, so return nullptr to indicate this.
280  if (rule.isNull()) {
281  return nullptr;
282  }
283 
284  // RULES point to a named rule, and LETTER is a single, printable
285  // character.
286  char letter = rule.letter();
287  if (letter >= 32) {
288  return letterBuf;
289  }
290 
291  // RULES points to a named rule, and the LETTER is a string. The
292  // rule->letter is a non-printable number < 32, which is an index into
293  // a list of strings given by match->era->zonePolicy->letters[].
294  const ZonePolicyBroker policy = match->era.zonePolicy();
295  uint8_t numLetters = policy.numLetters();
296  if (letter >= numLetters) {
297  // This should never happen unless there is a programming error. If it
298  // does, return an empty string. (createTransitionForYear() sets
299  // letterBuf to a NUL terminated empty string if rule->letter < 32)
300  return letterBuf;
301  }
302 
303  // Return the string at index 'rule->letter'.
304  return policy.letter(letter);
305  }
306 
308  void log() const {
309  logging::printf("Transition(");
310  if (sizeof(acetime_t) == sizeof(int)) {
311  logging::printf("sE: %d", startEpochSeconds);
312  } else {
313  logging::printf("sE: %ld", startEpochSeconds);
314  }
315  logging::printf("; match: %snull", (match) ? "!" : "");
316  logging::printf("; era: %snull",
317  (match && match->era.isNotNull()) ? "!" : "");
318  logging::printf("; oCode: %d", offsetCode);
319  logging::printf("; dCode: %d", deltaCode);
320  logging::printf("; tt: "); transitionTime.log();
321  if (rule.isNotNull()) {
322  logging::printf("; R.fY: %d", rule.fromYearTiny());
323  logging::printf("; R.tY: %d", rule.toYearTiny());
324  logging::printf("; R.M: %d", rule.inMonth());
325  logging::printf("; R.dow: %d", rule.onDayOfWeek());
326  logging::printf("; R.dom: %d", rule.onDayOfMonth());
327  }
328  }
329 };
330 
357 template<uint8_t SIZE>
359  public:
362 
364  void init() {
365  for (uint8_t i = 0; i < SIZE; i++) {
366  mTransitions[i] = &mPool[i];
367  }
368  mIndexPrior = 0;
369  mIndexCandidates = 0;
370  mIndexFree = 0;
371  }
372 
374  Transition* getPrior() { return mTransitions[mIndexPrior]; }
375 
377  void swap(Transition** a, Transition** b) {
378  Transition* tmp = *a;
379  *a = *b;
380  *b = tmp;
381  }
382 
392  mIndexCandidates = mIndexPrior;
393  mIndexFree = mIndexPrior;
394  }
395 
396  Transition** getCandidatePoolBegin() {
397  return &mTransitions[mIndexCandidates];
398  }
399  Transition** getCandidatePoolEnd() {
400  return &mTransitions[mIndexFree];
401  }
402 
403  Transition** getActivePoolBegin() { return &mTransitions[0]; }
404  Transition** getActivePoolEnd() { return &mTransitions[mIndexFree]; }
405 
412  // Set the internal high water mark. If that index becomes SIZE,
413  // then we know we have an overflow.
414  if (mIndexFree > mHighWater) {
415  mHighWater = mIndexFree;
416  }
417 
418  if (mIndexFree < SIZE) {
419  return mTransitions[mIndexFree];
420  } else {
421  return mTransitions[SIZE - 1];
422  }
423  }
424 
433  if (mIndexFree >= SIZE) return;
434  mIndexFree++;
435  mIndexPrior = mIndexFree;
436  mIndexCandidates = mIndexFree;
437  }
438 
445  mIndexCandidates++;
446  mIndexFree++;
447  return &mTransitions[mIndexPrior];
448  }
449 
452  swap(&mTransitions[mIndexPrior], &mTransitions[mIndexFree]);
453  }
454 
461  mIndexCandidates--;
462  }
463 
471  if (mIndexFree >= SIZE) return;
472  for (uint8_t i = mIndexFree; i > mIndexCandidates; i--) {
473  Transition* curr = mTransitions[i];
474  Transition* prev = mTransitions[i - 1];
475  if (curr->transitionTime >= prev->transitionTime) break;
476  mTransitions[i] = prev;
477  mTransitions[i - 1] = curr;
478  }
479  mIndexFree++;
480  }
481 
487  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
488  logging::printf("addActiveCandidatesToActivePool()\n");
489  }
490  uint8_t iActive = mIndexPrior;
491  uint8_t iCandidate = mIndexCandidates;
492  for (; iCandidate < mIndexFree; iCandidate++) {
493  if (mTransitions[iCandidate]->active) {
494  if (iActive != iCandidate) {
495  swap(&mTransitions[iActive], &mTransitions[iCandidate]);
496  }
497  ++iActive;
498  }
499  }
500  mIndexPrior = iActive;
501  mIndexCandidates = iActive;
502  mIndexFree = iActive;
503  }
504 
513  const Transition* findTransition(acetime_t epochSeconds) const {
514  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
515  logging::printf( "findTransition(): mIndexFree: %d\n", mIndexFree);
516  }
517 
518  const Transition* match = nullptr;
519  for (uint8_t i = 0; i < mIndexFree; i++) {
520  const Transition* candidate = mTransitions[i];
521  if (candidate->startEpochSeconds > epochSeconds) break;
522  match = candidate;
523  }
524  return match;
525  }
526 
551  const {
552  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
553  logging::printf(
554  "findTransitionForDateTime(): mIndexFree: %d\n", mIndexFree);
555  }
556 
557  // Convert LocalDateTime to DateTuple.
558  DateTuple localDate = { ldt.yearTiny(), ldt.month(), ldt.day(),
559  (int16_t) (ldt.hour() * 60 + ldt.minute()),
561  const Transition* match = nullptr;
562 
563  // Find the last Transition that matches
564  for (uint8_t i = 0; i < mIndexFree; i++) {
565  const Transition* candidate = mTransitions[i];
566  if (candidate->startDateTime > localDate) break;
567  match = candidate;
568  }
569  return match;
570  }
571 
573  void log() const {
574  logging::printf("TransitionStorage:\n");
575  logging::printf(" mIndexPrior: %d\n", mIndexPrior);
576  logging::printf(" mIndexCandidates: %d\n", mIndexCandidates);
577  logging::printf(" mIndexFree: %d\n", mIndexFree);
578  if (mIndexPrior != 0) {
579  logging::printf(" Actives:\n");
580  for (uint8_t i = 0; i < mIndexPrior; i++) {
581  mTransitions[i]->log();
582  logging::printf("\n");
583  }
584  }
585  if (mIndexPrior != mIndexCandidates) {
586  logging::printf(" Prior: ");
587  mTransitions[mIndexPrior]->log();
588  logging::printf("\n");
589  }
590  if (mIndexCandidates != mIndexFree) {
591  logging::printf(" Candidates:\n");
592  for (uint8_t i = mIndexCandidates; i < mIndexFree; i++) {
593  mTransitions[i]->log();
594  logging::printf("\n");
595  }
596  }
597  }
598 
600  void resetHighWater() { mHighWater = 0; }
601 
607  uint8_t getHighWater() const { return mHighWater; }
608 
609  private:
610  friend class ::TransitionStorageTest_getFreeAgent;
611  friend class ::TransitionStorageTest_getFreeAgent2;
612  friend class ::TransitionStorageTest_addFreeAgentToActivePool;
613  friend class ::TransitionStorageTest_reservePrior;
614  friend class ::TransitionStorageTest_addFreeAgentToCandidatePool;
615  friend class ::TransitionStorageTest_setFreeAgentAsPrior;
616  friend class ::TransitionStorageTest_addActiveCandidatesToActivePool;
617  friend class ::TransitionStorageTest_findTransitionForDateTime;
618  friend class ::TransitionStorageTest_resetCandidatePool;
619 
621  Transition* getTransition(uint8_t i) { return mTransitions[i]; }
622 
623  Transition mPool[SIZE];
624  Transition* mTransitions[SIZE];
625  uint8_t mIndexPrior;
626  uint8_t mIndexCandidates;
627  uint8_t mIndexFree;
628 
630  uint8_t mHighWater = 0;
631 };
632 
633 } // namespace extended
634 
659  public:
665  const extended::ZoneInfo* zoneInfo = nullptr):
666  ZoneProcessor(kTypeExtended),
667  mZoneInfo(zoneInfo) {}
668 
670  const void* getZoneInfo() const override {
671  return mZoneInfo.zoneInfo();
672  }
673 
674  uint32_t getZoneId() const override { return mZoneInfo.zoneId(); }
675 
676  TimeOffset getUtcOffset(acetime_t epochSeconds) const override {
677  bool success = init(epochSeconds);
678  if (!success) return TimeOffset::forError();
679  const extended::Transition* transition = findTransition(epochSeconds);
680  return (transition)
682  transition->offsetCode + transition->deltaCode)
684  }
685 
686  TimeOffset getDeltaOffset(acetime_t epochSeconds) const override {
687  bool success = init(epochSeconds);
688  if (!success) return TimeOffset::forError();
689  const extended::Transition* transition = findTransition(epochSeconds);
690  return TimeOffset::forOffsetCode(transition->deltaCode);
691  }
692 
693  const char* getAbbrev(acetime_t epochSeconds) const override {
694  bool success = init(epochSeconds);
695  if (!success) return "";
696  const extended::Transition* transition = findTransition(epochSeconds);
697  return transition->abbrev;
698  }
699 
700  OffsetDateTime getOffsetDateTime(const LocalDateTime& ldt) const override {
701  TimeOffset offset;
702  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
703  logging::printf("getOffsetDateTime(): ldt=");
704  ldt.printTo(SERIAL_PORT_MONITOR);
705  SERIAL_PORT_MONITOR.println();
706  }
707  bool success = init(ldt.localDate());
708 
709  // Find the Transition to get the DST offset
710  if (success) {
711  const extended::Transition* transition =
712  mTransitionStorage.findTransitionForDateTime(ldt);
713  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
714  logging::printf("getOffsetDateTime(): match transition=");
715  transition->log();
716  logging::printf("\n");
717  }
718  offset = (transition)
720  transition->offsetCode + transition->deltaCode)
722  } else {
723  offset = TimeOffset::forError();
724  }
725 
726  auto odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset);
727  if (offset.isError()) {
728  return odt;
729  }
730  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
731  logging::printf("getOffsetDateTime(): odt=");
732  odt.printTo(SERIAL_PORT_MONITOR);
733  SERIAL_PORT_MONITOR.println();
734  }
735 
736  // Normalize the OffsetDateTime, causing LocalDateTime in the DST
737  // transtion gap to be shifted forward one hour. For LocalDateTime in an
738  // overlap (DST->STD transition), the earlier UTC offset is selected// by
739  // findTransitionForDateTime(). Use that to calculate the epochSeconds,
740  // then recalculate the offset. Use this final offset to determine the
741  // effective OffsetDateTime that will survive a round-trip unchanged.
742  acetime_t epochSeconds = odt.toEpochSeconds();
743  const extended::Transition* transition =
744  mTransitionStorage.findTransition(epochSeconds);
745  offset = (transition)
747  transition->offsetCode + transition->deltaCode)
749  odt = OffsetDateTime::forEpochSeconds(epochSeconds, offset);
750  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
751  logging::printf("getOffsetDateTime(): normalized(odt)=");
752  odt.printTo(SERIAL_PORT_MONITOR);
753  SERIAL_PORT_MONITOR.println();
754  }
755  return odt;
756  }
757 
758  void printTo(Print& printer) const override;
759 
760  void printShortTo(Print& printer) const override;
761 
763  void log() const {
764  logging::printf("ExtendedZoneProcessor:\n");
765  logging::printf(" mYear: %d\n", mYear);
766  logging::printf(" mNumMatches: %d\n", mNumMatches);
767  for (int i = 0; i < mNumMatches; i++) {
768  logging::printf(" Match %d: ", i);
769  mMatches[i].log();
770  logging::printf("\n");
771  }
772  mTransitionStorage.log();
773  }
774 
777  mTransitionStorage.resetHighWater();
778  }
779 
781  uint8_t getTransitionHighWater() const {
782  return mTransitionStorage.getHighWater();
783  }
784 
785  private:
786  friend class ::ExtendedZoneProcessorTest_compareEraToYearMonth;
787  friend class ::ExtendedZoneProcessorTest_compareEraToYearMonth2;
788  friend class ::ExtendedZoneProcessorTest_createMatch;
789  friend class ::ExtendedZoneProcessorTest_findMatches_simple;
790  friend class ::ExtendedZoneProcessorTest_findMatches_named;
791  friend class ::ExtendedZoneProcessorTest_findCandidateTransitions;
792  friend class ::ExtendedZoneProcessorTest_findTransitionsFromNamedMatch;
793  friend class ::ExtendedZoneProcessorTest_getTransitionTime;
794  friend class ::ExtendedZoneProcessorTest_createTransitionForYear;
795  friend class ::ExtendedZoneProcessorTest_normalizeDateTuple;
796  friend class ::ExtendedZoneProcessorTest_expandDateTuple;
797  friend class ::ExtendedZoneProcessorTest_calcInteriorYears;
798  friend class ::ExtendedZoneProcessorTest_getMostRecentPriorYear;
799  friend class ::ExtendedZoneProcessorTest_compareTransitionToMatchFuzzy;
800  friend class ::ExtendedZoneProcessorTest_compareTransitionToMatch;
801  friend class ::ExtendedZoneProcessorTest_processActiveTransition;
802  friend class ::ExtendedZoneProcessorTest_fixTransitionTimes_generateStartUntilTimes;
803  friend class ::ExtendedZoneProcessorTest_createAbbreviation;
804  friend class ::ExtendedZoneProcessorTest_setZoneInfo;
805  friend class ::TransitionStorageTest_findTransitionForDateTime;
806 
807  template<uint8_t SIZE, uint8_t TYPE, typename ZS, typename ZI, typename ZIB>
808  friend class ZoneProcessorCacheImpl; // setZoneInfo()
809 
810  // Disable copy constructor and assignment operator.
812  ExtendedZoneProcessor& operator=(const ExtendedZoneProcessor&) = delete;
813 
818  static const uint8_t kMaxMatches = 4;
819 
827  static const uint8_t kMaxTransitions = 8;
828 
833  static const uint8_t kMaxInteriorYears = 4;
834 
836  static const extended::ZoneEra kAnchorEra;
837 
838  bool equals(const ZoneProcessor& other) const override {
839  const auto& that = (const ExtendedZoneProcessor&) other;
840  return getZoneInfo() == that.getZoneInfo();
841  }
842 
844  void setZoneInfo(const void* zoneInfo) override {
845  if (mZoneInfo.zoneInfo() == zoneInfo) return;
846 
847  mZoneInfo = extended::ZoneInfoBroker(
848  (const extended::ZoneInfo*) zoneInfo);
849  mYear = 0;
850  mIsFilled = false;
851  mNumMatches = 0;
852  }
853 
858  const extended::Transition* findTransition(acetime_t epochSeconds) const {
859  return mTransitionStorage.findTransition(epochSeconds);
860  }
861 
863  bool init(acetime_t epochSeconds) const {
864  LocalDate ld = LocalDate::forEpochSeconds(epochSeconds);
865  return init(ld);
866  }
867 
872  bool init(const LocalDate& ld) const {
873  int16_t year = ld.year();
874  if (isFilled(year)) return true;
875  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
876  logging::printf("init(): %d\n", year);
877  }
878 
879  mYear = year;
880  mNumMatches = 0; // clear cache
881  mTransitionStorage.init();
882 
883  if (year < mZoneInfo.startYear() - 1 || mZoneInfo.untilYear() < year) {
884  return false;
885  }
886 
887  extended::YearMonthTuple startYm = {
888  (int8_t) (year - LocalDate::kEpochYear - 1), 12 };
889  extended::YearMonthTuple untilYm = {
890  (int8_t) (year - LocalDate::kEpochYear + 1), 2 };
891 
892  mNumMatches = findMatches(mZoneInfo, startYm, untilYm, mMatches,
893  kMaxMatches);
894  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
895  findTransitions(mTransitionStorage, mMatches, mNumMatches);
896  extended::Transition** begin = mTransitionStorage.getActivePoolBegin();
897  extended::Transition** end = mTransitionStorage.getActivePoolEnd();
898  fixTransitionTimes(begin, end);
899  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
900  generateStartUntilTimes(begin, end);
901  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
902  calcAbbreviations(begin, end);
903  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
904 
905  mIsFilled = true;
906  return true;
907  }
908 
910  bool isFilled(int16_t year) const {
911  return mIsFilled && (year == mYear);
912  }
913 
921  static uint8_t findMatches(const extended::ZoneInfoBroker zoneInfo,
922  const extended::YearMonthTuple& startYm,
923  const extended::YearMonthTuple& untilYm,
924  extended::ZoneMatch* matches, uint8_t maxMatches) {
925  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
926  logging::printf("findMatches()\n");
927  }
928  uint8_t iMatch = 0;
930  for (uint8_t iEra = 0; iEra < zoneInfo.numEras(); iEra++) {
931  const extended::ZoneEraBroker era = zoneInfo.era(iEra);
932  if (eraOverlapsInterval(prev, era, startYm, untilYm)) {
933  if (iMatch < maxMatches) {
934  matches[iMatch] = createMatch(prev, era, startYm, untilYm);
935  iMatch++;
936  }
937  }
938  prev = era;
939  }
940  return iMatch;
941  }
942 
953  static bool eraOverlapsInterval(
954  const extended::ZoneEraBroker prev,
955  const extended::ZoneEraBroker era,
956  const extended::YearMonthTuple& startYm,
957  const extended::YearMonthTuple& untilYm) {
958  return compareEraToYearMonth(prev, untilYm.yearTiny, untilYm.month) < 0
959  && compareEraToYearMonth(era, startYm.yearTiny, startYm.month) > 0;
960  }
961 
963  static int8_t compareEraToYearMonth(const extended::ZoneEraBroker era,
964  int8_t yearTiny, uint8_t month) {
965  if (era.untilYearTiny() < yearTiny) return -1;
966  if (era.untilYearTiny() > yearTiny) return 1;
967  if (era.untilMonth() < month) return -1;
968  if (era.untilMonth() > month) return 1;
969  if (era.untilDay() > 1) return 1;
970  //if (era.untilTimeMinutes() < 0) return -1; // never possible
971  if (era.untilTimeMinutes() > 0) return 1;
972  return 0;
973  }
974 
981  static extended::ZoneMatch createMatch(
982  const extended::ZoneEraBroker prev,
983  const extended::ZoneEraBroker era,
984  const extended::YearMonthTuple& startYm,
985  const extended::YearMonthTuple& untilYm) {
986  extended::DateTuple startDate = {
987  prev.untilYearTiny(), prev.untilMonth(), prev.untilDay(),
988  (int16_t) prev.untilTimeMinutes(), prev.untilTimeModifier()
989  };
990  extended::DateTuple lowerBound = {
991  startYm.yearTiny, startYm.month, 1, 0,
993  };
994  if (startDate < lowerBound) {
995  startDate = lowerBound;
996  }
997 
998  extended::DateTuple untilDate = {
999  era.untilYearTiny(), era.untilMonth(), era.untilDay(),
1000  (int16_t) era.untilTimeMinutes(), era.untilTimeModifier()
1001  };
1002  extended::DateTuple upperBound = {
1003  untilYm.yearTiny, untilYm.month, 1, 0,
1005  };
1006  if (upperBound < untilDate) {
1007  untilDate = upperBound;
1008  }
1009 
1010  return {startDate, untilDate, era};
1011  }
1012 
1017  static void findTransitions(
1019  extended::ZoneMatch* matches,
1020  uint8_t numMatches) {
1021  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1022  logging::printf("findTransitions()\n");
1023  }
1024  for (uint8_t i = 0; i < numMatches; i++) {
1025  findTransitionsForMatch(transitionStorage, &matches[i]);
1026  }
1027  }
1028 
1030  static void findTransitionsForMatch(
1032  const extended::ZoneMatch* match) {
1033  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1034  logging::printf("findTransitionsForMatch()\n");
1035  }
1036  const extended::ZonePolicyBroker policy = match->era.zonePolicy();
1037  if (policy.isNull()) {
1038  findTransitionsFromSimpleMatch(transitionStorage, match);
1039  } else {
1040  findTransitionsFromNamedMatch(transitionStorage, match);
1041  }
1042  }
1043 
1044  static void findTransitionsFromSimpleMatch(
1046  const extended::ZoneMatch* match) {
1047  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1048  logging::printf("findTransitionsFromSimpleMatch()\n");
1049  }
1050  extended::Transition* freeTransition = transitionStorage.getFreeAgent();
1051  createTransitionForYear(freeTransition, 0 /*not used*/,
1052  extended::ZoneRuleBroker(nullptr) /*rule*/, match);
1053  transitionStorage.addFreeAgentToActivePool();
1054  }
1055 
1056  static void findTransitionsFromNamedMatch(
1058  const extended::ZoneMatch* match) {
1059  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1060  logging::printf("findTransitionsFromNamedMatch()\n");
1061  }
1062  transitionStorage.resetCandidatePool();
1063  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1064  match->log(); logging::printf("\n");
1065  }
1066  findCandidateTransitions(transitionStorage, match);
1067  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1068  transitionStorage.log(); logging::printf("\n");
1069  }
1070  fixTransitionTimes(
1071  transitionStorage.getCandidatePoolBegin(),
1072  transitionStorage.getCandidatePoolEnd());
1073  selectActiveTransitions(transitionStorage, match);
1074  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1075  transitionStorage.log(); logging::printf("\n");
1076  }
1077 
1078  transitionStorage.addActiveCandidatesToActivePool();
1079  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1080  transitionStorage.log(); logging::printf("\n");
1081  }
1082  }
1083 
1084  static void findCandidateTransitions(
1086  const extended::ZoneMatch* match) {
1087  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1088  logging::printf("findCandidateTransitions(): ");
1089  match->log();
1090  logging::printf("\n");
1091  }
1092  const extended::ZonePolicyBroker policy = match->era.zonePolicy();
1093  uint8_t numRules = policy.numRules();
1094  int8_t startY = match->startDateTime.yearTiny;
1095  int8_t endY = match->untilDateTime.yearTiny;
1096 
1097  extended::Transition** prior = transitionStorage.reservePrior();
1098  (*prior)->active = false; // indicates "no prior transition"
1099  for (uint8_t r = 0; r < numRules; r++) {
1100  const extended::ZoneRuleBroker rule = policy.rule(r);
1101 
1102  // Add Transitions for interior years
1103  int8_t interiorYears[kMaxInteriorYears];
1104  uint8_t numYears = calcInteriorYears(interiorYears, kMaxInteriorYears,
1105  rule.fromYearTiny(), rule.toYearTiny(), startY, endY);
1106  for (uint8_t y = 0; y < numYears; y++) {
1107  int8_t year = interiorYears[y];
1108  extended::Transition* t = transitionStorage.getFreeAgent();
1109  createTransitionForYear(t, year, rule, match);
1110  int8_t status = compareTransitionToMatchFuzzy(t, match);
1111  if (status < 0) {
1112  setAsPriorTransition(transitionStorage, t);
1113  } else if (status == 1) {
1114  transitionStorage.addFreeAgentToCandidatePool();
1115  }
1116  }
1117 
1118  // Add Transition for prior year
1119  int8_t priorYear = getMostRecentPriorYear(
1120  rule.fromYearTiny(), rule.toYearTiny(), startY, endY);
1121  if (priorYear != LocalDate::kInvalidYearTiny) {
1122  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1123  logging::printf(
1124  "findCandidateTransitions(): priorYear: %d\n", priorYear);
1125  }
1126  extended::Transition* t = transitionStorage.getFreeAgent();
1127  createTransitionForYear(t, priorYear, rule, match);
1128  setAsPriorTransition(transitionStorage, t);
1129  }
1130  }
1131 
1132  // Add the reserved prior into the Candidate pool only if 'active' is
1133  // true, meaning that a prior was found.
1134  if ((*prior)->active) {
1135  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1136  logging::printf(
1137  "findCandidateTransitions(): adding prior to Candidate pool\n");
1138  }
1139  transitionStorage.addPriorToCandidatePool();
1140  }
1141  }
1142 
1147  static uint8_t calcInteriorYears(int8_t* interiorYears,
1148  uint8_t maxInteriorYears, int8_t fromYear, int8_t toYear,
1149  int8_t startYear, int8_t endYear) {
1150  uint8_t i = 0;
1151  for (int8_t year = startYear; year <= endYear; year++) {
1152  if (fromYear <= year && year <= toYear) {
1153  interiorYears[i] = year;
1154  i++;
1155  if (i >= maxInteriorYears) break;
1156  }
1157  }
1158  return i;
1159  }
1160 
1167  static void createTransitionForYear(extended::Transition* t, int8_t year,
1168  const extended::ZoneRuleBroker rule,
1169  const extended::ZoneMatch* match) {
1170  t->match = match;
1171  t->rule = rule;
1172  t->offsetCode = match->era.offsetCode();
1173  t->letterBuf[0] = '\0';
1174 
1175  if (rule.isNotNull()) {
1176  t->transitionTime = getTransitionTime(year, rule);
1177  t->deltaCode = rule.deltaCode();
1178 
1179  char letter = rule.letter();
1180  if (letter >= 32) {
1181  // If LETTER is a '-', treat it the same as an empty string.
1182  if (letter != '-') {
1183  t->letterBuf[0] = letter;
1184  t->letterBuf[1] = '\0';
1185  }
1186  } else {
1187  // rule->letter is a long string, so is referenced as an offset index
1188  // into the ZonePolicy.letters array. The string cannot fit in
1189  // letterBuf, so will be retrieved by the letter() method below.
1190  }
1191  } else {
1192  t->transitionTime = match->startDateTime;
1193  t->deltaCode = match->era.deltaCode();
1194  }
1195  }
1196 
1202  static int8_t getMostRecentPriorYear(int8_t fromYear, int8_t toYear,
1203  int8_t startYear, int8_t /*endYear*/) {
1204  if (fromYear < startYear) {
1205  if (toYear < startYear) {
1206  return toYear;
1207  } else {
1208  return startYear - 1;
1209  }
1210  } else {
1212  }
1213  }
1214 
1215  static extended::DateTuple getTransitionTime(
1216  int8_t yearTiny, const extended::ZoneRuleBroker rule) {
1218  yearTiny + LocalDate::kEpochYear, rule.inMonth(), rule.onDayOfWeek(),
1219  rule.onDayOfMonth());
1220  return {yearTiny, monthDay.month, monthDay.day,
1221  (int16_t) rule.atTimeMinutes(), rule.atTimeModifier()};
1222  }
1223 
1234  static int8_t compareTransitionToMatchFuzzy(
1235  const extended::Transition* t, const extended::ZoneMatch* match) {
1236  int16_t ttMonths = t->transitionTime.yearTiny * 12
1237  + t->transitionTime.month;
1238 
1239  int16_t matchStartMonths = match->startDateTime.yearTiny * 12
1240  + match->startDateTime.month;
1241  if (ttMonths < matchStartMonths - 1) return -1;
1242 
1243  int16_t matchUntilMonths = match->untilDateTime.yearTiny * 12
1244  + match->untilDateTime.month;
1245  if (matchUntilMonths + 2 <= ttMonths) return 2;
1246 
1247  return 1;
1248  }
1249 
1251  static void setAsPriorTransition(
1253  extended::Transition* t) {
1254  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1255  logging::printf("setAsPriorTransition()\n");
1256  }
1257  extended::Transition* prior = transitionStorage.getPrior();
1258  if (prior->active) {
1259  if (prior->transitionTime < t->transitionTime) {
1260  t->active = true;
1261  transitionStorage.setFreeAgentAsPrior();
1262  }
1263  } else {
1264  t->active = true;
1265  transitionStorage.setFreeAgentAsPrior();
1266  }
1267  }
1268 
1277  static void fixTransitionTimes(
1278  extended::Transition** begin, extended::Transition** end) {
1279  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1280  logging::printf("fixTransitionTimes(): #transitions: %d;\n",
1281  (int) (end - begin));
1282  }
1283 
1284  // extend first Transition to -infinity
1285  extended::Transition* prev = *begin;
1286 
1287  for (extended::Transition** iter = begin; iter != end; ++iter) {
1288  extended::Transition* curr = *iter;
1289  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1290  logging::printf("fixTransitionTimes(): LOOP\n");
1291  curr->log();
1292  logging::printf("\n");
1293  }
1294  expandDateTuple(&curr->transitionTime,
1295  &curr->transitionTimeS, &curr->transitionTimeU,
1296  prev->offsetCode, prev->deltaCode);
1297  prev = curr;
1298  }
1299  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1300  logging::printf("fixTransitionTimes(): END\n");
1301  }
1302  }
1303 
1309  static void expandDateTuple(extended::DateTuple* tt,
1311  int8_t offsetCode, int8_t deltaCode) {
1312  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1313  logging::printf("expandDateTuple()\n");
1314  }
1315  if (tt->modifier == extended::ZoneContext::TIME_MODIFIER_S) {
1316  *tts = *tt;
1317  *ttu = {tt->yearTiny, tt->month, tt->day,
1318  (int16_t) (tt->minutes - 15 * offsetCode),
1320  *tt = {tt->yearTiny, tt->month, tt->day,
1321  (int16_t) (tt->minutes + 15 * deltaCode),
1323  } else if (tt->modifier == extended::ZoneContext::TIME_MODIFIER_U) {
1324  *ttu = *tt;
1325  *tts = {tt->yearTiny, tt->month, tt->day,
1326  (int16_t) (tt->minutes + 15 * offsetCode),
1328  *tt = {tt->yearTiny, tt->month, tt->day,
1329  (int16_t) (tt->minutes + 15 * (offsetCode + deltaCode)),
1331  } else {
1332  // Explicit set the modifier to 'w' in case it was something else.
1334  *tts = {tt->yearTiny, tt->month, tt->day,
1335  (int16_t) (tt->minutes - 15 * deltaCode),
1337  *ttu = {tt->yearTiny, tt->month, tt->day,
1338  (int16_t) (tt->minutes - 15 * (deltaCode + offsetCode)),
1340  }
1341 
1342  normalizeDateTuple(tt);
1343  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1344  logging::printf("expandDateTuple(): normalizeDateTuple(tt): ");
1345  tt->log();
1346  logging::printf("\n");
1347  }
1348 
1349  normalizeDateTuple(tts);
1350  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1351  logging::printf("expandDateTuple(): normalizeDateTuple(tts): ");
1352  tts->log();
1353  logging::printf("\n");
1354  }
1355 
1356  normalizeDateTuple(ttu);
1357  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1358  logging::printf("expandDateTuple(): normalizeDateTuple(ttu): ");
1359  ttu->log();
1360  logging::printf("\n");
1361  }
1362  }
1363 
1368  static void normalizeDateTuple(extended::DateTuple* dt) {
1369  const int16_t kOneDayAsMinutes = 60 * 24;
1370  if (dt->minutes <= -kOneDayAsMinutes) {
1372  dt->yearTiny, dt->month, dt->day);
1373  local_date_mutation::decrementOneDay(ld);
1374  dt->yearTiny = ld.yearTiny();
1375  dt->month = ld.month();
1376  dt->day = ld.day();
1377  dt->minutes += kOneDayAsMinutes;
1378  } else if (kOneDayAsMinutes <= dt->minutes) {
1380  dt->yearTiny, dt->month, dt->day);
1381  local_date_mutation::incrementOneDay(ld);
1382  dt->yearTiny = ld.yearTiny();
1383  dt->month = ld.month();
1384  dt->day = ld.day();
1385  dt->minutes -= kOneDayAsMinutes;
1386  } else {
1387  // do nothing
1388  }
1389  }
1390 
1395  static void selectActiveTransitions(
1397  const extended::ZoneMatch* match) {
1398  extended::Transition** begin = transitionStorage.getCandidatePoolBegin();
1399  extended::Transition** end = transitionStorage.getCandidatePoolEnd();
1400  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1401  logging::printf("selectActiveTransitions(): #candidates: %d\n",
1402  (int) (end - begin));
1403  }
1404  extended::Transition* prior = nullptr;
1405  for (extended::Transition** iter = begin; iter != end; ++iter) {
1406  extended::Transition* transition = *iter;
1407  processActiveTransition(match, transition, &prior);
1408  }
1409 
1410  // If the latest prior transition is found, shift it to start at the
1411  // startDateTime of the current match.
1412  if (prior) {
1413  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1414  logging::printf(
1415  "selectActiveTransitions(): found latest prior\n");
1416  }
1417  prior->originalTransitionTime = prior->transitionTime;
1418  prior->transitionTime = match->startDateTime;
1419  }
1420  }
1421 
1429  static void processActiveTransition(
1430  const extended::ZoneMatch* match,
1431  extended::Transition* transition,
1432  extended::Transition** prior) {
1433  int8_t status = compareTransitionToMatch(transition, match);
1434  if (status == 2) {
1435  transition->active = false;
1436  } else if (status == 1) {
1437  transition->active = true;
1438  } else if (status == 0) {
1439  if (*prior) {
1440  (*prior)->active = false;
1441  }
1442  transition->active = true;
1443  (*prior) = transition;
1444  } else { // (status < 0)
1445  if (*prior) {
1446  if ((*prior)->transitionTime < transition->transitionTime) {
1447  (*prior)->active = false;
1448  transition->active = true;
1449  (*prior) = transition;
1450  }
1451  } else {
1452  transition->active = true;
1453  (*prior) = transition;
1454  }
1455  }
1456  }
1457 
1472  static int8_t compareTransitionToMatch(
1473  const extended::Transition* transition,
1474  const extended::ZoneMatch* match) {
1475  const extended::DateTuple* transitionTime;
1476 
1477  const extended::DateTuple& matchStart = match->startDateTime;
1478  if (matchStart.modifier == extended::ZoneContext::TIME_MODIFIER_S) {
1479  transitionTime = &transition->transitionTimeS;
1480  } else if (matchStart.modifier ==
1482  transitionTime = &transition->transitionTimeU;
1483  } else { // assume 'w'
1484  transitionTime = &transition->transitionTime;
1485  }
1486  if (*transitionTime < matchStart) return -1;
1487  if (*transitionTime == matchStart) return 0;
1488 
1489  const extended::DateTuple& matchUntil = match->untilDateTime;
1490  if (matchUntil.modifier == extended::ZoneContext::TIME_MODIFIER_S) {
1491  transitionTime = &transition->transitionTimeS;
1492  } else if (matchUntil.modifier ==
1494  transitionTime = &transition->transitionTimeU;
1495  } else { // assume 'w'
1496  transitionTime = &transition->transitionTime;
1497  }
1498  if (*transitionTime < matchUntil) return 1;
1499  return 2;
1500  }
1501 
1507  static void generateStartUntilTimes(
1508  extended::Transition** begin, extended::Transition** end) {
1509  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1510  logging::printf(
1511  "generateStartUntilTimes(): #transitions: %d;\n",
1512  (int) (end - begin));
1513  }
1514 
1515  extended::Transition* prev = *begin;
1516  bool isAfterFirst = false;
1517 
1518  for (extended::Transition** iter = begin; iter != end; ++iter) {
1519  extended::Transition* const t = *iter;
1520 
1521  // 1) Update the untilDateTime of the previous Transition
1522  const extended::DateTuple& tt = t->transitionTime;
1523  if (isAfterFirst) {
1524  prev->untilDateTime = tt;
1525  }
1526 
1527  // 2) Calculate the current startDateTime by shifting the
1528  // transitionTime (represented in the UTC offset of the previous
1529  // transition) into the UTC offset of the *current* transition.
1530  int16_t minutes = tt.minutes + 15 * (
1531  - prev->offsetCode - prev->deltaCode
1532  + t->offsetCode + t->deltaCode);
1533  t->startDateTime = {tt.yearTiny, tt.month, tt.day, minutes,
1534  tt.modifier};
1535  normalizeDateTuple(&t->startDateTime);
1536 
1537  // 3) The epochSecond of the 'transitionTime' is determined by the
1538  // UTC offset of the *previous* Transition. However, the
1539  // transitionTime can be represented by an illegal time (e.g. 24:00).
1540  // So, it is better to use the properly normalized startDateTime
1541  // (calculated above) with the *current* UTC offset.
1542  //
1543  // NOTE: We should also be able to calculate this directly from
1544  // 'transitionTimeU' which should still be a valid field, because it
1545  // hasn't been clobbered by 'untilDateTime' yet. Not sure if this saves
1546  // any CPU time though, since we still need to mutiply by 900.
1547  const extended::DateTuple& st = t->startDateTime;
1548  const acetime_t offsetSeconds = (acetime_t) 60
1549  * (st.minutes - 15 * (t->offsetCode + t->deltaCode));
1551  st.yearTiny, st.month, st.day);
1552  t->startEpochSeconds = ld.toEpochSeconds() + offsetSeconds;
1553 
1554  prev = t;
1555  isAfterFirst = true;
1556  }
1557 
1558  // The last Transition's until time is the until time of the ZoneMatch.
1559  extended::DateTuple untilTime = prev->match->untilDateTime;
1560  extended::DateTuple untilTimeS; // needed only for expandDateTuple
1561  extended::DateTuple untilTimeU; // needed only for expandDateTuple
1562  expandDateTuple(&untilTime, &untilTimeS, &untilTimeU,
1563  prev->offsetCode, prev->deltaCode);
1564  prev->untilDateTime = untilTime;
1565  }
1566 
1570  static void calcAbbreviations(
1571  extended::Transition** begin, extended::Transition** end) {
1572  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1573  logging::printf("calcAbbreviations(): #transitions: %d;\n",
1574  (int) (end - begin));
1575  }
1576  for (extended::Transition** iter = begin; iter != end; ++iter) {
1577  extended::Transition* const t = *iter;
1578  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1579  logging::printf(
1580  "calcAbbreviations(): format:%s, deltaCode:%d, letter:%s\n",
1581  t->format(), t->deltaCode, t->letter());
1582  }
1583  createAbbreviation(t->abbrev, extended::Transition::kAbbrevSize,
1584  t->format(), t->deltaCode, t->letter());
1585  }
1586  }
1587 
1622  static void createAbbreviation(char* dest, uint8_t destSize,
1623  const char* format, uint8_t deltaCode, const char* letterString) {
1624  // Check if RULES column is empty. Ignore the deltaCode because if
1625  // letterString is nullptr, we can only just copy the whole thing.
1626  if (letterString == nullptr) {
1627  strncpy(dest, format, destSize);
1628  dest[destSize - 1] = '\0';
1629  return;
1630  }
1631 
1632  // Check if FORMAT contains a '%'.
1633  if (strchr(format, '%') != nullptr) {
1634  copyAndReplace(dest, destSize, format, '%', letterString);
1635  } else {
1636  // Check if FORMAT contains a '/'.
1637  const char* slashPos = strchr(format, '/');
1638  if (slashPos != nullptr) {
1639  if (deltaCode == 0) {
1640  uint8_t headLength = (slashPos - format);
1641  if (headLength >= destSize) headLength = destSize - 1;
1642  memcpy(dest, format, headLength);
1643  dest[headLength] = '\0';
1644  } else {
1645  uint8_t tailLength = strlen(slashPos+1);
1646  if (tailLength >= destSize) tailLength = destSize - 1;
1647  memcpy(dest, slashPos+1, tailLength);
1648  dest[tailLength] = '\0';
1649  }
1650  } else {
1651  // Just copy the FORMAT disregarding deltaCode and letterString.
1652  strncpy(dest, format, destSize);
1653  dest[destSize - 1] = '\0';
1654  }
1655  }
1656  }
1657 
1663  static void copyAndReplace(char* dst, uint8_t dstSize, const char* src,
1664  char oldChar, const char* newString) {
1665  while (*src != '\0' && dstSize > 0) {
1666  if (*src == oldChar) {
1667  while (*newString != '\0' && dstSize > 0) {
1668  *dst++ = *newString++;
1669  dstSize--;
1670  }
1671  src++;
1672  } else {
1673  *dst++ = *src++;
1674  dstSize--;
1675  }
1676  }
1677 
1678  if (dstSize == 0) {
1679  --dst;
1680  }
1681  *dst = '\0';
1682  }
1683 
1684  extended::ZoneInfoBroker mZoneInfo;
1685 
1686  mutable int16_t mYear = 0; // maybe create LocalDate::kInvalidYear?
1687  mutable bool mIsFilled = false;
1688  // NOTE: Maybe move mNumMatches and mMatches into a MatchStorage object.
1689  mutable uint8_t mNumMatches = 0; // actual number of matches
1690  mutable extended::ZoneMatch mMatches[kMaxMatches];
1691  mutable extended::TransitionStorage<kMaxTransitions> mTransitionStorage;
1692 };
1693 
1694 } // namespace ace_time
1695 
1696 #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:113
Base interface for ZoneProcessor classes.
Definition: ZoneProcessor.h:45
ZoneEraBroker era
The ZoneEra that matched the given year.
A heap manager which is specialized and tuned to manage a collection of Transitions, keeping track of unused, used, and active states, using a fixed array of Transitions.
uint8_t minute() const
Return the minute.
void addActiveCandidatesToActivePool()
Add active candidates into the Active pool, and collapse the Candidate pool.
DateTuple transitionTimeU
Version of transitionTime in &#39;u&#39; mode, using the UTC offset of the previous transition.
void resetCandidatePool()
Empty the Candidate pool by resetting the various indexes.
char letterBuf[2]
Storage for the single letter &#39;letter&#39; field if &#39;rule&#39; is not null.
int8_t yearTiny() const
Return the single-byte year offset from year 2000.
Definition: LocalDate.h:229
void printTo(Print &printer) const
Print LocalDateTime to &#39;printer&#39; in ISO 8601 format.
DateTuple startDateTime
The effective start time of the matching ZoneEra.
Transition * getFreeAgent()
Return a pointer to the first Transition in the free pool.
A cache of ZoneProcessors that provides a ZoneProcessor to the TimeZone upon request.
static const uint8_t kAbbrevSize
Longest abbreviation currently seems to be 5 characters (https://www.timeanddate.com/time/zones/) but...
const ZoneMatch * match
The match which generated this Transition.
uint8_t getHighWater() const
Return the high water mark.
bool isError() const
Return true if this TimeOffset represents an error.
Definition: TimeOffset.h:159
Data broker for accessing ZonePolicy in PROGMEM.
Definition: Brokers.h:322
static OffsetDateTime forLocalDateTimeAndOffset(const LocalDateTime &localDateTime, TimeOffset timeOffset)
Factory method from LocalDateTime and TimeOffset.
DateTuple originalTransitionTime
If the transition is shifted to the beginning of a ZoneMatch, this is set to the transitionTime for d...
uint8_t day() const
Return the day of the month.
int8_t yearTiny() const
Return the single-byte year offset from year 2000.
The result of calcStartDayOfMonth().
virtual const void * getZoneInfo() const =0
Return the opaque zoneInfo.
Represents an interval of time where the time zone obeyed a certain UTC offset and DST delta...
void log() const
Verify that the indexes are valid.
const void * getZoneInfo() const override
Return the underlying ZoneInfo.
void addPriorToCandidatePool()
Add the current prior into the Candidates pool.
const char * getAbbrev(acetime_t epochSeconds) const override
Return the time zone abbreviation at epochSeconds.
Representation of a given time zone, implemented as an array of ZoneEra records.
Definition: ZoneInfo.h:86
void setFreeAgentAsPrior()
Swap the Free agrent transition with the current Prior transition.
static const uint8_t TIME_MODIFIER_W
Represents &#39;w&#39; or wall time.
Definition: ZoneContext.h:13
Transition * getPrior()
Return the current prior transition.
static LocalDate forTinyComponents(int8_t yearTiny, uint8_t month, uint8_t day)
Factory method using components with an int8_t yearTiny.
Definition: LocalDate.h:101
TimeOffset getUtcOffset(acetime_t epochSeconds) const override
Return the total UTC offset at epochSeconds, including DST offset.
static const uint8_t TIME_MODIFIER_S
Represents &#39;s&#39; or standard time.
Definition: ZoneContext.h:16
char abbrev[kAbbrevSize]
The calculated effective time zone abbreviation, e.g.
const char * letter() const
Return the letter string.
An entry in ZoneInfo which describes which ZonePolicy was being followed during a particular time per...
Definition: ZoneInfo.h:20
uint8_t day() const
Return the day of the month.
Definition: LocalDate.h:245
uint8_t month() const
Return the month with January=1, December=12.
static OffsetDateTime forEpochSeconds(acetime_t epochSeconds, TimeOffset timeOffset)
Factory method.
void addFreeAgentToCandidatePool()
Add the free agent Transition at index mIndexFree to the Candidate pool, sorted by transitionTime...
DateTuple transitionTime
The original transition time, usually &#39;w&#39; but sometimes &#39;s&#39; or &#39;u&#39;.
acetime_t toEpochSeconds() const
Return the number of seconds since AceTime epoch (2000-01-01 00:00:00).
Definition: LocalDate.h:328
const LocalDate & localDate() const
Return the LocalDate.
static const uint8_t kAbbrevSize
Size of the timezone abbreviation.
An implementation of ZoneProcessor that supports for all zones defined by the TZ Database.
static const uint8_t TIME_MODIFIER_U
Represents &#39;u&#39; or UTC time.
Definition: ZoneContext.h:19
const Transition * findTransitionForDateTime(const LocalDateTime &ldt) const
Return the Transition matching the given dateTime.
uint8_t hour() const
Return the hour.
TimeOffset getDeltaOffset(acetime_t epochSeconds) const override
Return the DST delta offset at epochSeconds.
DateTuple transitionTimeS
Version of transitionTime in &#39;s&#39; mode, using the UTC offset of the previous Transition.
acetime_t startEpochSeconds
The calculated transition time of the given rule.
A simple tuple to represent a year/month pair.
static LocalDate forEpochSeconds(acetime_t epochSeconds)
Factory method using the number of seconds since AceTime epoch of 2000-01-01.
Definition: LocalDate.h:145
static TimeOffset forOffsetCode(int8_t offsetCode)
Create TimeOffset from the offset code.
Definition: TimeOffset.h:120
void resetTransitionHighWater()
Reset the TransitionStorage high water mark.
bool active
Flag used for 2 slightly different meanings at different stages of init() processing.
uint32_t getZoneId() const override
Return the unique stable zoneId.
The date (year, month, day), time (hour, minute, second) and offset from UTC (timeOffset).
void log() const
Used only for debugging.
Transition ** reservePrior()
Allocate one Transition just after the Active pool, but before the Candidate pool, to keep the most recent prior Transition.
static const int8_t kInvalidYearTiny
Sentinel yearTiny which indicates an error condition or sometimes a year that &#39;does not exist&#39;...
Definition: LocalDate.h:45
void resetHighWater()
Reset the high water mark.
Macros and definitions that provide a consistency layer among the various Arduino boards for compatib...
void log() const
Used only for debugging.
A thin wrapper that represents a time offset from a reference point, usually 00:00 at UTC...
Definition: TimeOffset.h:61
int8_t deltaCode
The DST delta code.
void swap(Transition **a, Transition **b)
Swap 2 transitions.
The date (year, month, day) representing the date without regards to time zone.
Definition: LocalDate.h:36
DateTuple untilDateTime
Until time expressed using the UTC offset of the current Transition.
void log() const
Used only for debugging.
ExtendedZoneProcessor(const extended::ZoneInfo *zoneInfo=nullptr)
Constructor.
uint8_t getTransitionHighWater() const
Get the TransitionStorage high water mark.
static const int16_t kEpochYear
Base year of epoch.
Definition: LocalDate.h:39
DateTuple untilDateTime
The effective until time of the matching ZoneEra.
DateTuple startDateTime
Start time expressed using the UTC offset of the current Transition.
uint8_t month() const
Return the month with January=1, December=12.
Definition: LocalDate.h:239
int16_t year() const
Return the full year instead of just the last 2 digits.
Definition: LocalDate.h:219
static basic::MonthDay calcStartDayOfMonth(int16_t year, uint8_t month, uint8_t onDayOfWeek, int8_t onDayOfMonth)
Calculate the actual (month, day) of the expresssion (onDayOfWeek >= onDayOfMonth) or (onDayOfWeek <=...
A tuple that represents a date and time.
const Transition * findTransition(acetime_t epochSeconds) const
Return the Transition matching the given epochSeconds.
OffsetDateTime getOffsetDateTime(const LocalDateTime &ldt) const override
Return the best estimate of the OffsetDateTime at the given LocalDateTime for the timezone of the cur...
int8_t offsetCode
The base offset code, not the total effective UTC offset.
ZoneRuleBroker rule
The Zone transition rule that matched for the the given year.
Data structure that captures the matching ZoneEra and its ZoneRule transitions for a given year...
Class that holds the date-time as the components (year, month, day, hour, minute, second) without reg...
Definition: LocalDateTime.h:27