AceTime  1.11.1
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 (mZoneInfoBroker.equals(zoneKey)) return;
332 
333  mZoneInfoBroker = mBrokerFactory->createZoneInfoBroker(zoneKey);
334  mYearTiny = LocalDate::kInvalidYearTiny;
335  mIsFilled = false;
336  mNumTransitions = 0;
337  }
338 
339  bool equalsZoneKey(uintptr_t zoneKey) const override {
340  return mZoneInfoBroker.equals(zoneKey);
341  }
342 
344  void log() const {
345  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
346  if (!mIsFilled) {
347  logging::printf("*not initialized*\n");
348  return;
349  }
350  logging::printf("mYearTiny: %d\n", mYearTiny);
351  logging::printf("mNumTransitions: %d\n", mNumTransitions);
352  for (int i = 0; i < mNumTransitions; i++) {
353  logging::printf("mT[%d]=", i);
354  mTransitions[i].log();
355  }
356  }
357  }
358 
359  void setBrokerFactory(const BF* brokerFactory) {
360  mBrokerFactory = brokerFactory;
361  }
362 
363  protected:
364 
372  uint8_t type,
373  const BF* brokerFactory,
374  uintptr_t zoneKey
375  ) :
376  ZoneProcessor(type),
377  mBrokerFactory(brokerFactory)
378  {
379  setZoneKey(zoneKey);
380  }
381 
382  private:
383  friend class ::BasicZoneProcessorTest_priorYearOfRule;
384  friend class ::BasicZoneProcessorTest_compareRulesBeforeYear;
385  friend class ::BasicZoneProcessorTest_findLatestPriorRule;
386  friend class ::BasicZoneProcessorTest_findZoneEra;
387  friend class ::BasicZoneProcessorTest_init_primitives;
388  friend class ::BasicZoneProcessorTest_init;
389  friend class ::BasicZoneProcessorTest_setZoneKey;
390  friend class ::BasicZoneProcessorTest_createAbbreviation;
391  friend class ::BasicZoneProcessorTest_calcRuleOffsetMinutes;
392 
403  static const uint8_t kMaxCacheEntries = 5;
404 
410  static const acetime_t kMinEpochSeconds = INT32_MIN + 1;
411 
412  // Disable copy constructor and assignment operator.
415  delete;
416 
417  bool equals(const ZoneProcessor& other) const override {
418  return mZoneInfoBroker.equals(
419  ((const BasicZoneProcessorTemplate&) other).mZoneInfoBroker);
420  }
421 
423  const Transition* getTransition(acetime_t epochSeconds) const {
424  LocalDate ld = LocalDate::forEpochSeconds(epochSeconds);
425  bool success = init(ld);
426  return (success) ? findMatch(epochSeconds) : nullptr;
427  }
428 
457  bool init(const LocalDate& ld) const {
458  int8_t yearTiny = ld.yearTiny();
459  if (ld.month() == 1 && ld.day() == 1) {
460  yearTiny--;
461  }
462  if (isFilled(yearTiny)) {
463  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
464  logging::printf("init(): %d (using cached %d)\n",
465  ld.yearTiny(), yearTiny);
466  }
467  return true;
468  } else {
469  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
470  logging::printf("init(): %d (new year %d)\n",
471  ld.yearTiny(), yearTiny);
472  }
473  }
474 
475  mYearTiny = yearTiny;
476  mNumTransitions = 0; // clear cache
477 
478  if (yearTiny + LocalDate::kEpochYear
479  < mZoneInfoBroker.zoneContext()->startYear - 1
480  || mZoneInfoBroker.zoneContext()->untilYear
481  < yearTiny + LocalDate::kEpochYear) {
482  return false;
483  }
484 
485  ZEB priorEra = addTransitionPriorToYear(yearTiny);
486  ZEB currentEra = addTransitionsForYear(yearTiny, priorEra);
487  addTransitionAfterYear(yearTiny, currentEra);
488  calcTransitions();
489  calcAbbreviations();
490 
491  mIsFilled = true;
492 
493  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
494  log();
495  }
496 
497  return true;
498  }
499 
501  bool isFilled(int8_t yearTiny) const {
502  return mIsFilled && (yearTiny == mYearTiny);
503  }
504 
511  ZEB addTransitionPriorToYear(int8_t yearTiny) const {
512  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
513  logging::printf("addTransitionPriorToYear(): %d\n", yearTiny);
514  }
515 
516  const ZEB era = findZoneEra(mZoneInfoBroker, yearTiny - 1);
517 
518  // If the prior ZoneEra has a ZonePolicy), then find the latest rule
519  // within the ZoneEra. Otherwise, add a Transition using a rule==nullptr.
520  ZRB latest = findLatestPriorRule(
521  era.zonePolicy(), yearTiny);
522  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
523  logging::printf("addTransitionsPriorToYear(): adding latest prior ");
524  if (latest.isNull()) {
525  logging::printf("ZR(null)\n");
526  } else {
527  logging::printf("ZR[%d,%d]\n",
528  latest.fromYearTiny(), latest.toYearTiny());
529  }
530  }
531  addTransition(yearTiny - 1, 0 /*month*/, era, latest);
532 
533  return era;
534  }
535 
541  static ZRB findLatestPriorRule(const ZPB& zonePolicy, int8_t yearTiny) {
542  ZRB latest;
543  if (zonePolicy.isNull()) return latest;
544 
545  uint8_t numRules = zonePolicy.numRules();
546  for (uint8_t i = 0; i < numRules; i++) {
547  const ZRB rule = zonePolicy.rule(i);
548  // Check if rule is effective prior to the given year
549  if (rule.fromYearTiny() < yearTiny) {
550  if ((latest.isNull()) ||
551  compareRulesBeforeYear(yearTiny, rule, latest) > 0) {
552  latest = rule;
553  }
554  }
555  }
556 
557  return latest;
558  }
559 
561  static int8_t compareRulesBeforeYear(int8_t yearTiny,
562  const ZRB& a, const ZRB& b) {
563  return basic::compareYearMonth(
564  priorYearOfRule(yearTiny, a), a.inMonth(),
565  priorYearOfRule(yearTiny, b), b.inMonth());
566  }
567 
576  static int8_t priorYearOfRule(int8_t yearTiny, const ZRB& rule) {
577  if (rule.toYearTiny() < yearTiny) {
578  return rule.toYearTiny();
579  }
580  return yearTiny - 1;
581  }
582 
587  ZEB addTransitionsForYear(int8_t yearTiny, const ZEB& priorEra) const {
588  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
589  logging::printf("addTransitionsForYear(): %d\n", yearTiny);
590  }
591 
592  const ZEB era = findZoneEra(mZoneInfoBroker, yearTiny);
593 
594  // If the ZonePolicy has no rules, then add a Transition which takes
595  // effect at the start time of the current year.
596  const ZPB zonePolicy = era.zonePolicy();
597  if (zonePolicy.isNull()) {
598  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
599  logging::printf("addTransitionsForYear(): adding ZE.untilY=%d\n",
600  era.untilYearTiny());
601  }
602  addTransition(yearTiny, 0 /*month*/, era, ZRB());
603  return era;
604  }
605 
606  if (! era.equals(priorEra)) {
607  // The ZoneEra has changed, so we need to find the Rule in effect at
608  // the start of the current year of the current ZoneEra. This may be a
609  // rule far in the past, but shift the rule forward to {year, 1, 1}.
610  ZRB latestPrior = findLatestPriorRule(
611  era.zonePolicy(), yearTiny);
612  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
613  logging::printf(
614  "addTransitionsForYear(): adding latest prior ");
615  if (latestPrior.isNull()) {
616  logging::printf("ZR(null)\n");
617  } else {
618  logging::printf("ZR[%d,%d]\n",
619  latestPrior.fromYearTiny(), latestPrior.toYearTiny());
620  }
621  }
622  addTransition(yearTiny, 1 /*month*/, era, latestPrior);
623  }
624 
625  // Find all directly matching transitions (i.e. the [from, to] overlap
626  // with the current year) and add them to mTransitions, in sorted order
627  // according to the ZoneRule::inMonth field.
628  uint8_t numRules = zonePolicy.numRules();
629  for (uint8_t i = 0; i < numRules; i++) {
630  const ZRB rule = zonePolicy.rule(i);
631  if ((rule.fromYearTiny() <= yearTiny) &&
632  (yearTiny <= rule.toYearTiny())) {
633  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
634  logging::printf(
635  "addTransitionsForYear(): adding rule ");
636  if (rule.isNull()) {
637  logging::printf("ZR(null)\n");
638  } else {
639  logging::printf("ZR[%d,%d]\n",
640  rule.fromYearTiny(), rule.toYearTiny());
641  }
642  }
643  addTransition(yearTiny, 0 /*month*/, era, rule);
644  }
645  }
646 
647  return era;
648  }
649 
651  void addTransitionAfterYear(int8_t yearTiny, const ZEB& currentEra) const {
652  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
653  logging::printf("addTransitionAfterYear(): %d\n", yearTiny);
654  }
655 
656  const ZEB eraAfter = findZoneEra(mZoneInfoBroker, yearTiny + 1);
657 
658  // If the current era is the same as the following year, then we'll just
659  // assume that the latest ZoneRule carries over to Jan 1st of the next
660  // year. tzcompiler.py guarantees no ZoneRule occurs on Jan 1st.
661  if (currentEra.equals(eraAfter)) {
662  return;
663  }
664 
665  // If the ZoneEra did change, find the latest transition prior to
666  // {yearTiny + 1, 1, 1}, then shift that Transition to Jan 1st of the
667  // following year.
668  ZRB latest = findLatestPriorRule(eraAfter.zonePolicy(), yearTiny + 1);
669  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
670  logging::printf(
671  "addTransitionsAfterYear(): adding latest prior ");
672  if (latest.isNull()) {
673  logging::printf("ZR(null)\n");
674  } else {
675  logging::printf("ZR[%d,%d]\n",
676  latest.fromYearTiny(), latest.toYearTiny());
677  }
678  }
679  addTransition(yearTiny + 1, 1 /*month*/, eraAfter, latest);
680  }
681 
705  void addTransition(int8_t yearTiny, uint8_t month, const ZEB& era,
706  const ZRB& rule) const {
707 
708  // If a zone needs more transitions than kMaxCacheEntries, the check below
709  // will cause the DST transition information to be inaccurate, and it is
710  // highly likely that this situation would be caught in the
711  // AceTimeValidation tests. Since these integration tests pass, I feel
712  // confident that those zones which need more than kMaxCacheEntries are
713  // already filtered out by tzcompiler.py.
714  //
715  // Ideally, the tzcompiler.py script would explicitly remove those zones
716  // which need more than kMaxCacheEntries Transitions. But this would
717  // require a Python version of the BasicZoneProcessor, and unfortunately,
718  // zone_processor.py implements only the ExtendedZoneProcessor algorithm
719  // An early version of zone_processor.py may have implemented something
720  // close to BasicZoneProcessor, and it may be available in the git
721  // history. But it seems like too much work right now to try to dig that
722  // out, just to implement the explicit check for kMaxCacheEntries. It
723  // would mean maintaining another version of zone_processor.py.
724  if (mNumTransitions >= kMaxCacheEntries) return;
725 
726  // insert new element at the end of the list
727  mTransitions[mNumTransitions] = createTransition(
728  yearTiny, month, era, rule);
729  mNumTransitions++;
730 
731  // perform an insertion sort based on ZoneRule.inMonth()
732  for (uint8_t i = mNumTransitions - 1; i > 0; i--) {
733  Transition& left = mTransitions[i - 1];
734  Transition& right = mTransitions[i];
735  // assume only 1 rule per month
736  if (basic::compareYearMonth(left.yearTiny, left.month,
737  right.yearTiny, right.month) > 0) {
738  Transition tmp = left;
739  left = right;
740  right = tmp;
741  }
742  }
743  }
744 
750  static Transition createTransition(int8_t yearTiny, uint8_t month,
751  const ZEB& era, const ZRB& rule) {
752  int16_t deltaMinutes;
753  char letter;
754  uint8_t mon;
755  if (rule.isNull()) {
756  mon = 1; // RULES is either '-' or 'hh:mm' so takes effect in Jan
757  deltaMinutes = era.deltaMinutes();
758  letter = '\0';
759  } else {
760  mon = rule.inMonth();
761  deltaMinutes = rule.deltaMinutes();
762  letter = rule.letter();
763  }
764  // Clobber the month if specified.
765  if (month != 0) {
766  mon = month;
767  }
768  int16_t offsetMinutes = era.offsetMinutes() + deltaMinutes;
769 
770  return {
771  era,
772  rule,
773  0 /*epochSeconds*/,
774  offsetMinutes,
775  deltaMinutes,
776  yearTiny,
777  mon,
778  {letter} /*abbrev*/
779  };
780  }
781 
787  static ZEB findZoneEra(const ZIB& info, int8_t yearTiny) {
788  for (uint8_t i = 0; i < info.numEras(); i++) {
789  const ZEB era = info.era(i);
790  if (yearTiny < era.untilYearTiny()) return era;
791  }
792  // Return the last ZoneEra if we run off the end.
793  return info.era(info.numEras() - 1);
794  }
795 
803  void calcTransitions() const {
804  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
805  logging::printf("calcTransitions():\n");
806  }
807 
808  // Set the initial startEpochSeconds to be -Infinity
809  Transition* prevTransition = &mTransitions[0];
810  prevTransition->startEpochSeconds = kMinEpochSeconds;
811 
812  for (uint8_t i = 1; i < mNumTransitions; i++) {
813  Transition& transition = mTransitions[i];
814  const int16_t year = transition.yearTiny + LocalDate::kEpochYear;
815 
816  if (transition.rule.isNull()) {
817  // If the transition is simple (has no named rule), then the
818  // ZoneEra applies for the entire year (since BasicZoneProcessor
819  // supports only whole year in the UNTIL field). The whole year UNTIL
820  // field has an implied 'w' suffix on 00:00, we don't need to call
821  // calcRuleOffsetMinutes() with a 'w', we can just use the previous
822  // transition's offset to calculate the startDateTime of this
823  // transition.
824  //
825  // Also, when transition.rule == nullptr, the mNumTransitions should
826  // be 1, since only a single transition is added by
827  // addTransitionsForYear().
828  const int16_t prevOffsetMinutes = prevTransition->offsetMinutes;
829  OffsetDateTime startDateTime = OffsetDateTime::forComponents(
830  year, 1, 1, 0, 0, 0,
831  TimeOffset::forMinutes(prevOffsetMinutes));
832  transition.startEpochSeconds = startDateTime.toEpochSeconds();
833  } else {
834  // In this case, the transition points to a named ZonePolicy, which
835  // means that there could be multiple ZoneRules associated with the
836  // given year. For each transition, determine the startEpochSeconds,
837  // and the effective offset code.
838 
839  // Determine the start date of the rule.
840  const internal::MonthDay monthDay = internal::calcStartDayOfMonth(
841  year, transition.month, transition.rule.onDayOfWeek(),
842  transition.rule.onDayOfMonth());
843 
844  // Determine the offset of the 'atTimeSuffix'. The 'w' suffix
845  // requires the offset of the previous transition.
846  const int16_t prevOffsetMinutes = calcRuleOffsetMinutes(
847  prevTransition->offsetMinutes,
848  transition.era.offsetMinutes(),
849  transition.rule.atTimeSuffix());
850 
851  // startDateTime
852  const uint16_t minutes = transition.rule.atTimeMinutes();
853  const uint8_t atHour = minutes / 60;
854  const uint8_t atMinute = minutes % 60;
855  OffsetDateTime startDateTime = OffsetDateTime::forComponents(
856  year, monthDay.month, monthDay.day,
857  atHour, atMinute, 0 /*second*/,
858  TimeOffset::forMinutes(prevOffsetMinutes));
859  transition.startEpochSeconds = startDateTime.toEpochSeconds();
860  }
861 
862  prevTransition = &transition;
863  }
864  }
865 
872  static int16_t calcRuleOffsetMinutes(int16_t prevEffectiveOffsetMinutes,
873  int16_t currentBaseOffsetMinutes, uint8_t atSuffix) {
874  if (atSuffix == internal::ZoneContext::kSuffixW) {
875  return prevEffectiveOffsetMinutes;
876  } else if (atSuffix == internal::ZoneContext::kSuffixS) {
877  return currentBaseOffsetMinutes;
878  } else { // 'u', 'g' or 'z'
879  return 0;
880  }
881  }
882 
884  void calcAbbreviations() const {
885  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
886  logging::printf("calcAbbreviations():\n");
887  }
888 
889  for (uint8_t i = 0; i < mNumTransitions; i++) {
890  calcAbbreviation(&mTransitions[i]);
891  }
892  }
893 
895  static void calcAbbreviation(Transition* transition) {
896  createAbbreviation(
897  transition->abbrev,
898  internal::kAbbrevSize,
899  transition->era.format(),
900  transition->deltaMinutes,
901  transition->abbrev[0]);
902  }
903 
952  static void createAbbreviation(char* dest, uint8_t destSize,
953  const char* format, int16_t deltaMinutes, char letter) {
954  // Check if FORMAT contains a '%'.
955  if (strchr(format, '%') != nullptr) {
956  // Check if RULES column empty, therefore no 'letter'
957  if (letter == '\0') {
958  strncpy(dest, format, destSize - 1);
959  dest[destSize - 1] = '\0';
960  } else {
961  ace_common::copyReplaceChar(dest, destSize, format, '%',
962  letter == '-' ? '\0' : letter);
963  }
964  } else {
965  // Check if FORMAT contains a '/'.
966  const char* slashPos = strchr(format, '/');
967  if (slashPos != nullptr) {
968  if (deltaMinutes == 0) {
969  uint8_t headLength = (slashPos - format);
970  if (headLength >= destSize) headLength = destSize - 1;
971  memcpy(dest, format, headLength);
972  dest[headLength] = '\0';
973  } else {
974  uint8_t tailLength = strlen(slashPos+1);
975  if (tailLength >= destSize) tailLength = destSize - 1;
976  memcpy(dest, slashPos+1, tailLength);
977  dest[tailLength] = '\0';
978  }
979  } else {
980  // Just copy the FORMAT disregarding the deltaMinutes and letter.
981  strncpy(dest, format, destSize - 1);
982  dest[destSize - 1] = '\0';
983  }
984  }
985  }
986 
988  const Transition* findMatch(acetime_t epochSeconds) const {
989  const Transition* closestMatch = nullptr;
990  for (uint8_t i = 0; i < mNumTransitions; i++) {
991  const Transition* m = &mTransitions[i];
992  if (closestMatch == nullptr || m->startEpochSeconds <= epochSeconds) {
993  closestMatch = m;
994  }
995  }
996  return closestMatch;
997  }
998 
999  const BF* mBrokerFactory;
1000  ZIB mZoneInfoBroker;
1001 
1002  mutable int8_t mYearTiny = LocalDate::kInvalidYearTiny;
1003  mutable bool mIsFilled = false;
1004  mutable uint8_t mNumTransitions = 0;
1005  mutable Transition mTransitions[kMaxCacheEntries];
1006 };
1007 
1013  basic::BrokerFactory,
1014  basic::ZoneInfoBroker,
1015  basic::ZoneEraBroker,
1016  basic::ZonePolicyBroker,
1017  basic::ZoneRuleBroker> {
1018 
1019  public:
1021  static const uint8_t kTypeBasic = 3;
1022 
1023  explicit BasicZoneProcessor(const basic::ZoneInfo* zoneInfo = nullptr)
1025  basic::BrokerFactory,
1026  basic::ZoneInfoBroker,
1027  basic::ZoneEraBroker,
1028  basic::ZonePolicyBroker,
1029  basic::ZoneRuleBroker>(
1030  kTypeBasic, &mBrokerFactory, (uintptr_t) zoneInfo)
1031  {}
1032 
1033  private:
1034  basic::BrokerFactory mBrokerFactory;
1035 };
1036 
1037 } // namespace ace_time
1038 
1039 #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:344
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::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:339
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:371
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:1012
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:1021
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