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) {
119 if (
sizeof(acetime_t) ==
sizeof(
int)) {
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 :
"";
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;
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;
444 mZoneInfo = basic::ZoneInfoBroker((
const basic::ZoneInfo*) zoneInfo);
451 const basic::Transition* getTransition(acetime_t epochSeconds)
const {
453 bool success = init(ld);
454 return (success) ? findMatch(epochSeconds) : nullptr;
485 bool init(
const LocalDate& ld)
const {
486 int8_t yearTiny = ld.yearTiny();
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",
493 ld.yearTiny(), yearTiny);
497 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
498 logging::printf(
"init(): %d (new year %d)\n",
499 ld.yearTiny(), yearTiny);
503 mYearTiny = yearTiny;
511 basic::ZoneEraBroker priorEra = addTransitionPriorToYear(yearTiny);
512 basic::ZoneEraBroker currentEra = addTransitionsForYear(
514 addTransitionAfterYear(yearTiny, currentEra);
520 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
528 bool isFilled(int8_t yearTiny)
const {
529 return mIsFilled && (yearTiny == mYearTiny);
538 basic::ZoneEraBroker addTransitionPriorToYear(int8_t yearTiny)
const {
539 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
540 logging::printf(
"addTransitionPriorToYear(): %d\n", yearTiny);
543 const basic::ZoneEraBroker era = findZoneEra(mZoneInfo, yearTiny - 1);
547 basic::ZoneRuleBroker latest = findLatestPriorRule(
548 era.zonePolicy(), 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);
568 static basic::ZoneRuleBroker findLatestPriorRule(
569 basic::ZonePolicyBroker zonePolicy, int8_t yearTiny) {
570 basic::ZoneRuleBroker latest;
571 if (zonePolicy.isNull())
return latest;
573 uint8_t numRules = zonePolicy.numRules();
574 for (uint8_t i = 0; i < numRules; i++) {
575 const basic::ZoneRuleBroker rule = zonePolicy.rule(i);
577 if (rule.fromYearTiny() < yearTiny) {
578 if ((latest.isNull()) ||
579 compareRulesBeforeYear(yearTiny, rule, latest) > 0) {
589 static int8_t compareRulesBeforeYear(int8_t yearTiny,
590 const basic::ZoneRuleBroker a,
const basic::ZoneRuleBroker b) {
591 return basic::compareYearMonth(
592 priorYearOfRule(yearTiny, a), a.inMonth(),
593 priorYearOfRule(yearTiny, b), b.inMonth());
604 static int8_t priorYearOfRule(int8_t yearTiny,
605 const basic::ZoneRuleBroker rule) {
606 if (rule.toYearTiny() < yearTiny) {
607 return rule.toYearTiny();
616 basic::ZoneEraBroker addTransitionsForYear(
617 int8_t yearTiny, basic::ZoneEraBroker priorEra)
const {
618 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
619 logging::printf(
"addTransitionsForYear(): %d\n", yearTiny);
622 const basic::ZoneEraBroker era = findZoneEra(mZoneInfo, yearTiny);
626 const basic::ZonePolicyBroker zonePolicy = era.zonePolicy();
627 if (zonePolicy.isNull()) {
628 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
629 logging::printf(
"addTransitionsForYear(): adding ZE.untilY=%d\n",
630 era.untilYearTiny());
632 addTransition(yearTiny, 0 , era, basic::ZoneRuleBroker());
636 if (era.zoneEra() != priorEra.zoneEra()) {
640 basic::ZoneRuleBroker latestPrior = findLatestPriorRule(
641 era.zonePolicy(), yearTiny);
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++) {
660 const basic::ZoneRuleBroker rule = zonePolicy.rule(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,
682 basic::ZoneEraBroker currentEra)
const {
683 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
684 logging::printf(
"addTransitionAfterYear(): %d\n", yearTiny);
687 const basic::ZoneEraBroker eraAfter = findZoneEra(
688 mZoneInfo, yearTiny + 1);
693 if (currentEra.zoneEra() == eraAfter.zoneEra()) {
700 basic::ZoneRuleBroker latest = findLatestPriorRule(
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);
738 void addTransition(int8_t yearTiny, uint8_t month, basic::ZoneEraBroker era,
739 basic::ZoneRuleBroker rule)
const {
757 if (mNumTransitions >= kMaxCacheEntries)
return;
760 mTransitions[mNumTransitions] = createTransition(
761 yearTiny, month, era, rule);
765 for (uint8_t i = mNumTransitions - 1; i > 0; i--) {
766 basic::Transition& left = mTransitions[i - 1];
767 basic::Transition& right = mTransitions[i];
769 if (basic::compareYearMonth(left.yearTiny, left.month,
770 right.yearTiny, right.month) > 0) {
771 basic::Transition tmp = left;
783 static basic::Transition createTransition(int8_t yearTiny, uint8_t month,
784 basic::ZoneEraBroker era, basic::ZoneRuleBroker rule) {
785 int16_t deltaMinutes;
793 mon = rule.inMonth();
794 deltaMinutes = rule.deltaMinutes();
795 letter = rule.letter();
801 int16_t offsetMinutes = era.offsetMinutes() + deltaMinutes;
820 static basic::ZoneEraBroker findZoneEra(
821 basic::ZoneInfoBroker info, int8_t yearTiny) {
822 for (uint8_t i = 0; i < info.numEras(); i++) {
823 const basic::ZoneEraBroker era = info.era(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");
843 basic::Transition* prevTransition = &mTransitions[0];
846 for (uint8_t i = 1; i < mNumTransitions; i++) {
847 basic::Transition& transition = mTransitions[i];
850 if (transition.rule.isNull()) {
862 const int16_t prevOffsetMinutes = prevTransition->offsetMinutes;
866 transition.startEpochSeconds = startDateTime.toEpochSeconds();
875 year, transition.month, transition.rule.onDayOfWeek(),
876 transition.rule.onDayOfMonth());
880 const int16_t prevOffsetMinutes = calcRuleOffsetMinutes(
881 prevTransition->offsetMinutes,
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 ,
893 transition.startEpochSeconds = startDateTime.toEpochSeconds();
896 prevTransition = &transition;
906 static int16_t calcRuleOffsetMinutes(int16_t prevEffectiveOffsetMinutes,
907 int16_t currentBaseOffsetMinutes, uint8_t atSuffix) {
908 if (atSuffix == basic::ZoneContext::kSuffixW) {
909 return prevEffectiveOffsetMinutes;
910 }
else if (atSuffix == basic::ZoneContext::kSuffixS) {
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]);
929 static void calcAbbreviation(basic::Transition* transition) {
933 transition->era.format(),
934 transition->deltaMinutes,
935 transition->abbrev[0]);
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 ==
'-') {
1050 const basic::Transition* findMatch(acetime_t epochSeconds)
const {
1051 const basic::Transition* closestMatch =
nullptr;
1052 for (uint8_t i = 0; i < mNumTransitions; i++) {
1053 const basic::Transition* m = &mTransitions[i];
1054 if (closestMatch ==
nullptr || m->startEpochSeconds <= epochSeconds) {
1058 return closestMatch;
1061 basic::ZoneInfoBroker mZoneInfo;
1064 mutable bool mIsFilled =
false;
1065 mutable uint8_t mNumTransitions = 0;
1066 mutable basic::Transition mTransitions[kMaxCacheEntries];