AceTime  1.6
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> // uintptr_t
11 #include "common/compat.h"
12 #include "internal/ZonePolicy.h"
13 #include "internal/ZoneInfo.h"
15 #include "common/logging.h"
16 #include "TimeOffset.h"
17 #include "LocalDate.h"
18 #include "OffsetDateTime.h"
19 #include "ZoneProcessor.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_setZoneKey;
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 class Print;
54 
55 namespace ace_time {
56 namespace extended {
57 
62 struct DateTuple {
63  DateTuple() = default;
64 
65  DateTuple(int8_t y, uint8_t mon, uint8_t d, int16_t min, uint8_t mod):
66  yearTiny(y), month(mon), day(d), suffix(mod), minutes(min) {}
67 
68  int8_t yearTiny; // [-127, 126], 127 will cause bugs
69  uint8_t month; // [1-12]
70  uint8_t day; // [1-31]
71  uint8_t suffix; // kSuffixS, kSuffixW, kSuffixU
72  int16_t minutes; // negative values allowed
73 
75  void log() const {
76  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
77  char c = "wsu"[(suffix>>4)];
78  logging::printf("DateTuple(%04d-%02u-%02uT%d'%c')",
79  yearTiny+LocalDate::kEpochYear, month, day, minutes, c);
80  }
81  }
82 };
83 
85 inline bool operator<(const DateTuple& a, const DateTuple& b) {
86  if (a.yearTiny < b.yearTiny) return true;
87  if (a.yearTiny > b.yearTiny) return false;
88  if (a.month < b.month) return true;
89  if (a.month > b.month) return false;
90  if (a.day < b.day) return true;
91  if (a.day > b.day) return false;
92  if (a.minutes < b.minutes) return true;
93  if (a.minutes > b.minutes) return false;
94  return false;
95 }
96 
97 inline bool operator>=(const DateTuple& a, const DateTuple& b) {
98  return ! (a < b);
99 }
100 
101 inline bool operator<=(const DateTuple& a, const DateTuple& b) {
102  return ! (b < a);
103 }
104 
105 inline bool operator>(const DateTuple& a, const DateTuple& b) {
106  return (b < a);
107 }
108 
110 inline bool operator==(const DateTuple& a, const DateTuple& b) {
111  return a.yearTiny == b.yearTiny
112  && a.month == b.month
113  && a.day == b.day
114  && a.minutes == b.minutes
115  && a.suffix == b.suffix;
116 }
117 
120  int8_t yearTiny;
121  uint8_t month;
122 };
123 
130 template<typename ZEB>
134 
137 
139  ZEB era;
140 
141  void log() const {
142  logging::printf("ZoneMatch(");
143  logging::printf("Start:"); startDateTime.log();
144  logging::printf("; Until:"); untilDateTime.log();
145  logging::printf("; Era: %snull", (era.isNull()) ? "" : "!");
146  logging::printf(")");
147  }
148 };
149 
179 template <typename ZEB, typename ZPB, typename ZRB>
183 
189  ZRB rule;
190 
198 
199  union {
206 
212  };
213 
214  union {
221 
227  };
228 
234 
236  acetime_t startEpochSeconds;
237 
245  int16_t offsetMinutes;
246 
248  int16_t deltaMinutes;
249 
251  char abbrev[internal::kAbbrevSize];
252 
254  char letterBuf[2];
255 
268  bool active;
269 
270  //-------------------------------------------------------------------------
271 
272  const char* format() const {
273  return match->era.format();
274  }
275 
281  const char* letter() const {
282  // RULES column is '-' or hh:mm, so return nullptr to indicate this.
283  if (rule.isNull()) {
284  return nullptr;
285  }
286 
287  // RULES point to a named rule, and LETTER is a single, printable
288  // character.
289  char letter = rule.letter();
290  if (letter >= 32) {
291  return letterBuf;
292  }
293 
294  // RULES points to a named rule, and the LETTER is a string. The
295  // rule->letter is a non-printable number < 32, which is an index into
296  // a list of strings given by match->era->zonePolicy->letters[].
297  const ZPB policy = match->era.zonePolicy();
298  uint8_t numLetters = policy.numLetters();
299  if (letter >= numLetters) {
300  // This should never happen unless there is a programming error. If it
301  // does, return an empty string. (createTransitionForYear() sets
302  // letterBuf to a NUL terminated empty string if rule->letter < 32)
303  return letterBuf;
304  }
305 
306  // Return the string at index 'rule->letter'.
307  return policy.letter(letter);
308  }
309 
311  void log() const {
312  logging::printf("Transition(");
313  if (sizeof(acetime_t) <= sizeof(int)) {
314  logging::printf("sE: %d", startEpochSeconds);
315  } else {
316  logging::printf("sE: %ld", startEpochSeconds);
317  }
318  logging::printf("; match: %snull", (match) ? "!" : "");
319  logging::printf("; era: %snull",
320  (match && match->era.isNull()) ? "" : "!");
321  logging::printf("; oMinutes: %d", offsetMinutes);
322  logging::printf("; dMinutes: %d", deltaMinutes);
323  logging::printf("; tt: "); transitionTime.log();
324  if (! rule.isNull()) {
325  logging::printf("; R.fY: %d", rule.fromYearTiny());
326  logging::printf("; R.tY: %d", rule.toYearTiny());
327  logging::printf("; R.M: %d", rule.inMonth());
328  logging::printf("; R.dow: %d", rule.onDayOfWeek());
329  logging::printf("; R.dom: %d", rule.onDayOfMonth());
330  }
331  }
332 };
333 
365 template<uint8_t SIZE, typename ZEB, typename ZPB, typename ZRB>
367  public:
373 
376 
378  void init() {
379  for (uint8_t i = 0; i < SIZE; i++) {
380  mTransitions[i] = &mPool[i];
381  }
382  mIndexPrior = 0;
383  mIndexCandidates = 0;
384  mIndexFree = 0;
385  }
386 
389  return mTransitions[mIndexPrior];
390  }
391 
393  void swap(
394  Transition** a,
395  Transition** b) {
396  auto* tmp = *a;
397  *a = *b;
398  *b = tmp;
399  }
400 
410  mIndexCandidates = mIndexPrior;
411  mIndexFree = mIndexPrior;
412  }
413 
414  Transition** getCandidatePoolBegin() {
415  return &mTransitions[mIndexCandidates];
416  }
417  Transition** getCandidatePoolEnd() {
418  return &mTransitions[mIndexFree];
419  }
420 
421  Transition** getActivePoolBegin() {
422  return &mTransitions[0];
423  }
424  Transition** getActivePoolEnd() {
425  return &mTransitions[mIndexFree];
426  }
427 
434  // Set the internal high water mark. If that index becomes SIZE,
435  // then we know we have an overflow.
436  if (mIndexFree > mHighWater) {
437  mHighWater = mIndexFree;
438  }
439 
440  if (mIndexFree < SIZE) {
441  return mTransitions[mIndexFree];
442  } else {
443  return mTransitions[SIZE - 1];
444  }
445  }
446 
455  if (mIndexFree >= SIZE) return;
456  mIndexFree++;
457  mIndexPrior = mIndexFree;
458  mIndexCandidates = mIndexFree;
459  }
460 
467  mIndexCandidates++;
468  mIndexFree++;
469  return &mTransitions[mIndexPrior];
470  }
471 
474  swap(&mTransitions[mIndexPrior], &mTransitions[mIndexFree]);
475  }
476 
483  mIndexCandidates--;
484  }
485 
493  if (mIndexFree >= SIZE) return;
494  for (uint8_t i = mIndexFree; i > mIndexCandidates; i--) {
495  Transition* curr = mTransitions[i];
496  Transition* prev = mTransitions[i - 1];
497  if (curr->transitionTime >= prev->transitionTime) break;
498  mTransitions[i] = prev;
499  mTransitions[i - 1] = curr;
500  }
501  mIndexFree++;
502  }
503 
509  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
510  logging::printf("addActiveCandidatesToActivePool()\n");
511  }
512  uint8_t iActive = mIndexPrior;
513  uint8_t iCandidate = mIndexCandidates;
514  for (; iCandidate < mIndexFree; iCandidate++) {
515  if (mTransitions[iCandidate]->active) {
516  if (iActive != iCandidate) {
517  swap(&mTransitions[iActive], &mTransitions[iCandidate]);
518  }
519  ++iActive;
520  }
521  }
522  mIndexPrior = iActive;
523  mIndexCandidates = iActive;
524  mIndexFree = iActive;
525  }
526 
535  const Transition* findTransition(acetime_t epochSeconds) const {
536  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
537  logging::printf( "findTransition(): mIndexFree: %d\n", mIndexFree);
538  }
539 
540  const Transition* match = nullptr;
541  for (uint8_t i = 0; i < mIndexFree; i++) {
542  const Transition* candidate = mTransitions[i];
543  if (candidate->startEpochSeconds > epochSeconds) break;
544  match = candidate;
545  }
546  return match;
547  }
548 
573  const {
574  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
575  logging::printf(
576  "findTransitionForDateTime(): mIndexFree: %d\n", mIndexFree);
577  }
578 
579  // Convert LocalDateTime to DateTuple.
580  DateTuple localDate = { ldt.yearTiny(), ldt.month(), ldt.day(),
581  (int16_t) (ldt.hour() * 60 + ldt.minute()),
583  const Transition* match = nullptr;
584 
585  // Find the last Transition that matches
586  for (uint8_t i = 0; i < mIndexFree; i++) {
587  const Transition* candidate = mTransitions[i];
588  if (candidate->startDateTime > localDate) break;
589  match = candidate;
590  }
591  return match;
592  }
593 
595  void log() const {
596  logging::printf("TransitionStorage:\n");
597  logging::printf(" mIndexPrior: %d\n", mIndexPrior);
598  logging::printf(" mIndexCandidates: %d\n", mIndexCandidates);
599  logging::printf(" mIndexFree: %d\n", mIndexFree);
600  if (mIndexPrior != 0) {
601  logging::printf(" Actives:\n");
602  for (uint8_t i = 0; i < mIndexPrior; i++) {
603  mTransitions[i]->log();
604  logging::printf("\n");
605  }
606  }
607  if (mIndexPrior != mIndexCandidates) {
608  logging::printf(" Prior: ");
609  mTransitions[mIndexPrior]->log();
610  logging::printf("\n");
611  }
612  if (mIndexCandidates != mIndexFree) {
613  logging::printf(" Candidates:\n");
614  for (uint8_t i = mIndexCandidates; i < mIndexFree; i++) {
615  mTransitions[i]->log();
616  logging::printf("\n");
617  }
618  }
619  }
620 
622  void resetHighWater() { mHighWater = 0; }
623 
629  uint8_t getHighWater() const { return mHighWater; }
630 
631  private:
632  friend class ::TransitionStorageTest_getFreeAgent;
633  friend class ::TransitionStorageTest_getFreeAgent2;
634  friend class ::TransitionStorageTest_addFreeAgentToActivePool;
635  friend class ::TransitionStorageTest_reservePrior;
636  friend class ::TransitionStorageTest_addFreeAgentToCandidatePool;
637  friend class ::TransitionStorageTest_setFreeAgentAsPrior;
638  friend class ::TransitionStorageTest_addActiveCandidatesToActivePool;
639  friend class ::TransitionStorageTest_findTransitionForDateTime;
640  friend class ::TransitionStorageTest_resetCandidatePool;
641 
643  Transition* getTransition(uint8_t i) {
644  return mTransitions[i];
645  }
646 
647  Transition mPool[SIZE];
648  Transition* mTransitions[SIZE];
649  uint8_t mIndexPrior;
650  uint8_t mIndexCandidates;
651  uint8_t mIndexFree;
652 
654  uint8_t mHighWater = 0;
655 };
656 
662 inline void copyAndReplace(char* dst, uint8_t dstSize, const char* src,
663  char oldChar, const char* newString) {
664  while (*src != '\0' && dstSize > 0) {
665  if (*src == oldChar) {
666  while (*newString != '\0' && dstSize > 0) {
667  *dst++ = *newString++;
668  dstSize--;
669  }
670  src++;
671  } else {
672  *dst++ = *src++;
673  dstSize--;
674  }
675  }
676 
677  if (dstSize == 0) {
678  --dst;
679  }
680  *dst = '\0';
681 }
682 
683 } // namespace extended
684 
716 template <typename BF, typename ZIB, typename ZEB, typename ZPB, typename ZRB>
718  public:
727  static const uint8_t kMaxTransitions = 8;
728 
731 
734 
738 
739  uint32_t getZoneId() const override { return mZoneInfoBroker.zoneId(); }
740 
741  TimeOffset getUtcOffset(acetime_t epochSeconds) const override {
742  bool success = init(epochSeconds);
743  if (!success) return TimeOffset::forError();
744  const Transition* transition = findTransition(epochSeconds);
745  return (transition)
747  transition->offsetMinutes + transition->deltaMinutes)
749  }
750 
751  TimeOffset getDeltaOffset(acetime_t epochSeconds) const override {
752  bool success = init(epochSeconds);
753  if (!success) return TimeOffset::forError();
754  const Transition* transition = findTransition(epochSeconds);
755  return TimeOffset::forMinutes(transition->deltaMinutes);
756  }
757 
758  const char* getAbbrev(acetime_t epochSeconds) const override {
759  bool success = init(epochSeconds);
760  if (!success) return "";
761  const Transition* transition = findTransition(epochSeconds);
762  return transition->abbrev;
763  }
764 
765  OffsetDateTime getOffsetDateTime(const LocalDateTime& ldt) const override {
766  TimeOffset offset;
767  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
768  logging::printf("getOffsetDateTime(): ldt=");
769  ldt.printTo(SERIAL_PORT_MONITOR);
770  SERIAL_PORT_MONITOR.println();
771  }
772  bool success = init(ldt.localDate());
773 
774  // Find the Transition to get the DST offset
775  if (success) {
776  const Transition* transition =
777  mTransitionStorage.findTransitionForDateTime(ldt);
778  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
779  logging::printf("getOffsetDateTime(): match transition=");
780  transition->log();
781  logging::printf("\n");
782  }
783  offset = (transition)
785  transition->offsetMinutes + transition->deltaMinutes)
787  } else {
788  offset = TimeOffset::forError();
789  }
790 
791  auto odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset);
792  if (offset.isError()) {
793  return odt;
794  }
795  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
796  logging::printf("getOffsetDateTime(): odt=");
797  odt.printTo(SERIAL_PORT_MONITOR);
798  SERIAL_PORT_MONITOR.println();
799  }
800 
801  // Normalize the OffsetDateTime, causing LocalDateTime in the DST
802  // transtion gap to be shifted forward one hour. For LocalDateTime in an
803  // overlap (DST->STD transition), the earlier UTC offset is selected// by
804  // findTransitionForDateTime(). Use that to calculate the epochSeconds,
805  // then recalculate the offset. Use this final offset to determine the
806  // effective OffsetDateTime that will survive a round-trip unchanged.
807  acetime_t epochSeconds = odt.toEpochSeconds();
808  const Transition* transition =
809  mTransitionStorage.findTransition(epochSeconds);
810  offset = (transition)
812  transition->offsetMinutes + transition->deltaMinutes)
814  odt = OffsetDateTime::forEpochSeconds(epochSeconds, offset);
815  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
816  logging::printf("getOffsetDateTime(): normalized(odt)=");
817  odt.printTo(SERIAL_PORT_MONITOR);
818  SERIAL_PORT_MONITOR.println();
819  }
820  return odt;
821  }
822 
823  void printNameTo(Print& printer) const override {
824  mZoneInfoBroker.printNameTo(printer);
825  }
826 
827  void printShortNameTo(Print& printer) const override {
828  mZoneInfoBroker.printShortNameTo(printer);
829  }
830 
832  void log() const {
833  logging::printf("ExtendedZoneProcessor:\n");
834  logging::printf(" mYear: %d\n", mYear);
835  logging::printf(" mNumMatches: %d\n", mNumMatches);
836  for (int i = 0; i < mNumMatches; i++) {
837  logging::printf(" Match %d: ", i);
838  mMatches[i].log();
839  logging::printf("\n");
840  }
841  mTransitionStorage.log();
842  }
843 
846  mTransitionStorage.resetHighWater();
847  }
848 
850  uint8_t getTransitionHighWater() const {
851  return mTransitionStorage.getHighWater();
852  }
853 
854  void setZoneKey(uintptr_t zoneKey) override {
855  if (mZoneInfoBroker.equals(zoneKey)) return;
856 
857  mZoneInfoBroker = mBrokerFactory->createZoneInfoBroker(zoneKey);
858  mYear = 0;
859  mIsFilled = false;
860  mNumMatches = 0;
862  }
863 
864  bool equalsZoneKey(uintptr_t zoneKey) const override {
865  return mZoneInfoBroker.equals(zoneKey);
866  }
867 
868  void setBrokerFactory(const BF* brokerFactory) {
869  mBrokerFactory = brokerFactory;
870  }
871 
872  protected:
873 
881  uint8_t type,
882  const BF* brokerFactory,
883  uintptr_t zoneKey
884  ) :
885  ZoneProcessor(type),
886  mBrokerFactory(brokerFactory)
887  {
888  setZoneKey(zoneKey);
889  }
890 
891  private:
892  friend class ::ExtendedZoneProcessorTest_compareEraToYearMonth;
893  friend class ::ExtendedZoneProcessorTest_compareEraToYearMonth2;
894  friend class ::ExtendedZoneProcessorTest_createMatch;
895  friend class ::ExtendedZoneProcessorTest_findMatches_simple;
896  friend class ::ExtendedZoneProcessorTest_findMatches_named;
897  friend class ::ExtendedZoneProcessorTest_findCandidateTransitions;
898  friend class ::ExtendedZoneProcessorTest_findTransitionsFromNamedMatch;
899  friend class ::ExtendedZoneProcessorTest_getTransitionTime;
900  friend class ::ExtendedZoneProcessorTest_createTransitionForYear;
901  friend class ::ExtendedZoneProcessorTest_normalizeDateTuple;
902  friend class ::ExtendedZoneProcessorTest_expandDateTuple;
903  friend class ::ExtendedZoneProcessorTest_calcInteriorYears;
904  friend class ::ExtendedZoneProcessorTest_getMostRecentPriorYear;
905  friend class ::ExtendedZoneProcessorTest_compareTransitionToMatchFuzzy;
906  friend class ::ExtendedZoneProcessorTest_compareTransitionToMatch;
907  friend class ::ExtendedZoneProcessorTest_processActiveTransition;
908  friend class ::ExtendedZoneProcessorTest_fixTransitionTimes_generateStartUntilTimes;
909  friend class ::ExtendedZoneProcessorTest_createAbbreviation;
910  friend class ::ExtendedZoneProcessorTest_setZoneKey;
911  friend class ::TransitionStorageTest_findTransitionForDateTime;
912 
913  // Disable copy constructor and assignment operator.
915  const ExtendedZoneProcessorTemplate&) = delete;
917  const ExtendedZoneProcessorTemplate&) = delete;
918 
923  static const uint8_t kMaxMatches = 4;
924 
929  static const uint8_t kMaxInteriorYears = 4;
930 
931  bool equals(const ZoneProcessor& other) const override {
932  return mZoneInfoBroker.equals(
933  ((const ExtendedZoneProcessorTemplate&) other).mZoneInfoBroker);
934  }
935 
940  const Transition* findTransition(acetime_t epochSeconds) const {
941  return mTransitionStorage.findTransition(epochSeconds);
942  }
943 
945  bool init(acetime_t epochSeconds) const {
946  LocalDate ld = LocalDate::forEpochSeconds(epochSeconds);
947  return init(ld);
948  }
949 
954  bool init(const LocalDate& ld) const {
955  int16_t year = ld.year();
956  if (isFilled(year)) return true;
957  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
958  logging::printf("init(): %d\n", year);
959  }
960 
961  mYear = year;
962  mNumMatches = 0; // clear cache
963  mTransitionStorage.init();
964 
965  if (year < mZoneInfoBroker.zoneContext()->startYear - 1
966  || mZoneInfoBroker.zoneContext()->untilYear < year) {
967  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
968  logging::printf("init(): Year %d out of valid range [%d, %d)\n",
969  year,
970  mZoneInfoBroker.zoneContext()->startYear,
971  mZoneInfoBroker.zoneContext()->untilYear);
972  }
973  return false;
974  }
975 
976  extended::YearMonthTuple startYm = {
977  (int8_t) (year - LocalDate::kEpochYear - 1), 12 };
978  extended::YearMonthTuple untilYm = {
979  (int8_t) (year - LocalDate::kEpochYear + 1), 2 };
980 
981  mNumMatches = findMatches(mZoneInfoBroker, startYm, untilYm, mMatches,
982  kMaxMatches);
983  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
984  findTransitions(mTransitionStorage, mMatches, mNumMatches);
985  Transition** begin = mTransitionStorage.getActivePoolBegin();
986  Transition** end = mTransitionStorage.getActivePoolEnd();
987  fixTransitionTimes(begin, end);
988  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
989  generateStartUntilTimes(begin, end);
990  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
991  calcAbbreviations(begin, end);
992  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
993 
994  mIsFilled = true;
995  return true;
996  }
997 
999  bool isFilled(int16_t year) const {
1000  return mIsFilled && (year == mYear);
1001  }
1002 
1010  static uint8_t findMatches(const ZIB& zoneInfo,
1011  const extended::YearMonthTuple& startYm,
1012  const extended::YearMonthTuple& untilYm,
1013  ZoneMatch* matches, uint8_t maxMatches) {
1014  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1015  logging::printf("findMatches()\n");
1016  }
1017  uint8_t iMatch = 0;
1018  ZEB prev; // anchor ZoneEra, representing the earliest untilTime
1019  for (uint8_t iEra = 0; iEra < zoneInfo.numEras(); iEra++) {
1020  const ZEB era = zoneInfo.era(iEra);
1021  if (eraOverlapsInterval(prev, era, startYm, untilYm)) {
1022  if (iMatch < maxMatches) {
1023  matches[iMatch] = createMatch(prev, era, startYm, untilYm);
1024  iMatch++;
1025  }
1026  }
1027  prev = era;
1028  }
1029  return iMatch;
1030  }
1031 
1043  static bool eraOverlapsInterval(
1044  const ZEB& prev,
1045  const ZEB& era,
1046  const extended::YearMonthTuple& startYm,
1047  const extended::YearMonthTuple& untilYm) {
1048  return (prev.isNull() || compareEraToYearMonth(
1049  prev, untilYm.yearTiny, untilYm.month) < 0)
1050  && compareEraToYearMonth(era, startYm.yearTiny, startYm.month) > 0;
1051  }
1052 
1054  static int8_t compareEraToYearMonth(const ZEB& era,
1055  int8_t yearTiny, uint8_t month) {
1056  if (era.untilYearTiny() < yearTiny) return -1;
1057  if (era.untilYearTiny() > yearTiny) return 1;
1058  if (era.untilMonth() < month) return -1;
1059  if (era.untilMonth() > month) return 1;
1060  if (era.untilDay() > 1) return 1;
1061  //if (era.untilTimeMinutes() < 0) return -1; // never possible
1062  if (era.untilTimeMinutes() > 0) return 1;
1063  return 0;
1064  }
1065 
1072  static ZoneMatch createMatch(
1073  const ZEB& prev,
1074  const ZEB& era,
1075  const extended::YearMonthTuple& startYm,
1076  const extended::YearMonthTuple& untilYm) {
1077  // If prev.isNull(), set startDate to be earlier than all valid ZoneEra.
1078  extended::DateTuple startDate = prev.isNull()
1079  ? extended::DateTuple{
1080  LocalDate::kInvalidYearTiny, 1, 1, 0,
1082  }
1083  : extended::DateTuple{
1084  prev.untilYearTiny(), prev.untilMonth(), prev.untilDay(),
1085  (int16_t) prev.untilTimeMinutes(), prev.untilTimeSuffix()
1086  };
1087  extended::DateTuple lowerBound = {
1088  startYm.yearTiny, startYm.month, 1, 0,
1090  };
1091  if (startDate < lowerBound) {
1092  startDate = lowerBound;
1093  }
1094 
1095  extended::DateTuple untilDate = {
1096  era.untilYearTiny(), era.untilMonth(), era.untilDay(),
1097  (int16_t) era.untilTimeMinutes(), era.untilTimeSuffix()
1098  };
1099  extended::DateTuple upperBound = {
1100  untilYm.yearTiny, untilYm.month, 1, 0,
1102  };
1103  if (upperBound < untilDate) {
1104  untilDate = upperBound;
1105  }
1106 
1107  return {startDate, untilDate, era};
1108  }
1109 
1114  static void findTransitions(
1115  TransitionStorage& transitionStorage,
1116  ZoneMatch* matches,
1117  uint8_t numMatches) {
1118  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1119  logging::printf("findTransitions()\n");
1120  }
1121  for (uint8_t i = 0; i < numMatches; i++) {
1122  findTransitionsForMatch(transitionStorage, &matches[i]);
1123  }
1124  }
1125 
1127  static void findTransitionsForMatch(
1128  TransitionStorage& transitionStorage,
1129  const ZoneMatch* match) {
1130  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1131  logging::printf("findTransitionsForMatch()\n");
1132  }
1133  const ZPB policy = match->era.zonePolicy();
1134  if (policy.isNull()) {
1135  findTransitionsFromSimpleMatch(transitionStorage, match);
1136  } else {
1137  findTransitionsFromNamedMatch(transitionStorage, match);
1138  }
1139  }
1140 
1141  static void findTransitionsFromSimpleMatch(
1142  TransitionStorage& transitionStorage,
1143  const ZoneMatch* match) {
1144  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1145  logging::printf("findTransitionsFromSimpleMatch()\n");
1146  }
1147  Transition* freeTransition = transitionStorage.getFreeAgent();
1148  createTransitionForYear(freeTransition, 0 /*not used*/,
1149  ZRB() /*rule*/, match);
1150  transitionStorage.addFreeAgentToActivePool();
1151  }
1152 
1153  static void findTransitionsFromNamedMatch(
1154  TransitionStorage& transitionStorage,
1155  const ZoneMatch* match) {
1156  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1157  logging::printf("findTransitionsFromNamedMatch()\n");
1158  }
1159  transitionStorage.resetCandidatePool();
1160  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1161  match->log(); logging::printf("\n");
1162  }
1163  findCandidateTransitions(transitionStorage, match);
1164  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1165  transitionStorage.log(); logging::printf("\n");
1166  }
1167  fixTransitionTimes(
1168  transitionStorage.getCandidatePoolBegin(),
1169  transitionStorage.getCandidatePoolEnd());
1170  selectActiveTransitions(transitionStorage, match);
1171  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1172  transitionStorage.log(); logging::printf("\n");
1173  }
1174 
1175  transitionStorage.addActiveCandidatesToActivePool();
1176  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1177  transitionStorage.log(); logging::printf("\n");
1178  }
1179  }
1180 
1181  static void findCandidateTransitions(
1182  TransitionStorage& transitionStorage,
1183  const ZoneMatch* match) {
1184  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1185  logging::printf("findCandidateTransitions(): ");
1186  match->log();
1187  logging::printf("\n");
1188  }
1189  const ZPB policy = match->era.zonePolicy();
1190  uint8_t numRules = policy.numRules();
1191  int8_t startY = match->startDateTime.yearTiny;
1192  int8_t endY = match->untilDateTime.yearTiny;
1193 
1194  Transition** prior = transitionStorage.reservePrior();
1195  (*prior)->active = false; // indicates "no prior transition"
1196  for (uint8_t r = 0; r < numRules; r++) {
1197  const ZRB rule = policy.rule(r);
1198 
1199  // Add Transitions for interior years
1200  int8_t interiorYears[kMaxInteriorYears];
1201  uint8_t numYears = calcInteriorYears(interiorYears, kMaxInteriorYears,
1202  rule.fromYearTiny(), rule.toYearTiny(), startY, endY);
1203  for (uint8_t y = 0; y < numYears; y++) {
1204  int8_t year = interiorYears[y];
1205  Transition* t = transitionStorage.getFreeAgent();
1206  createTransitionForYear(t, year, rule, match);
1207  int8_t status = compareTransitionToMatchFuzzy(t, match);
1208  if (status < 0) {
1209  setAsPriorTransition(transitionStorage, t);
1210  } else if (status == 1) {
1211  transitionStorage.addFreeAgentToCandidatePool();
1212  }
1213  }
1214 
1215  // Add Transition for prior year
1216  int8_t priorYear = getMostRecentPriorYear(
1217  rule.fromYearTiny(), rule.toYearTiny(), startY, endY);
1218  if (priorYear != LocalDate::kInvalidYearTiny) {
1219  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1220  logging::printf(
1221  "findCandidateTransitions(): priorYear: %d\n", priorYear);
1222  }
1223  Transition* t = transitionStorage.getFreeAgent();
1224  createTransitionForYear(t, priorYear, rule, match);
1225  setAsPriorTransition(transitionStorage, t);
1226  }
1227  }
1228 
1229  // Add the reserved prior into the Candidate pool only if 'active' is
1230  // true, meaning that a prior was found.
1231  if ((*prior)->active) {
1232  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1233  logging::printf(
1234  "findCandidateTransitions(): adding prior to Candidate pool\n");
1235  }
1236  transitionStorage.addPriorToCandidatePool();
1237  }
1238  }
1239 
1244  static uint8_t calcInteriorYears(int8_t* interiorYears,
1245  uint8_t maxInteriorYears, int8_t fromYear, int8_t toYear,
1246  int8_t startYear, int8_t endYear) {
1247  uint8_t i = 0;
1248  for (int8_t year = startYear; year <= endYear; year++) {
1249  if (fromYear <= year && year <= toYear) {
1250  interiorYears[i] = year;
1251  i++;
1252  if (i >= maxInteriorYears) break;
1253  }
1254  }
1255  return i;
1256  }
1257 
1264  static void createTransitionForYear(Transition* t, int8_t year,
1265  const ZRB& rule, const ZoneMatch* match) {
1266  t->match = match;
1267  t->rule = rule;
1268  t->offsetMinutes = match->era.offsetMinutes();
1269  t->letterBuf[0] = '\0';
1270 
1271  if (! rule.isNull()) {
1272  t->transitionTime = getTransitionTime(year, rule);
1273  t->deltaMinutes = rule.deltaMinutes();
1274 
1275  char letter = rule.letter();
1276  if (letter >= 32) {
1277  // If LETTER is a '-', treat it the same as an empty string.
1278  if (letter != '-') {
1279  t->letterBuf[0] = letter;
1280  t->letterBuf[1] = '\0';
1281  }
1282  } else {
1283  // rule->letter is a long string, so is referenced as an offset index
1284  // into the ZonePolicy.letters array. The string cannot fit in
1285  // letterBuf, so will be retrieved by the letter() method below.
1286  }
1287  } else {
1288  t->transitionTime = match->startDateTime;
1289  t->deltaMinutes = match->era.deltaMinutes();
1290  }
1291  }
1292 
1298  static int8_t getMostRecentPriorYear(int8_t fromYear, int8_t toYear,
1299  int8_t startYear, int8_t /*endYear*/) {
1300  if (fromYear < startYear) {
1301  if (toYear < startYear) {
1302  return toYear;
1303  } else {
1304  return startYear - 1;
1305  }
1306  } else {
1308  }
1309  }
1310 
1311  static extended::DateTuple getTransitionTime(
1312  int8_t yearTiny, const ZRB& rule) {
1313  internal::MonthDay monthDay = internal::calcStartDayOfMonth(
1314  yearTiny + LocalDate::kEpochYear, rule.inMonth(), rule.onDayOfWeek(),
1315  rule.onDayOfMonth());
1316  return {yearTiny, monthDay.month, monthDay.day,
1317  (int16_t) rule.atTimeMinutes(), rule.atTimeSuffix()};
1318  }
1319 
1330  static int8_t compareTransitionToMatchFuzzy(
1331  const Transition* t, const ZoneMatch* match) {
1332  int16_t ttMonths = t->transitionTime.yearTiny * 12
1333  + t->transitionTime.month;
1334 
1335  int16_t matchStartMonths = match->startDateTime.yearTiny * 12
1336  + match->startDateTime.month;
1337  if (ttMonths < matchStartMonths - 1) return -1;
1338 
1339  int16_t matchUntilMonths = match->untilDateTime.yearTiny * 12
1340  + match->untilDateTime.month;
1341  if (matchUntilMonths + 2 <= ttMonths) return 2;
1342 
1343  return 1;
1344  }
1345 
1347  static void setAsPriorTransition(
1348  TransitionStorage& transitionStorage,
1349  Transition* t) {
1350  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1351  logging::printf("setAsPriorTransition()\n");
1352  }
1353  Transition* prior = transitionStorage.getPrior();
1354  if (prior->active) {
1355  if (prior->transitionTime < t->transitionTime) {
1356  t->active = true;
1357  transitionStorage.setFreeAgentAsPrior();
1358  }
1359  } else {
1360  t->active = true;
1361  transitionStorage.setFreeAgentAsPrior();
1362  }
1363  }
1364 
1373  static void fixTransitionTimes(Transition** begin, Transition** end) {
1374  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1375  logging::printf("fixTransitionTimes(): #transitions: %d;\n",
1376  (int) (end - begin));
1377  }
1378 
1379  // extend first Transition to -infinity
1380  Transition* prev = *begin;
1381 
1382  for (Transition** iter = begin; iter != end; ++iter) {
1383  Transition* curr = *iter;
1384  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1385  logging::printf("fixTransitionTimes(): LOOP\n");
1386  curr->log();
1387  logging::printf("\n");
1388  }
1389  expandDateTuple(&curr->transitionTime,
1390  &curr->transitionTimeS, &curr->transitionTimeU,
1391  prev->offsetMinutes, prev->deltaMinutes);
1392  prev = curr;
1393  }
1394  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1395  logging::printf("fixTransitionTimes(): END\n");
1396  }
1397  }
1398 
1404  static void expandDateTuple(extended::DateTuple* tt,
1405  extended::DateTuple* tts, extended::DateTuple* ttu,
1406  int16_t offsetMinutes, int16_t deltaMinutes) {
1407  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1408  logging::printf("expandDateTuple()\n");
1409  }
1410  if (tt->suffix == internal::ZoneContext::kSuffixS) {
1411  *tts = *tt;
1412  *ttu = {tt->yearTiny, tt->month, tt->day,
1413  (int16_t) (tt->minutes - offsetMinutes),
1415  *tt = {tt->yearTiny, tt->month, tt->day,
1416  (int16_t) (tt->minutes + deltaMinutes),
1418  } else if (tt->suffix == internal::ZoneContext::kSuffixU) {
1419  *ttu = *tt;
1420  *tts = {tt->yearTiny, tt->month, tt->day,
1421  (int16_t) (tt->minutes + offsetMinutes),
1423  *tt = {tt->yearTiny, tt->month, tt->day,
1424  (int16_t) (tt->minutes + (offsetMinutes + deltaMinutes)),
1426  } else {
1427  // Explicit set the suffix to 'w' in case it was something else.
1428  tt->suffix = internal::ZoneContext::kSuffixW;
1429  *tts = {tt->yearTiny, tt->month, tt->day,
1430  (int16_t) (tt->minutes - deltaMinutes),
1432  *ttu = {tt->yearTiny, tt->month, tt->day,
1433  (int16_t) (tt->minutes - (deltaMinutes + offsetMinutes)),
1435  }
1436 
1437  normalizeDateTuple(tt);
1438  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1439  logging::printf("expandDateTuple(): normalizeDateTuple(tt): ");
1440  tt->log();
1441  logging::printf("\n");
1442  }
1443 
1444  normalizeDateTuple(tts);
1445  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1446  logging::printf("expandDateTuple(): normalizeDateTuple(tts): ");
1447  tts->log();
1448  logging::printf("\n");
1449  }
1450 
1451  normalizeDateTuple(ttu);
1452  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1453  logging::printf("expandDateTuple(): normalizeDateTuple(ttu): ");
1454  ttu->log();
1455  logging::printf("\n");
1456  }
1457  }
1458 
1463  static void normalizeDateTuple(extended::DateTuple* dt) {
1464  const int16_t kOneDayAsMinutes = 60 * 24;
1465  if (dt->minutes <= -kOneDayAsMinutes) {
1466  LocalDate ld = LocalDate::forTinyComponents(
1467  dt->yearTiny, dt->month, dt->day);
1468  local_date_mutation::decrementOneDay(ld);
1469  dt->yearTiny = ld.yearTiny();
1470  dt->month = ld.month();
1471  dt->day = ld.day();
1472  dt->minutes += kOneDayAsMinutes;
1473  } else if (kOneDayAsMinutes <= dt->minutes) {
1474  LocalDate ld = LocalDate::forTinyComponents(
1475  dt->yearTiny, dt->month, dt->day);
1476  local_date_mutation::incrementOneDay(ld);
1477  dt->yearTiny = ld.yearTiny();
1478  dt->month = ld.month();
1479  dt->day = ld.day();
1480  dt->minutes -= kOneDayAsMinutes;
1481  } else {
1482  // do nothing
1483  }
1484  }
1485 
1490  static void selectActiveTransitions(
1491  TransitionStorage& transitionStorage,
1492  const ZoneMatch* match) {
1493  Transition** begin = transitionStorage.getCandidatePoolBegin();
1494  Transition** end = transitionStorage.getCandidatePoolEnd();
1495  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1496  logging::printf("selectActiveTransitions(): #candidates: %d\n",
1497  (int) (end - begin));
1498  }
1499  Transition* prior = nullptr;
1500  for (Transition** iter = begin; iter != end; ++iter) {
1501  Transition* transition = *iter;
1502  processActiveTransition(match, transition, &prior);
1503  }
1504 
1505  // If the latest prior transition is found, shift it to start at the
1506  // startDateTime of the current match.
1507  if (prior) {
1508  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1509  logging::printf(
1510  "selectActiveTransitions(): found latest prior\n");
1511  }
1512  prior->originalTransitionTime = prior->transitionTime;
1513  prior->transitionTime = match->startDateTime;
1514  }
1515  }
1516 
1524  static void processActiveTransition(
1525  const ZoneMatch* match,
1526  Transition* transition,
1527  Transition** prior) {
1528  int8_t status = compareTransitionToMatch(transition, match);
1529  if (status == 2) {
1530  transition->active = false;
1531  } else if (status == 1) {
1532  transition->active = true;
1533  } else if (status == 0) {
1534  if (*prior) {
1535  (*prior)->active = false;
1536  }
1537  transition->active = true;
1538  (*prior) = transition;
1539  } else { // (status < 0)
1540  if (*prior) {
1541  if ((*prior)->transitionTime < transition->transitionTime) {
1542  (*prior)->active = false;
1543  transition->active = true;
1544  (*prior) = transition;
1545  }
1546  } else {
1547  transition->active = true;
1548  (*prior) = transition;
1549  }
1550  }
1551  }
1552 
1567  static int8_t compareTransitionToMatch(
1568  const Transition* transition,
1569  const ZoneMatch* match) {
1570  const extended::DateTuple* transitionTime;
1571 
1572  const extended::DateTuple& matchStart = match->startDateTime;
1573  if (matchStart.suffix == internal::ZoneContext::kSuffixS) {
1574  transitionTime = &transition->transitionTimeS;
1575  } else if (matchStart.suffix == internal::ZoneContext::kSuffixU) {
1576  transitionTime = &transition->transitionTimeU;
1577  } else { // assume 'w'
1578  transitionTime = &transition->transitionTime;
1579  }
1580  if (*transitionTime < matchStart) return -1;
1581  if (*transitionTime == matchStart) return 0;
1582 
1583  const extended::DateTuple& matchUntil = match->untilDateTime;
1584  if (matchUntil.suffix == internal::ZoneContext::kSuffixS) {
1585  transitionTime = &transition->transitionTimeS;
1586  } else if (matchUntil.suffix ==
1588  transitionTime = &transition->transitionTimeU;
1589  } else { // assume 'w'
1590  transitionTime = &transition->transitionTime;
1591  }
1592  if (*transitionTime < matchUntil) return 1;
1593  return 2;
1594  }
1595 
1601  static void generateStartUntilTimes(Transition** begin, Transition** end) {
1602  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1603  logging::printf(
1604  "generateStartUntilTimes(): #transitions: %d;\n",
1605  (int) (end - begin));
1606  }
1607 
1608  Transition* prev = *begin;
1609  bool isAfterFirst = false;
1610 
1611  for (Transition** iter = begin; iter != end; ++iter) {
1612  Transition* const t = *iter;
1613 
1614  // 1) Update the untilDateTime of the previous Transition
1615  const extended::DateTuple& tt = t->transitionTime;
1616  if (isAfterFirst) {
1617  prev->untilDateTime = tt;
1618  }
1619 
1620  // 2) Calculate the current startDateTime by shifting the
1621  // transitionTime (represented in the UTC offset of the previous
1622  // transition) into the UTC offset of the *current* transition.
1623  int16_t minutes = tt.minutes + (
1624  - prev->offsetMinutes - prev->deltaMinutes
1625  + t->offsetMinutes + t->deltaMinutes);
1626  t->startDateTime = {tt.yearTiny, tt.month, tt.day, minutes,
1627  tt.suffix};
1628  normalizeDateTuple(&t->startDateTime);
1629 
1630  // 3) The epochSecond of the 'transitionTime' is determined by the
1631  // UTC offset of the *previous* Transition. However, the
1632  // transitionTime can be represented by an illegal time (e.g. 24:00).
1633  // So, it is better to use the properly normalized startDateTime
1634  // (calculated above) with the *current* UTC offset.
1635  //
1636  // NOTE: We should also be able to calculate this directly from
1637  // 'transitionTimeU' which should still be a valid field, because it
1638  // hasn't been clobbered by 'untilDateTime' yet. Not sure if this saves
1639  // any CPU time though, since we still need to mutiply by 900.
1640  const extended::DateTuple& st = t->startDateTime;
1641  const acetime_t offsetSeconds = (acetime_t) 60
1642  * (st.minutes - (t->offsetMinutes + t->deltaMinutes));
1643  LocalDate ld = LocalDate::forTinyComponents(
1644  st.yearTiny, st.month, st.day);
1645  t->startEpochSeconds = ld.toEpochSeconds() + offsetSeconds;
1646 
1647  prev = t;
1648  isAfterFirst = true;
1649  }
1650 
1651  // The last Transition's until time is the until time of the ZoneMatch.
1652  extended::DateTuple untilTime = prev->match->untilDateTime;
1653  extended::DateTuple untilTimeS; // needed only for expandDateTuple
1654  extended::DateTuple untilTimeU; // needed only for expandDateTuple
1655  expandDateTuple(&untilTime, &untilTimeS, &untilTimeU,
1656  prev->offsetMinutes, prev->deltaMinutes);
1657  prev->untilDateTime = untilTime;
1658  }
1659 
1663  static void calcAbbreviations(Transition** begin, Transition** end) {
1664  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1665  logging::printf("calcAbbreviations(): #transitions: %d;\n",
1666  (int) (end - begin));
1667  }
1668  for (Transition** iter = begin; iter != end; ++iter) {
1669  Transition* const t = *iter;
1670  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1671  logging::printf(
1672  "calcAbbreviations(): format:%s, deltaMinutes:%d, letter:%s\n",
1673  t->format(), t->deltaMinutes, t->letter());
1674  }
1675  createAbbreviation(t->abbrev, internal::kAbbrevSize,
1676  t->format(), t->deltaMinutes, t->letter());
1677  }
1678  }
1679 
1688  static void createAbbreviation(char* dest, uint8_t destSize,
1689  const char* format, uint16_t deltaMinutes, const char* letterString) {
1690  // Check if FORMAT contains a '%'.
1691  if (strchr(format, '%') != nullptr) {
1692  // Check if RULES column empty, therefore no 'letter'
1693  if (letterString == nullptr) {
1694  strncpy(dest, format, destSize - 1);
1695  dest[destSize - 1] = '\0';
1696  } else {
1697  extended::copyAndReplace(dest, destSize, format, '%', letterString);
1698  }
1699  } else {
1700  // Check if FORMAT contains a '/'.
1701  const char* slashPos = strchr(format, '/');
1702  if (slashPos != nullptr) {
1703  if (deltaMinutes == 0) {
1704  uint8_t headLength = (slashPos - format);
1705  if (headLength >= destSize) headLength = destSize - 1;
1706  memcpy(dest, format, headLength);
1707  dest[headLength] = '\0';
1708  } else {
1709  uint8_t tailLength = strlen(slashPos+1);
1710  if (tailLength >= destSize) tailLength = destSize - 1;
1711  memcpy(dest, slashPos+1, tailLength);
1712  dest[tailLength] = '\0';
1713  }
1714  } else {
1715  // Just copy the FORMAT disregarding deltaMinutes and letterString.
1716  strncpy(dest, format, destSize);
1717  dest[destSize - 1] = '\0';
1718  }
1719  }
1720  }
1721 
1722  const BF* mBrokerFactory;
1723  ZIB mZoneInfoBroker;
1724 
1725  mutable int16_t mYear = 0; // maybe create LocalDate::kInvalidYear?
1726  mutable bool mIsFilled = false;
1727  // NOTE: Maybe move mNumMatches and mMatches into a MatchStorage object.
1728  mutable uint8_t mNumMatches = 0; // actual number of matches
1729  mutable ZoneMatch mMatches[kMaxMatches];
1730  mutable TransitionStorage mTransitionStorage;
1731 };
1732 
1733 
1739  extended::BrokerFactory,
1740  extended::ZoneInfoBroker,
1741  extended::ZoneEraBroker,
1742  extended::ZonePolicyBroker,
1743  extended::ZoneRuleBroker> {
1744 
1745  public:
1747  static const uint8_t kTypeExtended = 4;
1748 
1749  explicit ExtendedZoneProcessor(const extended::ZoneInfo* zoneInfo = nullptr)
1751  extended::BrokerFactory,
1752  extended::ZoneInfoBroker,
1753  extended::ZoneEraBroker,
1754  extended::ZonePolicyBroker,
1755  extended::ZoneRuleBroker>(
1756  kTypeExtended, &mBrokerFactory, (uintptr_t) zoneInfo)
1757  {}
1758 
1759  private:
1760  extended::BrokerFactory mBrokerFactory;
1761 };
1762 
1763 } // namespace ace_time
1764 
1765 #endif
ace_time::extended::TransitionTemplate::untilDateTime
DateTuple untilDateTime
Until time expressed using the UTC offset of the current Transition.
Definition: ExtendedZoneProcessor.h:226
ace_time::extended::TransitionStorageTemplate::init
void init()
Initialize all pools.
Definition: ExtendedZoneProcessor.h:378
ace_time::extended::TransitionStorageTemplate::getPrior
Transition * getPrior()
Return the current prior transition.
Definition: ExtendedZoneProcessor.h:388
ace_time::extended::TransitionStorageTemplate::reservePrior
Transition ** reservePrior()
Allocate one Transition just after the Active pool, but before the Candidate pool,...
Definition: ExtendedZoneProcessor.h:466
ace_time::extended::TransitionTemplate::letterBuf
char letterBuf[2]
Storage for the single letter 'letter' field if 'rule' is not null.
Definition: ExtendedZoneProcessor.h:254
ace_time::LocalDateTime::hour
uint8_t hour() const
Return the hour.
Definition: LocalDateTime.h:201
ace_time::LocalDateTime
Class that holds the date-time as the components (year, month, day, hour, minute, second) without reg...
Definition: LocalDateTime.h:30
ace_time::TimeOffset::forError
static TimeOffset forError()
Return an error indicator.
Definition: TimeOffset.h:105
ace_time::extended::TransitionTemplate::rule
ZRB rule
The Zone transition rule that matched for the the given year.
Definition: ExtendedZoneProcessor.h:189
ace_time::TimeOffset::isError
bool isError() const
Return true if this TimeOffset represents an error.
Definition: TimeOffset.h:138
ace_time::extended::TransitionStorageTemplate::addPriorToCandidatePool
void addPriorToCandidatePool()
Add the current prior into the Candidates pool.
Definition: ExtendedZoneProcessor.h:482
ace_time::ExtendedZoneProcessorTemplate::getTransitionHighWater
uint8_t getTransitionHighWater() const
Get the TransitionStorage high water mark.
Definition: ExtendedZoneProcessor.h:850
ace_time::extended::ZoneMatchTemplate::startDateTime
DateTuple startDateTime
The effective start time of the matching ZoneEra.
Definition: ExtendedZoneProcessor.h:133
ace_time::extended::TransitionTemplate::offsetMinutes
int16_t offsetMinutes
The base offset minutes, not the total effective UTC offset.
Definition: ExtendedZoneProcessor.h:245
ace_time::extended::TransitionStorageTemplate::getFreeAgent
Transition * getFreeAgent()
Return a pointer to the first Transition in the free pool.
Definition: ExtendedZoneProcessor.h:433
ace_time::extended::TransitionStorageTemplate::TransitionStorageTemplate
TransitionStorageTemplate()
Constructor.
Definition: ExtendedZoneProcessor.h:375
ace_time::extended::TransitionTemplate::match
const ZoneMatchTemplate< ZEB > * match
The match which generated this Transition.
Definition: ExtendedZoneProcessor.h:182
ace_time::extended::TransitionTemplate::letter
const char * letter() const
Return the letter string.
Definition: ExtendedZoneProcessor.h:281
ace_time::extended::TransitionStorageTemplate::setFreeAgentAsPrior
void setFreeAgentAsPrior()
Swap the Free agrent transition with the current Prior transition.
Definition: ExtendedZoneProcessor.h:473
ace_time::LocalDateTime::printTo
void printTo(Print &printer) const
Print LocalDateTime to 'printer' in ISO 8601 format.
Definition: LocalDateTime.cpp:14
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::TransitionStorageTemplate::getHighWater
uint8_t getHighWater() const
Return the high water mark.
Definition: ExtendedZoneProcessor.h:629
ace_time::OffsetDateTime::forEpochSeconds
static OffsetDateTime forEpochSeconds(acetime_t epochSeconds, TimeOffset timeOffset)
Factory method.
Definition: OffsetDateTime.h:71
ace_time::internal::ZoneContext::kSuffixS
static const uint8_t kSuffixS
Represents 's' or standard time.
Definition: ZoneContext.h:21
ace_time::ExtendedZoneProcessorTemplate::getZoneId
uint32_t getZoneId() const override
Return the unique stable zoneId.
Definition: ExtendedZoneProcessor.h:739
ace_time::extended::ZoneMatchTemplate::era
ZEB era
The ZoneEra that matched the given year.
Definition: ExtendedZoneProcessor.h:139
ace_time::ExtendedZoneProcessor::kTypeExtended
static const uint8_t kTypeExtended
Unique TimeZone type identifier for BasicZoneProcessor.
Definition: ExtendedZoneProcessor.h:1747
ace_time::extended::TransitionTemplate::transitionTimeU
DateTuple transitionTimeU
Version of transitionTime in 'u' mode, using the UTC offset of the previous transition.
Definition: ExtendedZoneProcessor.h:220
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
A specific implementation of ExtendedZoneProcessorTemplate that uses ZoneXxxBrokers which read from z...
Definition: ExtendedZoneProcessor.h:1738
ace_time::extended::TransitionStorageTemplate::resetCandidatePool
void resetCandidatePool()
Empty the Candidate pool by resetting the various indexes.
Definition: ExtendedZoneProcessor.h:409
ace_time::ExtendedZoneProcessorTemplate::TransitionStorage
extended::TransitionStorageTemplate< kMaxTransitions, ZEB, ZPB, ZRB > TransitionStorage
Exposed only for testing purposes.
Definition: ExtendedZoneProcessor.h:737
ace_time::ExtendedZoneProcessorTemplate::getAbbrev
const char * getAbbrev(acetime_t epochSeconds) const override
Return the time zone abbreviation at epochSeconds.
Definition: ExtendedZoneProcessor.h:758
ace_time::LocalDateTime::month
uint8_t month() const
Return the month with January=1, December=12.
Definition: LocalDateTime.h:189
ace_time::OffsetDateTime
The date (year, month, day), time (hour, minute, second) and offset from UTC (timeOffset).
Definition: OffsetDateTime.h:33
ace_time::ExtendedZoneProcessorTemplate::ExtendedZoneProcessorTemplate
ExtendedZoneProcessorTemplate(uint8_t type, const BF *brokerFactory, uintptr_t zoneKey)
Constructor.
Definition: ExtendedZoneProcessor.h:880
compat.h
ace_time::ZoneProcessor
Base interface for ZoneProcessor classes.
Definition: ZoneProcessor.h:41
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::ExtendedZoneProcessorTemplate::getUtcOffset
TimeOffset getUtcOffset(acetime_t epochSeconds) const override
Return the total UTC offset at epochSeconds, including DST offset.
Definition: ExtendedZoneProcessor.h:741
ace_time::ExtendedZoneProcessorTemplate::printShortNameTo
void printShortNameTo(Print &printer) const override
Print a short human-readable identifier (e.g.
Definition: ExtendedZoneProcessor.h:827
ace_time::extended::TransitionTemplate::abbrev
char abbrev[internal::kAbbrevSize]
The calculated effective time zone abbreviation, e.g.
Definition: ExtendedZoneProcessor.h:251
ace_time::extended::TransitionStorageTemplate::findTransitionForDateTime
const Transition * findTransitionForDateTime(const LocalDateTime &ldt) const
Return the Transition matching the given dateTime.
Definition: ExtendedZoneProcessor.h:572
ace_time::extended::TransitionTemplate::startDateTime
DateTuple startDateTime
Start time expressed using the UTC offset of the current Transition.
Definition: ExtendedZoneProcessor.h:211
ace_time::extended::DateTuple
A tuple that represents a date and time.
Definition: ExtendedZoneProcessor.h:62
ace_time::ExtendedZoneProcessorTemplate::kMaxTransitions
static const uint8_t kMaxTransitions
Max number of Transitions required for a given Zone, including the most recent prior Transition.
Definition: ExtendedZoneProcessor.h:727
ace_time::ExtendedZoneProcessorTemplate::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:765
ace_time::LocalDateTime::minute
uint8_t minute() const
Return the minute.
Definition: LocalDateTime.h:207
ace_time::extended::BrokerFactory
A factory that creates a basic::ZoneInfoBroker.
Definition: ExtendedBrokers.h:478
ace_time::LocalDateTime::localDate
const LocalDate & localDate() const
Return the LocalDate.
Definition: LocalDateTime.h:222
ace_time::TimeOffset::forMinutes
static TimeOffset forMinutes(int16_t minutes)
Create TimeOffset from minutes from 00:00.
Definition: TimeOffset.h:83
ace_time::extended::ZoneMatchTemplate::untilDateTime
DateTuple untilDateTime
The effective until time of the matching ZoneEra.
Definition: ExtendedZoneProcessor.h:136
ace_time::extended::TransitionStorageTemplate
A heap manager which is specialized and tuned to manage a collection of Transitions,...
Definition: ExtendedZoneProcessor.h:366
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::TransitionTemplate::active
bool active
Flag used for 2 slightly different meanings at different stages of init() processing.
Definition: ExtendedZoneProcessor.h:268
ace_time::LocalDate::kEpochYear
static const int16_t kEpochYear
Base year of epoch.
Definition: LocalDate.h:39
ace_time::extended::TransitionTemplate::deltaMinutes
int16_t deltaMinutes
The DST delta minutes.
Definition: ExtendedZoneProcessor.h:248
ace_time::extended::TransitionTemplate::transitionTimeS
DateTuple transitionTimeS
Version of transitionTime in 's' mode, using the UTC offset of the previous Transition.
Definition: ExtendedZoneProcessor.h:205
ace_time::LocalDateTime::yearTiny
int8_t yearTiny() const
Return the single-byte year offset from year 2000.
Definition: LocalDateTime.h:179
ace_time::extended::TransitionStorageTemplate::swap
void swap(Transition **a, Transition **b)
Swap 2 transitions.
Definition: ExtendedZoneProcessor.h:393
ExtendedBrokers.h
ace_time::ExtendedZoneProcessorTemplate::ZoneMatch
extended::ZoneMatchTemplate< ZEB > ZoneMatch
Exposed only for testing purposes.
Definition: ExtendedZoneProcessor.h:733
ace_time::internal::ZoneContext::kSuffixU
static const uint8_t kSuffixU
Represents 'u' or UTC time.
Definition: ZoneContext.h:24
ace_time::ExtendedZoneProcessorTemplate::setZoneKey
void setZoneKey(uintptr_t zoneKey) override
Set the opaque zoneKey of this object to a new value, reseting any internally cached information.
Definition: ExtendedZoneProcessor.h:854
ace_time::extended::TransitionTemplate::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:233
ace_time::ExtendedZoneProcessorTemplate::equalsZoneKey
bool equalsZoneKey(uintptr_t zoneKey) const override
Return true if ZoneProcessor is associated with the given opaque zoneKey.
Definition: ExtendedZoneProcessor.h:864
ace_time::extended::TransitionStorageTemplate::resetHighWater
void resetHighWater()
Reset the high water mark.
Definition: ExtendedZoneProcessor.h:622
ace_time::extended::TransitionStorageTemplate::Transition
TransitionTemplate< ZEB, ZPB, ZRB > Transition
Template instantiation of TransitionTemplate used by this class.
Definition: ExtendedZoneProcessor.h:372
ace_time::extended::TransitionTemplate::startEpochSeconds
acetime_t startEpochSeconds
The calculated transition time of the given rule.
Definition: ExtendedZoneProcessor.h:236
ace_time::ExtendedZoneProcessorTemplate::log
void log() const
Used only for debugging.
Definition: ExtendedZoneProcessor.h:832
ace_time::extended::TransitionStorageTemplate::addFreeAgentToActivePool
void addFreeAgentToActivePool()
Immediately add the free agent Transition at index mIndexFree to the Active pool.
Definition: ExtendedZoneProcessor.h:454
ace_time::LocalDateTime::day
uint8_t day() const
Return the day of the month.
Definition: LocalDateTime.h:195
ace_time::extended::TransitionTemplate
Represents an interval of time where the time zone obeyed a certain UTC offset and DST delta.
Definition: ExtendedZoneProcessor.h:180
ace_time::ExtendedZoneProcessorTemplate::printNameTo
void printNameTo(Print &printer) const override
Print a human-readable identifier (e.g.
Definition: ExtendedZoneProcessor.h:823
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:119
ace_time::ExtendedZoneProcessorTemplate::getDeltaOffset
TimeOffset getDeltaOffset(acetime_t epochSeconds) const override
Return the DST delta offset at epochSeconds.
Definition: ExtendedZoneProcessor.h:751
ace_time::ExtendedZoneProcessorTemplate::resetTransitionHighWater
void resetTransitionHighWater()
Reset the TransitionStorage high water mark.
Definition: ExtendedZoneProcessor.h:845
ace_time::extended::DateTuple::log
void log() const
Used only for debugging.
Definition: ExtendedZoneProcessor.h:75
ace_time::extended::ZoneMatchTemplate
Data structure that captures the matching ZoneEra and its ZoneRule transitions for a given year.
Definition: ExtendedZoneProcessor.h:131
ace_time::extended::TransitionTemplate::transitionTime
DateTuple transitionTime
The original transition time, usually 'w' but sometimes 's' or 'u'.
Definition: ExtendedZoneProcessor.h:197
ace_time::internal::ZoneContext::kSuffixW
static const uint8_t kSuffixW
Represents 'w' or wall time.
Definition: ZoneContext.h:18
ace_time::ExtendedZoneProcessorTemplate::Transition
extended::TransitionTemplate< ZEB, ZPB, ZRB > Transition
Exposed only for testing purposes.
Definition: ExtendedZoneProcessor.h:730
ace_time::extended::TransitionStorageTemplate::addFreeAgentToCandidatePool
void addFreeAgentToCandidatePool()
Add the free agent Transition at index mIndexFree to the Candidate pool, sorted by transitionTime.
Definition: ExtendedZoneProcessor.h:492
ace_time::extended::TransitionStorageTemplate::findTransition
const Transition * findTransition(acetime_t epochSeconds) const
Return the Transition matching the given epochSeconds.
Definition: ExtendedZoneProcessor.h:535
ace_time::extended::TransitionTemplate::log
void log() const
Used only for debugging.
Definition: ExtendedZoneProcessor.h:311
ace_time::ExtendedZoneProcessorTemplate
An implementation of ZoneProcessor that supports for all zones defined by the TZ Database.
Definition: ExtendedZoneProcessor.h:717
ace_time::extended::TransitionStorageTemplate::addActiveCandidatesToActivePool
void addActiveCandidatesToActivePool()
Add active candidates into the Active pool, and collapse the Candidate pool.
Definition: ExtendedZoneProcessor.h:508
ace_time::extended::TransitionStorageTemplate::log
void log() const
Verify that the indexes are valid.
Definition: ExtendedZoneProcessor.h:595