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 
333  static basic::MonthDay calcStartDayOfMonth(int16_t year, uint8_t month,
334  uint8_t onDayOfWeek, int8_t onDayOfMonth) {
335  if (onDayOfWeek == 0) return {month, (uint8_t) onDayOfMonth};
336 
337  if (onDayOfMonth >= 0) {
338  // Convert "last{Xxx}" to "last{Xxx}>={daysInMonth-6}".
339  uint8_t daysInMonth = LocalDate::daysInMonth(year, month);
340  if (onDayOfMonth == 0) {
341  onDayOfMonth = daysInMonth - 6;
342  }
343 
344  auto limitDate = LocalDate::forComponents(year, month, onDayOfMonth);
345  uint8_t dayOfWeekShift = (onDayOfWeek - limitDate.dayOfWeek() + 7) % 7;
346  uint8_t day = (uint8_t) (onDayOfMonth + dayOfWeekShift);
347  if (day > daysInMonth) {
348  // TODO: Support shifting from Dec to Jan of following year.
349  day -= daysInMonth;
350  month++;
351  }
352  return {month, day};
353  } else {
354  onDayOfMonth = -onDayOfMonth;
355  auto limitDate = LocalDate::forComponents(year, month, onDayOfMonth);
356  int8_t dayOfWeekShift = (limitDate.dayOfWeek() - onDayOfWeek + 7) % 7;
357  int8_t day = onDayOfMonth - dayOfWeekShift;
358  if (day < 1) {
359  // TODO: Support shifting from Jan to Dec of the previous year.
360  month--;
361  uint8_t daysInPrevMonth = LocalDate::daysInMonth(year, month);
362  day += daysInPrevMonth;
363  }
364  return {month, (uint8_t) day};
365  }
366  }
367 
368  private:
369  friend class ::BasicZoneProcessorTest_init_primitives;
370  friend class ::BasicZoneProcessorTest_init;
371  friend class ::BasicZoneProcessorTest_setZoneInfo;
372  friend class ::BasicZoneProcessorTest_createAbbreviation;
373  friend class ::BasicZoneProcessorTest_calcStartDayOfMonth;
374  friend class ::BasicZoneProcessorTest_calcRuleOffsetCode;
375 
376  template<uint8_t SIZE, uint8_t TYPE, typename ZS, typename ZI, typename ZIB>
377  friend class ZoneProcessorCacheImpl; // setZoneInfo()
378 
380  static const uint8_t kMaxCacheEntries = 4;
381 
387  static const acetime_t kMinEpochSeconds = INT32_MIN + 1;
388 
389  // Disable copy constructor and assignment operator.
390  BasicZoneProcessor(const BasicZoneProcessor&) = delete;
391  BasicZoneProcessor& operator=(const BasicZoneProcessor&) = delete;
392 
393  bool equals(const ZoneProcessor& other) const override {
394  const auto& that = (const BasicZoneProcessor&) other;
395  return getZoneInfo() == that.getZoneInfo();
396  }
397 
399  void setZoneInfo(const void* zoneInfo) override {
400  if (mZoneInfo.zoneInfo() == zoneInfo) return;
401 
402  mZoneInfo = basic::ZoneInfoBroker((const basic::ZoneInfo*) zoneInfo);
403  mYear = 0;
404  mIsFilled = false;
405  mNumTransitions = 0;
406  }
407 
409  const basic::Transition* getTransition(acetime_t epochSeconds) const {
410  LocalDate ld = LocalDate::forEpochSeconds(epochSeconds);
411  bool success = init(ld);
412  return (success) ? findMatch(epochSeconds) : nullptr;
413  }
414 
443  bool init(const LocalDate& ld) const {
444  int16_t year = ld.year();
445  if (ld.month() == 1 && ld.day() == 1) {
446  year--;
447  }
448  if (isFilled(year)) return true;
449 
450  mYear = year;
451  mNumTransitions = 0; // clear cache
452 
453  if (year < mZoneInfo.startYear() - 1 || mZoneInfo.untilYear() < year) {
454  return false;
455  }
456 
457  addRulePriorToYear(year);
458  addRulesForYear(year);
459  calcTransitions();
460  calcAbbreviations();
461 
462  mIsFilled = true;
463  return true;
464  }
465 
467  bool isFilled(int16_t year) const {
468  return mIsFilled && (year == mYear);
469  }
470 
475  void addRulePriorToYear(int16_t year) const {
476  int8_t yearTiny = year - LocalDate::kEpochYear;
477  int8_t priorYearTiny = yearTiny - 1;
478 
479  // Find the prior Era.
480  const basic::ZoneEraBroker era = findZoneEraPriorTo(year);
481 
482  // If the prior ZoneEra is a simple Era (no zone policy), then create a
483  // Transition using a rule==nullptr. Otherwise, find the latest rule
484  // within the ZoneEra.
485  const basic::ZonePolicyBroker zonePolicy = era.zonePolicy();
486  basic::ZoneRuleBroker latest;
487  if (zonePolicy.isNotNull()) {
488  // Find the latest rule for the matching ZoneEra whose
489  // ZoneRule::toYearTiny < yearTiny. Assume that there are no more than
490  // 1 rule per month.
491  uint8_t numRules = zonePolicy.numRules();
492  for (uint8_t i = 0; i < numRules; i++) {
493  const basic::ZoneRuleBroker rule = zonePolicy.rule(i);
494  // Check if rule is effective prior to the given year
495  if (rule.fromYearTiny() < yearTiny) {
496  if ((latest.isNull()) || compareZoneRule(year, rule, latest) > 0) {
497  latest = rule;
498  }
499  }
500  }
501  }
502 
503  mPrevTransition = createTransition(era, latest, priorYearTiny);
504  }
505 
511  static basic::Transition createTransition(basic::ZoneEraBroker era,
513  int8_t offsetCode;
514  int8_t deltaCode;
515  char letter;
516  if (rule.isNull()) {
517  deltaCode = 0;
518  offsetCode = era.offsetCode();
519  letter = 0;
520  } else {
521  deltaCode = rule.deltaCode();
522  offsetCode = era.offsetCode() + deltaCode;
523  letter = rule.letter();
524  }
525 
526  return {
527  era,
528  rule,
529  0 /*epochSeconds*/,
530  yearTiny,
531  offsetCode,
532  deltaCode,
533  {letter} /*abbrev*/
534  };
535  }
536 
538  static int8_t compareZoneRule(int16_t year,
539  const basic::ZoneRuleBroker a, const basic::ZoneRuleBroker b) {
540  int16_t aYear = effectiveRuleYear(year, a);
541  int16_t bYear = effectiveRuleYear(year, b);
542  if (aYear < bYear) return -1;
543  if (aYear > bYear) return 1;
544  if (a.inMonth() < b.inMonth()) return -1;
545  if (a.inMonth() > b.inMonth()) return 1;
546  return 0;
547  }
548 
553  static int16_t effectiveRuleYear(int16_t year,
554  const basic::ZoneRuleBroker rule) {
555  int8_t yearTiny = year - LocalDate::kEpochYear;
556  if (rule.toYearTiny() < yearTiny) {
557  return rule.toYearTiny() + LocalDate::kEpochYear;
558  }
559  if (rule.fromYearTiny() < yearTiny) {
560  return year - 1;
561  }
562  return 0;
563  }
564 
566  void addRulesForYear(int16_t year) const {
567  const basic::ZoneEraBroker era = findZoneEra(year);
568 
569  // If the ZonePolicy has no rules, then add a Transition which takes
570  // effect at the start time of the current year.
571  const basic::ZonePolicyBroker zonePolicy = era.zonePolicy();
572  if (zonePolicy.isNull()) {
573  addRule(year, era, basic::ZoneRuleBroker());
574  return;
575  }
576 
577  // If the ZonePolicy has rules, find all matching transitions, and add
578  // them to mTransitions, in sorted order according to the
579  // ZoneRule::inMonth field.
580  int8_t yearTiny = year - LocalDate::kEpochYear;
581  uint8_t numRules = zonePolicy.numRules();
582  for (uint8_t i = 0; i < numRules; i++) {
583  const basic::ZoneRuleBroker rule = zonePolicy.rule(i);
584  if ((rule.fromYearTiny() <= yearTiny) &&
585  (yearTiny <= rule.toYearTiny())) {
586  addRule(year, era, rule);
587  }
588  }
589  }
590 
605  void addRule(int16_t year, basic::ZoneEraBroker era,
606  basic::ZoneRuleBroker rule) const {
607 
608  // If a zone needs more transitions than kMaxCacheEntries, the check below
609  // will cause the DST transition information to be inaccurate, and it is
610  // highly likely that this situation would be caught in the
611  // BasicValidationUsingPython or BasicValidationUsingJava unit tests.
612  // Since these unit tests pass, I feel confident that those zones which
613  // need more than kMaxCacheEntries are already filtered out by
614  // tzcompiler.py.
615  //
616  // Ideally, the tzcompiler.py script would explicitly remove those zones
617  // which need more than kMaxCacheEntries Transitions. But this would
618  // require a Python version of the BasicZoneProcessor, and unfortunately,
619  // zone_specifier.py implements only the ExtendedZoneProcessor algorithm
620  // An early version of zone_specifier.py may have implemented something
621  // close to BasicZoneProcessor, and it may be available in the git
622  // history. But it seems like too much work right now to try to dig that
623  // out, just to implement the explicit check for kMaxCacheEntries. It
624  // would mean maintaining another version of zone_specifier.py.
625  if (mNumTransitions >= kMaxCacheEntries) return;
626 
627  // insert new element at the end of the list
628  int8_t yearTiny = year - LocalDate::kEpochYear;
629  mTransitions[mNumTransitions] = createTransition(era, rule, yearTiny);
630  mNumTransitions++;
631 
632  // perform an insertion sort
633  for (uint8_t i = mNumTransitions - 1; i > 0; i--) {
634  basic::Transition& left = mTransitions[i - 1];
635  basic::Transition& right = mTransitions[i];
636  // assume only 1 rule per month
637  if ((left.rule.isNotNull() && right.rule.isNotNull() &&
638  left.rule.inMonth() > right.rule.inMonth())
639  || (left.rule.isNotNull() && right.rule.isNull())) {
640  basic::Transition tmp = left;
641  left = right;
642  right = tmp;
643  }
644  }
645  }
646 
652  const basic::ZoneEraBroker findZoneEra(int16_t year) const {
653  for (uint8_t i = 0; i < mZoneInfo.numEras(); i++) {
654  const basic::ZoneEraBroker era = mZoneInfo.era(i);
655  if (year < era.untilYearTiny() + LocalDate::kEpochYear) return era;
656  }
657  // Return the last ZoneEra if we run off the end.
658  return mZoneInfo.era(mZoneInfo.numEras() - 1);
659  }
660 
672  const basic::ZoneEraBroker findZoneEraPriorTo(int16_t year) const {
673  for (uint8_t i = 0; i < mZoneInfo.numEras(); i++) {
674  const basic::ZoneEraBroker era = mZoneInfo.era(i);
675  if (year <= era.untilYearTiny() + LocalDate::kEpochYear) return era;
676  }
677  // Return the last ZoneEra if we run off the end.
678  return mZoneInfo.era(mZoneInfo.numEras() - 1);
679  }
680 
688  void calcTransitions() const {
689  // Set the initial startEpochSeconds to be -Infinity
690  mPrevTransition.startEpochSeconds = kMinEpochSeconds;
691  const basic::Transition* prevTransition = &mPrevTransition;
692 
693  for (uint8_t i = 0; i < mNumTransitions; i++) {
694  basic::Transition& transition = mTransitions[i];
695  const int16_t year = transition.yearTiny + LocalDate::kEpochYear;
696 
697  if (transition.rule.isNull()) {
698  // If the transition is simple (has no named rule), then the
699  // ZoneEra applies for the entire year (since BasicZoneProcessor
700  // supports only whole year in the UNTIL field). The whole year UNTIL
701  // field has an implied 'w' modifier on 00:00, we don't need to call
702  // calcRuleOffsetCode() with a 'w', we can just use the previous
703  // transition's offset to calculate the startDateTime of this
704  // transition.
705  //
706  // Also, when transition.rule == nullptr, the mNumTransitions should
707  // be 1, since only a single transition is added by
708  // addRulesForYear().
709  const int8_t prevOffsetCode = prevTransition->offsetCode;
711  year, 1, 1, 0, 0, 0,
712  TimeOffset::forOffsetCode(prevOffsetCode));
713  transition.startEpochSeconds = startDateTime.toEpochSeconds();
714  } else {
715  // In this case, the transition points to a named ZonePolicy, which
716  // means that there could be multiple ZoneRules associated with the
717  // given year. For each transition, determine the startEpochSeconds,
718  // and the effective offset code.
719 
720  // Determine the start date of the rule.
721  const basic::MonthDay monthDay = calcStartDayOfMonth(
722  year, transition.rule.inMonth(), transition.rule.onDayOfWeek(),
723  transition.rule.onDayOfMonth());
724 
725  // Determine the offset of the 'atTimeModifier'. The 'w' modifier
726  // requires the offset of the previous transition.
727  const int8_t prevOffsetCode = calcRuleOffsetCode(
728  prevTransition->offsetCode,
729  transition.era.offsetCode(),
730  transition.rule.atTimeModifier());
731 
732  // startDateTime
733  const uint16_t minutes = transition.rule.atTimeMinutes();
734  const uint8_t atHour = minutes / 60;
735  const uint8_t atMinute = minutes % 60;
737  year, monthDay.month, monthDay.day,
738  atHour, atMinute, 0 /*second*/,
739  TimeOffset::forOffsetCode(prevOffsetCode));
740  transition.startEpochSeconds = startDateTime.toEpochSeconds();
741  }
742 
743  prevTransition = &transition;
744  }
745  }
746 
753  static int8_t calcRuleOffsetCode(int8_t prevEffectiveOffsetCode,
754  int8_t currentBaseOffsetCode, uint8_t atModifier) {
755  if (atModifier == basic::ZoneContext::TIME_MODIFIER_W) {
756  return prevEffectiveOffsetCode;
757  } else if (atModifier == basic::ZoneContext::TIME_MODIFIER_S) {
758  return currentBaseOffsetCode;
759  } else { // 'u', 'g' or 'z'
760  return 0;
761  }
762  }
763 
765  void calcAbbreviations() const {
766  calcAbbreviation(&mPrevTransition);
767  for (uint8_t i = 0; i < mNumTransitions; i++) {
768  calcAbbreviation(&mTransitions[i]);
769  }
770  }
771 
773  static void calcAbbreviation(basic::Transition* transition) {
774  createAbbreviation(
775  transition->abbrev,
777  transition->era.format(),
778  transition->deltaCode,
779  transition->abbrev[0]);
780  }
781 
817  static void createAbbreviation(char* dest, uint8_t destSize,
818  const char* format, uint8_t deltaCode, char letter) {
819  // Check if RULES column empty.
820  if (deltaCode == 0 && letter == '\0') {
821  strncpy(dest, format, destSize);
822  dest[destSize - 1] = '\0';
823  return;
824  }
825 
826  // Check if FORMAT contains a '%'.
827  if (strchr(format, '%') != nullptr) {
828  copyAndReplace(dest, destSize, format, '%', letter);
829  } else {
830  // Check if FORMAT contains a '/'.
831  const char* slashPos = strchr(format, '/');
832  if (slashPos != nullptr) {
833  if (deltaCode == 0) {
834  uint8_t headLength = (slashPos - format);
835  if (headLength >= destSize) headLength = destSize - 1;
836  memcpy(dest, format, headLength);
837  dest[headLength] = '\0';
838  } else {
839  uint8_t tailLength = strlen(slashPos+1);
840  if (tailLength >= destSize) tailLength = destSize - 1;
841  memcpy(dest, slashPos+1, tailLength);
842  dest[tailLength] = '\0';
843  }
844  } else {
845  // Just copy the FORMAT disregarding the deltaCode and letter.
846  strncpy(dest, format, destSize);
847  dest[destSize - 1] = '\0';
848  }
849  }
850  }
851 
857  static void copyAndReplace(char* dst, uint8_t dstSize, const char* src,
858  char oldChar, char newChar) {
859  while (*src != '\0' && dstSize > 0) {
860  if (*src == oldChar) {
861  if (newChar == '-') {
862  src++;
863  } else {
864  *dst = newChar;
865  dst++;
866  src++;
867  dstSize--;
868  }
869  } else {
870  *dst++ = *src++;
871  dstSize--;
872  }
873  }
874 
875  if (dstSize == 0) {
876  --dst;
877  }
878  *dst = '\0';
879  }
880 
882  const basic::Transition* findMatch(acetime_t epochSeconds) const {
883  const basic::Transition* closestMatch = &mPrevTransition;
884  for (uint8_t i = 0; i < mNumTransitions; i++) {
885  const basic::Transition* m = &mTransitions[i];
886  if (m->startEpochSeconds <= epochSeconds) {
887  closestMatch = m;
888  }
889  }
890  return closestMatch;
891  }
892 
893  basic::ZoneInfoBroker mZoneInfo;
894 
895  mutable int16_t mYear = 0; // maybe create LocalDate::kInvalidYear?
896  mutable bool mIsFilled = false;
897  mutable uint8_t mNumTransitions = 0;
898  mutable basic::Transition mTransitions[kMaxCacheEntries];
899  mutable basic::Transition mPrevTransition; // previous year's transition
900 };
901 
902 }
903 
904 #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_MODIFIER_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_MODIFIER_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.