AceTime  1.11.2
Date and time classes for Arduino that support timezones from the TZ Database.
BasicZoneProcessor.h
1 /*
2  * MIT License
3  * Copyright (c) 2019 Brian T. Park
4  */
5 
6 #ifndef ACE_TIME_BASIC_ZONE_PROCESSOR_H
7 #define ACE_TIME_BASIC_ZONE_PROCESSOR_H
8 
9 #include <string.h> // strchr()
10 #include <stdint.h>
11 #include <AceCommon.h> // copyReplaceChar()
12 #include "internal/ZonePolicy.h"
13 #include "internal/ZoneInfo.h"
14 #include "internal/BasicBrokers.h"
15 #include "common/logging.h"
16 #include "TimeOffset.h"
17 #include "LocalDate.h"
18 #include "OffsetDateTime.h"
19 #include "ZoneProcessor.h"
20 
21 #ifndef ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG
22 #define ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG 0
23 #endif
24 
25 class BasicZoneProcessorTest_priorYearOfRule;
26 class BasicZoneProcessorTest_compareRulesBeforeYear;
27 class BasicZoneProcessorTest_findLatestPriorRule;
28 class BasicZoneProcessorTest_findZoneEra;
29 class BasicZoneProcessorTest_init_primitives;
30 class BasicZoneProcessorTest_init;
31 class BasicZoneProcessorTest_setZoneKey;
32 class BasicZoneProcessorTest_createAbbreviation;
33 class BasicZoneProcessorTest_calcRuleOffsetMinutes;
34 
35 class Print;
36 
37 namespace ace_time {
38 namespace basic {
39 
60 template <typename ZIB, typename ZEB, typename ZPB, typename ZRB>
67  ZEB era;
68 
78  ZRB rule;
79 
81  acetime_t startEpochSeconds;
82 
88  int16_t offsetMinutes;
89 
91  int16_t deltaMinutes;
92 
94  int8_t yearTiny;
95 
101  uint8_t month;
102 
110  char abbrev[internal::kAbbrevSize];
111 
113  void log() const {
114  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
115  logging::printf("(%d/%d)", yearTiny, month);
116  if (sizeof(acetime_t) == sizeof(int)) {
117  logging::printf("; stEps: %d", startEpochSeconds);
118  } else {
119  logging::printf("; stEps: %ld", startEpochSeconds);
120  }
121  logging::printf("; offMin: %d", offsetMinutes);
122  logging::printf("; abbrev: %s", abbrev);
123  if (! rule.isNull()) {
124  logging::printf("; r.fromYear: %d", rule.fromYearTiny());
125  logging::printf("; r.toYear: %d", rule.toYearTiny());
126  logging::printf("; r.inMonth: %d", rule.inMonth());
127  logging::printf("; r.onDayOfMonth: %d", rule.onDayOfMonth());
128  }
129  logging::printf("\n");
130  }
131  }
132 };
133 
135 inline int8_t compareYearMonth(int8_t aYear, uint8_t aMonth,
136  int8_t bYear, uint8_t bMonth) {
137  if (aYear < bYear) return -1;
138  if (aYear > bYear) return 1;
139  if (aMonth < bMonth) return -1;
140  if (aMonth > bMonth) return 1;
141  return 0;
142 }
143 
144 } // namespace basic
145 
203 template <typename BF, typename ZIB, typename ZEB, typename ZPB, typename ZRB>
205  public:
208 
209  bool isLink() const override { return mZoneInfoBroker.isLink(); }
210 
211  uint32_t getZoneId(bool followLink = false) const override {
212  ZIB zib = (isLink() && followLink)
213  ? mZoneInfoBroker.targetZoneInfo()
214  : mZoneInfoBroker;
215  return zib.zoneId();
216  }
217 
218  TimeOffset getUtcOffset(acetime_t epochSeconds) const override {
219  const Transition* transition = getTransition(epochSeconds);
220  int16_t minutes = (transition)
222  return TimeOffset::forMinutes(minutes);
223  }
224 
225  TimeOffset getDeltaOffset(acetime_t epochSeconds) const override {
226  const Transition* transition = getTransition(epochSeconds);
227  int16_t minutes = (transition)
228  ? transition->deltaMinutes : TimeOffset::kErrorMinutes;
229  return TimeOffset::forMinutes(minutes);
230  }
231 
232  const char* getAbbrev(acetime_t epochSeconds) const override {
233  const Transition* transition = getTransition(epochSeconds);
234  return (transition) ? transition->abbrev : "";
235  }
236 
266  OffsetDateTime getOffsetDateTime(const LocalDateTime& ldt) const override {
267  // Only a single local variable of OffsetDateTime used, to allow Return
268  // Value Optimization (and save 20 bytes of flash for WorldClock).
269  OffsetDateTime odt;
270  bool success = init(ldt.localDate());
271  if (success) {
272  // 0) Use the UTC epochSeconds to get intial guess of offset.
273  acetime_t epochSeconds0 = ldt.toEpochSeconds();
274  auto offset0 = getUtcOffset(epochSeconds0);
275 
276  // 1) Use offset0 to get the next epochSeconds and offset.
277  odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset0);
278  acetime_t epochSeconds1 = odt.toEpochSeconds();
279  auto offset1 = getUtcOffset(epochSeconds1);
280 
281  // 2) Use offset1 to get the next epochSeconds and offset.
282  odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset1);
283  acetime_t epochSeconds2 = odt.toEpochSeconds();
284  auto offset2 = getUtcOffset(epochSeconds2);
285 
286  // If offset1 and offset2 are equal, then we have an equilibrium
287  // and odt(1) must equal odt(2), so we can just return the last odt.
288  if (offset1 == offset2) {
289  // pass
290  } else {
291  // Pick the later epochSeconds and offset
292  acetime_t epochSeconds;
293  TimeOffset offset;
294  if (epochSeconds1 > epochSeconds2) {
295  epochSeconds = epochSeconds1;
296  offset = offset1;
297  } else {
298  epochSeconds = epochSeconds2;
299  offset = offset2;
300  }
301  odt = OffsetDateTime::forEpochSeconds(epochSeconds, offset);
302  }
303  } else {
304  odt = OffsetDateTime::forError();
305  }
306 
307  return odt;
308  }
309 
310  OffsetDateTime getOffsetDateTime(acetime_t epochSeconds) const override {
311  TimeOffset timeOffset = getUtcOffset(epochSeconds);
312  return OffsetDateTime::forEpochSeconds(epochSeconds, timeOffset);
313  }
314 
315  void printNameTo(Print& printer, bool followLink = false) const override {
316  ZIB zib = (isLink() && followLink)
317  ? mZoneInfoBroker.targetZoneInfo()
318  : mZoneInfoBroker;
319  zib.printNameTo(printer);
320  }
321 
322  void printShortNameTo(Print& printer, bool followLink = false)
323  const override {
324  ZIB zib = (isLink() && followLink)
325  ? mZoneInfoBroker.targetZoneInfo()
326  : mZoneInfoBroker;
327  zib.printShortNameTo(printer);
328  }
329 
330  void setZoneKey(uintptr_t zoneKey) override {
331  if (! mBrokerFactory) return;
332  if (mZoneInfoBroker.equals(zoneKey)) return;
333 
334  mZoneInfoBroker = mBrokerFactory->createZoneInfoBroker(zoneKey);
335 
336  mYearTiny = LocalDate::kInvalidYearTiny;
337  mIsFilled = false;
338  mNumTransitions = 0;
339  }
340 
341  bool equalsZoneKey(uintptr_t zoneKey) const override {
342  return mZoneInfoBroker.equals(zoneKey);
343  }
344 
346  void log() const {
347  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
348  if (!mIsFilled) {
349  logging::printf("*not initialized*\n");
350  return;
351  }
352  logging::printf("mYearTiny: %d\n", mYearTiny);
353  logging::printf("mNumTransitions: %d\n", mNumTransitions);
354  for (int i = 0; i < mNumTransitions; i++) {
355  logging::printf("mT[%d]=", i);
356  mTransitions[i].log();
357  }
358  }
359  }
360 
367  void setBrokerFactory(const BF* brokerFactory) {
368  mBrokerFactory = brokerFactory;
369  }
370 
371  protected:
372 
382  uint8_t type,
383  const BF* brokerFactory /*nullable*/,
384  uintptr_t zoneKey
385  ) :
386  ZoneProcessor(type),
387  mBrokerFactory(brokerFactory)
388  {
389  setZoneKey(zoneKey);
390  }
391 
392  private:
393  friend class ::BasicZoneProcessorTest_priorYearOfRule;
394  friend class ::BasicZoneProcessorTest_compareRulesBeforeYear;
395  friend class ::BasicZoneProcessorTest_findLatestPriorRule;
396  friend class ::BasicZoneProcessorTest_findZoneEra;
397  friend class ::BasicZoneProcessorTest_init_primitives;
398  friend class ::BasicZoneProcessorTest_init;
399  friend class ::BasicZoneProcessorTest_setZoneKey;
400  friend class ::BasicZoneProcessorTest_createAbbreviation;
401  friend class ::BasicZoneProcessorTest_calcRuleOffsetMinutes;
402 
413  static const uint8_t kMaxCacheEntries = 5;
414 
420  static const acetime_t kMinEpochSeconds = INT32_MIN + 1;
421 
422  // Disable copy constructor and assignment operator.
425  delete;
426 
427  bool equals(const ZoneProcessor& other) const override {
428  return mZoneInfoBroker.equals(
429  ((const BasicZoneProcessorTemplate&) other).mZoneInfoBroker);
430  }
431 
433  const Transition* getTransition(acetime_t epochSeconds) const {
434  LocalDate ld = LocalDate::forEpochSeconds(epochSeconds);
435  bool success = init(ld);
436  return (success) ? findMatch(epochSeconds) : nullptr;
437  }
438 
467  bool init(const LocalDate& ld) const {
468  int8_t yearTiny = ld.yearTiny();
469  if (ld.month() == 1 && ld.day() == 1) {
470  yearTiny--;
471  }
472  if (isFilled(yearTiny)) {
473  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
474  logging::printf("init(): %d (using cached %d)\n",
475  ld.yearTiny(), yearTiny);
476  }
477  return true;
478  } else {
479  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
480  logging::printf("init(): %d (new year %d)\n",
481  ld.yearTiny(), yearTiny);
482  }
483  }
484 
485  mYearTiny = yearTiny;
486  mNumTransitions = 0; // clear cache
487 
488  if (yearTiny + LocalDate::kEpochYear
489  < mZoneInfoBroker.zoneContext()->startYear - 1
490  || mZoneInfoBroker.zoneContext()->untilYear
491  < yearTiny + LocalDate::kEpochYear) {
492  return false;
493  }
494 
495  ZEB priorEra = addTransitionPriorToYear(yearTiny);
496  ZEB currentEra = addTransitionsForYear(yearTiny, priorEra);
497  addTransitionAfterYear(yearTiny, currentEra);
498  calcTransitions();
499  calcAbbreviations();
500 
501  mIsFilled = true;
502 
503  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
504  log();
505  }
506 
507  return true;
508  }
509 
511  bool isFilled(int8_t yearTiny) const {
512  return mIsFilled && (yearTiny == mYearTiny);
513  }
514 
521  ZEB addTransitionPriorToYear(int8_t yearTiny) const {
522  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
523  logging::printf("addTransitionPriorToYear(): %d\n", yearTiny);
524  }
525 
526  const ZEB era = findZoneEra(mZoneInfoBroker, yearTiny - 1);
527 
528  // If the prior ZoneEra has a ZonePolicy), then find the latest rule
529  // within the ZoneEra. Otherwise, add a Transition using a rule==nullptr.
530  ZRB latest = findLatestPriorRule(
531  era.zonePolicy(), yearTiny);
532  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
533  logging::printf("addTransitionsPriorToYear(): adding latest prior ");
534  if (latest.isNull()) {
535  logging::printf("ZR(null)\n");
536  } else {
537  logging::printf("ZR[%d,%d]\n",
538  latest.fromYearTiny(), latest.toYearTiny());
539  }
540  }
541  addTransition(yearTiny - 1, 0 /*month*/, era, latest);
542 
543  return era;
544  }
545 
551  static ZRB findLatestPriorRule(const ZPB& zonePolicy, int8_t yearTiny) {
552  ZRB latest;
553  if (zonePolicy.isNull()) return latest;
554 
555  uint8_t numRules = zonePolicy.numRules();
556  for (uint8_t i = 0; i < numRules; i++) {
557  const ZRB rule = zonePolicy.rule(i);
558  // Check if rule is effective prior to the given year
559  if (rule.fromYearTiny() < yearTiny) {
560  if ((latest.isNull()) ||
561  compareRulesBeforeYear(yearTiny, rule, latest) > 0) {
562  latest = rule;
563  }
564  }
565  }
566 
567  return latest;
568  }
569 
571  static int8_t compareRulesBeforeYear(int8_t yearTiny,
572  const ZRB& a, const ZRB& b) {
573  return basic::compareYearMonth(
574  priorYearOfRule(yearTiny, a), a.inMonth(),
575  priorYearOfRule(yearTiny, b), b.inMonth());
576  }
577 
586  static int8_t priorYearOfRule(int8_t yearTiny, const ZRB& rule) {
587  if (rule.toYearTiny() < yearTiny) {
588  return rule.toYearTiny();
589  }
590  return yearTiny - 1;
591  }
592 
597  ZEB addTransitionsForYear(int8_t yearTiny, const ZEB& priorEra) const {
598  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
599  logging::printf("addTransitionsForYear(): %d\n", yearTiny);
600  }
601 
602  const ZEB era = findZoneEra(mZoneInfoBroker, yearTiny);
603 
604  // If the ZonePolicy has no rules, then add a Transition which takes
605  // effect at the start time of the current year.
606  const ZPB zonePolicy = era.zonePolicy();
607  if (zonePolicy.isNull()) {
608  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
609  logging::printf("addTransitionsForYear(): adding ZE.untilY=%d\n",
610  era.untilYearTiny());
611  }
612  addTransition(yearTiny, 0 /*month*/, era, ZRB());
613  return era;
614  }
615 
616  if (! era.equals(priorEra)) {
617  // The ZoneEra has changed, so we need to find the Rule in effect at
618  // the start of the current year of the current ZoneEra. This may be a
619  // rule far in the past, but shift the rule forward to {year, 1, 1}.
620  ZRB latestPrior = findLatestPriorRule(
621  era.zonePolicy(), yearTiny);
622  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
623  logging::printf(
624  "addTransitionsForYear(): adding latest prior ");
625  if (latestPrior.isNull()) {
626  logging::printf("ZR(null)\n");
627  } else {
628  logging::printf("ZR[%d,%d]\n",
629  latestPrior.fromYearTiny(), latestPrior.toYearTiny());
630  }
631  }
632  addTransition(yearTiny, 1 /*month*/, era, latestPrior);
633  }
634 
635  // Find all directly matching transitions (i.e. the [from, to] overlap
636  // with the current year) and add them to mTransitions, in sorted order
637  // according to the ZoneRule::inMonth field.
638  uint8_t numRules = zonePolicy.numRules();
639  for (uint8_t i = 0; i < numRules; i++) {
640  const ZRB rule = zonePolicy.rule(i);
641  if ((rule.fromYearTiny() <= yearTiny) &&
642  (yearTiny <= rule.toYearTiny())) {
643  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
644  logging::printf(
645  "addTransitionsForYear(): adding rule ");
646  if (rule.isNull()) {
647  logging::printf("ZR(null)\n");
648  } else {
649  logging::printf("ZR[%d,%d]\n",
650  rule.fromYearTiny(), rule.toYearTiny());
651  }
652  }
653  addTransition(yearTiny, 0 /*month*/, era, rule);
654  }
655  }
656 
657  return era;
658  }
659 
661  void addTransitionAfterYear(int8_t yearTiny, const ZEB& currentEra) const {
662  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
663  logging::printf("addTransitionAfterYear(): %d\n", yearTiny);
664  }
665 
666  const ZEB eraAfter = findZoneEra(mZoneInfoBroker, yearTiny + 1);
667 
668  // If the current era is the same as the following year, then we'll just
669  // assume that the latest ZoneRule carries over to Jan 1st of the next
670  // year. tzcompiler.py guarantees no ZoneRule occurs on Jan 1st.
671  if (currentEra.equals(eraAfter)) {
672  return;
673  }
674 
675  // If the ZoneEra did change, find the latest transition prior to
676  // {yearTiny + 1, 1, 1}, then shift that Transition to Jan 1st of the
677  // following year.
678  ZRB latest = findLatestPriorRule(eraAfter.zonePolicy(), yearTiny + 1);
679  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
680  logging::printf(
681  "addTransitionsAfterYear(): adding latest prior ");
682  if (latest.isNull()) {
683  logging::printf("ZR(null)\n");
684  } else {
685  logging::printf("ZR[%d,%d]\n",
686  latest.fromYearTiny(), latest.toYearTiny());
687  }
688  }
689  addTransition(yearTiny + 1, 1 /*month*/, eraAfter, latest);
690  }
691 
715  void addTransition(int8_t yearTiny, uint8_t month, const ZEB& era,
716  const ZRB& rule) const {
717 
718  // If a zone needs more transitions than kMaxCacheEntries, the check below
719  // will cause the DST transition information to be inaccurate, and it is
720  // highly likely that this situation would be caught in the
721  // AceTimeValidation tests. Since these integration tests pass, I feel
722  // confident that those zones which need more than kMaxCacheEntries are
723  // already filtered out by tzcompiler.py.
724  //
725  // Ideally, the tzcompiler.py script would explicitly remove those zones
726  // which need more than kMaxCacheEntries Transitions. But this would
727  // require a Python version of the BasicZoneProcessor, and unfortunately,
728  // zone_processor.py implements only the ExtendedZoneProcessor algorithm
729  // An early version of zone_processor.py may have implemented something
730  // close to BasicZoneProcessor, and it may be available in the git
731  // history. But it seems like too much work right now to try to dig that
732  // out, just to implement the explicit check for kMaxCacheEntries. It
733  // would mean maintaining another version of zone_processor.py.
734  if (mNumTransitions >= kMaxCacheEntries) return;
735 
736  // insert new element at the end of the list
737  mTransitions[mNumTransitions] = createTransition(
738  yearTiny, month, era, rule);
739  mNumTransitions++;
740 
741  // perform an insertion sort based on ZoneRule.inMonth()
742  for (uint8_t i = mNumTransitions - 1; i > 0; i--) {
743  Transition& left = mTransitions[i - 1];
744  Transition& right = mTransitions[i];
745  // assume only 1 rule per month
746  if (basic::compareYearMonth(left.yearTiny, left.month,
747  right.yearTiny, right.month) > 0) {
748  Transition tmp = left;
749  left = right;
750  right = tmp;
751  }
752  }
753  }
754 
760  static Transition createTransition(int8_t yearTiny, uint8_t month,
761  const ZEB& era, const ZRB& rule) {
762  int16_t deltaMinutes;
763  char letter;
764  uint8_t mon;
765  if (rule.isNull()) {
766  mon = 1; // RULES is either '-' or 'hh:mm' so takes effect in Jan
767  deltaMinutes = era.deltaMinutes();
768  letter = '\0';
769  } else {
770  mon = rule.inMonth();
771  deltaMinutes = rule.deltaMinutes();
772  letter = rule.letter();
773  }
774  // Clobber the month if specified.
775  if (month != 0) {
776  mon = month;
777  }
778  int16_t offsetMinutes = era.offsetMinutes() + deltaMinutes;
779 
780  return {
781  era,
782  rule,
783  0 /*epochSeconds*/,
784  offsetMinutes,
785  deltaMinutes,
786  yearTiny,
787  mon,
788  {letter} /*abbrev*/
789  };
790  }
791 
797  static ZEB findZoneEra(const ZIB& info, int8_t yearTiny) {
798  for (uint8_t i = 0; i < info.numEras(); i++) {
799  const ZEB era = info.era(i);
800  if (yearTiny < era.untilYearTiny()) return era;
801  }
802  // Return the last ZoneEra if we run off the end.
803  return info.era(info.numEras() - 1);
804  }
805 
813  void calcTransitions() const {
814  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
815  logging::printf("calcTransitions():\n");
816  }
817 
818  // Set the initial startEpochSeconds to be -Infinity
819  Transition* prevTransition = &mTransitions[0];
820  prevTransition->startEpochSeconds = kMinEpochSeconds;
821 
822  for (uint8_t i = 1; i < mNumTransitions; i++) {
823  Transition& transition = mTransitions[i];
824  const int16_t year = transition.yearTiny + LocalDate::kEpochYear;
825 
826  if (transition.rule.isNull()) {
827  // If the transition is simple (has no named rule), then the
828  // ZoneEra applies for the entire year (since BasicZoneProcessor
829  // supports only whole year in the UNTIL field). The whole year UNTIL
830  // field has an implied 'w' suffix on 00:00, we don't need to call
831  // calcRuleOffsetMinutes() with a 'w', we can just use the previous
832  // transition's offset to calculate the startDateTime of this
833  // transition.
834  //
835  // Also, when transition.rule == nullptr, the mNumTransitions should
836  // be 1, since only a single transition is added by
837  // addTransitionsForYear().
838  const int16_t prevOffsetMinutes = prevTransition->offsetMinutes;
839  OffsetDateTime startDateTime = OffsetDateTime::forComponents(
840  year, 1, 1, 0, 0, 0,
841  TimeOffset::forMinutes(prevOffsetMinutes));
842  transition.startEpochSeconds = startDateTime.toEpochSeconds();
843  } else {
844  // In this case, the transition points to a named ZonePolicy, which
845  // means that there could be multiple ZoneRules associated with the
846  // given year. For each transition, determine the startEpochSeconds,
847  // and the effective offset code.
848 
849  // Determine the start date of the rule.
850  const internal::MonthDay monthDay = internal::calcStartDayOfMonth(
851  year, transition.month, transition.rule.onDayOfWeek(),
852  transition.rule.onDayOfMonth());
853 
854  // Determine the offset of the 'atTimeSuffix'. The 'w' suffix
855  // requires the offset of the previous transition.
856  const int16_t prevOffsetMinutes = calcRuleOffsetMinutes(
857  prevTransition->offsetMinutes,
858  transition.era.offsetMinutes(),
859  transition.rule.atTimeSuffix());
860 
861  // startDateTime
862  const uint16_t minutes = transition.rule.atTimeMinutes();
863  const uint8_t atHour = minutes / 60;
864  const uint8_t atMinute = minutes % 60;
865  OffsetDateTime startDateTime = OffsetDateTime::forComponents(
866  year, monthDay.month, monthDay.day,
867  atHour, atMinute, 0 /*second*/,
868  TimeOffset::forMinutes(prevOffsetMinutes));
869  transition.startEpochSeconds = startDateTime.toEpochSeconds();
870  }
871 
872  prevTransition = &transition;
873  }
874  }
875 
882  static int16_t calcRuleOffsetMinutes(int16_t prevEffectiveOffsetMinutes,
883  int16_t currentBaseOffsetMinutes, uint8_t atSuffix) {
884  if (atSuffix == internal::ZoneContext::kSuffixW) {
885  return prevEffectiveOffsetMinutes;
886  } else if (atSuffix == internal::ZoneContext::kSuffixS) {
887  return currentBaseOffsetMinutes;
888  } else { // 'u', 'g' or 'z'
889  return 0;
890  }
891  }
892 
894  void calcAbbreviations() const {
895  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
896  logging::printf("calcAbbreviations():\n");
897  }
898 
899  for (uint8_t i = 0; i < mNumTransitions; i++) {
900  calcAbbreviation(&mTransitions[i]);
901  }
902  }
903 
905  static void calcAbbreviation(Transition* transition) {
906  createAbbreviation(
907  transition->abbrev,
908  internal::kAbbrevSize,
909  transition->era.format(),
910  transition->deltaMinutes,
911  transition->abbrev[0]);
912  }
913 
962  static void createAbbreviation(char* dest, uint8_t destSize,
963  const char* format, int16_t deltaMinutes, char letter) {
964  // Check if FORMAT contains a '%'.
965  if (strchr(format, '%') != nullptr) {
966  // Check if RULES column empty, therefore no 'letter'
967  if (letter == '\0') {
968  strncpy(dest, format, destSize - 1);
969  dest[destSize - 1] = '\0';
970  } else {
971  ace_common::copyReplaceChar(dest, destSize, format, '%',
972  letter == '-' ? '\0' : letter);
973  }
974  } else {
975  // Check if FORMAT contains a '/'.
976  const char* slashPos = strchr(format, '/');
977  if (slashPos != nullptr) {
978  if (deltaMinutes == 0) {
979  uint8_t headLength = (slashPos - format);
980  if (headLength >= destSize) headLength = destSize - 1;
981  memcpy(dest, format, headLength);
982  dest[headLength] = '\0';
983  } else {
984  uint8_t tailLength = strlen(slashPos+1);
985  if (tailLength >= destSize) tailLength = destSize - 1;
986  memcpy(dest, slashPos+1, tailLength);
987  dest[tailLength] = '\0';
988  }
989  } else {
990  // Just copy the FORMAT disregarding the deltaMinutes and letter.
991  strncpy(dest, format, destSize - 1);
992  dest[destSize - 1] = '\0';
993  }
994  }
995  }
996 
998  const Transition* findMatch(acetime_t epochSeconds) const {
999  const Transition* closestMatch = nullptr;
1000  for (uint8_t i = 0; i < mNumTransitions; i++) {
1001  const Transition* m = &mTransitions[i];
1002  if (closestMatch == nullptr || m->startEpochSeconds <= epochSeconds) {
1003  closestMatch = m;
1004  }
1005  }
1006  return closestMatch;
1007  }
1008 
1009  const BF* mBrokerFactory; // nullable
1010  ZIB mZoneInfoBroker;
1011 
1012  mutable int8_t mYearTiny = LocalDate::kInvalidYearTiny;
1013  mutable bool mIsFilled = false;
1014  mutable uint8_t mNumTransitions = 0;
1015  mutable Transition mTransitions[kMaxCacheEntries];
1016 };
1017 
1023  basic::BrokerFactory,
1024  basic::ZoneInfoBroker,
1025  basic::ZoneEraBroker,
1026  basic::ZonePolicyBroker,
1027  basic::ZoneRuleBroker> {
1028 
1029  public:
1031  static const uint8_t kTypeBasic = 3;
1032 
1033  explicit BasicZoneProcessor(const basic::ZoneInfo* zoneInfo = nullptr)
1035  basic::BrokerFactory,
1036  basic::ZoneInfoBroker,
1037  basic::ZoneEraBroker,
1038  basic::ZonePolicyBroker,
1039  basic::ZoneRuleBroker>(
1040  kTypeBasic, &mBrokerFactory, (uintptr_t) zoneInfo)
1041  {}
1042 
1043  private:
1044  basic::BrokerFactory mBrokerFactory;
1045 };
1046 
1047 } // namespace ace_time
1048 
1049 #endif
ace_time::BasicZoneProcessorTemplate::getDeltaOffset
TimeOffset getDeltaOffset(acetime_t epochSeconds) const override
Return the DST delta offset at epochSeconds.
Definition: BasicZoneProcessor.h:225
ace_time::basic::TransitionTemplate::log
void log() const
Used only for debugging.
Definition: BasicZoneProcessor.h:113
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, uint8_t fold=0)
Factory method using separated date, time, and UTC offset fields.
Definition: OffsetDateTime.h:57
ace_time::LocalDateTime
Class that holds the date-time as the components (year, month, day, hour, minute, second) without reg...
Definition: LocalDateTime.h:31
ace_time::basic::TransitionTemplate::rule
ZRB rule
The Zone transition rule that matched for the the given year.
Definition: BasicZoneProcessor.h:78
ace_time::BasicZoneProcessorTemplate::getUtcOffset
TimeOffset getUtcOffset(acetime_t epochSeconds) const override
Return the total UTC offset at epochSeconds, including DST offset.
Definition: BasicZoneProcessor.h:218
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:302
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:88
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:330
ace_time::BasicZoneProcessorTemplate::log
void log() const
Used only for debugging.
Definition: BasicZoneProcessor.h:346
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::BasicZoneProcessorTemplate::setBrokerFactory
void setBrokerFactory(const BF *brokerFactory)
Set the broker factory at runtime.
Definition: BasicZoneProcessor.h:367
BasicBrokers.h
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:91
ace_time::BasicZoneProcessorTemplate
An implementation of ZoneProcessor that supports a subset of the zones containing in the TZ Database.
Definition: BasicZoneProcessor.h:204
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:175
ace_time::basic::BrokerFactory
A factory that creates a basic::ZoneInfoBroker.
Definition: BasicBrokers.h:467
ace_time::basic::TransitionTemplate::abbrev
char abbrev[internal::kAbbrevSize]
The calculated effective time zone abbreviation, e.g.
Definition: BasicZoneProcessor.h:110
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:266
ace_time::basic::TransitionTemplate::yearTiny
int8_t yearTiny
Year of the Transition.
Definition: BasicZoneProcessor.h:94
ace_time::BasicZoneProcessorTemplate::getAbbrev
const char * getAbbrev(acetime_t epochSeconds) const override
Return the time zone abbreviation at epochSeconds.
Definition: BasicZoneProcessor.h:232
ace_time::BasicZoneProcessorTemplate::getZoneId
uint32_t getZoneId(bool followLink=false) const override
Return the unique stable zoneId.
Definition: BasicZoneProcessor.h:211
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:67
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:51
ace_time::OffsetDateTime::forError
static OffsetDateTime forError()
Factory method that returns an instance whose isError() is true.
Definition: OffsetDateTime.h:179
ace_time::LocalDateTime::localDate
const LocalDate & localDate() const
Return the LocalDate.
Definition: LocalDateTime.h:250
ace_time::TimeOffset::forMinutes
static TimeOffset forMinutes(int16_t minutes)
Create TimeOffset from minutes from 00:00.
Definition: TimeOffset.h:83
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:341
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:274
ace_time::BasicZoneProcessorTemplate::Transition
basic::TransitionTemplate< ZIB, ZEB, ZPB, ZRB > Transition
Exposed only for testing purposes.
Definition: BasicZoneProcessor.h:207
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:381
ace_time::basic::TransitionTemplate::month
uint8_t month
Month of the transition.
Definition: BasicZoneProcessor.h:101
ace_time::BasicZoneProcessorTemplate::getOffsetDateTime
OffsetDateTime getOffsetDateTime(acetime_t epochSeconds) const override
Return the best estimate of the OffsetDateTime at the given epochSeconds for the timezone of the curr...
Definition: BasicZoneProcessor.h:310
ace_time::basic::TransitionTemplate::startEpochSeconds
acetime_t startEpochSeconds
The calculated transition time of the given rule.
Definition: BasicZoneProcessor.h:81
ace_time::TimeOffset::kErrorMinutes
static const int16_t kErrorMinutes
Sentinel value that represents an error.
Definition: TimeOffset.h:59
ace_time::BasicZoneProcessorTemplate::printShortNameTo
void printShortNameTo(Print &printer, bool followLink=false) const override
Print a short human-readable identifier (e.g.
Definition: BasicZoneProcessor.h:322
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:61
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:1022
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:1031
ace_time::BasicZoneProcessorTemplate::isLink
bool isLink() const override
Return true if timezone is a Link entry pointing to a Zone entry.
Definition: BasicZoneProcessor.h:209
ace_time::BasicZoneProcessorTemplate::printNameTo
void printNameTo(Print &printer, bool followLink=false) const override
Print a human-readable identifier (e.g.
Definition: BasicZoneProcessor.h:315
ace_time::OffsetDateTime::forEpochSeconds
static OffsetDateTime forEpochSeconds(acetime_t epochSeconds, TimeOffset timeOffset, uint8_t fold=0)
Factory method.
Definition: OffsetDateTime.h:75