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), suffix(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 suffix; // TIME_SUFFIX_S, TIME_SUFFIX_W, TIME_SUFFIX_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"[(suffix>>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.suffix == b.suffix;
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 
854  void setZoneInfo(const void* zoneInfo) override {
855  if (mZoneInfo.zoneInfo() == zoneInfo) return;
856 
857  mZoneInfo = extended::ZoneInfoBroker(
858  (const extended::ZoneInfo*) zoneInfo);
859  mYear = 0;
860  mIsFilled = false;
861  mNumMatches = 0;
862  }
863 
868  const extended::Transition* findTransition(acetime_t epochSeconds) const {
869  return mTransitionStorage.findTransition(epochSeconds);
870  }
871 
873  bool init(acetime_t epochSeconds) const {
874  LocalDate ld = LocalDate::forEpochSeconds(epochSeconds);
875  return init(ld);
876  }
877 
882  bool init(const LocalDate& ld) const {
883  int16_t year = ld.year();
884  if (isFilled(year)) return true;
885  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
886  logging::printf("init(): %d\n", year);
887  }
888 
889  mYear = year;
890  mNumMatches = 0; // clear cache
891  mTransitionStorage.init();
892 
893  if (year < mZoneInfo.startYear() - 1 || mZoneInfo.untilYear() < year) {
894  return false;
895  }
896 
897  extended::YearMonthTuple startYm = {
898  (int8_t) (year - LocalDate::kEpochYear - 1), 12 };
899  extended::YearMonthTuple untilYm = {
900  (int8_t) (year - LocalDate::kEpochYear + 1), 2 };
901 
902  mNumMatches = findMatches(mZoneInfo, startYm, untilYm, mMatches,
903  kMaxMatches);
904  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
905  findTransitions(mTransitionStorage, mMatches, mNumMatches);
906  extended::Transition** begin = mTransitionStorage.getActivePoolBegin();
907  extended::Transition** end = mTransitionStorage.getActivePoolEnd();
908  fixTransitionTimes(begin, end);
909  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
910  generateStartUntilTimes(begin, end);
911  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
912  calcAbbreviations(begin, end);
913  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
914 
915  mIsFilled = true;
916  return true;
917  }
918 
920  bool isFilled(int16_t year) const {
921  return mIsFilled && (year == mYear);
922  }
923 
931  static uint8_t findMatches(const extended::ZoneInfoBroker zoneInfo,
932  const extended::YearMonthTuple& startYm,
933  const extended::YearMonthTuple& untilYm,
934  extended::ZoneMatch* matches, uint8_t maxMatches) {
935  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
936  logging::printf("findMatches()\n");
937  }
938  uint8_t iMatch = 0;
940  for (uint8_t iEra = 0; iEra < zoneInfo.numEras(); iEra++) {
941  const extended::ZoneEraBroker era = zoneInfo.era(iEra);
942  if (eraOverlapsInterval(prev, era, startYm, untilYm)) {
943  if (iMatch < maxMatches) {
944  matches[iMatch] = createMatch(prev, era, startYm, untilYm);
945  iMatch++;
946  }
947  }
948  prev = era;
949  }
950  return iMatch;
951  }
952 
963  static bool eraOverlapsInterval(
964  const extended::ZoneEraBroker prev,
965  const extended::ZoneEraBroker era,
966  const extended::YearMonthTuple& startYm,
967  const extended::YearMonthTuple& untilYm) {
968  return compareEraToYearMonth(prev, untilYm.yearTiny, untilYm.month) < 0
969  && compareEraToYearMonth(era, startYm.yearTiny, startYm.month) > 0;
970  }
971 
973  static int8_t compareEraToYearMonth(const extended::ZoneEraBroker era,
974  int8_t yearTiny, uint8_t month) {
975  if (era.untilYearTiny() < yearTiny) return -1;
976  if (era.untilYearTiny() > yearTiny) return 1;
977  if (era.untilMonth() < month) return -1;
978  if (era.untilMonth() > month) return 1;
979  if (era.untilDay() > 1) return 1;
980  //if (era.untilTimeMinutes() < 0) return -1; // never possible
981  if (era.untilTimeMinutes() > 0) return 1;
982  return 0;
983  }
984 
991  static extended::ZoneMatch createMatch(
992  const extended::ZoneEraBroker prev,
993  const extended::ZoneEraBroker era,
994  const extended::YearMonthTuple& startYm,
995  const extended::YearMonthTuple& untilYm) {
996  extended::DateTuple startDate = {
997  prev.untilYearTiny(), prev.untilMonth(), prev.untilDay(),
998  (int16_t) prev.untilTimeMinutes(), prev.untilTimeSuffix()
999  };
1000  extended::DateTuple lowerBound = {
1001  startYm.yearTiny, startYm.month, 1, 0,
1003  };
1004  if (startDate < lowerBound) {
1005  startDate = lowerBound;
1006  }
1007 
1008  extended::DateTuple untilDate = {
1009  era.untilYearTiny(), era.untilMonth(), era.untilDay(),
1010  (int16_t) era.untilTimeMinutes(), era.untilTimeSuffix()
1011  };
1012  extended::DateTuple upperBound = {
1013  untilYm.yearTiny, untilYm.month, 1, 0,
1015  };
1016  if (upperBound < untilDate) {
1017  untilDate = upperBound;
1018  }
1019 
1020  return {startDate, untilDate, era};
1021  }
1022 
1027  static void findTransitions(
1029  extended::ZoneMatch* matches,
1030  uint8_t numMatches) {
1031  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1032  logging::printf("findTransitions()\n");
1033  }
1034  for (uint8_t i = 0; i < numMatches; i++) {
1035  findTransitionsForMatch(transitionStorage, &matches[i]);
1036  }
1037  }
1038 
1040  static void findTransitionsForMatch(
1042  const extended::ZoneMatch* match) {
1043  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1044  logging::printf("findTransitionsForMatch()\n");
1045  }
1046  const extended::ZonePolicyBroker policy = match->era.zonePolicy();
1047  if (policy.isNull()) {
1048  findTransitionsFromSimpleMatch(transitionStorage, match);
1049  } else {
1050  findTransitionsFromNamedMatch(transitionStorage, match);
1051  }
1052  }
1053 
1054  static void findTransitionsFromSimpleMatch(
1056  const extended::ZoneMatch* match) {
1057  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1058  logging::printf("findTransitionsFromSimpleMatch()\n");
1059  }
1060  extended::Transition* freeTransition = transitionStorage.getFreeAgent();
1061  createTransitionForYear(freeTransition, 0 /*not used*/,
1062  extended::ZoneRuleBroker(nullptr) /*rule*/, match);
1063  transitionStorage.addFreeAgentToActivePool();
1064  }
1065 
1066  static void findTransitionsFromNamedMatch(
1068  const extended::ZoneMatch* match) {
1069  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1070  logging::printf("findTransitionsFromNamedMatch()\n");
1071  }
1072  transitionStorage.resetCandidatePool();
1073  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1074  match->log(); logging::printf("\n");
1075  }
1076  findCandidateTransitions(transitionStorage, match);
1077  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1078  transitionStorage.log(); logging::printf("\n");
1079  }
1080  fixTransitionTimes(
1081  transitionStorage.getCandidatePoolBegin(),
1082  transitionStorage.getCandidatePoolEnd());
1083  selectActiveTransitions(transitionStorage, match);
1084  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1085  transitionStorage.log(); logging::printf("\n");
1086  }
1087 
1088  transitionStorage.addActiveCandidatesToActivePool();
1089  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1090  transitionStorage.log(); logging::printf("\n");
1091  }
1092  }
1093 
1094  static void findCandidateTransitions(
1096  const extended::ZoneMatch* match) {
1097  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1098  logging::printf("findCandidateTransitions(): ");
1099  match->log();
1100  logging::printf("\n");
1101  }
1102  const extended::ZonePolicyBroker policy = match->era.zonePolicy();
1103  uint8_t numRules = policy.numRules();
1104  int8_t startY = match->startDateTime.yearTiny;
1105  int8_t endY = match->untilDateTime.yearTiny;
1106 
1107  extended::Transition** prior = transitionStorage.reservePrior();
1108  (*prior)->active = false; // indicates "no prior transition"
1109  for (uint8_t r = 0; r < numRules; r++) {
1110  const extended::ZoneRuleBroker rule = policy.rule(r);
1111 
1112  // Add Transitions for interior years
1113  int8_t interiorYears[kMaxInteriorYears];
1114  uint8_t numYears = calcInteriorYears(interiorYears, kMaxInteriorYears,
1115  rule.fromYearTiny(), rule.toYearTiny(), startY, endY);
1116  for (uint8_t y = 0; y < numYears; y++) {
1117  int8_t year = interiorYears[y];
1118  extended::Transition* t = transitionStorage.getFreeAgent();
1119  createTransitionForYear(t, year, rule, match);
1120  int8_t status = compareTransitionToMatchFuzzy(t, match);
1121  if (status < 0) {
1122  setAsPriorTransition(transitionStorage, t);
1123  } else if (status == 1) {
1124  transitionStorage.addFreeAgentToCandidatePool();
1125  }
1126  }
1127 
1128  // Add Transition for prior year
1129  int8_t priorYear = getMostRecentPriorYear(
1130  rule.fromYearTiny(), rule.toYearTiny(), startY, endY);
1131  if (priorYear != LocalDate::kInvalidYearTiny) {
1132  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1133  logging::printf(
1134  "findCandidateTransitions(): priorYear: %d\n", priorYear);
1135  }
1136  extended::Transition* t = transitionStorage.getFreeAgent();
1137  createTransitionForYear(t, priorYear, rule, match);
1138  setAsPriorTransition(transitionStorage, t);
1139  }
1140  }
1141 
1142  // Add the reserved prior into the Candidate pool only if 'active' is
1143  // true, meaning that a prior was found.
1144  if ((*prior)->active) {
1145  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1146  logging::printf(
1147  "findCandidateTransitions(): adding prior to Candidate pool\n");
1148  }
1149  transitionStorage.addPriorToCandidatePool();
1150  }
1151  }
1152 
1157  static uint8_t calcInteriorYears(int8_t* interiorYears,
1158  uint8_t maxInteriorYears, int8_t fromYear, int8_t toYear,
1159  int8_t startYear, int8_t endYear) {
1160  uint8_t i = 0;
1161  for (int8_t year = startYear; year <= endYear; year++) {
1162  if (fromYear <= year && year <= toYear) {
1163  interiorYears[i] = year;
1164  i++;
1165  if (i >= maxInteriorYears) break;
1166  }
1167  }
1168  return i;
1169  }
1170 
1177  static void createTransitionForYear(extended::Transition* t, int8_t year,
1178  const extended::ZoneRuleBroker rule,
1179  const extended::ZoneMatch* match) {
1180  t->match = match;
1181  t->rule = rule;
1182  t->offsetCode = match->era.offsetCode();
1183  t->letterBuf[0] = '\0';
1184 
1185  if (rule.isNotNull()) {
1186  t->transitionTime = getTransitionTime(year, rule);
1187  t->deltaCode = rule.deltaCode();
1188 
1189  char letter = rule.letter();
1190  if (letter >= 32) {
1191  // If LETTER is a '-', treat it the same as an empty string.
1192  if (letter != '-') {
1193  t->letterBuf[0] = letter;
1194  t->letterBuf[1] = '\0';
1195  }
1196  } else {
1197  // rule->letter is a long string, so is referenced as an offset index
1198  // into the ZonePolicy.letters array. The string cannot fit in
1199  // letterBuf, so will be retrieved by the letter() method below.
1200  }
1201  } else {
1202  t->transitionTime = match->startDateTime;
1203  t->deltaCode = match->era.deltaCode();
1204  }
1205  }
1206 
1212  static int8_t getMostRecentPriorYear(int8_t fromYear, int8_t toYear,
1213  int8_t startYear, int8_t /*endYear*/) {
1214  if (fromYear < startYear) {
1215  if (toYear < startYear) {
1216  return toYear;
1217  } else {
1218  return startYear - 1;
1219  }
1220  } else {
1222  }
1223  }
1224 
1225  static extended::DateTuple getTransitionTime(
1226  int8_t yearTiny, const extended::ZoneRuleBroker rule) {
1228  yearTiny + LocalDate::kEpochYear, rule.inMonth(), rule.onDayOfWeek(),
1229  rule.onDayOfMonth());
1230  return {yearTiny, monthDay.month, monthDay.day,
1231  (int16_t) rule.atTimeMinutes(), rule.atTimeSuffix()};
1232  }
1233 
1244  static int8_t compareTransitionToMatchFuzzy(
1245  const extended::Transition* t, const extended::ZoneMatch* match) {
1246  int16_t ttMonths = t->transitionTime.yearTiny * 12
1247  + t->transitionTime.month;
1248 
1249  int16_t matchStartMonths = match->startDateTime.yearTiny * 12
1250  + match->startDateTime.month;
1251  if (ttMonths < matchStartMonths - 1) return -1;
1252 
1253  int16_t matchUntilMonths = match->untilDateTime.yearTiny * 12
1254  + match->untilDateTime.month;
1255  if (matchUntilMonths + 2 <= ttMonths) return 2;
1256 
1257  return 1;
1258  }
1259 
1261  static void setAsPriorTransition(
1263  extended::Transition* t) {
1264  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1265  logging::printf("setAsPriorTransition()\n");
1266  }
1267  extended::Transition* prior = transitionStorage.getPrior();
1268  if (prior->active) {
1269  if (prior->transitionTime < t->transitionTime) {
1270  t->active = true;
1271  transitionStorage.setFreeAgentAsPrior();
1272  }
1273  } else {
1274  t->active = true;
1275  transitionStorage.setFreeAgentAsPrior();
1276  }
1277  }
1278 
1287  static void fixTransitionTimes(
1288  extended::Transition** begin, extended::Transition** end) {
1289  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1290  logging::printf("fixTransitionTimes(): #transitions: %d;\n",
1291  (int) (end - begin));
1292  }
1293 
1294  // extend first Transition to -infinity
1295  extended::Transition* prev = *begin;
1296 
1297  for (extended::Transition** iter = begin; iter != end; ++iter) {
1298  extended::Transition* curr = *iter;
1299  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1300  logging::printf("fixTransitionTimes(): LOOP\n");
1301  curr->log();
1302  logging::printf("\n");
1303  }
1304  expandDateTuple(&curr->transitionTime,
1305  &curr->transitionTimeS, &curr->transitionTimeU,
1306  prev->offsetCode, prev->deltaCode);
1307  prev = curr;
1308  }
1309  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1310  logging::printf("fixTransitionTimes(): END\n");
1311  }
1312  }
1313 
1319  static void expandDateTuple(extended::DateTuple* tt,
1321  int8_t offsetCode, int8_t deltaCode) {
1322  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1323  logging::printf("expandDateTuple()\n");
1324  }
1325  if (tt->suffix == extended::ZoneContext::TIME_SUFFIX_S) {
1326  *tts = *tt;
1327  *ttu = {tt->yearTiny, tt->month, tt->day,
1328  (int16_t) (tt->minutes - 15 * offsetCode),
1330  *tt = {tt->yearTiny, tt->month, tt->day,
1331  (int16_t) (tt->minutes + 15 * deltaCode),
1333  } else if (tt->suffix == extended::ZoneContext::TIME_SUFFIX_U) {
1334  *ttu = *tt;
1335  *tts = {tt->yearTiny, tt->month, tt->day,
1336  (int16_t) (tt->minutes + 15 * offsetCode),
1338  *tt = {tt->yearTiny, tt->month, tt->day,
1339  (int16_t) (tt->minutes + 15 * (offsetCode + deltaCode)),
1341  } else {
1342  // Explicit set the suffix to 'w' in case it was something else.
1344  *tts = {tt->yearTiny, tt->month, tt->day,
1345  (int16_t) (tt->minutes - 15 * deltaCode),
1347  *ttu = {tt->yearTiny, tt->month, tt->day,
1348  (int16_t) (tt->minutes - 15 * (deltaCode + offsetCode)),
1350  }
1351 
1352  normalizeDateTuple(tt);
1353  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1354  logging::printf("expandDateTuple(): normalizeDateTuple(tt): ");
1355  tt->log();
1356  logging::printf("\n");
1357  }
1358 
1359  normalizeDateTuple(tts);
1360  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1361  logging::printf("expandDateTuple(): normalizeDateTuple(tts): ");
1362  tts->log();
1363  logging::printf("\n");
1364  }
1365 
1366  normalizeDateTuple(ttu);
1367  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1368  logging::printf("expandDateTuple(): normalizeDateTuple(ttu): ");
1369  ttu->log();
1370  logging::printf("\n");
1371  }
1372  }
1373 
1378  static void normalizeDateTuple(extended::DateTuple* dt) {
1379  const int16_t kOneDayAsMinutes = 60 * 24;
1380  if (dt->minutes <= -kOneDayAsMinutes) {
1382  dt->yearTiny, dt->month, dt->day);
1383  local_date_mutation::decrementOneDay(ld);
1384  dt->yearTiny = ld.yearTiny();
1385  dt->month = ld.month();
1386  dt->day = ld.day();
1387  dt->minutes += kOneDayAsMinutes;
1388  } else if (kOneDayAsMinutes <= dt->minutes) {
1390  dt->yearTiny, dt->month, dt->day);
1391  local_date_mutation::incrementOneDay(ld);
1392  dt->yearTiny = ld.yearTiny();
1393  dt->month = ld.month();
1394  dt->day = ld.day();
1395  dt->minutes -= kOneDayAsMinutes;
1396  } else {
1397  // do nothing
1398  }
1399  }
1400 
1405  static void selectActiveTransitions(
1407  const extended::ZoneMatch* match) {
1408  extended::Transition** begin = transitionStorage.getCandidatePoolBegin();
1409  extended::Transition** end = transitionStorage.getCandidatePoolEnd();
1410  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1411  logging::printf("selectActiveTransitions(): #candidates: %d\n",
1412  (int) (end - begin));
1413  }
1414  extended::Transition* prior = nullptr;
1415  for (extended::Transition** iter = begin; iter != end; ++iter) {
1416  extended::Transition* transition = *iter;
1417  processActiveTransition(match, transition, &prior);
1418  }
1419 
1420  // If the latest prior transition is found, shift it to start at the
1421  // startDateTime of the current match.
1422  if (prior) {
1423  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1424  logging::printf(
1425  "selectActiveTransitions(): found latest prior\n");
1426  }
1427  prior->originalTransitionTime = prior->transitionTime;
1428  prior->transitionTime = match->startDateTime;
1429  }
1430  }
1431 
1439  static void processActiveTransition(
1440  const extended::ZoneMatch* match,
1441  extended::Transition* transition,
1442  extended::Transition** prior) {
1443  int8_t status = compareTransitionToMatch(transition, match);
1444  if (status == 2) {
1445  transition->active = false;
1446  } else if (status == 1) {
1447  transition->active = true;
1448  } else if (status == 0) {
1449  if (*prior) {
1450  (*prior)->active = false;
1451  }
1452  transition->active = true;
1453  (*prior) = transition;
1454  } else { // (status < 0)
1455  if (*prior) {
1456  if ((*prior)->transitionTime < transition->transitionTime) {
1457  (*prior)->active = false;
1458  transition->active = true;
1459  (*prior) = transition;
1460  }
1461  } else {
1462  transition->active = true;
1463  (*prior) = transition;
1464  }
1465  }
1466  }
1467 
1482  static int8_t compareTransitionToMatch(
1483  const extended::Transition* transition,
1484  const extended::ZoneMatch* match) {
1485  const extended::DateTuple* transitionTime;
1486 
1487  const extended::DateTuple& matchStart = match->startDateTime;
1488  if (matchStart.suffix == extended::ZoneContext::TIME_SUFFIX_S) {
1489  transitionTime = &transition->transitionTimeS;
1490  } else if (matchStart.suffix ==
1492  transitionTime = &transition->transitionTimeU;
1493  } else { // assume 'w'
1494  transitionTime = &transition->transitionTime;
1495  }
1496  if (*transitionTime < matchStart) return -1;
1497  if (*transitionTime == matchStart) return 0;
1498 
1499  const extended::DateTuple& matchUntil = match->untilDateTime;
1500  if (matchUntil.suffix == extended::ZoneContext::TIME_SUFFIX_S) {
1501  transitionTime = &transition->transitionTimeS;
1502  } else if (matchUntil.suffix ==
1504  transitionTime = &transition->transitionTimeU;
1505  } else { // assume 'w'
1506  transitionTime = &transition->transitionTime;
1507  }
1508  if (*transitionTime < matchUntil) return 1;
1509  return 2;
1510  }
1511 
1517  static void generateStartUntilTimes(
1518  extended::Transition** begin, extended::Transition** end) {
1519  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1520  logging::printf(
1521  "generateStartUntilTimes(): #transitions: %d;\n",
1522  (int) (end - begin));
1523  }
1524 
1525  extended::Transition* prev = *begin;
1526  bool isAfterFirst = false;
1527 
1528  for (extended::Transition** iter = begin; iter != end; ++iter) {
1529  extended::Transition* const t = *iter;
1530 
1531  // 1) Update the untilDateTime of the previous Transition
1532  const extended::DateTuple& tt = t->transitionTime;
1533  if (isAfterFirst) {
1534  prev->untilDateTime = tt;
1535  }
1536 
1537  // 2) Calculate the current startDateTime by shifting the
1538  // transitionTime (represented in the UTC offset of the previous
1539  // transition) into the UTC offset of the *current* transition.
1540  int16_t minutes = tt.minutes + 15 * (
1541  - prev->offsetCode - prev->deltaCode
1542  + t->offsetCode + t->deltaCode);
1543  t->startDateTime = {tt.yearTiny, tt.month, tt.day, minutes,
1544  tt.suffix};
1545  normalizeDateTuple(&t->startDateTime);
1546 
1547  // 3) The epochSecond of the 'transitionTime' is determined by the
1548  // UTC offset of the *previous* Transition. However, the
1549  // transitionTime can be represented by an illegal time (e.g. 24:00).
1550  // So, it is better to use the properly normalized startDateTime
1551  // (calculated above) with the *current* UTC offset.
1552  //
1553  // NOTE: We should also be able to calculate this directly from
1554  // 'transitionTimeU' which should still be a valid field, because it
1555  // hasn't been clobbered by 'untilDateTime' yet. Not sure if this saves
1556  // any CPU time though, since we still need to mutiply by 900.
1557  const extended::DateTuple& st = t->startDateTime;
1558  const acetime_t offsetSeconds = (acetime_t) 60
1559  * (st.minutes - 15 * (t->offsetCode + t->deltaCode));
1561  st.yearTiny, st.month, st.day);
1562  t->startEpochSeconds = ld.toEpochSeconds() + offsetSeconds;
1563 
1564  prev = t;
1565  isAfterFirst = true;
1566  }
1567 
1568  // The last Transition's until time is the until time of the ZoneMatch.
1569  extended::DateTuple untilTime = prev->match->untilDateTime;
1570  extended::DateTuple untilTimeS; // needed only for expandDateTuple
1571  extended::DateTuple untilTimeU; // needed only for expandDateTuple
1572  expandDateTuple(&untilTime, &untilTimeS, &untilTimeU,
1573  prev->offsetCode, prev->deltaCode);
1574  prev->untilDateTime = untilTime;
1575  }
1576 
1580  static void calcAbbreviations(
1581  extended::Transition** begin, extended::Transition** end) {
1582  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1583  logging::printf("calcAbbreviations(): #transitions: %d;\n",
1584  (int) (end - begin));
1585  }
1586  for (extended::Transition** iter = begin; iter != end; ++iter) {
1587  extended::Transition* const t = *iter;
1588  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1589  logging::printf(
1590  "calcAbbreviations(): format:%s, deltaCode:%d, letter:%s\n",
1591  t->format(), t->deltaCode, t->letter());
1592  }
1593  createAbbreviation(t->abbrev, extended::Transition::kAbbrevSize,
1594  t->format(), t->deltaCode, t->letter());
1595  }
1596  }
1597 
1606  static void createAbbreviation(char* dest, uint8_t destSize,
1607  const char* format, uint8_t deltaCode, const char* letterString) {
1608  // Check if FORMAT contains a '%'.
1609  if (strchr(format, '%') != nullptr) {
1610  // Check if RULES column empty, therefore no 'letter'
1611  if (letterString == nullptr) {
1612  strncpy(dest, format, destSize - 1);
1613  dest[destSize - 1] = '\0';
1614  } else {
1615  copyAndReplace(dest, destSize, format, '%', letterString);
1616  }
1617  } else {
1618  // Check if FORMAT contains a '/'.
1619  const char* slashPos = strchr(format, '/');
1620  if (slashPos != nullptr) {
1621  if (deltaCode == 0) {
1622  uint8_t headLength = (slashPos - format);
1623  if (headLength >= destSize) headLength = destSize - 1;
1624  memcpy(dest, format, headLength);
1625  dest[headLength] = '\0';
1626  } else {
1627  uint8_t tailLength = strlen(slashPos+1);
1628  if (tailLength >= destSize) tailLength = destSize - 1;
1629  memcpy(dest, slashPos+1, tailLength);
1630  dest[tailLength] = '\0';
1631  }
1632  } else {
1633  // Just copy the FORMAT disregarding deltaCode and letterString.
1634  strncpy(dest, format, destSize);
1635  dest[destSize - 1] = '\0';
1636  }
1637  }
1638  }
1639 
1645  static void copyAndReplace(char* dst, uint8_t dstSize, const char* src,
1646  char oldChar, const char* newString) {
1647  while (*src != '\0' && dstSize > 0) {
1648  if (*src == oldChar) {
1649  while (*newString != '\0' && dstSize > 0) {
1650  *dst++ = *newString++;
1651  dstSize--;
1652  }
1653  src++;
1654  } else {
1655  *dst++ = *src++;
1656  dstSize--;
1657  }
1658  }
1659 
1660  if (dstSize == 0) {
1661  --dst;
1662  }
1663  *dst = '\0';
1664  }
1665 
1666  extended::ZoneInfoBroker mZoneInfo;
1667 
1668  mutable int16_t mYear = 0; // maybe create LocalDate::kInvalidYear?
1669  mutable bool mIsFilled = false;
1670  // NOTE: Maybe move mNumMatches and mMatches into a MatchStorage object.
1671  mutable uint8_t mNumMatches = 0; // actual number of matches
1672  mutable extended::ZoneMatch mMatches[kMaxMatches];
1673  mutable extended::TransitionStorage<kMaxTransitions> mTransitionStorage;
1674 };
1675 
1676 } // namespace ace_time
1677 
1678 #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.
static const uint8_t TIME_SUFFIX_S
Represents &#39;s&#39; or standard time.
Definition: ZoneContext.h:16
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...
static const uint8_t TIME_SUFFIX_U
Represents &#39;u&#39; or UTC time.
Definition: ZoneContext.h:19
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.
Transition * getPrior()
Return the current prior transition.
static LocalDate forTinyComponents(int8_t yearTiny, uint8_t month, uint8_t day)
Factory method using components with an int8_t yearTiny.
Definition: LocalDate.h:101
TimeOffset getUtcOffset(acetime_t epochSeconds) const override
Return the total UTC offset at epochSeconds, including DST offset.
char abbrev[kAbbrevSize]
The calculated effective time zone abbreviation, e.g.
const char * letter() const
Return the letter string.
An entry in ZoneInfo which describes which ZonePolicy was being followed during a particular time per...
Definition: ZoneInfo.h:20
uint8_t day() const
Return the day of the month.
Definition: LocalDate.h: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.
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...
static const uint8_t TIME_SUFFIX_W
Represents &#39;w&#39; or wall time.
Definition: ZoneContext.h:13
Class that holds the date-time as the components (year, month, day, hour, minute, second) without reg...
Definition: LocalDateTime.h:27