6 #ifndef ACE_TIME_EXTENDED_ZONE_PROCESSOR_H
7 #define ACE_TIME_EXTENDED_ZONE_PROCESSOR_H
11 #include <AceCommon.h>
13 #include "internal/ZonePolicy.h"
14 #include "internal/ZoneInfo.h"
16 #include "common/logging.h"
17 #include "TimeOffset.h"
18 #include "LocalDate.h"
19 #include "OffsetDateTime.h"
20 #include "ZoneProcessor.h"
21 #include "local_date_mutation.h"
23 #ifndef ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG
24 #define ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG 0
27 class ExtendedZoneProcessorTest_compareEraToYearMonth;
28 class ExtendedZoneProcessorTest_compareEraToYearMonth2;
29 class ExtendedZoneProcessorTest_createMatchingEra;
30 class ExtendedZoneProcessorTest_findMatches_simple;
31 class ExtendedZoneProcessorTest_findMatches_named;
32 class ExtendedZoneProcessorTest_findCandidateTransitions;
33 class ExtendedZoneProcessorTest_createTransitionsFromNamedMatch;
34 class ExtendedZoneProcessorTest_getTransitionTime;
35 class ExtendedZoneProcessorTest_createTransitionForYearTiny;
36 class ExtendedZoneProcessorTest_normalizeDateTuple;
37 class ExtendedZoneProcessorTest_expandDateTuple;
38 class ExtendedZoneProcessorTest_calcInteriorYears;
39 class ExtendedZoneProcessorTest_getMostRecentPriorYear;
40 class ExtendedZoneProcessorTest_compareTransitionToMatchFuzzy;
41 class ExtendedZoneProcessorTest_compareTransitionToMatch;
42 class ExtendedZoneProcessorTest_processTransitionMatchStatus;
43 class ExtendedZoneProcessorTest_fixTransitionTimes_generateStartUntilTimes;
44 class ExtendedZoneProcessorTest_createAbbreviation;
45 class ExtendedZoneProcessorTest_setZoneKey;
46 class TransitionStorageTest_getFreeAgent;
47 class TransitionStorageTest_getFreeAgent2;
48 class TransitionStorageTest_addFreeAgentToActivePool;
49 class TransitionStorageTest_reservePrior;
50 class TransitionStorageTest_addPriorToCandidatePool;
51 class TransitionStorageTest_addFreeAgentToCandidatePool;
52 class TransitionStorageTest_setFreeAgentAsPriorIfValid;
53 class TransitionStorageTest_addActiveCandidatesToActivePool;
54 class TransitionStorageTest_findTransitionForDateTime;
55 class TransitionStorageTest_resetCandidatePool;
56 class TransitionValidation;
72 DateTuple(int8_t y, uint8_t mon, uint8_t d, int16_t min, uint8_t mod):
73 yearTiny(y), month(mon), day(d), suffix(mod), minutes(min) {}
83 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
84 int hour = minutes / 60;
85 int minute = minutes - hour * 60;
86 char c =
"wsu"[(suffix>>4)];
87 logging::printf(
"%04d-%02u-%02uT%02d:%02d%c",
94 inline bool operator<(
const DateTuple& a,
const DateTuple& b) {
95 if (a.yearTiny < b.yearTiny)
return true;
96 if (a.yearTiny > b.yearTiny)
return false;
97 if (a.month < b.month)
return true;
98 if (a.month > b.month)
return false;
99 if (a.day < b.day)
return true;
100 if (a.day > b.day)
return false;
101 if (a.minutes < b.minutes)
return true;
102 if (a.minutes > b.minutes)
return false;
106 inline bool operator>=(
const DateTuple& a,
const DateTuple& b) {
110 inline bool operator<=(
const DateTuple& a,
const DateTuple& b) {
114 inline bool operator>(
const DateTuple& a,
const DateTuple& b) {
119 inline bool operator==(
const DateTuple& a,
const DateTuple& b) {
120 return a.yearTiny == b.yearTiny
121 && a.month == b.month
123 && a.minutes == b.minutes
124 && a.suffix == b.suffix;
128 inline void normalizeDateTuple(DateTuple* dt) {
129 const int16_t kOneDayAsMinutes = 60 * 24;
130 if (dt->minutes <= -kOneDayAsMinutes) {
132 dt->yearTiny, dt->month, dt->day);
133 local_date_mutation::decrementOneDay(ld);
134 dt->yearTiny = ld.yearTiny();
135 dt->month = ld.month();
137 dt->minutes += kOneDayAsMinutes;
138 }
else if (kOneDayAsMinutes <= dt->minutes) {
140 dt->yearTiny, dt->month, dt->day);
141 local_date_mutation::incrementOneDay(ld);
142 dt->yearTiny = ld.yearTiny();
143 dt->month = ld.month();
145 dt->minutes -= kOneDayAsMinutes;
152 inline acetime_t subtractDateTuple(
const DateTuple& a,
const DateTuple& b) {
155 int32_t epochSecondsA = epochDaysA * 86400 + a.minutes * 60;
159 int32_t epochSecondsB = epochDaysB * 86400 + b.minutes * 60;
161 return epochSecondsA - epochSecondsB;
178 template<
typename ZEB>
202 logging::printf(
"MatchingEra(");
205 logging::printf(
"; era=%c", (
era.isNull()) ?
'-' :
'*');
206 logging::printf(
"; prevMatch=%c", (
prevMatch) ?
'*' :
'-');
207 logging::printf(
")");
212 template <
typename T>
213 void swap(T& a, T& b) {
223 enum class MatchStatus : uint8_t {
231 inline bool isMatchStatusActive(MatchStatus status) {
232 return status == MatchStatus::kExactMatch
233 || status == MatchStatus::kWithinMatch
234 || status == MatchStatus::kPrior;
271 template <
typename ZEB,
typename ZPB,
typename ZRB>
322 #if ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG
369 const char* format()
const {
370 return match->era.format();
395 const ZPB policy =
match->era.zonePolicy();
396 uint8_t numLetters = policy.numLetters();
397 if (
letter >= numLetters) {
405 return policy.letter(
letter);
410 logging::printf(
"Transition(");
411 if (
sizeof(acetime_t) <=
sizeof(
int)) {
417 logging::printf(
"; UTC");
424 logging::printf(
"; rule=-");
426 logging::printf(
"; rule=");
427 logging::printf(
"[%d,%d]",
442 uint8_t hour = minutes / 60;
443 uint8_t minute = minutes - hour * 60;
444 logging::printf(
"%c%02u:%02u", sign, (
unsigned) hour, (
unsigned) minute);
453 template <
typename ZEB,
typename ZPB,
typename ZRB>
464 template <
typename ZEB,
typename ZPB,
typename ZRB>
466 static constexpr int8_t kStatusGap = 0;
467 static constexpr int8_t kStatusExact = 1;
468 static constexpr int8_t kStatusOverlap = 2;
506 template<u
int8_t SIZE,
typename ZEB,
typename ZPB,
typename ZRB>
539 for (uint8_t i = 0; i < SIZE; i++) {
540 mTransitions[i] = &mPool[i];
543 mIndexCandidates = 0;
549 return mTransitions[mIndexPrior];
561 mIndexCandidates = mIndexPrior;
562 mIndexFree = mIndexPrior;
566 return &mTransitions[mIndexCandidates];
569 return &mTransitions[mIndexFree];
573 return &mTransitions[0];
576 return &mTransitions[mIndexFree];
585 if (mIndexFree < SIZE) {
587 if (mIndexFree >= mAllocSize) {
588 mAllocSize = mIndexFree + 1;
590 return mTransitions[mIndexFree];
596 return mTransitions[SIZE - 1];
608 if (mIndexFree >= SIZE)
return;
610 mIndexPrior = mIndexFree;
611 mIndexCandidates = mIndexFree;
627 return &mTransitions[mIndexPrior];
633 Transition* prior = mTransitions[mIndexPrior];
638 swap(mTransitions[mIndexPrior], mTransitions[mIndexFree]);
658 if (mIndexFree >= SIZE)
return;
667 for (uint8_t i = mIndexFree; i > mIndexCandidates; i--) {
671 mTransitions[i] = prev;
672 mTransitions[i - 1] = curr;
684 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
685 logging::printf(
"addActiveCandidatesToActivePool()\n");
689 uint8_t iActive = mIndexPrior;
690 uint8_t iCandidate = mIndexCandidates;
691 for (; iCandidate < mIndexFree; iCandidate++) {
692 if (isMatchStatusActive(mTransitions[iCandidate]->matchStatus)) {
693 if (iActive != iCandidate) {
696 swap(mTransitions[iActive], mTransitions[iCandidate]);
702 mIndexPrior = iActive;
703 mIndexCandidates = iActive;
704 mIndexFree = iActive;
706 return mTransitions[iActive - 1];
719 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
721 "findTransitionForSeconds(): mIndexFree: %d\n", mIndexFree);
726 for (uint8_t i = 0; i < mIndexFree; i++) {
727 const Transition* candidate = mTransitions[i];
732 uint8_t fold = calculateFold(epochSeconds, match, prevMatch);
736 static uint8_t calculateFold(
737 acetime_t epochSeconds,
741 if (match ==
nullptr)
return 0;
742 if (prevMatch ==
nullptr)
return 0;
745 acetime_t overlapSeconds = subtractDateTuple(
746 prevMatch->untilDateTime, match->startDateTime);
747 if (overlapSeconds <= 0)
return 0;
748 acetime_t secondsFromTransitionStart =
749 epochSeconds - match->startEpochSeconds;
750 if (secondsFromTransitionStart >= overlapSeconds)
return 0;
776 int8_t searchStatus = TransitionResult::kStatusGap;
777 for (uint8_t i = 0; i < mIndexFree; i++) {
778 candidate = mTransitions[i];
782 bool isExactMatch = (startDateTime <= localDate)
783 && (localDate < untilDateTime);
787 if (searchStatus == TransitionResult::kStatusExact) {
788 searchStatus = TransitionResult::kStatusOverlap;
793 searchStatus = TransitionResult::kStatusExact;
795 }
else if (startDateTime > localDate) {
800 prevCandidate = candidate;
809 if (searchStatus == TransitionResult::kStatusExact) {
823 logging::printf(
"TransitionStorage: ");
824 logging::printf(
"nActives=%d", mIndexPrior);
825 logging::printf(
", nPrior=%d", mIndexCandidates - mIndexPrior);
826 logging::printf(
", nCandidates=%d", mIndexFree - mIndexCandidates);
827 logging::printf(
", nFree=%d", SIZE - mIndexFree);
828 logging::printf(
"\n");
830 if (mIndexPrior != 0) {
831 logging::printf(
" Actives:\n");
832 for (uint8_t i = 0; i < mIndexPrior; i++) {
833 logging::printf(
" ");
834 mTransitions[i]->
log();
835 logging::printf(
"\n");
838 if (mIndexPrior != mIndexCandidates) {
839 logging::printf(
" Prior: \n");
840 logging::printf(
" ");
841 mTransitions[mIndexPrior]->
log();
842 logging::printf(
"\n");
844 if (mIndexCandidates != mIndexFree) {
845 logging::printf(
" Candidates:\n");
846 for (uint8_t i = mIndexCandidates; i < mIndexFree; i++) {
847 logging::printf(
" ");
848 mTransitions[i]->
log();
849 logging::printf(
"\n");
865 friend class ::TransitionStorageTest_getFreeAgent;
866 friend class ::TransitionStorageTest_getFreeAgent2;
867 friend class ::TransitionStorageTest_addFreeAgentToActivePool;
868 friend class ::TransitionStorageTest_reservePrior;
869 friend class ::TransitionStorageTest_addPriorToCandidatePool;
870 friend class ::TransitionStorageTest_addFreeAgentToCandidatePool;
871 friend class ::TransitionStorageTest_setFreeAgentAsPriorIfValid;
872 friend class ::TransitionStorageTest_addActiveCandidatesToActivePool;
873 friend class ::TransitionStorageTest_findTransitionForDateTime;
874 friend class ::TransitionStorageTest_resetCandidatePool;
878 return mTransitions[i];
884 uint8_t mIndexCandidates;
888 uint8_t mAllocSize = 0;
924 template <
typename BF,
typename ZIB,
typename ZEB,
typename ZPB,
typename ZRB>
957 bool isLink()
const override {
return mZoneInfoBroker.isLink(); }
959 uint32_t
getZoneId(
bool followLink =
false)
const override {
960 ZIB zib = (
isLink() && followLink)
961 ? mZoneInfoBroker.targetZoneInfo()
971 const Transition* transition = matchingTransition.transition;
983 const Transition* transition = matchingTransition.transition;
989 const char*
getAbbrev(acetime_t epochSeconds)
const override {
991 if (!success)
return "";
994 const Transition* transition = matchingTransition.transition;
995 return (transition) ? transition->
abbrev :
"";
1029 bool needsNormalization =
false;
1031 if (result.searchStatus == TransitionResult::kStatusExact) {
1032 transition = result.transition0;
1034 if (result.transition0 ==
nullptr || result.transition1 ==
nullptr) {
1036 transition =
nullptr;
1038 needsNormalization =
1039 (result.searchStatus == TransitionResult::kStatusGap);
1040 transition = (ldt.
fold() == 0)
1041 ? result.transition0
1042 : result.transition1;
1054 if (needsNormalization) {
1055 acetime_t epochSeconds = odt.toEpochSeconds();
1063 ? result.transition1
1064 : result.transition0;
1074 odt.fold(1 - ldt.
fold());
1094 const Transition* transition = matchingTransition.transition;
1100 epochSeconds, timeOffset, matchingTransition.fold);
1103 void printNameTo(Print& printer,
bool followLink =
false)
const override {
1104 ZIB zib = (
isLink() && followLink)
1105 ? mZoneInfoBroker.targetZoneInfo()
1107 zib.printNameTo(printer);
1112 ZIB zib = (
isLink() && followLink)
1113 ? mZoneInfoBroker.targetZoneInfo()
1115 zib.printShortNameTo(printer);
1120 logging::printf(
"ExtendedZoneProcessor\n");
1121 logging::printf(
" mYear: %d\n", mYear);
1122 logging::printf(
" mNumMatches: %d\n", mNumMatches);
1123 for (
int i = 0; i < mNumMatches; i++) {
1124 logging::printf(
" Match %d: ", i);
1126 logging::printf(
"\n");
1128 mTransitionStorage.
log();
1142 if (! mBrokerFactory)
return;
1143 if (mZoneInfoBroker.equals(zoneKey))
return;
1145 mZoneInfoBroker = mBrokerFactory->createZoneInfoBroker(zoneKey);
1153 return mZoneInfoBroker.equals(zoneKey);
1163 mBrokerFactory = brokerFactory;
1182 if (isFilled(year))
return true;
1183 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1184 logging::printf(
"initForYear(): %d\n", year);
1189 mTransitionStorage.
init();
1191 if (year < mZoneInfoBroker.zoneContext()->startYear - 1
1192 || mZoneInfoBroker.zoneContext()->untilYear < year) {
1193 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1195 "initForYear(): Year %d out of valid range [%d, %d)\n",
1197 mZoneInfoBroker.zoneContext()->startYear,
1198 mZoneInfoBroker.zoneContext()->untilYear);
1211 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1212 logging::printf(
"==== Step 1: findMatches()\n");
1214 mNumMatches = findMatches(mZoneInfoBroker, startYm, untilYm, mMatches,
1216 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
log(); }
1219 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1220 logging::printf(
"==== Step 2: createTransitions()\n");
1222 createTransitions(mTransitionStorage, mMatches, mNumMatches);
1223 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
log(); }
1226 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1227 logging::printf(
"==== Step 3: fixTransitionTimes()\n");
1229 Transition** begin = mTransitionStorage.getActivePoolBegin();
1230 Transition** end = mTransitionStorage.getActivePoolEnd();
1231 fixTransitionTimes(begin, end);
1232 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
log(); }
1235 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1236 logging::printf(
"==== Step 4: generateStartUntilTimes()\n");
1238 generateStartUntilTimes(begin, end);
1239 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
log(); }
1242 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1243 logging::printf(
"==== Step 5: calcAbbreviations()\n");
1245 calcAbbreviations(begin, end);
1246 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
log(); }
1264 const BF* brokerFactory ,
1268 mBrokerFactory(brokerFactory)
1274 friend class ::ExtendedZoneProcessorTest_compareEraToYearMonth;
1275 friend class ::ExtendedZoneProcessorTest_compareEraToYearMonth2;
1276 friend class ::ExtendedZoneProcessorTest_createMatchingEra;
1277 friend class ::ExtendedZoneProcessorTest_findMatches_simple;
1278 friend class ::ExtendedZoneProcessorTest_findMatches_named;
1279 friend class ::ExtendedZoneProcessorTest_findCandidateTransitions;
1280 friend class ::ExtendedZoneProcessorTest_createTransitionsFromNamedMatch;
1281 friend class ::ExtendedZoneProcessorTest_getTransitionTime;
1282 friend class ::ExtendedZoneProcessorTest_createTransitionForYearTiny;
1283 friend class ::ExtendedZoneProcessorTest_normalizeDateTuple;
1284 friend class ::ExtendedZoneProcessorTest_expandDateTuple;
1285 friend class ::ExtendedZoneProcessorTest_calcInteriorYears;
1286 friend class ::ExtendedZoneProcessorTest_getMostRecentPriorYear;
1287 friend class ::ExtendedZoneProcessorTest_compareTransitionToMatchFuzzy;
1288 friend class ::ExtendedZoneProcessorTest_compareTransitionToMatch;
1289 friend class ::ExtendedZoneProcessorTest_processTransitionMatchStatus;
1290 friend class ::ExtendedZoneProcessorTest_fixTransitionTimes_generateStartUntilTimes;
1291 friend class ::ExtendedZoneProcessorTest_createAbbreviation;
1292 friend class ::ExtendedZoneProcessorTest_setZoneKey;
1293 friend class ::TransitionStorageTest_findTransitionForDateTime;
1294 friend class ::TransitionValidation;
1306 static const uint8_t kMaxMatches = 4;
1312 static const uint8_t kMaxInteriorYears = 4;
1315 return mZoneInfoBroker.equals(
1320 bool isFilled(int16_t year)
const {
1321 return mIsFilled && (year == mYear);
1331 static uint8_t findMatches(
1332 const ZIB& zoneInfo,
1333 const extended::YearMonthTuple& startYm,
1334 const extended::YearMonthTuple& untilYm,
1338 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1339 logging::printf(
"findMatches()\n");
1343 for (uint8_t iEra = 0; iEra < zoneInfo.numEras(); iEra++) {
1344 const ZEB era = zoneInfo.era(iEra);
1345 if (eraOverlapsInterval(prevMatch, era, startYm, untilYm)) {
1346 if (iMatch < maxMatches) {
1347 matches[iMatch] = createMatchingEra(
1348 prevMatch, era, startYm, untilYm);
1349 prevMatch = &matches[iMatch];
1368 static bool eraOverlapsInterval(
1371 const extended::YearMonthTuple& startYm,
1372 const extended::YearMonthTuple& untilYm) {
1373 return (prevMatch ==
nullptr || compareEraToYearMonth(
1374 prevMatch->era, untilYm.yearTiny, untilYm.month) < 0)
1375 && compareEraToYearMonth(era, startYm.yearTiny, startYm.month) > 0;
1379 static int8_t compareEraToYearMonth(
const ZEB& era,
1380 int8_t yearTiny, uint8_t month) {
1381 if (era.untilYearTiny() < yearTiny)
return -1;
1382 if (era.untilYearTiny() > yearTiny)
return 1;
1383 if (era.untilMonth() < month)
return -1;
1384 if (era.untilMonth() > month)
return 1;
1385 if (era.untilDay() > 1)
return 1;
1387 if (era.untilTimeMinutes() > 0)
return 1;
1400 const extended::YearMonthTuple& startYm,
1401 const extended::YearMonthTuple& untilYm) {
1405 extended::DateTuple startDate = (prevMatch ==
nullptr)
1406 ? extended::DateTuple{
1413 : extended::DateTuple{
1414 prevMatch->era.untilYearTiny(),
1415 prevMatch->era.untilMonth(),
1416 prevMatch->era.untilDay(),
1417 (int16_t) prevMatch->era.untilTimeMinutes(),
1418 prevMatch->era.untilTimeSuffix()
1420 extended::DateTuple lowerBound{
1427 if (startDate < lowerBound) {
1428 startDate = lowerBound;
1431 extended::DateTuple untilDate{
1432 era.untilYearTiny(),
1435 (int16_t) era.untilTimeMinutes(),
1436 era.untilTimeSuffix()
1438 extended::DateTuple upperBound{
1445 if (upperBound < untilDate) {
1446 untilDate = upperBound;
1449 return {startDate, untilDate, era, prevMatch, 0, 0};
1456 static void createTransitions(
1459 uint8_t numMatches) {
1460 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1461 logging::printf(
"createTransitions()\n");
1464 for (uint8_t i = 0; i < numMatches; i++) {
1465 createTransitionsForMatch(transitionStorage, &matches[i]);
1470 static void createTransitionsForMatch(
1473 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1474 logging::printf(
"== createTransitionsForMatch()\n");
1476 const ZPB policy = match->era.zonePolicy();
1477 if (policy.isNull()) {
1478 createTransitionsFromSimpleMatch(transitionStorage, match);
1480 createTransitionsFromNamedMatch(transitionStorage, match);
1485 static void createTransitionsFromSimpleMatch(
1488 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1489 logging::printf(
"== createTransitionsFromSimpleMatch()\n");
1492 Transition* freeTransition = transitionStorage.getFreeAgent();
1493 createTransitionForYearTiny(freeTransition, 0 ,
1495 freeTransition->matchStatus = extended::MatchStatus::kExactMatch;
1496 match->lastOffsetMinutes = freeTransition->offsetMinutes;
1497 match->lastDeltaMinutes = freeTransition->deltaMinutes;
1498 transitionStorage.addFreeAgentToActivePool();
1499 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1500 freeTransition->log();
1501 logging::printf(
"\n");
1506 static void createTransitionsFromNamedMatch(
1509 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1510 logging::printf(
"== createTransitionsFromNamedMatch()\n");
1513 transitionStorage.resetCandidatePool();
1514 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1515 match->log(); logging::printf(
"\n");
1519 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1520 logging::printf(
"---- Pass 1: findCandidateTransitions()\n");
1522 findCandidateTransitions(transitionStorage, match);
1523 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1524 transitionStorage.log();
1529 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1530 logging::printf(
"---- Pass 2: fixTransitionTimes()\n");
1533 transitionStorage.getCandidatePoolBegin(),
1534 transitionStorage.getCandidatePoolEnd());
1538 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1539 logging::printf(
"---- Pass 3: selectActiveTransitions()\n");
1541 selectActiveTransitions(
1542 transitionStorage.getCandidatePoolBegin(),
1543 transitionStorage.getCandidatePoolEnd());
1544 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1545 transitionStorage.log();
1548 transitionStorage.addActiveCandidatesToActivePool();
1549 match->lastOffsetMinutes = lastTransition->offsetMinutes;
1550 match->lastDeltaMinutes = lastTransition->deltaMinutes;
1551 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1552 transitionStorage.log();
1557 static void findCandidateTransitions(
1560 using extended::MatchStatus;
1562 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1563 logging::printf(
"findCandidateTransitions(): \n");
1565 logging::printf(
"\n");
1567 const ZPB policy = match->era.zonePolicy();
1568 uint8_t numRules = policy.numRules();
1569 int8_t startY = match->startDateTime.yearTiny;
1570 int8_t endY = match->untilDateTime.yearTiny;
1575 Transition** prior = transitionStorage.reservePrior();
1576 (*prior)->isValidPrior =
false;
1577 for (uint8_t r = 0; r < numRules; r++) {
1578 const ZRB rule = policy.rule(r);
1581 int8_t interiorYears[kMaxInteriorYears];
1582 uint8_t numYears = calcInteriorYears(interiorYears, kMaxInteriorYears,
1583 rule.fromYearTiny(), rule.toYearTiny(), startY, endY);
1584 for (uint8_t y = 0; y < numYears; y++) {
1585 int8_t yearTiny = interiorYears[y];
1586 Transition* t = transitionStorage.getFreeAgent();
1587 createTransitionForYearTiny(t, yearTiny, rule, match);
1588 MatchStatus status = compareTransitionToMatchFuzzy(t, match);
1589 if (status == MatchStatus::kPrior) {
1590 transitionStorage.setFreeAgentAsPriorIfValid();
1591 }
else if (status == MatchStatus::kWithinMatch) {
1592 transitionStorage.addFreeAgentToCandidatePool();
1600 int8_t priorYearTiny = getMostRecentPriorYear(
1601 rule.fromYearTiny(), rule.toYearTiny(), startY, endY);
1603 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1605 "findCandidateTransitions(): priorYear: %d\n",
1608 Transition* t = transitionStorage.getFreeAgent();
1609 createTransitionForYearTiny(t, priorYearTiny, rule, match);
1610 transitionStorage.setFreeAgentAsPriorIfValid();
1616 if ((*prior)->isValidPrior) {
1617 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1619 "findCandidateTransitions(): adding prior to Candidate pool\n");
1621 logging::printf(
"\n");
1623 transitionStorage.addPriorToCandidatePool();
1646 static uint8_t calcInteriorYears(
1647 int8_t* interiorYears,
1648 uint8_t maxInteriorYears,
1649 int8_t fromYear, int8_t toYear,
1650 int8_t startYear, int8_t endYear) {
1652 for (int8_t year = startYear; year <= endYear; year++) {
1653 if (fromYear <= year && year <= toYear) {
1654 interiorYears[i] = year;
1656 if (i >= maxInteriorYears)
break;
1668 static void createTransitionForYearTiny(
1675 t->offsetMinutes = match->era.offsetMinutes();
1676 t->letterBuf[0] =
'\0';
1678 if (! rule.isNull()) {
1679 t->transitionTime = getTransitionTime(yearTiny, rule);
1680 t->deltaMinutes = rule.deltaMinutes();
1682 char letter = rule.letter();
1685 if (letter !=
'-') {
1686 t->letterBuf[0] = letter;
1687 t->letterBuf[1] =
'\0';
1697 t->transitionTime = match->startDateTime;
1698 t->deltaMinutes = match->era.deltaMinutes();
1714 static int8_t getMostRecentPriorYear(
1715 int8_t fromYear, int8_t toYear,
1716 int8_t startYear, int8_t endYear) {
1720 if (fromYear < startYear) {
1721 if (toYear < startYear) {
1724 return startYear - 1;
1735 static extended::DateTuple getTransitionTime(
1736 int8_t yearTiny,
const ZRB& rule) {
1738 internal::MonthDay monthDay = internal::calcStartDayOfMonth(
1742 rule.onDayOfMonth());
1747 (int16_t) rule.atTimeMinutes(),
1762 static extended::MatchStatus compareTransitionToMatchFuzzy(
1764 using extended::MatchStatus;
1766 int16_t ttMonths = t->transitionTime.yearTiny * 12
1767 + t->transitionTime.month;
1769 int16_t matchStartMonths = match->startDateTime.yearTiny * 12
1770 + match->startDateTime.month;
1771 if (ttMonths < matchStartMonths - 1)
return MatchStatus::kPrior;
1773 int16_t matchUntilMonths = match->untilDateTime.yearTiny * 12
1774 + match->untilDateTime.month;
1775 if (matchUntilMonths + 2 <= ttMonths)
return MatchStatus::kFarFuture;
1777 return MatchStatus::kWithinMatch;
1789 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1790 logging::printf(
"fixTransitionTimes(): START; #transitions=%d\n",
1791 (
int) (end - begin));
1792 printTransitions(begin, end);
1798 for (
Transition** iter = begin; iter != end; ++iter) {
1801 &curr->transitionTime,
1802 prev->offsetMinutes,
1804 &curr->transitionTime,
1805 &curr->transitionTimeS,
1806 &curr->transitionTimeU);
1809 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1810 logging::printf(
"fixTransitionTimes(): FIXED\n");
1811 printTransitions(begin, end);
1812 logging::printf(
"fixTransitionTimes(): END\n");
1816 #ifdef ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG
1818 for (
Transition** iter = begin; iter != end; ++iter) {
1820 logging::printf(
"\n");
1830 static void expandDateTuple(
1831 const extended::DateTuple* tt,
1832 int16_t offsetMinutes,
1833 int16_t deltaMinutes,
1834 extended::DateTuple* ttw,
1835 extended::DateTuple* tts,
1836 extended::DateTuple* ttu) {
1840 *ttu = {tt->yearTiny, tt->month, tt->day,
1841 (int16_t) (tt->minutes - offsetMinutes),
1843 *ttw = {tt->yearTiny, tt->month, tt->day,
1844 (int16_t) (tt->minutes + deltaMinutes),
1848 *tts = {tt->yearTiny, tt->month, tt->day,
1849 (int16_t) (tt->minutes + offsetMinutes),
1851 *ttw = {tt->yearTiny, tt->month, tt->day,
1852 (int16_t) (tt->minutes + (offsetMinutes + deltaMinutes)),
1858 *tts = {tt->yearTiny, tt->month, tt->day,
1859 (int16_t) (tt->minutes - deltaMinutes),
1861 *ttu = {tt->yearTiny, tt->month, tt->day,
1862 (int16_t) (tt->minutes - (deltaMinutes + offsetMinutes)),
1866 extended::normalizeDateTuple(ttw);
1867 extended::normalizeDateTuple(tts);
1868 extended::normalizeDateTuple(ttu);
1876 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1877 logging::printf(
"selectActiveTransitions(): #candidates: %d\n",
1878 (
int) (end - begin));
1882 for (
Transition** iter = begin; iter != end; ++iter) {
1884 processTransitionMatchStatus(transition, &prior);
1890 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1892 "selectActiveTransitions(): found latest prior\n");
1894 #if ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG
1895 prior->originalTransitionTime = prior->transitionTime;
1897 prior->transitionTime = prior->match->startDateTime;
1907 static void processTransitionMatchStatus(
1910 using extended::MatchStatus;
1912 MatchStatus status = compareTransitionToMatch(
1913 transition, transition->match);
1914 transition->matchStatus = status;
1916 if (status == MatchStatus::kExactMatch) {
1918 (*prior)->matchStatus = MatchStatus::kFarPast;
1920 (*prior) = transition;
1921 }
else if (status == MatchStatus::kPrior) {
1923 if ((*prior)->transitionTimeU <= transition->transitionTimeU) {
1924 (*prior)->matchStatus = MatchStatus::kFarPast;
1925 (*prior) = transition;
1927 transition->matchStatus = MatchStatus::kFarPast;
1930 (*prior) = transition;
1943 static extended::MatchStatus compareTransitionToMatch(
1948 int16_t prevMatchOffsetMinutes;
1949 int16_t prevMatchDeltaMinutes;
1950 if (match->prevMatch) {
1951 prevMatchOffsetMinutes = match->prevMatch->lastOffsetMinutes;
1952 prevMatchDeltaMinutes = match->prevMatch->lastDeltaMinutes;
1954 prevMatchOffsetMinutes = match->era.offsetMinutes();
1955 prevMatchDeltaMinutes = 0;
1959 extended::DateTuple stw;
1960 extended::DateTuple sts;
1961 extended::DateTuple stu;
1963 &match->startDateTime,
1964 prevMatchOffsetMinutes,
1965 prevMatchDeltaMinutes,
1971 const extended::DateTuple& ttw = transition->transitionTime;
1972 const extended::DateTuple& tts = transition->transitionTimeS;
1973 const extended::DateTuple& ttu = transition->transitionTimeU;
1978 if (ttw == stw || tts == sts || ttu == stu) {
1979 return extended::MatchStatus::kExactMatch;
1983 return extended::MatchStatus::kPrior;
1991 const extended::DateTuple& matchUntil = match->untilDateTime;
1992 const extended::DateTuple* transitionTime;
1994 transitionTime = &tts;
1996 transitionTime = &ttu;
1998 transitionTime = &ttw;
2000 if (*transitionTime < matchUntil) {
2001 return extended::MatchStatus::kWithinMatch;
2003 return extended::MatchStatus::kFarFuture;
2012 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
2014 "generateStartUntilTimes(): #transitions=%d\n",
2015 (
int) (end - begin));
2019 bool isAfterFirst =
false;
2021 for (
Transition** iter = begin; iter != end; ++iter) {
2025 const extended::DateTuple& tt = t->transitionTime;
2027 prev->untilDateTime = tt;
2033 int16_t minutes = tt.minutes + (
2034 - prev->offsetMinutes - prev->deltaMinutes
2035 + t->offsetMinutes + t->deltaMinutes);
2036 t->startDateTime = {tt.yearTiny, tt.month, tt.day, minutes,
2038 extended::normalizeDateTuple(&t->startDateTime);
2050 const extended::DateTuple& st = t->startDateTime;
2051 const acetime_t offsetSeconds = (acetime_t) 60
2052 * (st.minutes - (t->offsetMinutes + t->deltaMinutes));
2054 st.yearTiny, st.month, st.day);
2055 t->startEpochSeconds = ld.toEpochSeconds() + offsetSeconds;
2058 isAfterFirst =
true;
2062 extended::DateTuple untilTimeW;
2063 extended::DateTuple untilTimeS;
2064 extended::DateTuple untilTimeU;
2066 &prev->match->untilDateTime,
2067 prev->offsetMinutes,
2072 prev->untilDateTime = untilTimeW;
2079 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
2080 logging::printf(
"calcAbbreviations(): #transitions: %d\n",
2081 (
int) (end - begin));
2083 for (
Transition** iter = begin; iter != end; ++iter) {
2085 if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
2087 "calcAbbreviations(): format:%s, deltaMinutes:%d, letter:%s\n",
2088 t->format(), t->deltaMinutes, t->letter());
2092 internal::kAbbrevSize,
2107 static void createAbbreviation(
2111 uint16_t deltaMinutes,
2112 const char* letterString) {
2115 if (strchr(format,
'%') !=
nullptr) {
2117 if (letterString ==
nullptr) {
2118 strncpy(dest, format, destSize - 1);
2119 dest[destSize - 1] =
'\0';
2121 ace_common::copyReplaceString(
2122 dest, destSize, format,
'%', letterString);
2126 const char* slashPos = strchr(format,
'/');
2127 if (slashPos !=
nullptr) {
2128 if (deltaMinutes == 0) {
2129 uint8_t headLength = (slashPos - format);
2130 if (headLength >= destSize) headLength = destSize - 1;
2131 memcpy(dest, format, headLength);
2132 dest[headLength] =
'\0';
2134 uint8_t tailLength = strlen(slashPos+1);
2135 if (tailLength >= destSize) tailLength = destSize - 1;
2136 memcpy(dest, slashPos+1, tailLength);
2137 dest[tailLength] =
'\0';
2141 strncpy(dest, format, destSize);
2142 dest[destSize - 1] =
'\0';
2147 const BF* mBrokerFactory;
2148 ZIB mZoneInfoBroker;
2150 mutable int16_t mYear = 0;
2151 mutable bool mIsFilled =
false;
2153 mutable uint8_t mNumMatches = 0;
2164 extended::BrokerFactory,
2165 extended::ZoneInfoBroker,
2166 extended::ZoneEraBroker,
2167 extended::ZonePolicyBroker,
2168 extended::ZoneRuleBroker> {
2176 extended::BrokerFactory,
2177 extended::ZoneInfoBroker,
2178 extended::ZoneEraBroker,
2179 extended::ZonePolicyBroker,
2180 extended::ZoneRuleBroker>(
The classes provide a thin layer of indirection for accessing the zoneinfo files stored in the zonedb...
An implementation of ZoneProcessor that supports for all zones defined by the TZ Database.
bool initForEpochSeconds(acetime_t epochSeconds) const
Initialize using the epochSeconds.
void setBrokerFactory(const BF *brokerFactory)
Set the broker factory at runtime.
const char * getAbbrev(acetime_t epochSeconds) const override
Return the time zone abbreviation at epochSeconds.
extended::MatchingTransitionTemplate< ZEB, ZPB, ZRB > MatchingTransition
Exposed only for testing purposes.
extended::TransitionStorageTemplate< kMaxTransitions, ZEB, ZPB, ZRB > TransitionStorage
Exposed only for testing purposes.
OffsetDateTime getOffsetDateTime(acetime_t epochSeconds) const override
void printNameTo(Print &printer, bool followLink=false) const override
Print a human-readable identifier (e.g.
bool initForYear(int16_t year) const
Initialize the zone rules cache, keyed by the "current" year.
void printShortNameTo(Print &printer, bool followLink=false) const override
Print a short human-readable identifier (e.g.
bool equalsZoneKey(uintptr_t zoneKey) const override
Return true if ZoneProcessor is associated with the given opaque zoneKey.
OffsetDateTime getOffsetDateTime(const LocalDateTime &ldt) const override
bool isLink() const override
Return true if timezone is a Link entry pointing to a Zone entry.
TimeOffset getDeltaOffset(acetime_t epochSeconds) const override
Return the DST delta offset at epochSeconds.
void resetTransitionAllocSize()
Reset the TransitionStorage high water mark.
TimeOffset getUtcOffset(acetime_t epochSeconds) const override
Return the total UTC offset at epochSeconds, including DST offset.
extended::TransitionResultTemplate< ZEB, ZPB, ZRB > TransitionResult
Exposed only for testing purposes.
static const uint8_t kMaxTransitions
Max number of Transitions required for all Zones supported by this class.
extended::TransitionTemplate< ZEB, ZPB, ZRB > Transition
Exposed only for testing purposes.
void setZoneKey(uintptr_t zoneKey) override
Set the opaque zoneKey of this object to a new value, reseting any internally cached information.
ExtendedZoneProcessorTemplate(uint8_t type, const BF *brokerFactory, uintptr_t zoneKey)
Constructor.
void log() const
Used only for debugging.
uint32_t getZoneId(bool followLink=false) const override
Return the unique stable zoneId.
uint8_t getTransitionAllocSize() const
Get the largest allocation size of TransitionStorage.
extended::MatchingEraTemplate< ZEB > MatchingEra
Exposed only for testing purposes.
A specific implementation of ExtendedZoneProcessorTemplate that uses ZoneXxxBrokers which read from z...
static const uint8_t kTypeExtended
Unique TimeZone type identifier for ExtendedZoneProcessor.
Class that holds the date-time as the components (year, month, day, hour, minute, second) without reg...
int8_t yearTiny() const
Return the single-byte year offset from year 2000.
uint8_t day() const
Return the day of the month.
uint8_t month() const
Return the month with January=1, December=12.
uint8_t fold() const
Return the fold.
uint8_t minute() const
Return the minute.
uint8_t hour() const
Return the hour.
int16_t year() const
Return the year.
The date (year, month, day) representing the date without regards to time zone.
static const int16_t kEpochYear
Base year of epoch.
int16_t year() const
Return the full year instead of just the last 2 digits.
static LocalDate forEpochSeconds(acetime_t epochSeconds)
Factory method using the number of seconds since AceTime epoch of 2000-01-01.
int32_t toEpochDays() const
Return number of days since AceTime epoch (2000-01-01 00:00:00Z).
static const int8_t kInvalidYearTiny
Sentinel yearTiny which indicates an error condition or sometimes a year that 'does not exist'.
static LocalDate forTinyComponents(int8_t yearTiny, uint8_t month, uint8_t day)
Factory method using components with an int8_t yearTiny.
The date (year, month, day), time (hour, minute, second) and offset from UTC (timeOffset).
static OffsetDateTime forLocalDateTimeAndOffset(const LocalDateTime &localDateTime, TimeOffset timeOffset)
Factory method from LocalDateTime and TimeOffset.
static OffsetDateTime forError()
Factory method that returns an instance whose isError() is true.
static OffsetDateTime forEpochSeconds(acetime_t epochSeconds, TimeOffset timeOffset, uint8_t fold=0)
Factory method.
A thin wrapper that represents a time offset from a reference point, usually 00:00 at UTC,...
static TimeOffset forError()
Return an error indicator.
static TimeOffset forMinutes(int16_t minutes)
Create TimeOffset from minutes from 00:00.
Base interface for ZoneProcessor classes.
A factory that creates a basic::ZoneInfoBroker.
A heap manager which is specialized and tuned to manage a collection of Transitions,...
void resetAllocSize()
Reset the current allocation size.
void addFreeAgentToActivePool()
Immediately add the free agent Transition at index mIndexFree to the Active pool.
MatchingTransitionTemplate< ZEB, ZPB, ZRB > MatchingTransition
Template instantiation of MatchingTransitiontemplate used by this class.
void init()
Initialize all pools to 0 size, usually when a new year is initialized.
void setFreeAgentAsPriorIfValid()
Set the free agent transition as the most recent prior.
Transition * getFreeAgent()
Return a pointer to the first Transition in the free pool.
TransitionTemplate< ZEB, ZPB, ZRB > Transition
Template instantiation of TransitionTemplate used by this class.
void resetCandidatePool()
Empty the Candidate pool by resetting the various indexes.
TransitionResult findTransitionForDateTime(const LocalDateTime &ldt) const
Return the candidate Transitions matching the given dateTime.
uint8_t getAllocSize() const
Return the maximum number of transitions which was allocated.
void addFreeAgentToCandidatePool()
Add the free agent Transition at index mIndexFree to the Candidate pool, sorted by transitionTime.
Transition * getPrior()
Return the current prior transition.
TransitionStorageTemplate()
Constructor.
Transition * addActiveCandidatesToActivePool()
Add active candidates into the Active pool, and collapse the Candidate pool.
TransitionResultTemplate< ZEB, ZPB, ZRB > TransitionResult
Template instantiation of TransitionResultTemplate used by this class.
void addPriorToCandidatePool()
Add the current prior into the Candidates pool.
MatchingTransition findTransitionForSeconds(acetime_t epochSeconds) const
Return the Transition matching the given epochSeconds.
Transition ** reservePrior()
Allocate a free Transition then add it to the Prior pool.
void log() const
Verify that the indexes are valid.
Macros and definitions that provide a consistency layer among the various Arduino boards for compatib...
A tuple that represents a date and time.
void log() const
Used only for debugging.
Data structure that captures the matching ZoneEra and its ZoneRule transitions for a given year.
MatchingEraTemplate * prevMatch
The previous MatchingEra, needed to interpret startDateTime.
int16_t lastDeltaMinutes
The DST offset of the last Transition in this MatchingEra.
ZEB era
The ZoneEra that matched the given year.
DateTuple untilDateTime
The effective until time of the matching ZoneEra.
DateTuple startDateTime
The effective start time of the matching ZoneEra, which uses the UTC offsets of the previous matching...
int16_t lastOffsetMinutes
The STD offset of the last Transition in this MatchingEra.
Tuple of a matching Transition and its 'fold'.
The result of the findTransitionForDateTime(const LocalDatetime&) method which can return 2 possible ...
Represents an interval of time where the time zone obeyed a certain UTC offset and DST delta.
ZRB rule
The Zone transition rule that matched for the the given year.
bool isValidPrior
During findCandidateTransitions(), this flag indicates whether the current transition is a valid "pri...
DateTuple transitionTime
The original transition time, usually 'w' but sometimes 's' or 'u'.
acetime_t startEpochSeconds
The calculated transition time of the given rule.
DateTuple transitionTimeS
Version of transitionTime in 's' mode, using the UTC offset of the previous Transition.
DateTuple untilDateTime
Until time expressed using the UTC offset of the current Transition.
const char * letter() const
Return the letter string.
int16_t offsetMinutes
The base offset minutes, not the total effective UTC offset.
void log() const
Used only for debugging.
char letterBuf[2]
Storage for the single letter 'letter' field if 'rule' is not null.
DateTuple transitionTimeU
Version of transitionTime in 'u' mode, using the UTC offset of the previous transition.
static void logHourMinute(int16_t minutes)
Print minutes as [+/-]hh:mm.
MatchStatus matchStatus
During processTransitionMatchStatus(), this flag indicates how the transition falls within the time i...
DateTuple startDateTime
Start time expressed using the UTC offset of the current Transition.
char abbrev[internal::kAbbrevSize]
The calculated effective time zone abbreviation, e.g.
int16_t deltaMinutes
The DST delta minutes.
const MatchingEraTemplate< ZEB > * match
The match which generated this Transition.
A simple tuple to represent a year/month pair.
static const uint8_t kSuffixW
Represents 'w' or wall time.
static const uint8_t kSuffixS
Represents 's' or standard time.
static const uint8_t kSuffixU
Represents 'u' or UTC time.