AceTime  1.7.4
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
BasicZoneProcessor.h
1 /*
2  * MIT License
3  * Copyright (c) 2019 Brian T. Park
4  */
5 
6 #ifndef ACE_TIME_BASIC_ZONE_PROCESSOR_H
7 #define ACE_TIME_BASIC_ZONE_PROCESSOR_H
8 
9 #include <string.h> // strchr()
10 #include <stdint.h>
11 #include <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 #define ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG 0
22 
23 class BasicZoneProcessorTest_priorYearOfRule;
24 class BasicZoneProcessorTest_compareRulesBeforeYear;
25 class BasicZoneProcessorTest_findLatestPriorRule;
26 class BasicZoneProcessorTest_findZoneEra;
27 class BasicZoneProcessorTest_init_primitives;
28 class BasicZoneProcessorTest_init;
29 class BasicZoneProcessorTest_setZoneKey;
30 class BasicZoneProcessorTest_createAbbreviation;
31 class BasicZoneProcessorTest_calcRuleOffsetMinutes;
32 
33 class Print;
34 
35 namespace ace_time {
36 namespace basic {
37 
58 template <typename ZIB, typename ZEB, typename ZPB, typename ZRB>
65  ZEB era;
66 
76  ZRB rule;
77 
79  acetime_t startEpochSeconds;
80 
86  int16_t offsetMinutes;
87 
89  int16_t deltaMinutes;
90 
92  int8_t yearTiny;
93 
99  uint8_t month;
100 
108  char abbrev[internal::kAbbrevSize];
109 
111  void log() const {
112  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
113  logging::printf("(%d/%d)", yearTiny, month);
114  if (sizeof(acetime_t) == sizeof(int)) {
115  logging::printf("; stEps: %d", startEpochSeconds);
116  } else {
117  logging::printf("; stEps: %ld", startEpochSeconds);
118  }
119  logging::printf("; offMin: %d", offsetMinutes);
120  logging::printf("; abbrev: %s", abbrev);
121  if (! rule.isNull()) {
122  logging::printf("; r.fromYear: %d", rule.fromYearTiny());
123  logging::printf("; r.toYear: %d", rule.toYearTiny());
124  logging::printf("; r.inMonth: %d", rule.inMonth());
125  logging::printf("; r.onDayOfMonth: %d", rule.onDayOfMonth());
126  }
127  logging::printf("\n");
128  }
129  }
130 };
131 
133 inline int8_t compareYearMonth(int8_t aYear, uint8_t aMonth,
134  int8_t bYear, uint8_t bMonth) {
135  if (aYear < bYear) return -1;
136  if (aYear > bYear) return 1;
137  if (aMonth < bMonth) return -1;
138  if (aMonth > bMonth) return 1;
139  return 0;
140 }
141 
142 } // namespace basic
143 
201 template <typename BF, typename ZIB, typename ZEB, typename ZPB, typename ZRB>
203  public:
206 
207  uint32_t getZoneId() const override { return mZoneInfoBroker.zoneId(); }
208 
209  TimeOffset getUtcOffset(acetime_t epochSeconds) const override {
210  const Transition* transition = getTransition(epochSeconds);
211  int16_t minutes = (transition)
213  return TimeOffset::forMinutes(minutes);
214  }
215 
216  TimeOffset getDeltaOffset(acetime_t epochSeconds) const override {
217  const Transition* transition = getTransition(epochSeconds);
218  int16_t minutes = (transition)
219  ? transition->deltaMinutes : TimeOffset::kErrorMinutes;
220  return TimeOffset::forMinutes(minutes);
221  }
222 
223  const char* getAbbrev(acetime_t epochSeconds) const override {
224  const Transition* transition = getTransition(epochSeconds);
225  return (transition) ? transition->abbrev : "";
226  }
227 
257  OffsetDateTime getOffsetDateTime(const LocalDateTime& ldt) const override {
258  // Only a single local variable of OffsetDateTime used, to allow Return
259  // Value Optimization (and save 20 bytes of flash for WorldClock).
260  OffsetDateTime odt;
261  bool success = init(ldt.localDate());
262  if (success) {
263  // 0) Use the UTC epochSeconds to get intial guess of offset.
264  acetime_t epochSeconds0 = ldt.toEpochSeconds();
265  auto offset0 = getUtcOffset(epochSeconds0);
266 
267  // 1) Use offset0 to get the next epochSeconds and offset.
268  odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset0);
269  acetime_t epochSeconds1 = odt.toEpochSeconds();
270  auto offset1 = getUtcOffset(epochSeconds1);
271 
272  // 2) Use offset1 to get the next epochSeconds and offset.
273  odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset1);
274  acetime_t epochSeconds2 = odt.toEpochSeconds();
275  auto offset2 = getUtcOffset(epochSeconds2);
276 
277  // If offset1 and offset2 are equal, then we have an equilibrium
278  // and odt(1) must equal odt(2), so we can just return the last odt.
279  if (offset1 == offset2) {
280  // pass
281  } else {
282  // Pick the later epochSeconds and offset
283  acetime_t epochSeconds;
284  TimeOffset offset;
285  if (epochSeconds1 > epochSeconds2) {
286  epochSeconds = epochSeconds1;
287  offset = offset1;
288  } else {
289  epochSeconds = epochSeconds2;
290  offset = offset2;
291  }
292  odt = OffsetDateTime::forEpochSeconds(epochSeconds, offset);
293  }
294  } else {
295  odt = OffsetDateTime::forError();
296  }
297 
298  return odt;
299  }
300 
301  void printNameTo(Print& printer) const override {
302  mZoneInfoBroker.printNameTo(printer);
303  }
304 
305  void printShortNameTo(Print& printer) const override {
306  mZoneInfoBroker.printShortNameTo(printer);
307  }
308 
309  void setZoneKey(uintptr_t zoneKey) override {
310  if (mZoneInfoBroker.equals(zoneKey)) return;
311 
312  mZoneInfoBroker = mBrokerFactory->createZoneInfoBroker(zoneKey);
313  mYearTiny = LocalDate::kInvalidYearTiny;
314  mIsFilled = false;
315  mNumTransitions = 0;
316  }
317 
318  bool equalsZoneKey(uintptr_t zoneKey) const override {
319  return mZoneInfoBroker.equals(zoneKey);
320  }
321 
323  void log() const {
324  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
325  if (!mIsFilled) {
326  logging::printf("*not initialized*\n");
327  return;
328  }
329  logging::printf("mYearTiny: %d\n", mYearTiny);
330  logging::printf("mNumTransitions: %d\n", mNumTransitions);
331  for (int i = 0; i < mNumTransitions; i++) {
332  logging::printf("mT[%d]=", i);
333  mTransitions[i].log();
334  }
335  }
336  }
337 
338  void setBrokerFactory(const BF* brokerFactory) {
339  mBrokerFactory = brokerFactory;
340  }
341 
342  protected:
343 
351  uint8_t type,
352  const BF* brokerFactory,
353  uintptr_t zoneKey
354  ) :
355  ZoneProcessor(type),
356  mBrokerFactory(brokerFactory)
357  {
358  setZoneKey(zoneKey);
359  }
360 
361  private:
362  friend class ::BasicZoneProcessorTest_priorYearOfRule;
363  friend class ::BasicZoneProcessorTest_compareRulesBeforeYear;
364  friend class ::BasicZoneProcessorTest_findLatestPriorRule;
365  friend class ::BasicZoneProcessorTest_findZoneEra;
366  friend class ::BasicZoneProcessorTest_init_primitives;
367  friend class ::BasicZoneProcessorTest_init;
368  friend class ::BasicZoneProcessorTest_setZoneKey;
369  friend class ::BasicZoneProcessorTest_createAbbreviation;
370  friend class ::BasicZoneProcessorTest_calcRuleOffsetMinutes;
371 
382  static const uint8_t kMaxCacheEntries = 5;
383 
389  static const acetime_t kMinEpochSeconds = INT32_MIN + 1;
390 
391  // Disable copy constructor and assignment operator.
394  delete;
395 
396  bool equals(const ZoneProcessor& other) const override {
397  return mZoneInfoBroker.equals(
398  ((const BasicZoneProcessorTemplate&) other).mZoneInfoBroker);
399  }
400 
402  const Transition* getTransition(acetime_t epochSeconds) const {
403  LocalDate ld = LocalDate::forEpochSeconds(epochSeconds);
404  bool success = init(ld);
405  return (success) ? findMatch(epochSeconds) : nullptr;
406  }
407 
436  bool init(const LocalDate& ld) const {
437  int8_t yearTiny = ld.yearTiny();
438  if (ld.month() == 1 && ld.day() == 1) {
439  yearTiny--;
440  }
441  if (isFilled(yearTiny)) {
442  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
443  logging::printf("init(): %d (using cached %d)\n",
444  ld.yearTiny(), yearTiny);
445  }
446  return true;
447  } else {
448  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
449  logging::printf("init(): %d (new year %d)\n",
450  ld.yearTiny(), yearTiny);
451  }
452  }
453 
454  mYearTiny = yearTiny;
455  mNumTransitions = 0; // clear cache
456 
457  if (yearTiny + LocalDate::kEpochYear
458  < mZoneInfoBroker.zoneContext()->startYear - 1
459  || mZoneInfoBroker.zoneContext()->untilYear
460  < yearTiny + LocalDate::kEpochYear) {
461  return false;
462  }
463 
464  ZEB priorEra = addTransitionPriorToYear(yearTiny);
465  ZEB currentEra = addTransitionsForYear(yearTiny, priorEra);
466  addTransitionAfterYear(yearTiny, currentEra);
467  calcTransitions();
468  calcAbbreviations();
469 
470  mIsFilled = true;
471 
472  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
473  log();
474  }
475 
476  return true;
477  }
478 
480  bool isFilled(int8_t yearTiny) const {
481  return mIsFilled && (yearTiny == mYearTiny);
482  }
483 
490  ZEB addTransitionPriorToYear(int8_t yearTiny) const {
491  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
492  logging::printf("addTransitionPriorToYear(): %d\n", yearTiny);
493  }
494 
495  const ZEB era = findZoneEra(mZoneInfoBroker, yearTiny - 1);
496 
497  // If the prior ZoneEra has a ZonePolicy), then find the latest rule
498  // within the ZoneEra. Otherwise, add a Transition using a rule==nullptr.
499  ZRB latest = findLatestPriorRule(
500  era.zonePolicy(), yearTiny);
501  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
502  logging::printf("addTransitionsPriorToYear(): adding latest prior ");
503  if (latest.isNull()) {
504  logging::printf("ZR(null)\n");
505  } else {
506  logging::printf("ZR[%d,%d]\n",
507  latest.fromYearTiny(), latest.toYearTiny());
508  }
509  }
510  addTransition(yearTiny - 1, 0 /*month*/, era, latest);
511 
512  return era;
513  }
514 
520  static ZRB findLatestPriorRule(const ZPB& zonePolicy, int8_t yearTiny) {
521  ZRB latest;
522  if (zonePolicy.isNull()) return latest;
523 
524  uint8_t numRules = zonePolicy.numRules();
525  for (uint8_t i = 0; i < numRules; i++) {
526  const ZRB rule = zonePolicy.rule(i);
527  // Check if rule is effective prior to the given year
528  if (rule.fromYearTiny() < yearTiny) {
529  if ((latest.isNull()) ||
530  compareRulesBeforeYear(yearTiny, rule, latest) > 0) {
531  latest = rule;
532  }
533  }
534  }
535 
536  return latest;
537  }
538 
540  static int8_t compareRulesBeforeYear(int8_t yearTiny,
541  const ZRB& a, const ZRB& b) {
542  return basic::compareYearMonth(
543  priorYearOfRule(yearTiny, a), a.inMonth(),
544  priorYearOfRule(yearTiny, b), b.inMonth());
545  }
546 
555  static int8_t priorYearOfRule(int8_t yearTiny, const ZRB& rule) {
556  if (rule.toYearTiny() < yearTiny) {
557  return rule.toYearTiny();
558  }
559  return yearTiny - 1;
560  }
561 
566  ZEB addTransitionsForYear(int8_t yearTiny, const ZEB& priorEra) const {
567  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
568  logging::printf("addTransitionsForYear(): %d\n", yearTiny);
569  }
570 
571  const ZEB era = findZoneEra(mZoneInfoBroker, yearTiny);
572 
573  // If the ZonePolicy has no rules, then add a Transition which takes
574  // effect at the start time of the current year.
575  const ZPB zonePolicy = era.zonePolicy();
576  if (zonePolicy.isNull()) {
577  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
578  logging::printf("addTransitionsForYear(): adding ZE.untilY=%d\n",
579  era.untilYearTiny());
580  }
581  addTransition(yearTiny, 0 /*month*/, era, ZRB());
582  return era;
583  }
584 
585  if (! era.equals(priorEra)) {
586  // The ZoneEra has changed, so we need to find the Rule in effect at
587  // the start of the current year of the current ZoneEra. This may be a
588  // rule far in the past, but shift the rule forward to {year, 1, 1}.
589  ZRB latestPrior = findLatestPriorRule(
590  era.zonePolicy(), yearTiny);
591  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
592  logging::printf(
593  "addTransitionsForYear(): adding latest prior ");
594  if (latestPrior.isNull()) {
595  logging::printf("ZR(null)\n");
596  } else {
597  logging::printf("ZR[%d,%d]\n",
598  latestPrior.fromYearTiny(), latestPrior.toYearTiny());
599  }
600  }
601  addTransition(yearTiny, 1 /*month*/, era, latestPrior);
602  }
603 
604  // Find all directly matching transitions (i.e. the [from, to] overlap
605  // with the current year) and add them to mTransitions, in sorted order
606  // according to the ZoneRule::inMonth field.
607  uint8_t numRules = zonePolicy.numRules();
608  for (uint8_t i = 0; i < numRules; i++) {
609  const ZRB rule = zonePolicy.rule(i);
610  if ((rule.fromYearTiny() <= yearTiny) &&
611  (yearTiny <= rule.toYearTiny())) {
612  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
613  logging::printf(
614  "addTransitionsForYear(): adding rule ");
615  if (rule.isNull()) {
616  logging::printf("ZR(null)\n");
617  } else {
618  logging::printf("ZR[%d,%d]\n",
619  rule.fromYearTiny(), rule.toYearTiny());
620  }
621  }
622  addTransition(yearTiny, 0 /*month*/, era, rule);
623  }
624  }
625 
626  return era;
627  }
628 
630  void addTransitionAfterYear(int8_t yearTiny, const ZEB& currentEra) const {
631  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
632  logging::printf("addTransitionAfterYear(): %d\n", yearTiny);
633  }
634 
635  const ZEB eraAfter = findZoneEra(mZoneInfoBroker, yearTiny + 1);
636 
637  // If the current era is the same as the following year, then we'll just
638  // assume that the latest ZoneRule carries over to Jan 1st of the next
639  // year. tzcompiler.py guarantees no ZoneRule occurs on Jan 1st.
640  if (currentEra.equals(eraAfter)) {
641  return;
642  }
643 
644  // If the ZoneEra did change, find the latest transition prior to
645  // {yearTiny + 1, 1, 1}, then shift that Transition to Jan 1st of the
646  // following year.
647  ZRB latest = findLatestPriorRule(eraAfter.zonePolicy(), yearTiny + 1);
648  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
649  logging::printf(
650  "addTransitionsAfterYear(): adding latest prior ");
651  if (latest.isNull()) {
652  logging::printf("ZR(null)\n");
653  } else {
654  logging::printf("ZR[%d,%d]\n",
655  latest.fromYearTiny(), latest.toYearTiny());
656  }
657  }
658  addTransition(yearTiny + 1, 1 /*month*/, eraAfter, latest);
659  }
660 
684  void addTransition(int8_t yearTiny, uint8_t month, const ZEB& era,
685  const ZRB& rule) const {
686 
687  // If a zone needs more transitions than kMaxCacheEntries, the check below
688  // will cause the DST transition information to be inaccurate, and it is
689  // highly likely that this situation would be caught in the
690  // AceTimeValidation tests. Since these integration tests pass, I feel
691  // confident that those zones which need more than kMaxCacheEntries are
692  // already filtered out by tzcompiler.py.
693  //
694  // Ideally, the tzcompiler.py script would explicitly remove those zones
695  // which need more than kMaxCacheEntries Transitions. But this would
696  // require a Python version of the BasicZoneProcessor, and unfortunately,
697  // zone_specifier.py implements only the ExtendedZoneProcessor algorithm
698  // An early version of zone_specifier.py may have implemented something
699  // close to BasicZoneProcessor, and it may be available in the git
700  // history. But it seems like too much work right now to try to dig that
701  // out, just to implement the explicit check for kMaxCacheEntries. It
702  // would mean maintaining another version of zone_specifier.py.
703  if (mNumTransitions >= kMaxCacheEntries) return;
704 
705  // insert new element at the end of the list
706  mTransitions[mNumTransitions] = createTransition(
707  yearTiny, month, era, rule);
708  mNumTransitions++;
709 
710  // perform an insertion sort based on ZoneRule.inMonth()
711  for (uint8_t i = mNumTransitions - 1; i > 0; i--) {
712  Transition& left = mTransitions[i - 1];
713  Transition& right = mTransitions[i];
714  // assume only 1 rule per month
715  if (basic::compareYearMonth(left.yearTiny, left.month,
716  right.yearTiny, right.month) > 0) {
717  Transition tmp = left;
718  left = right;
719  right = tmp;
720  }
721  }
722  }
723 
729  static Transition createTransition(int8_t yearTiny, uint8_t month,
730  const ZEB& era, const ZRB& rule) {
731  int16_t deltaMinutes;
732  char letter;
733  uint8_t mon;
734  if (rule.isNull()) {
735  mon = 1; // RULES is either '-' or 'hh:mm' so takes effect in Jan
736  deltaMinutes = era.deltaMinutes();
737  letter = '\0';
738  } else {
739  mon = rule.inMonth();
740  deltaMinutes = rule.deltaMinutes();
741  letter = rule.letter();
742  }
743  // Clobber the month if specified.
744  if (month != 0) {
745  mon = month;
746  }
747  int16_t offsetMinutes = era.offsetMinutes() + deltaMinutes;
748 
749  return {
750  era,
751  rule,
752  0 /*epochSeconds*/,
753  offsetMinutes,
754  deltaMinutes,
755  yearTiny,
756  mon,
757  {letter} /*abbrev*/
758  };
759  }
760 
766  static ZEB findZoneEra(const ZIB& info, int8_t yearTiny) {
767  for (uint8_t i = 0; i < info.numEras(); i++) {
768  const ZEB era = info.era(i);
769  if (yearTiny < era.untilYearTiny()) return era;
770  }
771  // Return the last ZoneEra if we run off the end.
772  return info.era(info.numEras() - 1);
773  }
774 
782  void calcTransitions() const {
783  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
784  logging::printf("calcTransitions():\n");
785  }
786 
787  // Set the initial startEpochSeconds to be -Infinity
788  Transition* prevTransition = &mTransitions[0];
789  prevTransition->startEpochSeconds = kMinEpochSeconds;
790 
791  for (uint8_t i = 1; i < mNumTransitions; i++) {
792  Transition& transition = mTransitions[i];
793  const int16_t year = transition.yearTiny + LocalDate::kEpochYear;
794 
795  if (transition.rule.isNull()) {
796  // If the transition is simple (has no named rule), then the
797  // ZoneEra applies for the entire year (since BasicZoneProcessor
798  // supports only whole year in the UNTIL field). The whole year UNTIL
799  // field has an implied 'w' suffix on 00:00, we don't need to call
800  // calcRuleOffsetMinutes() with a 'w', we can just use the previous
801  // transition's offset to calculate the startDateTime of this
802  // transition.
803  //
804  // Also, when transition.rule == nullptr, the mNumTransitions should
805  // be 1, since only a single transition is added by
806  // addTransitionsForYear().
807  const int16_t prevOffsetMinutes = prevTransition->offsetMinutes;
808  OffsetDateTime startDateTime = OffsetDateTime::forComponents(
809  year, 1, 1, 0, 0, 0,
810  TimeOffset::forMinutes(prevOffsetMinutes));
811  transition.startEpochSeconds = startDateTime.toEpochSeconds();
812  } else {
813  // In this case, the transition points to a named ZonePolicy, which
814  // means that there could be multiple ZoneRules associated with the
815  // given year. For each transition, determine the startEpochSeconds,
816  // and the effective offset code.
817 
818  // Determine the start date of the rule.
819  const internal::MonthDay monthDay = internal::calcStartDayOfMonth(
820  year, transition.month, transition.rule.onDayOfWeek(),
821  transition.rule.onDayOfMonth());
822 
823  // Determine the offset of the 'atTimeSuffix'. The 'w' suffix
824  // requires the offset of the previous transition.
825  const int16_t prevOffsetMinutes = calcRuleOffsetMinutes(
826  prevTransition->offsetMinutes,
827  transition.era.offsetMinutes(),
828  transition.rule.atTimeSuffix());
829 
830  // startDateTime
831  const uint16_t minutes = transition.rule.atTimeMinutes();
832  const uint8_t atHour = minutes / 60;
833  const uint8_t atMinute = minutes % 60;
834  OffsetDateTime startDateTime = OffsetDateTime::forComponents(
835  year, monthDay.month, monthDay.day,
836  atHour, atMinute, 0 /*second*/,
837  TimeOffset::forMinutes(prevOffsetMinutes));
838  transition.startEpochSeconds = startDateTime.toEpochSeconds();
839  }
840 
841  prevTransition = &transition;
842  }
843  }
844 
851  static int16_t calcRuleOffsetMinutes(int16_t prevEffectiveOffsetMinutes,
852  int16_t currentBaseOffsetMinutes, uint8_t atSuffix) {
853  if (atSuffix == internal::ZoneContext::kSuffixW) {
854  return prevEffectiveOffsetMinutes;
855  } else if (atSuffix == internal::ZoneContext::kSuffixS) {
856  return currentBaseOffsetMinutes;
857  } else { // 'u', 'g' or 'z'
858  return 0;
859  }
860  }
861 
863  void calcAbbreviations() const {
864  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
865  logging::printf("calcAbbreviations():\n");
866  }
867 
868  for (uint8_t i = 0; i < mNumTransitions; i++) {
869  calcAbbreviation(&mTransitions[i]);
870  }
871  }
872 
874  static void calcAbbreviation(Transition* transition) {
875  createAbbreviation(
876  transition->abbrev,
877  internal::kAbbrevSize,
878  transition->era.format(),
879  transition->deltaMinutes,
880  transition->abbrev[0]);
881  }
882 
931  static void createAbbreviation(char* dest, uint8_t destSize,
932  const char* format, int16_t deltaMinutes, char letter) {
933  // Check if FORMAT contains a '%'.
934  if (strchr(format, '%') != nullptr) {
935  // Check if RULES column empty, therefore no 'letter'
936  if (letter == '\0') {
937  strncpy(dest, format, destSize - 1);
938  dest[destSize - 1] = '\0';
939  } else {
940  ace_common::copyReplaceChar(dest, destSize, format, '%',
941  letter == '-' ? '\0' : letter);
942  }
943  } else {
944  // Check if FORMAT contains a '/'.
945  const char* slashPos = strchr(format, '/');
946  if (slashPos != nullptr) {
947  if (deltaMinutes == 0) {
948  uint8_t headLength = (slashPos - format);
949  if (headLength >= destSize) headLength = destSize - 1;
950  memcpy(dest, format, headLength);
951  dest[headLength] = '\0';
952  } else {
953  uint8_t tailLength = strlen(slashPos+1);
954  if (tailLength >= destSize) tailLength = destSize - 1;
955  memcpy(dest, slashPos+1, tailLength);
956  dest[tailLength] = '\0';
957  }
958  } else {
959  // Just copy the FORMAT disregarding the deltaMinutes and letter.
960  strncpy(dest, format, destSize - 1);
961  dest[destSize - 1] = '\0';
962  }
963  }
964  }
965 
967  const Transition* findMatch(acetime_t epochSeconds) const {
968  const Transition* closestMatch = nullptr;
969  for (uint8_t i = 0; i < mNumTransitions; i++) {
970  const Transition* m = &mTransitions[i];
971  if (closestMatch == nullptr || m->startEpochSeconds <= epochSeconds) {
972  closestMatch = m;
973  }
974  }
975  return closestMatch;
976  }
977 
978  const BF* mBrokerFactory;
979  ZIB mZoneInfoBroker;
980 
981  mutable int8_t mYearTiny = LocalDate::kInvalidYearTiny;
982  mutable bool mIsFilled = false;
983  mutable uint8_t mNumTransitions = 0;
984  mutable Transition mTransitions[kMaxCacheEntries];
985 };
986 
992  basic::BrokerFactory,
993  basic::ZoneInfoBroker,
994  basic::ZoneEraBroker,
995  basic::ZonePolicyBroker,
996  basic::ZoneRuleBroker> {
997 
998  public:
1000  static const uint8_t kTypeBasic = 3;
1001 
1002  explicit BasicZoneProcessor(const basic::ZoneInfo* zoneInfo = nullptr)
1004  basic::BrokerFactory,
1005  basic::ZoneInfoBroker,
1006  basic::ZoneEraBroker,
1007  basic::ZonePolicyBroker,
1008  basic::ZoneRuleBroker>(
1009  kTypeBasic, &mBrokerFactory, (uintptr_t) zoneInfo)
1010  {}
1011 
1012  private:
1013  basic::BrokerFactory mBrokerFactory;
1014 };
1015 
1016 } // namespace ace_time
1017 
1018 #endif
ace_time::BasicZoneProcessorTemplate::getDeltaOffset
TimeOffset getDeltaOffset(acetime_t epochSeconds) const override
Return the DST delta offset at epochSeconds.
Definition: BasicZoneProcessor.h:216
ace_time::basic::TransitionTemplate::log
void log() const
Used only for debugging.
Definition: BasicZoneProcessor.h:111
ace_time::LocalDateTime
Class that holds the date-time as the components (year, month, day, hour, minute, second) without reg...
Definition: LocalDateTime.h:30
ace_time::basic::TransitionTemplate::rule
ZRB rule
The Zone transition rule that matched for the the given year.
Definition: BasicZoneProcessor.h:76
ace_time::BasicZoneProcessorTemplate::getUtcOffset
TimeOffset getUtcOffset(acetime_t epochSeconds) const override
Return the total UTC offset at epochSeconds, including DST offset.
Definition: BasicZoneProcessor.h:209
ace_time::OffsetDateTime::toEpochSeconds
acetime_t toEpochSeconds() const
Return seconds since AceTime epoch (2000-01-01 00:00:00Z), taking into account the offset zone.
Definition: OffsetDateTime.h:263
ace_time::basic::TransitionTemplate::offsetMinutes
int16_t offsetMinutes
The total effective UTC offset minutes at the start of transition, including DST offset.
Definition: BasicZoneProcessor.h:86
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:309
ace_time::BasicZoneProcessorTemplate::log
void log() const
Used only for debugging.
Definition: BasicZoneProcessor.h:323
ace_time::TimeOffset
A thin wrapper that represents a time offset from a reference point, usually 00:00 at UTC,...
Definition: TimeOffset.h:56
BasicBrokers.h
ace_time::OffsetDateTime::forEpochSeconds
static OffsetDateTime forEpochSeconds(acetime_t epochSeconds, TimeOffset timeOffset)
Factory method.
Definition: OffsetDateTime.h:71
ace_time::internal::ZoneContext::kSuffixS
static const uint8_t kSuffixS
Represents 's' or standard time.
Definition: ZoneContext.h:21
ace_time::basic::TransitionTemplate::deltaMinutes
int16_t deltaMinutes
The deltaMinutes from "standard time" at the start of transition.
Definition: BasicZoneProcessor.h:89
ace_time::BasicZoneProcessorTemplate
An implementation of ZoneProcessor that supports a subset of the zones containing in the TZ Database.
Definition: BasicZoneProcessor.h:202
ace_time::BasicZoneProcessorTemplate::printNameTo
void printNameTo(Print &printer) const override
Print a human-readable identifier (e.g.
Definition: BasicZoneProcessor.h:301
ace_time::LocalDate::forEpochSeconds
static LocalDate forEpochSeconds(acetime_t epochSeconds)
Factory method using the number of seconds since AceTime epoch of 2000-01-01.
Definition: LocalDate.h:157
ace_time::basic::BrokerFactory
A factory that creates a basic::ZoneInfoBroker.
Definition: BasicBrokers.h:444
ace_time::basic::TransitionTemplate::abbrev
char abbrev[internal::kAbbrevSize]
The calculated effective time zone abbreviation, e.g.
Definition: BasicZoneProcessor.h:108
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:257
ace_time::basic::TransitionTemplate::yearTiny
int8_t yearTiny
Year of the Transition.
Definition: BasicZoneProcessor.h:92
ace_time::BasicZoneProcessorTemplate::getAbbrev
const char * getAbbrev(acetime_t epochSeconds) const override
Return the time zone abbreviation at epochSeconds.
Definition: BasicZoneProcessor.h:223
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:65
ace_time::ZoneProcessor
Base interface for ZoneProcessor classes.
Definition: ZoneProcessor.h:41
ace_time::LocalDate::kInvalidYearTiny
static const int8_t kInvalidYearTiny
Sentinel yearTiny which indicates an error condition or sometimes a year that 'does not exist'.
Definition: LocalDate.h:45
ace_time::OffsetDateTime::forError
static OffsetDateTime forError()
Factory method that returns an instance whose isError() is true.
Definition: OffsetDateTime.h:146
ace_time::LocalDateTime::localDate
const LocalDate & localDate() const
Return the LocalDate.
Definition: LocalDateTime.h:221
ace_time::TimeOffset::forMinutes
static TimeOffset forMinutes(int16_t minutes)
Create TimeOffset from minutes from 00:00.
Definition: TimeOffset.h:83
ace_time::BasicZoneProcessorTemplate::printShortNameTo
void printShortNameTo(Print &printer) const override
Print a short human-readable identifier (e.g.
Definition: BasicZoneProcessor.h:305
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:318
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:245
ace_time::BasicZoneProcessorTemplate::Transition
basic::TransitionTemplate< ZIB, ZEB, ZPB, ZRB > Transition
Exposed only for testing purposes.
Definition: BasicZoneProcessor.h:205
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:350
ace_time::basic::TransitionTemplate::month
uint8_t month
Month of the transition.
Definition: BasicZoneProcessor.h:99
ace_time::basic::TransitionTemplate::startEpochSeconds
acetime_t startEpochSeconds
The calculated transition time of the given rule.
Definition: BasicZoneProcessor.h:79
ace_time::TimeOffset::kErrorMinutes
static const int16_t kErrorMinutes
Sentinel value that represents an error.
Definition: TimeOffset.h:59
ace_time::OffsetDateTime::forComponents
static OffsetDateTime forComponents(int16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second, TimeOffset timeOffset)
Factory method using separated date, time, and UTC offset fields.
Definition: OffsetDateTime.h:53
ace_time::basic::TransitionTemplate
Data structure that defines the start of a specific UTC offset as described by the matching ZoneEra a...
Definition: BasicZoneProcessor.h:59
ace_time::BasicZoneProcessorTemplate::getZoneId
uint32_t getZoneId() const override
Return the unique stable zoneId.
Definition: BasicZoneProcessor.h:207
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:991
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:1000