6 #ifndef ACE_TIME_BASIC_ZONE_PROCESSOR_H
7 #define ACE_TIME_BASIC_ZONE_PROCESSOR_H
11 #include <AceCommon.h>
12 #include "internal/ZonePolicy.h"
13 #include "internal/ZoneInfo.h"
15 #include "common/logging.h"
16 #include "TimeOffset.h"
17 #include "LocalDate.h"
18 #include "OffsetDateTime.h"
19 #include "ZoneProcessor.h"
21 #ifndef ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG
22 #define ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG 0
25 class BasicZoneProcessorTest_priorYearOfRule;
26 class BasicZoneProcessorTest_compareRulesBeforeYear;
27 class BasicZoneProcessorTest_findLatestPriorRule;
28 class BasicZoneProcessorTest_findZoneEra;
29 class BasicZoneProcessorTest_init_primitives;
30 class BasicZoneProcessorTest_init;
31 class BasicZoneProcessorTest_setZoneKey;
32 class BasicZoneProcessorTest_createAbbreviation;
33 class BasicZoneProcessorTest_calcRuleOffsetMinutes;
60 template <
typename ZIB,
typename ZEB,
typename ZPB,
typename ZRB>
114 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
116 if (
sizeof(acetime_t) ==
sizeof(
int)) {
122 logging::printf(
"; abbrev: %s",
abbrev);
123 if (!
rule.isNull()) {
124 logging::printf(
"; r.fromYear: %d",
rule.fromYearTiny());
125 logging::printf(
"; r.toYear: %d",
rule.toYearTiny());
126 logging::printf(
"; r.inMonth: %d",
rule.inMonth());
127 logging::printf(
"; r.onDayOfMonth: %d",
rule.onDayOfMonth());
129 logging::printf(
"\n");
135 inline int8_t compareYearMonth(int8_t aYear, uint8_t aMonth,
136 int8_t bYear, uint8_t bMonth) {
137 if (aYear < bYear)
return -1;
138 if (aYear > bYear)
return 1;
139 if (aMonth < bMonth)
return -1;
140 if (aMonth > bMonth)
return 1;
203 template <
typename BF,
typename ZIB,
typename ZEB,
typename ZPB,
typename ZRB>
209 bool isLink()
const override {
return mZoneInfoBroker.isLink(); }
211 uint32_t
getZoneId(
bool followLink =
false)
const override {
212 ZIB zib = (
isLink() && followLink)
213 ? mZoneInfoBroker.targetZoneInfo()
219 const Transition* transition = getTransition(epochSeconds);
220 int16_t minutes = (transition)
226 const Transition* transition = getTransition(epochSeconds);
227 int16_t minutes = (transition)
232 const char*
getAbbrev(acetime_t epochSeconds)
const override {
233 const Transition* transition = getTransition(epochSeconds);
234 return (transition) ? transition->
abbrev :
"";
288 if (offset1 == offset2) {
292 acetime_t epochSeconds;
294 if (epochSeconds1 > epochSeconds2) {
295 epochSeconds = epochSeconds1;
298 epochSeconds = epochSeconds2;
315 void printNameTo(Print& printer,
bool followLink =
false)
const override {
316 ZIB zib = (
isLink() && followLink)
317 ? mZoneInfoBroker.targetZoneInfo()
319 zib.printNameTo(printer);
324 ZIB zib = (
isLink() && followLink)
325 ? mZoneInfoBroker.targetZoneInfo()
327 zib.printShortNameTo(printer);
331 if (! mBrokerFactory)
return;
332 if (mZoneInfoBroker.equals(zoneKey))
return;
334 mZoneInfoBroker = mBrokerFactory->createZoneInfoBroker(zoneKey);
342 return mZoneInfoBroker.equals(zoneKey);
347 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
349 logging::printf(
"*not initialized*\n");
352 logging::printf(
"mYearTiny: %d\n", mYearTiny);
353 logging::printf(
"mNumTransitions: %d\n", mNumTransitions);
354 for (
int i = 0; i < mNumTransitions; i++) {
355 logging::printf(
"mT[%d]=", i);
356 mTransitions[i].
log();
368 mBrokerFactory = brokerFactory;
383 const BF* brokerFactory ,
387 mBrokerFactory(brokerFactory)
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_setZoneKey;
400 friend class ::BasicZoneProcessorTest_createAbbreviation;
401 friend class ::BasicZoneProcessorTest_calcRuleOffsetMinutes;
413 static const uint8_t kMaxCacheEntries = 5;
420 static const acetime_t kMinEpochSeconds = INT32_MIN + 1;
428 return mZoneInfoBroker.equals(
433 const Transition* getTransition(acetime_t epochSeconds)
const {
435 bool success = init(ld);
436 return (success) ? findMatch(epochSeconds) : nullptr;
467 bool init(
const LocalDate& ld)
const {
468 int8_t yearTiny = ld.yearTiny();
469 if (ld.month() == 1 && ld.day() == 1) {
472 if (isFilled(yearTiny)) {
473 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
474 logging::printf(
"init(): %d (using cached %d)\n",
475 ld.yearTiny(), yearTiny);
479 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
480 logging::printf(
"init(): %d (new year %d)\n",
481 ld.yearTiny(), yearTiny);
485 mYearTiny = yearTiny;
489 < mZoneInfoBroker.zoneContext()->startYear - 1
490 || mZoneInfoBroker.zoneContext()->untilYear
495 ZEB priorEra = addTransitionPriorToYear(yearTiny);
496 ZEB currentEra = addTransitionsForYear(yearTiny, priorEra);
497 addTransitionAfterYear(yearTiny, currentEra);
503 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
511 bool isFilled(int8_t yearTiny)
const {
512 return mIsFilled && (yearTiny == mYearTiny);
521 ZEB addTransitionPriorToYear(int8_t yearTiny)
const {
522 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
523 logging::printf(
"addTransitionPriorToYear(): %d\n", yearTiny);
526 const ZEB era = findZoneEra(mZoneInfoBroker, yearTiny - 1);
530 ZRB latest = findLatestPriorRule(
531 era.zonePolicy(), yearTiny);
532 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
533 logging::printf(
"addTransitionsPriorToYear(): adding latest prior ");
534 if (latest.isNull()) {
535 logging::printf(
"ZR(null)\n");
537 logging::printf(
"ZR[%d,%d]\n",
538 latest.fromYearTiny(), latest.toYearTiny());
541 addTransition(yearTiny - 1, 0 , era, latest);
551 static ZRB findLatestPriorRule(
const ZPB& zonePolicy, int8_t yearTiny) {
553 if (zonePolicy.isNull())
return latest;
555 uint8_t numRules = zonePolicy.numRules();
556 for (uint8_t i = 0; i < numRules; i++) {
557 const ZRB rule = zonePolicy.rule(i);
559 if (rule.fromYearTiny() < yearTiny) {
560 if ((latest.isNull()) ||
561 compareRulesBeforeYear(yearTiny, rule, latest) > 0) {
571 static int8_t compareRulesBeforeYear(int8_t yearTiny,
572 const ZRB& a,
const ZRB& b) {
573 return basic::compareYearMonth(
574 priorYearOfRule(yearTiny, a), a.inMonth(),
575 priorYearOfRule(yearTiny, b), b.inMonth());
586 static int8_t priorYearOfRule(int8_t yearTiny,
const ZRB& rule) {
587 if (rule.toYearTiny() < yearTiny) {
588 return rule.toYearTiny();
597 ZEB addTransitionsForYear(int8_t yearTiny,
const ZEB& priorEra)
const {
598 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
599 logging::printf(
"addTransitionsForYear(): %d\n", yearTiny);
602 const ZEB era = findZoneEra(mZoneInfoBroker, yearTiny);
606 const ZPB zonePolicy = era.zonePolicy();
607 if (zonePolicy.isNull()) {
608 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
609 logging::printf(
"addTransitionsForYear(): adding ZE.untilY=%d\n",
610 era.untilYearTiny());
612 addTransition(yearTiny, 0 , era, ZRB());
616 if (! era.equals(priorEra)) {
620 ZRB latestPrior = findLatestPriorRule(
621 era.zonePolicy(), yearTiny);
622 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
624 "addTransitionsForYear(): adding latest prior ");
625 if (latestPrior.isNull()) {
626 logging::printf(
"ZR(null)\n");
628 logging::printf(
"ZR[%d,%d]\n",
629 latestPrior.fromYearTiny(), latestPrior.toYearTiny());
632 addTransition(yearTiny, 1 , era, latestPrior);
638 uint8_t numRules = zonePolicy.numRules();
639 for (uint8_t i = 0; i < numRules; i++) {
640 const ZRB rule = zonePolicy.rule(i);
641 if ((rule.fromYearTiny() <= yearTiny) &&
642 (yearTiny <= rule.toYearTiny())) {
643 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
645 "addTransitionsForYear(): adding rule ");
647 logging::printf(
"ZR(null)\n");
649 logging::printf(
"ZR[%d,%d]\n",
650 rule.fromYearTiny(), rule.toYearTiny());
653 addTransition(yearTiny, 0 , era, rule);
661 void addTransitionAfterYear(int8_t yearTiny,
const ZEB& currentEra)
const {
662 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
663 logging::printf(
"addTransitionAfterYear(): %d\n", yearTiny);
666 const ZEB eraAfter = findZoneEra(mZoneInfoBroker, yearTiny + 1);
671 if (currentEra.equals(eraAfter)) {
678 ZRB latest = findLatestPriorRule(eraAfter.zonePolicy(), yearTiny + 1);
679 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
681 "addTransitionsAfterYear(): adding latest prior ");
682 if (latest.isNull()) {
683 logging::printf(
"ZR(null)\n");
685 logging::printf(
"ZR[%d,%d]\n",
686 latest.fromYearTiny(), latest.toYearTiny());
689 addTransition(yearTiny + 1, 1 , eraAfter, latest);
715 void addTransition(int8_t yearTiny, uint8_t month,
const ZEB& era,
716 const ZRB& rule)
const {
734 if (mNumTransitions >= kMaxCacheEntries)
return;
737 mTransitions[mNumTransitions] = createTransition(
738 yearTiny, month, era, rule);
742 for (uint8_t i = mNumTransitions - 1; i > 0; i--) {
746 if (basic::compareYearMonth(left.yearTiny, left.month,
747 right.yearTiny, right.month) > 0) {
760 static Transition createTransition(int8_t yearTiny, uint8_t month,
761 const ZEB& era,
const ZRB& rule) {
762 int16_t deltaMinutes;
767 deltaMinutes = era.deltaMinutes();
770 mon = rule.inMonth();
771 deltaMinutes = rule.deltaMinutes();
772 letter = rule.letter();
778 int16_t offsetMinutes = era.offsetMinutes() + deltaMinutes;
797 static ZEB findZoneEra(
const ZIB& info, int8_t yearTiny) {
798 for (uint8_t i = 0; i < info.numEras(); i++) {
799 const ZEB era = info.era(i);
800 if (yearTiny < era.untilYearTiny())
return era;
803 return info.era(info.numEras() - 1);
813 void calcTransitions()
const {
814 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
815 logging::printf(
"calcTransitions():\n");
819 Transition* prevTransition = &mTransitions[0];
822 for (uint8_t i = 1; i < mNumTransitions; i++) {
826 if (transition.rule.isNull()) {
838 const int16_t prevOffsetMinutes = prevTransition->offsetMinutes;
842 transition.startEpochSeconds = startDateTime.toEpochSeconds();
850 const internal::MonthDay monthDay = internal::calcStartDayOfMonth(
851 year, transition.month, transition.rule.onDayOfWeek(),
852 transition.rule.onDayOfMonth());
856 const int16_t prevOffsetMinutes = calcRuleOffsetMinutes(
857 prevTransition->offsetMinutes,
858 transition.era.offsetMinutes(),
859 transition.rule.atTimeSuffix());
862 const uint16_t minutes = transition.rule.atTimeMinutes();
863 const uint8_t atHour = minutes / 60;
864 const uint8_t atMinute = minutes % 60;
866 year, monthDay.month, monthDay.day,
867 atHour, atMinute, 0 ,
869 transition.startEpochSeconds = startDateTime.toEpochSeconds();
872 prevTransition = &transition;
882 static int16_t calcRuleOffsetMinutes(int16_t prevEffectiveOffsetMinutes,
883 int16_t currentBaseOffsetMinutes, uint8_t atSuffix) {
885 return prevEffectiveOffsetMinutes;
887 return currentBaseOffsetMinutes;
894 void calcAbbreviations()
const {
895 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
896 logging::printf(
"calcAbbreviations():\n");
899 for (uint8_t i = 0; i < mNumTransitions; i++) {
900 calcAbbreviation(&mTransitions[i]);
905 static void calcAbbreviation(
Transition* transition) {
908 internal::kAbbrevSize,
909 transition->era.format(),
910 transition->deltaMinutes,
911 transition->abbrev[0]);
962 static void createAbbreviation(
char* dest, uint8_t destSize,
963 const char* format, int16_t deltaMinutes,
char letter) {
965 if (strchr(format,
'%') !=
nullptr) {
967 if (letter ==
'\0') {
968 strncpy(dest, format, destSize - 1);
969 dest[destSize - 1] =
'\0';
971 ace_common::copyReplaceChar(dest, destSize, format,
'%',
972 letter ==
'-' ?
'\0' : letter);
976 const char* slashPos = strchr(format,
'/');
977 if (slashPos !=
nullptr) {
978 if (deltaMinutes == 0) {
979 uint8_t headLength = (slashPos - format);
980 if (headLength >= destSize) headLength = destSize - 1;
981 memcpy(dest, format, headLength);
982 dest[headLength] =
'\0';
984 uint8_t tailLength = strlen(slashPos+1);
985 if (tailLength >= destSize) tailLength = destSize - 1;
986 memcpy(dest, slashPos+1, tailLength);
987 dest[tailLength] =
'\0';
991 strncpy(dest, format, destSize - 1);
992 dest[destSize - 1] =
'\0';
998 const Transition* findMatch(acetime_t epochSeconds)
const {
1000 for (uint8_t i = 0; i < mNumTransitions; i++) {
1002 if (closestMatch ==
nullptr || m->startEpochSeconds <= epochSeconds) {
1006 return closestMatch;
1009 const BF* mBrokerFactory;
1010 ZIB mZoneInfoBroker;
1013 mutable bool mIsFilled =
false;
1014 mutable uint8_t mNumTransitions = 0;
1015 mutable Transition mTransitions[kMaxCacheEntries];
1023 basic::BrokerFactory,
1024 basic::ZoneInfoBroker,
1025 basic::ZoneEraBroker,
1026 basic::ZonePolicyBroker,
1027 basic::ZoneRuleBroker> {
1035 basic::BrokerFactory,
1036 basic::ZoneInfoBroker,
1037 basic::ZoneEraBroker,
1038 basic::ZonePolicyBroker,
1039 basic::ZoneRuleBroker>(
1040 kTypeBasic, &mBrokerFactory, (uintptr_t) zoneInfo)
The classes provide a thin layer of indirection for accessing the zoneinfo files stored in the zonedb...
An implementation of ZoneProcessor that supports a subset of the zones containing in the TZ Database.
void setBrokerFactory(const BF *brokerFactory)
Set the broker factory at runtime.
TimeOffset getDeltaOffset(acetime_t epochSeconds) const override
Return the DST delta offset at epochSeconds.
bool equalsZoneKey(uintptr_t zoneKey) const override
Return true if ZoneProcessor is associated with the given opaque zoneKey.
uint32_t getZoneId(bool followLink=false) const override
Return the unique stable zoneId.
bool isLink() const override
Return true if timezone is a Link entry pointing to a Zone entry.
void printNameTo(Print &printer, bool followLink=false) const override
Print a human-readable identifier (e.g.
void printShortNameTo(Print &printer, bool followLink=false) const override
Print a short human-readable identifier (e.g.
OffsetDateTime getOffsetDateTime(acetime_t epochSeconds) const override
Return the best estimate of the OffsetDateTime at the given epochSeconds for the timezone of the curr...
void setZoneKey(uintptr_t zoneKey) override
Set the opaque zoneKey of this object to a new value, reseting any internally cached information.
TimeOffset getUtcOffset(acetime_t epochSeconds) const override
Return the total UTC offset at epochSeconds, including DST offset.
BasicZoneProcessorTemplate(uint8_t type, const BF *brokerFactory, uintptr_t zoneKey)
Constructor.
basic::TransitionTemplate< ZIB, ZEB, ZPB, ZRB > Transition
Exposed only for testing purposes.
const char * getAbbrev(acetime_t epochSeconds) const override
Return the time zone abbreviation at epochSeconds.
OffsetDateTime getOffsetDateTime(const LocalDateTime &ldt) const override
Return the best estimate of the OffsetDateTime at the given LocalDateTime for the timezone of the cur...
void log() const
Used only for debugging.
A specific implementation of BasicZoneProcessorTemplate that uses ZoneXxxBrokers which read from zone...
static const uint8_t kTypeBasic
Unique TimeZone type identifier for BasicZoneProcessor.
Class that holds the date-time as the components (year, month, day, hour, minute, second) without reg...
const LocalDate & localDate() const
Return the LocalDate.
acetime_t toEpochSeconds() const
Return seconds since AceTime epoch 2000-01-01 00:00:00Z, after assuming that the date and time compon...
static const int16_t kEpochYear
Base year of epoch.
static LocalDate forEpochSeconds(acetime_t epochSeconds)
Factory method using the number of seconds since AceTime epoch of 2000-01-01.
static const int8_t kInvalidYearTiny
Sentinel yearTiny which indicates an error condition or sometimes a year that 'does not exist'.
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.
acetime_t toEpochSeconds() const
Return seconds since AceTime epoch (2000-01-01 00:00:00Z), taking into account the offset zone.
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.
static OffsetDateTime forComponents(int16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second, TimeOffset timeOffset, uint8_t fold=0)
Factory method using separated date, time, and UTC offset fields.
A thin wrapper that represents a time offset from a reference point, usually 00:00 at UTC,...
static const int16_t kErrorMinutes
Sentinel value that represents an error.
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.
Data structure that defines the start of a specific UTC offset as described by the matching ZoneEra a...
uint8_t month
Month of the transition.
acetime_t startEpochSeconds
The calculated transition time of the given rule.
ZRB rule
The Zone transition rule that matched for the the given year.
ZEB era
The ZoneEra that matched the given year.
void log() const
Used only for debugging.
int8_t yearTiny
Year of the Transition.
int16_t offsetMinutes
The total effective UTC offset minutes at the start of transition, including DST offset.
char abbrev[internal::kAbbrevSize]
The calculated effective time zone abbreviation, e.g.
int16_t deltaMinutes
The deltaMinutes from "standard time" at the start of transition.
static const uint8_t kSuffixW
Represents 'w' or wall time.
static const uint8_t kSuffixS
Represents 's' or standard time.