AceTime  1.7.5
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 #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  uint32_t getZoneId() const override { return mZoneInfoBroker.zoneId(); }
210 
211  TimeOffset getUtcOffset(acetime_t epochSeconds) const override {
212  const Transition* transition = getTransition(epochSeconds);
213  int16_t minutes = (transition)
215  return TimeOffset::forMinutes(minutes);
216  }
217 
218  TimeOffset getDeltaOffset(acetime_t epochSeconds) const override {
219  const Transition* transition = getTransition(epochSeconds);
220  int16_t minutes = (transition)
221  ? transition->deltaMinutes : TimeOffset::kErrorMinutes;
222  return TimeOffset::forMinutes(minutes);
223  }
224 
225  const char* getAbbrev(acetime_t epochSeconds) const override {
226  const Transition* transition = getTransition(epochSeconds);
227  return (transition) ? transition->abbrev : "";
228  }
229 
259  OffsetDateTime getOffsetDateTime(const LocalDateTime& ldt) const override {
260  // Only a single local variable of OffsetDateTime used, to allow Return
261  // Value Optimization (and save 20 bytes of flash for WorldClock).
262  OffsetDateTime odt;
263  bool success = init(ldt.localDate());
264  if (success) {
265  // 0) Use the UTC epochSeconds to get intial guess of offset.
266  acetime_t epochSeconds0 = ldt.toEpochSeconds();
267  auto offset0 = getUtcOffset(epochSeconds0);
268 
269  // 1) Use offset0 to get the next epochSeconds and offset.
270  odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset0);
271  acetime_t epochSeconds1 = odt.toEpochSeconds();
272  auto offset1 = getUtcOffset(epochSeconds1);
273 
274  // 2) Use offset1 to get the next epochSeconds and offset.
275  odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset1);
276  acetime_t epochSeconds2 = odt.toEpochSeconds();
277  auto offset2 = getUtcOffset(epochSeconds2);
278 
279  // If offset1 and offset2 are equal, then we have an equilibrium
280  // and odt(1) must equal odt(2), so we can just return the last odt.
281  if (offset1 == offset2) {
282  // pass
283  } else {
284  // Pick the later epochSeconds and offset
285  acetime_t epochSeconds;
286  TimeOffset offset;
287  if (epochSeconds1 > epochSeconds2) {
288  epochSeconds = epochSeconds1;
289  offset = offset1;
290  } else {
291  epochSeconds = epochSeconds2;
292  offset = offset2;
293  }
294  odt = OffsetDateTime::forEpochSeconds(epochSeconds, offset);
295  }
296  } else {
297  odt = OffsetDateTime::forError();
298  }
299 
300  return odt;
301  }
302 
303  void printNameTo(Print& printer) const override {
304  mZoneInfoBroker.printNameTo(printer);
305  }
306 
307  void printShortNameTo(Print& printer) const override {
308  mZoneInfoBroker.printShortNameTo(printer);
309  }
310 
311  void setZoneKey(uintptr_t zoneKey) override {
312  if (mZoneInfoBroker.equals(zoneKey)) return;
313 
314  mZoneInfoBroker = mBrokerFactory->createZoneInfoBroker(zoneKey);
315  mYearTiny = LocalDate::kInvalidYearTiny;
316  mIsFilled = false;
317  mNumTransitions = 0;
318  }
319 
320  bool equalsZoneKey(uintptr_t zoneKey) const override {
321  return mZoneInfoBroker.equals(zoneKey);
322  }
323 
325  void log() const {
326  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
327  if (!mIsFilled) {
328  logging::printf("*not initialized*\n");
329  return;
330  }
331  logging::printf("mYearTiny: %d\n", mYearTiny);
332  logging::printf("mNumTransitions: %d\n", mNumTransitions);
333  for (int i = 0; i < mNumTransitions; i++) {
334  logging::printf("mT[%d]=", i);
335  mTransitions[i].log();
336  }
337  }
338  }
339 
340  void setBrokerFactory(const BF* brokerFactory) {
341  mBrokerFactory = brokerFactory;
342  }
343 
344  protected:
345 
353  uint8_t type,
354  const BF* brokerFactory,
355  uintptr_t zoneKey
356  ) :
357  ZoneProcessor(type),
358  mBrokerFactory(brokerFactory)
359  {
360  setZoneKey(zoneKey);
361  }
362 
363  private:
364  friend class ::BasicZoneProcessorTest_priorYearOfRule;
365  friend class ::BasicZoneProcessorTest_compareRulesBeforeYear;
366  friend class ::BasicZoneProcessorTest_findLatestPriorRule;
367  friend class ::BasicZoneProcessorTest_findZoneEra;
368  friend class ::BasicZoneProcessorTest_init_primitives;
369  friend class ::BasicZoneProcessorTest_init;
370  friend class ::BasicZoneProcessorTest_setZoneKey;
371  friend class ::BasicZoneProcessorTest_createAbbreviation;
372  friend class ::BasicZoneProcessorTest_calcRuleOffsetMinutes;
373 
384  static const uint8_t kMaxCacheEntries = 5;
385 
391  static const acetime_t kMinEpochSeconds = INT32_MIN + 1;
392 
393  // Disable copy constructor and assignment operator.
396  delete;
397 
398  bool equals(const ZoneProcessor& other) const override {
399  return mZoneInfoBroker.equals(
400  ((const BasicZoneProcessorTemplate&) other).mZoneInfoBroker);
401  }
402 
404  const Transition* getTransition(acetime_t epochSeconds) const {
405  LocalDate ld = LocalDate::forEpochSeconds(epochSeconds);
406  bool success = init(ld);
407  return (success) ? findMatch(epochSeconds) : nullptr;
408  }
409 
438  bool init(const LocalDate& ld) const {
439  int8_t yearTiny = ld.yearTiny();
440  if (ld.month() == 1 && ld.day() == 1) {
441  yearTiny--;
442  }
443  if (isFilled(yearTiny)) {
444  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
445  logging::printf("init(): %d (using cached %d)\n",
446  ld.yearTiny(), yearTiny);
447  }
448  return true;
449  } else {
450  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
451  logging::printf("init(): %d (new year %d)\n",
452  ld.yearTiny(), yearTiny);
453  }
454  }
455 
456  mYearTiny = yearTiny;
457  mNumTransitions = 0; // clear cache
458 
459  if (yearTiny + LocalDate::kEpochYear
460  < mZoneInfoBroker.zoneContext()->startYear - 1
461  || mZoneInfoBroker.zoneContext()->untilYear
462  < yearTiny + LocalDate::kEpochYear) {
463  return false;
464  }
465 
466  ZEB priorEra = addTransitionPriorToYear(yearTiny);
467  ZEB currentEra = addTransitionsForYear(yearTiny, priorEra);
468  addTransitionAfterYear(yearTiny, currentEra);
469  calcTransitions();
470  calcAbbreviations();
471 
472  mIsFilled = true;
473 
474  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
475  log();
476  }
477 
478  return true;
479  }
480 
482  bool isFilled(int8_t yearTiny) const {
483  return mIsFilled && (yearTiny == mYearTiny);
484  }
485 
492  ZEB addTransitionPriorToYear(int8_t yearTiny) const {
493  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
494  logging::printf("addTransitionPriorToYear(): %d\n", yearTiny);
495  }
496 
497  const ZEB era = findZoneEra(mZoneInfoBroker, yearTiny - 1);
498 
499  // If the prior ZoneEra has a ZonePolicy), then find the latest rule
500  // within the ZoneEra. Otherwise, add a Transition using a rule==nullptr.
501  ZRB latest = findLatestPriorRule(
502  era.zonePolicy(), yearTiny);
503  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
504  logging::printf("addTransitionsPriorToYear(): adding latest prior ");
505  if (latest.isNull()) {
506  logging::printf("ZR(null)\n");
507  } else {
508  logging::printf("ZR[%d,%d]\n",
509  latest.fromYearTiny(), latest.toYearTiny());
510  }
511  }
512  addTransition(yearTiny - 1, 0 /*month*/, era, latest);
513 
514  return era;
515  }
516 
522  static ZRB findLatestPriorRule(const ZPB& zonePolicy, int8_t yearTiny) {
523  ZRB latest;
524  if (zonePolicy.isNull()) return latest;
525 
526  uint8_t numRules = zonePolicy.numRules();
527  for (uint8_t i = 0; i < numRules; i++) {
528  const ZRB rule = zonePolicy.rule(i);
529  // Check if rule is effective prior to the given year
530  if (rule.fromYearTiny() < yearTiny) {
531  if ((latest.isNull()) ||
532  compareRulesBeforeYear(yearTiny, rule, latest) > 0) {
533  latest = rule;
534  }
535  }
536  }
537 
538  return latest;
539  }
540 
542  static int8_t compareRulesBeforeYear(int8_t yearTiny,
543  const ZRB& a, const ZRB& b) {
544  return basic::compareYearMonth(
545  priorYearOfRule(yearTiny, a), a.inMonth(),
546  priorYearOfRule(yearTiny, b), b.inMonth());
547  }
548 
557  static int8_t priorYearOfRule(int8_t yearTiny, const ZRB& rule) {
558  if (rule.toYearTiny() < yearTiny) {
559  return rule.toYearTiny();
560  }
561  return yearTiny - 1;
562  }
563 
568  ZEB addTransitionsForYear(int8_t yearTiny, const ZEB& priorEra) const {
569  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
570  logging::printf("addTransitionsForYear(): %d\n", yearTiny);
571  }
572 
573  const ZEB era = findZoneEra(mZoneInfoBroker, yearTiny);
574 
575  // If the ZonePolicy has no rules, then add a Transition which takes
576  // effect at the start time of the current year.
577  const ZPB zonePolicy = era.zonePolicy();
578  if (zonePolicy.isNull()) {
579  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
580  logging::printf("addTransitionsForYear(): adding ZE.untilY=%d\n",
581  era.untilYearTiny());
582  }
583  addTransition(yearTiny, 0 /*month*/, era, ZRB());
584  return era;
585  }
586 
587  if (! era.equals(priorEra)) {
588  // The ZoneEra has changed, so we need to find the Rule in effect at
589  // the start of the current year of the current ZoneEra. This may be a
590  // rule far in the past, but shift the rule forward to {year, 1, 1}.
591  ZRB latestPrior = findLatestPriorRule(
592  era.zonePolicy(), yearTiny);
593  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
594  logging::printf(
595  "addTransitionsForYear(): adding latest prior ");
596  if (latestPrior.isNull()) {
597  logging::printf("ZR(null)\n");
598  } else {
599  logging::printf("ZR[%d,%d]\n",
600  latestPrior.fromYearTiny(), latestPrior.toYearTiny());
601  }
602  }
603  addTransition(yearTiny, 1 /*month*/, era, latestPrior);
604  }
605 
606  // Find all directly matching transitions (i.e. the [from, to] overlap
607  // with the current year) and add them to mTransitions, in sorted order
608  // according to the ZoneRule::inMonth field.
609  uint8_t numRules = zonePolicy.numRules();
610  for (uint8_t i = 0; i < numRules; i++) {
611  const ZRB rule = zonePolicy.rule(i);
612  if ((rule.fromYearTiny() <= yearTiny) &&
613  (yearTiny <= rule.toYearTiny())) {
614  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
615  logging::printf(
616  "addTransitionsForYear(): adding rule ");
617  if (rule.isNull()) {
618  logging::printf("ZR(null)\n");
619  } else {
620  logging::printf("ZR[%d,%d]\n",
621  rule.fromYearTiny(), rule.toYearTiny());
622  }
623  }
624  addTransition(yearTiny, 0 /*month*/, era, rule);
625  }
626  }
627 
628  return era;
629  }
630 
632  void addTransitionAfterYear(int8_t yearTiny, const ZEB& currentEra) const {
633  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
634  logging::printf("addTransitionAfterYear(): %d\n", yearTiny);
635  }
636 
637  const ZEB eraAfter = findZoneEra(mZoneInfoBroker, yearTiny + 1);
638 
639  // If the current era is the same as the following year, then we'll just
640  // assume that the latest ZoneRule carries over to Jan 1st of the next
641  // year. tzcompiler.py guarantees no ZoneRule occurs on Jan 1st.
642  if (currentEra.equals(eraAfter)) {
643  return;
644  }
645 
646  // If the ZoneEra did change, find the latest transition prior to
647  // {yearTiny + 1, 1, 1}, then shift that Transition to Jan 1st of the
648  // following year.
649  ZRB latest = findLatestPriorRule(eraAfter.zonePolicy(), yearTiny + 1);
650  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
651  logging::printf(
652  "addTransitionsAfterYear(): adding latest prior ");
653  if (latest.isNull()) {
654  logging::printf("ZR(null)\n");
655  } else {
656  logging::printf("ZR[%d,%d]\n",
657  latest.fromYearTiny(), latest.toYearTiny());
658  }
659  }
660  addTransition(yearTiny + 1, 1 /*month*/, eraAfter, latest);
661  }
662 
686  void addTransition(int8_t yearTiny, uint8_t month, const ZEB& era,
687  const ZRB& rule) const {
688 
689  // If a zone needs more transitions than kMaxCacheEntries, the check below
690  // will cause the DST transition information to be inaccurate, and it is
691  // highly likely that this situation would be caught in the
692  // AceTimeValidation tests. Since these integration tests pass, I feel
693  // confident that those zones which need more than kMaxCacheEntries are
694  // already filtered out by tzcompiler.py.
695  //
696  // Ideally, the tzcompiler.py script would explicitly remove those zones
697  // which need more than kMaxCacheEntries Transitions. But this would
698  // require a Python version of the BasicZoneProcessor, and unfortunately,
699  // zone_processor.py implements only the ExtendedZoneProcessor algorithm
700  // An early version of zone_processor.py may have implemented something
701  // close to BasicZoneProcessor, and it may be available in the git
702  // history. But it seems like too much work right now to try to dig that
703  // out, just to implement the explicit check for kMaxCacheEntries. It
704  // would mean maintaining another version of zone_processor.py.
705  if (mNumTransitions >= kMaxCacheEntries) return;
706 
707  // insert new element at the end of the list
708  mTransitions[mNumTransitions] = createTransition(
709  yearTiny, month, era, rule);
710  mNumTransitions++;
711 
712  // perform an insertion sort based on ZoneRule.inMonth()
713  for (uint8_t i = mNumTransitions - 1; i > 0; i--) {
714  Transition& left = mTransitions[i - 1];
715  Transition& right = mTransitions[i];
716  // assume only 1 rule per month
717  if (basic::compareYearMonth(left.yearTiny, left.month,
718  right.yearTiny, right.month) > 0) {
719  Transition tmp = left;
720  left = right;
721  right = tmp;
722  }
723  }
724  }
725 
731  static Transition createTransition(int8_t yearTiny, uint8_t month,
732  const ZEB& era, const ZRB& rule) {
733  int16_t deltaMinutes;
734  char letter;
735  uint8_t mon;
736  if (rule.isNull()) {
737  mon = 1; // RULES is either '-' or 'hh:mm' so takes effect in Jan
738  deltaMinutes = era.deltaMinutes();
739  letter = '\0';
740  } else {
741  mon = rule.inMonth();
742  deltaMinutes = rule.deltaMinutes();
743  letter = rule.letter();
744  }
745  // Clobber the month if specified.
746  if (month != 0) {
747  mon = month;
748  }
749  int16_t offsetMinutes = era.offsetMinutes() + deltaMinutes;
750 
751  return {
752  era,
753  rule,
754  0 /*epochSeconds*/,
755  offsetMinutes,
756  deltaMinutes,
757  yearTiny,
758  mon,
759  {letter} /*abbrev*/
760  };
761  }
762 
768  static ZEB findZoneEra(const ZIB& info, int8_t yearTiny) {
769  for (uint8_t i = 0; i < info.numEras(); i++) {
770  const ZEB era = info.era(i);
771  if (yearTiny < era.untilYearTiny()) return era;
772  }
773  // Return the last ZoneEra if we run off the end.
774  return info.era(info.numEras() - 1);
775  }
776 
784  void calcTransitions() const {
785  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
786  logging::printf("calcTransitions():\n");
787  }
788 
789  // Set the initial startEpochSeconds to be -Infinity
790  Transition* prevTransition = &mTransitions[0];
791  prevTransition->startEpochSeconds = kMinEpochSeconds;
792 
793  for (uint8_t i = 1; i < mNumTransitions; i++) {
794  Transition& transition = mTransitions[i];
795  const int16_t year = transition.yearTiny + LocalDate::kEpochYear;
796 
797  if (transition.rule.isNull()) {
798  // If the transition is simple (has no named rule), then the
799  // ZoneEra applies for the entire year (since BasicZoneProcessor
800  // supports only whole year in the UNTIL field). The whole year UNTIL
801  // field has an implied 'w' suffix on 00:00, we don't need to call
802  // calcRuleOffsetMinutes() with a 'w', we can just use the previous
803  // transition's offset to calculate the startDateTime of this
804  // transition.
805  //
806  // Also, when transition.rule == nullptr, the mNumTransitions should
807  // be 1, since only a single transition is added by
808  // addTransitionsForYear().
809  const int16_t prevOffsetMinutes = prevTransition->offsetMinutes;
810  OffsetDateTime startDateTime = OffsetDateTime::forComponents(
811  year, 1, 1, 0, 0, 0,
812  TimeOffset::forMinutes(prevOffsetMinutes));
813  transition.startEpochSeconds = startDateTime.toEpochSeconds();
814  } else {
815  // In this case, the transition points to a named ZonePolicy, which
816  // means that there could be multiple ZoneRules associated with the
817  // given year. For each transition, determine the startEpochSeconds,
818  // and the effective offset code.
819 
820  // Determine the start date of the rule.
821  const internal::MonthDay monthDay = internal::calcStartDayOfMonth(
822  year, transition.month, transition.rule.onDayOfWeek(),
823  transition.rule.onDayOfMonth());
824 
825  // Determine the offset of the 'atTimeSuffix'. The 'w' suffix
826  // requires the offset of the previous transition.
827  const int16_t prevOffsetMinutes = calcRuleOffsetMinutes(
828  prevTransition->offsetMinutes,
829  transition.era.offsetMinutes(),
830  transition.rule.atTimeSuffix());
831 
832  // startDateTime
833  const uint16_t minutes = transition.rule.atTimeMinutes();
834  const uint8_t atHour = minutes / 60;
835  const uint8_t atMinute = minutes % 60;
836  OffsetDateTime startDateTime = OffsetDateTime::forComponents(
837  year, monthDay.month, monthDay.day,
838  atHour, atMinute, 0 /*second*/,
839  TimeOffset::forMinutes(prevOffsetMinutes));
840  transition.startEpochSeconds = startDateTime.toEpochSeconds();
841  }
842 
843  prevTransition = &transition;
844  }
845  }
846 
853  static int16_t calcRuleOffsetMinutes(int16_t prevEffectiveOffsetMinutes,
854  int16_t currentBaseOffsetMinutes, uint8_t atSuffix) {
855  if (atSuffix == internal::ZoneContext::kSuffixW) {
856  return prevEffectiveOffsetMinutes;
857  } else if (atSuffix == internal::ZoneContext::kSuffixS) {
858  return currentBaseOffsetMinutes;
859  } else { // 'u', 'g' or 'z'
860  return 0;
861  }
862  }
863 
865  void calcAbbreviations() const {
866  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
867  logging::printf("calcAbbreviations():\n");
868  }
869 
870  for (uint8_t i = 0; i < mNumTransitions; i++) {
871  calcAbbreviation(&mTransitions[i]);
872  }
873  }
874 
876  static void calcAbbreviation(Transition* transition) {
877  createAbbreviation(
878  transition->abbrev,
879  internal::kAbbrevSize,
880  transition->era.format(),
881  transition->deltaMinutes,
882  transition->abbrev[0]);
883  }
884 
933  static void createAbbreviation(char* dest, uint8_t destSize,
934  const char* format, int16_t deltaMinutes, char letter) {
935  // Check if FORMAT contains a '%'.
936  if (strchr(format, '%') != nullptr) {
937  // Check if RULES column empty, therefore no 'letter'
938  if (letter == '\0') {
939  strncpy(dest, format, destSize - 1);
940  dest[destSize - 1] = '\0';
941  } else {
942  ace_common::copyReplaceChar(dest, destSize, format, '%',
943  letter == '-' ? '\0' : letter);
944  }
945  } else {
946  // Check if FORMAT contains a '/'.
947  const char* slashPos = strchr(format, '/');
948  if (slashPos != nullptr) {
949  if (deltaMinutes == 0) {
950  uint8_t headLength = (slashPos - format);
951  if (headLength >= destSize) headLength = destSize - 1;
952  memcpy(dest, format, headLength);
953  dest[headLength] = '\0';
954  } else {
955  uint8_t tailLength = strlen(slashPos+1);
956  if (tailLength >= destSize) tailLength = destSize - 1;
957  memcpy(dest, slashPos+1, tailLength);
958  dest[tailLength] = '\0';
959  }
960  } else {
961  // Just copy the FORMAT disregarding the deltaMinutes and letter.
962  strncpy(dest, format, destSize - 1);
963  dest[destSize - 1] = '\0';
964  }
965  }
966  }
967 
969  const Transition* findMatch(acetime_t epochSeconds) const {
970  const Transition* closestMatch = nullptr;
971  for (uint8_t i = 0; i < mNumTransitions; i++) {
972  const Transition* m = &mTransitions[i];
973  if (closestMatch == nullptr || m->startEpochSeconds <= epochSeconds) {
974  closestMatch = m;
975  }
976  }
977  return closestMatch;
978  }
979 
980  const BF* mBrokerFactory;
981  ZIB mZoneInfoBroker;
982 
983  mutable int8_t mYearTiny = LocalDate::kInvalidYearTiny;
984  mutable bool mIsFilled = false;
985  mutable uint8_t mNumTransitions = 0;
986  mutable Transition mTransitions[kMaxCacheEntries];
987 };
988 
994  basic::BrokerFactory,
995  basic::ZoneInfoBroker,
996  basic::ZoneEraBroker,
997  basic::ZonePolicyBroker,
998  basic::ZoneRuleBroker> {
999 
1000  public:
1002  static const uint8_t kTypeBasic = 3;
1003 
1004  explicit BasicZoneProcessor(const basic::ZoneInfo* zoneInfo = nullptr)
1006  basic::BrokerFactory,
1007  basic::ZoneInfoBroker,
1008  basic::ZoneEraBroker,
1009  basic::ZonePolicyBroker,
1010  basic::ZoneRuleBroker>(
1011  kTypeBasic, &mBrokerFactory, (uintptr_t) zoneInfo)
1012  {}
1013 
1014  private:
1015  basic::BrokerFactory mBrokerFactory;
1016 };
1017 
1018 } // namespace ace_time
1019 
1020 #endif
ace_time::BasicZoneProcessorTemplate::getDeltaOffset
TimeOffset getDeltaOffset(acetime_t epochSeconds) const override
Return the DST delta offset at epochSeconds.
Definition: BasicZoneProcessor.h:218
ace_time::basic::TransitionTemplate::log
void log() const
Used only for debugging.
Definition: BasicZoneProcessor.h:113
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: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:211
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: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:311
ace_time::BasicZoneProcessorTemplate::log
void log() const
Used only for debugging.
Definition: BasicZoneProcessor.h:325
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: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::BasicZoneProcessorTemplate::printNameTo
void printNameTo(Print &printer) const override
Print a human-readable identifier (e.g.
Definition: BasicZoneProcessor.h:303
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: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:259
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:225
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: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:307
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:320
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: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:352
ace_time::basic::TransitionTemplate::month
uint8_t month
Month of the transition.
Definition: BasicZoneProcessor.h:101
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::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:61
ace_time::BasicZoneProcessorTemplate::getZoneId
uint32_t getZoneId() const override
Return the unique stable zoneId.
Definition: BasicZoneProcessor.h:209
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:993
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:1002