AceTime  1.6
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/BasicBrokers.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_setZoneKey;
29 class BasicZoneProcessorTest_createAbbreviation;
30 class BasicZoneProcessorTest_calcRuleOffsetMinutes;
31 
32 class Print;
33 
34 namespace ace_time {
35 namespace basic {
36 
57 template <typename ZIB, typename ZEB, typename ZPB, typename ZRB>
64  ZEB era;
65 
75  ZRB rule;
76 
78  acetime_t startEpochSeconds;
79 
85  int16_t offsetMinutes;
86 
88  int16_t deltaMinutes;
89 
91  int8_t yearTiny;
92 
98  uint8_t month;
99 
107  char abbrev[internal::kAbbrevSize];
108 
110  void log() const {
111  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
112  logging::printf("(%d/%d)", yearTiny, month);
113  if (sizeof(acetime_t) == sizeof(int)) {
114  logging::printf("; stEps: %d", startEpochSeconds);
115  } else {
116  logging::printf("; stEps: %ld", startEpochSeconds);
117  }
118  logging::printf("; offMin: %d", offsetMinutes);
119  logging::printf("; abbrev: %s", abbrev);
120  if (! rule.isNull()) {
121  logging::printf("; r.fromYear: %d", rule.fromYearTiny());
122  logging::printf("; r.toYear: %d", rule.toYearTiny());
123  logging::printf("; r.inMonth: %d", rule.inMonth());
124  logging::printf("; r.onDayOfMonth: %d", rule.onDayOfMonth());
125  }
126  logging::printf("\n");
127  }
128  }
129 };
130 
132 inline int8_t compareYearMonth(int8_t aYear, uint8_t aMonth,
133  int8_t bYear, uint8_t bMonth) {
134  if (aYear < bYear) return -1;
135  if (aYear > bYear) return 1;
136  if (aMonth < bMonth) return -1;
137  if (aMonth > bMonth) return 1;
138  return 0;
139 }
140 
146 inline void copyAndReplace(char* dst, uint8_t dstSize, const char* src,
147  char oldChar, char newChar) {
148  while (*src != '\0' && dstSize > 0) {
149  if (*src == oldChar) {
150  if (newChar != '\0') {
151  *dst = newChar;
152  dst++;
153  dstSize--;
154  }
155  src++;
156  } else {
157  *dst++ = *src++;
158  dstSize--;
159  }
160  }
161 
162  if (dstSize == 0) {
163  --dst;
164  }
165  *dst = '\0';
166 }
167 
168 } // namespace basic
169 
227 template <typename BF, typename ZIB, typename ZEB, typename ZPB, typename ZRB>
229  public:
232 
233  uint32_t getZoneId() const override { return mZoneInfoBroker.zoneId(); }
234 
235  TimeOffset getUtcOffset(acetime_t epochSeconds) const override {
236  const Transition* transition = getTransition(epochSeconds);
237  int16_t minutes = (transition)
239  return TimeOffset::forMinutes(minutes);
240  }
241 
242  TimeOffset getDeltaOffset(acetime_t epochSeconds) const override {
243  const Transition* transition = getTransition(epochSeconds);
244  int16_t minutes = (transition)
245  ? transition->deltaMinutes : TimeOffset::kErrorMinutes;
246  return TimeOffset::forMinutes(minutes);
247  }
248 
249  const char* getAbbrev(acetime_t epochSeconds) const override {
250  const Transition* transition = getTransition(epochSeconds);
251  return (transition) ? transition->abbrev : "";
252  }
253 
283  OffsetDateTime getOffsetDateTime(const LocalDateTime& ldt) const override {
284  // Only a single local variable of OffsetDateTime used, to allow Return
285  // Value Optimization (and save 20 bytes of flash for WorldClock).
286  OffsetDateTime odt;
287  bool success = init(ldt.localDate());
288  if (success) {
289  // 0) Use the UTC epochSeconds to get intial guess of offset.
290  acetime_t epochSeconds0 = ldt.toEpochSeconds();
291  auto offset0 = getUtcOffset(epochSeconds0);
292 
293  // 1) Use offset0 to get the next epochSeconds and offset.
294  odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset0);
295  acetime_t epochSeconds1 = odt.toEpochSeconds();
296  auto offset1 = getUtcOffset(epochSeconds1);
297 
298  // 2) Use offset1 to get the next epochSeconds and offset.
299  odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset1);
300  acetime_t epochSeconds2 = odt.toEpochSeconds();
301  auto offset2 = getUtcOffset(epochSeconds2);
302 
303  // If offset1 and offset2 are equal, then we have an equilibrium
304  // and odt(1) must equal odt(2), so we can just return the last odt.
305  if (offset1 == offset2) {
306  // pass
307  } else {
308  // Pick the later epochSeconds and offset
309  acetime_t epochSeconds;
310  TimeOffset offset;
311  if (epochSeconds1 > epochSeconds2) {
312  epochSeconds = epochSeconds1;
313  offset = offset1;
314  } else {
315  epochSeconds = epochSeconds2;
316  offset = offset2;
317  }
318  odt = OffsetDateTime::forEpochSeconds(epochSeconds, offset);
319  }
320  } else {
321  odt = OffsetDateTime::forError();
322  }
323 
324  return odt;
325  }
326 
327  void printNameTo(Print& printer) const override {
328  mZoneInfoBroker.printNameTo(printer);
329  }
330 
331  void printShortNameTo(Print& printer) const override {
332  mZoneInfoBroker.printShortNameTo(printer);
333  }
334 
335  void setZoneKey(uintptr_t zoneKey) override {
336  if (mZoneInfoBroker.equals(zoneKey)) return;
337 
338  mZoneInfoBroker = mBrokerFactory->createZoneInfoBroker(zoneKey);
339  mYearTiny = LocalDate::kInvalidYearTiny;
340  mIsFilled = false;
341  mNumTransitions = 0;
342  }
343 
344  bool equalsZoneKey(uintptr_t zoneKey) const override {
345  return mZoneInfoBroker.equals(zoneKey);
346  }
347 
349  void log() const {
350  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
351  if (!mIsFilled) {
352  logging::printf("*not initialized*\n");
353  return;
354  }
355  logging::printf("mYearTiny: %d\n", mYearTiny);
356  logging::printf("mNumTransitions: %d\n", mNumTransitions);
357  for (int i = 0; i < mNumTransitions; i++) {
358  logging::printf("mT[%d]=", i);
359  mTransitions[i].log();
360  }
361  }
362  }
363 
364  void setBrokerFactory(const BF* brokerFactory) {
365  mBrokerFactory = brokerFactory;
366  }
367 
368  protected:
369 
377  uint8_t type,
378  const BF* brokerFactory,
379  uintptr_t zoneKey
380  ) :
381  ZoneProcessor(type),
382  mBrokerFactory(brokerFactory)
383  {
384  setZoneKey(zoneKey);
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_setZoneKey;
395  friend class ::BasicZoneProcessorTest_createAbbreviation;
396  friend class ::BasicZoneProcessorTest_calcRuleOffsetMinutes;
397 
408  static const uint8_t kMaxCacheEntries = 5;
409 
415  static const acetime_t kMinEpochSeconds = INT32_MIN + 1;
416 
417  // Disable copy constructor and assignment operator.
420  delete;
421 
422  bool equals(const ZoneProcessor& other) const override {
423  return mZoneInfoBroker.equals(
424  ((const BasicZoneProcessorTemplate&) other).mZoneInfoBroker);
425  }
426 
428  const Transition* getTransition(acetime_t epochSeconds) const {
429  LocalDate ld = LocalDate::forEpochSeconds(epochSeconds);
430  bool success = init(ld);
431  return (success) ? findMatch(epochSeconds) : nullptr;
432  }
433 
462  bool init(const LocalDate& ld) const {
463  int8_t yearTiny = ld.yearTiny();
464  if (ld.month() == 1 && ld.day() == 1) {
465  yearTiny--;
466  }
467  if (isFilled(yearTiny)) {
468  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
469  logging::printf("init(): %d (using cached %d)\n",
470  ld.yearTiny(), yearTiny);
471  }
472  return true;
473  } else {
474  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
475  logging::printf("init(): %d (new year %d)\n",
476  ld.yearTiny(), yearTiny);
477  }
478  }
479 
480  mYearTiny = yearTiny;
481  mNumTransitions = 0; // clear cache
482 
483  if (yearTiny + LocalDate::kEpochYear
484  < mZoneInfoBroker.zoneContext()->startYear - 1
485  || mZoneInfoBroker.zoneContext()->untilYear
486  < yearTiny + LocalDate::kEpochYear) {
487  return false;
488  }
489 
490  ZEB priorEra = addTransitionPriorToYear(yearTiny);
491  ZEB currentEra = addTransitionsForYear(yearTiny, priorEra);
492  addTransitionAfterYear(yearTiny, currentEra);
493  calcTransitions();
494  calcAbbreviations();
495 
496  mIsFilled = true;
497 
498  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
499  log();
500  }
501 
502  return true;
503  }
504 
506  bool isFilled(int8_t yearTiny) const {
507  return mIsFilled && (yearTiny == mYearTiny);
508  }
509 
516  ZEB addTransitionPriorToYear(int8_t yearTiny) const {
517  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
518  logging::printf("addTransitionPriorToYear(): %d\n", yearTiny);
519  }
520 
521  const ZEB era = findZoneEra(mZoneInfoBroker, yearTiny - 1);
522 
523  // If the prior ZoneEra has a ZonePolicy), then find the latest rule
524  // within the ZoneEra. Otherwise, add a Transition using a rule==nullptr.
525  ZRB latest = findLatestPriorRule(
526  era.zonePolicy(), yearTiny);
527  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
528  logging::printf("addTransitionsPriorToYear(): adding latest prior ");
529  if (latest.isNull()) {
530  logging::printf("ZR(null)\n");
531  } else {
532  logging::printf("ZR[%d,%d]\n",
533  latest.fromYearTiny(), latest.toYearTiny());
534  }
535  }
536  addTransition(yearTiny - 1, 0 /*month*/, era, latest);
537 
538  return era;
539  }
540 
546  static ZRB findLatestPriorRule(const ZPB& zonePolicy, int8_t yearTiny) {
547  ZRB latest;
548  if (zonePolicy.isNull()) return latest;
549 
550  uint8_t numRules = zonePolicy.numRules();
551  for (uint8_t i = 0; i < numRules; i++) {
552  const ZRB rule = zonePolicy.rule(i);
553  // Check if rule is effective prior to the given year
554  if (rule.fromYearTiny() < yearTiny) {
555  if ((latest.isNull()) ||
556  compareRulesBeforeYear(yearTiny, rule, latest) > 0) {
557  latest = rule;
558  }
559  }
560  }
561 
562  return latest;
563  }
564 
566  static int8_t compareRulesBeforeYear(int8_t yearTiny,
567  const ZRB& a, const ZRB& b) {
568  return basic::compareYearMonth(
569  priorYearOfRule(yearTiny, a), a.inMonth(),
570  priorYearOfRule(yearTiny, b), b.inMonth());
571  }
572 
581  static int8_t priorYearOfRule(int8_t yearTiny, const ZRB& rule) {
582  if (rule.toYearTiny() < yearTiny) {
583  return rule.toYearTiny();
584  }
585  return yearTiny - 1;
586  }
587 
592  ZEB addTransitionsForYear(int8_t yearTiny, const ZEB& priorEra) const {
593  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
594  logging::printf("addTransitionsForYear(): %d\n", yearTiny);
595  }
596 
597  const ZEB era = findZoneEra(mZoneInfoBroker, yearTiny);
598 
599  // If the ZonePolicy has no rules, then add a Transition which takes
600  // effect at the start time of the current year.
601  const ZPB zonePolicy = era.zonePolicy();
602  if (zonePolicy.isNull()) {
603  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
604  logging::printf("addTransitionsForYear(): adding ZE.untilY=%d\n",
605  era.untilYearTiny());
606  }
607  addTransition(yearTiny, 0 /*month*/, era, ZRB());
608  return era;
609  }
610 
611  if (! era.equals(priorEra)) {
612  // The ZoneEra has changed, so we need to find the Rule in effect at
613  // the start of the current year of the current ZoneEra. This may be a
614  // rule far in the past, but shift the rule forward to {year, 1, 1}.
615  ZRB latestPrior = findLatestPriorRule(
616  era.zonePolicy(), yearTiny);
617  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
618  logging::printf(
619  "addTransitionsForYear(): adding latest prior ");
620  if (latestPrior.isNull()) {
621  logging::printf("ZR(null)\n");
622  } else {
623  logging::printf("ZR[%d,%d]\n",
624  latestPrior.fromYearTiny(), latestPrior.toYearTiny());
625  }
626  }
627  addTransition(yearTiny, 1 /*month*/, era, latestPrior);
628  }
629 
630  // Find all directly matching transitions (i.e. the [from, to] overlap
631  // with the current year) and add them to mTransitions, in sorted order
632  // according to the ZoneRule::inMonth field.
633  uint8_t numRules = zonePolicy.numRules();
634  for (uint8_t i = 0; i < numRules; i++) {
635  const ZRB rule = zonePolicy.rule(i);
636  if ((rule.fromYearTiny() <= yearTiny) &&
637  (yearTiny <= rule.toYearTiny())) {
638  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
639  logging::printf(
640  "addTransitionsForYear(): adding rule ");
641  if (rule.isNull()) {
642  logging::printf("ZR(null)\n");
643  } else {
644  logging::printf("ZR[%d,%d]\n",
645  rule.fromYearTiny(), rule.toYearTiny());
646  }
647  }
648  addTransition(yearTiny, 0 /*month*/, era, rule);
649  }
650  }
651 
652  return era;
653  }
654 
656  void addTransitionAfterYear(int8_t yearTiny, const ZEB& currentEra) const {
657  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
658  logging::printf("addTransitionAfterYear(): %d\n", yearTiny);
659  }
660 
661  const ZEB eraAfter = findZoneEra(mZoneInfoBroker, yearTiny + 1);
662 
663  // If the current era is the same as the following year, then we'll just
664  // assume that the latest ZoneRule carries over to Jan 1st of the next
665  // year. tzcompiler.py guarantees no ZoneRule occurs on Jan 1st.
666  if (currentEra.equals(eraAfter)) {
667  return;
668  }
669 
670  // If the ZoneEra did change, find the latest transition prior to
671  // {yearTiny + 1, 1, 1}, then shift that Transition to Jan 1st of the
672  // following year.
673  ZRB latest = findLatestPriorRule(eraAfter.zonePolicy(), yearTiny + 1);
674  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
675  logging::printf(
676  "addTransitionsAfterYear(): adding latest prior ");
677  if (latest.isNull()) {
678  logging::printf("ZR(null)\n");
679  } else {
680  logging::printf("ZR[%d,%d]\n",
681  latest.fromYearTiny(), latest.toYearTiny());
682  }
683  }
684  addTransition(yearTiny + 1, 1 /*month*/, eraAfter, latest);
685  }
686 
710  void addTransition(int8_t yearTiny, uint8_t month, const ZEB& era,
711  const ZRB& rule) const {
712 
713  // If a zone needs more transitions than kMaxCacheEntries, the check below
714  // will cause the DST transition information to be inaccurate, and it is
715  // highly likely that this situation would be caught in the
716  // 'tests/validation' unit tests. Since these unit tests pass, I feel
717  // confident that those zones which need more than kMaxCacheEntries are
718  // already filtered out by tzcompiler.py.
719  //
720  // Ideally, the tzcompiler.py script would explicitly remove those zones
721  // which need more than kMaxCacheEntries Transitions. But this would
722  // require a Python version of the BasicZoneProcessor, and unfortunately,
723  // zone_specifier.py implements only the ExtendedZoneProcessor algorithm
724  // An early version of zone_specifier.py may have implemented something
725  // close to BasicZoneProcessor, and it may be available in the git
726  // history. But it seems like too much work right now to try to dig that
727  // out, just to implement the explicit check for kMaxCacheEntries. It
728  // would mean maintaining another version of zone_specifier.py.
729  if (mNumTransitions >= kMaxCacheEntries) return;
730 
731  // insert new element at the end of the list
732  mTransitions[mNumTransitions] = createTransition(
733  yearTiny, month, era, rule);
734  mNumTransitions++;
735 
736  // perform an insertion sort based on ZoneRule.inMonth()
737  for (uint8_t i = mNumTransitions - 1; i > 0; i--) {
738  Transition& left = mTransitions[i - 1];
739  Transition& right = mTransitions[i];
740  // assume only 1 rule per month
741  if (basic::compareYearMonth(left.yearTiny, left.month,
742  right.yearTiny, right.month) > 0) {
743  Transition tmp = left;
744  left = right;
745  right = tmp;
746  }
747  }
748  }
749 
755  static Transition createTransition(int8_t yearTiny, uint8_t month,
756  const ZEB& era, const ZRB& rule) {
757  int16_t deltaMinutes;
758  char letter;
759  uint8_t mon;
760  if (rule.isNull()) {
761  mon = 1; // RULES is either '-' or 'hh:mm' so takes effect in Jan
762  deltaMinutes = era.deltaMinutes();
763  letter = '\0';
764  } else {
765  mon = rule.inMonth();
766  deltaMinutes = rule.deltaMinutes();
767  letter = rule.letter();
768  }
769  // Clobber the month if specified.
770  if (month != 0) {
771  mon = month;
772  }
773  int16_t offsetMinutes = era.offsetMinutes() + deltaMinutes;
774 
775  return {
776  era,
777  rule,
778  0 /*epochSeconds*/,
779  offsetMinutes,
780  deltaMinutes,
781  yearTiny,
782  mon,
783  {letter} /*abbrev*/
784  };
785  }
786 
792  static ZEB findZoneEra(const ZIB& info, int8_t yearTiny) {
793  for (uint8_t i = 0; i < info.numEras(); i++) {
794  const ZEB era = info.era(i);
795  if (yearTiny < era.untilYearTiny()) return era;
796  }
797  // Return the last ZoneEra if we run off the end.
798  return info.era(info.numEras() - 1);
799  }
800 
808  void calcTransitions() const {
809  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
810  logging::printf("calcTransitions():\n");
811  }
812 
813  // Set the initial startEpochSeconds to be -Infinity
814  Transition* prevTransition = &mTransitions[0];
815  prevTransition->startEpochSeconds = kMinEpochSeconds;
816 
817  for (uint8_t i = 1; i < mNumTransitions; i++) {
818  Transition& transition = mTransitions[i];
819  const int16_t year = transition.yearTiny + LocalDate::kEpochYear;
820 
821  if (transition.rule.isNull()) {
822  // If the transition is simple (has no named rule), then the
823  // ZoneEra applies for the entire year (since BasicZoneProcessor
824  // supports only whole year in the UNTIL field). The whole year UNTIL
825  // field has an implied 'w' suffix on 00:00, we don't need to call
826  // calcRuleOffsetMinutes() with a 'w', we can just use the previous
827  // transition's offset to calculate the startDateTime of this
828  // transition.
829  //
830  // Also, when transition.rule == nullptr, the mNumTransitions should
831  // be 1, since only a single transition is added by
832  // addTransitionsForYear().
833  const int16_t prevOffsetMinutes = prevTransition->offsetMinutes;
834  OffsetDateTime startDateTime = OffsetDateTime::forComponents(
835  year, 1, 1, 0, 0, 0,
836  TimeOffset::forMinutes(prevOffsetMinutes));
837  transition.startEpochSeconds = startDateTime.toEpochSeconds();
838  } else {
839  // In this case, the transition points to a named ZonePolicy, which
840  // means that there could be multiple ZoneRules associated with the
841  // given year. For each transition, determine the startEpochSeconds,
842  // and the effective offset code.
843 
844  // Determine the start date of the rule.
845  const internal::MonthDay monthDay = internal::calcStartDayOfMonth(
846  year, transition.month, transition.rule.onDayOfWeek(),
847  transition.rule.onDayOfMonth());
848 
849  // Determine the offset of the 'atTimeSuffix'. The 'w' suffix
850  // requires the offset of the previous transition.
851  const int16_t prevOffsetMinutes = calcRuleOffsetMinutes(
852  prevTransition->offsetMinutes,
853  transition.era.offsetMinutes(),
854  transition.rule.atTimeSuffix());
855 
856  // startDateTime
857  const uint16_t minutes = transition.rule.atTimeMinutes();
858  const uint8_t atHour = minutes / 60;
859  const uint8_t atMinute = minutes % 60;
860  OffsetDateTime startDateTime = OffsetDateTime::forComponents(
861  year, monthDay.month, monthDay.day,
862  atHour, atMinute, 0 /*second*/,
863  TimeOffset::forMinutes(prevOffsetMinutes));
864  transition.startEpochSeconds = startDateTime.toEpochSeconds();
865  }
866 
867  prevTransition = &transition;
868  }
869  }
870 
877  static int16_t calcRuleOffsetMinutes(int16_t prevEffectiveOffsetMinutes,
878  int16_t currentBaseOffsetMinutes, uint8_t atSuffix) {
879  if (atSuffix == internal::ZoneContext::kSuffixW) {
880  return prevEffectiveOffsetMinutes;
881  } else if (atSuffix == internal::ZoneContext::kSuffixS) {
882  return currentBaseOffsetMinutes;
883  } else { // 'u', 'g' or 'z'
884  return 0;
885  }
886  }
887 
889  void calcAbbreviations() const {
890  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
891  logging::printf("calcAbbreviations():\n");
892  }
893 
894  for (uint8_t i = 0; i < mNumTransitions; i++) {
895  calcAbbreviation(&mTransitions[i]);
896  }
897  }
898 
900  static void calcAbbreviation(Transition* transition) {
901  createAbbreviation(
902  transition->abbrev,
903  internal::kAbbrevSize,
904  transition->era.format(),
905  transition->deltaMinutes,
906  transition->abbrev[0]);
907  }
908 
957  static void createAbbreviation(char* dest, uint8_t destSize,
958  const char* format, int16_t deltaMinutes, char letter) {
959  // Check if FORMAT contains a '%'.
960  if (strchr(format, '%') != nullptr) {
961  // Check if RULES column empty, therefore no 'letter'
962  if (letter == '\0') {
963  strncpy(dest, format, destSize - 1);
964  dest[destSize - 1] = '\0';
965  } else {
966  basic::copyAndReplace(dest, destSize, format, '%',
967  letter == '-' ? '\0' : letter);
968  }
969  } else {
970  // Check if FORMAT contains a '/'.
971  const char* slashPos = strchr(format, '/');
972  if (slashPos != nullptr) {
973  if (deltaMinutes == 0) {
974  uint8_t headLength = (slashPos - format);
975  if (headLength >= destSize) headLength = destSize - 1;
976  memcpy(dest, format, headLength);
977  dest[headLength] = '\0';
978  } else {
979  uint8_t tailLength = strlen(slashPos+1);
980  if (tailLength >= destSize) tailLength = destSize - 1;
981  memcpy(dest, slashPos+1, tailLength);
982  dest[tailLength] = '\0';
983  }
984  } else {
985  // Just copy the FORMAT disregarding the deltaMinutes and letter.
986  strncpy(dest, format, destSize - 1);
987  dest[destSize - 1] = '\0';
988  }
989  }
990  }
991 
993  const Transition* findMatch(acetime_t epochSeconds) const {
994  const Transition* closestMatch = nullptr;
995  for (uint8_t i = 0; i < mNumTransitions; i++) {
996  const Transition* m = &mTransitions[i];
997  if (closestMatch == nullptr || m->startEpochSeconds <= epochSeconds) {
998  closestMatch = m;
999  }
1000  }
1001  return closestMatch;
1002  }
1003 
1004  const BF* mBrokerFactory;
1005  ZIB mZoneInfoBroker;
1006 
1007  mutable int8_t mYearTiny = LocalDate::kInvalidYearTiny;
1008  mutable bool mIsFilled = false;
1009  mutable uint8_t mNumTransitions = 0;
1010  mutable Transition mTransitions[kMaxCacheEntries];
1011 };
1012 
1018  basic::BrokerFactory,
1019  basic::ZoneInfoBroker,
1020  basic::ZoneEraBroker,
1021  basic::ZonePolicyBroker,
1022  basic::ZoneRuleBroker> {
1023 
1024  public:
1026  static const uint8_t kTypeBasic = 3;
1027 
1028  explicit BasicZoneProcessor(const basic::ZoneInfo* zoneInfo = nullptr)
1030  basic::BrokerFactory,
1031  basic::ZoneInfoBroker,
1032  basic::ZoneEraBroker,
1033  basic::ZonePolicyBroker,
1034  basic::ZoneRuleBroker>(
1035  kTypeBasic, &mBrokerFactory, (uintptr_t) zoneInfo)
1036  {}
1037 
1038  private:
1039  basic::BrokerFactory mBrokerFactory;
1040 };
1041 
1042 } // namespace ace_time
1043 
1044 #endif
ace_time::BasicZoneProcessorTemplate::getDeltaOffset
TimeOffset getDeltaOffset(acetime_t epochSeconds) const override
Return the DST delta offset at epochSeconds.
Definition: BasicZoneProcessor.h:242
ace_time::basic::TransitionTemplate::log
void log() const
Used only for debugging.
Definition: BasicZoneProcessor.h:110
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::TransitionTemplate::rule
ZRB rule
The Zone transition rule that matched for the the given year.
Definition: BasicZoneProcessor.h:75
ace_time::BasicZoneProcessorTemplate::getUtcOffset
TimeOffset getUtcOffset(acetime_t epochSeconds) const override
Return the total UTC offset at epochSeconds, including DST offset.
Definition: BasicZoneProcessor.h:235
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::TransitionTemplate::offsetMinutes
int16_t offsetMinutes
The total effective UTC offset minutes at the start of transition, including DST offset.
Definition: BasicZoneProcessor.h:85
ace_time::BasicZoneProcessorTemplate::setZoneKey
void setZoneKey(uintptr_t zoneKey) override
Set the opaque zoneKey of this object to a new value, reseting any internally cached information.
Definition: BasicZoneProcessor.h:335
ace_time::BasicZoneProcessorTemplate::log
void log() const
Used only for debugging.
Definition: BasicZoneProcessor.h:349
ace_time::TimeOffset
A thin wrapper that represents a time offset from a reference point, usually 00:00 at UTC,...
Definition: TimeOffset.h:56
BasicBrokers.h
ace_time::OffsetDateTime::forEpochSeconds
static OffsetDateTime forEpochSeconds(acetime_t epochSeconds, TimeOffset timeOffset)
Factory method.
Definition: OffsetDateTime.h:71
ace_time::internal::ZoneContext::kSuffixS
static const uint8_t kSuffixS
Represents 's' or standard time.
Definition: ZoneContext.h:21
ace_time::basic::TransitionTemplate::deltaMinutes
int16_t deltaMinutes
The deltaMinutes from "standard time" at the start of transition.
Definition: BasicZoneProcessor.h:88
ace_time::BasicZoneProcessorTemplate
An implementation of ZoneProcessor that supports a subset of the zones containing in the TZ Database.
Definition: BasicZoneProcessor.h:228
ace_time::BasicZoneProcessorTemplate::printNameTo
void printNameTo(Print &printer) const override
Print a human-readable identifier (e.g.
Definition: BasicZoneProcessor.h:327
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::basic::BrokerFactory
A factory that creates a basic::ZoneInfoBroker.
Definition: BasicBrokers.h:444
ace_time::basic::TransitionTemplate::abbrev
char abbrev[internal::kAbbrevSize]
The calculated effective time zone abbreviation, e.g.
Definition: BasicZoneProcessor.h:107
ace_time::BasicZoneProcessorTemplate::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:283
ace_time::basic::TransitionTemplate::yearTiny
int8_t yearTiny
Year of the Transition.
Definition: BasicZoneProcessor.h:91
ace_time::BasicZoneProcessorTemplate::getAbbrev
const char * getAbbrev(acetime_t epochSeconds) const override
Return the time zone abbreviation at epochSeconds.
Definition: BasicZoneProcessor.h:249
ace_time::OffsetDateTime
The date (year, month, day), time (hour, minute, second) and offset from UTC (timeOffset).
Definition: OffsetDateTime.h:33
ace_time::basic::TransitionTemplate::era
ZEB era
The ZoneEra that matched the given year.
Definition: BasicZoneProcessor.h:64
ace_time::ZoneProcessor
Base interface for ZoneProcessor classes.
Definition: ZoneProcessor.h:41
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::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::BasicZoneProcessorTemplate::printShortNameTo
void printShortNameTo(Print &printer) const override
Print a short human-readable identifier (e.g.
Definition: BasicZoneProcessor.h:331
ace_time::BasicZoneProcessorTemplate::equalsZoneKey
bool equalsZoneKey(uintptr_t zoneKey) const override
Return true if ZoneProcessor is associated with the given opaque zoneKey.
Definition: BasicZoneProcessor.h:344
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::BasicZoneProcessorTemplate::Transition
basic::TransitionTemplate< ZIB, ZEB, ZPB, ZRB > Transition
Exposed only for testing purposes.
Definition: BasicZoneProcessor.h:231
ace_time::LocalDate::kEpochYear
static const int16_t kEpochYear
Base year of epoch.
Definition: LocalDate.h:39
ace_time::BasicZoneProcessorTemplate::BasicZoneProcessorTemplate
BasicZoneProcessorTemplate(uint8_t type, const BF *brokerFactory, uintptr_t zoneKey)
Constructor.
Definition: BasicZoneProcessor.h:376
ace_time::basic::TransitionTemplate::month
uint8_t month
Month of the transition.
Definition: BasicZoneProcessor.h:98
ace_time::basic::TransitionTemplate::startEpochSeconds
acetime_t startEpochSeconds
The calculated transition time of the given rule.
Definition: BasicZoneProcessor.h:78
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::basic::TransitionTemplate
Data structure that defines the start of a specific UTC offset as described by the matching ZoneEra a...
Definition: BasicZoneProcessor.h:58
ace_time::BasicZoneProcessorTemplate::getZoneId
uint32_t getZoneId() const override
Return the unique stable zoneId.
Definition: BasicZoneProcessor.h:233
ace_time::OffsetDateTime::forLocalDateTimeAndOffset
static OffsetDateTime forLocalDateTimeAndOffset(const LocalDateTime &localDateTime, TimeOffset timeOffset)
Factory method from LocalDateTime and TimeOffset.
Definition: OffsetDateTime.h:37
ace_time::BasicZoneProcessor
A specific implementation of BasicZoneProcessorTemplate that uses ZoneXxxBrokers which read from zone...
Definition: BasicZoneProcessor.h:1017
ace_time::internal::ZoneContext::kSuffixW
static const uint8_t kSuffixW
Represents 'w' or wall time.
Definition: ZoneContext.h:18
ace_time::BasicZoneProcessor::kTypeBasic
static const uint8_t kTypeBasic
Unique TimeZone type identifier for BasicZoneProcessor.
Definition: BasicZoneProcessor.h:1026