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