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.isNotNull()) {
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");
151 if (a.year < b.year)
return -1;
152 if (a.year > b.year)
return 1;
153 if (a.month < b.month)
return -1;
154 if (a.month > b.month)
return 1;
217 mZoneInfo(zoneInfo) {}
221 return mZoneInfo.zoneInfo();
224 uint32_t
getZoneId()
const override {
return mZoneInfo.zoneId(); }
228 int16_t minutes = (transition)
235 int16_t minutes = (transition)
240 const char*
getAbbrev(acetime_t epochSeconds)
const override {
242 return (transition) ? transition->
abbrev :
"";
282 auto offset0 = getUtcOffset(epochSeconds0);
287 auto offset1 = getUtcOffset(epochSeconds1);
292 auto offset2 = getUtcOffset(epochSeconds2);
296 if (offset1 == offset2) {
300 acetime_t epochSeconds;
302 if (epochSeconds1 > epochSeconds2) {
303 epochSeconds = epochSeconds1;
306 epochSeconds = epochSeconds2;
318 void printTo(Print& printer)
const override;
320 void printShortTo(Print& printer)
const override;
324 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
326 logging::printf(
"*not initialized*\n");
329 logging::printf(
"mYearTiny: %d\n", mYearTiny);
330 logging::printf(
"mNumTransitions: %d\n", mNumTransitions);
331 for (
int i = 0; i < mNumTransitions; i++) {
332 logging::printf(
"mT[%d]=", i);
333 mTransitions[i].log();
358 uint8_t onDayOfWeek, int8_t onDayOfMonth) {
359 if (onDayOfWeek == 0)
return {
month, (uint8_t) onDayOfMonth};
361 if (onDayOfMonth >= 0) {
364 if (onDayOfMonth == 0) {
365 onDayOfMonth = daysInMonth - 6;
369 uint8_t dayOfWeekShift = (onDayOfWeek - limitDate.dayOfWeek() + 7) % 7;
370 uint8_t day = (uint8_t) (onDayOfMonth + dayOfWeekShift);
371 if (day > daysInMonth) {
378 onDayOfMonth = -onDayOfMonth;
380 int8_t dayOfWeekShift = (limitDate.dayOfWeek() - onDayOfWeek + 7) % 7;
381 int8_t day = onDayOfMonth - dayOfWeekShift;
386 day += daysInPrevMonth;
388 return {
month, (uint8_t) day};
393 friend class ::BasicZoneProcessorTest_priorYearOfRule;
394 friend class ::BasicZoneProcessorTest_compareRulesBeforeYear;
395 friend class ::BasicZoneProcessorTest_findLatestPriorRule;
396 friend class ::BasicZoneProcessorTest_findZoneEra;
397 friend class ::BasicZoneProcessorTest_init_primitives;
398 friend class ::BasicZoneProcessorTest_init;
399 friend class ::BasicZoneProcessorTest_setZoneInfo;
400 friend class ::BasicZoneProcessorTest_createAbbreviation;
401 friend class ::BasicZoneProcessorTest_calcStartDayOfMonth;
402 friend class ::BasicZoneProcessorTest_calcRuleOffsetMinutes;
404 template<u
int8_t SIZE, u
int8_t TYPE,
typename ZS,
typename ZI,
typename ZIB>
417 static const uint8_t kMaxCacheEntries = 5;
424 static const acetime_t kMinEpochSeconds = INT32_MIN + 1;
446 void setZoneInfo(
const void* zoneInfo)
override {
447 if (mZoneInfo.zoneInfo() == zoneInfo)
return;
458 bool success = init(ld);
459 return (success) ? findMatch(epochSeconds) :
nullptr;
492 if (ld.
month() == 1 && ld.
day() == 1) {
495 if (isFilled(yearTiny)) {
496 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
497 logging::printf(
"init(): %d (using cached %d)\n",
502 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
503 logging::printf(
"init(): %d (new year %d)\n",
519 addTransitionAfterYear(yearTiny, currentEra);
525 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
533 bool isFilled(int8_t
yearTiny)
const {
534 return mIsFilled && (yearTiny == mYearTiny);
544 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
545 logging::printf(
"addTransitionPriorToYear(): %d\n", yearTiny);
554 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
555 logging::printf(
"addTransitionsPriorToYear(): adding latest prior ");
556 if (latest.isNull()) {
557 logging::printf(
"ZR(null)\n");
559 logging::printf(
"ZR[%d,%d]\n",
560 latest.fromYearTiny(), latest.toYearTiny());
563 addTransition(yearTiny - 1, 0 , era, latest);
576 if (zonePolicy.isNull())
return latest;
578 uint8_t numRules = zonePolicy.numRules();
579 for (uint8_t i = 0; i < numRules; i++) {
582 if (rule.fromYearTiny() <
yearTiny) {
583 if ((latest.isNull()) ||
584 compareRulesBeforeYear(yearTiny, rule, latest) > 0) {
594 static int8_t compareRulesBeforeYear(int8_t yearTiny,
598 return basic::compareYearMonth(x, y);
609 static int8_t priorYearOfRule(int8_t yearTiny,
612 return rule.toYearTiny();
623 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
624 logging::printf(
"addTransitionsForYear(): %d\n", yearTiny);
632 if (zonePolicy.isNull()) {
633 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
634 logging::printf(
"addTransitionsForYear(): adding ZE.untilY=%d\n",
635 era.untilYearTiny());
641 if (era.zoneEra() != priorEra.zoneEra()) {
647 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
649 "addTransitionsForYear(): adding latest prior ");
650 if (latestPrior.isNull()) {
651 logging::printf(
"ZR(null)\n");
653 logging::printf(
"ZR[%d,%d]\n",
654 latestPrior.fromYearTiny(), latestPrior.toYearTiny());
657 addTransition(yearTiny, 1 , era, latestPrior);
663 uint8_t numRules = zonePolicy.numRules();
664 for (uint8_t i = 0; i < numRules; i++) {
666 if ((rule.fromYearTiny() <=
yearTiny) &&
667 (yearTiny <= rule.toYearTiny())) {
668 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
670 "addTransitionsForYear(): adding rule ");
672 logging::printf(
"ZR(null)\n");
674 logging::printf(
"ZR[%d,%d]\n",
675 rule.fromYearTiny(), rule.toYearTiny());
678 addTransition(yearTiny, 0 , era, rule);
686 void addTransitionAfterYear(int8_t yearTiny,
688 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
689 logging::printf(
"addTransitionAfterYear(): %d\n", yearTiny);
693 mZoneInfo, yearTiny + 1);
698 if (currentEra.zoneEra() == eraAfter.zoneEra()) {
706 eraAfter.zonePolicy(), yearTiny + 1);
707 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
709 "addTransitionsAfterYear(): adding latest prior ");
710 if (latest.isNull()) {
711 logging::printf(
"ZR(null)\n");
713 logging::printf(
"ZR[%d,%d]\n",
714 latest.fromYearTiny(), latest.toYearTiny());
717 addTransition(yearTiny + 1, 1 , eraAfter, latest);
762 if (mNumTransitions >= kMaxCacheEntries)
return;
765 mTransitions[mNumTransitions] = createTransition(
766 yearTiny, month, era, rule);
770 for (uint8_t i = mNumTransitions - 1; i > 0; i--) {
774 if (basic::compareYearMonth(
795 deltaMinutes = era.deltaMinutes();
798 mon = rule.inMonth();
799 deltaMinutes = rule.deltaMinutes();
800 letter = rule.letter();
827 for (uint8_t i = 0; i < info.numEras(); i++) {
829 if (yearTiny < era.untilYearTiny())
return era;
832 return info.era(info.numEras() - 1);
842 void calcTransitions()
const {
843 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
844 logging::printf(
"calcTransitions():\n");
851 for (uint8_t i = 1; i < mNumTransitions; i++) {
855 if (transition.
rule.isNull()) {
867 const int16_t prevOffsetMinutes = prevTransition->
offsetMinutes;
880 year, transition.
month, transition.
rule.onDayOfWeek(),
881 transition.
rule.onDayOfMonth());
885 const int16_t prevOffsetMinutes = calcRuleOffsetMinutes(
887 transition.
era.offsetMinutes(),
888 transition.
rule.atTimeSuffix());
891 const uint16_t minutes = transition.
rule.atTimeMinutes();
892 const uint8_t atHour = minutes / 60;
893 const uint8_t atMinute = minutes % 60;
895 year, monthDay.month, monthDay.day,
896 atHour, atMinute, 0 ,
901 prevTransition = &transition;
911 static int16_t calcRuleOffsetMinutes(int16_t prevEffectiveOffsetMinutes,
912 int16_t currentBaseOffsetMinutes, uint8_t atSuffix) {
914 return prevEffectiveOffsetMinutes;
916 return currentBaseOffsetMinutes;
923 void calcAbbreviations()
const {
924 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
925 logging::printf(
"calcAbbreviations():\n");
928 for (uint8_t i = 0; i < mNumTransitions; i++) {
929 calcAbbreviation(&mTransitions[i]);
938 transition->
era.format(),
991 static void createAbbreviation(
char* dest, uint8_t destSize,
992 const char* format, int16_t
deltaMinutes,
char letter) {
994 if (strchr(format,
'%') !=
nullptr) {
996 if (letter ==
'\0') {
997 strncpy(dest, format, destSize - 1);
998 dest[destSize - 1] =
'\0';
1000 copyAndReplace(dest, destSize, format,
'%', letter);
1004 const char* slashPos = strchr(format,
'/');
1005 if (slashPos !=
nullptr) {
1006 if (deltaMinutes == 0) {
1007 uint8_t headLength = (slashPos - format);
1008 if (headLength >= destSize) headLength = destSize - 1;
1009 memcpy(dest, format, headLength);
1010 dest[headLength] =
'\0';
1012 uint8_t tailLength = strlen(slashPos+1);
1013 if (tailLength >= destSize) tailLength = destSize - 1;
1014 memcpy(dest, slashPos+1, tailLength);
1015 dest[tailLength] =
'\0';
1019 strncpy(dest, format, destSize - 1);
1020 dest[destSize - 1] =
'\0';
1030 static void copyAndReplace(
char* dst, uint8_t dstSize,
const char* src,
1031 char oldChar,
char newChar) {
1032 while (*src !=
'\0' && dstSize > 0) {
1033 if (*src == oldChar) {
1034 if (newChar ==
'-') {
1057 for (uint8_t i = 0; i < mNumTransitions; i++) {
1063 return closestMatch;
1069 mutable bool mIsFilled =
false;
1070 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.