AceTime  0.7
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 #define ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG 0
21 
22 class BasicZoneProcessorTest_init_primitives;
23 class BasicZoneProcessorTest_init;
24 class BasicZoneProcessorTest_setZoneInfo;
25 class BasicZoneProcessorTest_createAbbreviation;
26 class BasicZoneProcessorTest_calcStartDayOfMonth;
27 class BasicZoneProcessorTest_calcRuleOffsetCode;
28 
29 namespace ace_time {
30 
31 template<uint8_t SIZE, uint8_t TYPE, typename ZS, typename ZI, typename ZIB>
33 
34 namespace basic {
35 
51 struct Transition {
59  static const uint8_t kAbbrevSize = 6 + 1;
60 
67 
79 
81  acetime_t startEpochSeconds;
82 
84  int8_t yearTiny;
85 
91  int8_t offsetCode;
92 
94  int8_t deltaCode;
95 
104 
106  void log() const {
107  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
108  if (sizeof(acetime_t) == sizeof(int)) {
109  logging::printf("startEpochSeconds: %d\n", startEpochSeconds);
110  } else {
111  logging::printf("startEpochSeconds: %ld\n", startEpochSeconds);
112  }
113  logging::printf("offsetCode: %d\n", offsetCode);
114  logging::printf("abbrev: %s\n", abbrev);
115  if (rule.isNotNull()) {
116  logging::printf("Rule.fromYear: %d\n", rule.fromYearTiny());
117  logging::printf("Rule.toYear: %d\n", rule.toYearTiny());
118  logging::printf("Rule.inMonth: %d\n", rule.inMonth());
119  logging::printf("Rule.onDayOfMonth: %d\n", rule.onDayOfMonth());
120  }
121  }
122  }
123 };
124 
126 struct MonthDay {
127  uint8_t month;
128  uint8_t day;
129 };
130 
131 } // namespace basic
132 
185  public:
190  explicit BasicZoneProcessor(const basic::ZoneInfo* zoneInfo = nullptr):
191  ZoneProcessor(kTypeBasic),
192  mZoneInfo(zoneInfo) {}
193 
195  const void* getZoneInfo() const override {
196  return mZoneInfo.zoneInfo();
197  }
198 
199  uint32_t getZoneId() const override { return mZoneInfo.zoneId(); }
200 
201  TimeOffset getUtcOffset(acetime_t epochSeconds) const override {
202  const basic::Transition* transition = getTransition(epochSeconds);
203  int8_t code = (transition)
204  ? transition->offsetCode : TimeOffset::kErrorCode;
205  return TimeOffset::forOffsetCode(code);
206  }
207 
208  TimeOffset getDeltaOffset(acetime_t epochSeconds) const override {
209  const basic::Transition* transition = getTransition(epochSeconds);
210  int8_t code = (transition)
211  ? transition->deltaCode : TimeOffset::kErrorCode;
212  return TimeOffset::forOffsetCode(code);
213  }
214 
215  const char* getAbbrev(acetime_t epochSeconds) const override {
216  const basic::Transition* transition = getTransition(epochSeconds);
217  return (transition) ? transition->abbrev : "";
218  }
219 
249  OffsetDateTime getOffsetDateTime(const LocalDateTime& ldt) const override {
250  // Only a single local variable of OffsetDateTime used, to allow Return
251  // Value Optimization (and save 20 bytes of flash for WorldClock).
252  OffsetDateTime odt;
253  bool success = init(ldt.localDate());
254  if (success) {
255  // 0) Use the UTC epochSeconds to get intial guess of offset.
256  acetime_t epochSeconds0 = ldt.toEpochSeconds();
257  auto offset0 = getUtcOffset(epochSeconds0);
258 
259  // 1) Use offset0 to get the next epochSeconds and offset.
260  odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset0);
261  acetime_t epochSeconds1 = odt.toEpochSeconds();
262  auto offset1 = getUtcOffset(epochSeconds1);
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 offset2 = getUtcOffset(epochSeconds2);
268 
269  // If offset1 and offset2 are equal, then we have an equilibrium
270  // and odt(1) must equal odt(2), so we can just return the last odt.
271  if (offset1.toOffsetCode() == offset2.toOffsetCode()) {
272  // pass
273  } else {
274  // Pick the later epochSeconds and offset
275  acetime_t epochSeconds;
276  TimeOffset offset;
277  if (epochSeconds1 > epochSeconds2) {
278  epochSeconds = epochSeconds1;
279  offset = offset1;
280  } else {
281  epochSeconds = epochSeconds2;
282  offset = offset2;
283  }
284  odt = OffsetDateTime::forEpochSeconds(epochSeconds, offset);
285  }
286  } else {
287  odt = OffsetDateTime::forError();
288  }
289 
290  return odt;
291  }
292 
293  void printTo(Print& printer) const override;
294 
295  void printShortTo(Print& printer) const override;
296 
298  void log() const {
299  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
300  if (!mIsFilled) {
301  logging::printf("*not initialized*\n");
302  return;
303  }
304  logging::printf("mYear: %d\n", mYear);
305  logging::printf("mNumTransitions: %d\n", mNumTransitions);
306  logging::printf("---- PrevTransition\n");
307  mPrevTransition.log();
308  for (int i = 0; i < mNumTransitions; i++) {
309  logging::printf("---- Transition: %d\n", i);
310  mTransitions[i].log();
311  }
312  }
313  }
314 
334  static basic::MonthDay calcStartDayOfMonth(int16_t year, uint8_t month,
335  uint8_t onDayOfWeek, int8_t onDayOfMonth) {
336  if (onDayOfWeek == 0) return {month, (uint8_t) onDayOfMonth};
337 
338  if (onDayOfMonth >= 0) {
339  // Convert "last{Xxx}" to "last{Xxx}>={daysInMonth-6}".
340  uint8_t daysInMonth = LocalDate::daysInMonth(year, month);
341  if (onDayOfMonth == 0) {
342  onDayOfMonth = daysInMonth - 6;
343  }
344 
345  auto limitDate = LocalDate::forComponents(year, month, onDayOfMonth);
346  uint8_t dayOfWeekShift = (onDayOfWeek - limitDate.dayOfWeek() + 7) % 7;
347  uint8_t day = (uint8_t) (onDayOfMonth + dayOfWeekShift);
348  if (day > daysInMonth) {
349  // TODO: Support shifting from Dec to Jan of following year.
350  day -= daysInMonth;
351  month++;
352  }
353  return {month, day};
354  } else {
355  onDayOfMonth = -onDayOfMonth;
356  auto limitDate = LocalDate::forComponents(year, month, onDayOfMonth);
357  int8_t dayOfWeekShift = (limitDate.dayOfWeek() - onDayOfWeek + 7) % 7;
358  int8_t day = onDayOfMonth - dayOfWeekShift;
359  if (day < 1) {
360  // TODO: Support shifting from Jan to Dec of the previous year.
361  month--;
362  uint8_t daysInPrevMonth = LocalDate::daysInMonth(year, month);
363  day += daysInPrevMonth;
364  }
365  return {month, (uint8_t) day};
366  }
367  }
368 
369  private:
370  friend class ::BasicZoneProcessorTest_init_primitives;
371  friend class ::BasicZoneProcessorTest_init;
372  friend class ::BasicZoneProcessorTest_setZoneInfo;
373  friend class ::BasicZoneProcessorTest_createAbbreviation;
374  friend class ::BasicZoneProcessorTest_calcStartDayOfMonth;
375  friend class ::BasicZoneProcessorTest_calcRuleOffsetCode;
376 
377  template<uint8_t SIZE, uint8_t TYPE, typename ZS, typename ZI, typename ZIB>
378  friend class ZoneProcessorCacheImpl; // setZoneInfo()
379 
381  static const uint8_t kMaxCacheEntries = 4;
382 
388  static const acetime_t kMinEpochSeconds = INT32_MIN + 1;
389 
390  // Disable copy constructor and assignment operator.
391  BasicZoneProcessor(const BasicZoneProcessor&) = delete;
392  BasicZoneProcessor& operator=(const BasicZoneProcessor&) = delete;
393 
394  bool equals(const ZoneProcessor& other) const override {
395  const auto& that = (const BasicZoneProcessor&) other;
396  return getZoneInfo() == that.getZoneInfo();
397  }
398 
410  void setZoneInfo(const void* zoneInfo) override {
411  if (mZoneInfo.zoneInfo() == zoneInfo) return;
412 
413  mZoneInfo = basic::ZoneInfoBroker((const basic::ZoneInfo*) zoneInfo);
414  mYear = 0;
415  mIsFilled = false;
416  mNumTransitions = 0;
417  }
418 
420  const basic::Transition* getTransition(acetime_t epochSeconds) const {
421  LocalDate ld = LocalDate::forEpochSeconds(epochSeconds);
422  bool success = init(ld);
423  return (success) ? findMatch(epochSeconds) : nullptr;
424  }
425 
454  bool init(const LocalDate& ld) const {
455  int16_t year = ld.year();
456  if (ld.month() == 1 && ld.day() == 1) {
457  year--;
458  }
459  if (isFilled(year)) return true;
460 
461  mYear = year;
462  mNumTransitions = 0; // clear cache
463 
464  if (year < mZoneInfo.startYear() - 1 || mZoneInfo.untilYear() < year) {
465  return false;
466  }
467 
468  addTransitionPriorToYear(year);
469  addTransitionsForYear(year);
470  calcTransitions();
471  calcAbbreviations();
472 
473  mIsFilled = true;
474  return true;
475  }
476 
478  bool isFilled(int16_t year) const {
479  return mIsFilled && (year == mYear);
480  }
481 
486  void addTransitionPriorToYear(int16_t year) const {
487  int8_t yearTiny = year - LocalDate::kEpochYear;
488  int8_t priorYearTiny = yearTiny - 1;
489 
490  // Find the prior Era.
491  const basic::ZoneEraBroker era = findZoneEraPriorTo(year);
492 
493  // If the prior ZoneEra is a simple Era (no zone policy), then create a
494  // Transition using a rule==nullptr. Otherwise, find the latest rule
495  // within the ZoneEra.
496  const basic::ZonePolicyBroker zonePolicy = era.zonePolicy();
497  basic::ZoneRuleBroker latest;
498  if (zonePolicy.isNotNull()) {
499  // Find the latest rule for the matching ZoneEra whose
500  // ZoneRule::toYearTiny < yearTiny. Assume that there are no more than
501  // 1 rule per month.
502  uint8_t numRules = zonePolicy.numRules();
503  for (uint8_t i = 0; i < numRules; i++) {
504  const basic::ZoneRuleBroker rule = zonePolicy.rule(i);
505  // Check if rule is effective prior to the given year
506  if (rule.fromYearTiny() < yearTiny) {
507  if ((latest.isNull()) || compareZoneRule(year, rule, latest) > 0) {
508  latest = rule;
509  }
510  }
511  }
512  }
513 
514  mPrevTransition = createTransition(era, latest, priorYearTiny);
515  }
516 
522  static basic::Transition createTransition(basic::ZoneEraBroker era,
524  int8_t deltaCode;
525  char letter;
526  if (rule.isNull()) {
527  deltaCode = era.deltaCode();
528  letter = '\0';
529  } else {
530  deltaCode = rule.deltaCode();
531  letter = rule.letter();
532  }
533  int8_t offsetCode = era.offsetCode() + deltaCode;
534 
535  return {
536  era,
537  rule,
538  0 /*epochSeconds*/,
539  yearTiny,
540  offsetCode,
541  deltaCode,
542  {letter} /*abbrev*/
543  };
544  }
545 
547  static int8_t compareZoneRule(int16_t year,
548  const basic::ZoneRuleBroker a, const basic::ZoneRuleBroker b) {
549  int16_t aYear = effectiveRuleYear(year, a);
550  int16_t bYear = effectiveRuleYear(year, b);
551  if (aYear < bYear) return -1;
552  if (aYear > bYear) return 1;
553  if (a.inMonth() < b.inMonth()) return -1;
554  if (a.inMonth() > b.inMonth()) return 1;
555  return 0;
556  }
557 
562  static int16_t effectiveRuleYear(int16_t year,
563  const basic::ZoneRuleBroker rule) {
564  int8_t yearTiny = year - LocalDate::kEpochYear;
565  if (rule.toYearTiny() < yearTiny) {
566  return rule.toYearTiny() + LocalDate::kEpochYear;
567  }
568  if (rule.fromYearTiny() < yearTiny) {
569  return year - 1;
570  }
571  return 0;
572  }
573 
575  void addTransitionsForYear(int16_t year) const {
576  const basic::ZoneEraBroker era = findZoneEra(year);
577 
578  // If the ZonePolicy has no rules, then add a Transition which takes
579  // effect at the start time of the current year.
580  const basic::ZonePolicyBroker zonePolicy = era.zonePolicy();
581  if (zonePolicy.isNull()) {
582  addTransition(year, era, basic::ZoneRuleBroker());
583  return;
584  }
585 
586  // If the ZonePolicy has rules, find all matching transitions, and add
587  // them to mTransitions, in sorted order according to the
588  // ZoneRule::inMonth field.
589  int8_t yearTiny = year - LocalDate::kEpochYear;
590  uint8_t numRules = zonePolicy.numRules();
591  for (uint8_t i = 0; i < numRules; i++) {
592  const basic::ZoneRuleBroker rule = zonePolicy.rule(i);
593  if ((rule.fromYearTiny() <= yearTiny) &&
594  (yearTiny <= rule.toYearTiny())) {
595  addTransition(year, era, rule);
596  }
597  }
598  }
599 
614  void addTransition(int16_t year, basic::ZoneEraBroker era,
615  basic::ZoneRuleBroker rule) const {
616 
617  // If a zone needs more transitions than kMaxCacheEntries, the check below
618  // will cause the DST transition information to be inaccurate, and it is
619  // highly likely that this situation would be caught in the
620  // 'tests/validation' unit tests. Since these unit tests pass, I feel
621  // confident that those zones which need more than kMaxCacheEntries are
622  // already filtered out by tzcompiler.py.
623  //
624  // Ideally, the tzcompiler.py script would explicitly remove those zones
625  // which need more than kMaxCacheEntries Transitions. But this would
626  // require a Python version of the BasicZoneProcessor, and unfortunately,
627  // zone_specifier.py implements only the ExtendedZoneProcessor algorithm
628  // An early version of zone_specifier.py may have implemented something
629  // close to BasicZoneProcessor, and it may be available in the git
630  // history. But it seems like too much work right now to try to dig that
631  // out, just to implement the explicit check for kMaxCacheEntries. It
632  // would mean maintaining another version of zone_specifier.py.
633  if (mNumTransitions >= kMaxCacheEntries) return;
634 
635  // insert new element at the end of the list
636  int8_t yearTiny = year - LocalDate::kEpochYear;
637  mTransitions[mNumTransitions] = createTransition(era, rule, yearTiny);
638  mNumTransitions++;
639 
640  // perform an insertion sort based on ZoneRule.inMonth()
641  for (uint8_t i = mNumTransitions - 1; i > 0; i--) {
642  basic::Transition& left = mTransitions[i - 1];
643  basic::Transition& right = mTransitions[i];
644  // assume only 1 rule per month
645  if ((left.rule.isNotNull() && right.rule.isNotNull() &&
646  left.rule.inMonth() > right.rule.inMonth())
647  || (left.rule.isNotNull() && right.rule.isNull())) {
648  basic::Transition tmp = left;
649  left = right;
650  right = tmp;
651  }
652  }
653  }
654 
660  const basic::ZoneEraBroker findZoneEra(int16_t year) const {
661  for (uint8_t i = 0; i < mZoneInfo.numEras(); i++) {
662  const basic::ZoneEraBroker era = mZoneInfo.era(i);
663  if (year < era.untilYearTiny() + LocalDate::kEpochYear) return era;
664  }
665  // Return the last ZoneEra if we run off the end.
666  return mZoneInfo.era(mZoneInfo.numEras() - 1);
667  }
668 
680  const basic::ZoneEraBroker findZoneEraPriorTo(int16_t year) const {
681  for (uint8_t i = 0; i < mZoneInfo.numEras(); i++) {
682  const basic::ZoneEraBroker era = mZoneInfo.era(i);
683  if (year <= era.untilYearTiny() + LocalDate::kEpochYear) return era;
684  }
685  // Return the last ZoneEra if we run off the end.
686  return mZoneInfo.era(mZoneInfo.numEras() - 1);
687  }
688 
696  void calcTransitions() const {
697  // Set the initial startEpochSeconds to be -Infinity
698  mPrevTransition.startEpochSeconds = kMinEpochSeconds;
699  const basic::Transition* prevTransition = &mPrevTransition;
700 
701  for (uint8_t i = 0; i < mNumTransitions; i++) {
702  basic::Transition& transition = mTransitions[i];
703  const int16_t year = transition.yearTiny + LocalDate::kEpochYear;
704 
705  if (transition.rule.isNull()) {
706  // If the transition is simple (has no named rule), then the
707  // ZoneEra applies for the entire year (since BasicZoneProcessor
708  // supports only whole year in the UNTIL field). The whole year UNTIL
709  // field has an implied 'w' suffix on 00:00, we don't need to call
710  // calcRuleOffsetCode() with a 'w', we can just use the previous
711  // transition's offset to calculate the startDateTime of this
712  // transition.
713  //
714  // Also, when transition.rule == nullptr, the mNumTransitions should
715  // be 1, since only a single transition is added by
716  // addTransitionsForYear().
717  const int8_t prevOffsetCode = prevTransition->offsetCode;
719  year, 1, 1, 0, 0, 0,
720  TimeOffset::forOffsetCode(prevOffsetCode));
721  transition.startEpochSeconds = startDateTime.toEpochSeconds();
722  } else {
723  // In this case, the transition points to a named ZonePolicy, which
724  // means that there could be multiple ZoneRules associated with the
725  // given year. For each transition, determine the startEpochSeconds,
726  // and the effective offset code.
727 
728  // Determine the start date of the rule.
729  const basic::MonthDay monthDay = calcStartDayOfMonth(
730  year, transition.rule.inMonth(), transition.rule.onDayOfWeek(),
731  transition.rule.onDayOfMonth());
732 
733  // Determine the offset of the 'atTimeSuffix'. The 'w' suffix
734  // requires the offset of the previous transition.
735  const int8_t prevOffsetCode = calcRuleOffsetCode(
736  prevTransition->offsetCode,
737  transition.era.offsetCode(),
738  transition.rule.atTimeSuffix());
739 
740  // startDateTime
741  const uint16_t minutes = transition.rule.atTimeMinutes();
742  const uint8_t atHour = minutes / 60;
743  const uint8_t atMinute = minutes % 60;
745  year, monthDay.month, monthDay.day,
746  atHour, atMinute, 0 /*second*/,
747  TimeOffset::forOffsetCode(prevOffsetCode));
748  transition.startEpochSeconds = startDateTime.toEpochSeconds();
749  }
750 
751  prevTransition = &transition;
752  }
753  }
754 
761  static int8_t calcRuleOffsetCode(int8_t prevEffectiveOffsetCode,
762  int8_t currentBaseOffsetCode, uint8_t atSuffix) {
763  if (atSuffix == basic::ZoneContext::TIME_SUFFIX_W) {
764  return prevEffectiveOffsetCode;
765  } else if (atSuffix == basic::ZoneContext::TIME_SUFFIX_S) {
766  return currentBaseOffsetCode;
767  } else { // 'u', 'g' or 'z'
768  return 0;
769  }
770  }
771 
773  void calcAbbreviations() const {
774  calcAbbreviation(&mPrevTransition);
775  for (uint8_t i = 0; i < mNumTransitions; i++) {
776  calcAbbreviation(&mTransitions[i]);
777  }
778  }
779 
781  static void calcAbbreviation(basic::Transition* transition) {
782  createAbbreviation(
783  transition->abbrev,
785  transition->era.format(),
786  transition->deltaCode,
787  transition->abbrev[0]);
788  }
789 
836  static void createAbbreviation(char* dest, uint8_t destSize,
837  const char* format, uint8_t deltaCode, char letter) {
838  // Check if FORMAT contains a '%'.
839  if (strchr(format, '%') != nullptr) {
840  // Check if RULES column empty, therefore no 'letter'
841  if (letter == '\0') {
842  strncpy(dest, format, destSize - 1);
843  dest[destSize - 1] = '\0';
844  } else {
845  copyAndReplace(dest, destSize, format, '%', letter);
846  }
847  } else {
848  // Check if FORMAT contains a '/'.
849  const char* slashPos = strchr(format, '/');
850  if (slashPos != nullptr) {
851  if (deltaCode == 0) {
852  uint8_t headLength = (slashPos - format);
853  if (headLength >= destSize) headLength = destSize - 1;
854  memcpy(dest, format, headLength);
855  dest[headLength] = '\0';
856  } else {
857  uint8_t tailLength = strlen(slashPos+1);
858  if (tailLength >= destSize) tailLength = destSize - 1;
859  memcpy(dest, slashPos+1, tailLength);
860  dest[tailLength] = '\0';
861  }
862  } else {
863  // Just copy the FORMAT disregarding the deltaCode and letter.
864  strncpy(dest, format, destSize - 1);
865  dest[destSize - 1] = '\0';
866  }
867  }
868  }
869 
875  static void copyAndReplace(char* dst, uint8_t dstSize, const char* src,
876  char oldChar, char newChar) {
877  while (*src != '\0' && dstSize > 0) {
878  if (*src == oldChar) {
879  if (newChar == '-') {
880  src++;
881  } else {
882  *dst = newChar;
883  dst++;
884  src++;
885  dstSize--;
886  }
887  } else {
888  *dst++ = *src++;
889  dstSize--;
890  }
891  }
892 
893  if (dstSize == 0) {
894  --dst;
895  }
896  *dst = '\0';
897  }
898 
900  const basic::Transition* findMatch(acetime_t epochSeconds) const {
901  const basic::Transition* closestMatch = &mPrevTransition;
902  for (uint8_t i = 0; i < mNumTransitions; i++) {
903  const basic::Transition* m = &mTransitions[i];
904  if (m->startEpochSeconds <= epochSeconds) {
905  closestMatch = m;
906  }
907  }
908  return closestMatch;
909  }
910 
911  basic::ZoneInfoBroker mZoneInfo;
912 
913  mutable int16_t mYear = 0; // maybe create LocalDate::kInvalidYear?
914  mutable bool mIsFilled = false;
915  mutable uint8_t mNumTransitions = 0;
916  mutable basic::Transition mTransitions[kMaxCacheEntries];
917  mutable basic::Transition mPrevTransition; // previous year's transition
918 };
919 
920 }
921 
922 #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...
Data broker for accessing ZonePolicy in PROGMEM.
Definition: Brokers.h:322
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:86
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:245
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:64
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:120
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:61
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 uint8_t TIME_SUFFIX_S
Represents &#39;s&#39; or standard time.
Definition: ZoneContext.h:16
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:239
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 <=...
static const uint8_t TIME_SUFFIX_W
Represents &#39;w&#39; or wall time.
Definition: ZoneContext.h:13
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.