AceTime  2.4.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 "../zoneinfo/brokers.h"
13 #include "common/common.h" // kAbbrevSize
14 #include "common/logging.h"
15 #include "TimeOffset.h"
16 #include "LocalDate.h"
17 #include "OffsetDateTime.h"
18 #include "ZoneProcessor.h"
19 
20 #ifndef ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG
21 #define ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG 0
22 #endif
23 
24 class BasicZoneProcessorTest_priorYearOfRule;
25 class BasicZoneProcessorTest_compareRulesBeforeYear;
26 class BasicZoneProcessorTest_findLatestPriorRule;
27 class BasicZoneProcessorTest_findZoneEra;
28 class BasicZoneProcessorTest_init_primitives;
29 class BasicZoneProcessorTest_initForLocalDate;
30 class BasicZoneProcessorTest_setZoneKey;
31 class BasicZoneProcessorTest_calcRuleOffsetMinutes;
32 
33 class Print;
34 
35 namespace ace_time {
36 namespace basic {
37 
58 template <typename ZIB, typename ZEB, typename ZPB, typename ZRB>
65  ZEB era;
66 
76  ZRB rule;
77 
80 
85  int16_t offsetMinutes;
86 
88  int16_t deltaMinutes;
89 
91  int16_t year;
92 
98  uint8_t month;
99 
107  char abbrev[internal::kAbbrevSize];
108 
110  void log() const {
111  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
112  logging::printf("(%d/%d)", year, month);
113  if (sizeof(acetime_t) == sizeof(int)) {
114  logging::printf("; stEps: %d", startEpochSeconds);
115  } else {
116  logging::printf("; stEps: %ld", startEpochSeconds);
117  }
118  logging::printf("; offMin: %d", offsetMinutes);
119  logging::printf("; abbrev: %s", abbrev);
120  if (! rule.isNull()) {
121  logging::printf("; r.fromYear: %d", rule.fromYear());
122  logging::printf("; r.toYear: %d", rule.toYear());
123  logging::printf("; r.inMonth: %d", rule.inMonth());
124  logging::printf("; r.onDayOfMonth: %d", rule.onDayOfMonth());
125  }
126  logging::printf("\n");
127  }
128  }
129 };
130 
132 inline int8_t compareYearMonth(int16_t aYear, uint8_t aMonth,
133  int16_t bYear, uint8_t bMonth) {
134  if (aYear < bYear) return -1;
135  if (aYear > bYear) return 1;
136  if (aMonth < bMonth) return -1;
137  if (aMonth > bMonth) return 1;
138  return 0;
139 }
140 
141 } // namespace basic
142 
200 template <typename ZIS, typename ZIB, typename ZEB, typename ZPB, typename ZRB>
202  public:
205 
206  bool isLink() const override {
207  return ! mZoneInfoBroker.targetInfo().isNull();
208  }
209 
210  uint32_t getZoneId() const override {
211  return mZoneInfoBroker.zoneId();
212  }
213 
243  const LocalDateTime& ldt) const override {
244  FindResult result;
245  bool success = initForLocalDate(ldt.localDate());
246  if (!success) return result;
247 
248  // 0) Use the UTC epochSeconds to get intial guess of offset.
249  acetime_t epochSeconds0 = ldt.toEpochSeconds();
250  auto result0 = findByEpochSeconds(epochSeconds0);
251  if (result0.type == FindResult::kTypeNotFound) return result;
252  auto offset0 = TimeOffset::forSeconds(
253  result0.reqStdOffsetSeconds + result0.reqDstOffsetSeconds);
254 
255  // 1) Use offset0 to get the next epochSeconds and offset.
256  auto odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset0);
257  acetime_t epochSeconds1 = odt.toEpochSeconds();
258  auto result1 = findByEpochSeconds(epochSeconds1);
259  if (result1.type == FindResult::kTypeNotFound) return result;
260  auto offset1 = TimeOffset::forSeconds(
261  result1.reqStdOffsetSeconds + result1.reqDstOffsetSeconds);
262 
263  // 2) Use offset1 to get the next epochSeconds and offset.
264  odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset1);
265  acetime_t epochSeconds2 = odt.toEpochSeconds();
266  auto result2 = findByEpochSeconds(epochSeconds2);
267  if (result2.type == FindResult::kTypeNotFound) return result;
268  auto offset2 = TimeOffset::forSeconds(
269  result2.reqStdOffsetSeconds + result2.reqDstOffsetSeconds);
270 
271  // If offset1 and offset2 are equal, then we have an equilibrium
272  // and odt(1) must equal odt(2).
273  if (offset1 == offset2) {
274  // I think this happens for kTypeExact or kTypeOverlap, but the current
275  // algorithm cannot distinguish between the two, so let's pretend that
276  // it is kTypeExact. Pick either of result1 or result2.
277  result = result1;
278  } else {
279  // If the offsets don't match, then I think we have a kTypeGap.
280  // Pick the stdOffset and dstOffset that generate the later epochSeconds
281  // (the earlier transition), but convert into the LocalDateTime of the
282  // earlier epochSeconds (the later transition).
283  if (epochSeconds1 > epochSeconds2) {
284  result = result1;
285  } else {
286  result = result2;
287  }
288  result.type = FindResult::kTypeGap;
289  }
290 
291  return result;
292  }
293 
294  FindResult findByEpochSeconds(acetime_t epochSeconds) const override {
295  FindResult result;
296  const Transition* transition = getTransition(epochSeconds);
297  if (!transition) return result;
298 
299  result.dstOffsetSeconds = transition->deltaMinutes * kSecPerMin;
300  result.stdOffsetSeconds = transition->offsetMinutes * kSecPerMin;
301  result.reqStdOffsetSeconds = result.stdOffsetSeconds;
302  result.reqDstOffsetSeconds = result.dstOffsetSeconds;
303  result.type = FindResult::kTypeExact;
304  result.abbrev = transition->abbrev;
305 
306  return result;
307  }
308 
309  void printNameTo(Print& printer) const override {
310  mZoneInfoBroker.printNameTo(printer);
311  }
312 
313  void printShortNameTo(Print& printer) const override {
314  mZoneInfoBroker.printShortNameTo(printer);
315  }
316 
317  void printTargetNameTo(Print& printer) const override {
318  if (isLink()) {
319  mZoneInfoBroker.targetInfo().printNameTo(printer);
320  }
321  }
322 
323  void setZoneKey(uintptr_t zoneKey) override {
324  if (! mZoneInfoStore) return;
325  if (mZoneInfoBroker.equals(zoneKey)) return;
326 
327  mZoneInfoBroker = mZoneInfoStore->createZoneInfoBroker(zoneKey);
329  mNumTransitions = 0;
330  }
331 
332  bool equalsZoneKey(uintptr_t zoneKey) const override {
333  return mZoneInfoBroker.equals(zoneKey);
334  }
335 
337  void log() const {
338  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
339  logging::printf("BasicZoneProcessor:\n");
340  logging::printf(" mEpochYear: %d\n", mEpochYear);
341  logging::printf(" mYear: %d\n", mYear);
342  logging::printf(" mNumTransitions: %d\n", mNumTransitions);
343  for (int i = 0; i < mNumTransitions; i++) {
344  logging::printf(" mT[%d]=", i);
345  mTransitions[i].log();
346  }
347  }
348  }
349 
356  void setZoneInfoStore(const ZIS* zoneInfoStore) {
357  mZoneInfoStore = zoneInfoStore;
358  }
359 
360  protected:
361 
373  uint8_t type,
374  const ZIS* zoneInfoStore /*nullable*/,
375  uintptr_t zoneKey
376  ) :
377  ZoneProcessor(type),
378  mZoneInfoStore(zoneInfoStore)
379  {
380  setZoneKey(zoneKey);
381  }
382 
383  private:
384  friend class ::BasicZoneProcessorTest_priorYearOfRule;
385  friend class ::BasicZoneProcessorTest_compareRulesBeforeYear;
386  friend class ::BasicZoneProcessorTest_findLatestPriorRule;
387  friend class ::BasicZoneProcessorTest_findZoneEra;
388  friend class ::BasicZoneProcessorTest_init_primitives;
389  friend class ::BasicZoneProcessorTest_initForLocalDate;
390  friend class ::BasicZoneProcessorTest_setZoneKey;
391  friend class ::BasicZoneProcessorTest_calcRuleOffsetMinutes;
392 
403  static const uint8_t kMaxCacheEntries = 5;
404 
410  static const acetime_t kMinEpochSeconds = INT32_MIN + 1;
411 
412  // Disable copy constructor and assignment operator.
415  delete;
416 
417  bool equals(const ZoneProcessor& other) const override {
418  return mZoneInfoBroker.equals(
419  ((const BasicZoneProcessorTemplate&) other).mZoneInfoBroker);
420  }
421 
423  const Transition* getTransition(acetime_t epochSeconds) const {
424  bool success = initForEpochSeconds(epochSeconds);
425  return (success) ? findMatch(epochSeconds) : nullptr;
426  }
427 
456  bool initForLocalDate(const LocalDate& ld) const {
457  int16_t year = ld.year();
458  if (ld.month() == 1 && ld.day() == 1) {
459  year--;
460  }
461  // Restrict to [1,9999], even though LocalDate should be able to handle
462  // [0,10000].
463  if (year <= LocalDate::kMinYear || LocalDate::kMaxYear <= year) {
464  return false;
465  }
466 
467  if (isFilled(year)) return true;
468  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
469  logging::printf("initForLocalDate(): %d (new year %d)\n",
470  ld.year(), year);
471  }
472 
473  mYear = year;
475  mNumTransitions = 0; // clear cache
476 
477  ZEB priorEra = addTransitionPriorToYear(year);
478  ZEB currentEra = addTransitionsForYear(year, priorEra);
479  addTransitionAfterYear(year, currentEra);
480  calcTransitions();
481  calcAbbreviations();
482 
483  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
484  log();
485  }
486 
487  return true;
488  }
489 
495  bool initForEpochSeconds(acetime_t epochSeconds) const {
496  LocalDate ld = LocalDate::forEpochSeconds(epochSeconds);
497  return initForLocalDate(ld);
498  }
499 
506  ZEB addTransitionPriorToYear(int16_t year) const {
507  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
508  logging::printf("addTransitionPriorToYear(): %d\n", year);
509  }
510 
511  const ZEB era = findZoneEra(mZoneInfoBroker, year - 1);
512 
513  // If the prior ZoneEra has a ZonePolicy), then find the latest rule
514  // within the ZoneEra. Otherwise, add a Transition using a rule==nullptr.
515  ZRB latest = findLatestPriorRule(era.zonePolicy(), year);
516  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
517  logging::printf("addTransitionsPriorToYear(): adding latest prior ");
518  if (latest.isNull()) {
519  logging::printf("ZR(null)\n");
520  } else {
521  logging::printf("ZR[%d,%d]\n", latest.fromYear(), latest.toYear());
522  }
523  }
524  addTransition(year - 1, 0 /*month*/, era, latest);
525 
526  return era;
527  }
528 
534  static ZRB findLatestPriorRule(const ZPB& zonePolicy, int16_t year) {
535  ZRB latest;
536  if (zonePolicy.isNull()) return latest;
537 
538  uint8_t numRules = zonePolicy.numRules();
539  for (uint8_t i = 0; i < numRules; i++) {
540  const ZRB rule = zonePolicy.rule(i);
541  // Check if rule is effective prior to the given year
542  if (rule.fromYear() < year) {
543  if ((latest.isNull()) ||
544  compareRulesBeforeYear(year, rule, latest) > 0) {
545  latest = rule;
546  }
547  }
548  }
549 
550  return latest;
551  }
552 
554  static int8_t compareRulesBeforeYear(
555  int16_t year, const ZRB& a, const ZRB& b) {
556  return basic::compareYearMonth(
557  priorYearOfRule(year, a), a.inMonth(),
558  priorYearOfRule(year, b), b.inMonth());
559  }
560 
569  static int16_t priorYearOfRule(int16_t year, const ZRB& rule) {
570  if (rule.toYear() < year) {
571  return rule.toYear();
572  }
573  return year - 1;
574  }
575 
580  ZEB addTransitionsForYear(int16_t year, const ZEB& priorEra) const {
581  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
582  logging::printf("addTransitionsForYear(): %d\n", year);
583  }
584 
585  const ZEB era = findZoneEra(mZoneInfoBroker, year);
586 
587  // If the ZonePolicy has no rules, then add a Transition which takes
588  // effect at the start time of the current year.
589  const ZPB zonePolicy = era.zonePolicy();
590  if (zonePolicy.isNull()) {
591  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
592  logging::printf("addTransitionsForYear(): adding ZE.untilY=%d\n",
593  era.untilYear());
594  }
595  addTransition(year, 0 /*month*/, era, ZRB());
596  return era;
597  }
598 
599  if (! era.equals(priorEra)) {
600  // The ZoneEra has changed, so we need to find the Rule in effect at
601  // the start of the current year of the current ZoneEra. This may be a
602  // rule far in the past, but shift the rule forward to {year, 1, 1}.
603  ZRB latestPrior = 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 ZRB 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(int16_t year, const ZEB& currentEra) const {
642  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
643  logging::printf("addTransitionAfterYear(): %d\n", year);
644  }
645 
646  const ZEB eraAfter = findZoneEra(mZoneInfoBroker, year + 1);
647 
648  // If the current era is the same as the following year, then we'll just
649  // assume that the latest ZoneRule carries over to Jan 1st of the next
650  // year. tzcompiler.py guarantees no ZoneRule occurs on Jan 1st.
651  if (currentEra.equals(eraAfter)) {
652  return;
653  }
654 
655  // If the ZoneEra did change, find the latest transition prior to
656  // {year + 1, 1, 1}, then shift that Transition to Jan 1st of the
657  // following year.
658  ZRB latest = findLatestPriorRule(eraAfter.zonePolicy(), year + 1);
659  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
660  logging::printf(
661  "addTransitionsAfterYear(): adding latest prior ");
662  if (latest.isNull()) {
663  logging::printf("ZR(null)\n");
664  } else {
665  logging::printf("ZR[%d,%d]\n", latest.fromYear(), latest.toYear());
666  }
667  }
668  addTransition(year + 1, 1 /*month*/, eraAfter, latest);
669  }
670 
694  void addTransition(int16_t year, uint8_t month, const ZEB& era,
695  const ZRB& rule) const {
696 
697  // If a zone needs more transitions than kMaxCacheEntries, the check below
698  // will cause the DST transition information to be inaccurate, and it is
699  // highly likely that this situation would be caught in the
700  // AceTimeValidation tests. Since these integration tests pass, I feel
701  // confident that those zones which need more than kMaxCacheEntries are
702  // already filtered out by tzcompiler.py.
703  //
704  // Ideally, the tzcompiler.py script would explicitly remove those zones
705  // which need more than kMaxCacheEntries Transitions. But this would
706  // require a Python version of the BasicZoneProcessor, and unfortunately,
707  // zone_processor.py implements only the ExtendedZoneProcessor algorithm
708  // An early version of zone_processor.py may have implemented something
709  // close to BasicZoneProcessor, and it may be available in the git
710  // history. But it seems like too much work right now to try to dig that
711  // out, just to implement the explicit check for kMaxCacheEntries. It
712  // would mean maintaining another version of zone_processor.py.
713  if (mNumTransitions >= kMaxCacheEntries) return;
714 
715  // Insert new element at the end of the list.
716  // NOTE: It is probably tempting to pass a pointer (or reference) to
717  // mTransitions[mNumTransitions] into createTransition(), instead of
718  // returning it by value. However, MemoryBenchmark shows that directly
719  // updating the Transition through the pointer increases flash memory
720  // consumption by ~110 bytes on AVR processors. It seems that creating a
721  // local copy of Transition on the stack, filling it, and then copying it
722  // by value takes fewer instructions.
723  mTransitions[mNumTransitions] = createTransition(year, month, era, rule);
724  mNumTransitions++;
725 
726  // perform an insertion sort based on ZoneRule.inMonth()
727  for (uint8_t i = mNumTransitions - 1; i > 0; i--) {
728  Transition& left = mTransitions[i - 1];
729  Transition& right = mTransitions[i];
730  // assume only 1 rule per month
731  if (basic::compareYearMonth(left.year, left.month,
732  right.year, right.month) > 0) {
733  Transition tmp = left;
734  left = right;
735  right = tmp;
736  }
737  }
738  }
739 
745  static Transition createTransition(int16_t year, uint8_t month,
746  const ZEB& era, const ZRB& rule) {
747 
748  Transition transition;
749  int16_t deltaMinutes;
750  uint8_t mon;
751  if (rule.isNull()) {
752  mon = 1; // RULES is either '-' or 'hh:mm' so takes effect in Jan
753  deltaMinutes = era.deltaSeconds() / kSecPerMin;
754  transition.abbrev[0] = '\0';
755  } else {
756  mon = rule.inMonth();
757  deltaMinutes = rule.deltaSeconds() / kSecPerMin;
758  ace_common::strncpy_T(
759  transition.abbrev, rule.letter(), internal::kAbbrevSize - 1);
760  transition.abbrev[internal::kAbbrevSize - 1] = '\0';
761  }
762  // Clobber the month if specified.
763  if (month != 0) {
764  mon = month;
765  }
766  int16_t offsetMinutes = era.offsetSeconds() / kSecPerMin;
767 
768  transition.era = era;
769  transition.rule = rule;
770  transition.startEpochSeconds = 0;
771  transition.offsetMinutes = offsetMinutes;
772  transition.deltaMinutes = deltaMinutes;
773  transition.year = year;
774  transition.month = mon;
775  return transition;
776  }
777 
782  static ZEB findZoneEra(const ZIB& info, int16_t year) {
783  for (uint8_t i = 0; i < info.numEras(); i++) {
784  const ZEB era = info.era(i);
785  if (year < era.untilYear()) return era;
786  }
787  // Return the last ZoneEra if we run off the end.
788  return info.era(info.numEras() - 1);
789  }
790 
798  void calcTransitions() const {
799  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
800  logging::printf("calcTransitions():\n");
801  }
802 
803  // Set the initial startEpochSeconds to be -Infinity
804  Transition* prevTransition = &mTransitions[0];
805  prevTransition->startEpochSeconds = kMinEpochSeconds;
806 
807  for (uint8_t i = 1; i < mNumTransitions; i++) {
808  Transition& transition = mTransitions[i];
809  const int16_t year = transition.year;
810 
811  if (transition.rule.isNull()) {
812  // If the transition is simple (has no named rule), then the
813  // ZoneEra applies for the entire year (since BasicZoneProcessor
814  // supports only whole year in the UNTIL field). The whole year UNTIL
815  // field has an implied 'w' suffix on 00:00, we don't need to call
816  // calcRuleOffsetMinutes() with a 'w', we can just use the previous
817  // transition's offset to calculate the startDateTime of this
818  // transition.
819  //
820  // Also, when transition.rule == nullptr, the mNumTransitions should
821  // be 1, since only a single transition is added by
822  // addTransitionsForYear().
823  const int16_t prevTotalOffsetMinutes = prevTransition->offsetMinutes
824  + prevTransition->deltaMinutes;
825  OffsetDateTime startDateTime = OffsetDateTime::forComponents(
826  year, 1, 1, 0, 0, 0,
827  TimeOffset::forMinutes(prevTotalOffsetMinutes));
828  transition.startEpochSeconds = startDateTime.toEpochSeconds();
829  } else {
830  // In this case, the transition points to a named ZonePolicy, which
831  // means that there could be multiple ZoneRules associated with the
832  // given year. For each transition, determine the startEpochSeconds,
833  // and the effective offset time.
834 
835  // Determine the start date of the rule.
836  const internal::MonthDay monthDay = internal::calcStartDayOfMonth(
837  year, transition.month, transition.rule.onDayOfWeek(),
838  transition.rule.onDayOfMonth());
839 
840  // Determine the offset of the 'atTimeSuffix'. The 'w' suffix
841  // requires the offset of the previous transition.
842  const int16_t prevTotalOffsetMinutes = calcRuleOffsetMinutes(
843  prevTransition->offsetMinutes + prevTransition->deltaMinutes,
844  transition.era.offsetSeconds() / kSecPerMin,
845  transition.rule.atTimeSuffix());
846 
847  // startDateTime
848  const uint16_t minutes = transition.rule.atTimeSeconds() / 60;
849  const uint8_t atHour = minutes / 60;
850  const uint8_t atMinute = minutes % 60;
851  OffsetDateTime startDateTime = OffsetDateTime::forComponents(
852  year, monthDay.month, monthDay.day,
853  atHour, atMinute, 0 /*second*/,
854  TimeOffset::forMinutes(prevTotalOffsetMinutes));
855  transition.startEpochSeconds = startDateTime.toEpochSeconds();
856  }
857 
858  prevTransition = &transition;
859  }
860  }
861 
868  static int16_t calcRuleOffsetMinutes(int16_t prevTotalOffsetMinutes,
869  int16_t currentBaseOffsetMinutes, uint8_t atSuffix) {
870  if (atSuffix == basic::ZoneContext::kSuffixW) {
871  return prevTotalOffsetMinutes;
872  } else if (atSuffix == basic::ZoneContext::kSuffixS) {
873  return currentBaseOffsetMinutes;
874  } else { // 'u', 'g' or 'z'
875  return 0;
876  }
877  }
878 
880  void calcAbbreviations() const {
881  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
882  logging::printf("calcAbbreviations():\n");
883  }
884 
885  for (uint8_t i = 0; i < mNumTransitions; i++) {
886  Transition* transition = &mTransitions[i];
887  internal::createAbbreviation(
888  transition->abbrev,
889  internal::kAbbrevSize,
890  transition->era.format(),
891  transition->offsetMinutes * kSecPerMin,
892  transition->deltaMinutes * kSecPerMin,
893  transition->abbrev);
894  }
895  }
896 
898  const Transition* findMatch(acetime_t epochSeconds) const {
899  const Transition* closestMatch = nullptr;
900  for (uint8_t i = 0; i < mNumTransitions; i++) {
901  const Transition* m = &mTransitions[i];
902  if (closestMatch == nullptr || m->startEpochSeconds <= epochSeconds) {
903  closestMatch = m;
904  }
905  }
906  return closestMatch;
907  }
908 
909  private:
910  static const int32_t kSecPerMin = 60;
911 
912  const ZIS* mZoneInfoStore; // nullable
913  ZIB mZoneInfoBroker;
914 
915  mutable uint8_t mNumTransitions = 0;
916  mutable Transition mTransitions[kMaxCacheEntries];
917 };
918 
924  basic::ZoneInfoStore,
925  basic::ZoneInfoBroker,
926  basic::ZoneEraBroker,
927  basic::ZonePolicyBroker,
928  basic::ZoneRuleBroker> {
929 
930  public:
932  static const uint8_t kTypeBasic = 3;
933 
934  explicit BasicZoneProcessor(const basic::ZoneInfo* zoneInfo = nullptr)
936  basic::ZoneInfoStore,
937  basic::ZoneInfoBroker,
938  basic::ZoneEraBroker,
939  basic::ZonePolicyBroker,
940  basic::ZoneRuleBroker>(
941  kTypeBasic, &mZoneInfoStore, (uintptr_t) zoneInfo)
942  {}
943 
944  private:
945  basic::ZoneInfoStore mZoneInfoStore;
946 };
947 
948 } // namespace ace_time
949 
950 #endif
An implementation of ZoneProcessor that supports a subset of the zones containing in the TZ Database.
bool isLink() const override
Return true if timezone is a Link entry pointing to a Zone entry.
basic::TransitionTemplate< ZIB, 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.
void printNameTo(Print &printer) const override
Print a human-readable identifier (e.g.
FindResult findByEpochSeconds(acetime_t epochSeconds) const override
Return the search results at given epochSeconds.
void setZoneInfoStore(const ZIS *zoneInfoStore)
Set the zone info store at runtime.
void log() const
Used only for debugging.
BasicZoneProcessorTemplate(uint8_t type, const ZIS *zoneInfoStore, uintptr_t zoneKey)
Constructor.
void printShortNameTo(Print &printer) const override
Print a short human-readable identifier (e.g.
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 printTargetNameTo(Print &printer) const override
Print the full identifier (e.g.
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
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.
int32_t acetime_t
Type for the number of seconds from epoch.
Definition: common.h:24
Data structure that defines the start of a specific UTC offset as described by the matching ZoneEra a...
uint8_t month
Month of the transition.
acetime_t startEpochSeconds
The calculated transition time of the given rule.
ZRB rule
The Zone transition rule that matched for the the given year.
ZEB era
The ZoneEra that matched the given year.
void log() const
Used only for debugging.
int16_t year
Year of the Transition.
int16_t offsetMinutes
The standard time offset minutes at the start of transition, not including DST offset.
char abbrev[internal::kAbbrevSize]
The calculated effective time zone abbreviation, e.g.
int16_t deltaMinutes
The deltaMinutes from "standard time" at the start of transition.
static const uint8_t kSuffixS
Represents 's' or standard time.
Definition: ZoneInfoLow.h:83
static const uint8_t kSuffixW
Represents 'w' or wall time.
Definition: ZoneInfoLow.h:80
Representation of a given time zone, implemented as an array of ZoneEra records.
Definition: ZoneInfoLow.h:320