AceTime  0.8
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; // kSuffixS, kSuffixW, kSuffixU
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 
242  int16_t offsetMinutes;
243 
245  int16_t deltaMinutes;
246 
248  char abbrev[kAbbrevSize];
249 
251  char letterBuf[2];
252 
265  bool active;
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("; oMinutes: %d", offsetMinutes);
319  logging::printf("; dMinutes: %d", deltaMinutes);
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->offsetMinutes + transition->deltaMinutes)
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::forMinutes(transition->deltaMinutes);
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->offsetMinutes + transition->deltaMinutes)
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->offsetMinutes + transition->deltaMinutes)
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  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
895  logging::printf("init(): Year %d out of valid range [%d, %d)\n",
896  year, mZoneInfo.startYear(), mZoneInfo.untilYear());
897  }
898  return false;
899  }
900 
901  extended::YearMonthTuple startYm = {
902  (int8_t) (year - LocalDate::kEpochYear - 1), 12 };
903  extended::YearMonthTuple untilYm = {
904  (int8_t) (year - LocalDate::kEpochYear + 1), 2 };
905 
906  mNumMatches = findMatches(mZoneInfo, startYm, untilYm, mMatches,
907  kMaxMatches);
908  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
909  findTransitions(mTransitionStorage, mMatches, mNumMatches);
910  extended::Transition** begin = mTransitionStorage.getActivePoolBegin();
911  extended::Transition** end = mTransitionStorage.getActivePoolEnd();
912  fixTransitionTimes(begin, end);
913  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
914  generateStartUntilTimes(begin, end);
915  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
916  calcAbbreviations(begin, end);
917  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
918 
919  mIsFilled = true;
920  return true;
921  }
922 
924  bool isFilled(int16_t year) const {
925  return mIsFilled && (year == mYear);
926  }
927 
935  static uint8_t findMatches(const extended::ZoneInfoBroker zoneInfo,
936  const extended::YearMonthTuple& startYm,
937  const extended::YearMonthTuple& untilYm,
938  extended::ZoneMatch* matches, uint8_t maxMatches) {
939  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
940  logging::printf("findMatches()\n");
941  }
942  uint8_t iMatch = 0;
944  for (uint8_t iEra = 0; iEra < zoneInfo.numEras(); iEra++) {
945  const extended::ZoneEraBroker era = zoneInfo.era(iEra);
946  if (eraOverlapsInterval(prev, era, startYm, untilYm)) {
947  if (iMatch < maxMatches) {
948  matches[iMatch] = createMatch(prev, era, startYm, untilYm);
949  iMatch++;
950  }
951  }
952  prev = era;
953  }
954  return iMatch;
955  }
956 
967  static bool eraOverlapsInterval(
968  const extended::ZoneEraBroker prev,
969  const extended::ZoneEraBroker era,
970  const extended::YearMonthTuple& startYm,
971  const extended::YearMonthTuple& untilYm) {
972  return compareEraToYearMonth(prev, untilYm.yearTiny, untilYm.month) < 0
973  && compareEraToYearMonth(era, startYm.yearTiny, startYm.month) > 0;
974  }
975 
977  static int8_t compareEraToYearMonth(const extended::ZoneEraBroker era,
978  int8_t yearTiny, uint8_t month) {
979  if (era.untilYearTiny() < yearTiny) return -1;
980  if (era.untilYearTiny() > yearTiny) return 1;
981  if (era.untilMonth() < month) return -1;
982  if (era.untilMonth() > month) return 1;
983  if (era.untilDay() > 1) return 1;
984  //if (era.untilTimeMinutes() < 0) return -1; // never possible
985  if (era.untilTimeMinutes() > 0) return 1;
986  return 0;
987  }
988 
995  static extended::ZoneMatch createMatch(
996  const extended::ZoneEraBroker prev,
997  const extended::ZoneEraBroker era,
998  const extended::YearMonthTuple& startYm,
999  const extended::YearMonthTuple& untilYm) {
1000  extended::DateTuple startDate = {
1001  prev.untilYearTiny(), prev.untilMonth(), prev.untilDay(),
1002  (int16_t) prev.untilTimeMinutes(), prev.untilTimeSuffix()
1003  };
1004  extended::DateTuple lowerBound = {
1005  startYm.yearTiny, startYm.month, 1, 0,
1007  };
1008  if (startDate < lowerBound) {
1009  startDate = lowerBound;
1010  }
1011 
1012  extended::DateTuple untilDate = {
1013  era.untilYearTiny(), era.untilMonth(), era.untilDay(),
1014  (int16_t) era.untilTimeMinutes(), era.untilTimeSuffix()
1015  };
1016  extended::DateTuple upperBound = {
1017  untilYm.yearTiny, untilYm.month, 1, 0,
1019  };
1020  if (upperBound < untilDate) {
1021  untilDate = upperBound;
1022  }
1023 
1024  return {startDate, untilDate, era};
1025  }
1026 
1031  static void findTransitions(
1033  extended::ZoneMatch* matches,
1034  uint8_t numMatches) {
1035  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1036  logging::printf("findTransitions()\n");
1037  }
1038  for (uint8_t i = 0; i < numMatches; i++) {
1039  findTransitionsForMatch(transitionStorage, &matches[i]);
1040  }
1041  }
1042 
1044  static void findTransitionsForMatch(
1046  const extended::ZoneMatch* match) {
1047  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1048  logging::printf("findTransitionsForMatch()\n");
1049  }
1050  const extended::ZonePolicyBroker policy = match->era.zonePolicy();
1051  if (policy.isNull()) {
1052  findTransitionsFromSimpleMatch(transitionStorage, match);
1053  } else {
1054  findTransitionsFromNamedMatch(transitionStorage, match);
1055  }
1056  }
1057 
1058  static void findTransitionsFromSimpleMatch(
1060  const extended::ZoneMatch* match) {
1061  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1062  logging::printf("findTransitionsFromSimpleMatch()\n");
1063  }
1064  extended::Transition* freeTransition = transitionStorage.getFreeAgent();
1065  createTransitionForYear(freeTransition, 0 /*not used*/,
1066  extended::ZoneRuleBroker(nullptr) /*rule*/, match);
1067  transitionStorage.addFreeAgentToActivePool();
1068  }
1069 
1070  static void findTransitionsFromNamedMatch(
1072  const extended::ZoneMatch* match) {
1073  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1074  logging::printf("findTransitionsFromNamedMatch()\n");
1075  }
1076  transitionStorage.resetCandidatePool();
1077  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1078  match->log(); logging::printf("\n");
1079  }
1080  findCandidateTransitions(transitionStorage, match);
1081  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1082  transitionStorage.log(); logging::printf("\n");
1083  }
1084  fixTransitionTimes(
1085  transitionStorage.getCandidatePoolBegin(),
1086  transitionStorage.getCandidatePoolEnd());
1087  selectActiveTransitions(transitionStorage, match);
1088  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1089  transitionStorage.log(); logging::printf("\n");
1090  }
1091 
1092  transitionStorage.addActiveCandidatesToActivePool();
1093  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1094  transitionStorage.log(); logging::printf("\n");
1095  }
1096  }
1097 
1098  static void findCandidateTransitions(
1100  const extended::ZoneMatch* match) {
1101  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1102  logging::printf("findCandidateTransitions(): ");
1103  match->log();
1104  logging::printf("\n");
1105  }
1106  const extended::ZonePolicyBroker policy = match->era.zonePolicy();
1107  uint8_t numRules = policy.numRules();
1108  int8_t startY = match->startDateTime.yearTiny;
1109  int8_t endY = match->untilDateTime.yearTiny;
1110 
1111  extended::Transition** prior = transitionStorage.reservePrior();
1112  (*prior)->active = false; // indicates "no prior transition"
1113  for (uint8_t r = 0; r < numRules; r++) {
1114  const extended::ZoneRuleBroker rule = policy.rule(r);
1115 
1116  // Add Transitions for interior years
1117  int8_t interiorYears[kMaxInteriorYears];
1118  uint8_t numYears = calcInteriorYears(interiorYears, kMaxInteriorYears,
1119  rule.fromYearTiny(), rule.toYearTiny(), startY, endY);
1120  for (uint8_t y = 0; y < numYears; y++) {
1121  int8_t year = interiorYears[y];
1122  extended::Transition* t = transitionStorage.getFreeAgent();
1123  createTransitionForYear(t, year, rule, match);
1124  int8_t status = compareTransitionToMatchFuzzy(t, match);
1125  if (status < 0) {
1126  setAsPriorTransition(transitionStorage, t);
1127  } else if (status == 1) {
1128  transitionStorage.addFreeAgentToCandidatePool();
1129  }
1130  }
1131 
1132  // Add Transition for prior year
1133  int8_t priorYear = getMostRecentPriorYear(
1134  rule.fromYearTiny(), rule.toYearTiny(), startY, endY);
1135  if (priorYear != LocalDate::kInvalidYearTiny) {
1136  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1137  logging::printf(
1138  "findCandidateTransitions(): priorYear: %d\n", priorYear);
1139  }
1140  extended::Transition* t = transitionStorage.getFreeAgent();
1141  createTransitionForYear(t, priorYear, rule, match);
1142  setAsPriorTransition(transitionStorage, t);
1143  }
1144  }
1145 
1146  // Add the reserved prior into the Candidate pool only if 'active' is
1147  // true, meaning that a prior was found.
1148  if ((*prior)->active) {
1149  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1150  logging::printf(
1151  "findCandidateTransitions(): adding prior to Candidate pool\n");
1152  }
1153  transitionStorage.addPriorToCandidatePool();
1154  }
1155  }
1156 
1161  static uint8_t calcInteriorYears(int8_t* interiorYears,
1162  uint8_t maxInteriorYears, int8_t fromYear, int8_t toYear,
1163  int8_t startYear, int8_t endYear) {
1164  uint8_t i = 0;
1165  for (int8_t year = startYear; year <= endYear; year++) {
1166  if (fromYear <= year && year <= toYear) {
1167  interiorYears[i] = year;
1168  i++;
1169  if (i >= maxInteriorYears) break;
1170  }
1171  }
1172  return i;
1173  }
1174 
1181  static void createTransitionForYear(extended::Transition* t, int8_t year,
1182  const extended::ZoneRuleBroker rule,
1183  const extended::ZoneMatch* match) {
1184  t->match = match;
1185  t->rule = rule;
1186  t->offsetMinutes = match->era.offsetMinutes();
1187  t->letterBuf[0] = '\0';
1188 
1189  if (rule.isNotNull()) {
1190  t->transitionTime = getTransitionTime(year, rule);
1191  t->deltaMinutes = rule.deltaMinutes();
1192 
1193  char letter = rule.letter();
1194  if (letter >= 32) {
1195  // If LETTER is a '-', treat it the same as an empty string.
1196  if (letter != '-') {
1197  t->letterBuf[0] = letter;
1198  t->letterBuf[1] = '\0';
1199  }
1200  } else {
1201  // rule->letter is a long string, so is referenced as an offset index
1202  // into the ZonePolicy.letters array. The string cannot fit in
1203  // letterBuf, so will be retrieved by the letter() method below.
1204  }
1205  } else {
1206  t->transitionTime = match->startDateTime;
1207  t->deltaMinutes = match->era.deltaMinutes();
1208  }
1209  }
1210 
1216  static int8_t getMostRecentPriorYear(int8_t fromYear, int8_t toYear,
1217  int8_t startYear, int8_t /*endYear*/) {
1218  if (fromYear < startYear) {
1219  if (toYear < startYear) {
1220  return toYear;
1221  } else {
1222  return startYear - 1;
1223  }
1224  } else {
1226  }
1227  }
1228 
1229  static extended::DateTuple getTransitionTime(
1230  int8_t yearTiny, const extended::ZoneRuleBroker rule) {
1232  yearTiny + LocalDate::kEpochYear, rule.inMonth(), rule.onDayOfWeek(),
1233  rule.onDayOfMonth());
1234  return {yearTiny, monthDay.month, monthDay.day,
1235  (int16_t) rule.atTimeMinutes(), rule.atTimeSuffix()};
1236  }
1237 
1248  static int8_t compareTransitionToMatchFuzzy(
1249  const extended::Transition* t, const extended::ZoneMatch* match) {
1250  int16_t ttMonths = t->transitionTime.yearTiny * 12
1251  + t->transitionTime.month;
1252 
1253  int16_t matchStartMonths = match->startDateTime.yearTiny * 12
1254  + match->startDateTime.month;
1255  if (ttMonths < matchStartMonths - 1) return -1;
1256 
1257  int16_t matchUntilMonths = match->untilDateTime.yearTiny * 12
1258  + match->untilDateTime.month;
1259  if (matchUntilMonths + 2 <= ttMonths) return 2;
1260 
1261  return 1;
1262  }
1263 
1265  static void setAsPriorTransition(
1267  extended::Transition* t) {
1268  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1269  logging::printf("setAsPriorTransition()\n");
1270  }
1271  extended::Transition* prior = transitionStorage.getPrior();
1272  if (prior->active) {
1273  if (prior->transitionTime < t->transitionTime) {
1274  t->active = true;
1275  transitionStorage.setFreeAgentAsPrior();
1276  }
1277  } else {
1278  t->active = true;
1279  transitionStorage.setFreeAgentAsPrior();
1280  }
1281  }
1282 
1291  static void fixTransitionTimes(
1292  extended::Transition** begin, extended::Transition** end) {
1293  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1294  logging::printf("fixTransitionTimes(): #transitions: %d;\n",
1295  (int) (end - begin));
1296  }
1297 
1298  // extend first Transition to -infinity
1299  extended::Transition* prev = *begin;
1300 
1301  for (extended::Transition** iter = begin; iter != end; ++iter) {
1302  extended::Transition* curr = *iter;
1303  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1304  logging::printf("fixTransitionTimes(): LOOP\n");
1305  curr->log();
1306  logging::printf("\n");
1307  }
1308  expandDateTuple(&curr->transitionTime,
1309  &curr->transitionTimeS, &curr->transitionTimeU,
1310  prev->offsetMinutes, prev->deltaMinutes);
1311  prev = curr;
1312  }
1313  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1314  logging::printf("fixTransitionTimes(): END\n");
1315  }
1316  }
1317 
1323  static void expandDateTuple(extended::DateTuple* tt,
1325  int16_t offsetMinutes, int16_t deltaMinutes) {
1326  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1327  logging::printf("expandDateTuple()\n");
1328  }
1329  if (tt->suffix == extended::ZoneContext::kSuffixS) {
1330  *tts = *tt;
1331  *ttu = {tt->yearTiny, tt->month, tt->day,
1332  (int16_t) (tt->minutes - offsetMinutes),
1334  *tt = {tt->yearTiny, tt->month, tt->day,
1335  (int16_t) (tt->minutes + deltaMinutes),
1337  } else if (tt->suffix == extended::ZoneContext::kSuffixU) {
1338  *ttu = *tt;
1339  *tts = {tt->yearTiny, tt->month, tt->day,
1340  (int16_t) (tt->minutes + offsetMinutes),
1342  *tt = {tt->yearTiny, tt->month, tt->day,
1343  (int16_t) (tt->minutes + (offsetMinutes + deltaMinutes)),
1345  } else {
1346  // Explicit set the suffix to 'w' in case it was something else.
1347  tt->suffix = extended::ZoneContext::kSuffixW;
1348  *tts = {tt->yearTiny, tt->month, tt->day,
1349  (int16_t) (tt->minutes - deltaMinutes),
1351  *ttu = {tt->yearTiny, tt->month, tt->day,
1352  (int16_t) (tt->minutes - (deltaMinutes + offsetMinutes)),
1354  }
1355 
1356  normalizeDateTuple(tt);
1357  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1358  logging::printf("expandDateTuple(): normalizeDateTuple(tt): ");
1359  tt->log();
1360  logging::printf("\n");
1361  }
1362 
1363  normalizeDateTuple(tts);
1364  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1365  logging::printf("expandDateTuple(): normalizeDateTuple(tts): ");
1366  tts->log();
1367  logging::printf("\n");
1368  }
1369 
1370  normalizeDateTuple(ttu);
1371  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1372  logging::printf("expandDateTuple(): normalizeDateTuple(ttu): ");
1373  ttu->log();
1374  logging::printf("\n");
1375  }
1376  }
1377 
1382  static void normalizeDateTuple(extended::DateTuple* dt) {
1383  const int16_t kOneDayAsMinutes = 60 * 24;
1384  if (dt->minutes <= -kOneDayAsMinutes) {
1386  dt->yearTiny, dt->month, dt->day);
1387  local_date_mutation::decrementOneDay(ld);
1388  dt->yearTiny = ld.yearTiny();
1389  dt->month = ld.month();
1390  dt->day = ld.day();
1391  dt->minutes += kOneDayAsMinutes;
1392  } else if (kOneDayAsMinutes <= dt->minutes) {
1394  dt->yearTiny, dt->month, dt->day);
1395  local_date_mutation::incrementOneDay(ld);
1396  dt->yearTiny = ld.yearTiny();
1397  dt->month = ld.month();
1398  dt->day = ld.day();
1399  dt->minutes -= kOneDayAsMinutes;
1400  } else {
1401  // do nothing
1402  }
1403  }
1404 
1409  static void selectActiveTransitions(
1411  const extended::ZoneMatch* match) {
1412  extended::Transition** begin = transitionStorage.getCandidatePoolBegin();
1413  extended::Transition** end = transitionStorage.getCandidatePoolEnd();
1414  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1415  logging::printf("selectActiveTransitions(): #candidates: %d\n",
1416  (int) (end - begin));
1417  }
1418  extended::Transition* prior = nullptr;
1419  for (extended::Transition** iter = begin; iter != end; ++iter) {
1420  extended::Transition* transition = *iter;
1421  processActiveTransition(match, transition, &prior);
1422  }
1423 
1424  // If the latest prior transition is found, shift it to start at the
1425  // startDateTime of the current match.
1426  if (prior) {
1427  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1428  logging::printf(
1429  "selectActiveTransitions(): found latest prior\n");
1430  }
1431  prior->originalTransitionTime = prior->transitionTime;
1432  prior->transitionTime = match->startDateTime;
1433  }
1434  }
1435 
1443  static void processActiveTransition(
1444  const extended::ZoneMatch* match,
1445  extended::Transition* transition,
1446  extended::Transition** prior) {
1447  int8_t status = compareTransitionToMatch(transition, match);
1448  if (status == 2) {
1449  transition->active = false;
1450  } else if (status == 1) {
1451  transition->active = true;
1452  } else if (status == 0) {
1453  if (*prior) {
1454  (*prior)->active = false;
1455  }
1456  transition->active = true;
1457  (*prior) = transition;
1458  } else { // (status < 0)
1459  if (*prior) {
1460  if ((*prior)->transitionTime < transition->transitionTime) {
1461  (*prior)->active = false;
1462  transition->active = true;
1463  (*prior) = transition;
1464  }
1465  } else {
1466  transition->active = true;
1467  (*prior) = transition;
1468  }
1469  }
1470  }
1471 
1486  static int8_t compareTransitionToMatch(
1487  const extended::Transition* transition,
1488  const extended::ZoneMatch* match) {
1489  const extended::DateTuple* transitionTime;
1490 
1491  const extended::DateTuple& matchStart = match->startDateTime;
1492  if (matchStart.suffix == extended::ZoneContext::kSuffixS) {
1493  transitionTime = &transition->transitionTimeS;
1494  } else if (matchStart.suffix ==
1496  transitionTime = &transition->transitionTimeU;
1497  } else { // assume 'w'
1498  transitionTime = &transition->transitionTime;
1499  }
1500  if (*transitionTime < matchStart) return -1;
1501  if (*transitionTime == matchStart) return 0;
1502 
1503  const extended::DateTuple& matchUntil = match->untilDateTime;
1504  if (matchUntil.suffix == extended::ZoneContext::kSuffixS) {
1505  transitionTime = &transition->transitionTimeS;
1506  } else if (matchUntil.suffix ==
1508  transitionTime = &transition->transitionTimeU;
1509  } else { // assume 'w'
1510  transitionTime = &transition->transitionTime;
1511  }
1512  if (*transitionTime < matchUntil) return 1;
1513  return 2;
1514  }
1515 
1521  static void generateStartUntilTimes(
1522  extended::Transition** begin, extended::Transition** end) {
1523  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1524  logging::printf(
1525  "generateStartUntilTimes(): #transitions: %d;\n",
1526  (int) (end - begin));
1527  }
1528 
1529  extended::Transition* prev = *begin;
1530  bool isAfterFirst = false;
1531 
1532  for (extended::Transition** iter = begin; iter != end; ++iter) {
1533  extended::Transition* const t = *iter;
1534 
1535  // 1) Update the untilDateTime of the previous Transition
1536  const extended::DateTuple& tt = t->transitionTime;
1537  if (isAfterFirst) {
1538  prev->untilDateTime = tt;
1539  }
1540 
1541  // 2) Calculate the current startDateTime by shifting the
1542  // transitionTime (represented in the UTC offset of the previous
1543  // transition) into the UTC offset of the *current* transition.
1544  int16_t minutes = tt.minutes + (
1545  - prev->offsetMinutes - prev->deltaMinutes
1546  + t->offsetMinutes + t->deltaMinutes);
1547  t->startDateTime = {tt.yearTiny, tt.month, tt.day, minutes,
1548  tt.suffix};
1549  normalizeDateTuple(&t->startDateTime);
1550 
1551  // 3) The epochSecond of the 'transitionTime' is determined by the
1552  // UTC offset of the *previous* Transition. However, the
1553  // transitionTime can be represented by an illegal time (e.g. 24:00).
1554  // So, it is better to use the properly normalized startDateTime
1555  // (calculated above) with the *current* UTC offset.
1556  //
1557  // NOTE: We should also be able to calculate this directly from
1558  // 'transitionTimeU' which should still be a valid field, because it
1559  // hasn't been clobbered by 'untilDateTime' yet. Not sure if this saves
1560  // any CPU time though, since we still need to mutiply by 900.
1561  const extended::DateTuple& st = t->startDateTime;
1562  const acetime_t offsetSeconds = (acetime_t) 60
1563  * (st.minutes - (t->offsetMinutes + t->deltaMinutes));
1565  st.yearTiny, st.month, st.day);
1566  t->startEpochSeconds = ld.toEpochSeconds() + offsetSeconds;
1567 
1568  prev = t;
1569  isAfterFirst = true;
1570  }
1571 
1572  // The last Transition's until time is the until time of the ZoneMatch.
1573  extended::DateTuple untilTime = prev->match->untilDateTime;
1574  extended::DateTuple untilTimeS; // needed only for expandDateTuple
1575  extended::DateTuple untilTimeU; // needed only for expandDateTuple
1576  expandDateTuple(&untilTime, &untilTimeS, &untilTimeU,
1577  prev->offsetMinutes, prev->deltaMinutes);
1578  prev->untilDateTime = untilTime;
1579  }
1580 
1584  static void calcAbbreviations(
1585  extended::Transition** begin, extended::Transition** end) {
1586  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1587  logging::printf("calcAbbreviations(): #transitions: %d;\n",
1588  (int) (end - begin));
1589  }
1590  for (extended::Transition** iter = begin; iter != end; ++iter) {
1591  extended::Transition* const t = *iter;
1592  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1593  logging::printf(
1594  "calcAbbreviations(): format:%s, deltaMinutes:%d, letter:%s\n",
1595  t->format(), t->deltaMinutes, t->letter());
1596  }
1597  createAbbreviation(t->abbrev, extended::Transition::kAbbrevSize,
1598  t->format(), t->deltaMinutes, t->letter());
1599  }
1600  }
1601 
1610  static void createAbbreviation(char* dest, uint8_t destSize,
1611  const char* format, uint16_t deltaMinutes, const char* letterString) {
1612  // Check if FORMAT contains a '%'.
1613  if (strchr(format, '%') != nullptr) {
1614  // Check if RULES column empty, therefore no 'letter'
1615  if (letterString == nullptr) {
1616  strncpy(dest, format, destSize - 1);
1617  dest[destSize - 1] = '\0';
1618  } else {
1619  copyAndReplace(dest, destSize, format, '%', letterString);
1620  }
1621  } else {
1622  // Check if FORMAT contains a '/'.
1623  const char* slashPos = strchr(format, '/');
1624  if (slashPos != nullptr) {
1625  if (deltaMinutes == 0) {
1626  uint8_t headLength = (slashPos - format);
1627  if (headLength >= destSize) headLength = destSize - 1;
1628  memcpy(dest, format, headLength);
1629  dest[headLength] = '\0';
1630  } else {
1631  uint8_t tailLength = strlen(slashPos+1);
1632  if (tailLength >= destSize) tailLength = destSize - 1;
1633  memcpy(dest, slashPos+1, tailLength);
1634  dest[tailLength] = '\0';
1635  }
1636  } else {
1637  // Just copy the FORMAT disregarding deltaMinutes and letterString.
1638  strncpy(dest, format, destSize);
1639  dest[destSize - 1] = '\0';
1640  }
1641  }
1642  }
1643 
1649  static void copyAndReplace(char* dst, uint8_t dstSize, const char* src,
1650  char oldChar, const char* newString) {
1651  while (*src != '\0' && dstSize > 0) {
1652  if (*src == oldChar) {
1653  while (*newString != '\0' && dstSize > 0) {
1654  *dst++ = *newString++;
1655  dstSize--;
1656  }
1657  src++;
1658  } else {
1659  *dst++ = *src++;
1660  dstSize--;
1661  }
1662  }
1663 
1664  if (dstSize == 0) {
1665  --dst;
1666  }
1667  *dst = '\0';
1668  }
1669 
1670  extended::ZoneInfoBroker mZoneInfo;
1671 
1672  mutable int16_t mYear = 0; // maybe create LocalDate::kInvalidYear?
1673  mutable bool mIsFilled = false;
1674  // NOTE: Maybe move mNumMatches and mMatches into a MatchStorage object.
1675  mutable uint8_t mNumMatches = 0; // actual number of matches
1676  mutable extended::ZoneMatch mMatches[kMaxMatches];
1677  mutable extended::TransitionStorage<kMaxTransitions> mTransitionStorage;
1678 };
1679 
1680 } // namespace ace_time
1681 
1682 #endif
void addFreeAgentToActivePool()
Immediately add the free agent Transition at index mIndexFree to the Active pool. ...
static TimeOffset forError()
Return an error indicator.
Definition: TimeOffset.h:105
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.
static const uint8_t kSuffixS
Represents &#39;s&#39; or standard time.
Definition: ZoneContext.h:16
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:241
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:138
Data broker for accessing ZoneRule.
Definition: Brokers.h:430
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:100
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:113
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:257
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...
int16_t deltaMinutes
The DST delta minutes.
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:340
Data broker for accessing ZonePolicy.
Definition: Brokers.h:522
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:157
void resetTransitionHighWater()
Reset the TransitionStorage high water mark.
Data broker for accessing ZoneInfo.
Definition: Brokers.h:682
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:56
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.
static TimeOffset forMinutes(int16_t minutes)
Create TimeOffset from minutes from 00:00.
Definition: TimeOffset.h:83
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:251
int16_t year() const
Return the full year instead of just the last 2 digits.
Definition: LocalDate.h:231
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.
static const uint8_t kSuffixW
Represents &#39;w&#39; or wall time.
Definition: ZoneContext.h:13
OffsetDateTime getOffsetDateTime(const LocalDateTime &ldt) const override
Return the best estimate of the OffsetDateTime at the given LocalDateTime for the timezone of the cur...
Data broker for accessing ZoneEra.
Definition: Brokers.h:580
int16_t offsetMinutes
The base offset minutes, not the total effective UTC offset.
ZoneRuleBroker rule
The Zone transition rule that matched for the the given year.
static const uint8_t kSuffixU
Represents &#39;u&#39; or UTC time.
Definition: ZoneContext.h:19
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