AceTime  1.11.6
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
The classes provide a thin layer of indirection for accessing the zoneinfo files stored in the zonedb...
An implementation of ZoneProcessor that supports a subset of the zones containing in the TZ Database.
void setBrokerFactory(const BF *brokerFactory)
Set the broker factory at runtime.
TimeOffset getDeltaOffset(acetime_t epochSeconds) const override
Return the DST delta offset at epochSeconds.
bool equalsZoneKey(uintptr_t zoneKey) const override
Return true if ZoneProcessor is associated with the given opaque zoneKey.
uint32_t getZoneId(bool followLink=false) const override
Return the unique stable zoneId.
bool isLink() const override
Return true if timezone is a Link entry pointing to a Zone entry.
void printNameTo(Print &printer, bool followLink=false) const override
Print a human-readable identifier (e.g.
void printShortNameTo(Print &printer, bool followLink=false) const override
Print a short human-readable identifier (e.g.
OffsetDateTime getOffsetDateTime(acetime_t epochSeconds) const override
Return the best estimate of the OffsetDateTime at the given epochSeconds for the timezone of the curr...
void setZoneKey(uintptr_t zoneKey) override
Set the opaque zoneKey of this object to a new value, reseting any internally cached information.
TimeOffset getUtcOffset(acetime_t epochSeconds) const override
Return the total UTC offset at epochSeconds, including DST offset.
BasicZoneProcessorTemplate(uint8_t type, const BF *brokerFactory, uintptr_t zoneKey)
Constructor.
basic::TransitionTemplate< ZIB, ZEB, ZPB, ZRB > Transition
Exposed only for testing purposes.
const char * getAbbrev(acetime_t epochSeconds) const override
Return the time zone abbreviation at epochSeconds.
OffsetDateTime getOffsetDateTime(const LocalDateTime &ldt) const override
Return the best estimate of the OffsetDateTime at the given LocalDateTime for the timezone of the cur...
void log() const
Used only for debugging.
A specific implementation of BasicZoneProcessorTemplate that uses ZoneXxxBrokers which read from zone...
static const uint8_t kTypeBasic
Unique TimeZone type identifier for BasicZoneProcessor.
Class that holds the date-time as the components (year, month, day, hour, minute, second) without reg...
Definition: LocalDateTime.h:31
const LocalDate & localDate() const
Return the LocalDate.
acetime_t toEpochSeconds() const
Return seconds since AceTime epoch 2000-01-01 00:00:00Z, after assuming that the date and time compon...
static const int16_t kEpochYear
Base year of epoch.
Definition: LocalDate.h:39
static LocalDate forEpochSeconds(acetime_t epochSeconds)
Factory method using the number of seconds since AceTime epoch of 2000-01-01.
Definition: LocalDate.h:175
static const int8_t kInvalidYearTiny
Sentinel yearTiny which indicates an error condition or sometimes a year that 'does not exist'.
Definition: LocalDate.h:51
The date (year, month, day), time (hour, minute, second) and offset from UTC (timeOffset).
static OffsetDateTime forLocalDateTimeAndOffset(const LocalDateTime &localDateTime, TimeOffset timeOffset)
Factory method from LocalDateTime and TimeOffset.
acetime_t toEpochSeconds() const
Return seconds since AceTime epoch (2000-01-01 00:00:00Z), taking into account the offset zone.
static OffsetDateTime forError()
Factory method that returns an instance whose isError() is true.
static OffsetDateTime forEpochSeconds(acetime_t epochSeconds, TimeOffset timeOffset, uint8_t fold=0)
Factory method.
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.
A thin wrapper that represents a time offset from a reference point, usually 00:00 at UTC,...
Definition: TimeOffset.h:56
static const int16_t kErrorMinutes
Sentinel value that represents an error.
Definition: TimeOffset.h:59
static TimeOffset forMinutes(int16_t minutes)
Create TimeOffset from minutes from 00:00.
Definition: TimeOffset.h:83
Base interface for ZoneProcessor classes.
Definition: ZoneProcessor.h:41
A factory that creates a basic::ZoneInfoBroker.
Definition: BasicBrokers.h:467
Data structure that defines the start of a specific UTC offset as described by the matching ZoneEra a...
uint8_t month
Month of the transition.
acetime_t startEpochSeconds
The calculated transition time of the given rule.
ZRB rule
The Zone transition rule that matched for the the given year.
ZEB era
The ZoneEra that matched the given year.
void log() const
Used only for debugging.
int8_t yearTiny
Year of the Transition.
int16_t offsetMinutes
The total effective UTC offset minutes at the start of transition, including DST offset.
char abbrev[internal::kAbbrevSize]
The calculated effective time zone abbreviation, e.g.
int16_t deltaMinutes
The deltaMinutes from "standard time" at the start of transition.
static const uint8_t kSuffixW
Represents 'w' or wall time.
Definition: ZoneContext.h:18
static const uint8_t kSuffixS
Represents 's' or standard time.
Definition: ZoneContext.h:21