AceTime  2.1.0
Date and time classes for Arduino that support timezones from the TZ Database.
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 <stdint.h> // uintptr_t
10 #include <string.h> // memcpy(), strncpy()
11 #include <AceCommon.h> // copyReplaceString()
12 #include "common/compat.h"
13 #include "internal/common.h" // kAbbrevSize
14 #include "internal/ZonePolicy.h"
15 #include "internal/ZoneInfo.h"
17 #include "common/logging.h"
18 #include "TimeOffset.h"
19 #include "LocalDate.h"
20 #include "ZoneProcessor.h"
21 #include "Transition.h"
22 
23 #ifndef ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG
24 #define ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG 0
25 #endif
26 
27 class ExtendedZoneProcessorTest_compareEraToYearMonth;
28 class ExtendedZoneProcessorTest_compareEraToYearMonth2;
29 class ExtendedZoneProcessorTest_createMatchingEra;
30 class ExtendedZoneProcessorTest_findMatches_simple;
31 class ExtendedZoneProcessorTest_findMatches_named;
32 class ExtendedZoneProcessorTest_findCandidateTransitions;
33 class ExtendedZoneProcessorTest_createTransitionsFromNamedMatch;
34 class ExtendedZoneProcessorTest_getTransitionTime;
35 class ExtendedZoneProcessorTest_createTransitionForYear;
36 class ExtendedZoneProcessorTest_normalizeDateTuple;
37 class ExtendedZoneProcessorTest_expandDateTuple;
38 class ExtendedZoneProcessorTest_calcInteriorYears;
39 class ExtendedZoneProcessorTest_getMostRecentPriorYear;
40 class ExtendedZoneProcessorTest_compareTransitionToMatchFuzzy;
41 class ExtendedZoneProcessorTest_compareTransitionToMatch;
42 class ExtendedZoneProcessorTest_processTransitionMatchStatus;
43 class ExtendedZoneProcessorTest_fixTransitionTimes_generateStartUntilTimes;
44 class ExtendedZoneProcessorTest_createAbbreviation;
45 class ExtendedZoneProcessorTest_setZoneKey;
46 class ExtendedTransitionValidation;
47 
48 class Print;
49 
50 namespace ace_time {
51 
52 namespace extended {
53 
56  int16_t year;
57  uint8_t month;
58 };
59 
60 }
61 
93 template <typename BF, typename ZIB, typename ZEB, typename ZPB, typename ZRB>
95  public:
106  static const uint8_t kMaxTransitions = 8;
107 
110 
114 
118 
121 
125 
126  bool isLink() const override {
127  return ! mZoneInfoBroker.targetInfo().isNull();
128  }
129 
130  uint32_t getZoneId() const override {
131  return mZoneInfoBroker.zoneId();
132  }
133 
134  FindResult findByLocalDateTime(const LocalDateTime& ldt) const override {
135  FindResult result;
136 
137  bool success = initForYear(ldt.year());
138  if (! success) {
139  return result;
140  }
141 
142  // Find the Transition(s) in the gap or overlap.
143  TransitionForDateTime transitionForDateTime =
144  mTransitionStorage.findTransitionForDateTime(ldt);
145 
146  // Extract the target Transition, depending on the requested ldt.fold
147  // and the result.num.
148  const Transition* transition;
149  if (transitionForDateTime.num == 1) {
150  transition = transitionForDateTime.curr;
151  result.type = FindResult::kTypeExact;
152  result.reqStdOffsetMinutes = transition->offsetMinutes;
153  result.reqDstOffsetMinutes = transition->deltaMinutes;
154  } else { // num = 0 or 2
155  if (transitionForDateTime.prev == nullptr
156  || transitionForDateTime.curr == nullptr) {
157  // ldt was far past or far future
158  transition = nullptr;
159  result.type = FindResult::kTypeNotFound;
160  } else { // gap or overlap
161  if (transitionForDateTime.num == 0) { // num==0, Gap
162  result.type = FindResult::kTypeGap;
163  if (ldt.fold() == 0) {
164  // ldt wants to use the 'prev' transition to convert to
165  // epochSeconds.
166  result.reqStdOffsetMinutes =
167  transitionForDateTime.prev->offsetMinutes;
168  result.reqDstOffsetMinutes =
169  transitionForDateTime.prev->deltaMinutes;
170  // But after normalization, it will be shifted into the curr
171  // transition, so select 'curr' as the target transition.
172  transition = transitionForDateTime.curr;
173  } else {
174  // ldt wants to use the 'curr' transition to convert to
175  // epochSeconds.
176  result.reqStdOffsetMinutes =
177  transitionForDateTime.curr->offsetMinutes;
178  result.reqDstOffsetMinutes =
179  transitionForDateTime.curr->deltaMinutes;
180  // But after normalization, it will be shifted into the prev
181  // transition, so select 'prev' as the target transition.
182  transition = transitionForDateTime.prev;
183  }
184  } else { // num==2, Overlap
185  transition = (ldt.fold() == 0)
186  ? transitionForDateTime.prev
187  : transitionForDateTime.curr;
188  result.type = FindResult::kTypeOverlap;
189  result.reqStdOffsetMinutes = transition->offsetMinutes;
190  result.reqDstOffsetMinutes = transition->deltaMinutes;
191  result.fold = ldt.fold();
192  }
193  }
194  }
195 
196  if (! transition) {
197  return result;
198  }
199 
200  result.stdOffsetMinutes = transition->offsetMinutes;
201  result.dstOffsetMinutes = transition->deltaMinutes;
202  result.abbrev = transition->abbrev;
203 
204  return result;
205  }
206 
215  FindResult findByEpochSeconds(acetime_t epochSeconds) const override {
216  FindResult result;
217  bool success = initForEpochSeconds(epochSeconds);
218  if (!success) return result;
219 
220  TransitionForSeconds transitionForSeconds =
221  mTransitionStorage.findTransitionForSeconds(epochSeconds);
222  const Transition* transition = transitionForSeconds.curr;
223  if (!transition) return result;
224 
225  result.stdOffsetMinutes = transition->offsetMinutes;
226  result.dstOffsetMinutes = transition->deltaMinutes;
227  result.reqStdOffsetMinutes = transition->offsetMinutes;
228  result.reqDstOffsetMinutes = transition->deltaMinutes;
229  result.abbrev = transition->abbrev;
230  result.fold = transitionForSeconds.fold;
231  if (transitionForSeconds.num == 2) {
232  result.type = FindResult::kTypeOverlap;
233  } else {
234  result.type = FindResult::kTypeExact;
235  }
236  return result;
237  }
238 
239  void printNameTo(Print& printer) const override {
240  mZoneInfoBroker.printNameTo(printer);
241  }
242 
243  void printShortNameTo(Print& printer) const override {
244  mZoneInfoBroker.printShortNameTo(printer);
245  }
246 
247  void printTargetNameTo(Print& printer) const override {
248  if (isLink()) {
249  mZoneInfoBroker.targetInfo().printNameTo(printer);
250  }
251  }
252 
254  void log() const {
255  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
256  logging::printf("ExtendedZoneProcessor:\n");
257  logging::printf(" mYear: %d\n", mYear);
258  logging::printf(" mNumMatches: %d\n", mNumMatches);
259  for (int i = 0; i < mNumMatches; i++) {
260  logging::printf(" Match %d: ", i);
261  mMatches[i].log();
262  logging::printf("\n");
263  }
264  mTransitionStorage.log();
265  }
266  }
267 
270  mTransitionStorage.resetAllocSize();
271  }
272 
274  uint8_t getTransitionAllocSize() const {
275  return mTransitionStorage.getAllocSize();
276  }
277 
278  void setZoneKey(uintptr_t zoneKey) override {
279  if (! mBrokerFactory) return;
280  if (mZoneInfoBroker.equals(zoneKey)) return;
281 
282  mZoneInfoBroker = mBrokerFactory->createZoneInfoBroker(zoneKey);
283  mYear = LocalDate::kInvalidYear;
284  mIsFilled = false;
285  mNumMatches = 0;
286  resetTransitionAllocSize(); // clear the alloc size for new zone
287  }
288 
289  bool equalsZoneKey(uintptr_t zoneKey) const override {
290  return mZoneInfoBroker.equals(zoneKey);
291  }
292 
299  void setBrokerFactory(const BF* brokerFactory) {
300  mBrokerFactory = brokerFactory;
301  }
302 
308  bool initForEpochSeconds(acetime_t epochSeconds) const {
309  LocalDate ld = LocalDate::forEpochSeconds(epochSeconds);
310  return initForYear(ld.year());
311  }
312 
318  bool initForYear(int16_t year) const {
319  if (isFilled(year)) return true;
320  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
321  logging::printf("initForYear(): %d\n", year);
322  }
323 
324  mYear = year;
325  mNumMatches = 0; // clear cache
326  mTransitionStorage.init();
327 
328  if (year < mZoneInfoBroker.zoneContext()->startYear - 1
329  || mZoneInfoBroker.zoneContext()->untilYear < year) {
330  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
331  logging::printf(
332  "initForYear(): Year %d out of valid range [%d, %d)\n",
333  year,
334  mZoneInfoBroker.zoneContext()->startYear,
335  mZoneInfoBroker.zoneContext()->untilYear);
336  }
337  return false;
338  }
339 
340  extended::YearMonthTuple startYm = { (int16_t) (year - 1), 12 };
341  extended::YearMonthTuple untilYm = { (int16_t) (year + 1), 2 };
342 
343  // Step 1. The equivalent steps for the Python version are in the
344  // AceTimePython project, under
345  // zone_processor.ZoneProcessor.init_for_year().
346  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
347  logging::printf("==== Step 1: findMatches()\n");
348  }
349  mNumMatches = findMatches(mZoneInfoBroker, startYm, untilYm, mMatches,
350  kMaxMatches);
351  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
352 
353  // Step 2
354  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
355  logging::printf("==== Step 2: createTransitions()\n");
356  }
357  createTransitions(mTransitionStorage, mMatches, mNumMatches);
358  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
359 
360  // Step 3
361  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
362  logging::printf("==== Step 3: fixTransitionTimes()\n");
363  }
364  Transition** begin = mTransitionStorage.getActivePoolBegin();
365  Transition** end = mTransitionStorage.getActivePoolEnd();
366  fixTransitionTimes(begin, end);
367  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
368 
369  // Step 4
370  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
371  logging::printf("==== Step 4: generateStartUntilTimes()\n");
372  }
373  generateStartUntilTimes(begin, end);
374  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
375 
376  // Step 5
377  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
378  logging::printf("==== Step 5: calcAbbreviations()\n");
379  }
380  calcAbbreviations(begin, end);
381  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
382 
383  mIsFilled = true;
384  return true;
385  }
386 
387  protected:
399  uint8_t type,
400  const BF* brokerFactory /*nullable*/,
401  uintptr_t zoneKey
402  ) :
403  ZoneProcessor(type),
404  mBrokerFactory(brokerFactory)
405  {
406  setZoneKey(zoneKey);
407  }
408 
409  private:
410  friend class ::ExtendedZoneProcessorTest_compareEraToYearMonth;
411  friend class ::ExtendedZoneProcessorTest_compareEraToYearMonth2;
412  friend class ::ExtendedZoneProcessorTest_createMatchingEra;
413  friend class ::ExtendedZoneProcessorTest_findMatches_simple;
414  friend class ::ExtendedZoneProcessorTest_findMatches_named;
415  friend class ::ExtendedZoneProcessorTest_findCandidateTransitions;
416  friend class ::ExtendedZoneProcessorTest_createTransitionsFromNamedMatch;
417  friend class ::ExtendedZoneProcessorTest_getTransitionTime;
418  friend class ::ExtendedZoneProcessorTest_createTransitionForYear;
419  friend class ::ExtendedZoneProcessorTest_normalizeDateTuple;
420  friend class ::ExtendedZoneProcessorTest_expandDateTuple;
421  friend class ::ExtendedZoneProcessorTest_calcInteriorYears;
422  friend class ::ExtendedZoneProcessorTest_getMostRecentPriorYear;
423  friend class ::ExtendedZoneProcessorTest_compareTransitionToMatchFuzzy;
424  friend class ::ExtendedZoneProcessorTest_compareTransitionToMatch;
425  friend class ::ExtendedZoneProcessorTest_processTransitionMatchStatus;
426  friend class ::ExtendedZoneProcessorTest_fixTransitionTimes_generateStartUntilTimes;
427  friend class ::ExtendedZoneProcessorTest_createAbbreviation;
428  friend class ::ExtendedZoneProcessorTest_setZoneKey;
429  friend class ::ExtendedTransitionValidation;
430 
431  // Disable copy constructor and assignment operator.
433  const ExtendedZoneProcessorTemplate&) = delete;
435  const ExtendedZoneProcessorTemplate&) = delete;
436 
441  static const uint8_t kMaxMatches = 4;
442 
447  static const uint8_t kMaxInteriorYears = 4;
448 
449  bool equals(const ZoneProcessor& other) const override {
450  return mZoneInfoBroker.equals(
451  ((const ExtendedZoneProcessorTemplate&) other).mZoneInfoBroker);
452  }
453 
461  static uint8_t findMatches(
462  const ZIB& zoneInfo,
463  const extended::YearMonthTuple& startYm,
464  const extended::YearMonthTuple& untilYm,
465  MatchingEra* matches,
466  uint8_t maxMatches
467  ) {
468  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
469  logging::printf("findMatches()\n");
470  }
471  uint8_t iMatch = 0;
472  MatchingEra* prevMatch = nullptr;
473  for (uint8_t iEra = 0; iEra < zoneInfo.numEras(); iEra++) {
474  const ZEB era = zoneInfo.era(iEra);
475  if (eraOverlapsInterval(prevMatch, era, startYm, untilYm)) {
476  if (iMatch < maxMatches) {
477  matches[iMatch] = createMatchingEra(
478  prevMatch, era, startYm, untilYm);
479  prevMatch = &matches[iMatch];
480  iMatch++;
481  }
482  }
483  }
484  return iMatch;
485  }
486 
498  static bool eraOverlapsInterval(
499  const MatchingEra* prevMatch,
500  const ZEB& era,
501  const extended::YearMonthTuple& startYm,
502  const extended::YearMonthTuple& untilYm) {
503  return (prevMatch == nullptr || compareEraToYearMonth(
504  prevMatch->era, untilYm.year, untilYm.month) < 0)
505  && compareEraToYearMonth(era, startYm.year, startYm.month) > 0;
506  }
507 
509  static int8_t compareEraToYearMonth(const ZEB& era,
510  int16_t year, uint8_t month) {
511  if (era.untilYear() < year) return -1;
512  if (era.untilYear() > year) return 1;
513  if (era.untilMonth() < month) return -1;
514  if (era.untilMonth() > month) return 1;
515  if (era.untilDay() > 1) return 1;
516  //if (era.untilTimeMinutes() < 0) return -1; // never possible
517  if (era.untilTimeMinutes() > 0) return 1;
518  return 0;
519  }
520 
527  static MatchingEra createMatchingEra(
528  MatchingEra* prevMatch,
529  const ZEB& era,
530  const extended::YearMonthTuple& startYm,
531  const extended::YearMonthTuple& untilYm) {
532 
533  // If prevMatch is null, set startDate to be earlier than all valid
534  // ZoneEra.
535  extended::DateTuple startDate = (prevMatch == nullptr)
536  ? extended::DateTuple{
538  1,
539  1,
540  0,
542  }
543  : extended::DateTuple{
544  prevMatch->era.untilYear(),
545  prevMatch->era.untilMonth(),
546  prevMatch->era.untilDay(),
547  (int16_t) prevMatch->era.untilTimeMinutes(),
548  prevMatch->era.untilTimeSuffix()
549  };
550  extended::DateTuple lowerBound{
551  startYm.year,
552  startYm.month,
553  1,
554  0,
556  };
557  if (startDate < lowerBound) {
558  startDate = lowerBound;
559  }
560 
561  extended::DateTuple untilDate{
562  era.untilYear(),
563  era.untilMonth(),
564  era.untilDay(),
565  (int16_t) era.untilTimeMinutes(),
566  era.untilTimeSuffix()
567  };
568  extended::DateTuple upperBound{
569  untilYm.year,
570  untilYm.month,
571  1,
572  0,
574  };
575  if (upperBound < untilDate) {
576  untilDate = upperBound;
577  }
578 
579  return {startDate, untilDate, era, prevMatch, 0, 0};
580  }
581 
586  static void createTransitions(
587  TransitionStorage& transitionStorage,
588  MatchingEra* matches,
589  uint8_t numMatches) {
590  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
591  logging::printf("createTransitions()\n");
592  }
593 
594  for (uint8_t i = 0; i < numMatches; i++) {
595  createTransitionsForMatch(transitionStorage, &matches[i]);
596  }
597  }
598 
600  static void createTransitionsForMatch(
601  TransitionStorage& transitionStorage,
602  MatchingEra* match) {
603  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
604  logging::printf("== createTransitionsForMatch()\n");
605  }
606  const ZPB policy = match->era.zonePolicy();
607  if (policy.isNull()) {
608  createTransitionsFromSimpleMatch(transitionStorage, match);
609  } else {
610  createTransitionsFromNamedMatch(transitionStorage, match);
611  }
612  }
613 
614  // Step 2A
615  static void createTransitionsFromSimpleMatch(
616  TransitionStorage& transitionStorage,
617  MatchingEra* match) {
618  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
619  logging::printf("== createTransitionsFromSimpleMatch()\n");
620  }
621 
622  Transition* freeTransition = transitionStorage.getFreeAgent();
623  createTransitionForYear(freeTransition, 0 /*not used*/,
624  ZRB() /*rule*/, match);
625  freeTransition->matchStatus = extended::MatchStatus::kExactMatch;
626  match->lastOffsetMinutes = freeTransition->offsetMinutes;
627  match->lastDeltaMinutes = freeTransition->deltaMinutes;
628  transitionStorage.addFreeAgentToActivePool();
629  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
630  transitionStorage.log();
631  }
632  }
633 
634  // Step 2B
635  static void createTransitionsFromNamedMatch(
636  TransitionStorage& transitionStorage,
637  MatchingEra* match) {
638  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
639  logging::printf("== createTransitionsFromNamedMatch()\n");
640  }
641 
642  transitionStorage.resetCandidatePool();
643  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
644  match->log(); logging::printf("\n");
645  }
646 
647  // Pass 1: Find candidate transitions using whole years.
648  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
649  logging::printf("---- Pass 1: findCandidateTransitions()\n");
650  }
651  findCandidateTransitions(transitionStorage, match);
652  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
653  transitionStorage.log();
654  }
655 
656  // Pass 2: Fix the transitions times, converting 's' and 'u' into 'w'
657  // uniformly.
658  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
659  logging::printf("---- Pass 2: fixTransitionTimes()\n");
660  }
661  fixTransitionTimes(
662  transitionStorage.getCandidatePoolBegin(),
663  transitionStorage.getCandidatePoolEnd());
664 
665  // Pass 3: Select only those Transitions which overlap with the actual
666  // start and until times of the MatchingEra.
667  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
668  logging::printf("---- Pass 3: selectActiveTransitions()\n");
669  }
670  selectActiveTransitions(
671  transitionStorage.getCandidatePoolBegin(),
672  transitionStorage.getCandidatePoolEnd());
673  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
674  transitionStorage.log();
675  }
676  Transition* lastTransition =
677  transitionStorage.addActiveCandidatesToActivePool();
678  match->lastOffsetMinutes = lastTransition->offsetMinutes;
679  match->lastDeltaMinutes = lastTransition->deltaMinutes;
680  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
681  transitionStorage.log();
682  }
683  }
684 
685  // Step 2B: Pass 1
686  static void findCandidateTransitions(
687  TransitionStorage& transitionStorage,
688  const MatchingEra* match) {
689  using extended::MatchStatus;
690 
691  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
692  logging::printf("findCandidateTransitions(): \n");
693  match->log();
694  logging::printf("\n");
695  }
696  const ZPB policy = match->era.zonePolicy();
697  uint8_t numRules = policy.numRules();
698  int16_t startY = match->startDateTime.year;
699  int16_t endY = match->untilDateTime.year;
700 
701  // The prior is referenced through a handle (i.e. pointer to pointer)
702  // because the actual pointer to the prior could change through the
703  // transitionStorage.setFreeAgentAsPriorIfValid() method.
704  Transition** prior = transitionStorage.reservePrior();
705  (*prior)->isValidPrior = false; // indicates "no prior transition"
706  for (uint8_t r = 0; r < numRules; r++) {
707  const ZRB rule = policy.rule(r);
708 
709  // Add Transitions for interior years
710  int16_t interiorYears[kMaxInteriorYears];
711  uint8_t numYears = calcInteriorYears(interiorYears, kMaxInteriorYears,
712  rule.fromYear(), rule.toYear(), startY, endY);
713  for (uint8_t y = 0; y < numYears; y++) {
714  int16_t year = interiorYears[y];
715  Transition* t = transitionStorage.getFreeAgent();
716  createTransitionForYear(t, year, rule, match);
717  MatchStatus status = compareTransitionToMatchFuzzy(t, match);
718  if (status == MatchStatus::kPrior) {
719  transitionStorage.setFreeAgentAsPriorIfValid();
720  } else if (status == MatchStatus::kWithinMatch) {
721  transitionStorage.addFreeAgentToCandidatePool();
722  } else {
723  // Must be kFarFuture.
724  // Do nothing, allowing the free agent to be reused.
725  }
726  }
727 
728  // Add Transition for prior year
729  int16_t priorYear = getMostRecentPriorYear(
730  rule.fromYear(), rule.toYear(), startY, endY);
731  if (priorYear != LocalDate::kInvalidYear) {
732  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
733  logging::printf(
734  "findCandidateTransitions(): priorYear: %d\n", priorYear);
735  }
736  Transition* t = transitionStorage.getFreeAgent();
737  createTransitionForYear(t, priorYear, rule, match);
738  transitionStorage.setFreeAgentAsPriorIfValid();
739  }
740  }
741 
742  // Add the reserved prior into the Candidate pool only if 'isValidPrior'
743  // is true.
744  if ((*prior)->isValidPrior) {
745  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
746  logging::printf(
747  "findCandidateTransitions(): adding prior to Candidate pool\n");
748  logging::printf(" ");
749  (*prior)->log();
750  logging::printf("\n");
751  }
752  transitionStorage.addPriorToCandidatePool();
753  }
754  }
755 
775  static uint8_t calcInteriorYears(
776  int16_t* interiorYears,
777  uint8_t maxInteriorYears,
778  int16_t fromYear, int16_t toYear,
779  int16_t startYear, int16_t endYear) {
780  uint8_t i = 0;
781  for (int16_t year = startYear; year <= endYear; year++) {
782  if (fromYear <= year && year <= toYear) {
783  interiorYears[i] = year;
784  i++;
785  if (i >= maxInteriorYears) break;
786  }
787  }
788  return i;
789  }
790 
797  static void createTransitionForYear(
798  Transition* t,
799  int16_t year,
800  const ZRB& rule,
801  const MatchingEra* match) {
802  t->match = match;
803  t->rule = rule;
804  t->offsetMinutes = match->era.offsetMinutes();
805  t->letterBuf[0] = '\0';
806 
807  if (! rule.isNull()) {
808  t->transitionTime = getTransitionTime(year, rule);
809  t->deltaMinutes = rule.deltaMinutes();
810 
811  char letter = rule.letter();
812  if (letter >= 32) {
813  // If LETTER is a '-', treat it the same as an empty string.
814  if (letter != '-') {
815  t->letterBuf[0] = letter;
816  t->letterBuf[1] = '\0';
817  }
818  } else {
819  // rule->letter is a long string, so is referenced as an offset index
820  // into the ZonePolicy.letters array. The string cannot fit in
821  // letterBuf, so will be retrieved by the letter() method below.
822  }
823  } else {
824  // Create a Transition using the MatchingEra for the transitionTime.
825  // Used for simple MatchingEra.
826  t->transitionTime = match->startDateTime;
827  t->deltaMinutes = match->era.deltaMinutes();
828  }
829  }
830 
843  static int16_t getMostRecentPriorYear(
844  int16_t fromYear, int16_t toYear,
845  int16_t startYear, int16_t /*endYear*/) {
846 
847  if (fromYear < startYear) {
848  if (toYear < startYear) {
849  return toYear;
850  } else {
851  return startYear - 1;
852  }
853  } else {
855  }
856  }
857 
862  static extended::DateTuple getTransitionTime(
863  int16_t year, const ZRB& rule) {
864 
865  internal::MonthDay monthDay = internal::calcStartDayOfMonth(
866  year,
867  rule.inMonth(),
868  rule.onDayOfWeek(),
869  rule.onDayOfMonth());
870  return {
871  year,
872  monthDay.month,
873  monthDay.day,
874  (int16_t) rule.atTimeMinutes(),
875  rule.atTimeSuffix()
876  };
877  }
878 
889  static extended::MatchStatus compareTransitionToMatchFuzzy(
890  const Transition* t, const MatchingEra* match) {
891  return compareDateTupleFuzzy(
892  t->transitionTime,
893  match->startDateTime,
894  match->untilDateTime);
895  }
896 
905  static void fixTransitionTimes(Transition** begin, Transition** end) {
906  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
907  logging::printf("fixTransitionTimes(): START; #transitions=%d\n",
908  (int) (end - begin));
909  Transition::printTransitions(" ", begin, end);
910  }
911 
912  // extend first Transition to -infinity
913  Transition* prev = *begin;
914 
915  for (Transition** iter = begin; iter != end; ++iter) {
916  Transition* curr = *iter;
917  expandDateTuple(
918  &curr->transitionTime,
919  prev->offsetMinutes,
920  prev->deltaMinutes,
921  &curr->transitionTime,
922  &curr->transitionTimeS,
923  &curr->transitionTimeU);
924  prev = curr;
925  }
926  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
927  logging::printf("fixTransitionTimes(): FIXED\n");
928  Transition::printTransitions(" ", begin, end);
929  logging::printf("fixTransitionTimes(): END\n");
930  }
931  }
932 
938  static void expandDateTuple(
939  const extended::DateTuple* tt,
940  int16_t offsetMinutes,
941  int16_t deltaMinutes,
942  extended::DateTuple* ttw,
943  extended::DateTuple* tts,
944  extended::DateTuple* ttu) {
945 
946  if (tt->suffix == internal::ZoneContext::kSuffixS) {
947  *tts = *tt;
948  *ttu = {tt->year, tt->month, tt->day,
949  (int16_t) (tt->minutes - offsetMinutes),
951  *ttw = {tt->year, tt->month, tt->day,
952  (int16_t) (tt->minutes + deltaMinutes),
954  } else if (tt->suffix == internal::ZoneContext::kSuffixU) {
955  *ttu = *tt;
956  *tts = {tt->year, tt->month, tt->day,
957  (int16_t) (tt->minutes + offsetMinutes),
959  *ttw = {tt->year, tt->month, tt->day,
960  (int16_t) (tt->minutes + (offsetMinutes + deltaMinutes)),
962  } else {
963  // Explicit set the suffix to 'w' in case it was something else.
964  *ttw = *tt;
965  ttw->suffix = internal::ZoneContext::kSuffixW;
966  *tts = {tt->year, tt->month, tt->day,
967  (int16_t) (tt->minutes - deltaMinutes),
969  *ttu = {tt->year, tt->month, tt->day,
970  (int16_t) (tt->minutes - (deltaMinutes + offsetMinutes)),
972  }
973 
974  extended::normalizeDateTuple(ttw);
975  extended::normalizeDateTuple(tts);
976  extended::normalizeDateTuple(ttu);
977  }
978 
983  static void selectActiveTransitions(Transition** begin, Transition** end) {
984  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
985  logging::printf("selectActiveTransitions(): #candidates: %d\n",
986  (int) (end - begin));
987  }
988 
989  Transition* prior = nullptr;
990  for (Transition** iter = begin; iter != end; ++iter) {
991  Transition* transition = *iter;
992  processTransitionMatchStatus(transition, &prior);
993  }
994 
995  // If the latest prior transition is found, shift it to start at the
996  // startDateTime of the current match.
997  if (prior) {
998  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
999  logging::printf(
1000  "selectActiveTransitions(): found latest prior\n");
1001  }
1002  #if ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG
1003  prior->originalTransitionTime = prior->transitionTime;
1004  #endif
1005  prior->transitionTime = prior->match->startDateTime;
1006  }
1007  }
1008 
1015  static void processTransitionMatchStatus(
1016  Transition* transition,
1017  Transition** prior) {
1018  using extended::MatchStatus;
1019 
1020  MatchStatus status = compareTransitionToMatch(
1021  transition, transition->match);
1022  transition->matchStatus = status;
1023 
1024  if (status == MatchStatus::kExactMatch) {
1025  if (*prior) {
1026  (*prior)->matchStatus = MatchStatus::kFarPast;
1027  }
1028  (*prior) = transition;
1029  } else if (status == MatchStatus::kPrior) {
1030  if (*prior) {
1031  if ((*prior)->transitionTimeU <= transition->transitionTimeU) {
1032  (*prior)->matchStatus = MatchStatus::kFarPast;
1033  (*prior) = transition;
1034  } else {
1035  transition->matchStatus = MatchStatus::kFarPast;
1036  }
1037  } else {
1038  (*prior) = transition;
1039  }
1040  }
1041  }
1042 
1051  static extended::MatchStatus compareTransitionToMatch(
1052  const Transition* transition,
1053  const MatchingEra* match) {
1054 
1055  // Find the previous Match offsets.
1056  int16_t prevMatchOffsetMinutes;
1057  int16_t prevMatchDeltaMinutes;
1058  if (match->prevMatch) {
1059  prevMatchOffsetMinutes = match->prevMatch->lastOffsetMinutes;
1060  prevMatchDeltaMinutes = match->prevMatch->lastDeltaMinutes;
1061  } else {
1062  prevMatchOffsetMinutes = match->era.offsetMinutes();
1063  prevMatchDeltaMinutes = 0;
1064  }
1065 
1066  // Expand start times.
1067  extended::DateTuple stw;
1068  extended::DateTuple sts;
1069  extended::DateTuple stu;
1070  expandDateTuple(
1071  &match->startDateTime,
1072  prevMatchOffsetMinutes,
1073  prevMatchDeltaMinutes,
1074  &stw,
1075  &sts,
1076  &stu);
1077 
1078  // Transition times.
1079  const extended::DateTuple& ttw = transition->transitionTime;
1080  const extended::DateTuple& tts = transition->transitionTimeS;
1081  const extended::DateTuple& ttu = transition->transitionTimeU;
1082 
1083  // Compare Transition to Match, where equality is assumed if *any* of the
1084  // 'w', 's', or 'u' versions of the DateTuple are equal. This prevents
1085  // duplicate Transition instances from being created in a few cases.
1086  if (ttw == stw || tts == sts || ttu == stu) {
1087  return extended::MatchStatus::kExactMatch;
1088  }
1089 
1090  if (ttu < stu) {
1091  return extended::MatchStatus::kPrior;
1092  }
1093 
1094  // Now check if the transition occurs after the given match. The
1095  // untilDateTime of the current match uses the same UTC offsets as the
1096  // transitionTime of the current transition, so no complicated adjustments
1097  // are needed. We just make sure we compare 'w' with 'w', 's' with 's',
1098  // and 'u' with 'u'.
1099  const extended::DateTuple& matchUntil = match->untilDateTime;
1100  const extended::DateTuple* transitionTime;
1101  if (matchUntil.suffix == internal::ZoneContext::kSuffixS) {
1102  transitionTime = &tts;
1103  } else if (matchUntil.suffix == internal::ZoneContext::kSuffixU) {
1104  transitionTime = &ttu;
1105  } else { // assume 'w'
1106  transitionTime = &ttw;
1107  }
1108  if (*transitionTime < matchUntil) {
1109  return extended::MatchStatus::kWithinMatch;
1110  }
1111  return extended::MatchStatus::kFarFuture;
1112  }
1113 
1119  static void generateStartUntilTimes(Transition** begin, Transition** end) {
1120  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1121  logging::printf(
1122  "generateStartUntilTimes(): #transitions=%d\n",
1123  (int) (end - begin));
1124  }
1125 
1126  // It is possible that there are no matching transitions. This can happen
1127  // if the zonedbx is corrupted and ZoneInfo contains invalid fields.
1128  if (begin == end) return;
1129 
1130  Transition* prev = *begin;
1131  bool isAfterFirst = false;
1132 
1133  for (Transition** iter = begin; iter != end; ++iter) {
1134  Transition* const t = *iter;
1135 
1136  // 1) Update the untilDateTime of the previous Transition
1137  const extended::DateTuple& tt = t->transitionTime;
1138  if (isAfterFirst) {
1139  prev->untilDateTime = tt;
1140  }
1141 
1142  // 2) Calculate the current startDateTime by shifting the
1143  // transitionTime (represented in the UTC offset of the previous
1144  // transition) into the UTC offset of the *current* transition.
1145  int16_t minutes = tt.minutes + (
1146  - prev->offsetMinutes - prev->deltaMinutes
1147  + t->offsetMinutes + t->deltaMinutes);
1148  t->startDateTime = {tt.year, tt.month, tt.day, minutes, tt.suffix};
1149  extended::normalizeDateTuple(&t->startDateTime);
1150 
1151  // 3) The epochSecond of the 'transitionTime' is determined by the
1152  // UTC offset of the *previous* Transition. However, the
1153  // transitionTime can be represented by an illegal time (e.g. 24:00).
1154  // So, it is better to use the properly normalized startDateTime
1155  // (calculated above) with the *current* UTC offset.
1156  //
1157  // NOTE: We should also be able to calculate this directly from
1158  // 'transitionTimeU' which should still be a valid field, because it
1159  // hasn't been clobbered by 'untilDateTime' yet. Not sure if this saves
1160  // any CPU time though, since we still need to mutiply by 900.
1161  const extended::DateTuple& st = t->startDateTime;
1162  const acetime_t offsetSeconds = (acetime_t) 60
1163  * (st.minutes - (t->offsetMinutes + t->deltaMinutes));
1164  LocalDate ld = LocalDate::forComponents(st.year, st.month, st.day);
1165  t->startEpochSeconds = ld.toEpochSeconds() + offsetSeconds;
1166 
1167  prev = t;
1168  isAfterFirst = true;
1169  }
1170 
1171  // The last Transition's until time is the until time of the MatchingEra.
1172  extended::DateTuple untilTimeW;
1173  extended::DateTuple untilTimeS;
1174  extended::DateTuple untilTimeU;
1175  expandDateTuple(
1176  &prev->match->untilDateTime,
1177  prev->offsetMinutes,
1178  prev->deltaMinutes,
1179  &untilTimeW,
1180  &untilTimeS,
1181  &untilTimeU);
1182  prev->untilDateTime = untilTimeW;
1183  }
1184 
1188  static void calcAbbreviations(Transition** begin, Transition** end) {
1189  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1190  logging::printf("calcAbbreviations(): #transitions: %d\n",
1191  (int) (end - begin));
1192  }
1193  for (Transition** iter = begin; iter != end; ++iter) {
1194  Transition* const t = *iter;
1195  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1196  logging::printf(
1197  "calcAbbreviations(): format:%s, deltaMinutes:%d, letter:%s\n",
1198  t->format(), t->deltaMinutes, t->letter());
1199  }
1200  createAbbreviation(
1201  t->abbrev,
1202  internal::kAbbrevSize,
1203  t->format(),
1204  t->deltaMinutes,
1205  t->letter());
1206  }
1207  }
1208 
1217  static void createAbbreviation(
1218  char* dest,
1219  uint8_t destSize,
1220  const char* format,
1221  uint16_t deltaMinutes,
1222  const char* letterString) {
1223 
1224  // Check if FORMAT contains a '%'.
1225  if (strchr(format, '%') != nullptr) {
1226  // Check if RULES column empty, therefore no 'letter'
1227  if (letterString == nullptr) {
1228  strncpy(dest, format, destSize - 1);
1229  dest[destSize - 1] = '\0';
1230  } else {
1231  ace_common::copyReplaceString(
1232  dest, destSize, format, '%', letterString);
1233  }
1234  } else {
1235  // Check if FORMAT contains a '/'.
1236  const char* slashPos = strchr(format, '/');
1237  if (slashPos != nullptr) {
1238  if (deltaMinutes == 0) {
1239  uint8_t headLength = (slashPos - format);
1240  if (headLength >= destSize) headLength = destSize - 1;
1241  memcpy(dest, format, headLength);
1242  dest[headLength] = '\0';
1243  } else {
1244  uint8_t tailLength = strlen(slashPos+1);
1245  if (tailLength >= destSize) tailLength = destSize - 1;
1246  memcpy(dest, slashPos+1, tailLength);
1247  dest[tailLength] = '\0';
1248  }
1249  } else {
1250  // Just copy the FORMAT disregarding deltaMinutes and letterString.
1251  strncpy(dest, format, destSize - 1);
1252  dest[destSize - 1] = '\0';
1253  }
1254  }
1255  }
1256 
1257  private:
1258  const BF* mBrokerFactory; // nullable
1259  ZIB mZoneInfoBroker;
1260 
1261  // NOTE: Maybe move mNumMatches and mMatches into a MatchStorage object.
1262  mutable uint8_t mNumMatches = 0; // actual number of matches
1263  mutable MatchingEra mMatches[kMaxMatches];
1264  mutable TransitionStorage mTransitionStorage;
1265 };
1266 
1267 
1273  extended::BrokerFactory,
1274  extended::ZoneInfoBroker,
1275  extended::ZoneEraBroker,
1276  extended::ZonePolicyBroker,
1277  extended::ZoneRuleBroker> {
1278 
1279  public:
1281  static const uint8_t kTypeExtended = 4;
1282 
1283  explicit ExtendedZoneProcessor(const extended::ZoneInfo* zoneInfo = nullptr)
1285  extended::BrokerFactory,
1286  extended::ZoneInfoBroker,
1287  extended::ZoneEraBroker,
1288  extended::ZonePolicyBroker,
1289  extended::ZoneRuleBroker>(
1290  kTypeExtended, &mBrokerFactory, (uintptr_t) zoneInfo)
1291  {}
1292 
1293  private:
1294  extended::BrokerFactory mBrokerFactory;
1295 };
1296 
1297 } // namespace ace_time
1298 
1299 #endif
The classes provide a thin layer of indirection for accessing the zoneinfo files stored in the zonedb...
An implementation of ZoneProcessor that supports for all zones defined by the TZ Database.
uint32_t getZoneId() const override
Return the unique stable zoneId.
FindResult findByLocalDateTime(const LocalDateTime &ldt) const override
Return the search results at given LocalDateTime.
bool initForEpochSeconds(acetime_t epochSeconds) const
Initialize using the epochSeconds.
void setBrokerFactory(const BF *brokerFactory)
Set the broker factory at runtime.
extended::TransitionForDateTimeTemplate< ZEB, ZPB, ZRB > TransitionForDateTime
Exposed only for testing purposes.
extended::TransitionStorageTemplate< kMaxTransitions, ZEB, ZPB, ZRB > TransitionStorage
Exposed only for testing purposes.
bool initForYear(int16_t year) const
Initialize the zone rules cache, keyed by the "current" year.
bool equalsZoneKey(uintptr_t zoneKey) const override
Return true if ZoneProcessor is associated with the given opaque zoneKey.
extended::TransitionForSecondsTemplate< ZEB, ZPB, ZRB > TransitionForSeconds
Exposed only for testing purposes.
bool isLink() const override
Return true if timezone is a Link entry pointing to a Zone entry.
void resetTransitionAllocSize()
Reset the TransitionStorage high water mark.
void printShortNameTo(Print &printer) const override
Print a short human-readable identifier (e.g.
void printTargetNameTo(Print &printer) const override
Print the full identifier (e.g.
static const uint8_t kMaxTransitions
Max number of Transitions required for all Zones supported by this class.
extended::TransitionTemplate< ZEB, ZPB, ZRB > Transition
Exposed only for testing purposes.
void setZoneKey(uintptr_t zoneKey) override
Set the opaque zoneKey of this object to a new value, reseting any internally cached information.
FindResult findByEpochSeconds(acetime_t epochSeconds) const override
ExtendedZoneProcessorTemplate(uint8_t type, const BF *brokerFactory, uintptr_t zoneKey)
Constructor.
void log() const
Used only for debugging.
void printNameTo(Print &printer) const override
Print a human-readable identifier (e.g.
uint8_t getTransitionAllocSize() const
Get the largest allocation size of TransitionStorage.
extended::MatchingEraTemplate< ZEB > MatchingEra
Exposed only for testing purposes.
A specific implementation of ExtendedZoneProcessorTemplate that uses ZoneXxxBrokers which read from z...
static const uint8_t kTypeExtended
Unique TimeZone type identifier for ExtendedZoneProcessor.
Result of a search for transition at a specific epochSeconds or a specific LocalDateTime.
Definition: ZoneProcessor.h:25
uint8_t fold
For findByLocalDateTime(), when type==kTypeOverlap, this is a copy of the requested LocalDateTime::fo...
Definition: ZoneProcessor.h:78
int16_t dstOffsetMinutes
DST offset of the resulting OffsetDateTime.
Definition: ZoneProcessor.h:84
int16_t reqStdOffsetMinutes
STD offset of the Transition which matched the epochSeconds requested by findByEpochSeconds(),...
Definition: ZoneProcessor.h:97
int16_t stdOffsetMinutes
STD offset of the resulting OffsetDateTime.
Definition: ZoneProcessor.h:81
int16_t reqDstOffsetMinutes
DST offset of the Transition which matched the epochSeconds requested by findByEpochSeconds(),...
const char * abbrev
Pointer to the abbreviation stored in the transient Transition::abbrev variable.
uint8_t type
Result of the findByEpochSeconds() or findByLocalDateTime() search methods.
Definition: ZoneProcessor.h:67
Class that holds the date-time as the components (year, month, day, hour, minute, second) without reg...
Definition: LocalDateTime.h:31
uint8_t fold() const
Return the fold.
int16_t year() const
Return the year.
The date (year, month, day) representing the date without regards to time zone.
Definition: LocalDate.h:49
static LocalDate forComponents(int16_t year, uint8_t month, uint8_t day)
Factory method using separated year, month and day fields.
Definition: LocalDate.h:156
int16_t year() const
Return the year.
Definition: LocalDate.h:304
static LocalDate forEpochSeconds(acetime_t epochSeconds)
Factory method using the number of seconds since the current epoch year given by currentEpochYear().
Definition: LocalDate.h:208
static const int16_t kInvalidYear
Sentinel year which indicates one or more of the following conditions:
Definition: LocalDate.h:61
Base interface for ZoneProcessor classes.
bool isFilled(int16_t year) const
Check if the Transition cache is filled for the given year.
A factory that creates a basic::ZoneInfoBroker.
void resetAllocSize()
Reset the current allocation size.
Definition: Transition.h:920
void init()
Initialize all pools to 0 size, usually when a new year is initialized.
Definition: Transition.h:561
uint8_t getAllocSize() const
Return the maximum number of transitions which was allocated.
Definition: Transition.h:927
TransitionForDateTime findTransitionForDateTime(const LocalDateTime &ldt) const
Return the candidate Transitions matching the given dateTime.
Definition: Transition.h:838
TransitionForSeconds findTransitionForSeconds(acetime_t epochSeconds) const
Return the Transition matching the given epochSeconds.
Definition: Transition.h:740
void log() const
Verify that the indexes are valid.
Definition: Transition.h:894
int32_t acetime_t
Type for the number of seconds from epoch.
Definition: common.h:24
Macros and definitions that provide a consistency layer among the various Arduino boards for compatib...
Internal identifiers used by implementation code, not intended to be publically exported.
Data structure that captures the matching ZoneEra and its ZoneRule transitions for a given year.
Definition: Transition.h:193
The result of the findTransitionForDateTime(const LocalDatetime& ldt) method which can return 0,...
Definition: Transition.h:486
uint8_t num
Number of matches: 0, 1, 2.
Definition: Transition.h:495
const TransitionTemplate< ZEB, ZPB, ZRB > * prev
The previous transition.
Definition: Transition.h:489
const TransitionTemplate< ZEB, ZPB, ZRB > * curr
The matching transition, or null if not found or in gap.
Definition: Transition.h:492
Tuple of a matching Transition and its 'fold'.
Definition: Transition.h:456
uint8_t fold
1 if corresponding datetime occurred the second time
Definition: Transition.h:461
const TransitionTemplate< ZEB, ZPB, ZRB > * curr
The matching transition, or null if not found.
Definition: Transition.h:458
uint8_t num
Number of occurrences of the resulting LocalDateTime: 0, 1, or 2.
Definition: Transition.h:469
Represents an interval of time where the time zone obeyed a certain UTC offset and DST delta.
Definition: Transition.h:260
static void printTransitions(const char *prefix, const TransitionTemplate *const *begin, const TransitionTemplate *const *end)
Print an iterable of Transitions from 'begin' to 'end'.
Definition: Transition.h:435
int16_t offsetMinutes
The base offset minutes, not the total effective UTC offset.
Definition: Transition.h:328
char abbrev[internal::kAbbrevSize]
The calculated effective time zone abbreviation, e.g.
Definition: Transition.h:334
int16_t deltaMinutes
The DST delta minutes.
Definition: Transition.h:331
A simple tuple to represent a year/month pair.
static const uint8_t kSuffixW
Represents 'w' or wall time.
Definition: ZoneContext.h:18
static const uint8_t kSuffixS
Represents 's' or standard time.
Definition: ZoneContext.h:21
static const uint8_t kSuffixU
Represents 'u' or UTC time.
Definition: ZoneContext.h:24