AceTime  2.3.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 "../zoneinfo/brokers.h"
13 #include "common/common.h" // kAbbrevSize
14 #include "common/logging.h"
15 #include "LocalDate.h"
16 #include "ZoneProcessor.h"
17 #include "Transition.h"
18 
19 #ifndef ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG
20 #define ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG 0
21 #endif
22 
23 class ExtendedZoneProcessorTest_compareEraToYearMonth;
24 class ExtendedZoneProcessorTest_compareEraToYearMonth2;
25 class ExtendedZoneProcessorTest_createMatchingEra;
26 class ExtendedZoneProcessorTest_findMatches_simple;
27 class ExtendedZoneProcessorTest_findMatches_named;
28 class ExtendedZoneProcessorTest_findCandidateTransitions;
29 class ExtendedZoneProcessorTest_createTransitionsFromNamedMatch;
30 class ExtendedZoneProcessorTest_getTransitionTime;
31 class ExtendedZoneProcessorTest_createTransitionForYear;
32 class ExtendedZoneProcessorTest_calcInteriorYears;
33 class ExtendedZoneProcessorTest_getMostRecentPriorYear;
34 class ExtendedZoneProcessorTest_compareTransitionToMatchFuzzy;
35 class ExtendedZoneProcessorTest_compareTransitionToMatch;
36 class ExtendedZoneProcessorTest_processTransitionCompareStatus;
37 class ExtendedZoneProcessorTest_fixTransitionTimes_generateStartUntilTimes;
38 class ExtendedZoneProcessorTest_setZoneKey;
39 class ExtendedTransitionValidation;
40 class CompleteTransitionValidation;
41 
42 class Print;
43 
44 namespace ace_time {
45 
46 namespace extended {
47 
50  int16_t year;
51  uint8_t month;
52 };
53 
54 }
55 
87 template <typename ZIS, typename ZIB, typename ZEB, typename ZPB, typename ZRB>
89  public:
101  static const uint8_t kMaxTransitions = 8;
102 
105 
109 
113 
116 
120 
121  bool isLink() const override {
122  return ! mZoneInfoBroker.targetInfo().isNull();
123  }
124 
125  uint32_t getZoneId() const override {
126  return mZoneInfoBroker.zoneId();
127  }
128 
129  FindResult findByLocalDateTime(const LocalDateTime& ldt) const override {
130  FindResult result;
131 
132  bool success = initForYear(ldt.year());
133  if (! success) {
134  return result;
135  }
136 
137  // Find the Transition(s) in the gap or overlap.
138  TransitionForDateTime transitionForDateTime =
139  mTransitionStorage.findTransitionForDateTime(ldt);
140 
141  // Extract the target Transition, depending on the requested ldt.fold
142  // and the result.num.
143  const Transition* transition;
144  if (transitionForDateTime.num == 1) {
145  transition = transitionForDateTime.curr;
146  result.type = FindResult::kTypeExact;
147  result.reqStdOffsetSeconds = transition->offsetSeconds;
148  result.reqDstOffsetSeconds = transition->deltaSeconds;
149  } else { // num = 0 or 2
150  if (transitionForDateTime.prev == nullptr
151  || transitionForDateTime.curr == nullptr) {
152  // ldt was far past or far future
153  transition = nullptr;
154  result.type = FindResult::kTypeNotFound;
155  } else { // gap or overlap
156  if (transitionForDateTime.num == 0) { // num==0, Gap
157  result.type = FindResult::kTypeGap;
158  if (ldt.fold() == 0) {
159  // ldt wants to use the 'prev' transition to convert to
160  // epochSeconds.
161  result.reqStdOffsetSeconds =
162  transitionForDateTime.prev->offsetSeconds;
163  result.reqDstOffsetSeconds =
164  transitionForDateTime.prev->deltaSeconds;
165  // But after normalization, it will be shifted into the curr
166  // transition, so select 'curr' as the target transition.
167  transition = transitionForDateTime.curr;
168  } else {
169  // ldt wants to use the 'curr' transition to convert to
170  // epochSeconds.
171  result.reqStdOffsetSeconds =
172  transitionForDateTime.curr->offsetSeconds;
173  result.reqDstOffsetSeconds =
174  transitionForDateTime.curr->deltaSeconds;
175  // But after normalization, it will be shifted into the prev
176  // transition, so select 'prev' as the target transition.
177  transition = transitionForDateTime.prev;
178  }
179  } else { // num==2, Overlap
180  transition = (ldt.fold() == 0)
181  ? transitionForDateTime.prev
182  : transitionForDateTime.curr;
183  result.type = FindResult::kTypeOverlap;
184  result.reqStdOffsetSeconds = transition->offsetSeconds;
185  result.reqDstOffsetSeconds = transition->deltaSeconds;
186  result.fold = ldt.fold();
187  }
188  }
189  }
190 
191  if (! transition) {
192  return result;
193  }
194 
195  result.stdOffsetSeconds = transition->offsetSeconds;
196  result.dstOffsetSeconds = transition->deltaSeconds;
197  result.abbrev = transition->abbrev;
198 
199  return result;
200  }
201 
210  FindResult findByEpochSeconds(acetime_t epochSeconds) const override {
211  FindResult result;
212  bool success = initForEpochSeconds(epochSeconds);
213  if (!success) return result;
214 
215  TransitionForSeconds transitionForSeconds =
216  mTransitionStorage.findTransitionForSeconds(epochSeconds);
217  const Transition* transition = transitionForSeconds.curr;
218  if (!transition) return result;
219 
220  result.stdOffsetSeconds = transition->offsetSeconds;
221  result.dstOffsetSeconds = transition->deltaSeconds;
222  result.reqStdOffsetSeconds = transition->offsetSeconds;
223  result.reqDstOffsetSeconds = transition->deltaSeconds;
224  result.abbrev = transition->abbrev;
225  result.fold = transitionForSeconds.fold;
226  if (transitionForSeconds.num == 2) {
227  result.type = FindResult::kTypeOverlap;
228  } else {
229  result.type = FindResult::kTypeExact;
230  }
231  return result;
232  }
233 
234  void printNameTo(Print& printer) const override {
235  mZoneInfoBroker.printNameTo(printer);
236  }
237 
238  void printShortNameTo(Print& printer) const override {
239  mZoneInfoBroker.printShortNameTo(printer);
240  }
241 
242  void printTargetNameTo(Print& printer) const override {
243  if (isLink()) {
244  mZoneInfoBroker.targetInfo().printNameTo(printer);
245  }
246  }
247 
249  void log() const {
250  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
251  logging::printf("ExtendedZoneProcessor:\n");
252  logging::printf(" mEpochYear: %d\n", mEpochYear);
253  logging::printf(" mYear: %d\n", mYear);
254  logging::printf(" mNumMatches: %d\n", mNumMatches);
255  for (int i = 0; i < mNumMatches; i++) {
256  logging::printf(" Match %d: ", i);
257  mMatches[i].log();
258  logging::printf("\n");
259  }
260  mTransitionStorage.log();
261  }
262  }
263 
266  mTransitionStorage.resetAllocSize();
267  }
268 
270  uint8_t getTransitionAllocSize() const {
271  return mTransitionStorage.getAllocSize();
272  }
273 
274  void setZoneKey(uintptr_t zoneKey) override {
275  if (! mZoneInfoStore) return;
276  if (mZoneInfoBroker.equals(zoneKey)) return;
277 
278  mZoneInfoBroker = mZoneInfoStore->createZoneInfoBroker(zoneKey);
280  mNumMatches = 0;
281  resetTransitionAllocSize(); // clear the alloc size for new zone
282  }
283 
284  bool equalsZoneKey(uintptr_t zoneKey) const override {
285  return mZoneInfoBroker.equals(zoneKey);
286  }
287 
294  void setZoneInfoStore(const ZIS* zoneInfoStore) {
295  mZoneInfoStore = zoneInfoStore;
296  }
297 
303  bool initForEpochSeconds(acetime_t epochSeconds) const {
304  LocalDate ld = LocalDate::forEpochSeconds(epochSeconds);
305  return initForYear(ld.year());
306  }
307 
313  bool initForYear(int16_t year) const {
314  // Restrict to [1,9999] even though LocalDate should be able to handle
315  // [0,10000].
316  if (year <= LocalDate::kMinYear || LocalDate::kMaxYear <= year) {
317  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
318  logging::printf(
319  "initForYear(): Year %d outside range [%d, %d]\n",
321  }
322  return false;
323  }
324 
325  if (isFilled(year)) return true;
326  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
327  logging::printf("initForYear(): %d\n", year);
328  }
329  mYear = year;
331  mNumMatches = 0; // clear cache
332  mTransitionStorage.init();
333 
334  // Fill transitions over a 14-month window straddling the given year.
335  extended::YearMonthTuple startYm = { (int16_t) (year - 1), 12 };
336  extended::YearMonthTuple untilYm = { (int16_t) (year + 1), 2 };
337 
338  // Step 1. The equivalent steps for the Python version are in the
339  // acetimepy project, under zone_processor.ZoneProcessor.init_for_year().
340  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
341  logging::printf("==== Step 1: findMatches()\n");
342  }
343  mNumMatches = findMatches(mZoneInfoBroker, startYm, untilYm, mMatches,
344  kMaxMatches);
345  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
346 
347  // Step 2
348  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
349  logging::printf("==== Step 2: createTransitions()\n");
350  }
351  createTransitions(mTransitionStorage, mMatches, mNumMatches);
352  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
353 
354  // Step 3
355  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
356  logging::printf("==== Step 3: fixTransitionTimes()\n");
357  }
358  Transition** begin = mTransitionStorage.getActivePoolBegin();
359  Transition** end = mTransitionStorage.getActivePoolEnd();
360  fixTransitionTimes(begin, end);
361  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
362 
363  // Step 4
364  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
365  logging::printf("==== Step 4: generateStartUntilTimes()\n");
366  }
367  generateStartUntilTimes(begin, end);
368  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
369 
370  // Step 5
371  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
372  logging::printf("==== Step 5: calcAbbreviations()\n");
373  }
374  calcAbbreviations(begin, end);
375  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) { log(); }
376 
377  return true;
378  }
379 
380  protected:
392  uint8_t type,
393  const ZIS* zoneInfoStore /*nullable*/,
394  uintptr_t zoneKey
395  ) :
396  ZoneProcessor(type),
397  mZoneInfoStore(zoneInfoStore)
398  {
399  setZoneKey(zoneKey);
400  }
401 
402  private:
403  friend class ::ExtendedZoneProcessorTest_compareEraToYearMonth;
404  friend class ::ExtendedZoneProcessorTest_compareEraToYearMonth2;
405  friend class ::ExtendedZoneProcessorTest_createMatchingEra;
406  friend class ::ExtendedZoneProcessorTest_findMatches_simple;
407  friend class ::ExtendedZoneProcessorTest_findMatches_named;
408  friend class ::ExtendedZoneProcessorTest_findCandidateTransitions;
409  friend class ::ExtendedZoneProcessorTest_createTransitionsFromNamedMatch;
410  friend class ::ExtendedZoneProcessorTest_getTransitionTime;
411  friend class ::ExtendedZoneProcessorTest_createTransitionForYear;
412  friend class ::ExtendedZoneProcessorTest_calcInteriorYears;
413  friend class ::ExtendedZoneProcessorTest_getMostRecentPriorYear;
414  friend class ::ExtendedZoneProcessorTest_compareTransitionToMatchFuzzy;
415  friend class ::ExtendedZoneProcessorTest_compareTransitionToMatch;
416  friend class ::ExtendedZoneProcessorTest_processTransitionCompareStatus;
417  friend class ::ExtendedZoneProcessorTest_fixTransitionTimes_generateStartUntilTimes;
418  friend class ::ExtendedZoneProcessorTest_setZoneKey;
419  friend class ::ExtendedTransitionValidation;
420  friend class ::CompleteTransitionValidation;
421 
422  // Disable copy constructor and assignment operator.
424  const ExtendedZoneProcessorTemplate&) = delete;
426  const ExtendedZoneProcessorTemplate&) = delete;
427 
432  static const uint8_t kMaxMatches = 4;
433 
438  static const uint8_t kMaxInteriorYears = 4;
439 
440  bool equals(const ZoneProcessor& other) const override {
441  return mZoneInfoBroker.equals(
442  ((const ExtendedZoneProcessorTemplate&) other).mZoneInfoBroker);
443  }
444 
452  static uint8_t findMatches(
453  const ZIB& zoneInfo,
454  const extended::YearMonthTuple& startYm,
455  const extended::YearMonthTuple& untilYm,
456  MatchingEra* matches,
457  uint8_t maxMatches
458  ) {
459  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
460  logging::printf("findMatches()\n");
461  }
462  uint8_t iMatch = 0;
463  MatchingEra* prevMatch = nullptr;
464  for (uint8_t iEra = 0; iEra < zoneInfo.numEras(); iEra++) {
465  const ZEB era = zoneInfo.era(iEra);
466  if (eraOverlapsInterval(prevMatch, era, startYm, untilYm)) {
467  if (iMatch < maxMatches) {
468  matches[iMatch] = createMatchingEra(
469  prevMatch, era, startYm, untilYm);
470  prevMatch = &matches[iMatch];
471  iMatch++;
472  }
473  }
474  }
475  return iMatch;
476  }
477 
503  static bool eraOverlapsInterval(
504  const MatchingEra* prevMatch,
505  const ZEB& era,
506  const extended::YearMonthTuple& startYm,
507  const extended::YearMonthTuple& untilYm) {
508  return (prevMatch == nullptr || compareEraToYearMonth(
509  prevMatch->era, untilYm.year, untilYm.month) < 0)
510  && compareEraToYearMonth(era, startYm.year, startYm.month) > 0;
511  }
512 
514  static int8_t compareEraToYearMonth(const ZEB& era,
515  int16_t year, uint8_t month) {
516  if (era.untilYear() < year) return -1;
517  if (era.untilYear() > year) return 1;
518  if (era.untilMonth() < month) return -1;
519  if (era.untilMonth() > month) return 1;
520  if (era.untilDay() > 1) return 1;
521  //if (era.untilTimeSeconds() < 0) return -1; // never possible
522  if (era.untilTimeSeconds() > 0) return 1;
523  return 0;
524  }
525 
532  static MatchingEra createMatchingEra(
533  MatchingEra* prevMatch,
534  const ZEB& era,
535  const extended::YearMonthTuple& startYm,
536  const extended::YearMonthTuple& untilYm) {
537 
538  // If prevMatch is null, set startDate to be earlier than all valid
539  // ZoneEra.
540  extended::DateTuple startDate = (prevMatch == nullptr)
541  ? extended::DateTuple{
543  1,
544  1,
545  0,
547  }
548  : extended::DateTuple{
549  prevMatch->era.untilYear(),
550  prevMatch->era.untilMonth(),
551  prevMatch->era.untilDay(),
552  (int32_t) prevMatch->era.untilTimeSeconds(),
553  prevMatch->era.untilTimeSuffix()
554  };
555  extended::DateTuple lowerBound{
556  startYm.year,
557  startYm.month,
558  1,
559  0,
561  };
562  if (startDate < lowerBound) {
563  startDate = lowerBound;
564  }
565 
566  extended::DateTuple untilDate{
567  era.untilYear(),
568  era.untilMonth(),
569  era.untilDay(),
570  (int32_t) era.untilTimeSeconds(),
571  era.untilTimeSuffix()
572  };
573  extended::DateTuple upperBound{
574  untilYm.year,
575  untilYm.month,
576  1,
577  0,
579  };
580  if (upperBound < untilDate) {
581  untilDate = upperBound;
582  }
583 
584  return {startDate, untilDate, era, prevMatch, 0, 0};
585  }
586 
591  static void createTransitions(
592  TransitionStorage& transitionStorage,
593  MatchingEra* matches,
594  uint8_t numMatches) {
595  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
596  logging::printf("createTransitions()\n");
597  }
598 
599  for (uint8_t i = 0; i < numMatches; i++) {
600  createTransitionsForMatch(transitionStorage, &matches[i]);
601  }
602  }
603 
605  static void createTransitionsForMatch(
606  TransitionStorage& transitionStorage,
607  MatchingEra* match) {
608  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
609  logging::printf("== createTransitionsForMatch()\n");
610  }
611  const ZPB policy = match->era.zonePolicy();
612  if (policy.isNull()) {
613  createTransitionsFromSimpleMatch(transitionStorage, match);
614  } else {
615  createTransitionsFromNamedMatch(transitionStorage, match);
616  }
617  }
618 
619  // Step 2A
620  static void createTransitionsFromSimpleMatch(
621  TransitionStorage& transitionStorage,
622  MatchingEra* match) {
623  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
624  logging::printf("== createTransitionsFromSimpleMatch()\n");
625  }
626 
627  Transition* freeTransition = transitionStorage.getFreeAgent();
628  createTransitionForYear(freeTransition, 0 /*not used*/,
629  ZRB() /*rule*/, match);
630  freeTransition->compareStatus = extended::CompareStatus::kExactMatch;
631  match->lastOffsetSeconds = freeTransition->offsetSeconds;
632  match->lastDeltaSeconds = freeTransition->deltaSeconds;
633  transitionStorage.addFreeAgentToActivePool();
634  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
635  transitionStorage.log();
636  }
637  }
638 
639  // Step 2B
640  static void createTransitionsFromNamedMatch(
641  TransitionStorage& transitionStorage,
642  MatchingEra* match) {
643  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
644  logging::printf("== createTransitionsFromNamedMatch()\n");
645  }
646 
647  transitionStorage.resetCandidatePool();
648  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
649  match->log(); logging::printf("\n");
650  }
651 
652  // Pass 1: Find candidate transitions using whole years.
653  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
654  logging::printf("---- Pass 1: findCandidateTransitions()\n");
655  }
656  findCandidateTransitions(transitionStorage, match);
657  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
658  transitionStorage.log();
659  }
660 
661  // Pass 2: Fix the transitions times, converting 's' and 'u' into 'w'
662  // uniformly.
663  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
664  logging::printf("---- Pass 2: fixTransitionTimes()\n");
665  }
666  fixTransitionTimes(
667  transitionStorage.getCandidatePoolBegin(),
668  transitionStorage.getCandidatePoolEnd());
669 
670  // Pass 3: Select only those Transitions which overlap with the actual
671  // start and until times of the MatchingEra.
672  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
673  logging::printf("---- Pass 3: selectActiveTransitions()\n");
674  }
675  selectActiveTransitions(
676  transitionStorage.getCandidatePoolBegin(),
677  transitionStorage.getCandidatePoolEnd());
678  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
679  transitionStorage.log();
680  }
681  Transition* lastTransition =
682  transitionStorage.addActiveCandidatesToActivePool();
683  match->lastOffsetSeconds = lastTransition->offsetSeconds;
684  match->lastDeltaSeconds = lastTransition->deltaSeconds;
685  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
686  transitionStorage.log();
687  }
688  }
689 
690  // Step 2B: Pass 1
691  static void findCandidateTransitions(
692  TransitionStorage& transitionStorage,
693  const MatchingEra* match) {
694  using extended::CompareStatus;
695 
696  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
697  logging::printf("findCandidateTransitions(): \n");
698  match->log();
699  logging::printf("\n");
700  }
701  const ZPB policy = match->era.zonePolicy();
702  uint8_t numRules = policy.numRules();
703  int16_t startY = match->startDateTime.year;
704  int16_t endY = match->untilDateTime.year;
705 
706  // The prior is referenced through a handle (i.e. pointer to pointer)
707  // because the actual pointer to the prior could change through the
708  // transitionStorage.setFreeAgentAsPriorIfValid() method.
709  Transition** prior = transitionStorage.reservePrior();
710  (*prior)->isValidPrior = false; // indicates "no prior transition"
711  for (uint8_t r = 0; r < numRules; r++) {
712  const ZRB rule = policy.rule(r);
713 
714  // Add Transitions for interior years
715  int16_t interiorYears[kMaxInteriorYears];
716  uint8_t numYears = calcInteriorYears(interiorYears, kMaxInteriorYears,
717  rule.fromYear(), rule.toYear(), startY, endY);
718  for (uint8_t y = 0; y < numYears; y++) {
719  int16_t year = interiorYears[y];
720  Transition* t = transitionStorage.getFreeAgent();
721  createTransitionForYear(t, year, rule, match);
722  CompareStatus status = compareTransitionToMatchFuzzy(t, match);
723  if (status == CompareStatus::kPrior) {
724  transitionStorage.setFreeAgentAsPriorIfValid();
725  } else if (status == CompareStatus::kWithinMatch) {
726  transitionStorage.addFreeAgentToCandidatePool();
727  } else {
728  // Must be kFarFuture.
729  // Do nothing, allowing the free agent to be reused.
730  }
731  }
732 
733  // Add Transition for prior year
734  int16_t priorYear = getMostRecentPriorYear(
735  rule.fromYear(), rule.toYear(), startY, endY);
736  if (priorYear != LocalDate::kInvalidYear) {
737  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
738  logging::printf(
739  "findCandidateTransitions(): priorYear: %d\n", priorYear);
740  }
741  Transition* t = transitionStorage.getFreeAgent();
742  createTransitionForYear(t, priorYear, rule, match);
743  transitionStorage.setFreeAgentAsPriorIfValid();
744  }
745  }
746 
747  // Add the reserved prior into the Candidate pool only if 'isValidPrior'
748  // is true.
749  if ((*prior)->isValidPrior) {
750  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
751  logging::printf(
752  "findCandidateTransitions(): adding prior to Candidate pool\n");
753  logging::printf(" ");
754  (*prior)->log();
755  logging::printf("\n");
756  }
757  transitionStorage.addPriorToCandidatePool();
758  }
759  }
760 
780  static uint8_t calcInteriorYears(
781  int16_t* interiorYears,
782  uint8_t maxInteriorYears,
783  int16_t fromYear, int16_t toYear,
784  int16_t startYear, int16_t endYear) {
785  uint8_t i = 0;
786  for (int16_t year = startYear; year <= endYear; year++) {
787  if (fromYear <= year && year <= toYear) {
788  interiorYears[i] = year;
789  i++;
790  if (i >= maxInteriorYears) break;
791  }
792  }
793  return i;
794  }
795 
801  static void createTransitionForYear(
802  Transition* t,
803  int16_t year,
804  const ZRB& rule,
805  const MatchingEra* match) {
806  t->match = match;
807  t->offsetSeconds = match->era.offsetSeconds();
808  #if ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG
809  t->rule = rule;
810  #endif
811 
812  if (rule.isNull()) {
813  // Create a Transition using the MatchingEra for the transitionTime.
814  // Used for simple MatchingEra.
815  t->transitionTime = match->startDateTime;
816  t->deltaSeconds = match->era.deltaSeconds();
817  t->abbrev[0] = '\0';
818  } else {
819  t->transitionTime = getTransitionTime(year, rule);
820  t->deltaSeconds = rule.deltaSeconds();
821  ace_common::strncpy_T(
822  t->abbrev, rule.letter(), internal::kAbbrevSize - 1);
823  t->abbrev[internal::kAbbrevSize - 1] = '\0';
824  }
825  }
826 
839  static int16_t getMostRecentPriorYear(
840  int16_t fromYear, int16_t toYear,
841  int16_t startYear, int16_t /*endYear*/) {
842 
843  if (fromYear < startYear) {
844  if (toYear < startYear) {
845  return toYear;
846  } else {
847  return startYear - 1;
848  }
849  } else {
851  }
852  }
853 
858  static extended::DateTuple getTransitionTime(
859  int16_t year, const ZRB& rule) {
860 
861  internal::MonthDay monthDay = internal::calcStartDayOfMonth(
862  year,
863  rule.inMonth(),
864  rule.onDayOfWeek(),
865  rule.onDayOfMonth());
866  return {
867  year,
868  monthDay.month,
869  monthDay.day,
870  (int32_t) rule.atTimeSeconds(),
871  rule.atTimeSuffix()
872  };
873  }
874 
885  static extended::CompareStatus compareTransitionToMatchFuzzy(
886  const Transition* t, const MatchingEra* match) {
887  return compareDateTupleFuzzy(
888  t->transitionTime,
889  match->startDateTime,
890  match->untilDateTime);
891  }
892 
901  static void fixTransitionTimes(Transition** begin, Transition** end) {
902  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
903  logging::printf("fixTransitionTimes(): START; #transitions=%d\n",
904  (int) (end - begin));
905  Transition::printTransitions(" ", begin, end);
906  }
907 
908  // extend first Transition to -infinity
909  Transition* prev = *begin;
910 
911  for (Transition** iter = begin; iter != end; ++iter) {
912  Transition* curr = *iter;
913  expandDateTuple(
914  &curr->transitionTime,
915  prev->offsetSeconds,
916  prev->deltaSeconds,
917  &curr->transitionTime,
918  &curr->transitionTimeS,
919  &curr->transitionTimeU);
920  prev = curr;
921  }
922  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
923  logging::printf("fixTransitionTimes(): FIXED\n");
924  Transition::printTransitions(" ", begin, end);
925  logging::printf("fixTransitionTimes(): END\n");
926  }
927  }
928 
933  static void selectActiveTransitions(Transition** begin, Transition** end) {
934  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
935  logging::printf("selectActiveTransitions(): #candidates: %d\n",
936  (int) (end - begin));
937  }
938 
939  Transition* prior = nullptr;
940  for (Transition** iter = begin; iter != end; ++iter) {
941  Transition* transition = *iter;
942  processTransitionCompareStatus(transition, &prior);
943  }
944 
945  // If the latest prior transition is found, shift it to start at the
946  // startDateTime of the current match.
947  if (prior) {
948  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
949  logging::printf(
950  "selectActiveTransitions(): found latest prior\n");
951  }
952  #if ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG
953  prior->originalTransitionTime = prior->transitionTime;
954  #endif
955  prior->transitionTime = prior->match->startDateTime;
956  }
957  }
958 
965  static void processTransitionCompareStatus(
966  Transition* transition,
967  Transition** prior) {
968  using extended::CompareStatus;
969 
970  CompareStatus status = compareTransitionToMatch(
971  transition, transition->match);
972  transition->compareStatus = status;
973 
974  if (status == CompareStatus::kExactMatch) {
975  if (*prior) {
976  (*prior)->compareStatus = CompareStatus::kFarPast;
977  }
978  (*prior) = transition;
979  } else if (status == CompareStatus::kPrior) {
980  if (*prior) {
981  if ((*prior)->transitionTimeU <= transition->transitionTimeU) {
982  (*prior)->compareStatus = CompareStatus::kFarPast;
983  (*prior) = transition;
984  } else {
985  transition->compareStatus = CompareStatus::kFarPast;
986  }
987  } else {
988  (*prior) = transition;
989  }
990  }
991  }
992 
1001  static extended::CompareStatus compareTransitionToMatch(
1002  const Transition* transition,
1003  const MatchingEra* match) {
1004 
1005  // Find the previous Match offsets.
1006  int32_t prevMatchOffsetSeconds;
1007  int32_t prevMatchDeltaSeconds;
1008  if (match->prevMatch) {
1009  prevMatchOffsetSeconds = match->prevMatch->lastOffsetSeconds;
1010  prevMatchDeltaSeconds = match->prevMatch->lastDeltaSeconds;
1011  } else {
1012  prevMatchOffsetSeconds = match->era.offsetSeconds();
1013  prevMatchDeltaSeconds = 0;
1014  }
1015 
1016  // Expand start times.
1017  extended::DateTuple stw;
1018  extended::DateTuple sts;
1019  extended::DateTuple stu;
1020  expandDateTuple(
1021  &match->startDateTime,
1022  prevMatchOffsetSeconds,
1023  prevMatchDeltaSeconds,
1024  &stw,
1025  &sts,
1026  &stu);
1027 
1028  // Transition times.
1029  const extended::DateTuple& ttw = transition->transitionTime;
1030  const extended::DateTuple& tts = transition->transitionTimeS;
1031  const extended::DateTuple& ttu = transition->transitionTimeU;
1032 
1033  // Compare Transition to Match, where equality is assumed if *any* of the
1034  // 'w', 's', or 'u' versions of the DateTuple are equal. This prevents
1035  // duplicate Transition instances from being created in a few cases.
1036  if (ttw == stw || tts == sts || ttu == stu) {
1037  return extended::CompareStatus::kExactMatch;
1038  }
1039 
1040  if (ttu < stu) {
1041  return extended::CompareStatus::kPrior;
1042  }
1043 
1044  // Now check if the transition occurs after the given match. The
1045  // untilDateTime of the current match uses the same UTC offsets as the
1046  // transitionTime of the current transition, so no complicated adjustments
1047  // are needed. We just make sure we compare 'w' with 'w', 's' with 's',
1048  // and 'u' with 'u'.
1049  const extended::DateTuple& matchUntil = match->untilDateTime;
1050  const extended::DateTuple* transitionTime;
1051  if (matchUntil.suffix == extended::ZoneContext::kSuffixS) {
1052  transitionTime = &tts;
1053  } else if (matchUntil.suffix == extended::ZoneContext::kSuffixU) {
1054  transitionTime = &ttu;
1055  } else { // assume 'w'
1056  transitionTime = &ttw;
1057  }
1058  if (*transitionTime < matchUntil) {
1059  return extended::CompareStatus::kWithinMatch;
1060  }
1061  return extended::CompareStatus::kFarFuture;
1062  }
1063 
1069  static void generateStartUntilTimes(Transition** begin, Transition** end) {
1070  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1071  logging::printf(
1072  "generateStartUntilTimes(): #transitions=%d\n",
1073  (int) (end - begin));
1074  }
1075 
1076  // It is possible that there are no matching transitions. This can happen
1077  // if the zonedbx is corrupted and ZoneInfo contains invalid fields.
1078  if (begin == end) return;
1079 
1080  Transition* prev = *begin;
1081  bool isAfterFirst = false;
1082 
1083  for (Transition** iter = begin; iter != end; ++iter) {
1084  Transition* const t = *iter;
1085 
1086  // 1) Update the untilDateTime of the previous Transition
1087  const extended::DateTuple& tt = t->transitionTime;
1088  if (isAfterFirst) {
1089  prev->untilDateTime = tt;
1090  }
1091 
1092  // 2) Calculate the current startDateTime by shifting the
1093  // transitionTime (represented in the UTC offset of the previous
1094  // transition) into the UTC offset of the *current* transition.
1095  int32_t seconds = tt.seconds + (
1096  - prev->offsetSeconds - prev->deltaSeconds
1097  + t->offsetSeconds + t->deltaSeconds);
1098  t->startDateTime = {tt.year, tt.month, tt.day, seconds, tt.suffix};
1099  extended::normalizeDateTuple(&t->startDateTime);
1100 
1101  // 3) The epochSecond of the 'transitionTime' is determined by the
1102  // UTC offset of the *previous* Transition. However, the
1103  // transitionTime can be represented by an illegal time (e.g. 24:00).
1104  // So, it is better to use the properly normalized startDateTime
1105  // (calculated above) with the *current* UTC offset.
1106  //
1107  // NOTE: We should also be able to calculate this directly from
1108  // 'transitionTimeU' which should still be a valid field, because it
1109  // hasn't been clobbered by 'untilDateTime' yet. Not sure if this saves
1110  // any CPU time though, since we still need to mutiply by 900.
1111  const extended::DateTuple& st = t->startDateTime;
1112  const acetime_t offsetSeconds =
1113  st.seconds - (t->offsetSeconds + t->deltaSeconds);
1114  LocalDate ld = LocalDate::forComponents(st.year, st.month, st.day);
1115  t->startEpochSeconds = ld.toEpochSeconds() + offsetSeconds;
1116 
1117  prev = t;
1118  isAfterFirst = true;
1119  }
1120 
1121  // The last Transition's until time is the until time of the MatchingEra.
1122  extended::DateTuple untilTimeW;
1123  extended::DateTuple untilTimeS;
1124  extended::DateTuple untilTimeU;
1125  expandDateTuple(
1126  &prev->match->untilDateTime,
1127  prev->offsetSeconds,
1128  prev->deltaSeconds,
1129  &untilTimeW,
1130  &untilTimeS,
1131  &untilTimeU);
1132  prev->untilDateTime = untilTimeW;
1133  }
1134 
1138  static void calcAbbreviations(Transition** begin, Transition** end) {
1139  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1140  logging::printf("calcAbbreviations(): #transitions: %d\n",
1141  (int) (end - begin));
1142  }
1143  for (Transition** iter = begin; iter != end; ++iter) {
1144  Transition* const t = *iter;
1145  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1146  logging::printf(
1147  "calcAbbreviations(): format:%s, deltaSeconds:%d, letter:%s\n",
1148  t->format(), t->deltaSeconds, t->abbrev);
1149  }
1150  internal::createAbbreviation(
1151  t->abbrev,
1152  internal::kAbbrevSize,
1153  t->format(),
1154  t->deltaSeconds,
1155  t->abbrev);
1156  }
1157  }
1158 
1159  private:
1160  const ZIS* mZoneInfoStore; // nullable
1161  ZIB mZoneInfoBroker;
1162 
1163  // NOTE: Maybe move mNumMatches and mMatches into a MatchStorage object.
1164  mutable uint8_t mNumMatches = 0; // actual number of matches
1165  mutable MatchingEra mMatches[kMaxMatches];
1166  mutable TransitionStorage mTransitionStorage;
1167 };
1168 
1175  extended::ZoneInfoStore,
1176  extended::ZoneInfoBroker,
1177  extended::ZoneEraBroker,
1178  extended::ZonePolicyBroker,
1179  extended::ZoneRuleBroker> {
1180 
1181  public:
1183  static const uint8_t kTypeExtended = 4;
1184 
1185  explicit ExtendedZoneProcessor(const extended::ZoneInfo* zoneInfo = nullptr)
1187  extended::ZoneInfoStore,
1188  extended::ZoneInfoBroker,
1189  extended::ZoneEraBroker,
1190  extended::ZonePolicyBroker,
1191  extended::ZoneRuleBroker>(
1192  kTypeExtended, &mZoneInfoStore, (uintptr_t) zoneInfo)
1193  {}
1194 
1195  private:
1196  extended::ZoneInfoStore mZoneInfoStore;
1197 };
1198 
1199 } // namespace ace_time
1200 
1201 #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.
extended::TransitionForSecondsTemplate< ZEB, ZPB, ZRB > TransitionForSeconds
Exposed only for testing purposes.
bool equalsZoneKey(uintptr_t zoneKey) const override
Return true if ZoneProcessor is associated with the given opaque zoneKey.
FindResult findByLocalDateTime(const LocalDateTime &ldt) const override
Return the search results at given LocalDateTime.
extended::MatchingEraTemplate< ZEB > MatchingEra
Exposed only for testing purposes.
extended::TransitionTemplate< ZEB, ZPB, ZRB > Transition
Exposed only for testing purposes.
void printTargetNameTo(Print &printer) const override
Print the full identifier (e.g.
ExtendedZoneProcessorTemplate(uint8_t type, const ZIS *zoneInfoStore, uintptr_t zoneKey)
Constructor.
void setZoneKey(uintptr_t zoneKey) override
Set the opaque zoneKey of this object to a new value, reseting any internally cached information.
extended::TransitionForDateTimeTemplate< ZEB, ZPB, ZRB > TransitionForDateTime
Exposed only for testing purposes.
void printNameTo(Print &printer) const override
Print a human-readable identifier (e.g.
uint32_t getZoneId() const override
Return the unique stable zoneId.
bool initForEpochSeconds(acetime_t epochSeconds) const
Initialize using the epochSeconds.
FindResult findByEpochSeconds(acetime_t epochSeconds) const override
void log() const
Used only for debugging.
void setZoneInfoStore(const ZIS *zoneInfoStore)
Set the zone info store at runtime.
uint8_t getTransitionAllocSize() const
Get the largest allocation size of TransitionStorage.
static const uint8_t kMaxTransitions
Max number of Transitions required for all Zones supported by this class.
void printShortNameTo(Print &printer) const override
Print a short human-readable identifier (e.g.
extended::TransitionStorageTemplate< kMaxTransitions, ZEB, ZPB, ZRB > TransitionStorage
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.
bool initForYear(int16_t year) const
Initialize the zone rules cache, keyed by the "current" year.
A specific implementation of ExtendedZoneProcessorTemplate that uses the extended::ZoneXxxBrokers cla...
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
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.
void resetAllocSize()
Reset the current allocation size.
Definition: Transition.h:751
void init()
Initialize all pools to 0 size, usually when a new year is initialized.
Definition: Transition.h:392
uint8_t getAllocSize() const
Return the maximum number of transitions which was allocated.
Definition: Transition.h:758
TransitionForDateTime findTransitionForDateTime(const LocalDateTime &ldt) const
Return the candidate Transitions matching the given dateTime.
Definition: Transition.h:669
TransitionForSeconds findTransitionForSeconds(acetime_t epochSeconds) const
Return the Transition matching the given epochSeconds.
Definition: Transition.h:571
void log() const
Verify that the indexes are valid.
Definition: Transition.h:725
Identifiers used by implementation code which need to be publically exported.
int32_t acetime_t
Type for the number of seconds from epoch.
Definition: common.h:24
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:317
uint8_t num
Number of matches: 0, 1, 2.
Definition: Transition.h:326
const TransitionTemplate< ZEB, ZPB, ZRB > * prev
The previous transition.
Definition: Transition.h:320
const TransitionTemplate< ZEB, ZPB, ZRB > * curr
The matching transition, or null if not found or in gap.
Definition: Transition.h:323
Tuple of a matching Transition and its 'fold'.
Definition: Transition.h:287
uint8_t fold
1 if corresponding datetime occurred the second time
Definition: Transition.h:292
const TransitionTemplate< ZEB, ZPB, ZRB > * curr
The matching transition, or null if not found.
Definition: Transition.h:289
uint8_t num
Number of occurrences of the resulting LocalDateTime: 0, 1, or 2.
Definition: Transition.h:300
Represents an interval of time where the time zone obeyed a certain UTC offset and DST delta.
Definition: Transition.h:114
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:266
int32_t offsetSeconds
The base offset minutes, not the total effective UTC offset.
Definition: Transition.h:185
int32_t deltaSeconds
The DST delta seconds.
Definition: Transition.h:188
char abbrev[internal::kAbbrevSize]
The calculated effective time zone abbreviation, e.g.
Definition: Transition.h:196
A simple tuple to represent a year/month pair.
static const uint8_t kSuffixS
Represents 's' or standard time.
Definition: ZoneInfoLow.h:65
static const uint8_t kSuffixW
Represents 'w' or wall time.
Definition: ZoneInfoLow.h:62
static const uint8_t kSuffixU
Represents 'u' or UTC time.
Definition: ZoneInfoLow.h:68
Representation of a given time zone, implemented as an array of ZoneEra records.
Definition: ZoneInfoLow.h:302