AceTime  0.3
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.
BasicZoneSpecifier.h
1 #ifndef ACE_TIME_BASIC_ZONE_SPECIFIER_H
2 #define ACE_TIME_BASIC_ZONE_SPECIFIER_H
3 
4 #include <Arduino.h>
5 #include <string.h> // strchr()
6 #include <stdint.h>
7 #include "common/ZonePolicy.h"
8 #include "common/ZoneInfo.h"
9 #include "common/logger.h"
10 #include "TimeOffset.h"
11 #include "LocalDate.h"
12 #include "OffsetDateTime.h"
13 #include "ZoneSpecifier.h"
14 
15 class BasicZoneSpecifierTest_init_primitives;
16 class BasicZoneSpecifierTest_init;
17 class BasicZoneSpecifierTest_createAbbreviation;
18 class BasicZoneSpecifierTest_calcStartDayOfMonth;
19 class BasicZoneSpecifierTest_calcRuleOffsetCode;
20 
21 namespace ace_time {
22 
23 namespace basic {
24 
31 struct Transition {
39  static const uint8_t kAbbrevSize = 6 + 1;
40 
42  const ZoneEra* era;
43 
51  const ZoneRule* rule;
52 
54  int8_t yearTiny;
55 
57  acetime_t startEpochSeconds;
58 
64  int8_t offsetCode;
65 
68 
70  void log() const {
71  if (sizeof(acetime_t) == sizeof(int)) {
72  logging::println("startEpochSeconds: %d", startEpochSeconds);
73  } else {
74  logging::println("startEpochSeconds: %ld", startEpochSeconds);
75  }
76  logging::println("offsetCode: %d", offsetCode);
77  logging::println("abbrev: %s", abbrev);
78  if (rule != nullptr) {
79  logging::println("Rule.fromYear: %d", rule->fromYearTiny);
80  logging::println("Rule.toYear: %d", rule->toYearTiny);
81  logging::println("Rule.inMonth: %d", rule->inMonth);
82  logging::println("Rule.onDayOfMonth: %d", rule->onDayOfMonth);
83  }
84  }
85 };
86 
87 } // namespace basic
88 
141  public:
146  explicit BasicZoneSpecifier(const basic::ZoneInfo* zoneInfo):
147  ZoneSpecifier(kTypeBasic),
148  mZoneInfo(zoneInfo) {}
149 
151  const basic::ZoneInfo* getZoneInfo() const { return mZoneInfo; }
152 
153  TimeOffset getUtcOffset(acetime_t epochSeconds) const override {
154  const basic::Transition* transition = getTransition(epochSeconds);
155  int8_t code = (transition)
156  ? transition->offsetCode : TimeOffset::kErrorCode;
157  return TimeOffset::forOffsetCode(code);
158  }
159 
160  TimeOffset getDeltaOffset(acetime_t epochSeconds) const override {
161  const basic::Transition* transition = getTransition(epochSeconds);
162  int8_t code;
163  if (!transition) {
164  code = TimeOffset::kErrorCode;
165  } else if (transition->rule == nullptr) {
166  code = 0;
167  } else {
168  code = transition->rule->deltaCode;
169  }
170  return TimeOffset::forOffsetCode(code);
171  }
172 
173  const char* getAbbrev(acetime_t epochSeconds) const override {
174  const basic::Transition* transition = getTransition(epochSeconds);
175  return (transition) ? transition->abbrev : "";
176  }
177 
193  OffsetDateTime getOffsetDateTime(const LocalDateTime& ldt) const override {
194  // Only a single local variable of OffsetDateTime used, to allow Return
195  // Value Optimization (and save 20 bytes of flash for WorldClock).
196  OffsetDateTime odt;
197  bool success = init(ldt.localDate());
198  if (success) {
199  // 0) Use the UTC epochSeconds to get intial guess of offset.
200  acetime_t epochSeconds0 = ldt.toEpochSeconds();
201  auto offset0 = getUtcOffset(epochSeconds0);
202 
203  // 1) Use offset0 to get the next epochSeconds and offset.
204  odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset0);
205  acetime_t epochSeconds1 = odt.toEpochSeconds();
206  auto offset1 = getUtcOffset(epochSeconds1);
207 
208  // 2) Use offset1 to get the next epochSeconds and offset.
209  odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset1);
210  acetime_t epochSeconds2 = odt.toEpochSeconds();
211  auto offset2 = getUtcOffset(epochSeconds2);
212 
213  // If offset1 and offset2 are equal, then we have an equilibrium
214  // and odt(1) must equal odt(2), so we can just return the last odt.
215  if (offset1.toOffsetCode() == offset2.toOffsetCode()) {
216  // pass
217  } else {
218  // Pick the later epochSeconds and offset
219  acetime_t epochSeconds;
220  TimeOffset offset;
221  if (epochSeconds1 > epochSeconds2) {
222  epochSeconds = epochSeconds1;
223  offset = offset1;
224  } else {
225  epochSeconds = epochSeconds2;
226  offset = offset2;
227  }
228  odt = OffsetDateTime::forEpochSeconds(epochSeconds, offset);
229  }
230  } else {
231  odt = OffsetDateTime::forError();
232  }
233 
234  return odt;
235  }
236 
238  void printTo(Print& printer) const override;
239 
241  void log() const {
242  if (!mIsFilled) {
243  logging::println("*not initialized*");
244  return;
245  }
246  logging::println("mYear: %d", mYear);
247  logging::println("mNumTransitions: %d", mNumTransitions);
248  logging::println("---- PrevTransition");
249  mPrevTransition.log();
250  for (int i = 0; i < mNumTransitions; i++) {
251  logging::println("---- Transition: %d", i);
252  mTransitions[i].log();
253  }
254  }
255 
264  static uint8_t calcStartDayOfMonth(int16_t year, uint8_t month,
265  uint8_t onDayOfWeek, uint8_t onDayOfMonth) {
266  if (onDayOfWeek == 0) return onDayOfMonth;
267 
268 
269  // Convert "last{Xxx}" to "last{Xxx}>={daysInMonth-6}".
270  if (onDayOfMonth == 0) {
271  onDayOfMonth = LocalDate::daysInMonth(year, month) - 6;
272  }
273 
275  year, month, onDayOfMonth);
276  uint8_t dayOfWeekShift = (onDayOfWeek - limitDate.dayOfWeek() + 7) % 7;
277  return onDayOfMonth + dayOfWeekShift;
278  }
279 
280  private:
281  friend class ::BasicZoneSpecifierTest_init_primitives;
282  friend class ::BasicZoneSpecifierTest_init;
283  friend class ::BasicZoneSpecifierTest_createAbbreviation;
284  friend class ::BasicZoneSpecifierTest_calcStartDayOfMonth;
285  friend class ::BasicZoneSpecifierTest_calcRuleOffsetCode;
286 
288  static const uint8_t kMaxCacheEntries = 4;
289 
295  static const acetime_t kMinEpochSeconds = INT32_MIN + 1;
296 
297  // Disable copy constructor and assignment operator.
298  BasicZoneSpecifier(const BasicZoneSpecifier&) = delete;
299  BasicZoneSpecifier& operator=(const BasicZoneSpecifier&) = delete;
300 
301  bool equals(const ZoneSpecifier& other) const override {
302  const auto& that = (const BasicZoneSpecifier&) other;
303  return getZoneInfo() == that.getZoneInfo();
304  }
305 
307  const basic::Transition* getTransition(acetime_t epochSeconds) const {
308  LocalDate ld = LocalDate::forEpochSeconds(epochSeconds);
309  bool success = init(ld);
310  return (success) ? findMatch(epochSeconds) : nullptr;
311  }
312 
341  bool init(const LocalDate& ld) const {
342  int16_t year = ld.year();
343  if (ld.month() == 1 && ld.day() == 1) {
344  year--;
345  }
346  if (isFilled(year)) return true;
347 
348  mYear = year;
349  mNumTransitions = 0; // clear cache
350 
351  if (year < mZoneInfo->zoneContext->startYear - 1
352  || mZoneInfo->zoneContext->untilYear < year) {
353  return false;
354  }
355 
356  addRulePriorToYear(year);
357  addRulesForYear(year);
358  calcTransitions();
359  calcAbbreviations();
360 
361  mIsFilled = true;
362  return true;
363  }
364 
366  bool isFilled(int16_t year) const {
367  return mIsFilled && (year == mYear);
368  }
369 
374  void addRulePriorToYear(int16_t year) const {
375  int8_t yearTiny = year - LocalDate::kEpochYear;
376  int8_t priorYearTiny = yearTiny - 1;
377 
378  // Find the prior Era.
379  const basic::ZoneEra* const era = findZoneEraPriorTo(year);
380 
381  // If the prior ZoneEra is a simple Era (no zone policy), then create a
382  // Transition using a rule==nullptr. Otherwise, find the latest rule
383  // within the ZoneEra.
384  const basic::ZonePolicy* const zonePolicy = era->zonePolicy;
385  const basic::ZoneRule* latest = nullptr;
386  if (zonePolicy != nullptr) {
387  // Find the latest rule for the matching ZoneEra whose
388  // ZoneRule::toYearTiny < yearTiny. Assume that there are no more than
389  // 1 rule per month.
390  for (uint8_t i = 0; i < zonePolicy->numRules; i++) {
391  const basic::ZoneRule* const rule = &zonePolicy->rules[i];
392  // Check if rule is effective prior to the given year
393  if (rule->fromYearTiny < yearTiny) {
394  if ((latest == nullptr)
395  || compareZoneRule(year, rule, latest) > 0) {
396  latest = rule;
397  }
398  }
399  }
400  }
401 
402  mPrevTransition = {
403  era,
404  latest /*rule*/,
405  priorYearTiny /*yearTiny*/,
406  0 /*epochSeconds*/,
407  0 /*offsetCode*/,
408  {0} /*abbrev*/
409  };
410  }
411 
413  static int8_t compareZoneRule(int16_t year,
414  const basic::ZoneRule* a, const basic::ZoneRule* b) {
415  int16_t aYear = effectiveRuleYear(year, a);
416  int16_t bYear = effectiveRuleYear(year, b);
417  if (aYear < bYear) return -1;
418  if (aYear > bYear) return 1;
419  if (a->inMonth < b->inMonth) return -1;
420  if (a->inMonth > b->inMonth) return 1;
421  return 0;
422  }
423 
428  static int16_t effectiveRuleYear(int16_t year,
429  const basic::ZoneRule* rule) {
430  int8_t yearTiny = year - LocalDate::kEpochYear;
431  if (rule->toYearTiny < yearTiny) {
432  return rule->toYearTiny + LocalDate::kEpochYear;
433  }
434  if (rule->fromYearTiny < yearTiny) {
435  return year - 1;
436  }
437  return 0;
438  }
439 
441  void addRulesForYear(int16_t year) const {
442  const basic::ZoneEra* const era = findZoneEra(year);
443 
444  // If the ZonePolicy has no rules, then add a Transition which takes
445  // effect at the start time of the current year.
446  const basic::ZonePolicy* const zonePolicy = era->zonePolicy;
447  if (zonePolicy == nullptr) {
448  addRule(year, era, nullptr);
449  return;
450  }
451 
452  // If the ZonePolicy has rules, find all matching transitions, and add
453  // them to mTransitions, in sorted order according to the
454  // ZoneRule::inMonth field.
455  int8_t yearTiny = year - LocalDate::kEpochYear;
456  for (uint8_t i = 0; i < zonePolicy->numRules; i++) {
457  const basic::ZoneRule* const rule = &zonePolicy->rules[i];
458  if ((rule->fromYearTiny <= yearTiny) &&
459  (yearTiny <= rule->toYearTiny)) {
460  addRule(year, era, rule);
461  }
462  }
463  }
464 
479  void addRule(int16_t year, const basic::ZoneEra* era,
480  const basic::ZoneRule* rule) const {
481 
482  // If a zone needs more transitions than kMaxCacheEntries, the check below
483  // will cause the DST transition information to be inaccurate, and it is
484  // highly likely that this situation would be caught in the
485  // BasicValidationUsingPython or BasicValidationUsingJava unit tests.
486  // Since these unit tests pass, I feel confident that those zones which
487  // need more than kMaxCacheEntries are already filtered out by
488  // tzcompiler.py.
489  //
490  // Ideally, the tzcompiler.py script would explicitly remove those zones
491  // which need more than kMaxCacheEntries Transitions. But this would
492  // require a Python version of the BasicZoneSpecifier, and unfortunately,
493  // zone_specifier.py implements only the ExtendedZoneSpecifier algorithm
494  // An early version of zone_specifier.py may have implemented something
495  // close to BasicZoneSpecifier, and it may be available in the git
496  // history. But it seems like too much work right now to try to dig that
497  // out, just to implement the explicit check for kMaxCacheEntries. It
498  // would mean maintaining another version of zone_specifier.py.
499  if (mNumTransitions >= kMaxCacheEntries) return;
500 
501  // insert new element at the end of the list
502  int8_t yearTiny = year - LocalDate::kEpochYear;
503  mTransitions[mNumTransitions] = {
504  era,
505  rule,
506  yearTiny,
507  0 /*epochSeconds*/,
508  0 /*offsetCode*/,
509  {0} /*abbrev*/
510  };
511  mNumTransitions++;
512 
513  // perform an insertion sort
514  for (uint8_t i = mNumTransitions - 1; i > 0; i--) {
515  basic::Transition& left = mTransitions[i - 1];
516  basic::Transition& right = mTransitions[i];
517  // assume only 1 rule per month
518  if ((left.rule != nullptr && right.rule != nullptr &&
519  left.rule->inMonth > right.rule->inMonth)
520  || (left.rule != nullptr && right.rule == nullptr)) {
521  basic::Transition tmp = left;
522  left = right;
523  right = tmp;
524  }
525  }
526  }
527 
533  const basic::ZoneEra* findZoneEra(int16_t year) const {
534  for (uint8_t i = 0; i < mZoneInfo->numEras; i++) {
535  const basic::ZoneEra* era = &mZoneInfo->eras[i];
536  if (year < era->untilYearTiny + LocalDate::kEpochYear) return era;
537  }
538  // Return the last ZoneEra if we run off the end.
539  return &mZoneInfo->eras[mZoneInfo->numEras - 1];
540  }
541 
553  const basic::ZoneEra* findZoneEraPriorTo(int16_t year) const {
554  for (uint8_t i = 0; i < mZoneInfo->numEras; i++) {
555  const basic::ZoneEra* era = &mZoneInfo->eras[i];
556  if (year <= era->untilYearTiny + LocalDate::kEpochYear) return era;
557  }
558  // Return the last ZoneEra if we run off the end.
559  return &mZoneInfo->eras[mZoneInfo->numEras - 1];
560  }
561 
563  void calcTransitions() const {
564  // Calculate epochSeconds and offsetCode for the prevTransition.
565  mPrevTransition.startEpochSeconds = kMinEpochSeconds;
566  int8_t deltaCode = (mPrevTransition.rule == nullptr)
567  ? 0 : mPrevTransition.rule->deltaCode;
568  mPrevTransition.offsetCode = mPrevTransition.era->offsetCode + deltaCode;
569  const basic::Transition* prevTransition = &mPrevTransition;
570 
571  // Loop through Transition items to calculate:
572  // 1) Transition::startEpochSeconds
573  // 2) Transition::offsetCode
574  for (uint8_t i = 0; i < mNumTransitions; i++) {
575  basic::Transition& transition = mTransitions[i];
576  const int16_t year = transition.yearTiny + LocalDate::kEpochYear;
577 
578  if (transition.rule == nullptr) {
579  // If the transition is simple (has no named rule), then the
580  // ZoneEra applies for the entire year (since BasicZoneSpecifier
581  // supports only whole year in the UNTIL field). The whole year UNTIL
582  // field has an implied 'w' modifier on 00:00, we don't need to call
583  // calcRuleOffsetCode() with a 'w', we can just use the previous
584  // transition's offset to calculate the startDateTime of this
585  // transition.
586  //
587  // Also, when transition.rule == nullptr, the mNumTransitions should
588  // be 1, since only a single transition is added by
589  // addRulesForYear().
590  const int8_t offsetCode = prevTransition->offsetCode;
592  year, 1, 1, 0, 0, 0,
593  TimeOffset::forOffsetCode(offsetCode));
594  transition.startEpochSeconds = startDateTime.toEpochSeconds();
595  transition.offsetCode = transition.era->offsetCode;
596  } else {
597  // In this case, the transition points to a named ZonePolicy, which
598  // means that there could be multiple ZoneRules associated with the
599  // given year. For each transition, determine the startEpochSeconds,
600  // and the effective offset code.
601 
602  // Determine the start date of the rule.
603  const uint8_t startDayOfMonth = calcStartDayOfMonth(
604  year, transition.rule->inMonth, transition.rule->onDayOfWeek,
605  transition.rule->onDayOfMonth);
606 
607  // Determine the offset of the 'atTimeModifier'. The 'w' modifier
608  // requires the offset of the previous transition.
609  const int8_t offsetCode = calcRuleOffsetCode(
610  prevTransition->offsetCode,
611  transition.era->offsetCode,
612  transition.rule->atTimeModifier);
613 
614  // startDateTime
615  const uint8_t atHour = transition.rule->atTimeCode / 4;
616  const uint8_t atMinute = (transition.rule->atTimeCode % 4) * 15;
618  year, transition.rule->inMonth, startDayOfMonth,
619  atHour, atMinute, 0 /*second*/,
620  TimeOffset::forOffsetCode(offsetCode));
621  transition.startEpochSeconds = startDateTime.toEpochSeconds();
622 
623  // Determine the effective offset code
624  transition.offsetCode =
625  transition.era->offsetCode + transition.rule->deltaCode;
626  }
627 
628  prevTransition = &transition;
629  }
630  }
631 
638  static int8_t calcRuleOffsetCode(int8_t prevEffectiveOffsetCode,
639  int8_t currentBaseOffsetCode, uint8_t atModifier) {
640  if (atModifier == 'w') {
641  return prevEffectiveOffsetCode;
642  } else if (atModifier == 's') {
643  return currentBaseOffsetCode;
644  } else { // 'u', 'g' or 'z'
645  return 0;
646  }
647  }
648 
650  void calcAbbreviations() const {
651  calcAbbreviation(&mPrevTransition);
652  for (uint8_t i = 0; i < mNumTransitions; i++) {
653  calcAbbreviation(&mTransitions[i]);
654  }
655  }
656 
658  static void calcAbbreviation(basic::Transition* transition) {
659  createAbbreviation(
660  transition->abbrev,
662  transition->era->format,
663  (transition->rule) ? transition->rule->deltaCode : 0,
664  (transition->rule) ? transition->rule->letter : '\0');
665  }
666 
702  static void createAbbreviation(char* dest, uint8_t destSize,
703  const char* format, uint8_t deltaCode, char letter) {
704  // Check if RULES column empty.
705  if (deltaCode == 0 && letter == '\0') {
706  strncpy(dest, format, destSize);
707  dest[destSize - 1] = '\0';
708  return;
709  }
710 
711  // Check if FORMAT contains a '%'.
712  if (strchr(format, '%') != nullptr) {
713  copyAndReplace(dest, destSize, format, '%', letter);
714  } else {
715  // Check if FORMAT contains a '/'.
716  const char* slashPos = strchr(format, '/');
717  if (slashPos != nullptr) {
718  if (deltaCode == 0) {
719  uint8_t headLength = (slashPos - format);
720  if (headLength >= destSize) headLength = destSize - 1;
721  memcpy(dest, format, headLength);
722  dest[headLength] = '\0';
723  } else {
724  uint8_t tailLength = strlen(slashPos+1);
725  if (tailLength >= destSize) tailLength = destSize - 1;
726  memcpy(dest, slashPos+1, tailLength);
727  dest[tailLength] = '\0';
728  }
729  } else {
730  // Just copy the FORMAT disregarding the deltaCode and letter.
731  strncpy(dest, format, destSize);
732  dest[destSize - 1] = '\0';
733  }
734  }
735  }
736 
742  static void copyAndReplace(char* dst, uint8_t dstSize, const char* src,
743  char oldChar, char newChar) {
744  while (*src != '\0' && dstSize > 0) {
745  if (*src == oldChar) {
746  if (newChar == '-') {
747  src++;
748  } else {
749  *dst = newChar;
750  dst++;
751  src++;
752  dstSize--;
753  }
754  } else {
755  *dst++ = *src++;
756  dstSize--;
757  }
758  }
759 
760  if (dstSize == 0) {
761  --dst;
762  }
763  *dst = '\0';
764  }
765 
767  const basic::Transition* findMatch(acetime_t epochSeconds) const {
768  const basic::Transition* closestMatch = &mPrevTransition;
769  for (uint8_t i = 0; i < mNumTransitions; i++) {
770  const basic::Transition* m = &mTransitions[i];
771  if (m->startEpochSeconds <= epochSeconds) {
772  closestMatch = m;
773  }
774  }
775  return closestMatch;
776  }
777 
778  const basic::ZoneInfo* const mZoneInfo;
779 
780  mutable int16_t mYear = 0;
781  mutable bool mIsFilled = false;
782  mutable uint8_t mNumTransitions = 0;
783  mutable basic::Transition mTransitions[kMaxCacheEntries];
784  mutable basic::Transition mPrevTransition; // previous year's transition
785 };
786 
787 }
788 
789 #endif
const char * getAbbrev(acetime_t epochSeconds) const override
Return the time zone abbreviation at epochSeconds.
static uint8_t calcStartDayOfMonth(int16_t year, uint8_t month, uint8_t onDayOfWeek, uint8_t onDayOfMonth)
Calculate the actual dayOfMonth of the expresssion (onDayOfWeek >= onDayOfMonth). ...
An entry in ZoneInfo which describes which ZonePolicy was being followed during a particular time per...
Definition: ZoneInfo.h:15
const ZoneEra * era
The ZoneEra that matched the given year.
void log() const
Used only for debugging.
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.
A collection of transition rules which describe the DST rules of a given administrative region...
Definition: ZonePolicy.h:91
static OffsetDateTime forLocalDateTimeAndOffset(const LocalDateTime &localDateTime, TimeOffset timeOffset)
Factory method from LocalDateTime and TimeOffset.
OffsetDateTime getOffsetDateTime(const LocalDateTime &ldt) const override
Representation of a given time zone, implemented as an array of ZoneEra records.
Definition: ZoneInfo.h:72
uint8_t const inMonth
Determined by the IN column.
Definition: ZonePolicy.h:21
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.
int8_t const deltaCode
Determined by the SAVE column, containing the offset from UTC, in 15-min increments.
Definition: ZonePolicy.h:60
uint8_t day() const
Return the day of the month.
Definition: LocalDate.h:231
int8_t yearTiny
Year which applies to the ZoneEra or ZoneRule.
static OffsetDateTime forEpochSeconds(acetime_t epochSeconds, TimeOffset timeOffset)
Factory method.
Base interface for ZoneSpecifier classes.
Definition: ZoneSpecifier.h:40
TimeOffset getDeltaOffset(acetime_t epochSeconds) const override
Return the DST delta offset at epochSeconds.
acetime_t toEpochSeconds() const
Return seconds since AceTime epoch 2000-01-01 00:00:00Z, after assuming that the date and time compon...
int8_t const deltaCode
If zonePolicy is nullptr, then this indicates the DST offset in 15 minute increments.
Definition: ZoneInfo.h:32
const LocalDate & localDate() const
Return the LocalDate.
const ZoneRule * rule
The Zone transition rule that matched for the the given year.
uint8_t const atTimeModifier
Determined by the suffix in the AT column: &#39;w&#39;=wall; &#39;s&#39;=standard; &#39;u&#39;=meridian (&#39;g&#39; and &#39;z&#39; mean the...
Definition: ZonePolicy.h:54
static const int8_t kErrorCode
Sentinel value that represents an error.
Definition: TimeOffset.h:56
static LocalDate forEpochSeconds(acetime_t epochSeconds)
Factory method using the number of seconds since AceTime epoch of 2000-01-01.
Definition: LocalDate.h:139
static TimeOffset forOffsetCode(int8_t offsetCode)
Create TimeOffset from the offset code.
Definition: TimeOffset.h:112
const ZonePolicy *const zonePolicy
Zone policy, determined by the RULES column.
Definition: ZoneInfo.h:26
TimeOffset getUtcOffset(acetime_t epochSeconds) const override
Return the total UTC offset at epochSeconds, including DST offset.
The date (year, month, day) and time (hour, minute, second) fields representing the time with an offs...
Data structure that defines the start of a specific UTC offset as described by the matching ZoneEra a...
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:53
static LocalDate forComponents(int16_t year, uint8_t month, uint8_t day)
Factory method using separated year, month and day fields.
Definition: LocalDate.h:88
uint8_t const onDayOfWeek
Determined by the ON column.
Definition: ZonePolicy.h:35
The date (year, month, day) representing the date without regards to time zone.
Definition: LocalDate.h:30
A time zone transition rule.
Definition: ZonePolicy.h:7
int8_t const fromYearTiny
FROM year as an offset from year 2000 stored as a single byte.
Definition: ZonePolicy.h:15
static const int16_t kEpochYear
Base year of epoch.
Definition: LocalDate.h:33
int8_t offsetCode
The total effective UTC offsetCode at the start of transition, including DST offset.
const basic::ZoneInfo * getZoneInfo() const
Return the underlying ZoneInfo.
static uint8_t daysInMonth(int16_t year, uint8_t month)
Return the number of days in the current month.
Definition: LocalDate.h:204
int8_t const offsetCode
UTC offset in 15 min increments.
Definition: ZoneInfo.h:20
uint8_t month() const
Return the month with January=1, December=12.
Definition: LocalDate.h:225
int16_t year() const
Return the full year instead of just the last 2 digits.
Definition: LocalDate.h:213
BasicZoneSpecifier(const basic::ZoneInfo *zoneInfo)
Constructor.
int8_t const toYearTiny
TO year as an offset from year 2000 stored as a single byte.
Definition: ZonePolicy.h:18
uint8_t const atTimeCode
Determined by the AT column in units of 15-minutes from 00:00.
Definition: ZonePolicy.h:47
uint8_t const letter
Determined by the LETTER column.
Definition: ZonePolicy.h:78
uint8_t const onDayOfMonth
Determined by the ON column.
Definition: ZonePolicy.h:41
An implementation of ZoneSpecifier that supports a subset of the zones containing in the TZ Database...
static OffsetDateTime forError()
Factory method that returns an instance whose isError() is true.
void log() const
Used only for debugging.
uint8_t dayOfWeek() const
Calculate the day of week given the (year, month, day).
Definition: LocalDate.h:242
const char *const format
Zone abbreviations (e.g.
Definition: ZoneInfo.h:40