AceTime  1.7.1
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 <AceCommon.h> // copyReplaceString()
12 #include "common/compat.h"
13 #include "internal/ZonePolicy.h"
14 #include "internal/ZoneInfo.h"
16 #include "common/logging.h"
17 #include "TimeOffset.h"
18 #include "LocalDate.h"
19 #include "OffsetDateTime.h"
20 #include "ZoneProcessor.h"
21 #include "local_date_mutation.h"
22 
23 #define ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG 0
24 
25 class ExtendedZoneProcessorTest_compareEraToYearMonth;
26 class ExtendedZoneProcessorTest_compareEraToYearMonth2;
27 class ExtendedZoneProcessorTest_createMatch;
28 class ExtendedZoneProcessorTest_findMatches_simple;
29 class ExtendedZoneProcessorTest_findMatches_named;
30 class ExtendedZoneProcessorTest_findCandidateTransitions;
31 class ExtendedZoneProcessorTest_findTransitionsFromNamedMatch;
32 class ExtendedZoneProcessorTest_getTransitionTime;
33 class ExtendedZoneProcessorTest_createTransitionForYear;
34 class ExtendedZoneProcessorTest_normalizeDateTuple;
35 class ExtendedZoneProcessorTest_expandDateTuple;
36 class ExtendedZoneProcessorTest_calcInteriorYears;
37 class ExtendedZoneProcessorTest_getMostRecentPriorYear;
38 class ExtendedZoneProcessorTest_compareTransitionToMatchFuzzy;
39 class ExtendedZoneProcessorTest_compareTransitionToMatch;
40 class ExtendedZoneProcessorTest_processActiveTransition;
41 class ExtendedZoneProcessorTest_fixTransitionTimes_generateStartUntilTimes;
42 class ExtendedZoneProcessorTest_createAbbreviation;
43 class ExtendedZoneProcessorTest_setZoneKey;
44 class TransitionStorageTest_getFreeAgent;
45 class TransitionStorageTest_getFreeAgent2;
46 class TransitionStorageTest_addFreeAgentToActivePool;
47 class TransitionStorageTest_reservePrior;
48 class TransitionStorageTest_addFreeAgentToCandidatePool;
49 class TransitionStorageTest_setFreeAgentAsPrior;
50 class TransitionStorageTest_addActiveCandidatesToActivePool;
51 class TransitionStorageTest_resetCandidatePool;
52 class TransitionStorageTest_findTransitionForDateTime;
53 
54 class Print;
55 
56 namespace ace_time {
57 namespace extended {
58 
63 struct DateTuple {
64  DateTuple() = default;
65 
66  DateTuple(int8_t y, uint8_t mon, uint8_t d, int16_t min, uint8_t mod):
67  yearTiny(y), month(mon), day(d), suffix(mod), minutes(min) {}
68 
69  int8_t yearTiny; // [-127, 126], 127 will cause bugs
70  uint8_t month; // [1-12]
71  uint8_t day; // [1-31]
72  uint8_t suffix; // kSuffixS, kSuffixW, kSuffixU
73  int16_t minutes; // negative values allowed
74 
76  void log() const {
77  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
78  char c = "wsu"[(suffix>>4)];
79  logging::printf("DateTuple(%04d-%02u-%02uT%d'%c')",
80  yearTiny+LocalDate::kEpochYear, month, day, minutes, c);
81  }
82  }
83 };
84 
86 inline bool operator<(const DateTuple& a, const DateTuple& b) {
87  if (a.yearTiny < b.yearTiny) return true;
88  if (a.yearTiny > b.yearTiny) return false;
89  if (a.month < b.month) return true;
90  if (a.month > b.month) return false;
91  if (a.day < b.day) return true;
92  if (a.day > b.day) return false;
93  if (a.minutes < b.minutes) return true;
94  if (a.minutes > b.minutes) return false;
95  return false;
96 }
97 
98 inline bool operator>=(const DateTuple& a, const DateTuple& b) {
99  return ! (a < b);
100 }
101 
102 inline bool operator<=(const DateTuple& a, const DateTuple& b) {
103  return ! (b < a);
104 }
105 
106 inline bool operator>(const DateTuple& a, const DateTuple& b) {
107  return (b < a);
108 }
109 
111 inline bool operator==(const DateTuple& a, const DateTuple& b) {
112  return a.yearTiny == b.yearTiny
113  && a.month == b.month
114  && a.day == b.day
115  && a.minutes == b.minutes
116  && a.suffix == b.suffix;
117 }
118 
121  int8_t yearTiny;
122  uint8_t month;
123 };
124 
131 template<typename ZEB>
135 
138 
140  ZEB era;
141 
142  void log() const {
143  logging::printf("ZoneMatch(");
144  logging::printf("Start:"); startDateTime.log();
145  logging::printf("; Until:"); untilDateTime.log();
146  logging::printf("; Era: %snull", (era.isNull()) ? "" : "!");
147  logging::printf(")");
148  }
149 };
150 
180 template <typename ZEB, typename ZPB, typename ZRB>
184 
190  ZRB rule;
191 
199 
200  union {
207 
213  };
214 
215  union {
222 
228  };
229 
235 
237  acetime_t startEpochSeconds;
238 
246  int16_t offsetMinutes;
247 
249  int16_t deltaMinutes;
250 
252  char abbrev[internal::kAbbrevSize];
253 
255  char letterBuf[2];
256 
269  bool active;
270 
271  //-------------------------------------------------------------------------
272 
273  const char* format() const {
274  return match->era.format();
275  }
276 
282  const char* letter() const {
283  // RULES column is '-' or hh:mm, so return nullptr to indicate this.
284  if (rule.isNull()) {
285  return nullptr;
286  }
287 
288  // RULES point to a named rule, and LETTER is a single, printable
289  // character.
290  char letter = rule.letter();
291  if (letter >= 32) {
292  return letterBuf;
293  }
294 
295  // RULES points to a named rule, and the LETTER is a string. The
296  // rule->letter is a non-printable number < 32, which is an index into
297  // a list of strings given by match->era->zonePolicy->letters[].
298  const ZPB policy = match->era.zonePolicy();
299  uint8_t numLetters = policy.numLetters();
300  if (letter >= numLetters) {
301  // This should never happen unless there is a programming error. If it
302  // does, return an empty string. (createTransitionForYear() sets
303  // letterBuf to a NUL terminated empty string if rule->letter < 32)
304  return letterBuf;
305  }
306 
307  // Return the string at index 'rule->letter'.
308  return policy.letter(letter);
309  }
310 
312  void log() const {
313  logging::printf("Transition(");
314  if (sizeof(acetime_t) <= sizeof(int)) {
315  logging::printf("sE: %d", startEpochSeconds);
316  } else {
317  logging::printf("sE: %ld", startEpochSeconds);
318  }
319  logging::printf("; match: %snull", (match) ? "!" : "");
320  logging::printf("; era: %snull",
321  (match && match->era.isNull()) ? "" : "!");
322  logging::printf("; oMinutes: %d", offsetMinutes);
323  logging::printf("; dMinutes: %d", deltaMinutes);
324  logging::printf("; tt: "); transitionTime.log();
325  if (! rule.isNull()) {
326  logging::printf("; R.fY: %d", rule.fromYearTiny());
327  logging::printf("; R.tY: %d", rule.toYearTiny());
328  logging::printf("; R.M: %d", rule.inMonth());
329  logging::printf("; R.dow: %d", rule.onDayOfWeek());
330  logging::printf("; R.dom: %d", rule.onDayOfMonth());
331  }
332  }
333 };
334 
366 template<uint8_t SIZE, typename ZEB, typename ZPB, typename ZRB>
368  public:
374 
377 
379  void init() {
380  for (uint8_t i = 0; i < SIZE; i++) {
381  mTransitions[i] = &mPool[i];
382  }
383  mIndexPrior = 0;
384  mIndexCandidates = 0;
385  mIndexFree = 0;
386  }
387 
390  return mTransitions[mIndexPrior];
391  }
392 
394  void swap(
395  Transition** a,
396  Transition** b) {
397  auto* tmp = *a;
398  *a = *b;
399  *b = tmp;
400  }
401 
411  mIndexCandidates = mIndexPrior;
412  mIndexFree = mIndexPrior;
413  }
414 
415  Transition** getCandidatePoolBegin() {
416  return &mTransitions[mIndexCandidates];
417  }
418  Transition** getCandidatePoolEnd() {
419  return &mTransitions[mIndexFree];
420  }
421 
422  Transition** getActivePoolBegin() {
423  return &mTransitions[0];
424  }
425  Transition** getActivePoolEnd() {
426  return &mTransitions[mIndexFree];
427  }
428 
435  // Set the internal high water mark. If that index becomes SIZE,
436  // then we know we have an overflow.
437  if (mIndexFree > mHighWater) {
438  mHighWater = mIndexFree;
439  }
440 
441  if (mIndexFree < SIZE) {
442  return mTransitions[mIndexFree];
443  } else {
444  return mTransitions[SIZE - 1];
445  }
446  }
447 
456  if (mIndexFree >= SIZE) return;
457  mIndexFree++;
458  mIndexPrior = mIndexFree;
459  mIndexCandidates = mIndexFree;
460  }
461 
468  mIndexCandidates++;
469  mIndexFree++;
470  return &mTransitions[mIndexPrior];
471  }
472 
475  swap(&mTransitions[mIndexPrior], &mTransitions[mIndexFree]);
476  }
477 
484  mIndexCandidates--;
485  }
486 
494  if (mIndexFree >= SIZE) return;
495  for (uint8_t i = mIndexFree; i > mIndexCandidates; i--) {
496  Transition* curr = mTransitions[i];
497  Transition* prev = mTransitions[i - 1];
498  if (curr->transitionTime >= prev->transitionTime) break;
499  mTransitions[i] = prev;
500  mTransitions[i - 1] = curr;
501  }
502  mIndexFree++;
503  }
504 
510  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
511  logging::printf("addActiveCandidatesToActivePool()\n");
512  }
513  uint8_t iActive = mIndexPrior;
514  uint8_t iCandidate = mIndexCandidates;
515  for (; iCandidate < mIndexFree; iCandidate++) {
516  if (mTransitions[iCandidate]->active) {
517  if (iActive != iCandidate) {
518  swap(&mTransitions[iActive], &mTransitions[iCandidate]);
519  }
520  ++iActive;
521  }
522  }
523  mIndexPrior = iActive;
524  mIndexCandidates = iActive;
525  mIndexFree = iActive;
526  }
527 
536  const Transition* findTransition(acetime_t epochSeconds) const {
537  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
538  logging::printf( "findTransition(): mIndexFree: %d\n", mIndexFree);
539  }
540 
541  const Transition* match = nullptr;
542  for (uint8_t i = 0; i < mIndexFree; i++) {
543  const Transition* candidate = mTransitions[i];
544  if (candidate->startEpochSeconds > epochSeconds) break;
545  match = candidate;
546  }
547  return match;
548  }
549 
574  const {
575  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
576  logging::printf(
577  "findTransitionForDateTime(): mIndexFree: %d\n", mIndexFree);
578  }
579 
580  // Convert LocalDateTime to DateTuple.
581  DateTuple localDate = { ldt.yearTiny(), ldt.month(), ldt.day(),
582  (int16_t) (ldt.hour() * 60 + ldt.minute()),
584  const Transition* match = nullptr;
585 
586  // Find the last Transition that matches
587  for (uint8_t i = 0; i < mIndexFree; i++) {
588  const Transition* candidate = mTransitions[i];
589  if (candidate->startDateTime > localDate) break;
590  match = candidate;
591  }
592  return match;
593  }
594 
596  void log() const {
597  logging::printf("TransitionStorage:\n");
598  logging::printf(" mIndexPrior: %d\n", mIndexPrior);
599  logging::printf(" mIndexCandidates: %d\n", mIndexCandidates);
600  logging::printf(" mIndexFree: %d\n", mIndexFree);
601  if (mIndexPrior != 0) {
602  logging::printf(" Actives:\n");
603  for (uint8_t i = 0; i < mIndexPrior; i++) {
604  mTransitions[i]->log();
605  logging::printf("\n");
606  }
607  }
608  if (mIndexPrior != mIndexCandidates) {
609  logging::printf(" Prior: ");
610  mTransitions[mIndexPrior]->log();
611  logging::printf("\n");
612  }
613  if (mIndexCandidates != mIndexFree) {
614  logging::printf(" Candidates:\n");
615  for (uint8_t i = mIndexCandidates; i < mIndexFree; i++) {
616  mTransitions[i]->log();
617  logging::printf("\n");
618  }
619  }
620  }
621 
623  void resetHighWater() { mHighWater = 0; }
624 
630  uint8_t getHighWater() const { return mHighWater; }
631 
632  private:
633  friend class ::TransitionStorageTest_getFreeAgent;
634  friend class ::TransitionStorageTest_getFreeAgent2;
635  friend class ::TransitionStorageTest_addFreeAgentToActivePool;
636  friend class ::TransitionStorageTest_reservePrior;
637  friend class ::TransitionStorageTest_addFreeAgentToCandidatePool;
638  friend class ::TransitionStorageTest_setFreeAgentAsPrior;
639  friend class ::TransitionStorageTest_addActiveCandidatesToActivePool;
640  friend class ::TransitionStorageTest_findTransitionForDateTime;
641  friend class ::TransitionStorageTest_resetCandidatePool;
642 
644  Transition* getTransition(uint8_t i) {
645  return mTransitions[i];
646  }
647 
648  Transition mPool[SIZE];
649  Transition* mTransitions[SIZE];
650  uint8_t mIndexPrior;
651  uint8_t mIndexCandidates;
652  uint8_t mIndexFree;
653 
655  uint8_t mHighWater = 0;
656 };
657 
658 } // namespace extended
659 
691 template <typename BF, typename ZIB, typename ZEB, typename ZPB, typename ZRB>
693  public:
702  static const uint8_t kMaxTransitions = 8;
703 
706 
709 
713 
714  uint32_t getZoneId() const override { return mZoneInfoBroker.zoneId(); }
715 
716  TimeOffset getUtcOffset(acetime_t epochSeconds) const override {
717  bool success = init(epochSeconds);
718  if (!success) return TimeOffset::forError();
719  const Transition* transition = findTransition(epochSeconds);
720  return (transition)
722  transition->offsetMinutes + transition->deltaMinutes)
724  }
725 
726  TimeOffset getDeltaOffset(acetime_t epochSeconds) const override {
727  bool success = init(epochSeconds);
728  if (!success) return TimeOffset::forError();
729  const Transition* transition = findTransition(epochSeconds);
730  return TimeOffset::forMinutes(transition->deltaMinutes);
731  }
732 
733  const char* getAbbrev(acetime_t epochSeconds) const override {
734  bool success = init(epochSeconds);
735  if (!success) return "";
736  const Transition* transition = findTransition(epochSeconds);
737  return transition->abbrev;
738  }
739 
740  OffsetDateTime getOffsetDateTime(const LocalDateTime& ldt) const override {
741  TimeOffset offset;
742  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
743  logging::printf("getOffsetDateTime(): ldt=");
744  ldt.printTo(SERIAL_PORT_MONITOR);
745  SERIAL_PORT_MONITOR.println();
746  }
747  bool success = init(ldt.localDate());
748 
749  // Find the Transition to get the DST offset
750  if (success) {
751  const Transition* transition =
752  mTransitionStorage.findTransitionForDateTime(ldt);
753  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
754  logging::printf("getOffsetDateTime(): match transition=");
755  transition->log();
756  logging::printf("\n");
757  }
758  offset = (transition)
760  transition->offsetMinutes + transition->deltaMinutes)
762  } else {
763  offset = TimeOffset::forError();
764  }
765 
766  auto odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset);
767  if (offset.isError()) {
768  return odt;
769  }
770  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
771  logging::printf("getOffsetDateTime(): odt=");
772  odt.printTo(SERIAL_PORT_MONITOR);
773  SERIAL_PORT_MONITOR.println();
774  }
775 
776  // Normalize the OffsetDateTime, causing LocalDateTime in the DST
777  // transtion gap to be shifted forward one hour. For LocalDateTime in an
778  // overlap (DST->STD transition), the earlier UTC offset is selected// by
779  // findTransitionForDateTime(). Use that to calculate the epochSeconds,
780  // then recalculate the offset. Use this final offset to determine the
781  // effective OffsetDateTime that will survive a round-trip unchanged.
782  acetime_t epochSeconds = odt.toEpochSeconds();
783  const Transition* transition =
784  mTransitionStorage.findTransition(epochSeconds);
785  offset = (transition)
787  transition->offsetMinutes + transition->deltaMinutes)
789  odt = OffsetDateTime::forEpochSeconds(epochSeconds, offset);
790  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
791  logging::printf("getOffsetDateTime(): normalized(odt)=");
792  odt.printTo(SERIAL_PORT_MONITOR);
793  SERIAL_PORT_MONITOR.println();
794  }
795  return odt;
796  }
797 
798  void printNameTo(Print& printer) const override {
799  mZoneInfoBroker.printNameTo(printer);
800  }
801 
802  void printShortNameTo(Print& printer) const override {
803  mZoneInfoBroker.printShortNameTo(printer);
804  }
805 
807  void log() const {
808  logging::printf("ExtendedZoneProcessor:\n");
809  logging::printf(" mYear: %d\n", mYear);
810  logging::printf(" mNumMatches: %d\n", mNumMatches);
811  for (int i = 0; i < mNumMatches; i++) {
812  logging::printf(" Match %d: ", i);
813  mMatches[i].log();
814  logging::printf("\n");
815  }
816  mTransitionStorage.log();
817  }
818 
821  mTransitionStorage.resetHighWater();
822  }
823 
825  uint8_t getTransitionHighWater() const {
826  return mTransitionStorage.getHighWater();
827  }
828 
829  void setZoneKey(uintptr_t zoneKey) override {
830  if (mZoneInfoBroker.equals(zoneKey)) return;
831 
832  mZoneInfoBroker = mBrokerFactory->createZoneInfoBroker(zoneKey);
833  mYear = 0;
834  mIsFilled = false;
835  mNumMatches = 0;
837  }
838 
839  bool equalsZoneKey(uintptr_t zoneKey) const override {
840  return mZoneInfoBroker.equals(zoneKey);
841  }
842 
843  void setBrokerFactory(const BF* brokerFactory) {
844  mBrokerFactory = brokerFactory;
845  }
846 
847  protected:
848 
856  uint8_t type,
857  const BF* brokerFactory,
858  uintptr_t zoneKey
859  ) :
860  ZoneProcessor(type),
861  mBrokerFactory(brokerFactory)
862  {
863  setZoneKey(zoneKey);
864  }
865 
866  private:
867  friend class ::ExtendedZoneProcessorTest_compareEraToYearMonth;
868  friend class ::ExtendedZoneProcessorTest_compareEraToYearMonth2;
869  friend class ::ExtendedZoneProcessorTest_createMatch;
870  friend class ::ExtendedZoneProcessorTest_findMatches_simple;
871  friend class ::ExtendedZoneProcessorTest_findMatches_named;
872  friend class ::ExtendedZoneProcessorTest_findCandidateTransitions;
873  friend class ::ExtendedZoneProcessorTest_findTransitionsFromNamedMatch;
874  friend class ::ExtendedZoneProcessorTest_getTransitionTime;
875  friend class ::ExtendedZoneProcessorTest_createTransitionForYear;
876  friend class ::ExtendedZoneProcessorTest_normalizeDateTuple;
877  friend class ::ExtendedZoneProcessorTest_expandDateTuple;
878  friend class ::ExtendedZoneProcessorTest_calcInteriorYears;
879  friend class ::ExtendedZoneProcessorTest_getMostRecentPriorYear;
880  friend class ::ExtendedZoneProcessorTest_compareTransitionToMatchFuzzy;
881  friend class ::ExtendedZoneProcessorTest_compareTransitionToMatch;
882  friend class ::ExtendedZoneProcessorTest_processActiveTransition;
883  friend class ::ExtendedZoneProcessorTest_fixTransitionTimes_generateStartUntilTimes;
884  friend class ::ExtendedZoneProcessorTest_createAbbreviation;
885  friend class ::ExtendedZoneProcessorTest_setZoneKey;
886  friend class ::TransitionStorageTest_findTransitionForDateTime;
887 
888  // Disable copy constructor and assignment operator.
890  const ExtendedZoneProcessorTemplate&) = delete;
892  const ExtendedZoneProcessorTemplate&) = delete;
893 
898  static const uint8_t kMaxMatches = 4;
899 
904  static const uint8_t kMaxInteriorYears = 4;
905 
906  bool equals(const ZoneProcessor& other) const override {
907  return mZoneInfoBroker.equals(
908  ((const ExtendedZoneProcessorTemplate&) other).mZoneInfoBroker);
909  }
910 
915  const Transition* findTransition(acetime_t epochSeconds) const {
916  return mTransitionStorage.findTransition(epochSeconds);
917  }
918 
920  bool init(acetime_t epochSeconds) const {
921  LocalDate ld = LocalDate::forEpochSeconds(epochSeconds);
922  return init(ld);
923  }
924 
929  bool init(const LocalDate& ld) const {
930  int16_t year = ld.year();
931  if (isFilled(year)) return true;
932  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
933  logging::printf("init(): %d\n", year);
934  }
935 
936  mYear = year;
937  mNumMatches = 0; // clear cache
938  mTransitionStorage.init();
939 
940  if (year < mZoneInfoBroker.zoneContext()->startYear - 1
941  || mZoneInfoBroker.zoneContext()->untilYear < year) {
942  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
943  logging::printf("init(): Year %d out of valid range [%d, %d)\n",
944  year,
945  mZoneInfoBroker.zoneContext()->startYear,
946  mZoneInfoBroker.zoneContext()->untilYear);
947  }
948  return false;
949  }
950 
951  extended::YearMonthTuple startYm = {
952  (int8_t) (year - LocalDate::kEpochYear - 1), 12 };
953  extended::YearMonthTuple untilYm = {
954  (int8_t) (year - LocalDate::kEpochYear + 1), 2 };
955 
956  mNumMatches = findMatches(mZoneInfoBroker, startYm, untilYm, mMatches,
957  kMaxMatches);
958  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
959  findTransitions(mTransitionStorage, mMatches, mNumMatches);
960  Transition** begin = mTransitionStorage.getActivePoolBegin();
961  Transition** end = mTransitionStorage.getActivePoolEnd();
962  fixTransitionTimes(begin, end);
963  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
964  generateStartUntilTimes(begin, end);
965  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
966  calcAbbreviations(begin, end);
967  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
968 
969  mIsFilled = true;
970  return true;
971  }
972 
974  bool isFilled(int16_t year) const {
975  return mIsFilled && (year == mYear);
976  }
977 
985  static uint8_t findMatches(const ZIB& zoneInfo,
986  const extended::YearMonthTuple& startYm,
987  const extended::YearMonthTuple& untilYm,
988  ZoneMatch* matches, uint8_t maxMatches) {
989  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
990  logging::printf("findMatches()\n");
991  }
992  uint8_t iMatch = 0;
993  ZEB prev; // anchor ZoneEra, representing the earliest untilTime
994  for (uint8_t iEra = 0; iEra < zoneInfo.numEras(); iEra++) {
995  const ZEB era = zoneInfo.era(iEra);
996  if (eraOverlapsInterval(prev, era, startYm, untilYm)) {
997  if (iMatch < maxMatches) {
998  matches[iMatch] = createMatch(prev, era, startYm, untilYm);
999  iMatch++;
1000  }
1001  }
1002  prev = era;
1003  }
1004  return iMatch;
1005  }
1006 
1018  static bool eraOverlapsInterval(
1019  const ZEB& prev,
1020  const ZEB& era,
1021  const extended::YearMonthTuple& startYm,
1022  const extended::YearMonthTuple& untilYm) {
1023  return (prev.isNull() || compareEraToYearMonth(
1024  prev, untilYm.yearTiny, untilYm.month) < 0)
1025  && compareEraToYearMonth(era, startYm.yearTiny, startYm.month) > 0;
1026  }
1027 
1029  static int8_t compareEraToYearMonth(const ZEB& era,
1030  int8_t yearTiny, uint8_t month) {
1031  if (era.untilYearTiny() < yearTiny) return -1;
1032  if (era.untilYearTiny() > yearTiny) return 1;
1033  if (era.untilMonth() < month) return -1;
1034  if (era.untilMonth() > month) return 1;
1035  if (era.untilDay() > 1) return 1;
1036  //if (era.untilTimeMinutes() < 0) return -1; // never possible
1037  if (era.untilTimeMinutes() > 0) return 1;
1038  return 0;
1039  }
1040 
1047  static ZoneMatch createMatch(
1048  const ZEB& prev,
1049  const ZEB& era,
1050  const extended::YearMonthTuple& startYm,
1051  const extended::YearMonthTuple& untilYm) {
1052  // If prev.isNull(), set startDate to be earlier than all valid ZoneEra.
1053  extended::DateTuple startDate = prev.isNull()
1054  ? extended::DateTuple{
1055  LocalDate::kInvalidYearTiny, 1, 1, 0,
1057  }
1058  : extended::DateTuple{
1059  prev.untilYearTiny(), prev.untilMonth(), prev.untilDay(),
1060  (int16_t) prev.untilTimeMinutes(), prev.untilTimeSuffix()
1061  };
1062  extended::DateTuple lowerBound = {
1063  startYm.yearTiny, startYm.month, 1, 0,
1065  };
1066  if (startDate < lowerBound) {
1067  startDate = lowerBound;
1068  }
1069 
1070  extended::DateTuple untilDate = {
1071  era.untilYearTiny(), era.untilMonth(), era.untilDay(),
1072  (int16_t) era.untilTimeMinutes(), era.untilTimeSuffix()
1073  };
1074  extended::DateTuple upperBound = {
1075  untilYm.yearTiny, untilYm.month, 1, 0,
1077  };
1078  if (upperBound < untilDate) {
1079  untilDate = upperBound;
1080  }
1081 
1082  return {startDate, untilDate, era};
1083  }
1084 
1089  static void findTransitions(
1090  TransitionStorage& transitionStorage,
1091  ZoneMatch* matches,
1092  uint8_t numMatches) {
1093  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1094  logging::printf("findTransitions()\n");
1095  }
1096  for (uint8_t i = 0; i < numMatches; i++) {
1097  findTransitionsForMatch(transitionStorage, &matches[i]);
1098  }
1099  }
1100 
1102  static void findTransitionsForMatch(
1103  TransitionStorage& transitionStorage,
1104  const ZoneMatch* match) {
1105  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1106  logging::printf("findTransitionsForMatch()\n");
1107  }
1108  const ZPB policy = match->era.zonePolicy();
1109  if (policy.isNull()) {
1110  findTransitionsFromSimpleMatch(transitionStorage, match);
1111  } else {
1112  findTransitionsFromNamedMatch(transitionStorage, match);
1113  }
1114  }
1115 
1116  static void findTransitionsFromSimpleMatch(
1117  TransitionStorage& transitionStorage,
1118  const ZoneMatch* match) {
1119  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1120  logging::printf("findTransitionsFromSimpleMatch()\n");
1121  }
1122  Transition* freeTransition = transitionStorage.getFreeAgent();
1123  createTransitionForYear(freeTransition, 0 /*not used*/,
1124  ZRB() /*rule*/, match);
1125  transitionStorage.addFreeAgentToActivePool();
1126  }
1127 
1128  static void findTransitionsFromNamedMatch(
1129  TransitionStorage& transitionStorage,
1130  const ZoneMatch* match) {
1131  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1132  logging::printf("findTransitionsFromNamedMatch()\n");
1133  }
1134  transitionStorage.resetCandidatePool();
1135  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1136  match->log(); logging::printf("\n");
1137  }
1138  findCandidateTransitions(transitionStorage, match);
1139  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1140  transitionStorage.log(); logging::printf("\n");
1141  }
1142  fixTransitionTimes(
1143  transitionStorage.getCandidatePoolBegin(),
1144  transitionStorage.getCandidatePoolEnd());
1145  selectActiveTransitions(transitionStorage, match);
1146  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1147  transitionStorage.log(); logging::printf("\n");
1148  }
1149 
1150  transitionStorage.addActiveCandidatesToActivePool();
1151  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1152  transitionStorage.log(); logging::printf("\n");
1153  }
1154  }
1155 
1156  static void findCandidateTransitions(
1157  TransitionStorage& transitionStorage,
1158  const ZoneMatch* match) {
1159  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1160  logging::printf("findCandidateTransitions(): ");
1161  match->log();
1162  logging::printf("\n");
1163  }
1164  const ZPB policy = match->era.zonePolicy();
1165  uint8_t numRules = policy.numRules();
1166  int8_t startY = match->startDateTime.yearTiny;
1167  int8_t endY = match->untilDateTime.yearTiny;
1168 
1169  Transition** prior = transitionStorage.reservePrior();
1170  (*prior)->active = false; // indicates "no prior transition"
1171  for (uint8_t r = 0; r < numRules; r++) {
1172  const ZRB rule = policy.rule(r);
1173 
1174  // Add Transitions for interior years
1175  int8_t interiorYears[kMaxInteriorYears];
1176  uint8_t numYears = calcInteriorYears(interiorYears, kMaxInteriorYears,
1177  rule.fromYearTiny(), rule.toYearTiny(), startY, endY);
1178  for (uint8_t y = 0; y < numYears; y++) {
1179  int8_t year = interiorYears[y];
1180  Transition* t = transitionStorage.getFreeAgent();
1181  createTransitionForYear(t, year, rule, match);
1182  int8_t status = compareTransitionToMatchFuzzy(t, match);
1183  if (status < 0) {
1184  setAsPriorTransition(transitionStorage, t);
1185  } else if (status == 1) {
1186  transitionStorage.addFreeAgentToCandidatePool();
1187  }
1188  }
1189 
1190  // Add Transition for prior year
1191  int8_t priorYear = getMostRecentPriorYear(
1192  rule.fromYearTiny(), rule.toYearTiny(), startY, endY);
1193  if (priorYear != LocalDate::kInvalidYearTiny) {
1194  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1195  logging::printf(
1196  "findCandidateTransitions(): priorYear: %d\n", priorYear);
1197  }
1198  Transition* t = transitionStorage.getFreeAgent();
1199  createTransitionForYear(t, priorYear, rule, match);
1200  setAsPriorTransition(transitionStorage, t);
1201  }
1202  }
1203 
1204  // Add the reserved prior into the Candidate pool only if 'active' is
1205  // true, meaning that a prior was found.
1206  if ((*prior)->active) {
1207  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1208  logging::printf(
1209  "findCandidateTransitions(): adding prior to Candidate pool\n");
1210  }
1211  transitionStorage.addPriorToCandidatePool();
1212  }
1213  }
1214 
1219  static uint8_t calcInteriorYears(int8_t* interiorYears,
1220  uint8_t maxInteriorYears, int8_t fromYear, int8_t toYear,
1221  int8_t startYear, int8_t endYear) {
1222  uint8_t i = 0;
1223  for (int8_t year = startYear; year <= endYear; year++) {
1224  if (fromYear <= year && year <= toYear) {
1225  interiorYears[i] = year;
1226  i++;
1227  if (i >= maxInteriorYears) break;
1228  }
1229  }
1230  return i;
1231  }
1232 
1239  static void createTransitionForYear(Transition* t, int8_t year,
1240  const ZRB& rule, const ZoneMatch* match) {
1241  t->match = match;
1242  t->rule = rule;
1243  t->offsetMinutes = match->era.offsetMinutes();
1244  t->letterBuf[0] = '\0';
1245 
1246  if (! rule.isNull()) {
1247  t->transitionTime = getTransitionTime(year, rule);
1248  t->deltaMinutes = rule.deltaMinutes();
1249 
1250  char letter = rule.letter();
1251  if (letter >= 32) {
1252  // If LETTER is a '-', treat it the same as an empty string.
1253  if (letter != '-') {
1254  t->letterBuf[0] = letter;
1255  t->letterBuf[1] = '\0';
1256  }
1257  } else {
1258  // rule->letter is a long string, so is referenced as an offset index
1259  // into the ZonePolicy.letters array. The string cannot fit in
1260  // letterBuf, so will be retrieved by the letter() method below.
1261  }
1262  } else {
1263  t->transitionTime = match->startDateTime;
1264  t->deltaMinutes = match->era.deltaMinutes();
1265  }
1266  }
1267 
1273  static int8_t getMostRecentPriorYear(int8_t fromYear, int8_t toYear,
1274  int8_t startYear, int8_t /*endYear*/) {
1275  if (fromYear < startYear) {
1276  if (toYear < startYear) {
1277  return toYear;
1278  } else {
1279  return startYear - 1;
1280  }
1281  } else {
1283  }
1284  }
1285 
1286  static extended::DateTuple getTransitionTime(
1287  int8_t yearTiny, const ZRB& rule) {
1288  internal::MonthDay monthDay = internal::calcStartDayOfMonth(
1289  yearTiny + LocalDate::kEpochYear, rule.inMonth(), rule.onDayOfWeek(),
1290  rule.onDayOfMonth());
1291  return {yearTiny, monthDay.month, monthDay.day,
1292  (int16_t) rule.atTimeMinutes(), rule.atTimeSuffix()};
1293  }
1294 
1305  static int8_t compareTransitionToMatchFuzzy(
1306  const Transition* t, const ZoneMatch* match) {
1307  int16_t ttMonths = t->transitionTime.yearTiny * 12
1308  + t->transitionTime.month;
1309 
1310  int16_t matchStartMonths = match->startDateTime.yearTiny * 12
1311  + match->startDateTime.month;
1312  if (ttMonths < matchStartMonths - 1) return -1;
1313 
1314  int16_t matchUntilMonths = match->untilDateTime.yearTiny * 12
1315  + match->untilDateTime.month;
1316  if (matchUntilMonths + 2 <= ttMonths) return 2;
1317 
1318  return 1;
1319  }
1320 
1322  static void setAsPriorTransition(
1323  TransitionStorage& transitionStorage,
1324  Transition* t) {
1325  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1326  logging::printf("setAsPriorTransition()\n");
1327  }
1328  Transition* prior = transitionStorage.getPrior();
1329  if (prior->active) {
1330  if (prior->transitionTime < t->transitionTime) {
1331  t->active = true;
1332  transitionStorage.setFreeAgentAsPrior();
1333  }
1334  } else {
1335  t->active = true;
1336  transitionStorage.setFreeAgentAsPrior();
1337  }
1338  }
1339 
1348  static void fixTransitionTimes(Transition** begin, Transition** end) {
1349  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1350  logging::printf("fixTransitionTimes(): #transitions: %d;\n",
1351  (int) (end - begin));
1352  }
1353 
1354  // extend first Transition to -infinity
1355  Transition* prev = *begin;
1356 
1357  for (Transition** iter = begin; iter != end; ++iter) {
1358  Transition* curr = *iter;
1359  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1360  logging::printf("fixTransitionTimes(): LOOP\n");
1361  curr->log();
1362  logging::printf("\n");
1363  }
1364  expandDateTuple(&curr->transitionTime,
1365  &curr->transitionTimeS, &curr->transitionTimeU,
1366  prev->offsetMinutes, prev->deltaMinutes);
1367  prev = curr;
1368  }
1369  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1370  logging::printf("fixTransitionTimes(): END\n");
1371  }
1372  }
1373 
1379  static void expandDateTuple(extended::DateTuple* tt,
1380  extended::DateTuple* tts, extended::DateTuple* ttu,
1381  int16_t offsetMinutes, int16_t deltaMinutes) {
1382  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1383  logging::printf("expandDateTuple()\n");
1384  }
1385  if (tt->suffix == internal::ZoneContext::kSuffixS) {
1386  *tts = *tt;
1387  *ttu = {tt->yearTiny, tt->month, tt->day,
1388  (int16_t) (tt->minutes - offsetMinutes),
1390  *tt = {tt->yearTiny, tt->month, tt->day,
1391  (int16_t) (tt->minutes + deltaMinutes),
1393  } else if (tt->suffix == internal::ZoneContext::kSuffixU) {
1394  *ttu = *tt;
1395  *tts = {tt->yearTiny, tt->month, tt->day,
1396  (int16_t) (tt->minutes + offsetMinutes),
1398  *tt = {tt->yearTiny, tt->month, tt->day,
1399  (int16_t) (tt->minutes + (offsetMinutes + deltaMinutes)),
1401  } else {
1402  // Explicit set the suffix to 'w' in case it was something else.
1403  tt->suffix = internal::ZoneContext::kSuffixW;
1404  *tts = {tt->yearTiny, tt->month, tt->day,
1405  (int16_t) (tt->minutes - deltaMinutes),
1407  *ttu = {tt->yearTiny, tt->month, tt->day,
1408  (int16_t) (tt->minutes - (deltaMinutes + offsetMinutes)),
1410  }
1411 
1412  normalizeDateTuple(tt);
1413  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1414  logging::printf("expandDateTuple(): normalizeDateTuple(tt): ");
1415  tt->log();
1416  logging::printf("\n");
1417  }
1418 
1419  normalizeDateTuple(tts);
1420  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1421  logging::printf("expandDateTuple(): normalizeDateTuple(tts): ");
1422  tts->log();
1423  logging::printf("\n");
1424  }
1425 
1426  normalizeDateTuple(ttu);
1427  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1428  logging::printf("expandDateTuple(): normalizeDateTuple(ttu): ");
1429  ttu->log();
1430  logging::printf("\n");
1431  }
1432  }
1433 
1438  static void normalizeDateTuple(extended::DateTuple* dt) {
1439  const int16_t kOneDayAsMinutes = 60 * 24;
1440  if (dt->minutes <= -kOneDayAsMinutes) {
1441  LocalDate ld = LocalDate::forTinyComponents(
1442  dt->yearTiny, dt->month, dt->day);
1443  local_date_mutation::decrementOneDay(ld);
1444  dt->yearTiny = ld.yearTiny();
1445  dt->month = ld.month();
1446  dt->day = ld.day();
1447  dt->minutes += kOneDayAsMinutes;
1448  } else if (kOneDayAsMinutes <= dt->minutes) {
1449  LocalDate ld = LocalDate::forTinyComponents(
1450  dt->yearTiny, dt->month, dt->day);
1451  local_date_mutation::incrementOneDay(ld);
1452  dt->yearTiny = ld.yearTiny();
1453  dt->month = ld.month();
1454  dt->day = ld.day();
1455  dt->minutes -= kOneDayAsMinutes;
1456  } else {
1457  // do nothing
1458  }
1459  }
1460 
1465  static void selectActiveTransitions(
1466  TransitionStorage& transitionStorage,
1467  const ZoneMatch* match) {
1468  Transition** begin = transitionStorage.getCandidatePoolBegin();
1469  Transition** end = transitionStorage.getCandidatePoolEnd();
1470  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1471  logging::printf("selectActiveTransitions(): #candidates: %d\n",
1472  (int) (end - begin));
1473  }
1474  Transition* prior = nullptr;
1475  for (Transition** iter = begin; iter != end; ++iter) {
1476  Transition* transition = *iter;
1477  processActiveTransition(match, transition, &prior);
1478  }
1479 
1480  // If the latest prior transition is found, shift it to start at the
1481  // startDateTime of the current match.
1482  if (prior) {
1483  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1484  logging::printf(
1485  "selectActiveTransitions(): found latest prior\n");
1486  }
1487  prior->originalTransitionTime = prior->transitionTime;
1488  prior->transitionTime = match->startDateTime;
1489  }
1490  }
1491 
1499  static void processActiveTransition(
1500  const ZoneMatch* match,
1501  Transition* transition,
1502  Transition** prior) {
1503  int8_t status = compareTransitionToMatch(transition, match);
1504  if (status == 2) {
1505  transition->active = false;
1506  } else if (status == 1) {
1507  transition->active = true;
1508  } else if (status == 0) {
1509  if (*prior) {
1510  (*prior)->active = false;
1511  }
1512  transition->active = true;
1513  (*prior) = transition;
1514  } else { // (status < 0)
1515  if (*prior) {
1516  if ((*prior)->transitionTime < transition->transitionTime) {
1517  (*prior)->active = false;
1518  transition->active = true;
1519  (*prior) = transition;
1520  }
1521  } else {
1522  transition->active = true;
1523  (*prior) = transition;
1524  }
1525  }
1526  }
1527 
1542  static int8_t compareTransitionToMatch(
1543  const Transition* transition,
1544  const ZoneMatch* match) {
1545  const extended::DateTuple* transitionTime;
1546 
1547  const extended::DateTuple& matchStart = match->startDateTime;
1548  if (matchStart.suffix == internal::ZoneContext::kSuffixS) {
1549  transitionTime = &transition->transitionTimeS;
1550  } else if (matchStart.suffix == internal::ZoneContext::kSuffixU) {
1551  transitionTime = &transition->transitionTimeU;
1552  } else { // assume 'w'
1553  transitionTime = &transition->transitionTime;
1554  }
1555  if (*transitionTime < matchStart) return -1;
1556  if (*transitionTime == matchStart) return 0;
1557 
1558  const extended::DateTuple& matchUntil = match->untilDateTime;
1559  if (matchUntil.suffix == internal::ZoneContext::kSuffixS) {
1560  transitionTime = &transition->transitionTimeS;
1561  } else if (matchUntil.suffix ==
1563  transitionTime = &transition->transitionTimeU;
1564  } else { // assume 'w'
1565  transitionTime = &transition->transitionTime;
1566  }
1567  if (*transitionTime < matchUntil) return 1;
1568  return 2;
1569  }
1570 
1576  static void generateStartUntilTimes(Transition** begin, Transition** end) {
1577  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1578  logging::printf(
1579  "generateStartUntilTimes(): #transitions: %d;\n",
1580  (int) (end - begin));
1581  }
1582 
1583  Transition* prev = *begin;
1584  bool isAfterFirst = false;
1585 
1586  for (Transition** iter = begin; iter != end; ++iter) {
1587  Transition* const t = *iter;
1588 
1589  // 1) Update the untilDateTime of the previous Transition
1590  const extended::DateTuple& tt = t->transitionTime;
1591  if (isAfterFirst) {
1592  prev->untilDateTime = tt;
1593  }
1594 
1595  // 2) Calculate the current startDateTime by shifting the
1596  // transitionTime (represented in the UTC offset of the previous
1597  // transition) into the UTC offset of the *current* transition.
1598  int16_t minutes = tt.minutes + (
1599  - prev->offsetMinutes - prev->deltaMinutes
1600  + t->offsetMinutes + t->deltaMinutes);
1601  t->startDateTime = {tt.yearTiny, tt.month, tt.day, minutes,
1602  tt.suffix};
1603  normalizeDateTuple(&t->startDateTime);
1604 
1605  // 3) The epochSecond of the 'transitionTime' is determined by the
1606  // UTC offset of the *previous* Transition. However, the
1607  // transitionTime can be represented by an illegal time (e.g. 24:00).
1608  // So, it is better to use the properly normalized startDateTime
1609  // (calculated above) with the *current* UTC offset.
1610  //
1611  // NOTE: We should also be able to calculate this directly from
1612  // 'transitionTimeU' which should still be a valid field, because it
1613  // hasn't been clobbered by 'untilDateTime' yet. Not sure if this saves
1614  // any CPU time though, since we still need to mutiply by 900.
1615  const extended::DateTuple& st = t->startDateTime;
1616  const acetime_t offsetSeconds = (acetime_t) 60
1617  * (st.minutes - (t->offsetMinutes + t->deltaMinutes));
1618  LocalDate ld = LocalDate::forTinyComponents(
1619  st.yearTiny, st.month, st.day);
1620  t->startEpochSeconds = ld.toEpochSeconds() + offsetSeconds;
1621 
1622  prev = t;
1623  isAfterFirst = true;
1624  }
1625 
1626  // The last Transition's until time is the until time of the ZoneMatch.
1627  extended::DateTuple untilTime = prev->match->untilDateTime;
1628  extended::DateTuple untilTimeS; // needed only for expandDateTuple
1629  extended::DateTuple untilTimeU; // needed only for expandDateTuple
1630  expandDateTuple(&untilTime, &untilTimeS, &untilTimeU,
1631  prev->offsetMinutes, prev->deltaMinutes);
1632  prev->untilDateTime = untilTime;
1633  }
1634 
1638  static void calcAbbreviations(Transition** begin, Transition** end) {
1639  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1640  logging::printf("calcAbbreviations(): #transitions: %d;\n",
1641  (int) (end - begin));
1642  }
1643  for (Transition** iter = begin; iter != end; ++iter) {
1644  Transition* const t = *iter;
1645  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1646  logging::printf(
1647  "calcAbbreviations(): format:%s, deltaMinutes:%d, letter:%s\n",
1648  t->format(), t->deltaMinutes, t->letter());
1649  }
1650  createAbbreviation(t->abbrev, internal::kAbbrevSize,
1651  t->format(), t->deltaMinutes, t->letter());
1652  }
1653  }
1654 
1663  static void createAbbreviation(char* dest, uint8_t destSize,
1664  const char* format, uint16_t deltaMinutes, const char* letterString) {
1665  // Check if FORMAT contains a '%'.
1666  if (strchr(format, '%') != nullptr) {
1667  // Check if RULES column empty, therefore no 'letter'
1668  if (letterString == nullptr) {
1669  strncpy(dest, format, destSize - 1);
1670  dest[destSize - 1] = '\0';
1671  } else {
1672  ace_common::copyReplaceString(
1673  dest, destSize, format, '%', letterString);
1674  }
1675  } else {
1676  // Check if FORMAT contains a '/'.
1677  const char* slashPos = strchr(format, '/');
1678  if (slashPos != nullptr) {
1679  if (deltaMinutes == 0) {
1680  uint8_t headLength = (slashPos - format);
1681  if (headLength >= destSize) headLength = destSize - 1;
1682  memcpy(dest, format, headLength);
1683  dest[headLength] = '\0';
1684  } else {
1685  uint8_t tailLength = strlen(slashPos+1);
1686  if (tailLength >= destSize) tailLength = destSize - 1;
1687  memcpy(dest, slashPos+1, tailLength);
1688  dest[tailLength] = '\0';
1689  }
1690  } else {
1691  // Just copy the FORMAT disregarding deltaMinutes and letterString.
1692  strncpy(dest, format, destSize);
1693  dest[destSize - 1] = '\0';
1694  }
1695  }
1696  }
1697 
1698  const BF* mBrokerFactory;
1699  ZIB mZoneInfoBroker;
1700 
1701  mutable int16_t mYear = 0; // maybe create LocalDate::kInvalidYear?
1702  mutable bool mIsFilled = false;
1703  // NOTE: Maybe move mNumMatches and mMatches into a MatchStorage object.
1704  mutable uint8_t mNumMatches = 0; // actual number of matches
1705  mutable ZoneMatch mMatches[kMaxMatches];
1706  mutable TransitionStorage mTransitionStorage;
1707 };
1708 
1709 
1715  extended::BrokerFactory,
1716  extended::ZoneInfoBroker,
1717  extended::ZoneEraBroker,
1718  extended::ZonePolicyBroker,
1719  extended::ZoneRuleBroker> {
1720 
1721  public:
1723  static const uint8_t kTypeExtended = 4;
1724 
1725  explicit ExtendedZoneProcessor(const extended::ZoneInfo* zoneInfo = nullptr)
1727  extended::BrokerFactory,
1728  extended::ZoneInfoBroker,
1729  extended::ZoneEraBroker,
1730  extended::ZonePolicyBroker,
1731  extended::ZoneRuleBroker>(
1732  kTypeExtended, &mBrokerFactory, (uintptr_t) zoneInfo)
1733  {}
1734 
1735  private:
1736  extended::BrokerFactory mBrokerFactory;
1737 };
1738 
1739 } // namespace ace_time
1740 
1741 #endif
ace_time::extended::TransitionTemplate::untilDateTime
DateTuple untilDateTime
Until time expressed using the UTC offset of the current Transition.
Definition: ExtendedZoneProcessor.h:227
ace_time::extended::TransitionStorageTemplate::init
void init()
Initialize all pools.
Definition: ExtendedZoneProcessor.h:379
ace_time::extended::TransitionStorageTemplate::getPrior
Transition * getPrior()
Return the current prior transition.
Definition: ExtendedZoneProcessor.h:389
ace_time::extended::TransitionStorageTemplate::reservePrior
Transition ** reservePrior()
Allocate one Transition just after the Active pool, but before the Candidate pool,...
Definition: ExtendedZoneProcessor.h:467
ace_time::extended::TransitionTemplate::letterBuf
char letterBuf[2]
Storage for the single letter 'letter' field if 'rule' is not null.
Definition: ExtendedZoneProcessor.h:255
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:190
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:483
ace_time::ExtendedZoneProcessorTemplate::getTransitionHighWater
uint8_t getTransitionHighWater() const
Get the TransitionStorage high water mark.
Definition: ExtendedZoneProcessor.h:825
ace_time::extended::ZoneMatchTemplate::startDateTime
DateTuple startDateTime
The effective start time of the matching ZoneEra.
Definition: ExtendedZoneProcessor.h:134
ace_time::extended::TransitionTemplate::offsetMinutes
int16_t offsetMinutes
The base offset minutes, not the total effective UTC offset.
Definition: ExtendedZoneProcessor.h:246
ace_time::extended::TransitionStorageTemplate::getFreeAgent
Transition * getFreeAgent()
Return a pointer to the first Transition in the free pool.
Definition: ExtendedZoneProcessor.h:434
ace_time::extended::TransitionStorageTemplate::TransitionStorageTemplate
TransitionStorageTemplate()
Constructor.
Definition: ExtendedZoneProcessor.h:376
ace_time::extended::TransitionTemplate::match
const ZoneMatchTemplate< ZEB > * match
The match which generated this Transition.
Definition: ExtendedZoneProcessor.h:183
ace_time::extended::TransitionTemplate::letter
const char * letter() const
Return the letter string.
Definition: ExtendedZoneProcessor.h:282
ace_time::extended::TransitionStorageTemplate::setFreeAgentAsPrior
void setFreeAgentAsPrior()
Swap the Free agrent transition with the current Prior transition.
Definition: ExtendedZoneProcessor.h:474
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:630
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:714
ace_time::extended::ZoneMatchTemplate::era
ZEB era
The ZoneEra that matched the given year.
Definition: ExtendedZoneProcessor.h:140
ace_time::ExtendedZoneProcessor::kTypeExtended
static const uint8_t kTypeExtended
Unique TimeZone type identifier for BasicZoneProcessor.
Definition: ExtendedZoneProcessor.h:1723
ace_time::extended::TransitionTemplate::transitionTimeU
DateTuple transitionTimeU
Version of transitionTime in 'u' mode, using the UTC offset of the previous transition.
Definition: ExtendedZoneProcessor.h:221
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:1714
ace_time::extended::TransitionStorageTemplate::resetCandidatePool
void resetCandidatePool()
Empty the Candidate pool by resetting the various indexes.
Definition: ExtendedZoneProcessor.h:410
ace_time::ExtendedZoneProcessorTemplate::TransitionStorage
extended::TransitionStorageTemplate< kMaxTransitions, ZEB, ZPB, ZRB > TransitionStorage
Exposed only for testing purposes.
Definition: ExtendedZoneProcessor.h:712
ace_time::ExtendedZoneProcessorTemplate::getAbbrev
const char * getAbbrev(acetime_t epochSeconds) const override
Return the time zone abbreviation at epochSeconds.
Definition: ExtendedZoneProcessor.h:733
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:855
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:716
ace_time::ExtendedZoneProcessorTemplate::printShortNameTo
void printShortNameTo(Print &printer) const override
Print a short human-readable identifier (e.g.
Definition: ExtendedZoneProcessor.h:802
ace_time::extended::TransitionTemplate::abbrev
char abbrev[internal::kAbbrevSize]
The calculated effective time zone abbreviation, e.g.
Definition: ExtendedZoneProcessor.h:252
ace_time::extended::TransitionStorageTemplate::findTransitionForDateTime
const Transition * findTransitionForDateTime(const LocalDateTime &ldt) const
Return the Transition matching the given dateTime.
Definition: ExtendedZoneProcessor.h:573
ace_time::extended::TransitionTemplate::startDateTime
DateTuple startDateTime
Start time expressed using the UTC offset of the current Transition.
Definition: ExtendedZoneProcessor.h:212
ace_time::extended::DateTuple
A tuple that represents a date and time.
Definition: ExtendedZoneProcessor.h:63
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:702
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:740
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:137
ace_time::extended::TransitionStorageTemplate
A heap manager which is specialized and tuned to manage a collection of Transitions,...
Definition: ExtendedZoneProcessor.h:367
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:269
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:249
ace_time::extended::TransitionTemplate::transitionTimeS
DateTuple transitionTimeS
Version of transitionTime in 's' mode, using the UTC offset of the previous Transition.
Definition: ExtendedZoneProcessor.h:206
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:394
ExtendedBrokers.h
ace_time::ExtendedZoneProcessorTemplate::ZoneMatch
extended::ZoneMatchTemplate< ZEB > ZoneMatch
Exposed only for testing purposes.
Definition: ExtendedZoneProcessor.h:708
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:829
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:234
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:839
ace_time::extended::TransitionStorageTemplate::resetHighWater
void resetHighWater()
Reset the high water mark.
Definition: ExtendedZoneProcessor.h:623
ace_time::extended::TransitionStorageTemplate::Transition
TransitionTemplate< ZEB, ZPB, ZRB > Transition
Template instantiation of TransitionTemplate used by this class.
Definition: ExtendedZoneProcessor.h:373
ace_time::extended::TransitionTemplate::startEpochSeconds
acetime_t startEpochSeconds
The calculated transition time of the given rule.
Definition: ExtendedZoneProcessor.h:237
ace_time::ExtendedZoneProcessorTemplate::log
void log() const
Used only for debugging.
Definition: ExtendedZoneProcessor.h:807
ace_time::extended::TransitionStorageTemplate::addFreeAgentToActivePool
void addFreeAgentToActivePool()
Immediately add the free agent Transition at index mIndexFree to the Active pool.
Definition: ExtendedZoneProcessor.h:455
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:181
ace_time::ExtendedZoneProcessorTemplate::printNameTo
void printNameTo(Print &printer) const override
Print a human-readable identifier (e.g.
Definition: ExtendedZoneProcessor.h:798
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:120
ace_time::ExtendedZoneProcessorTemplate::getDeltaOffset
TimeOffset getDeltaOffset(acetime_t epochSeconds) const override
Return the DST delta offset at epochSeconds.
Definition: ExtendedZoneProcessor.h:726
ace_time::ExtendedZoneProcessorTemplate::resetTransitionHighWater
void resetTransitionHighWater()
Reset the TransitionStorage high water mark.
Definition: ExtendedZoneProcessor.h:820
ace_time::extended::DateTuple::log
void log() const
Used only for debugging.
Definition: ExtendedZoneProcessor.h:76
ace_time::extended::ZoneMatchTemplate
Data structure that captures the matching ZoneEra and its ZoneRule transitions for a given year.
Definition: ExtendedZoneProcessor.h:132
ace_time::extended::TransitionTemplate::transitionTime
DateTuple transitionTime
The original transition time, usually 'w' but sometimes 's' or 'u'.
Definition: ExtendedZoneProcessor.h:198
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:705
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:493
ace_time::extended::TransitionStorageTemplate::findTransition
const Transition * findTransition(acetime_t epochSeconds) const
Return the Transition matching the given epochSeconds.
Definition: ExtendedZoneProcessor.h:536
ace_time::extended::TransitionTemplate::log
void log() const
Used only for debugging.
Definition: ExtendedZoneProcessor.h:312
ace_time::ExtendedZoneProcessorTemplate
An implementation of ZoneProcessor that supports for all zones defined by the TZ Database.
Definition: ExtendedZoneProcessor.h:692
ace_time::extended::TransitionStorageTemplate::addActiveCandidatesToActivePool
void addActiveCandidatesToActivePool()
Add active candidates into the Active pool, and collapse the Candidate pool.
Definition: ExtendedZoneProcessor.h:509
ace_time::extended::TransitionStorageTemplate::log
void log() const
Verify that the indexes are valid.
Definition: ExtendedZoneProcessor.h:596