AceTime  0.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 <Arduino.h>
10 #include <string.h> // strchr()
11 #include <stdint.h>
12 #include "internal/ZonePolicy.h"
13 #include "internal/ZoneInfo.h"
14 #include "internal/Brokers.h"
15 #include "common/logger.h"
16 #include "TimeOffset.h"
17 #include "LocalDate.h"
18 #include "OffsetDateTime.h"
19 #include "ZoneProcessor.h"
20 
21 class BasicZoneProcessorTest_init_primitives;
22 class BasicZoneProcessorTest_init;
23 class BasicZoneProcessorTest_setZoneInfo;
24 class BasicZoneProcessorTest_createAbbreviation;
25 class BasicZoneProcessorTest_calcStartDayOfMonth;
26 class BasicZoneProcessorTest_calcRuleOffsetCode;
27 
28 namespace ace_time {
29 
30 template<uint8_t SIZE, uint8_t TYPE, typename ZS, typename ZI, typename ZIB>
32 
33 namespace basic {
34 
47 struct Transition {
55  static const uint8_t kAbbrevSize = 6 + 1;
56 
63 
75 
77  int8_t yearTiny;
78 
80  acetime_t startEpochSeconds;
81 
87  int8_t offsetCode;
88 
90  int8_t deltaCode;
91 
100 
102  void log() const {
103  if (sizeof(acetime_t) == sizeof(int)) {
104  logging::println("startEpochSeconds: %d", startEpochSeconds);
105  } else {
106  logging::println("startEpochSeconds: %ld", startEpochSeconds);
107  }
108  logging::println("offsetCode: %d", offsetCode);
109  logging::println("abbrev: %s", abbrev);
110  if (rule.isNotNull()) {
111  logging::println("Rule.fromYear: %d", rule.fromYearTiny());
112  logging::println("Rule.toYear: %d", rule.toYearTiny());
113  logging::println("Rule.inMonth: %d", rule.inMonth());
114  logging::println("Rule.onDayOfMonth: %d", rule.onDayOfMonth());
115  }
116  }
117 };
118 
120 struct MonthDay {
121  uint8_t month;
122  uint8_t day;
123 };
124 
125 } // namespace basic
126 
179  public:
184  explicit BasicZoneProcessor(const basic::ZoneInfo* zoneInfo = nullptr):
185  ZoneProcessor(kTypeBasic),
186  mZoneInfo(zoneInfo) {}
187 
189  const void* getZoneInfo() const override {
190  return mZoneInfo.zoneInfo();
191  }
192 
193  uint32_t getZoneId() const override { return mZoneInfo.zoneId(); }
194 
195  TimeOffset getUtcOffset(acetime_t epochSeconds) const override {
196  const basic::Transition* transition = getTransition(epochSeconds);
197  int8_t code = (transition)
198  ? transition->offsetCode : TimeOffset::kErrorCode;
199  return TimeOffset::forOffsetCode(code);
200  }
201 
202  TimeOffset getDeltaOffset(acetime_t epochSeconds) const override {
203  const basic::Transition* transition = getTransition(epochSeconds);
204  int8_t code = (transition)
205  ? transition->deltaCode : TimeOffset::kErrorCode;
206  return TimeOffset::forOffsetCode(code);
207  }
208 
209  const char* getAbbrev(acetime_t epochSeconds) const override {
210  const basic::Transition* transition = getTransition(epochSeconds);
211  return (transition) ? transition->abbrev : "";
212  }
213 
243  OffsetDateTime getOffsetDateTime(const LocalDateTime& ldt) const override {
244  // Only a single local variable of OffsetDateTime used, to allow Return
245  // Value Optimization (and save 20 bytes of flash for WorldClock).
246  OffsetDateTime odt;
247  bool success = init(ldt.localDate());
248  if (success) {
249  // 0) Use the UTC epochSeconds to get intial guess of offset.
250  acetime_t epochSeconds0 = ldt.toEpochSeconds();
251  auto offset0 = getUtcOffset(epochSeconds0);
252 
253  // 1) Use offset0 to get the next epochSeconds and offset.
254  odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset0);
255  acetime_t epochSeconds1 = odt.toEpochSeconds();
256  auto offset1 = getUtcOffset(epochSeconds1);
257 
258  // 2) Use offset1 to get the next epochSeconds and offset.
259  odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset1);
260  acetime_t epochSeconds2 = odt.toEpochSeconds();
261  auto offset2 = getUtcOffset(epochSeconds2);
262 
263  // If offset1 and offset2 are equal, then we have an equilibrium
264  // and odt(1) must equal odt(2), so we can just return the last odt.
265  if (offset1.toOffsetCode() == offset2.toOffsetCode()) {
266  // pass
267  } else {
268  // Pick the later epochSeconds and offset
269  acetime_t epochSeconds;
270  TimeOffset offset;
271  if (epochSeconds1 > epochSeconds2) {
272  epochSeconds = epochSeconds1;
273  offset = offset1;
274  } else {
275  epochSeconds = epochSeconds2;
276  offset = offset2;
277  }
278  odt = OffsetDateTime::forEpochSeconds(epochSeconds, offset);
279  }
280  } else {
281  odt = OffsetDateTime::forError();
282  }
283 
284  return odt;
285  }
286 
287  void printTo(Print& printer) const override;
288 
289  void printShortTo(Print& printer) const override;
290 
292  void log() const {
293  if (!mIsFilled) {
294  logging::println("*not initialized*");
295  return;
296  }
297  logging::println("mYear: %d", mYear);
298  logging::println("mNumTransitions: %d", mNumTransitions);
299  logging::println("---- PrevTransition");
300  mPrevTransition.log();
301  for (int i = 0; i < mNumTransitions; i++) {
302  logging::println("---- Transition: %d", i);
303  mTransitions[i].log();
304  }
305  }
306 
325  static basic::MonthDay calcStartDayOfMonth(int16_t year, uint8_t month,
326  uint8_t onDayOfWeek, int8_t onDayOfMonth) {
327  if (onDayOfWeek == 0) return {month, (uint8_t) onDayOfMonth};
328 
329  if (onDayOfMonth >= 0) {
330  // Convert "last{Xxx}" to "last{Xxx}>={daysInMonth-6}".
331  uint8_t daysInMonth = LocalDate::daysInMonth(year, month);
332  if (onDayOfMonth == 0) {
333  onDayOfMonth = daysInMonth - 6;
334  }
335 
336  auto limitDate = LocalDate::forComponents(year, month, onDayOfMonth);
337  uint8_t dayOfWeekShift = (onDayOfWeek - limitDate.dayOfWeek() + 7) % 7;
338  uint8_t day = (uint8_t) (onDayOfMonth + dayOfWeekShift);
339  if (day > daysInMonth) {
340  // TODO: Support shifting from Dec to Jan of following year.
341  day -= daysInMonth;
342  month++;
343  }
344  return {month, day};
345  } else {
346  onDayOfMonth = -onDayOfMonth;
347  auto limitDate = LocalDate::forComponents(year, month, onDayOfMonth);
348  int8_t dayOfWeekShift = (limitDate.dayOfWeek() - onDayOfWeek + 7) % 7;
349  int8_t day = onDayOfMonth - dayOfWeekShift;
350  if (day < 1) {
351  // TODO: Support shifting from Jan to Dec of the previous year.
352  month--;
353  uint8_t daysInPrevMonth = LocalDate::daysInMonth(year, month);
354  day += daysInPrevMonth;
355  }
356  return {month, (uint8_t) day};
357  }
358  }
359 
360  private:
361  friend class ::BasicZoneProcessorTest_init_primitives;
362  friend class ::BasicZoneProcessorTest_init;
363  friend class ::BasicZoneProcessorTest_setZoneInfo;
364  friend class ::BasicZoneProcessorTest_createAbbreviation;
365  friend class ::BasicZoneProcessorTest_calcStartDayOfMonth;
366  friend class ::BasicZoneProcessorTest_calcRuleOffsetCode;
367 
368  template<uint8_t SIZE, uint8_t TYPE, typename ZS, typename ZI, typename ZIB>
369  friend class ZoneProcessorCacheImpl; // setZoneInfo()
370 
372  static const uint8_t kMaxCacheEntries = 4;
373 
379  static const acetime_t kMinEpochSeconds = INT32_MIN + 1;
380 
381  // Disable copy constructor and assignment operator.
382  BasicZoneProcessor(const BasicZoneProcessor&) = delete;
383  BasicZoneProcessor& operator=(const BasicZoneProcessor&) = delete;
384 
385  bool equals(const ZoneProcessor& other) const override {
386  const auto& that = (const BasicZoneProcessor&) other;
387  return getZoneInfo() == that.getZoneInfo();
388  }
389 
391  void setZoneInfo(const void* zoneInfo) override {
392  if (mZoneInfo.zoneInfo() == zoneInfo) return;
393 
394  mZoneInfo = basic::ZoneInfoBroker((const basic::ZoneInfo*) zoneInfo);
395  mYear = 0;
396  mIsFilled = false;
397  mNumTransitions = 0;
398  }
399 
401  const basic::Transition* getTransition(acetime_t epochSeconds) const {
402  LocalDate ld = LocalDate::forEpochSeconds(epochSeconds);
403  bool success = init(ld);
404  return (success) ? findMatch(epochSeconds) : nullptr;
405  }
406 
435  bool init(const LocalDate& ld) const {
436  int16_t year = ld.year();
437  if (ld.month() == 1 && ld.day() == 1) {
438  year--;
439  }
440  if (isFilled(year)) return true;
441 
442  mYear = year;
443  mNumTransitions = 0; // clear cache
444 
445  if (year < mZoneInfo.startYear() - 1 || mZoneInfo.untilYear() < year) {
446  return false;
447  }
448 
449  addRulePriorToYear(year);
450  addRulesForYear(year);
451  calcTransitions();
452  calcAbbreviations();
453 
454  mIsFilled = true;
455  return true;
456  }
457 
459  bool isFilled(int16_t year) const {
460  return mIsFilled && (year == mYear);
461  }
462 
467  void addRulePriorToYear(int16_t year) const {
468  int8_t yearTiny = year - LocalDate::kEpochYear;
469  int8_t priorYearTiny = yearTiny - 1;
470 
471  // Find the prior Era.
472  const basic::ZoneEraBroker era = findZoneEraPriorTo(year);
473 
474  // If the prior ZoneEra is a simple Era (no zone policy), then create a
475  // Transition using a rule==nullptr. Otherwise, find the latest rule
476  // within the ZoneEra.
477  const basic::ZonePolicyBroker zonePolicy = era.zonePolicy();
478  basic::ZoneRuleBroker latest;
479  if (zonePolicy.isNotNull()) {
480  // Find the latest rule for the matching ZoneEra whose
481  // ZoneRule::toYearTiny < yearTiny. Assume that there are no more than
482  // 1 rule per month.
483  uint8_t numRules = zonePolicy.numRules();
484  for (uint8_t i = 0; i < numRules; i++) {
485  const basic::ZoneRuleBroker rule = zonePolicy.rule(i);
486  // Check if rule is effective prior to the given year
487  if (rule.fromYearTiny() < yearTiny) {
488  if ((latest.isNull()) || compareZoneRule(year, rule, latest) > 0) {
489  latest = rule;
490  }
491  }
492  }
493  }
494 
495  mPrevTransition = createTransition(era, latest, priorYearTiny);
496  }
497 
503  static basic::Transition createTransition(basic::ZoneEraBroker era,
505  int8_t offsetCode;
506  int8_t deltaCode;
507  char letter;
508  if (rule.isNull()) {
509  deltaCode = 0;
510  offsetCode = era.offsetCode();
511  letter = 0;
512  } else {
513  deltaCode = rule.deltaCode();
514  offsetCode = era.offsetCode() + deltaCode;
515  letter = rule.letter();
516  }
517 
518  return {
519  era, rule, yearTiny,
520  0 /*epochSeconds*/,
521  offsetCode,
522  deltaCode,
523  {letter} /*abbrev*/
524  };
525  }
526 
528  static int8_t compareZoneRule(int16_t year,
529  const basic::ZoneRuleBroker a, const basic::ZoneRuleBroker b) {
530  int16_t aYear = effectiveRuleYear(year, a);
531  int16_t bYear = effectiveRuleYear(year, b);
532  if (aYear < bYear) return -1;
533  if (aYear > bYear) return 1;
534  if (a.inMonth() < b.inMonth()) return -1;
535  if (a.inMonth() > b.inMonth()) return 1;
536  return 0;
537  }
538 
543  static int16_t effectiveRuleYear(int16_t year,
544  const basic::ZoneRuleBroker rule) {
545  int8_t yearTiny = year - LocalDate::kEpochYear;
546  if (rule.toYearTiny() < yearTiny) {
547  return rule.toYearTiny() + LocalDate::kEpochYear;
548  }
549  if (rule.fromYearTiny() < yearTiny) {
550  return year - 1;
551  }
552  return 0;
553  }
554 
556  void addRulesForYear(int16_t year) const {
557  const basic::ZoneEraBroker era = findZoneEra(year);
558 
559  // If the ZonePolicy has no rules, then add a Transition which takes
560  // effect at the start time of the current year.
561  const basic::ZonePolicyBroker zonePolicy = era.zonePolicy();
562  if (zonePolicy.isNull()) {
563  addRule(year, era, basic::ZoneRuleBroker());
564  return;
565  }
566 
567  // If the ZonePolicy has rules, find all matching transitions, and add
568  // them to mTransitions, in sorted order according to the
569  // ZoneRule::inMonth field.
570  int8_t yearTiny = year - LocalDate::kEpochYear;
571  uint8_t numRules = zonePolicy.numRules();
572  for (uint8_t i = 0; i < numRules; i++) {
573  const basic::ZoneRuleBroker rule = zonePolicy.rule(i);
574  if ((rule.fromYearTiny() <= yearTiny) &&
575  (yearTiny <= rule.toYearTiny())) {
576  addRule(year, era, rule);
577  }
578  }
579  }
580 
595  void addRule(int16_t year, basic::ZoneEraBroker era,
596  basic::ZoneRuleBroker rule) const {
597 
598  // If a zone needs more transitions than kMaxCacheEntries, the check below
599  // will cause the DST transition information to be inaccurate, and it is
600  // highly likely that this situation would be caught in the
601  // BasicValidationUsingPython or BasicValidationUsingJava unit tests.
602  // Since these unit tests pass, I feel confident that those zones which
603  // need more than kMaxCacheEntries are already filtered out by
604  // tzcompiler.py.
605  //
606  // Ideally, the tzcompiler.py script would explicitly remove those zones
607  // which need more than kMaxCacheEntries Transitions. But this would
608  // require a Python version of the BasicZoneProcessor, and unfortunately,
609  // zone_specifier.py implements only the ExtendedZoneProcessor algorithm
610  // An early version of zone_specifier.py may have implemented something
611  // close to BasicZoneProcessor, and it may be available in the git
612  // history. But it seems like too much work right now to try to dig that
613  // out, just to implement the explicit check for kMaxCacheEntries. It
614  // would mean maintaining another version of zone_specifier.py.
615  if (mNumTransitions >= kMaxCacheEntries) return;
616 
617  // insert new element at the end of the list
618  int8_t yearTiny = year - LocalDate::kEpochYear;
619  mTransitions[mNumTransitions] = createTransition(era, rule, yearTiny);
620  mNumTransitions++;
621 
622  // perform an insertion sort
623  for (uint8_t i = mNumTransitions - 1; i > 0; i--) {
624  basic::Transition& left = mTransitions[i - 1];
625  basic::Transition& right = mTransitions[i];
626  // assume only 1 rule per month
627  if ((left.rule.isNotNull() && right.rule.isNotNull() &&
628  left.rule.inMonth() > right.rule.inMonth())
629  || (left.rule.isNotNull() && right.rule.isNull())) {
630  basic::Transition tmp = left;
631  left = right;
632  right = tmp;
633  }
634  }
635  }
636 
642  const basic::ZoneEraBroker findZoneEra(int16_t year) const {
643  for (uint8_t i = 0; i < mZoneInfo.numEras(); i++) {
644  const basic::ZoneEraBroker era = mZoneInfo.era(i);
645  if (year < era.untilYearTiny() + LocalDate::kEpochYear) return era;
646  }
647  // Return the last ZoneEra if we run off the end.
648  return mZoneInfo.era(mZoneInfo.numEras() - 1);
649  }
650 
662  const basic::ZoneEraBroker findZoneEraPriorTo(int16_t year) const {
663  for (uint8_t i = 0; i < mZoneInfo.numEras(); i++) {
664  const basic::ZoneEraBroker era = mZoneInfo.era(i);
665  if (year <= era.untilYearTiny() + LocalDate::kEpochYear) return era;
666  }
667  // Return the last ZoneEra if we run off the end.
668  return mZoneInfo.era(mZoneInfo.numEras() - 1);
669  }
670 
678  void calcTransitions() const {
679  // Set the initial startEpochSeconds to be -Infinity
680  mPrevTransition.startEpochSeconds = kMinEpochSeconds;
681  const basic::Transition* prevTransition = &mPrevTransition;
682 
683  for (uint8_t i = 0; i < mNumTransitions; i++) {
684  basic::Transition& transition = mTransitions[i];
685  const int16_t year = transition.yearTiny + LocalDate::kEpochYear;
686 
687  if (transition.rule.isNull()) {
688  // If the transition is simple (has no named rule), then the
689  // ZoneEra applies for the entire year (since BasicZoneProcessor
690  // supports only whole year in the UNTIL field). The whole year UNTIL
691  // field has an implied 'w' modifier on 00:00, we don't need to call
692  // calcRuleOffsetCode() with a 'w', we can just use the previous
693  // transition's offset to calculate the startDateTime of this
694  // transition.
695  //
696  // Also, when transition.rule == nullptr, the mNumTransitions should
697  // be 1, since only a single transition is added by
698  // addRulesForYear().
699  const int8_t prevOffsetCode = prevTransition->offsetCode;
701  year, 1, 1, 0, 0, 0,
702  TimeOffset::forOffsetCode(prevOffsetCode));
703  transition.startEpochSeconds = startDateTime.toEpochSeconds();
704  } else {
705  // In this case, the transition points to a named ZonePolicy, which
706  // means that there could be multiple ZoneRules associated with the
707  // given year. For each transition, determine the startEpochSeconds,
708  // and the effective offset code.
709 
710  // Determine the start date of the rule.
711  const basic::MonthDay monthDay = calcStartDayOfMonth(
712  year, transition.rule.inMonth(), transition.rule.onDayOfWeek(),
713  transition.rule.onDayOfMonth());
714 
715  // Determine the offset of the 'atTimeModifier'. The 'w' modifier
716  // requires the offset of the previous transition.
717  const int8_t prevOffsetCode = calcRuleOffsetCode(
718  prevTransition->offsetCode,
719  transition.era.offsetCode(),
720  transition.rule.atTimeModifier());
721 
722  // startDateTime
723  const uint8_t timeCode = transition.rule.atTimeCode();
724  const uint8_t atHour = timeCode / 4;
725  const uint8_t atMinute = (timeCode % 4) * 15;
727  year, monthDay.month, monthDay.day,
728  atHour, atMinute, 0 /*second*/,
729  TimeOffset::forOffsetCode(prevOffsetCode));
730  transition.startEpochSeconds = startDateTime.toEpochSeconds();
731  }
732 
733  prevTransition = &transition;
734  }
735  }
736 
743  static int8_t calcRuleOffsetCode(int8_t prevEffectiveOffsetCode,
744  int8_t currentBaseOffsetCode, uint8_t atModifier) {
745  if (atModifier == 'w') {
746  return prevEffectiveOffsetCode;
747  } else if (atModifier == 's') {
748  return currentBaseOffsetCode;
749  } else { // 'u', 'g' or 'z'
750  return 0;
751  }
752  }
753 
755  void calcAbbreviations() const {
756  calcAbbreviation(&mPrevTransition);
757  for (uint8_t i = 0; i < mNumTransitions; i++) {
758  calcAbbreviation(&mTransitions[i]);
759  }
760  }
761 
763  static void calcAbbreviation(basic::Transition* transition) {
764  createAbbreviation(
765  transition->abbrev,
767  transition->era.format(),
768  transition->deltaCode,
769  transition->abbrev[0]);
770  }
771 
807  static void createAbbreviation(char* dest, uint8_t destSize,
808  const char* format, uint8_t deltaCode, char letter) {
809  // Check if RULES column empty.
810  if (deltaCode == 0 && letter == '\0') {
811  strncpy(dest, format, destSize);
812  dest[destSize - 1] = '\0';
813  return;
814  }
815 
816  // Check if FORMAT contains a '%'.
817  if (strchr(format, '%') != nullptr) {
818  copyAndReplace(dest, destSize, format, '%', letter);
819  } else {
820  // Check if FORMAT contains a '/'.
821  const char* slashPos = strchr(format, '/');
822  if (slashPos != nullptr) {
823  if (deltaCode == 0) {
824  uint8_t headLength = (slashPos - format);
825  if (headLength >= destSize) headLength = destSize - 1;
826  memcpy(dest, format, headLength);
827  dest[headLength] = '\0';
828  } else {
829  uint8_t tailLength = strlen(slashPos+1);
830  if (tailLength >= destSize) tailLength = destSize - 1;
831  memcpy(dest, slashPos+1, tailLength);
832  dest[tailLength] = '\0';
833  }
834  } else {
835  // Just copy the FORMAT disregarding the deltaCode and letter.
836  strncpy(dest, format, destSize);
837  dest[destSize - 1] = '\0';
838  }
839  }
840  }
841 
847  static void copyAndReplace(char* dst, uint8_t dstSize, const char* src,
848  char oldChar, char newChar) {
849  while (*src != '\0' && dstSize > 0) {
850  if (*src == oldChar) {
851  if (newChar == '-') {
852  src++;
853  } else {
854  *dst = newChar;
855  dst++;
856  src++;
857  dstSize--;
858  }
859  } else {
860  *dst++ = *src++;
861  dstSize--;
862  }
863  }
864 
865  if (dstSize == 0) {
866  --dst;
867  }
868  *dst = '\0';
869  }
870 
872  const basic::Transition* findMatch(acetime_t epochSeconds) const {
873  const basic::Transition* closestMatch = &mPrevTransition;
874  for (uint8_t i = 0; i < mNumTransitions; i++) {
875  const basic::Transition* m = &mTransitions[i];
876  if (m->startEpochSeconds <= epochSeconds) {
877  closestMatch = m;
878  }
879  }
880  return closestMatch;
881  }
882 
883  basic::ZoneInfoBroker mZoneInfo;
884 
885  mutable int16_t mYear = 0; // maybe create LocalDate::kInvalidYear?
886  mutable bool mIsFilled = false;
887  mutable uint8_t mNumTransitions = 0;
888  mutable basic::Transition mTransitions[kMaxCacheEntries];
889  mutable basic::Transition mPrevTransition; // previous year's transition
890 };
891 
892 }
893 
894 #endif
Base interface for ZoneProcessor classes.
Definition: ZoneProcessor.h:45
ZoneEraBroker era
The ZoneEra that matched the given year.
const char * getAbbrev(acetime_t epochSeconds) const override
Return the time zone abbreviation at epochSeconds.
void log() const
Used only for debugging.
A cache of ZoneProcessors that provides a ZoneProcessor to the TimeZone upon request.
static const uint8_t kAbbrevSize
Longest abbreviation currently seems to be 5 characters (https://www.timeanddate.com/time/zones/) but...
char abbrev[kAbbrevSize]
The calculated effective time zone abbreviation, e.g.
static OffsetDateTime forLocalDateTimeAndOffset(const LocalDateTime &localDateTime, TimeOffset timeOffset)
Factory method from LocalDateTime and TimeOffset.
BasicZoneProcessor(const basic::ZoneInfo *zoneInfo=nullptr)
Constructor.
Representation of a given time zone, implemented as an array of ZoneEra records.
Definition: ZoneInfo.h:77
The result of calcStartDayOfMonth().
virtual const void * getZoneInfo() const =0
Return the opaque zoneInfo.
uint32_t getZoneId() const override
Return the unique stable zoneId.
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.
acetime_t startEpochSeconds
The calculated transition time of the given rule.
uint8_t day() const
Return the day of the month.
Definition: LocalDate.h:237
int8_t yearTiny
Year which applies to the ZoneEra or ZoneRule.
static OffsetDateTime forEpochSeconds(acetime_t epochSeconds, TimeOffset timeOffset)
Factory method.
ZoneRuleBroker rule
The Zone transition rule that matched for the the given year.
acetime_t toEpochSeconds() const
Return seconds since AceTime epoch 2000-01-01 00:00:00Z, after assuming that the date and time compon...
const LocalDate & localDate() const
Return the LocalDate.
TimeOffset getDeltaOffset(acetime_t epochSeconds) const override
Return the DST delta offset at epochSeconds.
const void * getZoneInfo() const override
Return the underlying ZoneInfo.
void log() const
Used only for debugging.
static const int8_t kErrorCode
Sentinel value that represents an error.
Definition: TimeOffset.h:61
An implementation of ZoneProcessor that supports a subset of the zones containing in the TZ Database...
static LocalDate forEpochSeconds(acetime_t epochSeconds)
Factory method using the number of seconds since AceTime epoch of 2000-01-01.
Definition: LocalDate.h:145
static TimeOffset forOffsetCode(int8_t offsetCode)
Create TimeOffset from the offset code.
Definition: TimeOffset.h:117
The classes provide a thin layer of indirection for accessing the zoneinfo files stored in the zonedb...
The date (year, month, day), time (hour, minute, second) and offset from UTC (timeOffset).
Data structure that defines the start of a specific UTC offset as described by the matching ZoneEra a...
OffsetDateTime getOffsetDateTime(const LocalDateTime &ldt) const override
acetime_t toEpochSeconds() const
Return seconds since AceTime epoch (2000-01-01 00:00:00Z), taking into account the offset zone...
A thin wrapper that represents a time offset from a reference point, usually 00:00 at UTC...
Definition: TimeOffset.h:58
static LocalDate forComponents(int16_t year, uint8_t month, uint8_t day)
Factory method using separated year, month and day fields.
Definition: LocalDate.h:94
The date (year, month, day) representing the date without regards to time zone.
Definition: LocalDate.h:36
static const int16_t kEpochYear
Base year of epoch.
Definition: LocalDate.h:39
int8_t offsetCode
The total effective UTC offsetCode at the start of transition, including DST offset.
static uint8_t daysInMonth(int16_t year, uint8_t month)
Return the number of days in the current month.
Definition: LocalDate.h:210
int8_t deltaCode
The delta offsetCode from "standard time" at the start of transition.
uint8_t month() const
Return the month with January=1, December=12.
Definition: LocalDate.h:231
int16_t year() const
Return the full year instead of just the last 2 digits.
Definition: LocalDate.h:219
static basic::MonthDay calcStartDayOfMonth(int16_t year, uint8_t month, uint8_t onDayOfWeek, int8_t onDayOfMonth)
Calculate the actual (month, day) of the expresssion (onDayOfWeek >= onDayOfMonth) or (onDayOfWeek <=...
Data broker for accessing ZonePolicy in PROGMEM.
Definition: Brokers.h:297
static OffsetDateTime forError()
Factory method that returns an instance whose isError() is true.
Class that holds the date-time as the components (year, month, day, hour, minute, second) without reg...
Definition: LocalDateTime.h:27
TimeOffset getUtcOffset(acetime_t epochSeconds) const override
Return the total UTC offset at epochSeconds, including DST offset.