AceTime  2.2.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 "../zoneinfo/compat.h"
13 #include "../zoneinfo/ZonePolicy.h"
14 #include "../zoneinfo/ZoneInfo.h"
15 #include "../zoneinfo/Brokers.h"
16 #include "common/common.h" // kAbbrevSize
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_processTransitionCompareStatus;
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_processTransitionCompareStatus;
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 
512  static bool eraOverlapsInterval(
513  const MatchingEra* prevMatch,
514  const ZEB& era,
515  const extended::YearMonthTuple& startYm,
516  const extended::YearMonthTuple& untilYm) {
517  return (prevMatch == nullptr || compareEraToYearMonth(
518  prevMatch->era, untilYm.year, untilYm.month) < 0)
519  && compareEraToYearMonth(era, startYm.year, startYm.month) > 0;
520  }
521 
523  static int8_t compareEraToYearMonth(const ZEB& era,
524  int16_t year, uint8_t month) {
525  if (era.untilYear() < year) return -1;
526  if (era.untilYear() > year) return 1;
527  if (era.untilMonth() < month) return -1;
528  if (era.untilMonth() > month) return 1;
529  if (era.untilDay() > 1) return 1;
530  //if (era.untilTimeMinutes() < 0) return -1; // never possible
531  if (era.untilTimeMinutes() > 0) return 1;
532  return 0;
533  }
534 
541  static MatchingEra createMatchingEra(
542  MatchingEra* prevMatch,
543  const ZEB& era,
544  const extended::YearMonthTuple& startYm,
545  const extended::YearMonthTuple& untilYm) {
546 
547  // If prevMatch is null, set startDate to be earlier than all valid
548  // ZoneEra.
549  extended::DateTuple startDate = (prevMatch == nullptr)
550  ? extended::DateTuple{
552  1,
553  1,
554  0,
556  }
557  : extended::DateTuple{
558  prevMatch->era.untilYear(),
559  prevMatch->era.untilMonth(),
560  prevMatch->era.untilDay(),
561  (int16_t) prevMatch->era.untilTimeMinutes(),
562  prevMatch->era.untilTimeSuffix()
563  };
564  extended::DateTuple lowerBound{
565  startYm.year,
566  startYm.month,
567  1,
568  0,
570  };
571  if (startDate < lowerBound) {
572  startDate = lowerBound;
573  }
574 
575  extended::DateTuple untilDate{
576  era.untilYear(),
577  era.untilMonth(),
578  era.untilDay(),
579  (int16_t) era.untilTimeMinutes(),
580  era.untilTimeSuffix()
581  };
582  extended::DateTuple upperBound{
583  untilYm.year,
584  untilYm.month,
585  1,
586  0,
588  };
589  if (upperBound < untilDate) {
590  untilDate = upperBound;
591  }
592 
593  return {startDate, untilDate, era, prevMatch, 0, 0};
594  }
595 
600  static void createTransitions(
601  TransitionStorage& transitionStorage,
602  MatchingEra* matches,
603  uint8_t numMatches) {
604  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
605  logging::printf("createTransitions()\n");
606  }
607 
608  for (uint8_t i = 0; i < numMatches; i++) {
609  createTransitionsForMatch(transitionStorage, &matches[i]);
610  }
611  }
612 
614  static void createTransitionsForMatch(
615  TransitionStorage& transitionStorage,
616  MatchingEra* match) {
617  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
618  logging::printf("== createTransitionsForMatch()\n");
619  }
620  const ZPB policy = match->era.zonePolicy();
621  if (policy.isNull()) {
622  createTransitionsFromSimpleMatch(transitionStorage, match);
623  } else {
624  createTransitionsFromNamedMatch(transitionStorage, match);
625  }
626  }
627 
628  // Step 2A
629  static void createTransitionsFromSimpleMatch(
630  TransitionStorage& transitionStorage,
631  MatchingEra* match) {
632  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
633  logging::printf("== createTransitionsFromSimpleMatch()\n");
634  }
635 
636  Transition* freeTransition = transitionStorage.getFreeAgent();
637  createTransitionForYear(freeTransition, 0 /*not used*/,
638  ZRB() /*rule*/, match);
639  freeTransition->compareStatus = extended::CompareStatus::kExactMatch;
640  match->lastOffsetMinutes = freeTransition->offsetMinutes;
641  match->lastDeltaMinutes = freeTransition->deltaMinutes;
642  transitionStorage.addFreeAgentToActivePool();
643  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
644  transitionStorage.log();
645  }
646  }
647 
648  // Step 2B
649  static void createTransitionsFromNamedMatch(
650  TransitionStorage& transitionStorage,
651  MatchingEra* match) {
652  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
653  logging::printf("== createTransitionsFromNamedMatch()\n");
654  }
655 
656  transitionStorage.resetCandidatePool();
657  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
658  match->log(); logging::printf("\n");
659  }
660 
661  // Pass 1: Find candidate transitions using whole years.
662  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
663  logging::printf("---- Pass 1: findCandidateTransitions()\n");
664  }
665  findCandidateTransitions(transitionStorage, match);
666  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
667  transitionStorage.log();
668  }
669 
670  // Pass 2: Fix the transitions times, converting 's' and 'u' into 'w'
671  // uniformly.
672  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
673  logging::printf("---- Pass 2: fixTransitionTimes()\n");
674  }
675  fixTransitionTimes(
676  transitionStorage.getCandidatePoolBegin(),
677  transitionStorage.getCandidatePoolEnd());
678 
679  // Pass 3: Select only those Transitions which overlap with the actual
680  // start and until times of the MatchingEra.
681  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
682  logging::printf("---- Pass 3: selectActiveTransitions()\n");
683  }
684  selectActiveTransitions(
685  transitionStorage.getCandidatePoolBegin(),
686  transitionStorage.getCandidatePoolEnd());
687  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
688  transitionStorage.log();
689  }
690  Transition* lastTransition =
691  transitionStorage.addActiveCandidatesToActivePool();
692  match->lastOffsetMinutes = lastTransition->offsetMinutes;
693  match->lastDeltaMinutes = lastTransition->deltaMinutes;
694  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
695  transitionStorage.log();
696  }
697  }
698 
699  // Step 2B: Pass 1
700  static void findCandidateTransitions(
701  TransitionStorage& transitionStorage,
702  const MatchingEra* match) {
703  using extended::CompareStatus;
704 
705  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
706  logging::printf("findCandidateTransitions(): \n");
707  match->log();
708  logging::printf("\n");
709  }
710  const ZPB policy = match->era.zonePolicy();
711  uint8_t numRules = policy.numRules();
712  int16_t startY = match->startDateTime.year;
713  int16_t endY = match->untilDateTime.year;
714 
715  // The prior is referenced through a handle (i.e. pointer to pointer)
716  // because the actual pointer to the prior could change through the
717  // transitionStorage.setFreeAgentAsPriorIfValid() method.
718  Transition** prior = transitionStorage.reservePrior();
719  (*prior)->isValidPrior = false; // indicates "no prior transition"
720  for (uint8_t r = 0; r < numRules; r++) {
721  const ZRB rule = policy.rule(r);
722 
723  // Add Transitions for interior years
724  int16_t interiorYears[kMaxInteriorYears];
725  uint8_t numYears = calcInteriorYears(interiorYears, kMaxInteriorYears,
726  rule.fromYear(), rule.toYear(), startY, endY);
727  for (uint8_t y = 0; y < numYears; y++) {
728  int16_t year = interiorYears[y];
729  Transition* t = transitionStorage.getFreeAgent();
730  createTransitionForYear(t, year, rule, match);
731  CompareStatus status = compareTransitionToMatchFuzzy(t, match);
732  if (status == CompareStatus::kPrior) {
733  transitionStorage.setFreeAgentAsPriorIfValid();
734  } else if (status == CompareStatus::kWithinMatch) {
735  transitionStorage.addFreeAgentToCandidatePool();
736  } else {
737  // Must be kFarFuture.
738  // Do nothing, allowing the free agent to be reused.
739  }
740  }
741 
742  // Add Transition for prior year
743  int16_t priorYear = getMostRecentPriorYear(
744  rule.fromYear(), rule.toYear(), startY, endY);
745  if (priorYear != LocalDate::kInvalidYear) {
746  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
747  logging::printf(
748  "findCandidateTransitions(): priorYear: %d\n", priorYear);
749  }
750  Transition* t = transitionStorage.getFreeAgent();
751  createTransitionForYear(t, priorYear, rule, match);
752  transitionStorage.setFreeAgentAsPriorIfValid();
753  }
754  }
755 
756  // Add the reserved prior into the Candidate pool only if 'isValidPrior'
757  // is true.
758  if ((*prior)->isValidPrior) {
759  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
760  logging::printf(
761  "findCandidateTransitions(): adding prior to Candidate pool\n");
762  logging::printf(" ");
763  (*prior)->log();
764  logging::printf("\n");
765  }
766  transitionStorage.addPriorToCandidatePool();
767  }
768  }
769 
789  static uint8_t calcInteriorYears(
790  int16_t* interiorYears,
791  uint8_t maxInteriorYears,
792  int16_t fromYear, int16_t toYear,
793  int16_t startYear, int16_t endYear) {
794  uint8_t i = 0;
795  for (int16_t year = startYear; year <= endYear; year++) {
796  if (fromYear <= year && year <= toYear) {
797  interiorYears[i] = year;
798  i++;
799  if (i >= maxInteriorYears) break;
800  }
801  }
802  return i;
803  }
804 
810  static void createTransitionForYear(
811  Transition* t,
812  int16_t year,
813  const ZRB& rule,
814  const MatchingEra* match) {
815  t->match = match;
816  t->offsetMinutes = match->era.offsetMinutes();
817  #if ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG
818  t->rule = rule;
819  #endif
820 
821  if (rule.isNull()) {
822  // Create a Transition using the MatchingEra for the transitionTime.
823  // Used for simple MatchingEra.
824  t->transitionTime = match->startDateTime;
825  t->deltaMinutes = match->era.deltaMinutes();
826  t->letter = "";
827  } else {
828  t->transitionTime = getTransitionTime(year, rule);
829  t->deltaMinutes = rule.deltaMinutes();
830  t->letter = rule.letter();
831  }
832  }
833 
846  static int16_t getMostRecentPriorYear(
847  int16_t fromYear, int16_t toYear,
848  int16_t startYear, int16_t /*endYear*/) {
849 
850  if (fromYear < startYear) {
851  if (toYear < startYear) {
852  return toYear;
853  } else {
854  return startYear - 1;
855  }
856  } else {
858  }
859  }
860 
865  static extended::DateTuple getTransitionTime(
866  int16_t year, const ZRB& rule) {
867 
868  internal::MonthDay monthDay = internal::calcStartDayOfMonth(
869  year,
870  rule.inMonth(),
871  rule.onDayOfWeek(),
872  rule.onDayOfMonth());
873  return {
874  year,
875  monthDay.month,
876  monthDay.day,
877  (int16_t) rule.atTimeMinutes(),
878  rule.atTimeSuffix()
879  };
880  }
881 
892  static extended::CompareStatus compareTransitionToMatchFuzzy(
893  const Transition* t, const MatchingEra* match) {
894  return compareDateTupleFuzzy(
895  t->transitionTime,
896  match->startDateTime,
897  match->untilDateTime);
898  }
899 
908  static void fixTransitionTimes(Transition** begin, Transition** end) {
909  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
910  logging::printf("fixTransitionTimes(): START; #transitions=%d\n",
911  (int) (end - begin));
912  Transition::printTransitions(" ", begin, end);
913  }
914 
915  // extend first Transition to -infinity
916  Transition* prev = *begin;
917 
918  for (Transition** iter = begin; iter != end; ++iter) {
919  Transition* curr = *iter;
920  expandDateTuple(
921  &curr->transitionTime,
922  prev->offsetMinutes,
923  prev->deltaMinutes,
924  &curr->transitionTime,
925  &curr->transitionTimeS,
926  &curr->transitionTimeU);
927  prev = curr;
928  }
929  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
930  logging::printf("fixTransitionTimes(): FIXED\n");
931  Transition::printTransitions(" ", begin, end);
932  logging::printf("fixTransitionTimes(): END\n");
933  }
934  }
935 
941  static void expandDateTuple(
942  const extended::DateTuple* tt,
943  int16_t offsetMinutes,
944  int16_t deltaMinutes,
945  extended::DateTuple* ttw,
946  extended::DateTuple* tts,
947  extended::DateTuple* ttu) {
948 
949  if (tt->suffix == internal::ZoneContext::kSuffixS) {
950  *tts = *tt;
951  *ttu = {tt->year, tt->month, tt->day,
952  (int16_t) (tt->minutes - offsetMinutes),
954  *ttw = {tt->year, tt->month, tt->day,
955  (int16_t) (tt->minutes + deltaMinutes),
957  } else if (tt->suffix == internal::ZoneContext::kSuffixU) {
958  *ttu = *tt;
959  *tts = {tt->year, tt->month, tt->day,
960  (int16_t) (tt->minutes + offsetMinutes),
962  *ttw = {tt->year, tt->month, tt->day,
963  (int16_t) (tt->minutes + (offsetMinutes + deltaMinutes)),
965  } else {
966  // Explicit set the suffix to 'w' in case it was something else.
967  *ttw = *tt;
968  ttw->suffix = internal::ZoneContext::kSuffixW;
969  *tts = {tt->year, tt->month, tt->day,
970  (int16_t) (tt->minutes - deltaMinutes),
972  *ttu = {tt->year, tt->month, tt->day,
973  (int16_t) (tt->minutes - (deltaMinutes + offsetMinutes)),
975  }
976 
977  extended::normalizeDateTuple(ttw);
978  extended::normalizeDateTuple(tts);
979  extended::normalizeDateTuple(ttu);
980  }
981 
986  static void selectActiveTransitions(Transition** begin, Transition** end) {
987  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
988  logging::printf("selectActiveTransitions(): #candidates: %d\n",
989  (int) (end - begin));
990  }
991 
992  Transition* prior = nullptr;
993  for (Transition** iter = begin; iter != end; ++iter) {
994  Transition* transition = *iter;
995  processTransitionCompareStatus(transition, &prior);
996  }
997 
998  // If the latest prior transition is found, shift it to start at the
999  // startDateTime of the current match.
1000  if (prior) {
1001  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1002  logging::printf(
1003  "selectActiveTransitions(): found latest prior\n");
1004  }
1005  #if ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG
1006  prior->originalTransitionTime = prior->transitionTime;
1007  #endif
1008  prior->transitionTime = prior->match->startDateTime;
1009  }
1010  }
1011 
1018  static void processTransitionCompareStatus(
1019  Transition* transition,
1020  Transition** prior) {
1021  using extended::CompareStatus;
1022 
1023  CompareStatus status = compareTransitionToMatch(
1024  transition, transition->match);
1025  transition->compareStatus = status;
1026 
1027  if (status == CompareStatus::kExactMatch) {
1028  if (*prior) {
1029  (*prior)->compareStatus = CompareStatus::kFarPast;
1030  }
1031  (*prior) = transition;
1032  } else if (status == CompareStatus::kPrior) {
1033  if (*prior) {
1034  if ((*prior)->transitionTimeU <= transition->transitionTimeU) {
1035  (*prior)->compareStatus = CompareStatus::kFarPast;
1036  (*prior) = transition;
1037  } else {
1038  transition->compareStatus = CompareStatus::kFarPast;
1039  }
1040  } else {
1041  (*prior) = transition;
1042  }
1043  }
1044  }
1045 
1054  static extended::CompareStatus compareTransitionToMatch(
1055  const Transition* transition,
1056  const MatchingEra* match) {
1057 
1058  // Find the previous Match offsets.
1059  int16_t prevMatchOffsetMinutes;
1060  int16_t prevMatchDeltaMinutes;
1061  if (match->prevMatch) {
1062  prevMatchOffsetMinutes = match->prevMatch->lastOffsetMinutes;
1063  prevMatchDeltaMinutes = match->prevMatch->lastDeltaMinutes;
1064  } else {
1065  prevMatchOffsetMinutes = match->era.offsetMinutes();
1066  prevMatchDeltaMinutes = 0;
1067  }
1068 
1069  // Expand start times.
1070  extended::DateTuple stw;
1071  extended::DateTuple sts;
1072  extended::DateTuple stu;
1073  expandDateTuple(
1074  &match->startDateTime,
1075  prevMatchOffsetMinutes,
1076  prevMatchDeltaMinutes,
1077  &stw,
1078  &sts,
1079  &stu);
1080 
1081  // Transition times.
1082  const extended::DateTuple& ttw = transition->transitionTime;
1083  const extended::DateTuple& tts = transition->transitionTimeS;
1084  const extended::DateTuple& ttu = transition->transitionTimeU;
1085 
1086  // Compare Transition to Match, where equality is assumed if *any* of the
1087  // 'w', 's', or 'u' versions of the DateTuple are equal. This prevents
1088  // duplicate Transition instances from being created in a few cases.
1089  if (ttw == stw || tts == sts || ttu == stu) {
1090  return extended::CompareStatus::kExactMatch;
1091  }
1092 
1093  if (ttu < stu) {
1094  return extended::CompareStatus::kPrior;
1095  }
1096 
1097  // Now check if the transition occurs after the given match. The
1098  // untilDateTime of the current match uses the same UTC offsets as the
1099  // transitionTime of the current transition, so no complicated adjustments
1100  // are needed. We just make sure we compare 'w' with 'w', 's' with 's',
1101  // and 'u' with 'u'.
1102  const extended::DateTuple& matchUntil = match->untilDateTime;
1103  const extended::DateTuple* transitionTime;
1104  if (matchUntil.suffix == internal::ZoneContext::kSuffixS) {
1105  transitionTime = &tts;
1106  } else if (matchUntil.suffix == internal::ZoneContext::kSuffixU) {
1107  transitionTime = &ttu;
1108  } else { // assume 'w'
1109  transitionTime = &ttw;
1110  }
1111  if (*transitionTime < matchUntil) {
1112  return extended::CompareStatus::kWithinMatch;
1113  }
1114  return extended::CompareStatus::kFarFuture;
1115  }
1116 
1122  static void generateStartUntilTimes(Transition** begin, Transition** end) {
1123  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1124  logging::printf(
1125  "generateStartUntilTimes(): #transitions=%d\n",
1126  (int) (end - begin));
1127  }
1128 
1129  // It is possible that there are no matching transitions. This can happen
1130  // if the zonedbx is corrupted and ZoneInfo contains invalid fields.
1131  if (begin == end) return;
1132 
1133  Transition* prev = *begin;
1134  bool isAfterFirst = false;
1135 
1136  for (Transition** iter = begin; iter != end; ++iter) {
1137  Transition* const t = *iter;
1138 
1139  // 1) Update the untilDateTime of the previous Transition
1140  const extended::DateTuple& tt = t->transitionTime;
1141  if (isAfterFirst) {
1142  prev->untilDateTime = tt;
1143  }
1144 
1145  // 2) Calculate the current startDateTime by shifting the
1146  // transitionTime (represented in the UTC offset of the previous
1147  // transition) into the UTC offset of the *current* transition.
1148  int16_t minutes = tt.minutes + (
1149  - prev->offsetMinutes - prev->deltaMinutes
1150  + t->offsetMinutes + t->deltaMinutes);
1151  t->startDateTime = {tt.year, tt.month, tt.day, minutes, tt.suffix};
1152  extended::normalizeDateTuple(&t->startDateTime);
1153 
1154  // 3) The epochSecond of the 'transitionTime' is determined by the
1155  // UTC offset of the *previous* Transition. However, the
1156  // transitionTime can be represented by an illegal time (e.g. 24:00).
1157  // So, it is better to use the properly normalized startDateTime
1158  // (calculated above) with the *current* UTC offset.
1159  //
1160  // NOTE: We should also be able to calculate this directly from
1161  // 'transitionTimeU' which should still be a valid field, because it
1162  // hasn't been clobbered by 'untilDateTime' yet. Not sure if this saves
1163  // any CPU time though, since we still need to mutiply by 900.
1164  const extended::DateTuple& st = t->startDateTime;
1165  const acetime_t offsetSeconds = (acetime_t) 60
1166  * (st.minutes - (t->offsetMinutes + t->deltaMinutes));
1167  LocalDate ld = LocalDate::forComponents(st.year, st.month, st.day);
1168  t->startEpochSeconds = ld.toEpochSeconds() + offsetSeconds;
1169 
1170  prev = t;
1171  isAfterFirst = true;
1172  }
1173 
1174  // The last Transition's until time is the until time of the MatchingEra.
1175  extended::DateTuple untilTimeW;
1176  extended::DateTuple untilTimeS;
1177  extended::DateTuple untilTimeU;
1178  expandDateTuple(
1179  &prev->match->untilDateTime,
1180  prev->offsetMinutes,
1181  prev->deltaMinutes,
1182  &untilTimeW,
1183  &untilTimeS,
1184  &untilTimeU);
1185  prev->untilDateTime = untilTimeW;
1186  }
1187 
1191  static void calcAbbreviations(Transition** begin, Transition** end) {
1192  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1193  logging::printf("calcAbbreviations(): #transitions: %d\n",
1194  (int) (end - begin));
1195  }
1196  for (Transition** iter = begin; iter != end; ++iter) {
1197  Transition* const t = *iter;
1198  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1199  logging::printf(
1200  "calcAbbreviations(): format:%s, deltaMinutes:%d, letter:%s\n",
1201  t->format(), t->deltaMinutes, t->letter);
1202  }
1203  createAbbreviation(
1204  t->abbrev,
1205  internal::kAbbrevSize,
1206  t->format(),
1207  t->deltaMinutes,
1208  t->letter);
1209  }
1210  }
1211 
1220  static void createAbbreviation(
1221  char* dest,
1222  uint8_t destSize,
1223  const char* format,
1224  uint16_t deltaMinutes,
1225  const char* letterString) {
1226 
1227  // Check if FORMAT contains a '%'.
1228  if (strchr(format, '%') != nullptr) {
1229  // Check if RULES column empty, therefore no 'letter'
1230  if (letterString == nullptr) {
1231  strncpy(dest, format, destSize - 1);
1232  dest[destSize - 1] = '\0';
1233  } else {
1234  ace_common::copyReplaceString(
1235  dest, destSize, format, '%', letterString);
1236  }
1237  } else {
1238  // Check if FORMAT contains a '/'.
1239  const char* slashPos = strchr(format, '/');
1240  if (slashPos != nullptr) {
1241  if (deltaMinutes == 0) {
1242  uint8_t headLength = (slashPos - format);
1243  if (headLength >= destSize) headLength = destSize - 1;
1244  memcpy(dest, format, headLength);
1245  dest[headLength] = '\0';
1246  } else {
1247  uint8_t tailLength = strlen(slashPos+1);
1248  if (tailLength >= destSize) tailLength = destSize - 1;
1249  memcpy(dest, slashPos+1, tailLength);
1250  dest[tailLength] = '\0';
1251  }
1252  } else {
1253  // Just copy the FORMAT disregarding deltaMinutes and letterString.
1254  strncpy(dest, format, destSize - 1);
1255  dest[destSize - 1] = '\0';
1256  }
1257  }
1258  }
1259 
1260  private:
1261  const BF* mBrokerFactory; // nullable
1262  ZIB mZoneInfoBroker;
1263 
1264  // NOTE: Maybe move mNumMatches and mMatches into a MatchStorage object.
1265  mutable uint8_t mNumMatches = 0; // actual number of matches
1266  mutable MatchingEra mMatches[kMaxMatches];
1267  mutable TransitionStorage mTransitionStorage;
1268 };
1269 
1270 
1276  extended::BrokerFactory,
1277  extended::ZoneInfoBroker,
1278  extended::ZoneEraBroker,
1279  extended::ZonePolicyBroker,
1280  extended::ZoneRuleBroker> {
1281 
1282  public:
1284  static const uint8_t kTypeExtended = 4;
1285 
1286  explicit ExtendedZoneProcessor(const extended::ZoneInfo* zoneInfo = nullptr)
1288  extended::BrokerFactory,
1289  extended::ZoneInfoBroker,
1290  extended::ZoneEraBroker,
1291  extended::ZonePolicyBroker,
1292  extended::ZoneRuleBroker>(
1293  kTypeExtended, &mBrokerFactory, (uintptr_t) zoneInfo)
1294  {}
1295 
1296  private:
1297  extended::BrokerFactory mBrokerFactory;
1298 };
1299 
1300 } // namespace ace_time
1301 
1302 #endif
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:24
uint8_t fold
For findByLocalDateTime(), when type==kTypeOverlap, this is a copy of the requested LocalDateTime::fo...
Definition: ZoneProcessor.h:77
int16_t dstOffsetMinutes
DST offset of the resulting OffsetDateTime.
Definition: ZoneProcessor.h:83
int16_t reqStdOffsetMinutes
STD offset of the Transition which matched the epochSeconds requested by findByEpochSeconds(),...
Definition: ZoneProcessor.h:96
int16_t stdOffsetMinutes
STD offset of the resulting OffsetDateTime.
Definition: ZoneProcessor.h:80
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:66
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.
void resetAllocSize()
Reset the current allocation size.
Definition: Transition.h:742
void init()
Initialize all pools to 0 size, usually when a new year is initialized.
Definition: Transition.h:383
uint8_t getAllocSize() const
Return the maximum number of transitions which was allocated.
Definition: Transition.h:749
TransitionForDateTime findTransitionForDateTime(const LocalDateTime &ldt) const
Return the candidate Transitions matching the given dateTime.
Definition: Transition.h:660
TransitionForSeconds findTransitionForSeconds(acetime_t epochSeconds) const
Return the Transition matching the given epochSeconds.
Definition: Transition.h:562
void log() const
Verify that the indexes are valid.
Definition: Transition.h:716
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:308
uint8_t num
Number of matches: 0, 1, 2.
Definition: Transition.h:317
const TransitionTemplate< ZEB, ZPB, ZRB > * prev
The previous transition.
Definition: Transition.h:311
const TransitionTemplate< ZEB, ZPB, ZRB > * curr
The matching transition, or null if not found or in gap.
Definition: Transition.h:314
Tuple of a matching Transition and its 'fold'.
Definition: Transition.h:278
uint8_t fold
1 if corresponding datetime occurred the second time
Definition: Transition.h:283
const TransitionTemplate< ZEB, ZPB, ZRB > * curr
The matching transition, or null if not found.
Definition: Transition.h:280
uint8_t num
Number of occurrences of the resulting LocalDateTime: 0, 1, or 2.
Definition: Transition.h:291
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:257
int16_t offsetMinutes
The base offset minutes, not the total effective UTC offset.
Definition: Transition.h:185
char abbrev[internal::kAbbrevSize]
The calculated effective time zone abbreviation, e.g.
Definition: Transition.h:191
int16_t deltaMinutes
The DST delta minutes.
Definition: Transition.h:188
A simple tuple to represent a year/month pair.
static const uint8_t kSuffixW
Represents 'w' or wall time.
Definition: ZoneContext.h:44
static const uint8_t kSuffixS
Represents 's' or standard time.
Definition: ZoneContext.h:47
static const uint8_t kSuffixU
Represents 'u' or UTC time.
Definition: ZoneContext.h:50