6 #ifndef ACE_TIME_BASIC_ZONE_PROCESSOR_H 7 #define ACE_TIME_BASIC_ZONE_PROCESSOR_H 11 #include "internal/ZonePolicy.h" 12 #include "internal/ZoneInfo.h" 14 #include "common/logging.h" 15 #include "TimeOffset.h" 16 #include "LocalDate.h" 17 #include "OffsetDateTime.h" 18 #include "ZoneProcessor.h" 20 #define ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG 0 22 class BasicZoneProcessorTest_priorYearOfRule;
23 class BasicZoneProcessorTest_compareRulesBeforeYear;
24 class BasicZoneProcessorTest_findLatestPriorRule;
25 class BasicZoneProcessorTest_findZoneEra;
26 class BasicZoneProcessorTest_init_primitives;
27 class BasicZoneProcessorTest_init;
28 class BasicZoneProcessorTest_setZoneInfo;
29 class BasicZoneProcessorTest_createAbbreviation;
30 class BasicZoneProcessorTest_calcStartDayOfMonth;
31 class BasicZoneProcessorTest_calcRuleOffsetMinutes;
35 template<u
int8_t SIZE, u
int8_t TYPE,
typename ZS,
typename ZI,
typename ZIB>
117 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
118 logging::printf(
"(%d/%d)", yearTiny, month);
119 if (
sizeof(acetime_t) ==
sizeof(
int)) {
120 logging::printf(
"; stEps: %d", startEpochSeconds);
122 logging::printf(
"; stEps: %ld", startEpochSeconds);
124 logging::printf(
"; offMin: %d", offsetMinutes);
125 logging::printf(
"; abbrev: %s", abbrev);
126 if (! rule.isNull()) {
127 logging::printf(
"; r.fromYear: %d", rule.fromYearTiny());
128 logging::printf(
"; r.toYear: %d", rule.toYearTiny());
129 logging::printf(
"; r.inMonth: %d", rule.inMonth());
130 logging::printf(
"; r.onDayOfMonth: %d", rule.onDayOfMonth());
132 logging::printf(
"\n");
144 inline int8_t compareYearMonth(int8_t aYear, uint8_t aMonth,
145 int8_t bYear, uint8_t bMonth) {
146 if (aYear < bYear)
return -1;
147 if (aYear > bYear)
return 1;
148 if (aMonth < bMonth)
return -1;
149 if (aMonth > bMonth)
return 1;
212 mZoneInfo(zoneInfo) {}
216 return mZoneInfo.zoneInfo();
219 uint32_t
getZoneId()
const override {
return mZoneInfo.zoneId(); }
223 int16_t minutes = (transition)
230 int16_t minutes = (transition)
235 const char*
getAbbrev(acetime_t epochSeconds)
const override {
237 return (transition) ? transition->
abbrev :
"";
277 auto offset0 = getUtcOffset(epochSeconds0);
282 auto offset1 = getUtcOffset(epochSeconds1);
287 auto offset2 = getUtcOffset(epochSeconds2);
291 if (offset1 == offset2) {
295 acetime_t epochSeconds;
297 if (epochSeconds1 > epochSeconds2) {
298 epochSeconds = epochSeconds1;
301 epochSeconds = epochSeconds2;
313 void printTo(Print& printer)
const override;
315 void printShortTo(Print& printer)
const override;
319 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
321 logging::printf(
"*not initialized*\n");
324 logging::printf(
"mYearTiny: %d\n", mYearTiny);
325 logging::printf(
"mNumTransitions: %d\n", mNumTransitions);
326 for (
int i = 0; i < mNumTransitions; i++) {
327 logging::printf(
"mT[%d]=", i);
328 mTransitions[i].log();
353 uint8_t onDayOfWeek, int8_t onDayOfMonth) {
354 if (onDayOfWeek == 0)
return {
month, (uint8_t) onDayOfMonth};
356 if (onDayOfMonth >= 0) {
359 if (onDayOfMonth == 0) {
360 onDayOfMonth = daysInMonth - 6;
364 uint8_t dayOfWeekShift = (onDayOfWeek - limitDate.dayOfWeek() + 7) % 7;
365 uint8_t day = (uint8_t) (onDayOfMonth + dayOfWeekShift);
366 if (day > daysInMonth) {
373 onDayOfMonth = -onDayOfMonth;
375 int8_t dayOfWeekShift = (limitDate.dayOfWeek() - onDayOfWeek + 7) % 7;
376 int8_t day = onDayOfMonth - dayOfWeekShift;
381 day += daysInPrevMonth;
383 return {
month, (uint8_t) day};
388 friend class ::BasicZoneProcessorTest_priorYearOfRule;
389 friend class ::BasicZoneProcessorTest_compareRulesBeforeYear;
390 friend class ::BasicZoneProcessorTest_findLatestPriorRule;
391 friend class ::BasicZoneProcessorTest_findZoneEra;
392 friend class ::BasicZoneProcessorTest_init_primitives;
393 friend class ::BasicZoneProcessorTest_init;
394 friend class ::BasicZoneProcessorTest_setZoneInfo;
395 friend class ::BasicZoneProcessorTest_createAbbreviation;
396 friend class ::BasicZoneProcessorTest_calcStartDayOfMonth;
397 friend class ::BasicZoneProcessorTest_calcRuleOffsetMinutes;
399 template<u
int8_t SIZE, u
int8_t TYPE,
typename ZS,
typename ZI,
typename ZIB>
412 static const uint8_t kMaxCacheEntries = 5;
419 static const acetime_t kMinEpochSeconds = INT32_MIN + 1;
441 void setZoneInfo(
const void* zoneInfo)
override {
442 if (mZoneInfo.zoneInfo() == zoneInfo)
return;
453 bool success = init(ld);
454 return (success) ? findMatch(epochSeconds) :
nullptr;
487 if (ld.
month() == 1 && ld.
day() == 1) {
490 if (isFilled(yearTiny)) {
491 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
492 logging::printf(
"init(): %d (using cached %d)\n",
497 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
498 logging::printf(
"init(): %d (new year %d)\n",
514 addTransitionAfterYear(yearTiny, currentEra);
520 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
528 bool isFilled(int8_t
yearTiny)
const {
529 return mIsFilled && (yearTiny == mYearTiny);
539 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
540 logging::printf(
"addTransitionPriorToYear(): %d\n", yearTiny);
549 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
550 logging::printf(
"addTransitionsPriorToYear(): adding latest prior ");
551 if (latest.isNull()) {
552 logging::printf(
"ZR(null)\n");
554 logging::printf(
"ZR[%d,%d]\n",
555 latest.fromYearTiny(), latest.toYearTiny());
558 addTransition(yearTiny - 1, 0 , era, latest);
571 if (zonePolicy.isNull())
return latest;
573 uint8_t numRules = zonePolicy.numRules();
574 for (uint8_t i = 0; i < numRules; i++) {
577 if (rule.fromYearTiny() <
yearTiny) {
578 if ((latest.isNull()) ||
579 compareRulesBeforeYear(yearTiny, rule, latest) > 0) {
589 static int8_t compareRulesBeforeYear(int8_t yearTiny,
591 return basic::compareYearMonth(
592 priorYearOfRule(yearTiny, a), a.inMonth(),
593 priorYearOfRule(yearTiny, b), b.inMonth());
604 static int8_t priorYearOfRule(int8_t yearTiny,
607 return rule.toYearTiny();
618 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
619 logging::printf(
"addTransitionsForYear(): %d\n", yearTiny);
627 if (zonePolicy.isNull()) {
628 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
629 logging::printf(
"addTransitionsForYear(): adding ZE.untilY=%d\n",
630 era.untilYearTiny());
636 if (era.zoneEra() != priorEra.zoneEra()) {
642 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
644 "addTransitionsForYear(): adding latest prior ");
645 if (latestPrior.isNull()) {
646 logging::printf(
"ZR(null)\n");
648 logging::printf(
"ZR[%d,%d]\n",
649 latestPrior.fromYearTiny(), latestPrior.toYearTiny());
652 addTransition(yearTiny, 1 , era, latestPrior);
658 uint8_t numRules = zonePolicy.numRules();
659 for (uint8_t i = 0; i < numRules; i++) {
661 if ((rule.fromYearTiny() <=
yearTiny) &&
662 (yearTiny <= rule.toYearTiny())) {
663 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
665 "addTransitionsForYear(): adding rule ");
667 logging::printf(
"ZR(null)\n");
669 logging::printf(
"ZR[%d,%d]\n",
670 rule.fromYearTiny(), rule.toYearTiny());
673 addTransition(yearTiny, 0 , era, rule);
681 void addTransitionAfterYear(int8_t yearTiny,
683 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
684 logging::printf(
"addTransitionAfterYear(): %d\n", yearTiny);
688 mZoneInfo, yearTiny + 1);
693 if (currentEra.zoneEra() == eraAfter.zoneEra()) {
701 eraAfter.zonePolicy(), yearTiny + 1);
702 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
704 "addTransitionsAfterYear(): adding latest prior ");
705 if (latest.isNull()) {
706 logging::printf(
"ZR(null)\n");
708 logging::printf(
"ZR[%d,%d]\n",
709 latest.fromYearTiny(), latest.toYearTiny());
712 addTransition(yearTiny + 1, 1 , eraAfter, latest);
757 if (mNumTransitions >= kMaxCacheEntries)
return;
760 mTransitions[mNumTransitions] = createTransition(
761 yearTiny, month, era, rule);
765 for (uint8_t i = mNumTransitions - 1; i > 0; i--) {
790 deltaMinutes = era.deltaMinutes();
793 mon = rule.inMonth();
794 deltaMinutes = rule.deltaMinutes();
795 letter = rule.letter();
822 for (uint8_t i = 0; i < info.numEras(); i++) {
824 if (yearTiny < era.untilYearTiny())
return era;
827 return info.era(info.numEras() - 1);
837 void calcTransitions()
const {
838 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
839 logging::printf(
"calcTransitions():\n");
846 for (uint8_t i = 1; i < mNumTransitions; i++) {
850 if (transition.
rule.isNull()) {
862 const int16_t prevOffsetMinutes = prevTransition->
offsetMinutes;
875 year, transition.
month, transition.
rule.onDayOfWeek(),
876 transition.
rule.onDayOfMonth());
880 const int16_t prevOffsetMinutes = calcRuleOffsetMinutes(
882 transition.
era.offsetMinutes(),
883 transition.
rule.atTimeSuffix());
886 const uint16_t minutes = transition.
rule.atTimeMinutes();
887 const uint8_t atHour = minutes / 60;
888 const uint8_t atMinute = minutes % 60;
890 year, monthDay.month, monthDay.day,
891 atHour, atMinute, 0 ,
896 prevTransition = &transition;
906 static int16_t calcRuleOffsetMinutes(int16_t prevEffectiveOffsetMinutes,
907 int16_t currentBaseOffsetMinutes, uint8_t atSuffix) {
909 return prevEffectiveOffsetMinutes;
911 return currentBaseOffsetMinutes;
918 void calcAbbreviations()
const {
919 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
920 logging::printf(
"calcAbbreviations():\n");
923 for (uint8_t i = 0; i < mNumTransitions; i++) {
924 calcAbbreviation(&mTransitions[i]);
933 transition->
era.format(),
986 static void createAbbreviation(
char* dest, uint8_t destSize,
987 const char* format, int16_t
deltaMinutes,
char letter) {
989 if (strchr(format,
'%') !=
nullptr) {
991 if (letter ==
'\0') {
992 strncpy(dest, format, destSize - 1);
993 dest[destSize - 1] =
'\0';
995 copyAndReplace(dest, destSize, format,
'%', letter);
999 const char* slashPos = strchr(format,
'/');
1000 if (slashPos !=
nullptr) {
1001 if (deltaMinutes == 0) {
1002 uint8_t headLength = (slashPos - format);
1003 if (headLength >= destSize) headLength = destSize - 1;
1004 memcpy(dest, format, headLength);
1005 dest[headLength] =
'\0';
1007 uint8_t tailLength = strlen(slashPos+1);
1008 if (tailLength >= destSize) tailLength = destSize - 1;
1009 memcpy(dest, slashPos+1, tailLength);
1010 dest[tailLength] =
'\0';
1014 strncpy(dest, format, destSize - 1);
1015 dest[destSize - 1] =
'\0';
1025 static void copyAndReplace(
char* dst, uint8_t dstSize,
const char* src,
1026 char oldChar,
char newChar) {
1027 while (*src !=
'\0' && dstSize > 0) {
1028 if (*src == oldChar) {
1029 if (newChar ==
'-') {
1052 for (uint8_t i = 0; i < mNumTransitions; i++) {
1058 return closestMatch;
1064 mutable bool mIsFilled =
false;
1065 mutable uint8_t mNumTransitions = 0;
Base interface for ZoneProcessor classes.
ZoneEraBroker era
The ZoneEra that matched the given year.
const char * getAbbrev(acetime_t epochSeconds) const override
Return the time zone abbreviation at epochSeconds.
int8_t yearTiny() const
Return the single-byte year offset from year 2000.
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.
static const uint8_t kSuffixW
Represents 'w' or wall time.
uint8_t month
Month of the transition.
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.
Data broker for accessing ZonePolicy.
uint8_t day() const
Return the day of the month.
int8_t yearTiny
Year of the Transition.
int16_t deltaMinutes
The deltaMinutes from "standard time" at the start of transition.
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.
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.
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...
static const int8_t kInvalidYearTiny
Sentinel yearTiny which indicates an error condition or sometimes a year that 'does not exist'...
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...
static const int16_t kErrorMinutes
Sentinel value that represents an error.
A thin wrapper that represents a time offset from a reference point, usually 00:00 at UTC...
static LocalDate forComponents(int16_t year, uint8_t month, uint8_t day)
Factory method using separated year, month and day fields.
The date (year, month, day) representing the date without regards to time zone.
static TimeOffset forMinutes(int16_t minutes)
Create TimeOffset from minutes from 00:00.
static const uint8_t kSuffixS
Represents 's' or standard time.
static const int16_t kEpochYear
Base year of epoch.
static uint8_t daysInMonth(int16_t year, uint8_t month)
Return the number of days in the current month.
uint8_t month() const
Return the month with January=1, December=12.
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 ZoneEra.
Data broker for accessing ZoneRule.
int16_t offsetMinutes
The total effective UTC offset minutes at the start of transition, including DST offset.
static OffsetDateTime forError()
Factory method that returns an instance whose isError() is true.
Data broker for accessing ZoneInfo.
Class that holds the date-time as the components (year, month, day, hour, minute, second) without reg...
TimeOffset getUtcOffset(acetime_t epochSeconds) const override
Return the total UTC offset at epochSeconds, including DST offset.