AceTime  2.2.0
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 "../zoneinfo/ZonePolicy.h"
13 #include "../zoneinfo/ZoneInfo.h"
14 #include "../zoneinfo/Brokers.h"
15 #include "common/common.h" // kAbbrevSize
16 #include "common/logging.h"
17 #include "TimeOffset.h"
18 #include "LocalDate.h"
19 #include "OffsetDateTime.h"
20 #include "ZoneProcessor.h"
21 
22 #ifndef ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG
23 #define ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG 0
24 #endif
25 
26 class BasicZoneProcessorTest_priorYearOfRule;
27 class BasicZoneProcessorTest_compareRulesBeforeYear;
28 class BasicZoneProcessorTest_findLatestPriorRule;
29 class BasicZoneProcessorTest_findZoneEra;
30 class BasicZoneProcessorTest_init_primitives;
31 class BasicZoneProcessorTest_initForLocalDate;
32 class BasicZoneProcessorTest_setZoneKey;
33 class BasicZoneProcessorTest_createAbbreviation;
34 class BasicZoneProcessorTest_calcRuleOffsetMinutes;
35 
36 class Print;
37 
38 namespace ace_time {
39 namespace basic {
40 
61 template <typename ZIB, typename ZEB, typename ZPB, typename ZRB>
68  ZEB era;
69 
79  ZRB rule;
80 
83 
89  int16_t offsetMinutes;
90 
92  int16_t deltaMinutes;
93 
95  int16_t year;
96 
102  uint8_t month;
103 
111  char abbrev[internal::kAbbrevSize];
112 
114  void log() const {
115  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
116  logging::printf("(%d/%d)", year, month);
117  if (sizeof(acetime_t) == sizeof(int)) {
118  logging::printf("; stEps: %d", startEpochSeconds);
119  } else {
120  logging::printf("; stEps: %ld", startEpochSeconds);
121  }
122  logging::printf("; offMin: %d", offsetMinutes);
123  logging::printf("; abbrev: %s", abbrev);
124  if (! rule.isNull()) {
125  logging::printf("; r.fromYear: %d", rule.fromYear());
126  logging::printf("; r.toYear: %d", rule.toYear());
127  logging::printf("; r.inMonth: %d", rule.inMonth());
128  logging::printf("; r.onDayOfMonth: %d", rule.onDayOfMonth());
129  }
130  logging::printf("\n");
131  }
132  }
133 };
134 
136 inline int8_t compareYearMonth(int16_t aYear, uint8_t aMonth,
137  int16_t bYear, uint8_t bMonth) {
138  if (aYear < bYear) return -1;
139  if (aYear > bYear) return 1;
140  if (aMonth < bMonth) return -1;
141  if (aMonth > bMonth) return 1;
142  return 0;
143 }
144 
145 } // namespace basic
146 
204 template <typename BF, typename ZIB, typename ZEB, typename ZPB, typename ZRB>
206  public:
209 
210  bool isLink() const override {
211  return ! mZoneInfoBroker.targetInfo().isNull();
212  }
213 
214  uint32_t getZoneId() const override {
215  return mZoneInfoBroker.zoneId();
216  }
217 
244  const LocalDateTime& ldt) const override {
245  FindResult result;
246  bool success = initForLocalDate(ldt.localDate());
247  if (!success) return result;
248 
249  // 0) Use the UTC epochSeconds to get intial guess of offset.
250  acetime_t epochSeconds0 = ldt.toEpochSeconds();
251  auto result0 = findByEpochSeconds(epochSeconds0);
252  if (result0.type == FindResult::kTypeNotFound) return result;
253  auto offset0 = TimeOffset::forMinutes(
254  result0.reqStdOffsetMinutes + result0.reqDstOffsetMinutes);
255 
256  // 1) Use offset0 to get the next epochSeconds and offset.
257  auto odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset0);
258  acetime_t epochSeconds1 = odt.toEpochSeconds();
259  auto result1 = findByEpochSeconds(epochSeconds1);
260  if (result1.type == FindResult::kTypeNotFound) return result;
261  auto offset1 = TimeOffset::forMinutes(
262  result1.reqStdOffsetMinutes + result1.reqDstOffsetMinutes);
263 
264  // 2) Use offset1 to get the next epochSeconds and offset.
265  odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset1);
266  acetime_t epochSeconds2 = odt.toEpochSeconds();
267  auto result2 = findByEpochSeconds(epochSeconds2);
268  if (result2.type == FindResult::kTypeNotFound) return result;
269  auto offset2 = TimeOffset::forMinutes(
270  result2.reqStdOffsetMinutes + result2.reqDstOffsetMinutes);
271 
272  // If offset1 and offset2 are equal, then we have an equilibrium
273  // and odt(1) must equal odt(2), so we can just return the last odt.
274  if (offset1 == offset2) {
275  // pass, pick any of result1 or result2
276  result = result1;
277  } else {
278  // Pick the req{Std,Dst}OffsetMinute that generates the later
279  // epochSeconds (the earlier transition), but convert into the
280  // LocalDateTime of the earlier epochSeconds (the later transition).
281  // TODO: This does not produce the desired result inside a DST gap.
282  if (epochSeconds1 > epochSeconds2) {
283  result = result1;
284  } else {
285  result = result2;
286  }
287  }
288 
289  return result;
290  }
291 
292  FindResult findByEpochSeconds(acetime_t epochSeconds) const override {
293  FindResult result;
294  const Transition* transition = getTransition(epochSeconds);
295  if (!transition) return result;
296 
297  result.dstOffsetMinutes = transition->deltaMinutes;
298  result.stdOffsetMinutes = transition->offsetMinutes
299  - transition->deltaMinutes;
300  result.reqStdOffsetMinutes = result.stdOffsetMinutes;
301  result.reqDstOffsetMinutes = result.dstOffsetMinutes;
302  result.type = FindResult::kTypeExact;
303  result.abbrev = transition->abbrev;
304 
305  return result;
306  }
307 
308  void printNameTo(Print& printer) const override {
309  mZoneInfoBroker.printNameTo(printer);
310  }
311 
312  void printShortNameTo(Print& printer) const override {
313  mZoneInfoBroker.printShortNameTo(printer);
314  }
315 
316  void printTargetNameTo(Print& printer) const override {
317  if (isLink()) {
318  mZoneInfoBroker.targetInfo().printNameTo(printer);
319  }
320  }
321 
322  void setZoneKey(uintptr_t zoneKey) override {
323  if (! mBrokerFactory) return;
324  if (mZoneInfoBroker.equals(zoneKey)) return;
325 
326  mZoneInfoBroker = mBrokerFactory->createZoneInfoBroker(zoneKey);
327  mYear = LocalDate::kInvalidYear;
328  mIsFilled = false;
329  mNumTransitions = 0;
330  }
331 
332  bool equalsZoneKey(uintptr_t zoneKey) const override {
333  return mZoneInfoBroker.equals(zoneKey);
334  }
335 
337  void log() const {
338  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
339  logging::printf("BasicZoneProcessor:\n");
340  logging::printf(" mYear: %d\n", mYear);
341  logging::printf(" mNumTransitions: %d\n", mNumTransitions);
342  for (int i = 0; i < mNumTransitions; i++) {
343  logging::printf(" mT[%d]=", i);
344  mTransitions[i].log();
345  }
346  }
347  }
348 
355  void setBrokerFactory(const BF* brokerFactory) {
356  mBrokerFactory = brokerFactory;
357  }
358 
359  protected:
360 
372  uint8_t type,
373  const BF* brokerFactory /*nullable*/,
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_initForLocalDate;
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  bool success = initForEpochSeconds(epochSeconds);
425  return (success) ? findMatch(epochSeconds) : nullptr;
426  }
427 
456  bool initForLocalDate(const LocalDate& ld) const {
457  int16_t year = ld.year();
458  if (ld.month() == 1 && ld.day() == 1) {
459  year--;
460  }
461  if (isFilled(year)) return true;
462  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
463  logging::printf("initForLocalDate(): %d (new year %d)\n",
464  ld.year(), year);
465  }
466 
467  mYear = year;
468  mNumTransitions = 0; // clear cache
469 
470  if (year < mZoneInfoBroker.zoneContext()->startYear - 1
471  || mZoneInfoBroker.zoneContext()->untilYear < year) {
472  return false;
473  }
474 
475  ZEB priorEra = addTransitionPriorToYear(year);
476  ZEB currentEra = addTransitionsForYear(year, priorEra);
477  addTransitionAfterYear(year, currentEra);
478  calcTransitions();
479  calcAbbreviations();
480 
481  mIsFilled = true;
482 
483  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
484  log();
485  }
486 
487  return true;
488  }
489 
495  bool initForEpochSeconds(acetime_t epochSeconds) const {
496  LocalDate ld = LocalDate::forEpochSeconds(epochSeconds);
497  return initForLocalDate(ld);
498  }
499 
506  ZEB addTransitionPriorToYear(int16_t year) const {
507  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
508  logging::printf("addTransitionPriorToYear(): %d\n", year);
509  }
510 
511  const ZEB era = findZoneEra(mZoneInfoBroker, year - 1);
512 
513  // If the prior ZoneEra has a ZonePolicy), then find the latest rule
514  // within the ZoneEra. Otherwise, add a Transition using a rule==nullptr.
515  ZRB latest = findLatestPriorRule(era.zonePolicy(), year);
516  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
517  logging::printf("addTransitionsPriorToYear(): adding latest prior ");
518  if (latest.isNull()) {
519  logging::printf("ZR(null)\n");
520  } else {
521  logging::printf("ZR[%d,%d]\n", latest.fromYear(), latest.toYear());
522  }
523  }
524  addTransition(year - 1, 0 /*month*/, era, latest);
525 
526  return era;
527  }
528 
534  static ZRB findLatestPriorRule(const ZPB& zonePolicy, int16_t year) {
535  ZRB latest;
536  if (zonePolicy.isNull()) return latest;
537 
538  uint8_t numRules = zonePolicy.numRules();
539  for (uint8_t i = 0; i < numRules; i++) {
540  const ZRB rule = zonePolicy.rule(i);
541  // Check if rule is effective prior to the given year
542  if (rule.fromYear() < year) {
543  if ((latest.isNull()) ||
544  compareRulesBeforeYear(year, rule, latest) > 0) {
545  latest = rule;
546  }
547  }
548  }
549 
550  return latest;
551  }
552 
554  static int8_t compareRulesBeforeYear(
555  int16_t year, const ZRB& a, const ZRB& b) {
556  return basic::compareYearMonth(
557  priorYearOfRule(year, a), a.inMonth(),
558  priorYearOfRule(year, b), b.inMonth());
559  }
560 
569  static int16_t priorYearOfRule(int16_t year, const ZRB& rule) {
570  if (rule.toYear() < year) {
571  return rule.toYear();
572  }
573  return year - 1;
574  }
575 
580  ZEB addTransitionsForYear(int16_t year, const ZEB& priorEra) const {
581  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
582  logging::printf("addTransitionsForYear(): %d\n", year);
583  }
584 
585  const ZEB era = findZoneEra(mZoneInfoBroker, year);
586 
587  // If the ZonePolicy has no rules, then add a Transition which takes
588  // effect at the start time of the current year.
589  const ZPB zonePolicy = era.zonePolicy();
590  if (zonePolicy.isNull()) {
591  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
592  logging::printf("addTransitionsForYear(): adding ZE.untilY=%d\n",
593  era.untilYear());
594  }
595  addTransition(year, 0 /*month*/, era, ZRB());
596  return era;
597  }
598 
599  if (! era.equals(priorEra)) {
600  // The ZoneEra has changed, so we need to find the Rule in effect at
601  // the start of the current year of the current ZoneEra. This may be a
602  // rule far in the past, but shift the rule forward to {year, 1, 1}.
603  ZRB latestPrior = findLatestPriorRule(era.zonePolicy(), year);
604  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
605  logging::printf(
606  "addTransitionsForYear(): adding latest prior ");
607  if (latestPrior.isNull()) {
608  logging::printf("ZR(null)\n");
609  } else {
610  logging::printf("ZR[%d,%d]\n",
611  latestPrior.fromYear(), latestPrior.toYear());
612  }
613  }
614  addTransition(year, 1 /*month*/, era, latestPrior);
615  }
616 
617  // Find all directly matching transitions (i.e. the [from, to] overlap
618  // with the current year) and add them to mTransitions, in sorted order
619  // according to the ZoneRule::inMonth field.
620  uint8_t numRules = zonePolicy.numRules();
621  for (uint8_t i = 0; i < numRules; i++) {
622  const ZRB rule = zonePolicy.rule(i);
623  if ((rule.fromYear() <= year) && (year <= rule.toYear())) {
624  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
625  logging::printf(
626  "addTransitionsForYear(): adding rule ");
627  if (rule.isNull()) {
628  logging::printf("ZR(null)\n");
629  } else {
630  logging::printf("ZR[%d,%d]\n", rule.fromYear(), rule.toYear());
631  }
632  }
633  addTransition(year, 0 /*month*/, era, rule);
634  }
635  }
636 
637  return era;
638  }
639 
641  void addTransitionAfterYear(int16_t year, const ZEB& currentEra) const {
642  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
643  logging::printf("addTransitionAfterYear(): %d\n", year);
644  }
645 
646  const ZEB eraAfter = findZoneEra(mZoneInfoBroker, year + 1);
647 
648  // If the current era is the same as the following year, then we'll just
649  // assume that the latest ZoneRule carries over to Jan 1st of the next
650  // year. tzcompiler.py guarantees no ZoneRule occurs on Jan 1st.
651  if (currentEra.equals(eraAfter)) {
652  return;
653  }
654 
655  // If the ZoneEra did change, find the latest transition prior to
656  // {year + 1, 1, 1}, then shift that Transition to Jan 1st of the
657  // following year.
658  ZRB latest = findLatestPriorRule(eraAfter.zonePolicy(), year + 1);
659  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
660  logging::printf(
661  "addTransitionsAfterYear(): adding latest prior ");
662  if (latest.isNull()) {
663  logging::printf("ZR(null)\n");
664  } else {
665  logging::printf("ZR[%d,%d]\n", latest.fromYear(), latest.toYear());
666  }
667  }
668  addTransition(year + 1, 1 /*month*/, eraAfter, latest);
669  }
670 
694  void addTransition(int16_t year, uint8_t month, const ZEB& era,
695  const ZRB& rule) const {
696 
697  // If a zone needs more transitions than kMaxCacheEntries, the check below
698  // will cause the DST transition information to be inaccurate, and it is
699  // highly likely that this situation would be caught in the
700  // AceTimeValidation tests. Since these integration tests pass, I feel
701  // confident that those zones which need more than kMaxCacheEntries are
702  // already filtered out by tzcompiler.py.
703  //
704  // Ideally, the tzcompiler.py script would explicitly remove those zones
705  // which need more than kMaxCacheEntries Transitions. But this would
706  // require a Python version of the BasicZoneProcessor, and unfortunately,
707  // zone_processor.py implements only the ExtendedZoneProcessor algorithm
708  // An early version of zone_processor.py may have implemented something
709  // close to BasicZoneProcessor, and it may be available in the git
710  // history. But it seems like too much work right now to try to dig that
711  // out, just to implement the explicit check for kMaxCacheEntries. It
712  // would mean maintaining another version of zone_processor.py.
713  if (mNumTransitions >= kMaxCacheEntries) return;
714 
715  // insert new element at the end of the list
716  mTransitions[mNumTransitions] = createTransition(year, month, era, rule);
717  mNumTransitions++;
718 
719  // perform an insertion sort based on ZoneRule.inMonth()
720  for (uint8_t i = mNumTransitions - 1; i > 0; i--) {
721  Transition& left = mTransitions[i - 1];
722  Transition& right = mTransitions[i];
723  // assume only 1 rule per month
724  if (basic::compareYearMonth(left.year, left.month,
725  right.year, right.month) > 0) {
726  Transition tmp = left;
727  left = right;
728  right = tmp;
729  }
730  }
731  }
732 
738  static Transition createTransition(int16_t year, uint8_t month,
739  const ZEB& era, const ZRB& rule) {
740 
741  int16_t deltaMinutes;
742  const char* letter;
743  uint8_t mon;
744  if (rule.isNull()) {
745  mon = 1; // RULES is either '-' or 'hh:mm' so takes effect in Jan
746  deltaMinutes = era.deltaMinutes();
747  letter = "";
748  } else {
749  mon = rule.inMonth();
750  deltaMinutes = rule.deltaMinutes();
751  letter = rule.letter();
752  }
753  // Clobber the month if specified.
754  if (month != 0) {
755  mon = month;
756  }
757  int16_t offsetMinutes = era.offsetMinutes() + deltaMinutes;
758 
759  return {
760  era,
761  rule,
762  0 /*epochSeconds*/,
763  offsetMinutes,
764  deltaMinutes,
765  year,
766  mon,
767  {letter[0]}, // only single letters are allowed in Basic
768  };
769  }
770 
776  static ZEB findZoneEra(const ZIB& info, int16_t year) {
777  for (uint8_t i = 0; i < info.numEras(); i++) {
778  const ZEB era = info.era(i);
779  if (year < era.untilYear()) return era;
780  }
781  // Return the last ZoneEra if we run off the end.
782  return info.era(info.numEras() - 1);
783  }
784 
792  void calcTransitions() const {
793  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
794  logging::printf("calcTransitions():\n");
795  }
796 
797  // Set the initial startEpochSeconds to be -Infinity
798  Transition* prevTransition = &mTransitions[0];
799  prevTransition->startEpochSeconds = kMinEpochSeconds;
800 
801  for (uint8_t i = 1; i < mNumTransitions; i++) {
802  Transition& transition = mTransitions[i];
803  const int16_t year = transition.year;
804 
805  if (transition.rule.isNull()) {
806  // If the transition is simple (has no named rule), then the
807  // ZoneEra applies for the entire year (since BasicZoneProcessor
808  // supports only whole year in the UNTIL field). The whole year UNTIL
809  // field has an implied 'w' suffix on 00:00, we don't need to call
810  // calcRuleOffsetMinutes() with a 'w', we can just use the previous
811  // transition's offset to calculate the startDateTime of this
812  // transition.
813  //
814  // Also, when transition.rule == nullptr, the mNumTransitions should
815  // be 1, since only a single transition is added by
816  // addTransitionsForYear().
817  const int16_t prevOffsetMinutes = prevTransition->offsetMinutes;
818  OffsetDateTime startDateTime = OffsetDateTime::forComponents(
819  year, 1, 1, 0, 0, 0,
820  TimeOffset::forMinutes(prevOffsetMinutes));
821  transition.startEpochSeconds = startDateTime.toEpochSeconds();
822  } else {
823  // In this case, the transition points to a named ZonePolicy, which
824  // means that there could be multiple ZoneRules associated with the
825  // given year. For each transition, determine the startEpochSeconds,
826  // and the effective offset code.
827 
828  // Determine the start date of the rule.
829  const internal::MonthDay monthDay = internal::calcStartDayOfMonth(
830  year, transition.month, transition.rule.onDayOfWeek(),
831  transition.rule.onDayOfMonth());
832 
833  // Determine the offset of the 'atTimeSuffix'. The 'w' suffix
834  // requires the offset of the previous transition.
835  const int16_t prevOffsetMinutes = calcRuleOffsetMinutes(
836  prevTransition->offsetMinutes,
837  transition.era.offsetMinutes(),
838  transition.rule.atTimeSuffix());
839 
840  // startDateTime
841  const uint16_t minutes = transition.rule.atTimeMinutes();
842  const uint8_t atHour = minutes / 60;
843  const uint8_t atMinute = minutes % 60;
844  OffsetDateTime startDateTime = OffsetDateTime::forComponents(
845  year, monthDay.month, monthDay.day,
846  atHour, atMinute, 0 /*second*/,
847  TimeOffset::forMinutes(prevOffsetMinutes));
848  transition.startEpochSeconds = startDateTime.toEpochSeconds();
849  }
850 
851  prevTransition = &transition;
852  }
853  }
854 
861  static int16_t calcRuleOffsetMinutes(int16_t prevEffectiveOffsetMinutes,
862  int16_t currentBaseOffsetMinutes, uint8_t atSuffix) {
863  if (atSuffix == internal::ZoneContext::kSuffixW) {
864  return prevEffectiveOffsetMinutes;
865  } else if (atSuffix == internal::ZoneContext::kSuffixS) {
866  return currentBaseOffsetMinutes;
867  } else { // 'u', 'g' or 'z'
868  return 0;
869  }
870  }
871 
873  void calcAbbreviations() const {
874  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
875  logging::printf("calcAbbreviations():\n");
876  }
877 
878  for (uint8_t i = 0; i < mNumTransitions; i++) {
879  calcAbbreviation(&mTransitions[i]);
880  }
881  }
882 
884  static void calcAbbreviation(Transition* transition) {
885  createAbbreviation(
886  transition->abbrev,
887  internal::kAbbrevSize,
888  transition->era.format(),
889  transition->deltaMinutes,
890  transition->abbrev[0]);
891  }
892 
935  static void createAbbreviation(char* dest, uint8_t destSize,
936  const char* format, int16_t deltaMinutes, char letter) {
937  // Check if FORMAT contains a '%'.
938  if (strchr(format, '%') != nullptr) {
939  ace_common::copyReplaceChar(dest, destSize, format, '%', letter);
940  } else {
941  // Check if FORMAT contains a '/'.
942  const char* slashPos = strchr(format, '/');
943  if (slashPos != nullptr) {
944  if (deltaMinutes == 0) {
945  uint8_t headLength = (slashPos - format);
946  if (headLength >= destSize) headLength = destSize - 1;
947  memcpy(dest, format, headLength);
948  dest[headLength] = '\0';
949  } else {
950  uint8_t tailLength = strlen(slashPos+1);
951  if (tailLength >= destSize) tailLength = destSize - 1;
952  memcpy(dest, slashPos+1, tailLength);
953  dest[tailLength] = '\0';
954  }
955  } else {
956  // Just copy the FORMAT disregarding the deltaMinutes and letter.
957  strncpy(dest, format, destSize - 1);
958  dest[destSize - 1] = '\0';
959  }
960  }
961  }
962 
964  const Transition* findMatch(acetime_t epochSeconds) const {
965  const Transition* closestMatch = nullptr;
966  for (uint8_t i = 0; i < mNumTransitions; i++) {
967  const Transition* m = &mTransitions[i];
968  if (closestMatch == nullptr || m->startEpochSeconds <= epochSeconds) {
969  closestMatch = m;
970  }
971  }
972  return closestMatch;
973  }
974 
975  const BF* mBrokerFactory; // nullable
976  ZIB mZoneInfoBroker;
977 
978  mutable uint8_t mNumTransitions = 0;
979  mutable Transition mTransitions[kMaxCacheEntries];
980 };
981 
987  basic::BrokerFactory,
988  basic::ZoneInfoBroker,
989  basic::ZoneEraBroker,
990  basic::ZonePolicyBroker,
991  basic::ZoneRuleBroker> {
992 
993  public:
995  static const uint8_t kTypeBasic = 3;
996 
997  explicit BasicZoneProcessor(const basic::ZoneInfo* zoneInfo = nullptr)
999  basic::BrokerFactory,
1000  basic::ZoneInfoBroker,
1001  basic::ZoneEraBroker,
1002  basic::ZonePolicyBroker,
1003  basic::ZoneRuleBroker>(
1004  kTypeBasic, &mBrokerFactory, (uintptr_t) zoneInfo)
1005  {}
1006 
1007  private:
1008  basic::BrokerFactory mBrokerFactory;
1009 };
1010 
1011 } // namespace ace_time
1012 
1013 #endif
An implementation of ZoneProcessor that supports a subset of the zones containing in the TZ Database.
void setBrokerFactory(const BF *brokerFactory)
Set the broker factory at runtime.
uint32_t getZoneId() const override
Return the unique stable zoneId.
bool equalsZoneKey(uintptr_t zoneKey) const override
Return true if ZoneProcessor is associated with the given opaque zoneKey.
bool isLink() const override
Return true if timezone is a Link entry pointing to a Zone entry.
void printTargetNameTo(Print &printer) const override
Print the full identifier (e.g.
FindResult findByEpochSeconds(acetime_t epochSeconds) const override
Return the search results at given epochSeconds.
void printNameTo(Print &printer) const override
Print a human-readable identifier (e.g.
void setZoneKey(uintptr_t zoneKey) override
Set the opaque zoneKey of this object to a new value, reseting any internally cached information.
void printShortNameTo(Print &printer) const override
Print a short human-readable identifier (e.g.
BasicZoneProcessorTemplate(uint8_t type, const BF *brokerFactory, uintptr_t zoneKey)
Constructor.
FindResult findByLocalDateTime(const LocalDateTime &ldt) const override
Return the search results at given LocalDateTime.
basic::TransitionTemplate< ZIB, ZEB, ZPB, ZRB > Transition
Exposed only for testing purposes.
void log() const
Used only for debugging.
A specific implementation of BasicZoneProcessorTemplate that uses ZoneXxxBrokers which read from zone...
static const uint8_t kTypeBasic
Unique TimeZone type identifier for BasicZoneProcessor.
Result of a search for transition at a specific epochSeconds or a specific LocalDateTime.
Definition: ZoneProcessor.h:24
int16_t dstOffsetMinutes
DST offset of the resulting OffsetDateTime.
Definition: ZoneProcessor.h:83
int16_t reqStdOffsetMinutes
STD offset of the Transition which matched the epochSeconds requested by findByEpochSeconds(),...
Definition: ZoneProcessor.h:96
int16_t stdOffsetMinutes
STD offset of the resulting OffsetDateTime.
Definition: ZoneProcessor.h:80
int16_t reqDstOffsetMinutes
DST offset of the Transition which matched the epochSeconds requested by findByEpochSeconds(),...
const char * abbrev
Pointer to the abbreviation stored in the transient Transition::abbrev variable.
uint8_t type
Result of the findByEpochSeconds() or findByLocalDateTime() search methods.
Definition: ZoneProcessor.h:66
Class that holds the date-time as the components (year, month, day, hour, minute, second) without reg...
Definition: LocalDateTime.h:31
const LocalDate & localDate() const
Return the LocalDate.
acetime_t toEpochSeconds() const
Return seconds since the current AceTime epoch defined by Epoch::currentEpochYear().
static LocalDate forEpochSeconds(acetime_t epochSeconds)
Factory method using the number of seconds since the current epoch year given by currentEpochYear().
Definition: LocalDate.h:208
static const int16_t kInvalidYear
Sentinel year which indicates one or more of the following conditions:
Definition: LocalDate.h:61
static OffsetDateTime forLocalDateTimeAndOffset(const LocalDateTime &localDateTime, TimeOffset timeOffset)
Factory method from LocalDateTime and TimeOffset.
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.
static TimeOffset forMinutes(int16_t minutes)
Create TimeOffset from minutes from 00:00.
Definition: TimeOffset.h:83
Base interface for ZoneProcessor classes.
bool isFilled(int16_t year) const
Check if the Transition cache is filled for the given year.
Identifiers used by implementation code which need to be publically exported.
int32_t acetime_t
Type for the number of seconds from epoch.
Definition: common.h:24
Data structure that defines the start of a specific UTC offset as described by the matching ZoneEra a...
uint8_t month
Month of the transition.
acetime_t startEpochSeconds
The calculated transition time of the given rule.
ZRB rule
The Zone transition rule that matched for the the given year.
ZEB era
The ZoneEra that matched the given year.
void log() const
Used only for debugging.
int16_t year
Year of the Transition.
int16_t offsetMinutes
The total effective UTC offset minutes at the start of transition, including DST offset.
char abbrev[internal::kAbbrevSize]
The calculated effective time zone abbreviation, e.g.
int16_t deltaMinutes
The deltaMinutes from "standard time" at the start of transition.
static const uint8_t kSuffixW
Represents 'w' or wall time.
Definition: ZoneContext.h:44
static const uint8_t kSuffixS
Represents 's' or standard time.
Definition: ZoneContext.h:47