AceTime  1.7.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
ExtendedZoneProcessor.h
1 /*
2  * MIT License
3  * Copyright (c) 2019 Brian T. Park
4  */
5 
6 #ifndef ACE_TIME_EXTENDED_ZONE_PROCESSOR_H
7 #define ACE_TIME_EXTENDED_ZONE_PROCESSOR_H
8 
9 #include <string.h> // memcpy()
10 #include <stdint.h> // 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  static void swap(Transition** a, Transition** b) {
395  auto* tmp = *a;
396  *a = *b;
397  *b = tmp;
398  }
399 
409  mIndexCandidates = mIndexPrior;
410  mIndexFree = mIndexPrior;
411  }
412 
413  Transition** getCandidatePoolBegin() {
414  return &mTransitions[mIndexCandidates];
415  }
416  Transition** getCandidatePoolEnd() {
417  return &mTransitions[mIndexFree];
418  }
419 
420  Transition** getActivePoolBegin() {
421  return &mTransitions[0];
422  }
423  Transition** getActivePoolEnd() {
424  return &mTransitions[mIndexFree];
425  }
426 
433  // Set the internal high water mark. If that index becomes SIZE,
434  // then we know we have an overflow.
435  if (mIndexFree > mHighWater) {
436  mHighWater = mIndexFree;
437  }
438 
439  if (mIndexFree < SIZE) {
440  return mTransitions[mIndexFree];
441  } else {
442  return mTransitions[SIZE - 1];
443  }
444  }
445 
454  if (mIndexFree >= SIZE) return;
455  mIndexFree++;
456  mIndexPrior = mIndexFree;
457  mIndexCandidates = mIndexFree;
458  }
459 
466  mIndexCandidates++;
467  mIndexFree++;
468  return &mTransitions[mIndexPrior];
469  }
470 
473  swap(&mTransitions[mIndexPrior], &mTransitions[mIndexFree]);
474  }
475 
482  mIndexCandidates--;
483  }
484 
492  if (mIndexFree >= SIZE) return;
493  for (uint8_t i = mIndexFree; i > mIndexCandidates; i--) {
494  Transition* curr = mTransitions[i];
495  Transition* prev = mTransitions[i - 1];
496  if (curr->transitionTime >= prev->transitionTime) break;
497  mTransitions[i] = prev;
498  mTransitions[i - 1] = curr;
499  }
500  mIndexFree++;
501  }
502 
508  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
509  logging::printf("addActiveCandidatesToActivePool()\n");
510  }
511  uint8_t iActive = mIndexPrior;
512  uint8_t iCandidate = mIndexCandidates;
513  for (; iCandidate < mIndexFree; iCandidate++) {
514  if (mTransitions[iCandidate]->active) {
515  if (iActive != iCandidate) {
516  swap(&mTransitions[iActive], &mTransitions[iCandidate]);
517  }
518  ++iActive;
519  }
520  }
521  mIndexPrior = iActive;
522  mIndexCandidates = iActive;
523  mIndexFree = iActive;
524  }
525 
534  const Transition* findTransition(acetime_t epochSeconds) const {
535  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
536  logging::printf( "findTransition(): mIndexFree: %d\n", mIndexFree);
537  }
538 
539  const Transition* match = nullptr;
540  for (uint8_t i = 0; i < mIndexFree; i++) {
541  const Transition* candidate = mTransitions[i];
542  if (candidate->startEpochSeconds > epochSeconds) break;
543  match = candidate;
544  }
545  return match;
546  }
547 
572  const {
573  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
574  logging::printf(
575  "findTransitionForDateTime(): mIndexFree: %d\n", mIndexFree);
576  }
577 
578  // Convert LocalDateTime to DateTuple.
579  DateTuple localDate = { ldt.yearTiny(), ldt.month(), ldt.day(),
580  (int16_t) (ldt.hour() * 60 + ldt.minute()),
582  const Transition* match = nullptr;
583 
584  // Find the last Transition that matches
585  for (uint8_t i = 0; i < mIndexFree; i++) {
586  const Transition* candidate = mTransitions[i];
587  if (candidate->startDateTime > localDate) break;
588  match = candidate;
589  }
590  return match;
591  }
592 
594  void log() const {
595  logging::printf("TransitionStorage:\n");
596  logging::printf(" mIndexPrior: %d\n", mIndexPrior);
597  logging::printf(" mIndexCandidates: %d\n", mIndexCandidates);
598  logging::printf(" mIndexFree: %d\n", mIndexFree);
599  if (mIndexPrior != 0) {
600  logging::printf(" Actives:\n");
601  for (uint8_t i = 0; i < mIndexPrior; i++) {
602  mTransitions[i]->log();
603  logging::printf("\n");
604  }
605  }
606  if (mIndexPrior != mIndexCandidates) {
607  logging::printf(" Prior: ");
608  mTransitions[mIndexPrior]->log();
609  logging::printf("\n");
610  }
611  if (mIndexCandidates != mIndexFree) {
612  logging::printf(" Candidates:\n");
613  for (uint8_t i = mIndexCandidates; i < mIndexFree; i++) {
614  mTransitions[i]->log();
615  logging::printf("\n");
616  }
617  }
618  }
619 
621  void resetHighWater() { mHighWater = 0; }
622 
628  uint8_t getHighWater() const { return mHighWater; }
629 
630  private:
631  friend class ::TransitionStorageTest_getFreeAgent;
632  friend class ::TransitionStorageTest_getFreeAgent2;
633  friend class ::TransitionStorageTest_addFreeAgentToActivePool;
634  friend class ::TransitionStorageTest_reservePrior;
635  friend class ::TransitionStorageTest_addFreeAgentToCandidatePool;
636  friend class ::TransitionStorageTest_setFreeAgentAsPrior;
637  friend class ::TransitionStorageTest_addActiveCandidatesToActivePool;
638  friend class ::TransitionStorageTest_findTransitionForDateTime;
639  friend class ::TransitionStorageTest_resetCandidatePool;
640 
642  Transition* getTransition(uint8_t i) {
643  return mTransitions[i];
644  }
645 
646  Transition mPool[SIZE];
647  Transition* mTransitions[SIZE];
648  uint8_t mIndexPrior;
649  uint8_t mIndexCandidates;
650  uint8_t mIndexFree;
651 
653  uint8_t mHighWater = 0;
654 };
655 
656 } // namespace extended
657 
689 template <typename BF, typename ZIB, typename ZEB, typename ZPB, typename ZRB>
691  public:
700  static const uint8_t kMaxTransitions = 8;
701 
704 
707 
711 
712  uint32_t getZoneId() const override { return mZoneInfoBroker.zoneId(); }
713 
714  TimeOffset getUtcOffset(acetime_t epochSeconds) const override {
715  bool success = init(epochSeconds);
716  if (!success) return TimeOffset::forError();
717  const Transition* transition = findTransition(epochSeconds);
718  return (transition)
720  transition->offsetMinutes + transition->deltaMinutes)
722  }
723 
724  TimeOffset getDeltaOffset(acetime_t epochSeconds) const override {
725  bool success = init(epochSeconds);
726  if (!success) return TimeOffset::forError();
727  const Transition* transition = findTransition(epochSeconds);
728  return TimeOffset::forMinutes(transition->deltaMinutes);
729  }
730 
731  const char* getAbbrev(acetime_t epochSeconds) const override {
732  bool success = init(epochSeconds);
733  if (!success) return "";
734  const Transition* transition = findTransition(epochSeconds);
735  return transition->abbrev;
736  }
737 
738  OffsetDateTime getOffsetDateTime(const LocalDateTime& ldt) const override {
739  TimeOffset offset;
740  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
741  logging::printf("getOffsetDateTime(): ldt=");
742  ldt.printTo(SERIAL_PORT_MONITOR);
743  SERIAL_PORT_MONITOR.println();
744  }
745  bool success = init(ldt.localDate());
746 
747  // Find the Transition to get the DST offset
748  if (success) {
749  const Transition* transition =
750  mTransitionStorage.findTransitionForDateTime(ldt);
751  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
752  logging::printf("getOffsetDateTime(): match transition=");
753  transition->log();
754  logging::printf("\n");
755  }
756  offset = (transition)
758  transition->offsetMinutes + transition->deltaMinutes)
760  } else {
761  offset = TimeOffset::forError();
762  }
763 
764  auto odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset);
765  if (offset.isError()) {
766  return odt;
767  }
768  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
769  logging::printf("getOffsetDateTime(): odt=");
770  odt.printTo(SERIAL_PORT_MONITOR);
771  SERIAL_PORT_MONITOR.println();
772  }
773 
774  // Normalize the OffsetDateTime, causing LocalDateTime in the DST
775  // transtion gap to be shifted forward one hour. For LocalDateTime in an
776  // overlap (DST->STD transition), the earlier UTC offset is selected// by
777  // findTransitionForDateTime(). Use that to calculate the epochSeconds,
778  // then recalculate the offset. Use this final offset to determine the
779  // effective OffsetDateTime that will survive a round-trip unchanged.
780  acetime_t epochSeconds = odt.toEpochSeconds();
781  const Transition* transition =
782  mTransitionStorage.findTransition(epochSeconds);
783  offset = (transition)
785  transition->offsetMinutes + transition->deltaMinutes)
787  odt = OffsetDateTime::forEpochSeconds(epochSeconds, offset);
788  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
789  logging::printf("getOffsetDateTime(): normalized(odt)=");
790  odt.printTo(SERIAL_PORT_MONITOR);
791  SERIAL_PORT_MONITOR.println();
792  }
793  return odt;
794  }
795 
796  void printNameTo(Print& printer) const override {
797  mZoneInfoBroker.printNameTo(printer);
798  }
799 
800  void printShortNameTo(Print& printer) const override {
801  mZoneInfoBroker.printShortNameTo(printer);
802  }
803 
805  void log() const {
806  logging::printf("ExtendedZoneProcessor:\n");
807  logging::printf(" mYear: %d\n", mYear);
808  logging::printf(" mNumMatches: %d\n", mNumMatches);
809  for (int i = 0; i < mNumMatches; i++) {
810  logging::printf(" Match %d: ", i);
811  mMatches[i].log();
812  logging::printf("\n");
813  }
814  mTransitionStorage.log();
815  }
816 
819  mTransitionStorage.resetHighWater();
820  }
821 
823  uint8_t getTransitionHighWater() const {
824  return mTransitionStorage.getHighWater();
825  }
826 
827  void setZoneKey(uintptr_t zoneKey) override {
828  if (mZoneInfoBroker.equals(zoneKey)) return;
829 
830  mZoneInfoBroker = mBrokerFactory->createZoneInfoBroker(zoneKey);
831  mYear = 0;
832  mIsFilled = false;
833  mNumMatches = 0;
835  }
836 
837  bool equalsZoneKey(uintptr_t zoneKey) const override {
838  return mZoneInfoBroker.equals(zoneKey);
839  }
840 
841  void setBrokerFactory(const BF* brokerFactory) {
842  mBrokerFactory = brokerFactory;
843  }
844 
845  protected:
846 
854  uint8_t type,
855  const BF* brokerFactory,
856  uintptr_t zoneKey
857  ) :
858  ZoneProcessor(type),
859  mBrokerFactory(brokerFactory)
860  {
861  setZoneKey(zoneKey);
862  }
863 
864  private:
865  friend class ::ExtendedZoneProcessorTest_compareEraToYearMonth;
866  friend class ::ExtendedZoneProcessorTest_compareEraToYearMonth2;
867  friend class ::ExtendedZoneProcessorTest_createMatch;
868  friend class ::ExtendedZoneProcessorTest_findMatches_simple;
869  friend class ::ExtendedZoneProcessorTest_findMatches_named;
870  friend class ::ExtendedZoneProcessorTest_findCandidateTransitions;
871  friend class ::ExtendedZoneProcessorTest_findTransitionsFromNamedMatch;
872  friend class ::ExtendedZoneProcessorTest_getTransitionTime;
873  friend class ::ExtendedZoneProcessorTest_createTransitionForYear;
874  friend class ::ExtendedZoneProcessorTest_normalizeDateTuple;
875  friend class ::ExtendedZoneProcessorTest_expandDateTuple;
876  friend class ::ExtendedZoneProcessorTest_calcInteriorYears;
877  friend class ::ExtendedZoneProcessorTest_getMostRecentPriorYear;
878  friend class ::ExtendedZoneProcessorTest_compareTransitionToMatchFuzzy;
879  friend class ::ExtendedZoneProcessorTest_compareTransitionToMatch;
880  friend class ::ExtendedZoneProcessorTest_processActiveTransition;
881  friend class ::ExtendedZoneProcessorTest_fixTransitionTimes_generateStartUntilTimes;
882  friend class ::ExtendedZoneProcessorTest_createAbbreviation;
883  friend class ::ExtendedZoneProcessorTest_setZoneKey;
884  friend class ::TransitionStorageTest_findTransitionForDateTime;
885 
886  // Disable copy constructor and assignment operator.
888  const ExtendedZoneProcessorTemplate&) = delete;
890  const ExtendedZoneProcessorTemplate&) = delete;
891 
896  static const uint8_t kMaxMatches = 4;
897 
902  static const uint8_t kMaxInteriorYears = 4;
903 
904  bool equals(const ZoneProcessor& other) const override {
905  return mZoneInfoBroker.equals(
906  ((const ExtendedZoneProcessorTemplate&) other).mZoneInfoBroker);
907  }
908 
913  const Transition* findTransition(acetime_t epochSeconds) const {
914  return mTransitionStorage.findTransition(epochSeconds);
915  }
916 
918  bool init(acetime_t epochSeconds) const {
919  LocalDate ld = LocalDate::forEpochSeconds(epochSeconds);
920  return init(ld);
921  }
922 
927  bool init(const LocalDate& ld) const {
928  int16_t year = ld.year();
929  if (isFilled(year)) return true;
930  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
931  logging::printf("init(): %d\n", year);
932  }
933 
934  mYear = year;
935  mNumMatches = 0; // clear cache
936  mTransitionStorage.init();
937 
938  if (year < mZoneInfoBroker.zoneContext()->startYear - 1
939  || mZoneInfoBroker.zoneContext()->untilYear < year) {
940  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
941  logging::printf("init(): Year %d out of valid range [%d, %d)\n",
942  year,
943  mZoneInfoBroker.zoneContext()->startYear,
944  mZoneInfoBroker.zoneContext()->untilYear);
945  }
946  return false;
947  }
948 
949  extended::YearMonthTuple startYm = {
950  (int8_t) (year - LocalDate::kEpochYear - 1), 12 };
951  extended::YearMonthTuple untilYm = {
952  (int8_t) (year - LocalDate::kEpochYear + 1), 2 };
953 
954  mNumMatches = findMatches(mZoneInfoBroker, startYm, untilYm, mMatches,
955  kMaxMatches);
956  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
957  findTransitions(mTransitionStorage, mMatches, mNumMatches);
958  Transition** begin = mTransitionStorage.getActivePoolBegin();
959  Transition** end = mTransitionStorage.getActivePoolEnd();
960  fixTransitionTimes(begin, end);
961  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
962  generateStartUntilTimes(begin, end);
963  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
964  calcAbbreviations(begin, end);
965  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
966 
967  mIsFilled = true;
968  return true;
969  }
970 
972  bool isFilled(int16_t year) const {
973  return mIsFilled && (year == mYear);
974  }
975 
983  static uint8_t findMatches(const ZIB& zoneInfo,
984  const extended::YearMonthTuple& startYm,
985  const extended::YearMonthTuple& untilYm,
986  ZoneMatch* matches, uint8_t maxMatches) {
987  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
988  logging::printf("findMatches()\n");
989  }
990  uint8_t iMatch = 0;
991  ZEB prev; // anchor ZoneEra, representing the earliest untilTime
992  for (uint8_t iEra = 0; iEra < zoneInfo.numEras(); iEra++) {
993  const ZEB era = zoneInfo.era(iEra);
994  if (eraOverlapsInterval(prev, era, startYm, untilYm)) {
995  if (iMatch < maxMatches) {
996  matches[iMatch] = createMatch(prev, era, startYm, untilYm);
997  iMatch++;
998  }
999  }
1000  prev = era;
1001  }
1002  return iMatch;
1003  }
1004 
1016  static bool eraOverlapsInterval(
1017  const ZEB& prev,
1018  const ZEB& era,
1019  const extended::YearMonthTuple& startYm,
1020  const extended::YearMonthTuple& untilYm) {
1021  return (prev.isNull() || compareEraToYearMonth(
1022  prev, untilYm.yearTiny, untilYm.month) < 0)
1023  && compareEraToYearMonth(era, startYm.yearTiny, startYm.month) > 0;
1024  }
1025 
1027  static int8_t compareEraToYearMonth(const ZEB& era,
1028  int8_t yearTiny, uint8_t month) {
1029  if (era.untilYearTiny() < yearTiny) return -1;
1030  if (era.untilYearTiny() > yearTiny) return 1;
1031  if (era.untilMonth() < month) return -1;
1032  if (era.untilMonth() > month) return 1;
1033  if (era.untilDay() > 1) return 1;
1034  //if (era.untilTimeMinutes() < 0) return -1; // never possible
1035  if (era.untilTimeMinutes() > 0) return 1;
1036  return 0;
1037  }
1038 
1045  static ZoneMatch createMatch(
1046  const ZEB& prev,
1047  const ZEB& era,
1048  const extended::YearMonthTuple& startYm,
1049  const extended::YearMonthTuple& untilYm) {
1050  // If prev.isNull(), set startDate to be earlier than all valid ZoneEra.
1051  extended::DateTuple startDate = prev.isNull()
1052  ? extended::DateTuple{
1053  LocalDate::kInvalidYearTiny, 1, 1, 0,
1055  }
1056  : extended::DateTuple{
1057  prev.untilYearTiny(), prev.untilMonth(), prev.untilDay(),
1058  (int16_t) prev.untilTimeMinutes(), prev.untilTimeSuffix()
1059  };
1060  extended::DateTuple lowerBound = {
1061  startYm.yearTiny, startYm.month, 1, 0,
1063  };
1064  if (startDate < lowerBound) {
1065  startDate = lowerBound;
1066  }
1067 
1068  extended::DateTuple untilDate = {
1069  era.untilYearTiny(), era.untilMonth(), era.untilDay(),
1070  (int16_t) era.untilTimeMinutes(), era.untilTimeSuffix()
1071  };
1072  extended::DateTuple upperBound = {
1073  untilYm.yearTiny, untilYm.month, 1, 0,
1075  };
1076  if (upperBound < untilDate) {
1077  untilDate = upperBound;
1078  }
1079 
1080  return {startDate, untilDate, era};
1081  }
1082 
1087  static void findTransitions(
1088  TransitionStorage& transitionStorage,
1089  ZoneMatch* matches,
1090  uint8_t numMatches) {
1091  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1092  logging::printf("findTransitions()\n");
1093  }
1094  for (uint8_t i = 0; i < numMatches; i++) {
1095  findTransitionsForMatch(transitionStorage, &matches[i]);
1096  }
1097  }
1098 
1100  static void findTransitionsForMatch(
1101  TransitionStorage& transitionStorage,
1102  const ZoneMatch* match) {
1103  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1104  logging::printf("findTransitionsForMatch()\n");
1105  }
1106  const ZPB policy = match->era.zonePolicy();
1107  if (policy.isNull()) {
1108  findTransitionsFromSimpleMatch(transitionStorage, match);
1109  } else {
1110  findTransitionsFromNamedMatch(transitionStorage, match);
1111  }
1112  }
1113 
1114  static void findTransitionsFromSimpleMatch(
1115  TransitionStorage& transitionStorage,
1116  const ZoneMatch* match) {
1117  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1118  logging::printf("findTransitionsFromSimpleMatch()\n");
1119  }
1120  Transition* freeTransition = transitionStorage.getFreeAgent();
1121  createTransitionForYear(freeTransition, 0 /*not used*/,
1122  ZRB() /*rule*/, match);
1123  transitionStorage.addFreeAgentToActivePool();
1124  }
1125 
1126  static void findTransitionsFromNamedMatch(
1127  TransitionStorage& transitionStorage,
1128  const ZoneMatch* match) {
1129  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1130  logging::printf("findTransitionsFromNamedMatch()\n");
1131  }
1132  transitionStorage.resetCandidatePool();
1133  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1134  match->log(); logging::printf("\n");
1135  }
1136  findCandidateTransitions(transitionStorage, match);
1137  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1138  transitionStorage.log(); logging::printf("\n");
1139  }
1140  fixTransitionTimes(
1141  transitionStorage.getCandidatePoolBegin(),
1142  transitionStorage.getCandidatePoolEnd());
1143  selectActiveTransitions(transitionStorage, match);
1144  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1145  transitionStorage.log(); logging::printf("\n");
1146  }
1147 
1148  transitionStorage.addActiveCandidatesToActivePool();
1149  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1150  transitionStorage.log(); logging::printf("\n");
1151  }
1152  }
1153 
1154  static void findCandidateTransitions(
1155  TransitionStorage& transitionStorage,
1156  const ZoneMatch* match) {
1157  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1158  logging::printf("findCandidateTransitions(): ");
1159  match->log();
1160  logging::printf("\n");
1161  }
1162  const ZPB policy = match->era.zonePolicy();
1163  uint8_t numRules = policy.numRules();
1164  int8_t startY = match->startDateTime.yearTiny;
1165  int8_t endY = match->untilDateTime.yearTiny;
1166 
1167  Transition** prior = transitionStorage.reservePrior();
1168  (*prior)->active = false; // indicates "no prior transition"
1169  for (uint8_t r = 0; r < numRules; r++) {
1170  const ZRB rule = policy.rule(r);
1171 
1172  // Add Transitions for interior years
1173  int8_t interiorYears[kMaxInteriorYears];
1174  uint8_t numYears = calcInteriorYears(interiorYears, kMaxInteriorYears,
1175  rule.fromYearTiny(), rule.toYearTiny(), startY, endY);
1176  for (uint8_t y = 0; y < numYears; y++) {
1177  int8_t year = interiorYears[y];
1178  Transition* t = transitionStorage.getFreeAgent();
1179  createTransitionForYear(t, year, rule, match);
1180  int8_t status = compareTransitionToMatchFuzzy(t, match);
1181  if (status < 0) {
1182  setAsPriorTransition(transitionStorage, t);
1183  } else if (status == 1) {
1184  transitionStorage.addFreeAgentToCandidatePool();
1185  }
1186  }
1187 
1188  // Add Transition for prior year
1189  int8_t priorYear = getMostRecentPriorYear(
1190  rule.fromYearTiny(), rule.toYearTiny(), startY, endY);
1191  if (priorYear != LocalDate::kInvalidYearTiny) {
1192  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1193  logging::printf(
1194  "findCandidateTransitions(): priorYear: %d\n", priorYear);
1195  }
1196  Transition* t = transitionStorage.getFreeAgent();
1197  createTransitionForYear(t, priorYear, rule, match);
1198  setAsPriorTransition(transitionStorage, t);
1199  }
1200  }
1201 
1202  // Add the reserved prior into the Candidate pool only if 'active' is
1203  // true, meaning that a prior was found.
1204  if ((*prior)->active) {
1205  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1206  logging::printf(
1207  "findCandidateTransitions(): adding prior to Candidate pool\n");
1208  }
1209  transitionStorage.addPriorToCandidatePool();
1210  }
1211  }
1212 
1217  static uint8_t calcInteriorYears(int8_t* interiorYears,
1218  uint8_t maxInteriorYears, int8_t fromYear, int8_t toYear,
1219  int8_t startYear, int8_t endYear) {
1220  uint8_t i = 0;
1221  for (int8_t year = startYear; year <= endYear; year++) {
1222  if (fromYear <= year && year <= toYear) {
1223  interiorYears[i] = year;
1224  i++;
1225  if (i >= maxInteriorYears) break;
1226  }
1227  }
1228  return i;
1229  }
1230 
1237  static void createTransitionForYear(Transition* t, int8_t year,
1238  const ZRB& rule, const ZoneMatch* match) {
1239  t->match = match;
1240  t->rule = rule;
1241  t->offsetMinutes = match->era.offsetMinutes();
1242  t->letterBuf[0] = '\0';
1243 
1244  if (! rule.isNull()) {
1245  t->transitionTime = getTransitionTime(year, rule);
1246  t->deltaMinutes = rule.deltaMinutes();
1247 
1248  char letter = rule.letter();
1249  if (letter >= 32) {
1250  // If LETTER is a '-', treat it the same as an empty string.
1251  if (letter != '-') {
1252  t->letterBuf[0] = letter;
1253  t->letterBuf[1] = '\0';
1254  }
1255  } else {
1256  // rule->letter is a long string, so is referenced as an offset index
1257  // into the ZonePolicy.letters array. The string cannot fit in
1258  // letterBuf, so will be retrieved by the letter() method below.
1259  }
1260  } else {
1261  t->transitionTime = match->startDateTime;
1262  t->deltaMinutes = match->era.deltaMinutes();
1263  }
1264  }
1265 
1271  static int8_t getMostRecentPriorYear(int8_t fromYear, int8_t toYear,
1272  int8_t startYear, int8_t /*endYear*/) {
1273  if (fromYear < startYear) {
1274  if (toYear < startYear) {
1275  return toYear;
1276  } else {
1277  return startYear - 1;
1278  }
1279  } else {
1281  }
1282  }
1283 
1284  static extended::DateTuple getTransitionTime(
1285  int8_t yearTiny, const ZRB& rule) {
1286  internal::MonthDay monthDay = internal::calcStartDayOfMonth(
1287  yearTiny + LocalDate::kEpochYear, rule.inMonth(), rule.onDayOfWeek(),
1288  rule.onDayOfMonth());
1289  return {yearTiny, monthDay.month, monthDay.day,
1290  (int16_t) rule.atTimeMinutes(), rule.atTimeSuffix()};
1291  }
1292 
1303  static int8_t compareTransitionToMatchFuzzy(
1304  const Transition* t, const ZoneMatch* match) {
1305  int16_t ttMonths = t->transitionTime.yearTiny * 12
1306  + t->transitionTime.month;
1307 
1308  int16_t matchStartMonths = match->startDateTime.yearTiny * 12
1309  + match->startDateTime.month;
1310  if (ttMonths < matchStartMonths - 1) return -1;
1311 
1312  int16_t matchUntilMonths = match->untilDateTime.yearTiny * 12
1313  + match->untilDateTime.month;
1314  if (matchUntilMonths + 2 <= ttMonths) return 2;
1315 
1316  return 1;
1317  }
1318 
1320  static void setAsPriorTransition(
1321  TransitionStorage& transitionStorage,
1322  Transition* t) {
1323  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1324  logging::printf("setAsPriorTransition()\n");
1325  }
1326  Transition* prior = transitionStorage.getPrior();
1327  if (prior->active) {
1328  if (prior->transitionTime < t->transitionTime) {
1329  t->active = true;
1330  transitionStorage.setFreeAgentAsPrior();
1331  }
1332  } else {
1333  t->active = true;
1334  transitionStorage.setFreeAgentAsPrior();
1335  }
1336  }
1337 
1346  static void fixTransitionTimes(Transition** begin, Transition** end) {
1347  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1348  logging::printf("fixTransitionTimes(): #transitions: %d;\n",
1349  (int) (end - begin));
1350  }
1351 
1352  // extend first Transition to -infinity
1353  Transition* prev = *begin;
1354 
1355  for (Transition** iter = begin; iter != end; ++iter) {
1356  Transition* curr = *iter;
1357  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1358  logging::printf("fixTransitionTimes(): LOOP\n");
1359  curr->log();
1360  logging::printf("\n");
1361  }
1362  expandDateTuple(&curr->transitionTime,
1363  &curr->transitionTimeS, &curr->transitionTimeU,
1364  prev->offsetMinutes, prev->deltaMinutes);
1365  prev = curr;
1366  }
1367  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1368  logging::printf("fixTransitionTimes(): END\n");
1369  }
1370  }
1371 
1377  static void expandDateTuple(extended::DateTuple* tt,
1378  extended::DateTuple* tts, extended::DateTuple* ttu,
1379  int16_t offsetMinutes, int16_t deltaMinutes) {
1380  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1381  logging::printf("expandDateTuple()\n");
1382  }
1383  if (tt->suffix == internal::ZoneContext::kSuffixS) {
1384  *tts = *tt;
1385  *ttu = {tt->yearTiny, tt->month, tt->day,
1386  (int16_t) (tt->minutes - offsetMinutes),
1388  *tt = {tt->yearTiny, tt->month, tt->day,
1389  (int16_t) (tt->minutes + deltaMinutes),
1391  } else if (tt->suffix == internal::ZoneContext::kSuffixU) {
1392  *ttu = *tt;
1393  *tts = {tt->yearTiny, tt->month, tt->day,
1394  (int16_t) (tt->minutes + offsetMinutes),
1396  *tt = {tt->yearTiny, tt->month, tt->day,
1397  (int16_t) (tt->minutes + (offsetMinutes + deltaMinutes)),
1399  } else {
1400  // Explicit set the suffix to 'w' in case it was something else.
1401  tt->suffix = internal::ZoneContext::kSuffixW;
1402  *tts = {tt->yearTiny, tt->month, tt->day,
1403  (int16_t) (tt->minutes - deltaMinutes),
1405  *ttu = {tt->yearTiny, tt->month, tt->day,
1406  (int16_t) (tt->minutes - (deltaMinutes + offsetMinutes)),
1408  }
1409 
1410  normalizeDateTuple(tt);
1411  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1412  logging::printf("expandDateTuple(): normalizeDateTuple(tt): ");
1413  tt->log();
1414  logging::printf("\n");
1415  }
1416 
1417  normalizeDateTuple(tts);
1418  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1419  logging::printf("expandDateTuple(): normalizeDateTuple(tts): ");
1420  tts->log();
1421  logging::printf("\n");
1422  }
1423 
1424  normalizeDateTuple(ttu);
1425  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1426  logging::printf("expandDateTuple(): normalizeDateTuple(ttu): ");
1427  ttu->log();
1428  logging::printf("\n");
1429  }
1430  }
1431 
1436  static void normalizeDateTuple(extended::DateTuple* dt) {
1437  const int16_t kOneDayAsMinutes = 60 * 24;
1438  if (dt->minutes <= -kOneDayAsMinutes) {
1439  LocalDate ld = LocalDate::forTinyComponents(
1440  dt->yearTiny, dt->month, dt->day);
1441  local_date_mutation::decrementOneDay(ld);
1442  dt->yearTiny = ld.yearTiny();
1443  dt->month = ld.month();
1444  dt->day = ld.day();
1445  dt->minutes += kOneDayAsMinutes;
1446  } else if (kOneDayAsMinutes <= dt->minutes) {
1447  LocalDate ld = LocalDate::forTinyComponents(
1448  dt->yearTiny, dt->month, dt->day);
1449  local_date_mutation::incrementOneDay(ld);
1450  dt->yearTiny = ld.yearTiny();
1451  dt->month = ld.month();
1452  dt->day = ld.day();
1453  dt->minutes -= kOneDayAsMinutes;
1454  } else {
1455  // do nothing
1456  }
1457  }
1458 
1463  static void selectActiveTransitions(
1464  TransitionStorage& transitionStorage,
1465  const ZoneMatch* match) {
1466  Transition** begin = transitionStorage.getCandidatePoolBegin();
1467  Transition** end = transitionStorage.getCandidatePoolEnd();
1468  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1469  logging::printf("selectActiveTransitions(): #candidates: %d\n",
1470  (int) (end - begin));
1471  }
1472  Transition* prior = nullptr;
1473  for (Transition** iter = begin; iter != end; ++iter) {
1474  Transition* transition = *iter;
1475  processActiveTransition(match, transition, &prior);
1476  }
1477 
1478  // If the latest prior transition is found, shift it to start at the
1479  // startDateTime of the current match.
1480  if (prior) {
1481  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1482  logging::printf(
1483  "selectActiveTransitions(): found latest prior\n");
1484  }
1485  prior->originalTransitionTime = prior->transitionTime;
1486  prior->transitionTime = match->startDateTime;
1487  }
1488  }
1489 
1497  static void processActiveTransition(
1498  const ZoneMatch* match,
1499  Transition* transition,
1500  Transition** prior) {
1501  int8_t status = compareTransitionToMatch(transition, match);
1502  if (status == 2) {
1503  transition->active = false;
1504  } else if (status == 1) {
1505  transition->active = true;
1506  } else if (status == 0) {
1507  if (*prior) {
1508  (*prior)->active = false;
1509  }
1510  transition->active = true;
1511  (*prior) = transition;
1512  } else { // (status < 0)
1513  if (*prior) {
1514  if ((*prior)->transitionTime < transition->transitionTime) {
1515  (*prior)->active = false;
1516  transition->active = true;
1517  (*prior) = transition;
1518  }
1519  } else {
1520  transition->active = true;
1521  (*prior) = transition;
1522  }
1523  }
1524  }
1525 
1540  static int8_t compareTransitionToMatch(
1541  const Transition* transition,
1542  const ZoneMatch* match) {
1543  const extended::DateTuple* transitionTime;
1544 
1545  const extended::DateTuple& matchStart = match->startDateTime;
1546  if (matchStart.suffix == internal::ZoneContext::kSuffixS) {
1547  transitionTime = &transition->transitionTimeS;
1548  } else if (matchStart.suffix == internal::ZoneContext::kSuffixU) {
1549  transitionTime = &transition->transitionTimeU;
1550  } else { // assume 'w'
1551  transitionTime = &transition->transitionTime;
1552  }
1553  if (*transitionTime < matchStart) return -1;
1554  if (*transitionTime == matchStart) return 0;
1555 
1556  const extended::DateTuple& matchUntil = match->untilDateTime;
1557  if (matchUntil.suffix == internal::ZoneContext::kSuffixS) {
1558  transitionTime = &transition->transitionTimeS;
1559  } else if (matchUntil.suffix ==
1561  transitionTime = &transition->transitionTimeU;
1562  } else { // assume 'w'
1563  transitionTime = &transition->transitionTime;
1564  }
1565  if (*transitionTime < matchUntil) return 1;
1566  return 2;
1567  }
1568 
1574  static void generateStartUntilTimes(Transition** begin, Transition** end) {
1575  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1576  logging::printf(
1577  "generateStartUntilTimes(): #transitions: %d;\n",
1578  (int) (end - begin));
1579  }
1580 
1581  Transition* prev = *begin;
1582  bool isAfterFirst = false;
1583 
1584  for (Transition** iter = begin; iter != end; ++iter) {
1585  Transition* const t = *iter;
1586 
1587  // 1) Update the untilDateTime of the previous Transition
1588  const extended::DateTuple& tt = t->transitionTime;
1589  if (isAfterFirst) {
1590  prev->untilDateTime = tt;
1591  }
1592 
1593  // 2) Calculate the current startDateTime by shifting the
1594  // transitionTime (represented in the UTC offset of the previous
1595  // transition) into the UTC offset of the *current* transition.
1596  int16_t minutes = tt.minutes + (
1597  - prev->offsetMinutes - prev->deltaMinutes
1598  + t->offsetMinutes + t->deltaMinutes);
1599  t->startDateTime = {tt.yearTiny, tt.month, tt.day, minutes,
1600  tt.suffix};
1601  normalizeDateTuple(&t->startDateTime);
1602 
1603  // 3) The epochSecond of the 'transitionTime' is determined by the
1604  // UTC offset of the *previous* Transition. However, the
1605  // transitionTime can be represented by an illegal time (e.g. 24:00).
1606  // So, it is better to use the properly normalized startDateTime
1607  // (calculated above) with the *current* UTC offset.
1608  //
1609  // NOTE: We should also be able to calculate this directly from
1610  // 'transitionTimeU' which should still be a valid field, because it
1611  // hasn't been clobbered by 'untilDateTime' yet. Not sure if this saves
1612  // any CPU time though, since we still need to mutiply by 900.
1613  const extended::DateTuple& st = t->startDateTime;
1614  const acetime_t offsetSeconds = (acetime_t) 60
1615  * (st.minutes - (t->offsetMinutes + t->deltaMinutes));
1616  LocalDate ld = LocalDate::forTinyComponents(
1617  st.yearTiny, st.month, st.day);
1618  t->startEpochSeconds = ld.toEpochSeconds() + offsetSeconds;
1619 
1620  prev = t;
1621  isAfterFirst = true;
1622  }
1623 
1624  // The last Transition's until time is the until time of the ZoneMatch.
1625  extended::DateTuple untilTime = prev->match->untilDateTime;
1626  extended::DateTuple untilTimeS; // needed only for expandDateTuple
1627  extended::DateTuple untilTimeU; // needed only for expandDateTuple
1628  expandDateTuple(&untilTime, &untilTimeS, &untilTimeU,
1629  prev->offsetMinutes, prev->deltaMinutes);
1630  prev->untilDateTime = untilTime;
1631  }
1632 
1636  static void calcAbbreviations(Transition** begin, Transition** end) {
1637  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1638  logging::printf("calcAbbreviations(): #transitions: %d;\n",
1639  (int) (end - begin));
1640  }
1641  for (Transition** iter = begin; iter != end; ++iter) {
1642  Transition* const t = *iter;
1643  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1644  logging::printf(
1645  "calcAbbreviations(): format:%s, deltaMinutes:%d, letter:%s\n",
1646  t->format(), t->deltaMinutes, t->letter());
1647  }
1648  createAbbreviation(t->abbrev, internal::kAbbrevSize,
1649  t->format(), t->deltaMinutes, t->letter());
1650  }
1651  }
1652 
1661  static void createAbbreviation(char* dest, uint8_t destSize,
1662  const char* format, uint16_t deltaMinutes, const char* letterString) {
1663  // Check if FORMAT contains a '%'.
1664  if (strchr(format, '%') != nullptr) {
1665  // Check if RULES column empty, therefore no 'letter'
1666  if (letterString == nullptr) {
1667  strncpy(dest, format, destSize - 1);
1668  dest[destSize - 1] = '\0';
1669  } else {
1670  ace_common::copyReplaceString(
1671  dest, destSize, format, '%', letterString);
1672  }
1673  } else {
1674  // Check if FORMAT contains a '/'.
1675  const char* slashPos = strchr(format, '/');
1676  if (slashPos != nullptr) {
1677  if (deltaMinutes == 0) {
1678  uint8_t headLength = (slashPos - format);
1679  if (headLength >= destSize) headLength = destSize - 1;
1680  memcpy(dest, format, headLength);
1681  dest[headLength] = '\0';
1682  } else {
1683  uint8_t tailLength = strlen(slashPos+1);
1684  if (tailLength >= destSize) tailLength = destSize - 1;
1685  memcpy(dest, slashPos+1, tailLength);
1686  dest[tailLength] = '\0';
1687  }
1688  } else {
1689  // Just copy the FORMAT disregarding deltaMinutes and letterString.
1690  strncpy(dest, format, destSize);
1691  dest[destSize - 1] = '\0';
1692  }
1693  }
1694  }
1695 
1696  const BF* mBrokerFactory;
1697  ZIB mZoneInfoBroker;
1698 
1699  mutable int16_t mYear = 0; // maybe create LocalDate::kInvalidYear?
1700  mutable bool mIsFilled = false;
1701  // NOTE: Maybe move mNumMatches and mMatches into a MatchStorage object.
1702  mutable uint8_t mNumMatches = 0; // actual number of matches
1703  mutable ZoneMatch mMatches[kMaxMatches];
1704  mutable TransitionStorage mTransitionStorage;
1705 };
1706 
1707 
1713  extended::BrokerFactory,
1714  extended::ZoneInfoBroker,
1715  extended::ZoneEraBroker,
1716  extended::ZonePolicyBroker,
1717  extended::ZoneRuleBroker> {
1718 
1719  public:
1721  static const uint8_t kTypeExtended = 4;
1722 
1723  explicit ExtendedZoneProcessor(const extended::ZoneInfo* zoneInfo = nullptr)
1725  extended::BrokerFactory,
1726  extended::ZoneInfoBroker,
1727  extended::ZoneEraBroker,
1728  extended::ZonePolicyBroker,
1729  extended::ZoneRuleBroker>(
1730  kTypeExtended, &mBrokerFactory, (uintptr_t) zoneInfo)
1731  {}
1732 
1733  private:
1734  extended::BrokerFactory mBrokerFactory;
1735 };
1736 
1737 } // namespace ace_time
1738 
1739 #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:465
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:200
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:481
ace_time::ExtendedZoneProcessorTemplate::getTransitionHighWater
uint8_t getTransitionHighWater() const
Get the TransitionStorage high water mark.
Definition: ExtendedZoneProcessor.h:823
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:432
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:472
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:628
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:712
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 ExtendedZoneProcessor.
Definition: ExtendedZoneProcessor.h:1721
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:1712
ace_time::extended::TransitionStorageTemplate::resetCandidatePool
void resetCandidatePool()
Empty the Candidate pool by resetting the various indexes.
Definition: ExtendedZoneProcessor.h:408
ace_time::ExtendedZoneProcessorTemplate::TransitionStorage
extended::TransitionStorageTemplate< kMaxTransitions, ZEB, ZPB, ZRB > TransitionStorage
Exposed only for testing purposes.
Definition: ExtendedZoneProcessor.h:710
ace_time::ExtendedZoneProcessorTemplate::getAbbrev
const char * getAbbrev(acetime_t epochSeconds) const override
Return the time zone abbreviation at epochSeconds.
Definition: ExtendedZoneProcessor.h:731
ace_time::LocalDateTime::month
uint8_t month() const
Return the month with January=1, December=12.
Definition: LocalDateTime.h:188
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:853
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:714
ace_time::ExtendedZoneProcessorTemplate::printShortNameTo
void printShortNameTo(Print &printer) const override
Print a short human-readable identifier (e.g.
Definition: ExtendedZoneProcessor.h:800
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:571
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:700
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:738
ace_time::LocalDateTime::minute
uint8_t minute() const
Return the minute.
Definition: LocalDateTime.h:206
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:221
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:178
ExtendedBrokers.h
ace_time::ExtendedZoneProcessorTemplate::ZoneMatch
extended::ZoneMatchTemplate< ZEB > ZoneMatch
Exposed only for testing purposes.
Definition: ExtendedZoneProcessor.h:706
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:827
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:837
ace_time::extended::TransitionStorageTemplate::resetHighWater
void resetHighWater()
Reset the high water mark.
Definition: ExtendedZoneProcessor.h:621
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:805
ace_time::extended::TransitionStorageTemplate::swap
static void swap(Transition **a, Transition **b)
Swap 2 transitions.
Definition: ExtendedZoneProcessor.h:394
ace_time::extended::TransitionStorageTemplate::addFreeAgentToActivePool
void addFreeAgentToActivePool()
Immediately add the free agent Transition at index mIndexFree to the Active pool.
Definition: ExtendedZoneProcessor.h:453
ace_time::LocalDateTime::day
uint8_t day() const
Return the day of the month.
Definition: LocalDateTime.h:194
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:796
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:724
ace_time::ExtendedZoneProcessorTemplate::resetTransitionHighWater
void resetTransitionHighWater()
Reset the TransitionStorage high water mark.
Definition: ExtendedZoneProcessor.h:818
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:703
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:491
ace_time::extended::TransitionStorageTemplate::findTransition
const Transition * findTransition(acetime_t epochSeconds) const
Return the Transition matching the given epochSeconds.
Definition: ExtendedZoneProcessor.h:534
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:690
ace_time::extended::TransitionStorageTemplate::addActiveCandidatesToActivePool
void addActiveCandidatesToActivePool()
Add active candidates into the Active pool, and collapse the Candidate pool.
Definition: ExtendedZoneProcessor.h:507
ace_time::extended::TransitionStorageTemplate::log
void log() const
Verify that the indexes are valid.
Definition: ExtendedZoneProcessor.h:594