AceTime  2.1.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 "internal/common.h" // kAbbrevSize
13 #include "internal/ZonePolicy.h"
14 #include "internal/ZoneInfo.h"
15 #include "internal/BasicBrokers.h"
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  int16_t deltaMinutes;
741  char letter;
742  uint8_t mon;
743  if (rule.isNull()) {
744  mon = 1; // RULES is either '-' or 'hh:mm' so takes effect in Jan
745  deltaMinutes = era.deltaMinutes();
746  letter = '\0';
747  } else {
748  mon = rule.inMonth();
749  deltaMinutes = rule.deltaMinutes();
750  letter = rule.letter();
751  }
752  // Clobber the month if specified.
753  if (month != 0) {
754  mon = month;
755  }
756  int16_t offsetMinutes = era.offsetMinutes() + deltaMinutes;
757 
758  return {
759  era,
760  rule,
761  0 /*epochSeconds*/,
762  offsetMinutes,
763  deltaMinutes,
764  year,
765  mon,
766  {letter} /*abbrev*/
767  };
768  }
769 
775  static ZEB findZoneEra(const ZIB& info, int16_t year) {
776  for (uint8_t i = 0; i < info.numEras(); i++) {
777  const ZEB era = info.era(i);
778  if (year < era.untilYear()) return era;
779  }
780  // Return the last ZoneEra if we run off the end.
781  return info.era(info.numEras() - 1);
782  }
783 
791  void calcTransitions() const {
792  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
793  logging::printf("calcTransitions():\n");
794  }
795 
796  // Set the initial startEpochSeconds to be -Infinity
797  Transition* prevTransition = &mTransitions[0];
798  prevTransition->startEpochSeconds = kMinEpochSeconds;
799 
800  for (uint8_t i = 1; i < mNumTransitions; i++) {
801  Transition& transition = mTransitions[i];
802  const int16_t year = transition.year;
803 
804  if (transition.rule.isNull()) {
805  // If the transition is simple (has no named rule), then the
806  // ZoneEra applies for the entire year (since BasicZoneProcessor
807  // supports only whole year in the UNTIL field). The whole year UNTIL
808  // field has an implied 'w' suffix on 00:00, we don't need to call
809  // calcRuleOffsetMinutes() with a 'w', we can just use the previous
810  // transition's offset to calculate the startDateTime of this
811  // transition.
812  //
813  // Also, when transition.rule == nullptr, the mNumTransitions should
814  // be 1, since only a single transition is added by
815  // addTransitionsForYear().
816  const int16_t prevOffsetMinutes = prevTransition->offsetMinutes;
817  OffsetDateTime startDateTime = OffsetDateTime::forComponents(
818  year, 1, 1, 0, 0, 0,
819  TimeOffset::forMinutes(prevOffsetMinutes));
820  transition.startEpochSeconds = startDateTime.toEpochSeconds();
821  } else {
822  // In this case, the transition points to a named ZonePolicy, which
823  // means that there could be multiple ZoneRules associated with the
824  // given year. For each transition, determine the startEpochSeconds,
825  // and the effective offset code.
826 
827  // Determine the start date of the rule.
828  const internal::MonthDay monthDay = internal::calcStartDayOfMonth(
829  year, transition.month, transition.rule.onDayOfWeek(),
830  transition.rule.onDayOfMonth());
831 
832  // Determine the offset of the 'atTimeSuffix'. The 'w' suffix
833  // requires the offset of the previous transition.
834  const int16_t prevOffsetMinutes = calcRuleOffsetMinutes(
835  prevTransition->offsetMinutes,
836  transition.era.offsetMinutes(),
837  transition.rule.atTimeSuffix());
838 
839  // startDateTime
840  const uint16_t minutes = transition.rule.atTimeMinutes();
841  const uint8_t atHour = minutes / 60;
842  const uint8_t atMinute = minutes % 60;
843  OffsetDateTime startDateTime = OffsetDateTime::forComponents(
844  year, monthDay.month, monthDay.day,
845  atHour, atMinute, 0 /*second*/,
846  TimeOffset::forMinutes(prevOffsetMinutes));
847  transition.startEpochSeconds = startDateTime.toEpochSeconds();
848  }
849 
850  prevTransition = &transition;
851  }
852  }
853 
860  static int16_t calcRuleOffsetMinutes(int16_t prevEffectiveOffsetMinutes,
861  int16_t currentBaseOffsetMinutes, uint8_t atSuffix) {
862  if (atSuffix == internal::ZoneContext::kSuffixW) {
863  return prevEffectiveOffsetMinutes;
864  } else if (atSuffix == internal::ZoneContext::kSuffixS) {
865  return currentBaseOffsetMinutes;
866  } else { // 'u', 'g' or 'z'
867  return 0;
868  }
869  }
870 
872  void calcAbbreviations() const {
873  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
874  logging::printf("calcAbbreviations():\n");
875  }
876 
877  for (uint8_t i = 0; i < mNumTransitions; i++) {
878  calcAbbreviation(&mTransitions[i]);
879  }
880  }
881 
883  static void calcAbbreviation(Transition* transition) {
884  createAbbreviation(
885  transition->abbrev,
886  internal::kAbbrevSize,
887  transition->era.format(),
888  transition->deltaMinutes,
889  transition->abbrev[0]);
890  }
891 
940  static void createAbbreviation(char* dest, uint8_t destSize,
941  const char* format, int16_t deltaMinutes, char letter) {
942  // Check if FORMAT contains a '%'.
943  if (strchr(format, '%') != nullptr) {
944  // Check if RULES column empty, therefore no 'letter'
945  if (letter == '\0') {
946  strncpy(dest, format, destSize - 1);
947  dest[destSize - 1] = '\0';
948  } else {
949  ace_common::copyReplaceChar(dest, destSize, format, '%',
950  letter == '-' ? '\0' : letter);
951  }
952  } else {
953  // Check if FORMAT contains a '/'.
954  const char* slashPos = strchr(format, '/');
955  if (slashPos != nullptr) {
956  if (deltaMinutes == 0) {
957  uint8_t headLength = (slashPos - format);
958  if (headLength >= destSize) headLength = destSize - 1;
959  memcpy(dest, format, headLength);
960  dest[headLength] = '\0';
961  } else {
962  uint8_t tailLength = strlen(slashPos+1);
963  if (tailLength >= destSize) tailLength = destSize - 1;
964  memcpy(dest, slashPos+1, tailLength);
965  dest[tailLength] = '\0';
966  }
967  } else {
968  // Just copy the FORMAT disregarding the deltaMinutes and letter.
969  strncpy(dest, format, destSize - 1);
970  dest[destSize - 1] = '\0';
971  }
972  }
973  }
974 
976  const Transition* findMatch(acetime_t epochSeconds) const {
977  const Transition* closestMatch = nullptr;
978  for (uint8_t i = 0; i < mNumTransitions; i++) {
979  const Transition* m = &mTransitions[i];
980  if (closestMatch == nullptr || m->startEpochSeconds <= epochSeconds) {
981  closestMatch = m;
982  }
983  }
984  return closestMatch;
985  }
986 
987  const BF* mBrokerFactory; // nullable
988  ZIB mZoneInfoBroker;
989 
990  mutable uint8_t mNumTransitions = 0;
991  mutable Transition mTransitions[kMaxCacheEntries];
992 };
993 
999  basic::BrokerFactory,
1000  basic::ZoneInfoBroker,
1001  basic::ZoneEraBroker,
1002  basic::ZonePolicyBroker,
1003  basic::ZoneRuleBroker> {
1004 
1005  public:
1007  static const uint8_t kTypeBasic = 3;
1008 
1009  explicit BasicZoneProcessor(const basic::ZoneInfo* zoneInfo = nullptr)
1011  basic::BrokerFactory,
1012  basic::ZoneInfoBroker,
1013  basic::ZoneEraBroker,
1014  basic::ZonePolicyBroker,
1015  basic::ZoneRuleBroker>(
1016  kTypeBasic, &mBrokerFactory, (uintptr_t) zoneInfo)
1017  {}
1018 
1019  private:
1020  basic::BrokerFactory mBrokerFactory;
1021 };
1022 
1023 } // namespace ace_time
1024 
1025 #endif
The classes provide a thin layer of indirection for accessing the zoneinfo files stored in the zonedb...
An implementation of ZoneProcessor that supports a subset of the zones containing in the TZ Database.
void setBrokerFactory(const BF *brokerFactory)
Set the broker factory at runtime.
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:25
int16_t dstOffsetMinutes
DST offset of the resulting OffsetDateTime.
Definition: ZoneProcessor.h:84
int16_t reqStdOffsetMinutes
STD offset of the Transition which matched the epochSeconds requested by findByEpochSeconds(),...
Definition: ZoneProcessor.h:97
int16_t stdOffsetMinutes
STD offset of the resulting OffsetDateTime.
Definition: ZoneProcessor.h:81
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:67
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.
A factory that creates a basic::ZoneInfoBroker.
Definition: BasicBrokers.h:414
int32_t acetime_t
Type for the number of seconds from epoch.
Definition: common.h:24
Internal identifiers used by implementation code, not intended to be publically exported.
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:18
static const uint8_t kSuffixS
Represents 's' or standard time.
Definition: ZoneContext.h:21