AceTime  1.2
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.isNull()) {
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 inline int8_t compareYearMonth(int8_t aYear, uint8_t aMonth,
145  int8_t bYear, uint8_t bMonth) {
146  if (aYear < bYear) return -1;
147  if (aYear > bYear) return 1;
148  if (aMonth < bMonth) return -1;
149  if (aMonth > bMonth) return 1;
150  return 0;
151 }
152 
153 } // namespace basic
154 
205  public:
210  explicit BasicZoneProcessor(const basic::ZoneInfo* zoneInfo = nullptr):
212  mZoneInfo(zoneInfo) {}
213 
215  const void* getZoneInfo() const override {
216  return mZoneInfo.zoneInfo();
217  }
218 
219  uint32_t getZoneId() const override { return mZoneInfo.zoneId(); }
220 
221  TimeOffset getUtcOffset(acetime_t epochSeconds) const override {
222  const basic::Transition* transition = getTransition(epochSeconds);
223  int16_t minutes = (transition)
225  return TimeOffset::forMinutes(minutes);
226  }
227 
228  TimeOffset getDeltaOffset(acetime_t epochSeconds) const override {
229  const basic::Transition* transition = getTransition(epochSeconds);
230  int16_t minutes = (transition)
231  ? transition->deltaMinutes : TimeOffset::kErrorMinutes;
232  return TimeOffset::forMinutes(minutes);
233  }
234 
235  const char* getAbbrev(acetime_t epochSeconds) const override {
236  const basic::Transition* transition = getTransition(epochSeconds);
237  return (transition) ? transition->abbrev : "";
238  }
239 
269  OffsetDateTime getOffsetDateTime(const LocalDateTime& ldt) const override {
270  // Only a single local variable of OffsetDateTime used, to allow Return
271  // Value Optimization (and save 20 bytes of flash for WorldClock).
272  OffsetDateTime odt;
273  bool success = init(ldt.localDate());
274  if (success) {
275  // 0) Use the UTC epochSeconds to get intial guess of offset.
276  acetime_t epochSeconds0 = ldt.toEpochSeconds();
277  auto offset0 = getUtcOffset(epochSeconds0);
278 
279  // 1) Use offset0 to get the next epochSeconds and offset.
280  odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset0);
281  acetime_t epochSeconds1 = odt.toEpochSeconds();
282  auto offset1 = getUtcOffset(epochSeconds1);
283 
284  // 2) Use offset1 to get the next epochSeconds and offset.
285  odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset1);
286  acetime_t epochSeconds2 = odt.toEpochSeconds();
287  auto offset2 = getUtcOffset(epochSeconds2);
288 
289  // If offset1 and offset2 are equal, then we have an equilibrium
290  // and odt(1) must equal odt(2), so we can just return the last odt.
291  if (offset1 == offset2) {
292  // pass
293  } else {
294  // Pick the later epochSeconds and offset
295  acetime_t epochSeconds;
296  TimeOffset offset;
297  if (epochSeconds1 > epochSeconds2) {
298  epochSeconds = epochSeconds1;
299  offset = offset1;
300  } else {
301  epochSeconds = epochSeconds2;
302  offset = offset2;
303  }
304  odt = OffsetDateTime::forEpochSeconds(epochSeconds, offset);
305  }
306  } else {
307  odt = OffsetDateTime::forError();
308  }
309 
310  return odt;
311  }
312 
313  void printTo(Print& printer) const override;
314 
315  void printShortTo(Print& printer) const override;
316 
318  void log() const {
319  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
320  if (!mIsFilled) {
321  logging::printf("*not initialized*\n");
322  return;
323  }
324  logging::printf("mYearTiny: %d\n", mYearTiny);
325  logging::printf("mNumTransitions: %d\n", mNumTransitions);
326  for (int i = 0; i < mNumTransitions; i++) {
327  logging::printf("mT[%d]=", i);
328  mTransitions[i].log();
329  }
330  }
331  }
332 
352  static basic::MonthDay calcStartDayOfMonth(int16_t year, uint8_t month,
353  uint8_t onDayOfWeek, int8_t onDayOfMonth) {
354  if (onDayOfWeek == 0) return {month, (uint8_t) onDayOfMonth};
355 
356  if (onDayOfMonth >= 0) {
357  // Convert "last{Xxx}" to "last{Xxx}>={daysInMonth-6}".
358  uint8_t daysInMonth = LocalDate::daysInMonth(year, month);
359  if (onDayOfMonth == 0) {
360  onDayOfMonth = daysInMonth - 6;
361  }
362 
363  auto limitDate = LocalDate::forComponents(year, month, onDayOfMonth);
364  uint8_t dayOfWeekShift = (onDayOfWeek - limitDate.dayOfWeek() + 7) % 7;
365  uint8_t day = (uint8_t) (onDayOfMonth + dayOfWeekShift);
366  if (day > daysInMonth) {
367  // TODO: Support shifting from Dec to Jan of following year.
368  day -= daysInMonth;
369  month++;
370  }
371  return {month, day};
372  } else {
373  onDayOfMonth = -onDayOfMonth;
374  auto limitDate = LocalDate::forComponents(year, month, onDayOfMonth);
375  int8_t dayOfWeekShift = (limitDate.dayOfWeek() - onDayOfWeek + 7) % 7;
376  int8_t day = onDayOfMonth - dayOfWeekShift;
377  if (day < 1) {
378  // TODO: Support shifting from Jan to Dec of the previous year.
379  month--;
380  uint8_t daysInPrevMonth = LocalDate::daysInMonth(year, month);
381  day += daysInPrevMonth;
382  }
383  return {month, (uint8_t) day};
384  }
385  }
386 
387  private:
388  friend class ::BasicZoneProcessorTest_priorYearOfRule;
389  friend class ::BasicZoneProcessorTest_compareRulesBeforeYear;
390  friend class ::BasicZoneProcessorTest_findLatestPriorRule;
391  friend class ::BasicZoneProcessorTest_findZoneEra;
392  friend class ::BasicZoneProcessorTest_init_primitives;
393  friend class ::BasicZoneProcessorTest_init;
394  friend class ::BasicZoneProcessorTest_setZoneInfo;
395  friend class ::BasicZoneProcessorTest_createAbbreviation;
396  friend class ::BasicZoneProcessorTest_calcStartDayOfMonth;
397  friend class ::BasicZoneProcessorTest_calcRuleOffsetMinutes;
398 
399  template<uint8_t SIZE, uint8_t TYPE, typename ZS, typename ZI, typename ZIB>
400  friend class ZoneProcessorCacheImpl; // setZoneInfo()
401 
412  static const uint8_t kMaxCacheEntries = 5;
413 
419  static const acetime_t kMinEpochSeconds = INT32_MIN + 1;
420 
421  // Disable copy constructor and assignment operator.
422  BasicZoneProcessor(const BasicZoneProcessor&) = delete;
423  BasicZoneProcessor& operator=(const BasicZoneProcessor&) = delete;
424 
425  bool equals(const ZoneProcessor& other) const override {
426  const auto& that = (const BasicZoneProcessor&) other;
427  return getZoneInfo() == that.getZoneInfo();
428  }
429 
441  void setZoneInfo(const void* zoneInfo) override {
442  if (mZoneInfo.zoneInfo() == zoneInfo) return;
443 
444  mZoneInfo = basic::ZoneInfoBroker((const basic::ZoneInfo*) zoneInfo);
445  mYearTiny = LocalDate::kInvalidYearTiny;
446  mIsFilled = false;
447  mNumTransitions = 0;
448  }
449 
451  const basic::Transition* getTransition(acetime_t epochSeconds) const {
452  LocalDate ld = LocalDate::forEpochSeconds(epochSeconds);
453  bool success = init(ld);
454  return (success) ? findMatch(epochSeconds) : nullptr;
455  }
456 
485  bool init(const LocalDate& ld) const {
486  int8_t yearTiny = ld.yearTiny();
487  if (ld.month() == 1 && ld.day() == 1) {
488  yearTiny--;
489  }
490  if (isFilled(yearTiny)) {
491  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
492  logging::printf("init(): %d (using cached %d)\n",
493  ld.yearTiny(), yearTiny);
494  }
495  return true;
496  } else {
497  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
498  logging::printf("init(): %d (new year %d)\n",
499  ld.yearTiny(), yearTiny);
500  }
501  }
502 
503  mYearTiny = yearTiny;
504  mNumTransitions = 0; // clear cache
505 
506  if (yearTiny + LocalDate::kEpochYear < mZoneInfo.startYear() - 1
507  || mZoneInfo.untilYear() < yearTiny + LocalDate::kEpochYear) {
508  return false;
509  }
510 
511  basic::ZoneEraBroker priorEra = addTransitionPriorToYear(yearTiny);
512  basic::ZoneEraBroker currentEra = addTransitionsForYear(
513  yearTiny, priorEra);
514  addTransitionAfterYear(yearTiny, currentEra);
515  calcTransitions();
516  calcAbbreviations();
517 
518  mIsFilled = true;
519 
520  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
521  log();
522  }
523 
524  return true;
525  }
526 
528  bool isFilled(int8_t yearTiny) const {
529  return mIsFilled && (yearTiny == mYearTiny);
530  }
531 
538  basic::ZoneEraBroker addTransitionPriorToYear(int8_t yearTiny) const {
539  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
540  logging::printf("addTransitionPriorToYear(): %d\n", yearTiny);
541  }
542 
543  const basic::ZoneEraBroker era = findZoneEra(mZoneInfo, yearTiny - 1);
544 
545  // If the prior ZoneEra has a ZonePolicy), then find the latest rule
546  // within the ZoneEra. Otherwise, add a Transition using a rule==nullptr.
547  basic::ZoneRuleBroker latest = findLatestPriorRule(
548  era.zonePolicy(), yearTiny);
549  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
550  logging::printf("addTransitionsPriorToYear(): adding latest prior ");
551  if (latest.isNull()) {
552  logging::printf("ZR(null)\n");
553  } else {
554  logging::printf("ZR[%d,%d]\n",
555  latest.fromYearTiny(), latest.toYearTiny());
556  }
557  }
558  addTransition(yearTiny - 1, 0 /*month*/, era, latest);
559 
560  return era;
561  }
562 
568  static basic::ZoneRuleBroker findLatestPriorRule(
569  basic::ZonePolicyBroker zonePolicy, int8_t yearTiny) {
570  basic::ZoneRuleBroker latest;
571  if (zonePolicy.isNull()) return latest;
572 
573  uint8_t numRules = zonePolicy.numRules();
574  for (uint8_t i = 0; i < numRules; i++) {
575  const basic::ZoneRuleBroker rule = zonePolicy.rule(i);
576  // Check if rule is effective prior to the given year
577  if (rule.fromYearTiny() < yearTiny) {
578  if ((latest.isNull()) ||
579  compareRulesBeforeYear(yearTiny, rule, latest) > 0) {
580  latest = rule;
581  }
582  }
583  }
584 
585  return latest;
586  }
587 
589  static int8_t compareRulesBeforeYear(int8_t yearTiny,
590  const basic::ZoneRuleBroker a, const basic::ZoneRuleBroker b) {
591  return basic::compareYearMonth(
592  priorYearOfRule(yearTiny, a), a.inMonth(),
593  priorYearOfRule(yearTiny, b), b.inMonth());
594  }
595 
604  static int8_t priorYearOfRule(int8_t yearTiny,
605  const basic::ZoneRuleBroker rule) {
606  if (rule.toYearTiny() < yearTiny) {
607  return rule.toYearTiny();
608  }
609  return yearTiny - 1;
610  }
611 
616  basic::ZoneEraBroker addTransitionsForYear(
617  int8_t yearTiny, basic::ZoneEraBroker priorEra) const {
618  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
619  logging::printf("addTransitionsForYear(): %d\n", yearTiny);
620  }
621 
622  const basic::ZoneEraBroker era = findZoneEra(mZoneInfo, yearTiny);
623 
624  // If the ZonePolicy has no rules, then add a Transition which takes
625  // effect at the start time of the current year.
626  const basic::ZonePolicyBroker zonePolicy = era.zonePolicy();
627  if (zonePolicy.isNull()) {
628  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
629  logging::printf("addTransitionsForYear(): adding ZE.untilY=%d\n",
630  era.untilYearTiny());
631  }
632  addTransition(yearTiny, 0 /*month*/, era, basic::ZoneRuleBroker());
633  return era;
634  }
635 
636  if (era.zoneEra() != priorEra.zoneEra()) {
637  // The ZoneEra has changed, so we need to find the Rule in effect at
638  // the start of the current year of the current ZoneEra. This may be a
639  // rule far in the past, but shift the rule forward to {year, 1, 1}.
640  basic::ZoneRuleBroker latestPrior = findLatestPriorRule(
641  era.zonePolicy(), yearTiny);
642  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
643  logging::printf(
644  "addTransitionsForYear(): adding latest prior ");
645  if (latestPrior.isNull()) {
646  logging::printf("ZR(null)\n");
647  } else {
648  logging::printf("ZR[%d,%d]\n",
649  latestPrior.fromYearTiny(), latestPrior.toYearTiny());
650  }
651  }
652  addTransition(yearTiny, 1 /*month*/, era, latestPrior);
653  }
654 
655  // Find all directly matching transitions (i.e. the [from, to] overlap
656  // with the current year) and add them to mTransitions, in sorted order
657  // according to the ZoneRule::inMonth field.
658  uint8_t numRules = zonePolicy.numRules();
659  for (uint8_t i = 0; i < numRules; i++) {
660  const basic::ZoneRuleBroker rule = zonePolicy.rule(i);
661  if ((rule.fromYearTiny() <= yearTiny) &&
662  (yearTiny <= rule.toYearTiny())) {
663  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
664  logging::printf(
665  "addTransitionsForYear(): adding rule ");
666  if (rule.isNull()) {
667  logging::printf("ZR(null)\n");
668  } else {
669  logging::printf("ZR[%d,%d]\n",
670  rule.fromYearTiny(), rule.toYearTiny());
671  }
672  }
673  addTransition(yearTiny, 0 /*month*/, era, rule);
674  }
675  }
676 
677  return era;
678  }
679 
681  void addTransitionAfterYear(int8_t yearTiny,
682  basic::ZoneEraBroker currentEra) const {
683  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
684  logging::printf("addTransitionAfterYear(): %d\n", yearTiny);
685  }
686 
687  const basic::ZoneEraBroker eraAfter = findZoneEra(
688  mZoneInfo, yearTiny + 1);
689 
690  // If the current era is the same as the following year, then we'll just
691  // assume that the latest ZoneRule carries over to Jan 1st of the next
692  // year. tzcompiler.py guarantees no ZoneRule occurs on Jan 1st.
693  if (currentEra.zoneEra() == eraAfter.zoneEra()) {
694  return;
695  }
696 
697  // If the ZoneEra did change, find the latest transition prior to
698  // {yearTiny + 1, 1, 1}, then shift that Transition to Jan 1st of the
699  // following year.
700  basic::ZoneRuleBroker latest = findLatestPriorRule(
701  eraAfter.zonePolicy(), yearTiny + 1);
702  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
703  logging::printf(
704  "addTransitionsAfterYear(): adding latest prior ");
705  if (latest.isNull()) {
706  logging::printf("ZR(null)\n");
707  } else {
708  logging::printf("ZR[%d,%d]\n",
709  latest.fromYearTiny(), latest.toYearTiny());
710  }
711  }
712  addTransition(yearTiny + 1, 1 /*month*/, eraAfter, latest);
713  }
714 
738  void addTransition(int8_t yearTiny, uint8_t month, basic::ZoneEraBroker era,
739  basic::ZoneRuleBroker rule) const {
740 
741  // If a zone needs more transitions than kMaxCacheEntries, the check below
742  // will cause the DST transition information to be inaccurate, and it is
743  // highly likely that this situation would be caught in the
744  // 'tests/validation' unit tests. Since these unit tests pass, I feel
745  // confident that those zones which need more than kMaxCacheEntries are
746  // already filtered out by tzcompiler.py.
747  //
748  // Ideally, the tzcompiler.py script would explicitly remove those zones
749  // which need more than kMaxCacheEntries Transitions. But this would
750  // require a Python version of the BasicZoneProcessor, and unfortunately,
751  // zone_specifier.py implements only the ExtendedZoneProcessor algorithm
752  // An early version of zone_specifier.py may have implemented something
753  // close to BasicZoneProcessor, and it may be available in the git
754  // history. But it seems like too much work right now to try to dig that
755  // out, just to implement the explicit check for kMaxCacheEntries. It
756  // would mean maintaining another version of zone_specifier.py.
757  if (mNumTransitions >= kMaxCacheEntries) return;
758 
759  // insert new element at the end of the list
760  mTransitions[mNumTransitions] = createTransition(
761  yearTiny, month, era, rule);
762  mNumTransitions++;
763 
764  // perform an insertion sort based on ZoneRule.inMonth()
765  for (uint8_t i = mNumTransitions - 1; i > 0; i--) {
766  basic::Transition& left = mTransitions[i - 1];
767  basic::Transition& right = mTransitions[i];
768  // assume only 1 rule per month
769  if (basic::compareYearMonth(left.yearTiny, left.month,
770  right.yearTiny, right.month) > 0) {
771  basic::Transition tmp = left;
772  left = right;
773  right = tmp;
774  }
775  }
776  }
777 
783  static basic::Transition createTransition(int8_t yearTiny, uint8_t month,
784  basic::ZoneEraBroker era, basic::ZoneRuleBroker rule) {
785  int16_t deltaMinutes;
786  char letter;
787  uint8_t mon;
788  if (rule.isNull()) {
789  mon = 1; // RULES is either '-' or 'hh:mm' so takes effect in Jan
790  deltaMinutes = era.deltaMinutes();
791  letter = '\0';
792  } else {
793  mon = rule.inMonth();
794  deltaMinutes = rule.deltaMinutes();
795  letter = rule.letter();
796  }
797  // Clobber the month if specified.
798  if (month != 0) {
799  mon = month;
800  }
801  int16_t offsetMinutes = era.offsetMinutes() + deltaMinutes;
802 
803  return {
804  era,
805  rule,
806  0 /*epochSeconds*/,
807  offsetMinutes,
808  deltaMinutes,
809  yearTiny,
810  mon,
811  {letter} /*abbrev*/
812  };
813  }
814 
820  static basic::ZoneEraBroker findZoneEra(
821  basic::ZoneInfoBroker info, int8_t yearTiny) {
822  for (uint8_t i = 0; i < info.numEras(); i++) {
823  const basic::ZoneEraBroker era = info.era(i);
824  if (yearTiny < era.untilYearTiny()) return era;
825  }
826  // Return the last ZoneEra if we run off the end.
827  return info.era(info.numEras() - 1);
828  }
829 
837  void calcTransitions() const {
838  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
839  logging::printf("calcTransitions():\n");
840  }
841 
842  // Set the initial startEpochSeconds to be -Infinity
843  basic::Transition* prevTransition = &mTransitions[0];
844  prevTransition->startEpochSeconds = kMinEpochSeconds;
845 
846  for (uint8_t i = 1; i < mNumTransitions; i++) {
847  basic::Transition& transition = mTransitions[i];
848  const int16_t year = transition.yearTiny + LocalDate::kEpochYear;
849 
850  if (transition.rule.isNull()) {
851  // If the transition is simple (has no named rule), then the
852  // ZoneEra applies for the entire year (since BasicZoneProcessor
853  // supports only whole year in the UNTIL field). The whole year UNTIL
854  // field has an implied 'w' suffix on 00:00, we don't need to call
855  // calcRuleOffsetMinutes() with a 'w', we can just use the previous
856  // transition's offset to calculate the startDateTime of this
857  // transition.
858  //
859  // Also, when transition.rule == nullptr, the mNumTransitions should
860  // be 1, since only a single transition is added by
861  // addTransitionsForYear().
862  const int16_t prevOffsetMinutes = prevTransition->offsetMinutes;
863  OffsetDateTime startDateTime = OffsetDateTime::forComponents(
864  year, 1, 1, 0, 0, 0,
865  TimeOffset::forMinutes(prevOffsetMinutes));
866  transition.startEpochSeconds = startDateTime.toEpochSeconds();
867  } else {
868  // In this case, the transition points to a named ZonePolicy, which
869  // means that there could be multiple ZoneRules associated with the
870  // given year. For each transition, determine the startEpochSeconds,
871  // and the effective offset code.
872 
873  // Determine the start date of the rule.
874  const basic::MonthDay monthDay = calcStartDayOfMonth(
875  year, transition.month, transition.rule.onDayOfWeek(),
876  transition.rule.onDayOfMonth());
877 
878  // Determine the offset of the 'atTimeSuffix'. The 'w' suffix
879  // requires the offset of the previous transition.
880  const int16_t prevOffsetMinutes = calcRuleOffsetMinutes(
881  prevTransition->offsetMinutes,
882  transition.era.offsetMinutes(),
883  transition.rule.atTimeSuffix());
884 
885  // startDateTime
886  const uint16_t minutes = transition.rule.atTimeMinutes();
887  const uint8_t atHour = minutes / 60;
888  const uint8_t atMinute = minutes % 60;
889  OffsetDateTime startDateTime = OffsetDateTime::forComponents(
890  year, monthDay.month, monthDay.day,
891  atHour, atMinute, 0 /*second*/,
892  TimeOffset::forMinutes(prevOffsetMinutes));
893  transition.startEpochSeconds = startDateTime.toEpochSeconds();
894  }
895 
896  prevTransition = &transition;
897  }
898  }
899 
906  static int16_t calcRuleOffsetMinutes(int16_t prevEffectiveOffsetMinutes,
907  int16_t currentBaseOffsetMinutes, uint8_t atSuffix) {
908  if (atSuffix == basic::ZoneContext::kSuffixW) {
909  return prevEffectiveOffsetMinutes;
910  } else if (atSuffix == basic::ZoneContext::kSuffixS) {
911  return currentBaseOffsetMinutes;
912  } else { // 'u', 'g' or 'z'
913  return 0;
914  }
915  }
916 
918  void calcAbbreviations() const {
919  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
920  logging::printf("calcAbbreviations():\n");
921  }
922 
923  for (uint8_t i = 0; i < mNumTransitions; i++) {
924  calcAbbreviation(&mTransitions[i]);
925  }
926  }
927 
929  static void calcAbbreviation(basic::Transition* transition) {
930  createAbbreviation(
931  transition->abbrev,
933  transition->era.format(),
934  transition->deltaMinutes,
935  transition->abbrev[0]);
936  }
937 
986  static void createAbbreviation(char* dest, uint8_t destSize,
987  const char* format, int16_t deltaMinutes, char letter) {
988  // Check if FORMAT contains a '%'.
989  if (strchr(format, '%') != nullptr) {
990  // Check if RULES column empty, therefore no 'letter'
991  if (letter == '\0') {
992  strncpy(dest, format, destSize - 1);
993  dest[destSize - 1] = '\0';
994  } else {
995  copyAndReplace(dest, destSize, format, '%', letter);
996  }
997  } else {
998  // Check if FORMAT contains a '/'.
999  const char* slashPos = strchr(format, '/');
1000  if (slashPos != nullptr) {
1001  if (deltaMinutes == 0) {
1002  uint8_t headLength = (slashPos - format);
1003  if (headLength >= destSize) headLength = destSize - 1;
1004  memcpy(dest, format, headLength);
1005  dest[headLength] = '\0';
1006  } else {
1007  uint8_t tailLength = strlen(slashPos+1);
1008  if (tailLength >= destSize) tailLength = destSize - 1;
1009  memcpy(dest, slashPos+1, tailLength);
1010  dest[tailLength] = '\0';
1011  }
1012  } else {
1013  // Just copy the FORMAT disregarding the deltaMinutes and letter.
1014  strncpy(dest, format, destSize - 1);
1015  dest[destSize - 1] = '\0';
1016  }
1017  }
1018  }
1019 
1025  static void copyAndReplace(char* dst, uint8_t dstSize, const char* src,
1026  char oldChar, char newChar) {
1027  while (*src != '\0' && dstSize > 0) {
1028  if (*src == oldChar) {
1029  if (newChar == '-') {
1030  src++;
1031  } else {
1032  *dst = newChar;
1033  dst++;
1034  src++;
1035  dstSize--;
1036  }
1037  } else {
1038  *dst++ = *src++;
1039  dstSize--;
1040  }
1041  }
1042 
1043  if (dstSize == 0) {
1044  --dst;
1045  }
1046  *dst = '\0';
1047  }
1048 
1050  const basic::Transition* findMatch(acetime_t epochSeconds) const {
1051  const basic::Transition* closestMatch = nullptr;
1052  for (uint8_t i = 0; i < mNumTransitions; i++) {
1053  const basic::Transition* m = &mTransitions[i];
1054  if (closestMatch == nullptr || m->startEpochSeconds <= epochSeconds) {
1055  closestMatch = m;
1056  }
1057  }
1058  return closestMatch;
1059  }
1060 
1061  basic::ZoneInfoBroker mZoneInfo;
1062 
1063  mutable int8_t mYearTiny = LocalDate::kInvalidYearTiny;
1064  mutable bool mIsFilled = false;
1065  mutable uint8_t mNumTransitions = 0;
1066  mutable basic::Transition mTransitions[kMaxCacheEntries];
1067 };
1068 
1069 }
1070 
1071 #endif
ace_time::basic::Transition::kAbbrevSize
static const uint8_t kAbbrevSize
Longest abbreviation currently seems to be 5 characters (https://www.timeanddate.com/time/zones/) but...
Definition: BasicZoneProcessor.h:63
ace_time::BasicZoneProcessor::calcStartDayOfMonth
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 <=...
Definition: BasicZoneProcessor.h:352
ace_time::basic::Transition::yearTiny
int8_t yearTiny
Year of the Transition.
Definition: BasicZoneProcessor.h:97
ace_time::ZoneProcessor::kTypeBasic
static const uint8_t kTypeBasic
Indicate BasicZoneProcessor.
Definition: ZoneProcessor.h:51
ace_time::LocalDateTime
Class that holds the date-time as the components (year, month, day, hour, minute, second) without reg...
Definition: LocalDateTime.h:30
ace_time::basic::ZoneRuleBroker
Data broker for accessing ZoneRule.
Definition: Brokers.h:66
ace_time::basic::Transition::era
ZoneEraBroker era
The ZoneEra that matched the given year.
Definition: BasicZoneProcessor.h:70
ace_time::OffsetDateTime::toEpochSeconds
acetime_t toEpochSeconds() const
Return seconds since AceTime epoch (2000-01-01 00:00:00Z), taking into account the offset zone.
Definition: OffsetDateTime.h:263
ace_time::basic::ZoneEraBroker
Data broker for accessing ZoneEra.
Definition: Brokers.h:213
ace_time::basic::Transition::deltaMinutes
int16_t deltaMinutes
The deltaMinutes from "standard time" at the start of transition.
Definition: BasicZoneProcessor.h:94
ace_time::basic::MonthDay
The result of calcStartDayOfMonth().
Definition: BasicZoneProcessor.h:138
ace_time::TimeOffset
A thin wrapper that represents a time offset from a reference point, usually 00:00 at UTC,...
Definition: TimeOffset.h:56
ace_time::OffsetDateTime::forEpochSeconds
static OffsetDateTime forEpochSeconds(acetime_t epochSeconds, TimeOffset timeOffset)
Factory method.
Definition: OffsetDateTime.h:71
Brokers.h
ace_time::BasicZoneProcessor::getOffsetDateTime
OffsetDateTime getOffsetDateTime(const LocalDateTime &ldt) const override
Return the best estimate of the OffsetDateTime at the given LocalDateTime for the timezone of the cur...
Definition: BasicZoneProcessor.h:269
ace_time::LocalDate::forEpochSeconds
static LocalDate forEpochSeconds(acetime_t epochSeconds)
Factory method using the number of seconds since AceTime epoch of 2000-01-01.
Definition: LocalDate.h:157
ace_time::BasicZoneProcessor::BasicZoneProcessor
BasicZoneProcessor(const basic::ZoneInfo *zoneInfo=nullptr)
Constructor.
Definition: BasicZoneProcessor.h:210
ace_time::basic::Transition
Data structure that defines the start of a specific UTC offset as described by the matching ZoneEra a...
Definition: BasicZoneProcessor.h:55
ace_time::OffsetDateTime
The date (year, month, day), time (hour, minute, second) and offset from UTC (timeOffset).
Definition: OffsetDateTime.h:33
ace_time::BasicZoneProcessor::printTo
void printTo(Print &printer) const override
Print a human-readable identifier (e.g.
Definition: BasicZoneProcessor.cpp:12
ace_time::ZoneProcessor
Base interface for ZoneProcessor classes.
Definition: ZoneProcessor.h:45
ace_time::LocalDate::kInvalidYearTiny
static const int8_t kInvalidYearTiny
Sentinel yearTiny which indicates an error condition or sometimes a year that 'does not exist'.
Definition: LocalDate.h:45
ace_time::OffsetDateTime::forError
static OffsetDateTime forError()
Factory method that returns an instance whose isError() is true.
Definition: OffsetDateTime.h:146
ace_time::BasicZoneProcessor::getUtcOffset
TimeOffset getUtcOffset(acetime_t epochSeconds) const override
Return the total UTC offset at epochSeconds, including DST offset.
Definition: BasicZoneProcessor.h:221
ace_time::basic::Transition::abbrev
char abbrev[kAbbrevSize]
The calculated effective time zone abbreviation, e.g.
Definition: BasicZoneProcessor.h:113
ace_time::BasicZoneProcessor::getDeltaOffset
TimeOffset getDeltaOffset(acetime_t epochSeconds) const override
Return the DST delta offset at epochSeconds.
Definition: BasicZoneProcessor.h:228
ace_time::basic::Transition::offsetMinutes
int16_t offsetMinutes
The total effective UTC offset minutes at the start of transition, including DST offset.
Definition: BasicZoneProcessor.h:91
ace_time::basic::Transition::month
uint8_t month
Month of the transition.
Definition: BasicZoneProcessor.h:104
ace_time::LocalDateTime::localDate
const LocalDate & localDate() const
Return the LocalDate.
Definition: LocalDateTime.h:222
ace_time::TimeOffset::forMinutes
static TimeOffset forMinutes(int16_t minutes)
Create TimeOffset from minutes from 00:00.
Definition: TimeOffset.h:83
ace_time::LocalDate::forComponents
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
ace_time::BasicZoneProcessor::getAbbrev
const char * getAbbrev(acetime_t epochSeconds) const override
Return the time zone abbreviation at epochSeconds.
Definition: BasicZoneProcessor.h:235
ace_time::LocalDateTime::toEpochSeconds
acetime_t toEpochSeconds() const
Return seconds since AceTime epoch 2000-01-01 00:00:00Z, after assuming that the date and time compon...
Definition: LocalDateTime.h:246
ace_time::LocalDate::kEpochYear
static const int16_t kEpochYear
Base year of epoch.
Definition: LocalDate.h:39
ace_time::basic::Transition::startEpochSeconds
acetime_t startEpochSeconds
The calculated transition time of the given rule.
Definition: BasicZoneProcessor.h:84
ace_time::TimeOffset::kErrorMinutes
static const int16_t kErrorMinutes
Sentinel value that represents an error.
Definition: TimeOffset.h:59
ace_time::OffsetDateTime::forComponents
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.
Definition: OffsetDateTime.h:53
ace_time::LocalDate::daysInMonth
static uint8_t daysInMonth(int16_t year, uint8_t month)
Return the number of days in the current month.
Definition: LocalDate.h:222
ace_time::basic::Transition::log
void log() const
Used only for debugging.
Definition: BasicZoneProcessor.h:116
ace_time::OffsetDateTime::forLocalDateTimeAndOffset
static OffsetDateTime forLocalDateTimeAndOffset(const LocalDateTime &localDateTime, TimeOffset timeOffset)
Factory method from LocalDateTime and TimeOffset.
Definition: OffsetDateTime.h:37
ace_time::ZoneProcessorCacheImpl
A cache of ZoneProcessors that provides a ZoneProcessor to the TimeZone upon request.
Definition: BasicZoneProcessor.h:36
ace_time::BasicZoneProcessor::getZoneId
uint32_t getZoneId() const override
Return the unique stable zoneId.
Definition: BasicZoneProcessor.h:219
ace_time::BasicZoneProcessor
An implementation of ZoneProcessor that supports a subset of the zones containing in the TZ Database.
Definition: BasicZoneProcessor.h:204
ace_time::basic::Transition::rule
ZoneRuleBroker rule
The Zone transition rule that matched for the the given year.
Definition: BasicZoneProcessor.h:81
ace_time::BasicZoneProcessor::getZoneInfo
const void * getZoneInfo() const override
Return the underlying ZoneInfo.
Definition: BasicZoneProcessor.h:215
ace_time::BasicZoneProcessor::printShortTo
void printShortTo(Print &printer) const override
Print a short human-readable identifier (e.g.
Definition: BasicZoneProcessor.cpp:16
ace_time::BasicZoneProcessor::log
void log() const
Used only for debugging.
Definition: BasicZoneProcessor.h:318