AceTime  3.0.0
Date and time classes for Arduino that support timezones from the TZ Database.
BasicZoneProcessor.h
1 /*
2  * MIT License
3  * Copyright (c) 2019 Brian T. Park
4  */
5 
6 #ifndef ACE_TIME_BASIC_ZONE_PROCESSOR_H
7 #define ACE_TIME_BASIC_ZONE_PROCESSOR_H
8 
9 #include <stdint.h>
10 #include <AceCommon.h> // strncpy_T()
11 #include "../zoneinfo/infos.h"
12 #include "common/common.h" // kAbbrevSize
13 #include "common/logging.h"
14 #include "TimeOffset.h"
15 #include "LocalDate.h"
16 #include "OffsetDateTime.h"
17 #include "ZoneProcessor.h"
18 
19 #ifndef ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG
20 #define ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG 0
21 #endif
22 
23 class BasicZoneProcessorTest_priorYearOfRule;
24 class BasicZoneProcessorTest_compareRulesBeforeYear;
25 class BasicZoneProcessorTest_findLatestPriorRule;
26 class BasicZoneProcessorTest_findZoneEra;
27 class BasicZoneProcessorTest_init_primitives;
28 class BasicZoneProcessorTest_initForLocalDate;
29 class BasicZoneProcessorTest_setZoneKey;
30 class BasicZoneProcessorTest_calcRuleOffsetMinutes;
31 
32 class Print;
33 
34 namespace ace_time {
35 namespace basic {
36 
54 template <typename D>
61  typename D::ZoneEraBroker era;
62 
72  typename D::ZoneRuleBroker rule;
73 
76 
81  int16_t offsetMinutes;
82 
84  int16_t deltaMinutes;
85 
87  int16_t year;
88 
94  uint8_t month;
95 
104 
106  void log() const {
107  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
108  logging::printf("(%d/%d)", year, month);
109  if (sizeof(acetime_t) == sizeof(int)) {
110  logging::printf("; stEps: %d", startEpochSeconds);
111  } else {
112  logging::printf("; stEps: %ld", startEpochSeconds);
113  }
114  logging::printf("; offMin: %d", offsetMinutes);
115  logging::printf("; abbrev: %s", abbrev);
116  if (! rule.isNull()) {
117  logging::printf("; r.fromYear: %d", rule.fromYear());
118  logging::printf("; r.toYear: %d", rule.toYear());
119  logging::printf("; r.inMonth: %d", rule.inMonth());
120  logging::printf("; r.onDayOfMonth: %d", rule.onDayOfMonth());
121  }
122  logging::printf("\n");
123  }
124  }
125 };
126 
128 inline int8_t compareYearMonth(int16_t aYear, uint8_t aMonth,
129  int16_t bYear, uint8_t bMonth) {
130  if (aYear < bYear) return -1;
131  if (aYear > bYear) return 1;
132  if (aMonth < bMonth) return -1;
133  if (aMonth > bMonth) return 1;
134  return 0;
135 }
136 
137 } // namespace basic
138 
190 template <typename D>
192  public:
195 
196  bool isLink() const override {
197  return ! mZoneInfoBroker.targetInfo().isNull();
198  }
199 
200  uint32_t getZoneId() const override {
201  return mZoneInfoBroker.zoneId();
202  }
203 
233  const LocalDateTime& ldt) const override {
234  FindResult result;
235  bool success = initForLocalDate(ldt.localDate());
236  if (!success) return result;
237 
238  // 0) Use the UTC epochSeconds to get intial guess of offset.
239  acetime_t epochSeconds0 = ldt.toEpochSeconds();
240  auto result0 = findByEpochSeconds(epochSeconds0);
241  if (result0.type == FindResult::kTypeNotFound) return result;
242  auto offset0 = TimeOffset::forSeconds(
243  result0.reqStdOffsetSeconds + result0.reqDstOffsetSeconds);
244 
245  // 1) Use offset0 to get the next epochSeconds and offset.
246  auto odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset0);
247  acetime_t epochSeconds1 = odt.toEpochSeconds();
248  auto result1 = findByEpochSeconds(epochSeconds1);
249  if (result1.type == FindResult::kTypeNotFound) return result;
250  auto offset1 = TimeOffset::forSeconds(
251  result1.reqStdOffsetSeconds + result1.reqDstOffsetSeconds);
252 
253  // 2) Use offset1 to get the next epochSeconds and offset.
254  odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset1);
255  acetime_t epochSeconds2 = odt.toEpochSeconds();
256  auto result2 = findByEpochSeconds(epochSeconds2);
257  if (result2.type == FindResult::kTypeNotFound) return result;
258  auto offset2 = TimeOffset::forSeconds(
259  result2.reqStdOffsetSeconds + result2.reqDstOffsetSeconds);
260 
261  // If offset1 and offset2 are equal, then we have an equilibrium
262  // and odt(1) must equal odt(2).
263  if (offset1 == offset2) {
264  // I think this happens for kTypeExact or kTypeOverlap, but the current
265  // algorithm cannot distinguish between the two, so let's pretend that
266  // it is kTypeExact. Pick either of result1 or result2.
267  result = result1;
268  } else {
269  // If the offsets don't match, then I think we have a kTypeGap.
270  // Pick the stdOffset and dstOffset that generate the later epochSeconds
271  // (the earlier transition), but convert into the LocalDateTime of the
272  // earlier epochSeconds (the later transition).
273  if (epochSeconds1 > epochSeconds2) {
274  result = result1;
275  } else {
276  result = result2;
277  }
278  result.type = FindResult::kTypeGap;
279  }
280 
281  return result;
282  }
283 
284  FindResult findByEpochSeconds(acetime_t epochSeconds) const override {
285  FindResult result;
286  const Transition* transition = getTransition(epochSeconds);
287  if (!transition) return result;
288 
289  result.dstOffsetSeconds = transition->deltaMinutes * kSecPerMin;
290  result.stdOffsetSeconds = transition->offsetMinutes * kSecPerMin;
291  result.reqStdOffsetSeconds = result.stdOffsetSeconds;
292  result.reqDstOffsetSeconds = result.dstOffsetSeconds;
293  result.type = FindResult::kTypeExact;
294  result.abbrev = transition->abbrev;
295 
296  return result;
297  }
298 
299  void printNameTo(Print& printer) const override {
300  mZoneInfoBroker.printNameTo(printer);
301  }
302 
303  void printShortNameTo(Print& printer) const override {
304  mZoneInfoBroker.printShortNameTo(printer);
305  }
306 
307  void printTargetNameTo(Print& printer) const override {
308  if (isLink()) {
309  mZoneInfoBroker.targetInfo().printNameTo(printer);
310  }
311  }
312 
313  void setZoneKey(uintptr_t zoneKey) override {
314  if (! mZoneInfoStore) return;
315  if (mZoneInfoBroker.equals(zoneKey)) return;
316 
317  mZoneInfoBroker = mZoneInfoStore->createZoneInfoBroker(zoneKey);
319  mNumTransitions = 0;
320  }
321 
322  bool equalsZoneKey(uintptr_t zoneKey) const override {
323  return mZoneInfoBroker.equals(zoneKey);
324  }
325 
327  void log() const {
328  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
329  logging::printf("BasicZoneProcessor:\n");
330  logging::printf(" mEpochYear: %d\n", mEpochYear);
331  logging::printf(" mYear: %d\n", mYear);
332  logging::printf(" mNumTransitions: %d\n", mNumTransitions);
333  for (int i = 0; i < mNumTransitions; i++) {
334  logging::printf(" mT[%d]=", i);
335  mTransitions[i].log();
336  }
337  }
338  }
339 
346  void setZoneInfoStore(const typename D::ZoneInfoStore* zoneInfoStore) {
347  mZoneInfoStore = zoneInfoStore;
348  }
349 
350  protected:
351 
364  uint8_t type,
365  const typename D::ZoneInfoStore* zoneInfoStore /*nullable*/,
366  uintptr_t zoneKey
367  ) :
368  ZoneProcessor(type),
369  mZoneInfoStore(zoneInfoStore)
370  {
371  setZoneKey(zoneKey);
372  }
373 
374  private:
375  friend class ::BasicZoneProcessorTest_priorYearOfRule;
376  friend class ::BasicZoneProcessorTest_compareRulesBeforeYear;
377  friend class ::BasicZoneProcessorTest_findLatestPriorRule;
378  friend class ::BasicZoneProcessorTest_findZoneEra;
379  friend class ::BasicZoneProcessorTest_init_primitives;
380  friend class ::BasicZoneProcessorTest_initForLocalDate;
381  friend class ::BasicZoneProcessorTest_setZoneKey;
382  friend class ::BasicZoneProcessorTest_calcRuleOffsetMinutes;
383 
394  static const uint8_t kMaxCacheEntries = 5;
395 
401  static const acetime_t kMinEpochSeconds = INT32_MIN + 1;
402 
403  // Disable copy constructor and assignment operator.
406  delete;
407 
408  bool equals(const ZoneProcessor& other) const override {
409  return mZoneInfoBroker.equals(
410  ((const BasicZoneProcessorTemplate&) other).mZoneInfoBroker);
411  }
412 
414  const Transition* getTransition(acetime_t epochSeconds) const {
415  bool success = initForEpochSeconds(epochSeconds);
416  return (success) ? findMatch(epochSeconds) : nullptr;
417  }
418 
447  bool initForLocalDate(const LocalDate& ld) const {
448  int16_t year = ld.year();
449  if (ld.month() == 1 && ld.day() == 1) {
450  year--;
451  }
452  // Restrict to [1,9999], even though LocalDate should be able to handle
453  // [0,10000].
454  if (year <= LocalDate::kMinYear || LocalDate::kMaxYear <= year) {
455  return false;
456  }
457 
458  if (isFilled(year)) return true;
459  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
460  logging::printf("initForLocalDate(): %d (new year %d)\n",
461  ld.year(), year);
462  }
463 
464  mYear = year;
466  mNumTransitions = 0; // clear cache
467 
468  typename D::ZoneEraBroker priorEra = addTransitionPriorToYear(year);
469  typename D::ZoneEraBroker currentEra =
470  addTransitionsForYear(year, priorEra);
471  addTransitionAfterYear(year, currentEra);
472  calcTransitions();
473  calcAbbreviations();
474 
475  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
476  log();
477  }
478 
479  return true;
480  }
481 
487  bool initForEpochSeconds(acetime_t epochSeconds) const {
488  LocalDate ld = LocalDate::forEpochSeconds(epochSeconds);
489  return initForLocalDate(ld);
490  }
491 
498  typename D::ZoneEraBroker addTransitionPriorToYear(int16_t year) const {
499  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
500  logging::printf("addTransitionPriorToYear(): %d\n", year);
501  }
502 
503  const typename D::ZoneEraBroker era =
504  findZoneEra(mZoneInfoBroker, year - 1);
505 
506  // If the prior ZoneEra has a ZonePolicy), then find the latest rule
507  // within the ZoneEra. Otherwise, add a Transition using a rule==nullptr.
508  typename D::ZoneRuleBroker latest =
509  findLatestPriorRule(era.zonePolicy(), year);
510  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
511  logging::printf("addTransitionsPriorToYear(): adding latest prior ");
512  if (latest.isNull()) {
513  logging::printf("ZR(null)\n");
514  } else {
515  logging::printf("ZR[%d,%d]\n", latest.fromYear(), latest.toYear());
516  }
517  }
518  addTransition(year - 1, 0 /*month*/, era, latest);
519 
520  return era;
521  }
522 
528  static typename D::ZoneRuleBroker findLatestPriorRule(
529  const typename D::ZonePolicyBroker& zonePolicy, int16_t year) {
530  typename D::ZoneRuleBroker latest;
531  if (zonePolicy.isNull()) return latest;
532 
533  uint8_t numRules = zonePolicy.numRules();
534  for (uint8_t i = 0; i < numRules; i++) {
535  const typename D::ZoneRuleBroker rule = zonePolicy.rule(i);
536  // Check if rule is effective prior to the given year
537  if (rule.fromYear() < year) {
538  if ((latest.isNull()) ||
539  compareRulesBeforeYear(year, rule, latest) > 0) {
540  latest = rule;
541  }
542  }
543  }
544 
545  return latest;
546  }
547 
549  static int8_t compareRulesBeforeYear(
550  int16_t year,
551  const typename D::ZoneRuleBroker& a,
552  const typename D::ZoneRuleBroker& b) {
553  return basic::compareYearMonth(
554  priorYearOfRule(year, a), a.inMonth(),
555  priorYearOfRule(year, b), b.inMonth());
556  }
557 
566  static int16_t priorYearOfRule(int16_t year,
567  const typename D::ZoneRuleBroker& rule) {
568  if (rule.toYear() < year) {
569  return rule.toYear();
570  }
571  return year - 1;
572  }
573 
578  typename D::ZoneEraBroker addTransitionsForYear(
579  int16_t year, const typename D::ZoneEraBroker& priorEra) const {
580  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
581  logging::printf("addTransitionsForYear(): %d\n", year);
582  }
583 
584  const typename D::ZoneEraBroker era = findZoneEra(mZoneInfoBroker, year);
585 
586  // If the ZonePolicy has no rules, then add a Transition which takes
587  // effect at the start time of the current year.
588  const typename D::ZonePolicyBroker zonePolicy = era.zonePolicy();
589  if (zonePolicy.isNull()) {
590  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
591  logging::printf("addTransitionsForYear(): adding ZE.untilY=%d\n",
592  era.untilYear());
593  }
594  addTransition(year, 0 /*month*/, era, typename D::ZoneRuleBroker());
595  return era;
596  }
597 
598  if (! era.equals(priorEra)) {
599  // The ZoneEra has changed, so we need to find the Rule in effect at
600  // the start of the current year of the current ZoneEra. This may be a
601  // rule far in the past, but shift the rule forward to {year, 1, 1}.
602  typename D::ZoneRuleBroker latestPrior =
603  findLatestPriorRule(era.zonePolicy(), year);
604  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
605  logging::printf(
606  "addTransitionsForYear(): adding latest prior ");
607  if (latestPrior.isNull()) {
608  logging::printf("ZR(null)\n");
609  } else {
610  logging::printf("ZR[%d,%d]\n",
611  latestPrior.fromYear(), latestPrior.toYear());
612  }
613  }
614  addTransition(year, 1 /*month*/, era, latestPrior);
615  }
616 
617  // Find all directly matching transitions (i.e. the [from, to] overlap
618  // with the current year) and add them to mTransitions, in sorted order
619  // according to the ZoneRule::inMonth field.
620  uint8_t numRules = zonePolicy.numRules();
621  for (uint8_t i = 0; i < numRules; i++) {
622  const typename D::ZoneRuleBroker rule = zonePolicy.rule(i);
623  if ((rule.fromYear() <= year) && (year <= rule.toYear())) {
624  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
625  logging::printf(
626  "addTransitionsForYear(): adding rule ");
627  if (rule.isNull()) {
628  logging::printf("ZR(null)\n");
629  } else {
630  logging::printf("ZR[%d,%d]\n", rule.fromYear(), rule.toYear());
631  }
632  }
633  addTransition(year, 0 /*month*/, era, rule);
634  }
635  }
636 
637  return era;
638  }
639 
641  void addTransitionAfterYear(
642  int16_t year, const typename D::ZoneEraBroker& currentEra) const {
643  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
644  logging::printf("addTransitionAfterYear(): %d\n", year);
645  }
646 
647  const typename D::ZoneEraBroker eraAfter =
648  findZoneEra(mZoneInfoBroker, year + 1);
649 
650  // If the current era is the same as the following year, then we'll just
651  // assume that the latest ZoneRule carries over to Jan 1st of the next
652  // year. tzcompiler.py guarantees no ZoneRule occurs on Jan 1st.
653  if (currentEra.equals(eraAfter)) {
654  return;
655  }
656 
657  // If the ZoneEra did change, find the latest transition prior to
658  // {year + 1, 1, 1}, then shift that Transition to Jan 1st of the
659  // following year.
660  typename D::ZoneRuleBroker latest =
661  findLatestPriorRule(eraAfter.zonePolicy(), year + 1);
662  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
663  logging::printf(
664  "addTransitionsAfterYear(): adding latest prior ");
665  if (latest.isNull()) {
666  logging::printf("ZR(null)\n");
667  } else {
668  logging::printf("ZR[%d,%d]\n", latest.fromYear(), latest.toYear());
669  }
670  }
671  addTransition(year + 1, 1 /*month*/, eraAfter, latest);
672  }
673 
697  void addTransition(int16_t year, uint8_t month,
698  const typename D::ZoneEraBroker& era,
699  const typename D::ZoneRuleBroker& rule) const {
700 
701  // If a zone needs more transitions than kMaxCacheEntries, the check below
702  // will cause the DST transition information to be inaccurate, and it is
703  // highly likely that this situation would be caught in the
704  // AceTimeValidation tests. Since these integration tests pass, I feel
705  // confident that those zones which need more than kMaxCacheEntries are
706  // already filtered out by tzcompiler.py.
707  //
708  // Ideally, the tzcompiler.py script would explicitly remove those zones
709  // which need more than kMaxCacheEntries Transitions. But this would
710  // require a Python version of the BasicZoneProcessor, and unfortunately,
711  // zone_processor.py implements only the ExtendedZoneProcessor algorithm
712  // An early version of zone_processor.py may have implemented something
713  // close to BasicZoneProcessor, and it may be available in the git
714  // history. But it seems like too much work right now to try to dig that
715  // out, just to implement the explicit check for kMaxCacheEntries. It
716  // would mean maintaining another version of zone_processor.py.
717  if (mNumTransitions >= kMaxCacheEntries) return;
718 
719  // Insert new element at the end of the list.
720  // NOTE: It is probably tempting to pass a pointer (or reference) to
721  // mTransitions[mNumTransitions] into createTransition(), instead of
722  // returning it by value. However, MemoryBenchmark shows that directly
723  // updating the Transition through the pointer increases flash memory
724  // consumption by ~110 bytes on AVR processors. It seems that creating a
725  // local copy of Transition on the stack, filling it, and then copying it
726  // by value takes fewer instructions.
727  mTransitions[mNumTransitions] = createTransition(year, month, era, rule);
728  mNumTransitions++;
729 
730  // perform an insertion sort based on ZoneRule.inMonth()
731  for (uint8_t i = mNumTransitions - 1; i > 0; i--) {
732  Transition& left = mTransitions[i - 1];
733  Transition& right = mTransitions[i];
734  // assume only 1 rule per month
735  if (basic::compareYearMonth(left.year, left.month,
736  right.year, right.month) > 0) {
737  Transition tmp = left;
738  left = right;
739  right = tmp;
740  }
741  }
742  }
743 
749  static Transition createTransition(
750  int16_t year,
751  uint8_t month,
752  const typename D::ZoneEraBroker& era,
753  const typename D::ZoneRuleBroker& rule) {
754 
755  Transition transition;
756  int16_t deltaMinutes;
757  uint8_t mon;
758  if (rule.isNull()) {
759  mon = 1; // RULES is either '-' or 'hh:mm' so takes effect in Jan
760  deltaMinutes = era.deltaSeconds() / kSecPerMin;
761  transition.abbrev[0] = '\0';
762  } else {
763  mon = rule.inMonth();
764  deltaMinutes = rule.deltaSeconds() / kSecPerMin;
765  ace_common::strncpy_T(
766  transition.abbrev, rule.letter(), kAbbrevSize - 1);
767  transition.abbrev[kAbbrevSize - 1] = '\0';
768  }
769  // Clobber the month if specified.
770  if (month != 0) {
771  mon = month;
772  }
773  int16_t offsetMinutes = era.offsetSeconds() / kSecPerMin;
774 
775  transition.era = era;
776  transition.rule = rule;
777  transition.startEpochSeconds = 0;
778  transition.offsetMinutes = offsetMinutes;
779  transition.deltaMinutes = deltaMinutes;
780  transition.year = year;
781  transition.month = mon;
782  return transition;
783  }
784 
789  static typename D::ZoneEraBroker findZoneEra(
790  const typename D::ZoneInfoBroker& info,
791  int16_t year) {
792  for (uint8_t i = 0; i < info.numEras(); i++) {
793  const typename D::ZoneEraBroker era = info.era(i);
794  if (year < era.untilYear()) return era;
795  }
796  // Return the last ZoneEra if we run off the end.
797  return info.era(info.numEras() - 1);
798  }
799 
807  void calcTransitions() const {
808  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
809  logging::printf("calcTransitions():\n");
810  }
811 
812  // Set the initial startEpochSeconds to be -Infinity
813  Transition* prevTransition = &mTransitions[0];
814  prevTransition->startEpochSeconds = kMinEpochSeconds;
815 
816  for (uint8_t i = 1; i < mNumTransitions; i++) {
817  Transition& transition = mTransitions[i];
818  const int16_t year = transition.year;
819 
820  if (transition.rule.isNull()) {
821  // If the transition is simple (has no named rule), then the
822  // ZoneEra applies for the entire year (since BasicZoneProcessor
823  // supports only whole year in the UNTIL field). The whole year UNTIL
824  // field has an implied 'w' suffix on 00:00, we don't need to call
825  // calcRuleOffsetMinutes() with a 'w', we can just use the previous
826  // transition's offset to calculate the startDateTime of this
827  // transition.
828  //
829  // Also, when transition.rule == nullptr, the mNumTransitions should
830  // be 1, since only a single transition is added by
831  // addTransitionsForYear().
832  const int16_t prevTotalOffsetMinutes = prevTransition->offsetMinutes
833  + prevTransition->deltaMinutes;
834  OffsetDateTime startDateTime = OffsetDateTime::forComponents(
835  year, 1, 1, 0, 0, 0,
836  TimeOffset::forMinutes(prevTotalOffsetMinutes));
837  transition.startEpochSeconds = startDateTime.toEpochSeconds();
838  } else {
839  // In this case, the transition points to a named ZonePolicy, which
840  // means that there could be multiple ZoneRules associated with the
841  // given year. For each transition, determine the startEpochSeconds,
842  // and the effective offset time.
843 
844  // Determine the start date of the rule.
845  const MonthDay monthDay = calcStartDayOfMonth(
846  year, transition.month, transition.rule.onDayOfWeek(),
847  transition.rule.onDayOfMonth());
848 
849  // Determine the offset of the 'atTimeSuffix'. The 'w' suffix
850  // requires the offset of the previous transition.
851  const int16_t prevTotalOffsetMinutes = calcRuleOffsetMinutes(
852  prevTransition->offsetMinutes + prevTransition->deltaMinutes,
853  transition.era.offsetSeconds() / kSecPerMin,
854  transition.rule.atTimeSuffix());
855 
856  // startDateTime
857  const uint16_t minutes = transition.rule.atTimeSeconds() / 60;
858  const uint8_t atHour = minutes / 60;
859  const uint8_t atMinute = minutes % 60;
860  OffsetDateTime startDateTime = OffsetDateTime::forComponents(
861  year, monthDay.month, monthDay.day,
862  atHour, atMinute, 0 /*second*/,
863  TimeOffset::forMinutes(prevTotalOffsetMinutes));
864  transition.startEpochSeconds = startDateTime.toEpochSeconds();
865  }
866 
867  prevTransition = &transition;
868  }
869  }
870 
877  static int16_t calcRuleOffsetMinutes(int16_t prevTotalOffsetMinutes,
878  int16_t currentBaseOffsetMinutes, uint8_t atSuffix) {
879  if (atSuffix == basic::Info::ZoneContext::kSuffixW) {
880  return prevTotalOffsetMinutes;
881  } else if (atSuffix == basic::Info::ZoneContext::kSuffixS) {
882  return currentBaseOffsetMinutes;
883  } else { // 'u', 'g' or 'z'
884  return 0;
885  }
886  }
887 
889  void calcAbbreviations() const {
890  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
891  logging::printf("calcAbbreviations():\n");
892  }
893 
894  for (uint8_t i = 0; i < mNumTransitions; i++) {
895  Transition* transition = &mTransitions[i];
896  createAbbreviation(
897  transition->abbrev,
898  kAbbrevSize,
899  transition->era.format(),
900  transition->offsetMinutes * kSecPerMin,
901  transition->deltaMinutes * kSecPerMin,
902  transition->abbrev);
903  }
904  }
905 
907  const Transition* findMatch(acetime_t epochSeconds) const {
908  const Transition* closestMatch = nullptr;
909  for (uint8_t i = 0; i < mNumTransitions; i++) {
910  const Transition* m = &mTransitions[i];
911  if (closestMatch == nullptr || m->startEpochSeconds <= epochSeconds) {
912  closestMatch = m;
913  }
914  }
915  return closestMatch;
916  }
917 
918  private:
919  static const int32_t kSecPerMin = 60;
920 
921  const typename D::ZoneInfoStore* mZoneInfoStore; // nullable
922  typename D::ZoneInfoBroker mZoneInfoBroker;
923 
924  mutable uint8_t mNumTransitions = 0;
925  mutable Transition mTransitions[kMaxCacheEntries];
926 };
927 
932 class BasicZoneProcessor: public BasicZoneProcessorTemplate<basic::Info> {
933 
934  public:
936  static const uint8_t kTypeBasic = 3;
937 
938  explicit BasicZoneProcessor(const basic::Info::ZoneInfo* zoneInfo = nullptr)
939  : BasicZoneProcessorTemplate<basic::Info>(
940  kTypeBasic, &mZoneInfoStore, (uintptr_t) zoneInfo)
941  {}
942 
943  private:
944  basic::Info::ZoneInfoStore mZoneInfoStore;
945 };
946 
947 } // namespace ace_time
948 
949 #endif
An implementation of ZoneProcessor that supports a subset of the zones containing in the TZ Database.
FindResult findByEpochSeconds(acetime_t epochSeconds) const override
Return the search results at given epochSeconds.
void log() const
Used only for debugging.
void setZoneInfoStore(const typename D::ZoneInfoStore *zoneInfoStore)
Set the zone info store at runtime.
basic::TransitionTemplate< D > Transition
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.
uint32_t getZoneId() const override
Return the unique stable zoneId.
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.
void printNameTo(Print &printer) const override
Print a human-readable identifier (e.g.
BasicZoneProcessorTemplate(uint8_t type, const typename D::ZoneInfoStore *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.
bool isLink() const override
Return true if timezone is a Link entry pointing to a Zone entry.
A specific implementation of BasicZoneProcessorTemplate that uses ZoneXxxBrokers which read from zone...
static const uint8_t kTypeBasic
Unique TimeZone type identifier for BasicZoneProcessor.
static int16_t currentEpochYear()
Get the current epoch year.
Definition: Epoch.h:27
Result of a search for transition at a specific epochSeconds or a specific LocalDateTime.
Definition: ZoneProcessor.h:23
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
const LocalDate & localDate() const
Return the LocalDate.
acetime_t toEpochSeconds() const
Return seconds since the current AceTime epoch defined by Epoch::currentEpochYear().
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
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
static OffsetDateTime forLocalDateTimeAndOffset(const LocalDateTime &localDateTime, TimeOffset timeOffset)
Factory method from LocalDateTime and TimeOffset.
static OffsetDateTime forComponents(int16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second, TimeOffset timeOffset, uint8_t fold=0)
Factory method using separated date, time, and UTC offset fields.
static TimeOffset forSeconds(int32_t seconds)
Create TimeOffset from seconds from 00:00.
Definition: TimeOffset.h:96
static TimeOffset forMinutes(int16_t minutes)
Create TimeOffset from minutes from 00:00.
Definition: TimeOffset.h:91
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.
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
Representation of a given time zone, implemented as an array of ZoneEra records.
Definition: ZoneInfoLow.h:324
Data structure that defines the start of a specific UTC offset as described by the matching ZoneEra a...
acetime_t startEpochSeconds
The calculated transition time of the given rule.
uint8_t month
Month of the transition.
D::ZoneRuleBroker rule
The Zone transition rule that matched for the the given year.
int16_t deltaMinutes
The deltaMinutes from "standard time" at the start of transition.
void log() const
Used only for debugging.
char abbrev[kAbbrevSize]
The calculated effective time zone abbreviation, e.g.
int16_t offsetMinutes
The standard time offset minutes at the start of transition, not including DST offset.
int16_t year
Year of the Transition.
D::ZoneEraBroker era
The ZoneEra that matched the given year.