AceTime  3.0.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 <AceCommon.h> // copyReplaceString()
11 #include "../zoneinfo/infos.h"
12 #include "common/common.h" // kAbbrevSize
13 #include "common/logging.h"
14 #include "LocalDate.h"
15 #include "ZoneProcessor.h"
16 #include "Transition.h"
17 
18 #ifndef ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG
19 #define ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG 0
20 #endif
21 
22 class ExtendedZoneProcessorTest_compareEraToYearMonth;
23 class ExtendedZoneProcessorTest_compareEraToYearMonth2;
24 class ExtendedZoneProcessorTest_createMatchingEra;
25 class ExtendedZoneProcessorTest_findMatches_simple;
26 class ExtendedZoneProcessorTest_findMatches_named;
27 class ExtendedZoneProcessorTest_findCandidateTransitions;
28 class ExtendedZoneProcessorTest_createTransitionsFromNamedMatch;
29 class ExtendedZoneProcessorTest_getTransitionTime;
30 class ExtendedZoneProcessorTest_createTransitionForYear;
31 class ExtendedZoneProcessorTest_calcInteriorYears;
32 class ExtendedZoneProcessorTest_getMostRecentPriorYear;
33 class ExtendedZoneProcessorTest_compareTransitionToMatchFuzzy;
34 class ExtendedZoneProcessorTest_compareTransitionToMatch;
35 class ExtendedZoneProcessorTest_processTransitionCompareStatus;
36 class ExtendedZoneProcessorTest_fixTransitionTimes_generateStartUntilTimes;
37 class ExtendedZoneProcessorTest_setZoneKey;
38 class ExtendedTransitionValidation;
39 class CompleteTransitionValidation;
40 
41 class Print;
42 
43 namespace ace_time {
44 
45 namespace extended {
46 
49  int16_t year;
50  uint8_t month;
51 };
52 
53 }
54 
80 template <typename D>
82  public:
94  static const uint8_t kMaxTransitions = 8;
95 
98 
102 
106 
109 
113 
114  bool isLink() const override {
115  return ! mZoneInfoBroker.targetInfo().isNull();
116  }
117 
118  uint32_t getZoneId() const override {
119  return mZoneInfoBroker.zoneId();
120  }
121 
122  FindResult findByLocalDateTime(const LocalDateTime& ldt) const override {
123  FindResult result;
124 
125  bool success = initForYear(ldt.year());
126  if (! success) {
127  return result;
128  }
129 
130  // Find the Transition(s) in the gap or overlap.
131  TransitionForDateTime transitionForDateTime =
132  mTransitionStorage.findTransitionForDateTime(ldt);
133 
134  // Extract the target Transition, depending on the requested ldt.fold
135  // and the result.num.
136  const Transition* transition;
137  if (transitionForDateTime.num == 1) {
138  transition = transitionForDateTime.curr;
139  result.type = FindResult::kTypeExact;
140  result.reqStdOffsetSeconds = transition->offsetSeconds;
141  result.reqDstOffsetSeconds = transition->deltaSeconds;
142  } else { // num = 0 or 2
143  if (transitionForDateTime.prev == nullptr
144  || transitionForDateTime.curr == nullptr) {
145  // ldt was far past or far future
146  transition = nullptr;
147  result.type = FindResult::kTypeNotFound;
148  } else { // gap or overlap
149  if (transitionForDateTime.num == 0) { // num==0, Gap
150  result.type = FindResult::kTypeGap;
151  if (ldt.fold() == 0) {
152  // ldt wants to use the 'prev' transition to convert to
153  // epochSeconds.
154  result.reqStdOffsetSeconds =
155  transitionForDateTime.prev->offsetSeconds;
156  result.reqDstOffsetSeconds =
157  transitionForDateTime.prev->deltaSeconds;
158  // But after normalization, it will be shifted into the curr
159  // transition, so select 'curr' as the target transition.
160  transition = transitionForDateTime.curr;
161  } else {
162  // ldt wants to use the 'curr' transition to convert to
163  // epochSeconds.
164  result.reqStdOffsetSeconds =
165  transitionForDateTime.curr->offsetSeconds;
166  result.reqDstOffsetSeconds =
167  transitionForDateTime.curr->deltaSeconds;
168  // But after normalization, it will be shifted into the prev
169  // transition, so select 'prev' as the target transition.
170  transition = transitionForDateTime.prev;
171  }
172  } else { // num==2, Overlap
173  transition = (ldt.fold() == 0)
174  ? transitionForDateTime.prev
175  : transitionForDateTime.curr;
176  result.type = FindResult::kTypeOverlap;
177  result.reqStdOffsetSeconds = transition->offsetSeconds;
178  result.reqDstOffsetSeconds = transition->deltaSeconds;
179  result.fold = ldt.fold();
180  }
181  }
182  }
183 
184  if (! transition) {
185  return result;
186  }
187 
188  result.stdOffsetSeconds = transition->offsetSeconds;
189  result.dstOffsetSeconds = transition->deltaSeconds;
190  result.abbrev = transition->abbrev;
191 
192  return result;
193  }
194 
203  FindResult findByEpochSeconds(acetime_t epochSeconds) const override {
204  FindResult result;
205  bool success = initForEpochSeconds(epochSeconds);
206  if (!success) return result;
207 
208  TransitionForSeconds transitionForSeconds =
209  mTransitionStorage.findTransitionForSeconds(epochSeconds);
210  const Transition* transition = transitionForSeconds.curr;
211  if (!transition) return result;
212 
213  result.stdOffsetSeconds = transition->offsetSeconds;
214  result.dstOffsetSeconds = transition->deltaSeconds;
215  result.reqStdOffsetSeconds = transition->offsetSeconds;
216  result.reqDstOffsetSeconds = transition->deltaSeconds;
217  result.abbrev = transition->abbrev;
218  result.fold = transitionForSeconds.fold;
219  if (transitionForSeconds.num == 2) {
220  result.type = FindResult::kTypeOverlap;
221  } else {
222  result.type = FindResult::kTypeExact;
223  }
224  return result;
225  }
226 
227  void printNameTo(Print& printer) const override {
228  mZoneInfoBroker.printNameTo(printer);
229  }
230 
231  void printShortNameTo(Print& printer) const override {
232  mZoneInfoBroker.printShortNameTo(printer);
233  }
234 
235  void printTargetNameTo(Print& printer) const override {
236  if (isLink()) {
237  mZoneInfoBroker.targetInfo().printNameTo(printer);
238  }
239  }
240 
242  void log() const {
243  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
244  logging::printf("ExtendedZoneProcessor:\n");
245  logging::printf(" mEpochYear: %d\n", mEpochYear);
246  logging::printf(" mYear: %d\n", mYear);
247  logging::printf(" mNumMatches: %d\n", mNumMatches);
248  for (int i = 0; i < mNumMatches; i++) {
249  logging::printf(" Match %d: ", i);
250  mMatches[i].log();
251  logging::printf("\n");
252  }
253  mTransitionStorage.log();
254  }
255  }
256 
259  mTransitionStorage.resetAllocSize();
260  }
261 
263  uint8_t getTransitionAllocSize() const {
264  return mTransitionStorage.getAllocSize();
265  }
266 
267  void setZoneKey(uintptr_t zoneKey) override {
268  if (! mZoneInfoStore) return;
269  if (mZoneInfoBroker.equals(zoneKey)) return;
270 
271  mZoneInfoBroker = mZoneInfoStore->createZoneInfoBroker(zoneKey);
273  mNumMatches = 0;
274  resetTransitionAllocSize(); // clear the alloc size for new zone
275  }
276 
277  bool equalsZoneKey(uintptr_t zoneKey) const override {
278  return mZoneInfoBroker.equals(zoneKey);
279  }
280 
287  void setZoneInfoStore(const typename D::ZoneInfoStore* zoneInfoStore) {
288  mZoneInfoStore = zoneInfoStore;
289  }
290 
296  bool initForEpochSeconds(acetime_t epochSeconds) const {
297  LocalDate ld = LocalDate::forEpochSeconds(epochSeconds);
298  return initForYear(ld.year());
299  }
300 
306  bool initForYear(int16_t year) const {
307  // Restrict to [1,9999] even though LocalDate should be able to handle
308  // [0,10000].
309  if (year <= LocalDate::kMinYear || LocalDate::kMaxYear <= year) {
310  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
311  logging::printf(
312  "initForYear(): Year %d outside range [%d, %d]\n",
314  }
315  return false;
316  }
317 
318  if (isFilled(year)) return true;
319  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
320  logging::printf("initForYear(): %d\n", year);
321  }
322  mYear = year;
324  mNumMatches = 0; // clear cache
325  mTransitionStorage.init();
326 
327  // Fill transitions over a 14-month window straddling the given year.
328  extended::YearMonthTuple startYm = { (int16_t) (year - 1), 12 };
329  extended::YearMonthTuple untilYm = { (int16_t) (year + 1), 2 };
330 
331  // Step 1. The equivalent steps for the Python version are in the
332  // acetimepy project, under zone_processor.ZoneProcessor.init_for_year().
333  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
334  logging::printf("==== Step 1: findMatches()\n");
335  }
336  mNumMatches = findMatches(mZoneInfoBroker, startYm, untilYm, mMatches,
337  kMaxMatches);
338  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
339 
340  // Step 2
341  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
342  logging::printf("==== Step 2: createTransitions()\n");
343  }
344  createTransitions(mTransitionStorage, mMatches, mNumMatches);
345  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
346 
347  // Step 3
348  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
349  logging::printf("==== Step 3: fixTransitionTimes()\n");
350  }
351  Transition** begin = mTransitionStorage.getActivePoolBegin();
352  Transition** end = mTransitionStorage.getActivePoolEnd();
353  fixTransitionTimes(begin, end);
354  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
355 
356  // Step 4
357  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
358  logging::printf("==== Step 4: generateStartUntilTimes()\n");
359  }
360  generateStartUntilTimes(begin, end);
361  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
362 
363  // Step 5
364  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
365  logging::printf("==== Step 5: calcAbbreviations()\n");
366  }
367  calcAbbreviations(begin, end);
368  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
369 
370  return true;
371  }
372 
373  protected:
386  uint8_t type,
387  const typename D::ZoneInfoStore* zoneInfoStore /*nullable*/,
388  uintptr_t zoneKey
389  ) :
390  ZoneProcessor(type),
391  mZoneInfoStore(zoneInfoStore)
392  {
393  setZoneKey(zoneKey);
394  }
395 
396  private:
397  friend class ::ExtendedZoneProcessorTest_compareEraToYearMonth;
398  friend class ::ExtendedZoneProcessorTest_compareEraToYearMonth2;
399  friend class ::ExtendedZoneProcessorTest_createMatchingEra;
400  friend class ::ExtendedZoneProcessorTest_findMatches_simple;
401  friend class ::ExtendedZoneProcessorTest_findMatches_named;
402  friend class ::ExtendedZoneProcessorTest_findCandidateTransitions;
403  friend class ::ExtendedZoneProcessorTest_createTransitionsFromNamedMatch;
404  friend class ::ExtendedZoneProcessorTest_getTransitionTime;
405  friend class ::ExtendedZoneProcessorTest_createTransitionForYear;
406  friend class ::ExtendedZoneProcessorTest_calcInteriorYears;
407  friend class ::ExtendedZoneProcessorTest_getMostRecentPriorYear;
408  friend class ::ExtendedZoneProcessorTest_compareTransitionToMatchFuzzy;
409  friend class ::ExtendedZoneProcessorTest_compareTransitionToMatch;
410  friend class ::ExtendedZoneProcessorTest_processTransitionCompareStatus;
411  friend class ::ExtendedZoneProcessorTest_fixTransitionTimes_generateStartUntilTimes;
412  friend class ::ExtendedZoneProcessorTest_setZoneKey;
413  friend class ::ExtendedTransitionValidation;
414  friend class ::CompleteTransitionValidation;
415 
416  // Disable copy constructor and assignment operator.
418  const ExtendedZoneProcessorTemplate&) = delete;
420  const ExtendedZoneProcessorTemplate&) = delete;
421 
426  static const uint8_t kMaxMatches = 4;
427 
432  static const uint8_t kMaxInteriorYears = 4;
433 
434  bool equals(const ZoneProcessor& other) const override {
435  return mZoneInfoBroker.equals(
436  ((const ExtendedZoneProcessorTemplate&) other).mZoneInfoBroker);
437  }
438 
446  static uint8_t findMatches(
447  const typename D::ZoneInfoBroker& zoneInfo,
448  const extended::YearMonthTuple& startYm,
449  const extended::YearMonthTuple& untilYm,
450  MatchingEra* matches,
451  uint8_t maxMatches
452  ) {
453  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
454  logging::printf("findMatches()\n");
455  }
456  uint8_t iMatch = 0;
457  MatchingEra* prevMatch = nullptr;
458  for (uint8_t iEra = 0; iEra < zoneInfo.numEras(); iEra++) {
459  const typename D::ZoneEraBroker era = zoneInfo.era(iEra);
460  if (eraOverlapsInterval(prevMatch, era, startYm, untilYm)) {
461  if (iMatch < maxMatches) {
462  matches[iMatch] = createMatchingEra(
463  prevMatch, era, startYm, untilYm);
464  prevMatch = &matches[iMatch];
465  iMatch++;
466  }
467  }
468  }
469  return iMatch;
470  }
471 
497  static bool eraOverlapsInterval(
498  const MatchingEra* prevMatch,
499  const typename D::ZoneEraBroker& era,
500  const extended::YearMonthTuple& startYm,
501  const extended::YearMonthTuple& untilYm) {
502  return (prevMatch == nullptr || compareEraToYearMonth(
503  prevMatch->era, untilYm.year, untilYm.month) < 0)
504  && compareEraToYearMonth(era, startYm.year, startYm.month) > 0;
505  }
506 
508  static int8_t compareEraToYearMonth(const typename D::ZoneEraBroker& era,
509  int16_t year, uint8_t month) {
510  if (era.untilYear() < year) return -1;
511  if (era.untilYear() > year) return 1;
512  if (era.untilMonth() < month) return -1;
513  if (era.untilMonth() > month) return 1;
514  if (era.untilDay() > 1) return 1;
515  //if (era.untilTimeSeconds() < 0) return -1; // never possible
516  if (era.untilTimeSeconds() > 0) return 1;
517  return 0;
518  }
519 
526  static MatchingEra createMatchingEra(
527  MatchingEra* prevMatch,
528  const typename D::ZoneEraBroker& era,
529  const extended::YearMonthTuple& startYm,
530  const extended::YearMonthTuple& untilYm) {
531 
532  // If prevMatch is null, set startDate to be earlier than all valid
533  // ZoneEra.
534  extended::DateTuple startDate = (prevMatch == nullptr)
535  ? extended::DateTuple{
537  1,
538  1,
539  0,
541  }
542  : extended::DateTuple{
543  prevMatch->era.untilYear(),
544  prevMatch->era.untilMonth(),
545  prevMatch->era.untilDay(),
546  (int32_t) prevMatch->era.untilTimeSeconds(),
547  prevMatch->era.untilTimeSuffix()
548  };
549  extended::DateTuple lowerBound{
550  startYm.year,
551  startYm.month,
552  1,
553  0,
555  };
556  if (startDate < lowerBound) {
557  startDate = lowerBound;
558  }
559 
560  extended::DateTuple untilDate{
561  era.untilYear(),
562  era.untilMonth(),
563  era.untilDay(),
564  (int32_t) era.untilTimeSeconds(),
565  era.untilTimeSuffix()
566  };
567  extended::DateTuple upperBound{
568  untilYm.year,
569  untilYm.month,
570  1,
571  0,
573  };
574  if (upperBound < untilDate) {
575  untilDate = upperBound;
576  }
577 
578  return {startDate, untilDate, era, prevMatch, 0, 0};
579  }
580 
585  static void createTransitions(
586  TransitionStorage& transitionStorage,
587  MatchingEra* matches,
588  uint8_t numMatches) {
589  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
590  logging::printf("createTransitions()\n");
591  }
592 
593  for (uint8_t i = 0; i < numMatches; i++) {
594  createTransitionsForMatch(transitionStorage, &matches[i]);
595  }
596  }
597 
599  static void createTransitionsForMatch(
600  TransitionStorage& transitionStorage,
601  MatchingEra* match) {
602  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
603  logging::printf("== createTransitionsForMatch()\n");
604  }
605  const typename D::ZonePolicyBroker policy = match->era.zonePolicy();
606  if (policy.isNull()) {
607  createTransitionsFromSimpleMatch(transitionStorage, match);
608  } else {
609  createTransitionsFromNamedMatch(transitionStorage, match);
610  }
611  }
612 
613  // Step 2A
614  static void createTransitionsFromSimpleMatch(
615  TransitionStorage& transitionStorage,
616  MatchingEra* match) {
617  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
618  logging::printf("== createTransitionsFromSimpleMatch()\n");
619  }
620 
621  Transition* freeTransition = transitionStorage.getFreeAgent();
622  createTransitionForYear(freeTransition, 0 /*not used*/,
623  typename D::ZoneRuleBroker() /*rule*/, match);
624  freeTransition->compareStatus = extended::CompareStatus::kExactMatch;
625  match->lastOffsetSeconds = freeTransition->offsetSeconds;
626  match->lastDeltaSeconds = freeTransition->deltaSeconds;
627  transitionStorage.addFreeAgentToActivePool();
628  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
629  transitionStorage.log();
630  }
631  }
632 
633  // Step 2B
634  static void createTransitionsFromNamedMatch(
635  TransitionStorage& transitionStorage,
636  MatchingEra* match) {
637  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
638  logging::printf("== createTransitionsFromNamedMatch()\n");
639  }
640 
641  transitionStorage.resetCandidatePool();
642  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
643  match->log(); logging::printf("\n");
644  }
645 
646  // Pass 1: Find candidate transitions using whole years.
647  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
648  logging::printf("---- Pass 1: findCandidateTransitions()\n");
649  }
650  findCandidateTransitions(transitionStorage, match);
651  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
652  transitionStorage.log();
653  }
654 
655  // Pass 2: Fix the transitions times, converting 's' and 'u' into 'w'
656  // uniformly.
657  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
658  logging::printf("---- Pass 2: fixTransitionTimes()\n");
659  }
660  fixTransitionTimes(
661  transitionStorage.getCandidatePoolBegin(),
662  transitionStorage.getCandidatePoolEnd());
663 
664  // Pass 3: Select only those Transitions which overlap with the actual
665  // start and until times of the MatchingEra.
666  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
667  logging::printf("---- Pass 3: selectActiveTransitions()\n");
668  }
669  selectActiveTransitions(
670  transitionStorage.getCandidatePoolBegin(),
671  transitionStorage.getCandidatePoolEnd());
672  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
673  transitionStorage.log();
674  }
675  Transition* lastTransition =
676  transitionStorage.addActiveCandidatesToActivePool();
677  match->lastOffsetSeconds = lastTransition->offsetSeconds;
678  match->lastDeltaSeconds = lastTransition->deltaSeconds;
679  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
680  transitionStorage.log();
681  }
682  }
683 
684  // Step 2B: Pass 1
685  static void findCandidateTransitions(
686  TransitionStorage& transitionStorage,
687  const MatchingEra* match) {
688  using extended::CompareStatus;
689 
690  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
691  logging::printf("findCandidateTransitions(): \n");
692  match->log();
693  logging::printf("\n");
694  }
695  const typename D::ZonePolicyBroker policy = match->era.zonePolicy();
696  uint8_t numRules = policy.numRules();
697  int16_t startY = match->startDateTime.year;
698  int16_t endY = match->untilDateTime.year;
699 
700  // The prior is referenced through a handle (i.e. pointer to pointer)
701  // because the actual pointer to the prior could change through the
702  // transitionStorage.setFreeAgentAsPriorIfValid() method.
703  Transition** prior = transitionStorage.reservePrior();
704  (*prior)->isValidPrior = false; // indicates "no prior transition"
705  for (uint8_t r = 0; r < numRules; r++) {
706  const typename D::ZoneRuleBroker rule = policy.rule(r);
707 
708  // Add Transitions for interior years
709  int16_t interiorYears[kMaxInteriorYears];
710  uint8_t numYears = calcInteriorYears(interiorYears, kMaxInteriorYears,
711  rule.fromYear(), rule.toYear(), startY, endY);
712  for (uint8_t y = 0; y < numYears; y++) {
713  int16_t year = interiorYears[y];
714  Transition* t = transitionStorage.getFreeAgent();
715  createTransitionForYear(t, year, rule, match);
716  CompareStatus status = compareTransitionToMatchFuzzy(t, match);
717  if (status == CompareStatus::kPrior) {
718  transitionStorage.setFreeAgentAsPriorIfValid();
719  } else if (status == CompareStatus::kWithinMatch) {
720  transitionStorage.addFreeAgentToCandidatePool();
721  } else {
722  // Must be kFarFuture.
723  // Do nothing, allowing the free agent to be reused.
724  }
725  }
726 
727  // Add Transition for prior year
728  int16_t priorYear = getMostRecentPriorYear(
729  rule.fromYear(), rule.toYear(), startY, endY);
730  if (priorYear != LocalDate::kInvalidYear) {
731  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
732  logging::printf(
733  "findCandidateTransitions(): priorYear: %d\n", priorYear);
734  }
735  Transition* t = transitionStorage.getFreeAgent();
736  createTransitionForYear(t, priorYear, rule, match);
737  transitionStorage.setFreeAgentAsPriorIfValid();
738  }
739  }
740 
741  // Add the reserved prior into the Candidate pool only if 'isValidPrior'
742  // is true.
743  if ((*prior)->isValidPrior) {
744  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
745  logging::printf(
746  "findCandidateTransitions(): adding prior to Candidate pool\n");
747  logging::printf(" ");
748  (*prior)->log();
749  logging::printf("\n");
750  }
751  transitionStorage.addPriorToCandidatePool();
752  }
753  }
754 
774  static uint8_t calcInteriorYears(
775  int16_t* interiorYears,
776  uint8_t maxInteriorYears,
777  int16_t fromYear, int16_t toYear,
778  int16_t startYear, int16_t endYear) {
779  uint8_t i = 0;
780  for (int16_t year = startYear; year <= endYear; year++) {
781  if (fromYear <= year && year <= toYear) {
782  interiorYears[i] = year;
783  i++;
784  if (i >= maxInteriorYears) break;
785  }
786  }
787  return i;
788  }
789 
795  static void createTransitionForYear(
796  Transition* t,
797  int16_t year,
798  const typename D::ZoneRuleBroker& rule,
799  const MatchingEra* match) {
800  t->match = match;
801  t->offsetSeconds = match->era.offsetSeconds();
802  #if ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG
803  t->rule = rule;
804  #endif
805 
806  if (rule.isNull()) {
807  // Create a Transition using the MatchingEra for the transitionTime.
808  // Used for simple MatchingEra.
809  t->transitionTime = match->startDateTime;
810  t->deltaSeconds = match->era.deltaSeconds();
811  t->abbrev[0] = '\0';
812  } else {
813  t->transitionTime = getTransitionTime(year, rule);
814  t->deltaSeconds = rule.deltaSeconds();
815  ace_common::strncpy_T(
816  t->abbrev, rule.letter(), kAbbrevSize - 1);
817  t->abbrev[kAbbrevSize - 1] = '\0';
818  }
819  }
820 
833  static int16_t getMostRecentPriorYear(
834  int16_t fromYear, int16_t toYear,
835  int16_t startYear, int16_t /*endYear*/) {
836 
837  if (fromYear < startYear) {
838  if (toYear < startYear) {
839  return toYear;
840  } else {
841  return startYear - 1;
842  }
843  } else {
845  }
846  }
847 
852  static extended::DateTuple getTransitionTime(
853  int16_t year, const typename D::ZoneRuleBroker& rule) {
854 
855  MonthDay monthDay = calcStartDayOfMonth(
856  year,
857  rule.inMonth(),
858  rule.onDayOfWeek(),
859  rule.onDayOfMonth());
860  return {
861  year,
862  monthDay.month,
863  monthDay.day,
864  (int32_t) rule.atTimeSeconds(),
865  rule.atTimeSuffix()
866  };
867  }
868 
879  static extended::CompareStatus compareTransitionToMatchFuzzy(
880  const Transition* t, const MatchingEra* match) {
881  return compareDateTupleFuzzy(
882  t->transitionTime,
883  match->startDateTime,
884  match->untilDateTime);
885  }
886 
895  static void fixTransitionTimes(Transition** begin, Transition** end) {
896  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
897  logging::printf("fixTransitionTimes(): START; #transitions=%d\n",
898  (int) (end - begin));
899  Transition::printTransitions(" ", begin, end);
900  }
901 
902  // extend first Transition to -infinity
903  Transition* prev = *begin;
904 
905  for (Transition** iter = begin; iter != end; ++iter) {
906  Transition* curr = *iter;
907  expandDateTuple(
908  &curr->transitionTime,
909  prev->offsetSeconds,
910  prev->deltaSeconds,
911  &curr->transitionTime,
912  &curr->transitionTimeS,
913  &curr->transitionTimeU);
914  prev = curr;
915  }
916  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
917  logging::printf("fixTransitionTimes(): FIXED\n");
918  Transition::printTransitions(" ", begin, end);
919  logging::printf("fixTransitionTimes(): END\n");
920  }
921  }
922 
927  static void selectActiveTransitions(Transition** begin, Transition** end) {
928  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
929  logging::printf("selectActiveTransitions(): #candidates: %d\n",
930  (int) (end - begin));
931  }
932 
933  Transition* prior = nullptr;
934  for (Transition** iter = begin; iter != end; ++iter) {
935  Transition* transition = *iter;
936  processTransitionCompareStatus(transition, &prior);
937  }
938 
939  // If the latest prior transition is found, shift it to start at the
940  // startDateTime of the current match.
941  if (prior) {
942  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
943  logging::printf(
944  "selectActiveTransitions(): found latest prior\n");
945  }
946  #if ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG
947  prior->originalTransitionTime = prior->transitionTime;
948  #endif
949  prior->transitionTime = prior->match->startDateTime;
950  }
951  }
952 
959  static void processTransitionCompareStatus(
960  Transition* transition,
961  Transition** prior) {
962  using extended::CompareStatus;
963 
964  CompareStatus status = compareTransitionToMatch(
965  transition, transition->match);
966  transition->compareStatus = status;
967 
968  if (status == CompareStatus::kExactMatch) {
969  if (*prior) {
970  (*prior)->compareStatus = CompareStatus::kFarPast;
971  }
972  (*prior) = transition;
973  } else if (status == CompareStatus::kPrior) {
974  if (*prior) {
975  if ((*prior)->transitionTimeU <= transition->transitionTimeU) {
976  (*prior)->compareStatus = CompareStatus::kFarPast;
977  (*prior) = transition;
978  } else {
979  transition->compareStatus = CompareStatus::kFarPast;
980  }
981  } else {
982  (*prior) = transition;
983  }
984  }
985  }
986 
995  static extended::CompareStatus compareTransitionToMatch(
996  const Transition* transition,
997  const MatchingEra* match) {
998 
999  // Find the previous Match offsets.
1000  int32_t prevMatchOffsetSeconds;
1001  int32_t prevMatchDeltaSeconds;
1002  if (match->prevMatch) {
1003  prevMatchOffsetSeconds = match->prevMatch->lastOffsetSeconds;
1004  prevMatchDeltaSeconds = match->prevMatch->lastDeltaSeconds;
1005  } else {
1006  prevMatchOffsetSeconds = match->era.offsetSeconds();
1007  prevMatchDeltaSeconds = 0;
1008  }
1009 
1010  // Expand start times.
1011  extended::DateTuple stw;
1012  extended::DateTuple sts;
1013  extended::DateTuple stu;
1014  expandDateTuple(
1015  &match->startDateTime,
1016  prevMatchOffsetSeconds,
1017  prevMatchDeltaSeconds,
1018  &stw,
1019  &sts,
1020  &stu);
1021 
1022  // Transition times.
1023  const extended::DateTuple& ttw = transition->transitionTime;
1024  const extended::DateTuple& tts = transition->transitionTimeS;
1025  const extended::DateTuple& ttu = transition->transitionTimeU;
1026 
1027  // Compare Transition to Match, where equality is assumed if *any* of the
1028  // 'w', 's', or 'u' versions of the DateTuple are equal. This prevents
1029  // duplicate Transition instances from being created in a few cases.
1030  if (ttw == stw || tts == sts || ttu == stu) {
1031  return extended::CompareStatus::kExactMatch;
1032  }
1033 
1034  if (ttu < stu) {
1035  return extended::CompareStatus::kPrior;
1036  }
1037 
1038  // Now check if the transition occurs after the given match. The
1039  // untilDateTime of the current match uses the same UTC offsets as the
1040  // transitionTime of the current transition, so no complicated adjustments
1041  // are needed. We just make sure we compare 'w' with 'w', 's' with 's',
1042  // and 'u' with 'u'.
1043  const extended::DateTuple& matchUntil = match->untilDateTime;
1044  const extended::DateTuple* transitionTime;
1045  if (matchUntil.suffix == extended::Info::ZoneContext::kSuffixS) {
1046  transitionTime = &tts;
1047  } else if (matchUntil.suffix == extended::Info::ZoneContext::kSuffixU) {
1048  transitionTime = &ttu;
1049  } else { // assume 'w'
1050  transitionTime = &ttw;
1051  }
1052  if (*transitionTime < matchUntil) {
1053  return extended::CompareStatus::kWithinMatch;
1054  }
1055  return extended::CompareStatus::kFarFuture;
1056  }
1057 
1063  static void generateStartUntilTimes(Transition** begin, Transition** end) {
1064  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1065  logging::printf(
1066  "generateStartUntilTimes(): #transitions=%d\n",
1067  (int) (end - begin));
1068  }
1069 
1070  // It is possible that there are no matching transitions. This can happen
1071  // if the zonedbx is corrupted and ZoneInfo contains invalid fields.
1072  if (begin == end) return;
1073 
1074  Transition* prev = *begin;
1075  bool isAfterFirst = false;
1076 
1077  for (Transition** iter = begin; iter != end; ++iter) {
1078  Transition* const t = *iter;
1079 
1080  // 1) Update the untilDateTime of the previous Transition
1081  const extended::DateTuple& tt = t->transitionTime;
1082  if (isAfterFirst) {
1083  prev->untilDateTime = tt;
1084  }
1085 
1086  // 2) Calculate the current startDateTime by shifting the
1087  // transitionTime (represented in the UTC offset of the previous
1088  // transition) into the UTC offset of the *current* transition.
1089  int32_t seconds = tt.seconds + (
1090  - prev->offsetSeconds - prev->deltaSeconds
1091  + t->offsetSeconds + t->deltaSeconds);
1092  t->startDateTime = {tt.year, tt.month, tt.day, seconds, tt.suffix};
1093  extended::normalizeDateTuple(&t->startDateTime);
1094 
1095  // 3) The epochSecond of the 'transitionTime' is determined by the
1096  // UTC offset of the *previous* Transition. However, the
1097  // transitionTime can be represented by an illegal time (e.g. 24:00).
1098  // So, it is better to use the properly normalized startDateTime
1099  // (calculated above) with the *current* UTC offset.
1100  //
1101  // NOTE: We should also be able to calculate this directly from
1102  // 'transitionTimeU' which should still be a valid field, because it
1103  // hasn't been clobbered by 'untilDateTime' yet. Not sure if this saves
1104  // any CPU time though, since we still need to mutiply by 900.
1105  const extended::DateTuple& st = t->startDateTime;
1106  const acetime_t offsetSeconds =
1107  st.seconds - (t->offsetSeconds + t->deltaSeconds);
1108  LocalDate ld = LocalDate::forComponents(st.year, st.month, st.day);
1109  t->startEpochSeconds = ld.toEpochSeconds() + offsetSeconds;
1110 
1111  prev = t;
1112  isAfterFirst = true;
1113  }
1114 
1115  // The last Transition's until time is the until time of the MatchingEra.
1116  extended::DateTuple untilTimeW;
1117  extended::DateTuple untilTimeS;
1118  extended::DateTuple untilTimeU;
1119  expandDateTuple(
1120  &prev->match->untilDateTime,
1121  prev->offsetSeconds,
1122  prev->deltaSeconds,
1123  &untilTimeW,
1124  &untilTimeS,
1125  &untilTimeU);
1126  prev->untilDateTime = untilTimeW;
1127  }
1128 
1132  static void calcAbbreviations(Transition** begin, Transition** end) {
1133  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1134  logging::printf("calcAbbreviations(): #transitions: %d\n",
1135  (int) (end - begin));
1136  }
1137  for (Transition** iter = begin; iter != end; ++iter) {
1138  Transition* const t = *iter;
1139  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1140  logging::printf(
1141  "calcAbbreviations(): format:%s, deltaSeconds:%d, letter:%s\n",
1142  t->format(), t->deltaSeconds, t->abbrev);
1143  }
1144  createAbbreviation(
1145  t->abbrev,
1146  kAbbrevSize,
1147  t->format(),
1148  t->offsetSeconds,
1149  t->deltaSeconds,
1150  t->abbrev);
1151  }
1152  }
1153 
1154  private:
1155  const typename D::ZoneInfoStore* mZoneInfoStore; // nullable
1156  typename D::ZoneInfoBroker mZoneInfoBroker;
1157 
1158  // NOTE: Maybe move mNumMatches and mMatches into a MatchStorage object.
1159  mutable uint8_t mNumMatches = 0; // actual number of matches
1160  mutable MatchingEra mMatches[kMaxMatches];
1161  mutable TransitionStorage mTransitionStorage;
1162 };
1163 
1170  public ExtendedZoneProcessorTemplate<extended::Info> {
1171 
1172  public:
1174  static const uint8_t kTypeExtended = 4;
1175 
1176  explicit ExtendedZoneProcessor(
1177  const extended::Info::ZoneInfo* zoneInfo = nullptr)
1178  : ExtendedZoneProcessorTemplate<extended::Info>(
1179  kTypeExtended, &mZoneInfoStore, (uintptr_t) zoneInfo)
1180  {}
1181 
1182  private:
1183  extended::Info::ZoneInfoStore mZoneInfoStore;
1184 };
1185 
1186 } // namespace ace_time
1187 
1188 #endif
static int16_t currentEpochYear()
Get the current epoch year.
Definition: Epoch.h:27
An implementation of ZoneProcessor that supports for all zones defined by the TZ Database.
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.
FindResult findByEpochSeconds(acetime_t epochSeconds) const override
uint8_t getTransitionAllocSize() const
Get the largest allocation size of TransitionStorage.
extended::TransitionForDateTimeTemplate< D > TransitionForDateTime
Exposed only for testing purposes.
void resetTransitionAllocSize()
Reset the TransitionStorage high water mark.
void printShortNameTo(Print &printer) const override
Print a short human-readable identifier (e.g.
extended::TransitionTemplate< D > Transition
Exposed only for testing purposes.
ExtendedZoneProcessorTemplate(uint8_t type, const typename D::ZoneInfoStore *zoneInfoStore, uintptr_t zoneKey)
Constructor.
bool equalsZoneKey(uintptr_t zoneKey) const override
Return true if ZoneProcessor is associated with the given opaque zoneKey.
extended::MatchingEraTemplate< D > MatchingEra
Exposed only for testing purposes.
bool initForEpochSeconds(acetime_t epochSeconds) const
Initialize using the epochSeconds.
void setZoneKey(uintptr_t zoneKey) override
Set the opaque zoneKey of this object to a new value, reseting any internally cached information.
uint32_t getZoneId() const override
Return the unique stable zoneId.
void printNameTo(Print &printer) const override
Print a human-readable identifier (e.g.
void log() const
Used only for debugging.
extended::TransitionForSecondsTemplate< D > TransitionForSeconds
Exposed only for testing purposes.
extended::TransitionStorageTemplate< kMaxTransitions, D > TransitionStorage
Exposed only for testing purposes.
FindResult findByLocalDateTime(const LocalDateTime &ldt) const override
Return the search results at given LocalDateTime.
void setZoneInfoStore(const typename D::ZoneInfoStore *zoneInfoStore)
Set the zone info store at runtime.
bool initForYear(int16_t year) const
Initialize the zone rules cache, keyed by the "current" year.
bool isLink() const override
Return true if timezone is a Link entry pointing to a Zone entry.
A specific implementation of ExtendedZoneProcessorTemplate that uses the extended::Info::ZoneXxxBroke...
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:23
uint8_t fold
For findByLocalDateTime(), when type==kTypeOverlap, this is a copy of the requested LocalDateTime::fo...
Definition: ZoneProcessor.h:76
int32_t stdOffsetSeconds
STD offset of the resulting OffsetDateTime.
Definition: ZoneProcessor.h:79
int32_t dstOffsetSeconds
DST offset of the resulting OffsetDateTime.
Definition: ZoneProcessor.h:82
int32_t reqDstOffsetSeconds
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.
int32_t reqStdOffsetSeconds
STD offset of the Transition which matched the epochSeconds requested by findByEpochSeconds(),...
Definition: ZoneProcessor.h:95
uint8_t type
Result of the findByEpochSeconds() or findByLocalDateTime() search methods.
Definition: ZoneProcessor.h:65
Class that holds the date-time as the components (year, month, day, hour, minute, second) without reg...
Definition: LocalDateTime.h:30
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:46
static LocalDate forComponents(int16_t year, uint8_t month, uint8_t day)
Factory method using separated year, month and day fields.
Definition: LocalDate.h:153
static const int16_t kMaxYear
The largest year that is expected to be handled by LocalDate.
Definition: LocalDate.h:78
static const int16_t kMinYear
The smallest year that is expected to be handled by LocalDate.
Definition: LocalDate.h:69
int16_t year() const
Return the year.
Definition: LocalDate.h:301
static LocalDate forEpochSeconds(acetime_t epochSeconds)
Factory method using the number of seconds since the current epoch year given by currentEpochYear().
Definition: LocalDate.h:205
static const int16_t kInvalidYear
Sentinel year which indicates one or more of the following conditions:
Definition: LocalDate.h:58
A storage object that creates an ZoneInfoBroker from a key that identifies the ZoneInfo.
Definition: ZoneInfoLow.h:807
Base interface for ZoneProcessor classes.
int16_t mYear
Year that was used to calculate the transitions in the current cache.
bool isFilled(int16_t year) const
Check if the Transition cache is filled for the given year and current epochYear.
int16_t mEpochYear
Epoch year that was used to calculate the transitions in the current cache.
uint8_t getAllocSize() const
Return the maximum number of transitions which was allocated.
Definition: Transition.h:766
void resetAllocSize()
Reset the current allocation size.
Definition: Transition.h:759
TransitionForDateTime findTransitionForDateTime(const LocalDateTime &ldt) const
Return the candidate Transitions matching the given dateTime.
Definition: Transition.h:677
TransitionForSeconds findTransitionForSeconds(acetime_t epochSeconds) const
Return the Transition matching the given epochSeconds.
Definition: Transition.h:561
void init()
Initialize all pools to 0 size, usually when a new year is initialized.
Definition: Transition.h:382
void log() const
Verify that the indexes are valid.
Definition: Transition.h:733
Identifiers used by implementation code which need to be publically exported.
const uint8_t kAbbrevSize
Size of the c-string buffer needed to hold a time zone abbreviation.
Definition: common.h:44
int32_t acetime_t
Type for the number of seconds from epoch.
Definition: common.h:24
static const uint8_t kSuffixW
Represents 'w' or wall time.
Definition: ZoneInfoLow.h:88
static const uint8_t kSuffixS
Represents 's' or standard time.
Definition: ZoneInfoLow.h:91
static const uint8_t kSuffixU
Represents 'u' or UTC time.
Definition: ZoneInfoLow.h:94
Representation of a given time zone, implemented as an array of ZoneEra records.
Definition: ZoneInfoLow.h:324
Data structure that captures the matching ZoneEra and its ZoneRule transitions for a given year.
Definition: Transition.h:47
The result of the findTransitionForDateTime(const LocalDatetime& ldt) method which can return 0,...
Definition: Transition.h:309
const TransitionTemplate< D > * curr
The matching transition, or null if not found or in gap.
Definition: Transition.h:315
const TransitionTemplate< D > * prev
The previous transition.
Definition: Transition.h:312
uint8_t num
Number of matches: 0, 1, 2.
Definition: Transition.h:318
Tuple of a matching Transition and its 'fold'.
Definition: Transition.h:279
const TransitionTemplate< D > * curr
The matching transition, or null if not found.
Definition: Transition.h:281
uint8_t num
Number of occurrences of the resulting LocalDateTime: 0, 1, or 2.
Definition: Transition.h:292
uint8_t fold
1 if corresponding datetime occurred the second time
Definition: Transition.h:284
Represents an interval of time where the time zone obeyed a certain UTC offset and DST delta.
Definition: Transition.h:112
int32_t deltaSeconds
The DST delta seconds.
Definition: Transition.h:180
char abbrev[kAbbrevSize]
The calculated effective time zone abbreviation, e.g.
Definition: Transition.h:188
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:258
int32_t offsetSeconds
The standard time offset seconds, not the total offset.
Definition: Transition.h:177
A simple tuple to represent a year/month pair.