AceTime  1.0
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):
211  ZoneProcessor(kTypeBasic),
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,
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*/,
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;
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;
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
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:154
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.
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:213
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:306
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.