AceTime  0.8
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
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 <string.h> // strchr()
10 #include <stdint.h>
11 #include "internal/ZonePolicy.h"
12 #include "internal/ZoneInfo.h"
13 #include "internal/Brokers.h"
14 #include "common/logging.h"
15 #include "TimeOffset.h"
16 #include "LocalDate.h"
17 #include "OffsetDateTime.h"
18 #include "ZoneProcessor.h"
19 
20 #define ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG 0
21 
22 class BasicZoneProcessorTest_priorYearOfRule;
23 class BasicZoneProcessorTest_compareRulesBeforeYear;
24 class BasicZoneProcessorTest_findLatestPriorRule;
25 class BasicZoneProcessorTest_findZoneEra;
26 class BasicZoneProcessorTest_init_primitives;
27 class BasicZoneProcessorTest_init;
28 class BasicZoneProcessorTest_setZoneInfo;
29 class BasicZoneProcessorTest_createAbbreviation;
30 class BasicZoneProcessorTest_calcStartDayOfMonth;
31 class BasicZoneProcessorTest_calcRuleOffsetMinutes;
32 
33 namespace ace_time {
34 
35 template<uint8_t SIZE, uint8_t TYPE, typename ZS, typename ZI, typename ZIB>
37 
38 namespace basic {
39 
55 struct Transition {
63  static const uint8_t kAbbrevSize = 6 + 1;
64 
71 
82 
84  acetime_t startEpochSeconds;
85 
91  int16_t offsetMinutes;
92 
94  int16_t deltaMinutes;
95 
97  int8_t yearTiny;
98 
104  uint8_t month;
105 
114 
116  void log() const {
117  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
118  logging::printf("(%d/%d)", yearTiny, month);
119  if (sizeof(acetime_t) == sizeof(int)) {
120  logging::printf("; stEps: %d", startEpochSeconds);
121  } else {
122  logging::printf("; stEps: %ld", startEpochSeconds);
123  }
124  logging::printf("; offMin: %d", offsetMinutes);
125  logging::printf("; abbrev: %s", abbrev);
126  if (rule.isNotNull()) {
127  logging::printf("; r.fromYear: %d", rule.fromYearTiny());
128  logging::printf("; r.toYear: %d", rule.toYearTiny());
129  logging::printf("; r.inMonth: %d", rule.inMonth());
130  logging::printf("; r.onDayOfMonth: %d", rule.onDayOfMonth());
131  }
132  logging::printf("\n");
133  }
134  }
135 };
136 
138 struct MonthDay {
139  uint8_t month;
140  uint8_t day;
141 };
142 
144 struct YearMonth {
145  int8_t year;
146  uint8_t month;
147 };
148 
150 inline int8_t compareYearMonth(const YearMonth& a, const YearMonth& b) {
151  if (a.year < b.year) return -1;
152  if (a.year > b.year) return 1;
153  if (a.month < b.month) return -1;
154  if (a.month > b.month) return 1;
155  return 0;
156 }
157 
158 } // namespace basic
159 
210  public:
215  explicit BasicZoneProcessor(const basic::ZoneInfo* zoneInfo = nullptr):
216  ZoneProcessor(kTypeBasic),
217  mZoneInfo(zoneInfo) {}
218 
220  const void* getZoneInfo() const override {
221  return mZoneInfo.zoneInfo();
222  }
223 
224  uint32_t getZoneId() const override { return mZoneInfo.zoneId(); }
225 
226  TimeOffset getUtcOffset(acetime_t epochSeconds) const override {
227  const basic::Transition* transition = getTransition(epochSeconds);
228  int16_t minutes = (transition)
230  return TimeOffset::forMinutes(minutes);
231  }
232 
233  TimeOffset getDeltaOffset(acetime_t epochSeconds) const override {
234  const basic::Transition* transition = getTransition(epochSeconds);
235  int16_t minutes = (transition)
236  ? transition->deltaMinutes : TimeOffset::kErrorMinutes;
237  return TimeOffset::forMinutes(minutes);
238  }
239 
240  const char* getAbbrev(acetime_t epochSeconds) const override {
241  const basic::Transition* transition = getTransition(epochSeconds);
242  return (transition) ? transition->abbrev : "";
243  }
244 
274  OffsetDateTime getOffsetDateTime(const LocalDateTime& ldt) const override {
275  // Only a single local variable of OffsetDateTime used, to allow Return
276  // Value Optimization (and save 20 bytes of flash for WorldClock).
277  OffsetDateTime odt;
278  bool success = init(ldt.localDate());
279  if (success) {
280  // 0) Use the UTC epochSeconds to get intial guess of offset.
281  acetime_t epochSeconds0 = ldt.toEpochSeconds();
282  auto offset0 = getUtcOffset(epochSeconds0);
283 
284  // 1) Use offset0 to get the next epochSeconds and offset.
285  odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset0);
286  acetime_t epochSeconds1 = odt.toEpochSeconds();
287  auto offset1 = getUtcOffset(epochSeconds1);
288 
289  // 2) Use offset1 to get the next epochSeconds and offset.
290  odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset1);
291  acetime_t epochSeconds2 = odt.toEpochSeconds();
292  auto offset2 = getUtcOffset(epochSeconds2);
293 
294  // If offset1 and offset2 are equal, then we have an equilibrium
295  // and odt(1) must equal odt(2), so we can just return the last odt.
296  if (offset1 == offset2) {
297  // pass
298  } else {
299  // Pick the later epochSeconds and offset
300  acetime_t epochSeconds;
301  TimeOffset offset;
302  if (epochSeconds1 > epochSeconds2) {
303  epochSeconds = epochSeconds1;
304  offset = offset1;
305  } else {
306  epochSeconds = epochSeconds2;
307  offset = offset2;
308  }
309  odt = OffsetDateTime::forEpochSeconds(epochSeconds, offset);
310  }
311  } else {
312  odt = OffsetDateTime::forError();
313  }
314 
315  return odt;
316  }
317 
318  void printTo(Print& printer) const override;
319 
320  void printShortTo(Print& printer) const override;
321 
323  void log() const {
324  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
325  if (!mIsFilled) {
326  logging::printf("*not initialized*\n");
327  return;
328  }
329  logging::printf("mYearTiny: %d\n", mYearTiny);
330  logging::printf("mNumTransitions: %d\n", mNumTransitions);
331  for (int i = 0; i < mNumTransitions; i++) {
332  logging::printf("mT[%d]=", i);
333  mTransitions[i].log();
334  }
335  }
336  }
337 
357  static basic::MonthDay calcStartDayOfMonth(int16_t year, uint8_t month,
358  uint8_t onDayOfWeek, int8_t onDayOfMonth) {
359  if (onDayOfWeek == 0) return {month, (uint8_t) onDayOfMonth};
360 
361  if (onDayOfMonth >= 0) {
362  // Convert "last{Xxx}" to "last{Xxx}>={daysInMonth-6}".
363  uint8_t daysInMonth = LocalDate::daysInMonth(year, month);
364  if (onDayOfMonth == 0) {
365  onDayOfMonth = daysInMonth - 6;
366  }
367 
368  auto limitDate = LocalDate::forComponents(year, month, onDayOfMonth);
369  uint8_t dayOfWeekShift = (onDayOfWeek - limitDate.dayOfWeek() + 7) % 7;
370  uint8_t day = (uint8_t) (onDayOfMonth + dayOfWeekShift);
371  if (day > daysInMonth) {
372  // TODO: Support shifting from Dec to Jan of following year.
373  day -= daysInMonth;
374  month++;
375  }
376  return {month, day};
377  } else {
378  onDayOfMonth = -onDayOfMonth;
379  auto limitDate = LocalDate::forComponents(year, month, onDayOfMonth);
380  int8_t dayOfWeekShift = (limitDate.dayOfWeek() - onDayOfWeek + 7) % 7;
381  int8_t day = onDayOfMonth - dayOfWeekShift;
382  if (day < 1) {
383  // TODO: Support shifting from Jan to Dec of the previous year.
384  month--;
385  uint8_t daysInPrevMonth = LocalDate::daysInMonth(year, month);
386  day += daysInPrevMonth;
387  }
388  return {month, (uint8_t) day};
389  }
390  }
391 
392  private:
393  friend class ::BasicZoneProcessorTest_priorYearOfRule;
394  friend class ::BasicZoneProcessorTest_compareRulesBeforeYear;
395  friend class ::BasicZoneProcessorTest_findLatestPriorRule;
396  friend class ::BasicZoneProcessorTest_findZoneEra;
397  friend class ::BasicZoneProcessorTest_init_primitives;
398  friend class ::BasicZoneProcessorTest_init;
399  friend class ::BasicZoneProcessorTest_setZoneInfo;
400  friend class ::BasicZoneProcessorTest_createAbbreviation;
401  friend class ::BasicZoneProcessorTest_calcStartDayOfMonth;
402  friend class ::BasicZoneProcessorTest_calcRuleOffsetMinutes;
403 
404  template<uint8_t SIZE, uint8_t TYPE, typename ZS, typename ZI, typename ZIB>
405  friend class ZoneProcessorCacheImpl; // setZoneInfo()
406 
417  static const uint8_t kMaxCacheEntries = 5;
418 
424  static const acetime_t kMinEpochSeconds = INT32_MIN + 1;
425 
426  // Disable copy constructor and assignment operator.
427  BasicZoneProcessor(const BasicZoneProcessor&) = delete;
428  BasicZoneProcessor& operator=(const BasicZoneProcessor&) = delete;
429 
430  bool equals(const ZoneProcessor& other) const override {
431  const auto& that = (const BasicZoneProcessor&) other;
432  return getZoneInfo() == that.getZoneInfo();
433  }
434 
446  void setZoneInfo(const void* zoneInfo) override {
447  if (mZoneInfo.zoneInfo() == zoneInfo) return;
448 
449  mZoneInfo = basic::ZoneInfoBroker((const basic::ZoneInfo*) zoneInfo);
450  mYearTiny = LocalDate::kInvalidYearTiny;
451  mIsFilled = false;
452  mNumTransitions = 0;
453  }
454 
456  const basic::Transition* getTransition(acetime_t epochSeconds) const {
457  LocalDate ld = LocalDate::forEpochSeconds(epochSeconds);
458  bool success = init(ld);
459  return (success) ? findMatch(epochSeconds) : nullptr;
460  }
461 
490  bool init(const LocalDate& ld) const {
491  int8_t yearTiny = ld.yearTiny();
492  if (ld.month() == 1 && ld.day() == 1) {
493  yearTiny--;
494  }
495  if (isFilled(yearTiny)) {
496  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
497  logging::printf("init(): %d (using cached %d)\n",
498  ld.yearTiny(), yearTiny);
499  }
500  return true;
501  } else {
502  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
503  logging::printf("init(): %d (new year %d)\n",
504  ld.yearTiny(), yearTiny);
505  }
506  }
507 
508  mYearTiny = yearTiny;
509  mNumTransitions = 0; // clear cache
510 
511  if (yearTiny + LocalDate::kEpochYear < mZoneInfo.startYear() - 1
512  || mZoneInfo.untilYear() < yearTiny + LocalDate::kEpochYear) {
513  return false;
514  }
515 
516  basic::ZoneEraBroker priorEra = addTransitionPriorToYear(yearTiny);
517  basic::ZoneEraBroker currentEra = addTransitionsForYear(
518  yearTiny, priorEra);
519  addTransitionAfterYear(yearTiny, currentEra);
520  calcTransitions();
521  calcAbbreviations();
522 
523  mIsFilled = true;
524 
525  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
526  log();
527  }
528 
529  return true;
530  }
531 
533  bool isFilled(int8_t yearTiny) const {
534  return mIsFilled && (yearTiny == mYearTiny);
535  }
536 
543  basic::ZoneEraBroker addTransitionPriorToYear(int8_t yearTiny) const {
544  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
545  logging::printf("addTransitionPriorToYear(): %d\n", yearTiny);
546  }
547 
548  const basic::ZoneEraBroker era = findZoneEra(mZoneInfo, yearTiny - 1);
549 
550  // If the prior ZoneEra has a ZonePolicy), then find the latest rule
551  // within the ZoneEra. Otherwise, add a Transition using a rule==nullptr.
552  basic::ZoneRuleBroker latest = findLatestPriorRule(
553  era.zonePolicy(), yearTiny);
554  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
555  logging::printf("addTransitionsPriorToYear(): adding latest prior ");
556  if (latest.isNull()) {
557  logging::printf("ZR(null)\n");
558  } else {
559  logging::printf("ZR[%d,%d]\n",
560  latest.fromYearTiny(), latest.toYearTiny());
561  }
562  }
563  addTransition(yearTiny - 1, 0 /*month*/, era, latest);
564 
565  return era;
566  }
567 
573  static basic::ZoneRuleBroker findLatestPriorRule(
574  basic::ZonePolicyBroker zonePolicy, int8_t yearTiny) {
575  basic::ZoneRuleBroker latest;
576  if (zonePolicy.isNull()) return latest;
577 
578  uint8_t numRules = zonePolicy.numRules();
579  for (uint8_t i = 0; i < numRules; i++) {
580  const basic::ZoneRuleBroker rule = zonePolicy.rule(i);
581  // Check if rule is effective prior to the given year
582  if (rule.fromYearTiny() < yearTiny) {
583  if ((latest.isNull()) ||
584  compareRulesBeforeYear(yearTiny, rule, latest) > 0) {
585  latest = rule;
586  }
587  }
588  }
589 
590  return latest;
591  }
592 
594  static int8_t compareRulesBeforeYear(int8_t yearTiny,
595  const basic::ZoneRuleBroker a, const basic::ZoneRuleBroker b) {
596  basic::YearMonth x = {priorYearOfRule(yearTiny, a), a.inMonth()};
597  basic::YearMonth y = {priorYearOfRule(yearTiny, b), b.inMonth()};
598  return basic::compareYearMonth(x, y);
599  }
600 
609  static int8_t priorYearOfRule(int8_t yearTiny,
610  const basic::ZoneRuleBroker rule) {
611  if (rule.toYearTiny() < yearTiny) {
612  return rule.toYearTiny();
613  }
614  return yearTiny - 1;
615  }
616 
621  basic::ZoneEraBroker addTransitionsForYear(
622  int8_t yearTiny, basic::ZoneEraBroker priorEra) const {
623  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
624  logging::printf("addTransitionsForYear(): %d\n", yearTiny);
625  }
626 
627  const basic::ZoneEraBroker era = findZoneEra(mZoneInfo, yearTiny);
628 
629  // If the ZonePolicy has no rules, then add a Transition which takes
630  // effect at the start time of the current year.
631  const basic::ZonePolicyBroker zonePolicy = era.zonePolicy();
632  if (zonePolicy.isNull()) {
633  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
634  logging::printf("addTransitionsForYear(): adding ZE.untilY=%d\n",
635  era.untilYearTiny());
636  }
637  addTransition(yearTiny, 0 /*month*/, era, basic::ZoneRuleBroker());
638  return era;
639  }
640 
641  if (era.zoneEra() != priorEra.zoneEra()) {
642  // The ZoneEra has changed, so we need to find the Rule in effect at
643  // the start of the current year of the current ZoneEra. This may be a
644  // rule far in the past, but shift the rule forward to {year, 1, 1}.
645  basic::ZoneRuleBroker latestPrior = findLatestPriorRule(
646  era.zonePolicy(), yearTiny);
647  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
648  logging::printf(
649  "addTransitionsForYear(): adding latest prior ");
650  if (latestPrior.isNull()) {
651  logging::printf("ZR(null)\n");
652  } else {
653  logging::printf("ZR[%d,%d]\n",
654  latestPrior.fromYearTiny(), latestPrior.toYearTiny());
655  }
656  }
657  addTransition(yearTiny, 1 /*month*/, era, latestPrior);
658  }
659 
660  // Find all directly matching transitions (i.e. the [from, to] overlap
661  // with the current year) and add them to mTransitions, in sorted order
662  // according to the ZoneRule::inMonth field.
663  uint8_t numRules = zonePolicy.numRules();
664  for (uint8_t i = 0; i < numRules; i++) {
665  const basic::ZoneRuleBroker rule = zonePolicy.rule(i);
666  if ((rule.fromYearTiny() <= yearTiny) &&
667  (yearTiny <= rule.toYearTiny())) {
668  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
669  logging::printf(
670  "addTransitionsForYear(): adding rule ");
671  if (rule.isNull()) {
672  logging::printf("ZR(null)\n");
673  } else {
674  logging::printf("ZR[%d,%d]\n",
675  rule.fromYearTiny(), rule.toYearTiny());
676  }
677  }
678  addTransition(yearTiny, 0 /*month*/, era, rule);
679  }
680  }
681 
682  return era;
683  }
684 
686  void addTransitionAfterYear(int8_t yearTiny,
687  basic::ZoneEraBroker currentEra) const {
688  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
689  logging::printf("addTransitionAfterYear(): %d\n", yearTiny);
690  }
691 
692  const basic::ZoneEraBroker eraAfter = findZoneEra(
693  mZoneInfo, yearTiny + 1);
694 
695  // If the current era is the same as the following year, then we'll just
696  // assume that the latest ZoneRule carries over to Jan 1st of the next
697  // year. tzcompiler.py guarantees no ZoneRule occurs on Jan 1st.
698  if (currentEra.zoneEra() == eraAfter.zoneEra()) {
699  return;
700  }
701 
702  // If the ZoneEra did change, find the latest transition prior to
703  // {yearTiny + 1, 1, 1}, then shift that Transition to Jan 1st of the
704  // following year.
705  basic::ZoneRuleBroker latest = findLatestPriorRule(
706  eraAfter.zonePolicy(), yearTiny + 1);
707  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
708  logging::printf(
709  "addTransitionsAfterYear(): adding latest prior ");
710  if (latest.isNull()) {
711  logging::printf("ZR(null)\n");
712  } else {
713  logging::printf("ZR[%d,%d]\n",
714  latest.fromYearTiny(), latest.toYearTiny());
715  }
716  }
717  addTransition(yearTiny + 1, 1 /*month*/, eraAfter, latest);
718  }
719 
743  void addTransition(int8_t yearTiny, uint8_t month, basic::ZoneEraBroker era,
744  basic::ZoneRuleBroker rule) const {
745 
746  // If a zone needs more transitions than kMaxCacheEntries, the check below
747  // will cause the DST transition information to be inaccurate, and it is
748  // highly likely that this situation would be caught in the
749  // 'tests/validation' unit tests. Since these unit tests pass, I feel
750  // confident that those zones which need more than kMaxCacheEntries are
751  // already filtered out by tzcompiler.py.
752  //
753  // Ideally, the tzcompiler.py script would explicitly remove those zones
754  // which need more than kMaxCacheEntries Transitions. But this would
755  // require a Python version of the BasicZoneProcessor, and unfortunately,
756  // zone_specifier.py implements only the ExtendedZoneProcessor algorithm
757  // An early version of zone_specifier.py may have implemented something
758  // close to BasicZoneProcessor, and it may be available in the git
759  // history. But it seems like too much work right now to try to dig that
760  // out, just to implement the explicit check for kMaxCacheEntries. It
761  // would mean maintaining another version of zone_specifier.py.
762  if (mNumTransitions >= kMaxCacheEntries) return;
763 
764  // insert new element at the end of the list
765  mTransitions[mNumTransitions] = createTransition(
766  yearTiny, month, era, rule);
767  mNumTransitions++;
768 
769  // perform an insertion sort based on ZoneRule.inMonth()
770  for (uint8_t i = mNumTransitions - 1; i > 0; i--) {
771  basic::Transition& left = mTransitions[i - 1];
772  basic::Transition& right = mTransitions[i];
773  // assume only 1 rule per month
774  if (basic::compareYearMonth(
775  {left.yearTiny, left.month}, {right.yearTiny, right.month}) > 0) {
776  basic::Transition tmp = left;
777  left = right;
778  right = tmp;
779  }
780  }
781  }
782 
788  static basic::Transition createTransition(int8_t yearTiny, uint8_t month,
790  int16_t deltaMinutes;
791  char letter;
792  uint8_t mon;
793  if (rule.isNull()) {
794  mon = 1; // RULES is either '-' or 'hh:mm' so takes effect in Jan
795  deltaMinutes = era.deltaMinutes();
796  letter = '\0';
797  } else {
798  mon = rule.inMonth();
799  deltaMinutes = rule.deltaMinutes();
800  letter = rule.letter();
801  }
802  // Clobber the month if specified.
803  if (month != 0) {
804  mon = month;
805  }
806  int16_t offsetMinutes = era.offsetMinutes() + deltaMinutes;
807 
808  return {
809  era,
810  rule,
811  0 /*epochSeconds*/,
813  deltaMinutes,
814  yearTiny,
815  mon,
816  {letter} /*abbrev*/
817  };
818  }
819 
825  static basic::ZoneEraBroker findZoneEra(
826  basic::ZoneInfoBroker info, int8_t yearTiny) {
827  for (uint8_t i = 0; i < info.numEras(); i++) {
828  const basic::ZoneEraBroker era = info.era(i);
829  if (yearTiny < era.untilYearTiny()) return era;
830  }
831  // Return the last ZoneEra if we run off the end.
832  return info.era(info.numEras() - 1);
833  }
834 
842  void calcTransitions() const {
843  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
844  logging::printf("calcTransitions():\n");
845  }
846 
847  // Set the initial startEpochSeconds to be -Infinity
848  basic::Transition* prevTransition = &mTransitions[0];
849  prevTransition->startEpochSeconds = kMinEpochSeconds;
850 
851  for (uint8_t i = 1; i < mNumTransitions; i++) {
852  basic::Transition& transition = mTransitions[i];
853  const int16_t year = transition.yearTiny + LocalDate::kEpochYear;
854 
855  if (transition.rule.isNull()) {
856  // If the transition is simple (has no named rule), then the
857  // ZoneEra applies for the entire year (since BasicZoneProcessor
858  // supports only whole year in the UNTIL field). The whole year UNTIL
859  // field has an implied 'w' suffix on 00:00, we don't need to call
860  // calcRuleOffsetMinutes() with a 'w', we can just use the previous
861  // transition's offset to calculate the startDateTime of this
862  // transition.
863  //
864  // Also, when transition.rule == nullptr, the mNumTransitions should
865  // be 1, since only a single transition is added by
866  // addTransitionsForYear().
867  const int16_t prevOffsetMinutes = prevTransition->offsetMinutes;
869  year, 1, 1, 0, 0, 0,
870  TimeOffset::forMinutes(prevOffsetMinutes));
871  transition.startEpochSeconds = startDateTime.toEpochSeconds();
872  } else {
873  // In this case, the transition points to a named ZonePolicy, which
874  // means that there could be multiple ZoneRules associated with the
875  // given year. For each transition, determine the startEpochSeconds,
876  // and the effective offset code.
877 
878  // Determine the start date of the rule.
879  const basic::MonthDay monthDay = calcStartDayOfMonth(
880  year, transition.month, transition.rule.onDayOfWeek(),
881  transition.rule.onDayOfMonth());
882 
883  // Determine the offset of the 'atTimeSuffix'. The 'w' suffix
884  // requires the offset of the previous transition.
885  const int16_t prevOffsetMinutes = calcRuleOffsetMinutes(
886  prevTransition->offsetMinutes,
887  transition.era.offsetMinutes(),
888  transition.rule.atTimeSuffix());
889 
890  // startDateTime
891  const uint16_t minutes = transition.rule.atTimeMinutes();
892  const uint8_t atHour = minutes / 60;
893  const uint8_t atMinute = minutes % 60;
895  year, monthDay.month, monthDay.day,
896  atHour, atMinute, 0 /*second*/,
897  TimeOffset::forMinutes(prevOffsetMinutes));
898  transition.startEpochSeconds = startDateTime.toEpochSeconds();
899  }
900 
901  prevTransition = &transition;
902  }
903  }
904 
911  static int16_t calcRuleOffsetMinutes(int16_t prevEffectiveOffsetMinutes,
912  int16_t currentBaseOffsetMinutes, uint8_t atSuffix) {
913  if (atSuffix == basic::ZoneContext::kSuffixW) {
914  return prevEffectiveOffsetMinutes;
915  } else if (atSuffix == basic::ZoneContext::kSuffixS) {
916  return currentBaseOffsetMinutes;
917  } else { // 'u', 'g' or 'z'
918  return 0;
919  }
920  }
921 
923  void calcAbbreviations() const {
924  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
925  logging::printf("calcAbbreviations():\n");
926  }
927 
928  for (uint8_t i = 0; i < mNumTransitions; i++) {
929  calcAbbreviation(&mTransitions[i]);
930  }
931  }
932 
934  static void calcAbbreviation(basic::Transition* transition) {
935  createAbbreviation(
936  transition->abbrev,
938  transition->era.format(),
939  transition->deltaMinutes,
940  transition->abbrev[0]);
941  }
942 
991  static void createAbbreviation(char* dest, uint8_t destSize,
992  const char* format, int16_t deltaMinutes, char letter) {
993  // Check if FORMAT contains a '%'.
994  if (strchr(format, '%') != nullptr) {
995  // Check if RULES column empty, therefore no 'letter'
996  if (letter == '\0') {
997  strncpy(dest, format, destSize - 1);
998  dest[destSize - 1] = '\0';
999  } else {
1000  copyAndReplace(dest, destSize, format, '%', letter);
1001  }
1002  } else {
1003  // Check if FORMAT contains a '/'.
1004  const char* slashPos = strchr(format, '/');
1005  if (slashPos != nullptr) {
1006  if (deltaMinutes == 0) {
1007  uint8_t headLength = (slashPos - format);
1008  if (headLength >= destSize) headLength = destSize - 1;
1009  memcpy(dest, format, headLength);
1010  dest[headLength] = '\0';
1011  } else {
1012  uint8_t tailLength = strlen(slashPos+1);
1013  if (tailLength >= destSize) tailLength = destSize - 1;
1014  memcpy(dest, slashPos+1, tailLength);
1015  dest[tailLength] = '\0';
1016  }
1017  } else {
1018  // Just copy the FORMAT disregarding the deltaMinutes and letter.
1019  strncpy(dest, format, destSize - 1);
1020  dest[destSize - 1] = '\0';
1021  }
1022  }
1023  }
1024 
1030  static void copyAndReplace(char* dst, uint8_t dstSize, const char* src,
1031  char oldChar, char newChar) {
1032  while (*src != '\0' && dstSize > 0) {
1033  if (*src == oldChar) {
1034  if (newChar == '-') {
1035  src++;
1036  } else {
1037  *dst = newChar;
1038  dst++;
1039  src++;
1040  dstSize--;
1041  }
1042  } else {
1043  *dst++ = *src++;
1044  dstSize--;
1045  }
1046  }
1047 
1048  if (dstSize == 0) {
1049  --dst;
1050  }
1051  *dst = '\0';
1052  }
1053 
1055  const basic::Transition* findMatch(acetime_t epochSeconds) const {
1056  const basic::Transition* closestMatch = nullptr;
1057  for (uint8_t i = 0; i < mNumTransitions; i++) {
1058  const basic::Transition* m = &mTransitions[i];
1059  if (closestMatch == nullptr || m->startEpochSeconds <= epochSeconds) {
1060  closestMatch = m;
1061  }
1062  }
1063  return closestMatch;
1064  }
1065 
1066  basic::ZoneInfoBroker mZoneInfo;
1067 
1068  mutable int8_t mYearTiny = LocalDate::kInvalidYearTiny;
1069  mutable bool mIsFilled = false;
1070  mutable uint8_t mNumTransitions = 0;
1071  mutable basic::Transition mTransitions[kMaxCacheEntries];
1072 };
1073 
1074 }
1075 
1076 #endif
Base interface for ZoneProcessor classes.
Definition: ZoneProcessor.h:45
ZoneEraBroker era
The ZoneEra that matched the given year.
const char * getAbbrev(acetime_t epochSeconds) const override
Return the time zone abbreviation at epochSeconds.
int8_t yearTiny() const
Return the single-byte year offset from year 2000.
Definition: LocalDate.h:241
void log() const
Used only for debugging.
A cache of ZoneProcessors that provides a ZoneProcessor to the TimeZone upon request.
static const uint8_t kAbbrevSize
Longest abbreviation currently seems to be 5 characters (https://www.timeanddate.com/time/zones/) but...
char abbrev[kAbbrevSize]
The calculated effective time zone abbreviation, e.g.
static OffsetDateTime forLocalDateTimeAndOffset(const LocalDateTime &localDateTime, TimeOffset timeOffset)
Factory method from LocalDateTime and TimeOffset.
BasicZoneProcessor(const basic::ZoneInfo *zoneInfo=nullptr)
Constructor.
Representation of a given time zone, implemented as an array of ZoneEra records.
Definition: ZoneInfo.h:100
static const uint8_t kSuffixW
Represents &#39;w&#39; or wall time.
Definition: ZoneContext.h:13
uint8_t month
Month of the transition.
The result of calcStartDayOfMonth().
virtual const void * getZoneInfo() const =0
Return the opaque zoneInfo.
uint32_t getZoneId() const override
Return the unique stable zoneId.
static OffsetDateTime forComponents(int16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second, TimeOffset timeOffset)
Factory method using separated date, time, and UTC offset fields.
acetime_t startEpochSeconds
The calculated transition time of the given rule.
Data broker for accessing ZonePolicy.
Definition: Brokers.h:156
uint8_t day() const
Return the day of the month.
Definition: LocalDate.h:257
int8_t yearTiny
Year of the Transition.
int16_t deltaMinutes
The deltaMinutes from "standard time" at the start of transition.
static OffsetDateTime forEpochSeconds(acetime_t epochSeconds, TimeOffset timeOffset)
Factory method.
ZoneRuleBroker rule
The Zone transition rule that matched for the the given year.
acetime_t toEpochSeconds() const
Return seconds since AceTime epoch 2000-01-01 00:00:00Z, after assuming that the date and time compon...
const LocalDate & localDate() const
Return the LocalDate.
TimeOffset getDeltaOffset(acetime_t epochSeconds) const override
Return the DST delta offset at epochSeconds.
Holds year and month.
const void * getZoneInfo() const override
Return the underlying ZoneInfo.
void log() const
Used only for debugging.
An implementation of ZoneProcessor that supports a subset of the zones containing in the TZ Database...
static LocalDate forEpochSeconds(acetime_t epochSeconds)
Factory method using the number of seconds since AceTime epoch of 2000-01-01.
Definition: LocalDate.h:157
The classes provide a thin layer of indirection for accessing the zoneinfo files stored in the zonedb...
The date (year, month, day), time (hour, minute, second) and offset from UTC (timeOffset).
Data structure that defines the start of a specific UTC offset as described by the matching ZoneEra a...
static const int8_t kInvalidYearTiny
Sentinel yearTiny which indicates an error condition or sometimes a year that &#39;does not exist&#39;...
Definition: LocalDate.h:45
OffsetDateTime getOffsetDateTime(const LocalDateTime &ldt) const override
acetime_t toEpochSeconds() const
Return seconds since AceTime epoch (2000-01-01 00:00:00Z), taking into account the offset zone...
static const int16_t kErrorMinutes
Sentinel value that represents an error.
Definition: TimeOffset.h:59
A thin wrapper that represents a time offset from a reference point, usually 00:00 at UTC...
Definition: TimeOffset.h:56
static LocalDate forComponents(int16_t year, uint8_t month, uint8_t day)
Factory method using separated year, month and day fields.
Definition: LocalDate.h:106
The date (year, month, day) representing the date without regards to time zone.
Definition: LocalDate.h:36
static TimeOffset forMinutes(int16_t minutes)
Create TimeOffset from minutes from 00:00.
Definition: TimeOffset.h:83
static const uint8_t kSuffixS
Represents &#39;s&#39; or standard time.
Definition: ZoneContext.h:16
static const int16_t kEpochYear
Base year of epoch.
Definition: LocalDate.h:39
static uint8_t daysInMonth(int16_t year, uint8_t month)
Return the number of days in the current month.
Definition: LocalDate.h:222
uint8_t month() const
Return the month with January=1, December=12.
Definition: LocalDate.h:251
static basic::MonthDay calcStartDayOfMonth(int16_t year, uint8_t month, uint8_t onDayOfWeek, int8_t onDayOfMonth)
Calculate the actual (month, day) of the expresssion (onDayOfWeek >= onDayOfMonth) or (onDayOfWeek <=...
Data broker for accessing ZoneEra.
Definition: Brokers.h:217
Data broker for accessing ZoneRule.
Definition: Brokers.h:66
int16_t offsetMinutes
The total effective UTC offset minutes at the start of transition, including DST offset.
static OffsetDateTime forError()
Factory method that returns an instance whose isError() is true.
Data broker for accessing ZoneInfo.
Definition: Brokers.h:312
Class that holds the date-time as the components (year, month, day, hour, minute, second) without reg...
Definition: LocalDateTime.h:27
TimeOffset getUtcOffset(acetime_t epochSeconds) const override
Return the total UTC offset at epochSeconds, including DST offset.