AceTime  1.1.2
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.isNull()) ? "" : "!");
145  logging::printf(")");
146  }
147 };
148 
174 struct Transition {
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 
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.isNull()) ? "" : "!");
318  logging::printf("; oMinutes: %d", offsetMinutes);
319  logging::printf("; dMinutes: %d", deltaMinutes);
320  logging::printf("; tt: "); transitionTime.log();
321  if (! rule.isNull()) {
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()),
560  ZoneContext::kSuffixW };
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):
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;
943  extended::ZoneEraBroker prev = extended::ZoneEraBroker(&kAnchorEra);
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,
1006  extended::ZoneContext::kSuffixW
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,
1018  extended::ZoneContext::kSuffixW
1019  };
1020  if (upperBound < untilDate) {
1021  untilDate = upperBound;
1022  }
1023 
1024  return {startDate, untilDate, era};
1025  }
1026 
1031  static void findTransitions(
1032  extended::TransitionStorage<kMaxTransitions>& transitionStorage,
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(
1045  extended::TransitionStorage<kMaxTransitions>& transitionStorage,
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(
1059  extended::TransitionStorage<kMaxTransitions>& transitionStorage,
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(
1071  extended::TransitionStorage<kMaxTransitions>& transitionStorage,
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(
1099  extended::TransitionStorage<kMaxTransitions>& transitionStorage,
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.isNull()) {
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) {
1231  basic::MonthDay monthDay = BasicZoneProcessor::calcStartDayOfMonth(
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(
1266  extended::TransitionStorage<kMaxTransitions>& transitionStorage,
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,
1324  extended::DateTuple* tts, extended::DateTuple* ttu,
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),
1333  extended::ZoneContext::kSuffixU};
1334  *tt = {tt->yearTiny, tt->month, tt->day,
1335  (int16_t) (tt->minutes + deltaMinutes),
1336  extended::ZoneContext::kSuffixW};
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),
1341  extended::ZoneContext::kSuffixS};
1342  *tt = {tt->yearTiny, tt->month, tt->day,
1343  (int16_t) (tt->minutes + (offsetMinutes + deltaMinutes)),
1344  extended::ZoneContext::kSuffixW};
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),
1350  extended::ZoneContext::kSuffixS};
1351  *ttu = {tt->yearTiny, tt->month, tt->day,
1352  (int16_t) (tt->minutes - (deltaMinutes + offsetMinutes)),
1353  extended::ZoneContext::kSuffixU};
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) {
1385  LocalDate ld = LocalDate::forTinyComponents(
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) {
1393  LocalDate ld = LocalDate::forTinyComponents(
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(
1410  extended::TransitionStorage<kMaxTransitions>& transitionStorage,
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 ==
1495  extended::ZoneContext::kSuffixU) {
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 ==
1507  extended::ZoneContext::kSuffixU) {
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));
1564  LocalDate ld = LocalDate::forTinyComponents(
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
ace_time::extended::Transition::transitionTime
DateTuple transitionTime
The original transition time, usually 'w' but sometimes 's' or 'u'.
Definition: ExtendedZoneProcessor.h:194
ace_time::extended::TransitionStorage::findTransition
const Transition * findTransition(acetime_t epochSeconds) const
Return the Transition matching the given epochSeconds.
Definition: ExtendedZoneProcessor.h:513
ace_time::basic::Transition::kAbbrevSize
static const uint8_t kAbbrevSize
Longest abbreviation currently seems to be 5 characters (https://www.timeanddate.com/time/zones/) but...
Definition: BasicZoneProcessor.h:63
ace_time::extended::ZoneMatch::untilDateTime
DateTuple untilDateTime
The effective until time of the matching ZoneEra.
Definition: ExtendedZoneProcessor.h:135
ace_time::extended::TransitionStorage
A heap manager which is specialized and tuned to manage a collection of Transitions,...
Definition: ExtendedZoneProcessor.h:358
ace_time::BasicZoneProcessor::calcStartDayOfMonth
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 <=...
Definition: BasicZoneProcessor.h:352
ace_time::extended::Transition::deltaMinutes
int16_t deltaMinutes
The DST delta minutes.
Definition: ExtendedZoneProcessor.h:245
ace_time::ExtendedZoneProcessor::printShortTo
void printShortTo(Print &printer) const override
Print a short human-readable identifier (e.g.
Definition: ExtendedZoneProcessor.cpp:29
ace_time::LocalDateTime::hour
uint8_t hour() const
Return the hour.
Definition: LocalDateTime.h:198
ace_time::LocalDateTime
Class that holds the date-time as the components (year, month, day, hour, minute, second) without reg...
Definition: LocalDateTime.h:27
ace_time::extended::Transition::untilDateTime
DateTuple untilDateTime
Until time expressed using the UTC offset of the current Transition.
Definition: ExtendedZoneProcessor.h:223
ace_time::TimeOffset::forError
static TimeOffset forError()
Return an error indicator.
Definition: TimeOffset.h:105
ace_time::ZoneProcessor::kTypeExtended
static const uint8_t kTypeExtended
Indicate ExtendedZoneProcessor.
Definition: ZoneProcessor.h:57
ace_time::TimeOffset::isError
bool isError() const
Return true if this TimeOffset represents an error.
Definition: TimeOffset.h:138
ace_time::extended::TransitionStorage::addActiveCandidatesToActivePool
void addActiveCandidatesToActivePool()
Add active candidates into the Active pool, and collapse the Candidate pool.
Definition: ExtendedZoneProcessor.h:486
ace_time::extended::Transition::letter
const char * letter() const
Return the letter string.
Definition: ExtendedZoneProcessor.h:278
ace_time::extended::Transition::startEpochSeconds
acetime_t startEpochSeconds
The calculated transition time of the given rule.
Definition: ExtendedZoneProcessor.h:233
ace_time::extended::TransitionStorage::TransitionStorage
TransitionStorage()
Constructor.
Definition: ExtendedZoneProcessor.h:361
ace_time::extended::ZoneRuleBroker
Data broker for accessing ZoneRule.
Definition: Brokers.h:424
ace_time::ExtendedZoneProcessor::resetTransitionHighWater
void resetTransitionHighWater()
Reset the TransitionStorage high water mark.
Definition: ExtendedZoneProcessor.h:776
ace_time::LocalDateTime::printTo
void printTo(Print &printer) const
Print LocalDateTime to 'printer' in ISO 8601 format.
Definition: LocalDateTime.cpp:14
ace_time::ExtendedZoneProcessor::getUtcOffset
TimeOffset getUtcOffset(acetime_t epochSeconds) const override
Return the total UTC offset at epochSeconds, including DST offset.
Definition: ExtendedZoneProcessor.h:676
ace_time::extended::TransitionStorage::addFreeAgentToActivePool
void addFreeAgentToActivePool()
Immediately add the free agent Transition at index mIndexFree to the Active pool.
Definition: ExtendedZoneProcessor.h:432
ace_time::ExtendedZoneProcessor::getZoneId
uint32_t getZoneId() const override
Return the unique stable zoneId.
Definition: ExtendedZoneProcessor.h:674
ace_time::extended::ZoneMatch::startDateTime
DateTuple startDateTime
The effective start time of the matching ZoneEra.
Definition: ExtendedZoneProcessor.h:132
ace_time::TimeOffset
A thin wrapper that represents a time offset from a reference point, usually 00:00 at UTC,...
Definition: TimeOffset.h:56
ace_time::extended::Transition::transitionTimeU
DateTuple transitionTimeU
Version of transitionTime in 'u' mode, using the UTC offset of the previous transition.
Definition: ExtendedZoneProcessor.h:217
ace_time::ExtendedZoneProcessor::getZoneInfo
const void * getZoneInfo() const override
Return the underlying ZoneInfo.
Definition: ExtendedZoneProcessor.h:670
ace_time::ExtendedZoneProcessor::getAbbrev
const char * getAbbrev(acetime_t epochSeconds) const override
Return the time zone abbreviation at epochSeconds.
Definition: ExtendedZoneProcessor.h:693
ace_time::OffsetDateTime::forEpochSeconds
static OffsetDateTime forEpochSeconds(acetime_t epochSeconds, TimeOffset timeOffset)
Factory method.
Definition: OffsetDateTime.h:71
ace_time::extended::TransitionStorage::getFreeAgent
Transition * getFreeAgent()
Return a pointer to the first Transition in the free pool.
Definition: ExtendedZoneProcessor.h:411
ace_time::extended::Transition::startDateTime
DateTuple startDateTime
Start time expressed using the UTC offset of the current Transition.
Definition: ExtendedZoneProcessor.h:208
ace_time::extended::TransitionStorage::addPriorToCandidatePool
void addPriorToCandidatePool()
Add the current prior into the Candidates pool.
Definition: ExtendedZoneProcessor.h:460
ace_time::extended::TransitionStorage::init
void init()
Initialize all pools.
Definition: ExtendedZoneProcessor.h:364
ace_time::ExtendedZoneProcessor::ExtendedZoneProcessor
ExtendedZoneProcessor(const extended::ZoneInfo *zoneInfo=nullptr)
Constructor.
Definition: ExtendedZoneProcessor.h:664
ace_time::LocalDate::forEpochSeconds
static LocalDate forEpochSeconds(acetime_t epochSeconds)
Factory method using the number of seconds since AceTime epoch of 2000-01-01.
Definition: LocalDate.h:157
ace_time::ExtendedZoneProcessor
An implementation of ZoneProcessor that supports for all zones defined by the TZ Database.
Definition: ExtendedZoneProcessor.h:658
ace_time::ExtendedZoneProcessor::log
void log() const
Used only for debugging.
Definition: ExtendedZoneProcessor.h:763
ace_time::extended::Transition::originalTransitionTime
DateTuple originalTransitionTime
If the transition is shifted to the beginning of a ZoneMatch, this is set to the transitionTime for d...
Definition: ExtendedZoneProcessor.h:230
ace_time::extended::ZoneMatch::era
ZoneEraBroker era
The ZoneEra that matched the given year.
Definition: ExtendedZoneProcessor.h:138
ace_time::extended::Transition
Represents an interval of time where the time zone obeyed a certain UTC offset and DST delta.
Definition: ExtendedZoneProcessor.h:174
ace_time::LocalDateTime::month
uint8_t month() const
Return the month with January=1, December=12.
Definition: LocalDateTime.h:186
ace_time::OffsetDateTime
The date (year, month, day), time (hour, minute, second) and offset from UTC (timeOffset).
Definition: OffsetDateTime.h:33
ace_time::extended::Transition::active
bool active
Flag used for 2 slightly different meanings at different stages of init() processing.
Definition: ExtendedZoneProcessor.h:265
compat.h
ace_time::extended::TransitionStorage::setFreeAgentAsPrior
void setFreeAgentAsPrior()
Swap the Free agrent transition with the current Prior transition.
Definition: ExtendedZoneProcessor.h:451
ace_time::ZoneProcessor
Base interface for ZoneProcessor classes.
Definition: ZoneProcessor.h:45
ace_time::extended::Transition::kAbbrevSize
static const uint8_t kAbbrevSize
Size of the timezone abbreviation.
Definition: ExtendedZoneProcessor.h:176
ace_time::ExtendedZoneProcessor::getDeltaOffset
TimeOffset getDeltaOffset(acetime_t epochSeconds) const override
Return the DST delta offset at epochSeconds.
Definition: ExtendedZoneProcessor.h:686
ace_time::LocalDate::kInvalidYearTiny
static const int8_t kInvalidYearTiny
Sentinel yearTiny which indicates an error condition or sometimes a year that 'does not exist'.
Definition: LocalDate.h:45
ace_time::extended::Transition::match
const ZoneMatch * match
The match which generated this Transition.
Definition: ExtendedZoneProcessor.h:179
ace_time::extended::TransitionStorage::resetHighWater
void resetHighWater()
Reset the high water mark.
Definition: ExtendedZoneProcessor.h:600
ace_time::extended::DateTuple
A tuple that represents a date and time.
Definition: ExtendedZoneProcessor.h:64
ace_time::extended::ZonePolicyBroker
Data broker for accessing ZonePolicy.
Definition: Brokers.h:514
ace_time::extended::TransitionStorage::findTransitionForDateTime
const Transition * findTransitionForDateTime(const LocalDateTime &ldt) const
Return the Transition matching the given dateTime.
Definition: ExtendedZoneProcessor.h:550
ace_time::LocalDateTime::minute
uint8_t minute() const
Return the minute.
Definition: LocalDateTime.h:204
ace_time::extended::TransitionStorage::swap
void swap(Transition **a, Transition **b)
Swap 2 transitions.
Definition: ExtendedZoneProcessor.h:377
ace_time::LocalDateTime::localDate
const LocalDate & localDate() const
Return the LocalDate.
Definition: LocalDateTime.h:219
ace_time::TimeOffset::forMinutes
static TimeOffset forMinutes(int16_t minutes)
Create TimeOffset from minutes from 00:00.
Definition: TimeOffset.h:83
ace_time::LocalDate::forTinyComponents
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
ace_time::extended::Transition::offsetMinutes
int16_t offsetMinutes
The base offset minutes, not the total effective UTC offset.
Definition: ExtendedZoneProcessor.h:242
ace_time::LocalDate::kEpochYear
static const int16_t kEpochYear
Base year of epoch.
Definition: LocalDate.h:39
ace_time::extended::Transition::log
void log() const
Used only for debugging.
Definition: ExtendedZoneProcessor.h:308
ace_time::extended::TransitionStorage::reservePrior
Transition ** reservePrior()
Allocate one Transition just after the Active pool, but before the Candidate pool,...
Definition: ExtendedZoneProcessor.h:444
ace_time::LocalDateTime::yearTiny
int8_t yearTiny() const
Return the single-byte year offset from year 2000.
Definition: LocalDateTime.h:176
ace_time::ExtendedZoneProcessor::getTransitionHighWater
uint8_t getTransitionHighWater() const
Get the TransitionStorage high water mark.
Definition: ExtendedZoneProcessor.h:781
ace_time::extended::ZoneEraBroker
Data broker for accessing ZoneEra.
Definition: Brokers.h:570
ace_time::extended::TransitionStorage::getPrior
Transition * getPrior()
Return the current prior transition.
Definition: ExtendedZoneProcessor.h:374
ace_time::extended::Transition::transitionTimeS
DateTuple transitionTimeS
Version of transitionTime in 's' mode, using the UTC offset of the previous Transition.
Definition: ExtendedZoneProcessor.h:202
ace_time::extended::TransitionStorage::resetCandidatePool
void resetCandidatePool()
Empty the Candidate pool by resetting the various indexes.
Definition: ExtendedZoneProcessor.h:391
ace_time::LocalDateTime::day
uint8_t day() const
Return the day of the month.
Definition: LocalDateTime.h:192
ace_time::extended::TransitionStorage::addFreeAgentToCandidatePool
void addFreeAgentToCandidatePool()
Add the free agent Transition at index mIndexFree to the Candidate pool, sorted by transitionTime.
Definition: ExtendedZoneProcessor.h:470
ace_time::OffsetDateTime::forLocalDateTimeAndOffset
static OffsetDateTime forLocalDateTimeAndOffset(const LocalDateTime &localDateTime, TimeOffset timeOffset)
Factory method from LocalDateTime and TimeOffset.
Definition: OffsetDateTime.h:37
ace_time::extended::YearMonthTuple
A simple tuple to represent a year/month pair.
Definition: ExtendedZoneProcessor.h:121
ace_time::extended::DateTuple::log
void log() const
Used only for debugging.
Definition: ExtendedZoneProcessor.h:77
ace_time::ZoneProcessorCacheImpl
A cache of ZoneProcessors that provides a ZoneProcessor to the TimeZone upon request.
Definition: BasicZoneProcessor.h:36
ace_time::extended::Transition::abbrev
char abbrev[kAbbrevSize]
The calculated effective time zone abbreviation, e.g.
Definition: ExtendedZoneProcessor.h:248
ace_time::ExtendedZoneProcessor::getOffsetDateTime
OffsetDateTime getOffsetDateTime(const LocalDateTime &ldt) const override
Return the best estimate of the OffsetDateTime at the given LocalDateTime for the timezone of the cur...
Definition: ExtendedZoneProcessor.h:700
ace_time::extended::ZoneMatch
Data structure that captures the matching ZoneEra and its ZoneRule transitions for a given year.
Definition: ExtendedZoneProcessor.h:130
ace_time::extended::TransitionStorage::getHighWater
uint8_t getHighWater() const
Return the high water mark.
Definition: ExtendedZoneProcessor.h:607
ace_time::extended::Transition::letterBuf
char letterBuf[2]
Storage for the single letter 'letter' field if 'rule' is not null.
Definition: ExtendedZoneProcessor.h:251
ace_time::extended::Transition::rule
ZoneRuleBroker rule
The Zone transition rule that matched for the the given year.
Definition: ExtendedZoneProcessor.h:186
ace_time::extended::TransitionStorage::log
void log() const
Verify that the indexes are valid.
Definition: ExtendedZoneProcessor.h:573
ace_time::ExtendedZoneProcessor::printTo
void printTo(Print &printer) const override
Print a human-readable identifier (e.g.
Definition: ExtendedZoneProcessor.cpp:25