AceTime  2.3.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_createAbbreviation;
32 class BasicZoneProcessorTest_calcRuleOffsetMinutes;
33 
34 class Print;
35 
36 namespace ace_time {
37 namespace basic {
38 
59 template <typename ZIB, typename ZEB, typename ZPB, typename ZRB>
66  ZEB era;
67 
77  ZRB rule;
78 
81 
87  int16_t offsetMinutes;
88 
90  int16_t deltaMinutes;
91 
93  int16_t year;
94 
100  uint8_t month;
101 
109  char abbrev[internal::kAbbrevSize];
110 
112  void log() const {
113  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
114  logging::printf("(%d/%d)", year, month);
115  if (sizeof(acetime_t) == sizeof(int)) {
116  logging::printf("; stEps: %d", startEpochSeconds);
117  } else {
118  logging::printf("; stEps: %ld", startEpochSeconds);
119  }
120  logging::printf("; offMin: %d", offsetMinutes);
121  logging::printf("; abbrev: %s", abbrev);
122  if (! rule.isNull()) {
123  logging::printf("; r.fromYear: %d", rule.fromYear());
124  logging::printf("; r.toYear: %d", rule.toYear());
125  logging::printf("; r.inMonth: %d", rule.inMonth());
126  logging::printf("; r.onDayOfMonth: %d", rule.onDayOfMonth());
127  }
128  logging::printf("\n");
129  }
130  }
131 };
132 
134 inline int8_t compareYearMonth(int16_t aYear, uint8_t aMonth,
135  int16_t bYear, uint8_t bMonth) {
136  if (aYear < bYear) return -1;
137  if (aYear > bYear) return 1;
138  if (aMonth < bMonth) return -1;
139  if (aMonth > bMonth) return 1;
140  return 0;
141 }
142 
143 } // namespace basic
144 
202 template <typename ZIS, typename ZIB, typename ZEB, typename ZPB, typename ZRB>
204  public:
207 
208  bool isLink() const override {
209  return ! mZoneInfoBroker.targetInfo().isNull();
210  }
211 
212  uint32_t getZoneId() const override {
213  return mZoneInfoBroker.zoneId();
214  }
215 
245  const LocalDateTime& ldt) const override {
246  FindResult result;
247  bool success = initForLocalDate(ldt.localDate());
248  if (!success) return result;
249 
250  // 0) Use the UTC epochSeconds to get intial guess of offset.
251  acetime_t epochSeconds0 = ldt.toEpochSeconds();
252  auto result0 = findByEpochSeconds(epochSeconds0);
253  if (result0.type == FindResult::kTypeNotFound) return result;
254  auto offset0 = TimeOffset::forSeconds(
255  result0.reqStdOffsetSeconds + result0.reqDstOffsetSeconds);
256 
257  // 1) Use offset0 to get the next epochSeconds and offset.
258  auto odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset0);
259  acetime_t epochSeconds1 = odt.toEpochSeconds();
260  auto result1 = findByEpochSeconds(epochSeconds1);
261  if (result1.type == FindResult::kTypeNotFound) return result;
262  auto offset1 = TimeOffset::forSeconds(
263  result1.reqStdOffsetSeconds + result1.reqDstOffsetSeconds);
264 
265  // 2) Use offset1 to get the next epochSeconds and offset.
266  odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset1);
267  acetime_t epochSeconds2 = odt.toEpochSeconds();
268  auto result2 = findByEpochSeconds(epochSeconds2);
269  if (result2.type == FindResult::kTypeNotFound) return result;
270  auto offset2 = TimeOffset::forSeconds(
271  result2.reqStdOffsetSeconds + result2.reqDstOffsetSeconds);
272 
273  // If offset1 and offset2 are equal, then we have an equilibrium
274  // and odt(1) must equal odt(2).
275  if (offset1 == offset2) {
276  // I think this happens for kTypeExact or kTypeOverlap, but the current
277  // algorithm cannot distinguish between the two, so let's pretend that
278  // it is kTypeExact. Pick either of result1 or result2.
279  result = result1;
280  } else {
281  // If the offsets don't match, then I think we have a kTypeGap.
282  // Pick the stdOffset and dstOffset that generate the later epochSeconds
283  // (the earlier transition), but convert into the LocalDateTime of the
284  // earlier epochSeconds (the later transition).
285  if (epochSeconds1 > epochSeconds2) {
286  result = result1;
287  } else {
288  result = result2;
289  }
290  result.type = FindResult::kTypeGap;
291  }
292 
293  return result;
294  }
295 
296  FindResult findByEpochSeconds(acetime_t epochSeconds) const override {
297  FindResult result;
298  const Transition* transition = getTransition(epochSeconds);
299  if (!transition) return result;
300 
301  result.dstOffsetSeconds = transition->deltaMinutes * kSecPerMin;
302  result.stdOffsetSeconds = (transition->offsetMinutes
303  - transition->deltaMinutes) * kSecPerMin;
304  result.reqStdOffsetSeconds = result.stdOffsetSeconds;
305  result.reqDstOffsetSeconds = result.dstOffsetSeconds;
306  result.type = FindResult::kTypeExact;
307  result.abbrev = transition->abbrev;
308 
309  return result;
310  }
311 
312  void printNameTo(Print& printer) const override {
313  mZoneInfoBroker.printNameTo(printer);
314  }
315 
316  void printShortNameTo(Print& printer) const override {
317  mZoneInfoBroker.printShortNameTo(printer);
318  }
319 
320  void printTargetNameTo(Print& printer) const override {
321  if (isLink()) {
322  mZoneInfoBroker.targetInfo().printNameTo(printer);
323  }
324  }
325 
326  void setZoneKey(uintptr_t zoneKey) override {
327  if (! mZoneInfoStore) return;
328  if (mZoneInfoBroker.equals(zoneKey)) return;
329 
330  mZoneInfoBroker = mZoneInfoStore->createZoneInfoBroker(zoneKey);
332  mNumTransitions = 0;
333  }
334 
335  bool equalsZoneKey(uintptr_t zoneKey) const override {
336  return mZoneInfoBroker.equals(zoneKey);
337  }
338 
340  void log() const {
341  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
342  logging::printf("BasicZoneProcessor:\n");
343  logging::printf(" mEpochYear: %d\n", mEpochYear);
344  logging::printf(" mYear: %d\n", mYear);
345  logging::printf(" mNumTransitions: %d\n", mNumTransitions);
346  for (int i = 0; i < mNumTransitions; i++) {
347  logging::printf(" mT[%d]=", i);
348  mTransitions[i].log();
349  }
350  }
351  }
352 
359  void setZoneInfoStore(const ZIS* zoneInfoStore) {
360  mZoneInfoStore = zoneInfoStore;
361  }
362 
363  protected:
364 
376  uint8_t type,
377  const ZIS* zoneInfoStore /*nullable*/,
378  uintptr_t zoneKey
379  ) :
380  ZoneProcessor(type),
381  mZoneInfoStore(zoneInfoStore)
382  {
383  setZoneKey(zoneKey);
384  }
385 
386  private:
387  friend class ::BasicZoneProcessorTest_priorYearOfRule;
388  friend class ::BasicZoneProcessorTest_compareRulesBeforeYear;
389  friend class ::BasicZoneProcessorTest_findLatestPriorRule;
390  friend class ::BasicZoneProcessorTest_findZoneEra;
391  friend class ::BasicZoneProcessorTest_init_primitives;
392  friend class ::BasicZoneProcessorTest_initForLocalDate;
393  friend class ::BasicZoneProcessorTest_setZoneKey;
394  friend class ::BasicZoneProcessorTest_createAbbreviation;
395  friend class ::BasicZoneProcessorTest_calcRuleOffsetMinutes;
396 
407  static const uint8_t kMaxCacheEntries = 5;
408 
414  static const acetime_t kMinEpochSeconds = INT32_MIN + 1;
415 
416  // Disable copy constructor and assignment operator.
419  delete;
420 
421  bool equals(const ZoneProcessor& other) const override {
422  return mZoneInfoBroker.equals(
423  ((const BasicZoneProcessorTemplate&) other).mZoneInfoBroker);
424  }
425 
427  const Transition* getTransition(acetime_t epochSeconds) const {
428  bool success = initForEpochSeconds(epochSeconds);
429  return (success) ? findMatch(epochSeconds) : nullptr;
430  }
431 
460  bool initForLocalDate(const LocalDate& ld) const {
461  int16_t year = ld.year();
462  if (ld.month() == 1 && ld.day() == 1) {
463  year--;
464  }
465  // Restrict to [1,9999], even though LocalDate should be able to handle
466  // [0,10000].
467  if (year <= LocalDate::kMinYear || LocalDate::kMaxYear <= year) {
468  return false;
469  }
470 
471  if (isFilled(year)) return true;
472  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
473  logging::printf("initForLocalDate(): %d (new year %d)\n",
474  ld.year(), year);
475  }
476 
477  mYear = year;
479  mNumTransitions = 0; // clear cache
480 
481  ZEB priorEra = addTransitionPriorToYear(year);
482  ZEB currentEra = addTransitionsForYear(year, priorEra);
483  addTransitionAfterYear(year, currentEra);
484  calcTransitions();
485  calcAbbreviations();
486 
487  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
488  log();
489  }
490 
491  return true;
492  }
493 
499  bool initForEpochSeconds(acetime_t epochSeconds) const {
500  LocalDate ld = LocalDate::forEpochSeconds(epochSeconds);
501  return initForLocalDate(ld);
502  }
503 
510  ZEB addTransitionPriorToYear(int16_t year) const {
511  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
512  logging::printf("addTransitionPriorToYear(): %d\n", year);
513  }
514 
515  const ZEB era = findZoneEra(mZoneInfoBroker, year - 1);
516 
517  // If the prior ZoneEra has a ZonePolicy), then find the latest rule
518  // within the ZoneEra. Otherwise, add a Transition using a rule==nullptr.
519  ZRB latest = findLatestPriorRule(era.zonePolicy(), year);
520  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
521  logging::printf("addTransitionsPriorToYear(): adding latest prior ");
522  if (latest.isNull()) {
523  logging::printf("ZR(null)\n");
524  } else {
525  logging::printf("ZR[%d,%d]\n", latest.fromYear(), latest.toYear());
526  }
527  }
528  addTransition(year - 1, 0 /*month*/, era, latest);
529 
530  return era;
531  }
532 
538  static ZRB findLatestPriorRule(const ZPB& zonePolicy, int16_t year) {
539  ZRB latest;
540  if (zonePolicy.isNull()) return latest;
541 
542  uint8_t numRules = zonePolicy.numRules();
543  for (uint8_t i = 0; i < numRules; i++) {
544  const ZRB rule = zonePolicy.rule(i);
545  // Check if rule is effective prior to the given year
546  if (rule.fromYear() < year) {
547  if ((latest.isNull()) ||
548  compareRulesBeforeYear(year, rule, latest) > 0) {
549  latest = rule;
550  }
551  }
552  }
553 
554  return latest;
555  }
556 
558  static int8_t compareRulesBeforeYear(
559  int16_t year, const ZRB& a, const ZRB& b) {
560  return basic::compareYearMonth(
561  priorYearOfRule(year, a), a.inMonth(),
562  priorYearOfRule(year, b), b.inMonth());
563  }
564 
573  static int16_t priorYearOfRule(int16_t year, const ZRB& rule) {
574  if (rule.toYear() < year) {
575  return rule.toYear();
576  }
577  return year - 1;
578  }
579 
584  ZEB addTransitionsForYear(int16_t year, const ZEB& priorEra) const {
585  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
586  logging::printf("addTransitionsForYear(): %d\n", year);
587  }
588 
589  const ZEB era = findZoneEra(mZoneInfoBroker, year);
590 
591  // If the ZonePolicy has no rules, then add a Transition which takes
592  // effect at the start time of the current year.
593  const ZPB zonePolicy = era.zonePolicy();
594  if (zonePolicy.isNull()) {
595  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
596  logging::printf("addTransitionsForYear(): adding ZE.untilY=%d\n",
597  era.untilYear());
598  }
599  addTransition(year, 0 /*month*/, era, ZRB());
600  return era;
601  }
602 
603  if (! era.equals(priorEra)) {
604  // The ZoneEra has changed, so we need to find the Rule in effect at
605  // the start of the current year of the current ZoneEra. This may be a
606  // rule far in the past, but shift the rule forward to {year, 1, 1}.
607  ZRB latestPrior = findLatestPriorRule(era.zonePolicy(), year);
608  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
609  logging::printf(
610  "addTransitionsForYear(): adding latest prior ");
611  if (latestPrior.isNull()) {
612  logging::printf("ZR(null)\n");
613  } else {
614  logging::printf("ZR[%d,%d]\n",
615  latestPrior.fromYear(), latestPrior.toYear());
616  }
617  }
618  addTransition(year, 1 /*month*/, era, latestPrior);
619  }
620 
621  // Find all directly matching transitions (i.e. the [from, to] overlap
622  // with the current year) and add them to mTransitions, in sorted order
623  // according to the ZoneRule::inMonth field.
624  uint8_t numRules = zonePolicy.numRules();
625  for (uint8_t i = 0; i < numRules; i++) {
626  const ZRB rule = zonePolicy.rule(i);
627  if ((rule.fromYear() <= year) && (year <= rule.toYear())) {
628  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
629  logging::printf(
630  "addTransitionsForYear(): adding rule ");
631  if (rule.isNull()) {
632  logging::printf("ZR(null)\n");
633  } else {
634  logging::printf("ZR[%d,%d]\n", rule.fromYear(), rule.toYear());
635  }
636  }
637  addTransition(year, 0 /*month*/, era, rule);
638  }
639  }
640 
641  return era;
642  }
643 
645  void addTransitionAfterYear(int16_t year, const ZEB& currentEra) const {
646  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
647  logging::printf("addTransitionAfterYear(): %d\n", year);
648  }
649 
650  const ZEB eraAfter = findZoneEra(mZoneInfoBroker, year + 1);
651 
652  // If the current era is the same as the following year, then we'll just
653  // assume that the latest ZoneRule carries over to Jan 1st of the next
654  // year. tzcompiler.py guarantees no ZoneRule occurs on Jan 1st.
655  if (currentEra.equals(eraAfter)) {
656  return;
657  }
658 
659  // If the ZoneEra did change, find the latest transition prior to
660  // {year + 1, 1, 1}, then shift that Transition to Jan 1st of the
661  // following year.
662  ZRB latest = findLatestPriorRule(eraAfter.zonePolicy(), year + 1);
663  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
664  logging::printf(
665  "addTransitionsAfterYear(): adding latest prior ");
666  if (latest.isNull()) {
667  logging::printf("ZR(null)\n");
668  } else {
669  logging::printf("ZR[%d,%d]\n", latest.fromYear(), latest.toYear());
670  }
671  }
672  addTransition(year + 1, 1 /*month*/, eraAfter, latest);
673  }
674 
698  void addTransition(int16_t year, uint8_t month, const ZEB& era,
699  const ZRB& 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(int16_t year, uint8_t month,
750  const ZEB& era, const ZRB& rule) {
751 
752  Transition transition;
753  int16_t deltaMinutes;
754  uint8_t mon;
755  if (rule.isNull()) {
756  mon = 1; // RULES is either '-' or 'hh:mm' so takes effect in Jan
757  deltaMinutes = era.deltaSeconds() / kSecPerMin;
758  transition.abbrev[0] = '\0';
759  } else {
760  mon = rule.inMonth();
761  deltaMinutes = rule.deltaSeconds() / kSecPerMin;
762  ace_common::strncpy_T(
763  transition.abbrev, rule.letter(), internal::kAbbrevSize - 1);
764  transition.abbrev[internal::kAbbrevSize - 1] = '\0';
765  }
766  // Clobber the month if specified.
767  if (month != 0) {
768  mon = month;
769  }
770  int16_t offsetMinutes = era.offsetSeconds() / kSecPerMin + deltaMinutes;
771 
772  transition.era = era;
773  transition.rule = rule;
774  transition.startEpochSeconds = 0;
775  transition.offsetMinutes = offsetMinutes;
776  transition.deltaMinutes = deltaMinutes;
777  transition.year = year;
778  transition.month = mon;
779  return transition;
780  }
781 
786  static ZEB findZoneEra(const ZIB& info, int16_t year) {
787  for (uint8_t i = 0; i < info.numEras(); i++) {
788  const ZEB era = info.era(i);
789  if (year < era.untilYear()) return era;
790  }
791  // Return the last ZoneEra if we run off the end.
792  return info.era(info.numEras() - 1);
793  }
794 
802  void calcTransitions() const {
803  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
804  logging::printf("calcTransitions():\n");
805  }
806 
807  // Set the initial startEpochSeconds to be -Infinity
808  Transition* prevTransition = &mTransitions[0];
809  prevTransition->startEpochSeconds = kMinEpochSeconds;
810 
811  for (uint8_t i = 1; i < mNumTransitions; i++) {
812  Transition& transition = mTransitions[i];
813  const int16_t year = transition.year;
814 
815  if (transition.rule.isNull()) {
816  // If the transition is simple (has no named rule), then the
817  // ZoneEra applies for the entire year (since BasicZoneProcessor
818  // supports only whole year in the UNTIL field). The whole year UNTIL
819  // field has an implied 'w' suffix on 00:00, we don't need to call
820  // calcRuleOffsetMinutes() with a 'w', we can just use the previous
821  // transition's offset to calculate the startDateTime of this
822  // transition.
823  //
824  // Also, when transition.rule == nullptr, the mNumTransitions should
825  // be 1, since only a single transition is added by
826  // addTransitionsForYear().
827  const int16_t prevOffsetMinutes = prevTransition->offsetMinutes;
828  OffsetDateTime startDateTime = OffsetDateTime::forComponents(
829  year, 1, 1, 0, 0, 0,
830  TimeOffset::forMinutes(prevOffsetMinutes));
831  transition.startEpochSeconds = startDateTime.toEpochSeconds();
832  } else {
833  // In this case, the transition points to a named ZonePolicy, which
834  // means that there could be multiple ZoneRules associated with the
835  // given year. For each transition, determine the startEpochSeconds,
836  // and the effective offset code.
837 
838  // Determine the start date of the rule.
839  const internal::MonthDay monthDay = internal::calcStartDayOfMonth(
840  year, transition.month, transition.rule.onDayOfWeek(),
841  transition.rule.onDayOfMonth());
842 
843  // Determine the offset of the 'atTimeSuffix'. The 'w' suffix
844  // requires the offset of the previous transition.
845  const int16_t prevOffsetMinutes = calcRuleOffsetMinutes(
846  prevTransition->offsetMinutes,
847  transition.era.offsetSeconds() / kSecPerMin,
848  transition.rule.atTimeSuffix());
849 
850  // startDateTime
851  const uint16_t minutes = transition.rule.atTimeSeconds() / 60;
852  const uint8_t atHour = minutes / 60;
853  const uint8_t atMinute = minutes % 60;
854  OffsetDateTime startDateTime = OffsetDateTime::forComponents(
855  year, monthDay.month, monthDay.day,
856  atHour, atMinute, 0 /*second*/,
857  TimeOffset::forMinutes(prevOffsetMinutes));
858  transition.startEpochSeconds = startDateTime.toEpochSeconds();
859  }
860 
861  prevTransition = &transition;
862  }
863  }
864 
871  static int16_t calcRuleOffsetMinutes(int16_t prevEffectiveOffsetMinutes,
872  int16_t currentBaseOffsetMinutes, uint8_t atSuffix) {
873  if (atSuffix == basic::ZoneContext::kSuffixW) {
874  return prevEffectiveOffsetMinutes;
875  } else if (atSuffix == basic::ZoneContext::kSuffixS) {
876  return currentBaseOffsetMinutes;
877  } else { // 'u', 'g' or 'z'
878  return 0;
879  }
880  }
881 
883  void calcAbbreviations() const {
884  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
885  logging::printf("calcAbbreviations():\n");
886  }
887 
888  for (uint8_t i = 0; i < mNumTransitions; i++) {
889  calcAbbreviation(&mTransitions[i]);
890  }
891  }
892 
894  static void calcAbbreviation(Transition* transition) {
895  internal::createAbbreviation(
896  transition->abbrev,
897  internal::kAbbrevSize,
898  transition->era.format(),
899  transition->deltaMinutes,
900  transition->abbrev);
901  }
902 
904  const Transition* findMatch(acetime_t epochSeconds) const {
905  const Transition* closestMatch = nullptr;
906  for (uint8_t i = 0; i < mNumTransitions; i++) {
907  const Transition* m = &mTransitions[i];
908  if (closestMatch == nullptr || m->startEpochSeconds <= epochSeconds) {
909  closestMatch = m;
910  }
911  }
912  return closestMatch;
913  }
914 
915  private:
916  static const int32_t kSecPerMin = 60;
917 
918  const ZIS* mZoneInfoStore; // nullable
919  ZIB mZoneInfoBroker;
920 
921  mutable uint8_t mNumTransitions = 0;
922  mutable Transition mTransitions[kMaxCacheEntries];
923 };
924 
930  basic::ZoneInfoStore,
931  basic::ZoneInfoBroker,
932  basic::ZoneEraBroker,
933  basic::ZonePolicyBroker,
934  basic::ZoneRuleBroker> {
935 
936  public:
938  static const uint8_t kTypeBasic = 3;
939 
940  explicit BasicZoneProcessor(const basic::ZoneInfo* zoneInfo = nullptr)
942  basic::ZoneInfoStore,
943  basic::ZoneInfoBroker,
944  basic::ZoneEraBroker,
945  basic::ZonePolicyBroker,
946  basic::ZoneRuleBroker>(
947  kTypeBasic, &mZoneInfoStore, (uintptr_t) zoneInfo)
948  {}
949 
950  private:
951  basic::ZoneInfoStore mZoneInfoStore;
952 };
953 
954 } // namespace ace_time
955 
956 #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 total effective UTC offset minutes at the start of transition, 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:65
static const uint8_t kSuffixW
Represents 'w' or wall time.
Definition: ZoneInfoLow.h:62
Representation of a given time zone, implemented as an array of ZoneEra records.
Definition: ZoneInfoLow.h:302