6 #ifndef ACE_TIME_BASIC_ZONE_PROCESSOR_H
7 #define ACE_TIME_BASIC_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"
22 #ifndef ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG
23 #define ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG 0
26 class BasicZoneProcessorTest_priorYearOfRule;
27 class BasicZoneProcessorTest_compareRulesBeforeYear;
28 class BasicZoneProcessorTest_findLatestPriorRule;
29 class BasicZoneProcessorTest_findZoneEra;
30 class BasicZoneProcessorTest_init_primitives;
31 class BasicZoneProcessorTest_initForLocalDate;
32 class BasicZoneProcessorTest_setZoneKey;
33 class BasicZoneProcessorTest_createAbbreviation;
34 class BasicZoneProcessorTest_calcRuleOffsetMinutes;
61 template <
typename ZIB,
typename ZEB,
typename ZPB,
typename ZRB>
115 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
123 logging::printf(
"; abbrev: %s",
abbrev);
124 if (!
rule.isNull()) {
125 logging::printf(
"; r.fromYear: %d",
rule.fromYear());
126 logging::printf(
"; r.toYear: %d",
rule.toYear());
127 logging::printf(
"; r.inMonth: %d",
rule.inMonth());
128 logging::printf(
"; r.onDayOfMonth: %d",
rule.onDayOfMonth());
130 logging::printf(
"\n");
136 inline int8_t compareYearMonth(int16_t aYear, uint8_t aMonth,
137 int16_t bYear, uint8_t bMonth) {
138 if (aYear < bYear)
return -1;
139 if (aYear > bYear)
return 1;
140 if (aMonth < bMonth)
return -1;
141 if (aMonth > bMonth)
return 1;
204 template <
typename BF,
typename ZIB,
typename ZEB,
typename ZPB,
typename ZRB>
211 return ! mZoneInfoBroker.targetInfo().isNull();
215 return mZoneInfoBroker.zoneId();
246 bool success = initForLocalDate(ldt.
localDate());
247 if (!success)
return result;
252 if (result0.type == FindResult::kTypeNotFound)
return result;
254 result0.reqStdOffsetMinutes + result0.reqDstOffsetMinutes);
258 acetime_t epochSeconds1 = odt.toEpochSeconds();
260 if (result1.type == FindResult::kTypeNotFound)
return result;
262 result1.reqStdOffsetMinutes + result1.reqDstOffsetMinutes);
266 acetime_t epochSeconds2 = odt.toEpochSeconds();
268 if (result2.type == FindResult::kTypeNotFound)
return result;
270 result2.reqStdOffsetMinutes + result2.reqDstOffsetMinutes);
274 if (offset1 == offset2) {
282 if (epochSeconds1 > epochSeconds2) {
294 const Transition* transition = getTransition(epochSeconds);
295 if (!transition)
return result;
302 result.
type = FindResult::kTypeExact;
309 mZoneInfoBroker.printNameTo(printer);
313 mZoneInfoBroker.printShortNameTo(printer);
318 mZoneInfoBroker.targetInfo().printNameTo(printer);
323 if (! mBrokerFactory)
return;
324 if (mZoneInfoBroker.equals(zoneKey))
return;
326 mZoneInfoBroker = mBrokerFactory->createZoneInfoBroker(zoneKey);
333 return mZoneInfoBroker.equals(zoneKey);
338 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
339 logging::printf(
"BasicZoneProcessor:\n");
340 logging::printf(
" mYear: %d\n", mYear);
341 logging::printf(
" mNumTransitions: %d\n", mNumTransitions);
342 for (
int i = 0; i < mNumTransitions; i++) {
343 logging::printf(
" mT[%d]=", i);
344 mTransitions[i].
log();
356 mBrokerFactory = brokerFactory;
373 const BF* brokerFactory ,
377 mBrokerFactory(brokerFactory)
383 friend class ::BasicZoneProcessorTest_priorYearOfRule;
384 friend class ::BasicZoneProcessorTest_compareRulesBeforeYear;
385 friend class ::BasicZoneProcessorTest_findLatestPriorRule;
386 friend class ::BasicZoneProcessorTest_findZoneEra;
387 friend class ::BasicZoneProcessorTest_init_primitives;
388 friend class ::BasicZoneProcessorTest_initForLocalDate;
389 friend class ::BasicZoneProcessorTest_setZoneKey;
390 friend class ::BasicZoneProcessorTest_createAbbreviation;
391 friend class ::BasicZoneProcessorTest_calcRuleOffsetMinutes;
403 static const uint8_t kMaxCacheEntries = 5;
410 static const acetime_t kMinEpochSeconds = INT32_MIN + 1;
418 return mZoneInfoBroker.equals(
423 const Transition* getTransition(acetime_t epochSeconds)
const {
424 bool success = initForEpochSeconds(epochSeconds);
425 return (success) ? findMatch(epochSeconds) : nullptr;
456 bool initForLocalDate(
const LocalDate& ld)
const {
457 int16_t year = ld.year();
458 if (ld.month() == 1 && ld.day() == 1) {
462 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
463 logging::printf(
"initForLocalDate(): %d (new year %d)\n",
470 if (year < mZoneInfoBroker.zoneContext()->startYear - 1
471 || mZoneInfoBroker.zoneContext()->untilYear < year) {
475 ZEB priorEra = addTransitionPriorToYear(year);
476 ZEB currentEra = addTransitionsForYear(year, priorEra);
477 addTransitionAfterYear(year, currentEra);
483 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
495 bool initForEpochSeconds(acetime_t epochSeconds)
const {
497 return initForLocalDate(ld);
506 ZEB addTransitionPriorToYear(int16_t year)
const {
507 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
508 logging::printf(
"addTransitionPriorToYear(): %d\n", year);
511 const ZEB era = findZoneEra(mZoneInfoBroker, year - 1);
515 ZRB latest = findLatestPriorRule(era.zonePolicy(), year);
516 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
517 logging::printf(
"addTransitionsPriorToYear(): adding latest prior ");
518 if (latest.isNull()) {
519 logging::printf(
"ZR(null)\n");
521 logging::printf(
"ZR[%d,%d]\n", latest.fromYear(), latest.toYear());
524 addTransition(year - 1, 0 , era, latest);
534 static ZRB findLatestPriorRule(
const ZPB& zonePolicy, int16_t year) {
536 if (zonePolicy.isNull())
return latest;
538 uint8_t numRules = zonePolicy.numRules();
539 for (uint8_t i = 0; i < numRules; i++) {
540 const ZRB rule = zonePolicy.rule(i);
542 if (rule.fromYear() < year) {
543 if ((latest.isNull()) ||
544 compareRulesBeforeYear(year, rule, latest) > 0) {
554 static int8_t compareRulesBeforeYear(
555 int16_t year,
const ZRB& a,
const ZRB& b) {
556 return basic::compareYearMonth(
557 priorYearOfRule(year, a), a.inMonth(),
558 priorYearOfRule(year, b), b.inMonth());
569 static int16_t priorYearOfRule(int16_t year,
const ZRB& rule) {
570 if (rule.toYear() < year) {
571 return rule.toYear();
580 ZEB addTransitionsForYear(int16_t year,
const ZEB& priorEra)
const {
581 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
582 logging::printf(
"addTransitionsForYear(): %d\n", year);
585 const ZEB era = findZoneEra(mZoneInfoBroker, year);
589 const ZPB zonePolicy = era.zonePolicy();
590 if (zonePolicy.isNull()) {
591 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
592 logging::printf(
"addTransitionsForYear(): adding ZE.untilY=%d\n",
595 addTransition(year, 0 , era, ZRB());
599 if (! era.equals(priorEra)) {
603 ZRB latestPrior = findLatestPriorRule(era.zonePolicy(), year);
604 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
606 "addTransitionsForYear(): adding latest prior ");
607 if (latestPrior.isNull()) {
608 logging::printf(
"ZR(null)\n");
610 logging::printf(
"ZR[%d,%d]\n",
611 latestPrior.fromYear(), latestPrior.toYear());
614 addTransition(year, 1 , era, latestPrior);
620 uint8_t numRules = zonePolicy.numRules();
621 for (uint8_t i = 0; i < numRules; i++) {
622 const ZRB rule = zonePolicy.rule(i);
623 if ((rule.fromYear() <= year) && (year <= rule.toYear())) {
624 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
626 "addTransitionsForYear(): adding rule ");
628 logging::printf(
"ZR(null)\n");
630 logging::printf(
"ZR[%d,%d]\n", rule.fromYear(), rule.toYear());
633 addTransition(year, 0 , era, rule);
641 void addTransitionAfterYear(int16_t year,
const ZEB& currentEra)
const {
642 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
643 logging::printf(
"addTransitionAfterYear(): %d\n", year);
646 const ZEB eraAfter = findZoneEra(mZoneInfoBroker, year + 1);
651 if (currentEra.equals(eraAfter)) {
658 ZRB latest = findLatestPriorRule(eraAfter.zonePolicy(), year + 1);
659 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
661 "addTransitionsAfterYear(): adding latest prior ");
662 if (latest.isNull()) {
663 logging::printf(
"ZR(null)\n");
665 logging::printf(
"ZR[%d,%d]\n", latest.fromYear(), latest.toYear());
668 addTransition(year + 1, 1 , eraAfter, latest);
694 void addTransition(int16_t year, uint8_t month,
const ZEB& era,
695 const ZRB& rule)
const {
713 if (mNumTransitions >= kMaxCacheEntries)
return;
716 mTransitions[mNumTransitions] = createTransition(year, month, era, rule);
720 for (uint8_t i = mNumTransitions - 1; i > 0; i--) {
724 if (basic::compareYearMonth(left.year, left.month,
725 right.year, right.month) > 0) {
738 static Transition createTransition(int16_t year, uint8_t month,
739 const ZEB& era,
const ZRB& rule) {
740 int16_t deltaMinutes;
745 deltaMinutes = era.deltaMinutes();
748 mon = rule.inMonth();
749 deltaMinutes = rule.deltaMinutes();
750 letter = rule.letter();
756 int16_t offsetMinutes = era.offsetMinutes() + deltaMinutes;
775 static ZEB findZoneEra(
const ZIB& info, int16_t year) {
776 for (uint8_t i = 0; i < info.numEras(); i++) {
777 const ZEB era = info.era(i);
778 if (year < era.untilYear())
return era;
781 return info.era(info.numEras() - 1);
791 void calcTransitions()
const {
792 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
793 logging::printf(
"calcTransitions():\n");
797 Transition* prevTransition = &mTransitions[0];
800 for (uint8_t i = 1; i < mNumTransitions; i++) {
802 const int16_t year = transition.
year;
804 if (transition.rule.isNull()) {
816 const int16_t prevOffsetMinutes = prevTransition->offsetMinutes;
820 transition.startEpochSeconds = startDateTime.toEpochSeconds();
828 const internal::MonthDay monthDay = internal::calcStartDayOfMonth(
829 year, transition.month, transition.rule.onDayOfWeek(),
830 transition.rule.onDayOfMonth());
834 const int16_t prevOffsetMinutes = calcRuleOffsetMinutes(
835 prevTransition->offsetMinutes,
836 transition.era.offsetMinutes(),
837 transition.rule.atTimeSuffix());
840 const uint16_t minutes = transition.rule.atTimeMinutes();
841 const uint8_t atHour = minutes / 60;
842 const uint8_t atMinute = minutes % 60;
844 year, monthDay.month, monthDay.day,
845 atHour, atMinute, 0 ,
847 transition.startEpochSeconds = startDateTime.toEpochSeconds();
850 prevTransition = &transition;
860 static int16_t calcRuleOffsetMinutes(int16_t prevEffectiveOffsetMinutes,
861 int16_t currentBaseOffsetMinutes, uint8_t atSuffix) {
863 return prevEffectiveOffsetMinutes;
865 return currentBaseOffsetMinutes;
872 void calcAbbreviations()
const {
873 if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
874 logging::printf(
"calcAbbreviations():\n");
877 for (uint8_t i = 0; i < mNumTransitions; i++) {
878 calcAbbreviation(&mTransitions[i]);
883 static void calcAbbreviation(
Transition* transition) {
886 internal::kAbbrevSize,
887 transition->era.format(),
888 transition->deltaMinutes,
889 transition->abbrev[0]);
940 static void createAbbreviation(
char* dest, uint8_t destSize,
941 const char* format, int16_t deltaMinutes,
char letter) {
943 if (strchr(format,
'%') !=
nullptr) {
945 if (letter ==
'\0') {
946 strncpy(dest, format, destSize - 1);
947 dest[destSize - 1] =
'\0';
949 ace_common::copyReplaceChar(dest, destSize, format,
'%',
950 letter ==
'-' ?
'\0' : letter);
954 const char* slashPos = strchr(format,
'/');
955 if (slashPos !=
nullptr) {
956 if (deltaMinutes == 0) {
957 uint8_t headLength = (slashPos - format);
958 if (headLength >= destSize) headLength = destSize - 1;
959 memcpy(dest, format, headLength);
960 dest[headLength] =
'\0';
962 uint8_t tailLength = strlen(slashPos+1);
963 if (tailLength >= destSize) tailLength = destSize - 1;
964 memcpy(dest, slashPos+1, tailLength);
965 dest[tailLength] =
'\0';
969 strncpy(dest, format, destSize - 1);
970 dest[destSize - 1] =
'\0';
976 const Transition* findMatch(acetime_t epochSeconds)
const {
978 for (uint8_t i = 0; i < mNumTransitions; i++) {
980 if (closestMatch ==
nullptr || m->startEpochSeconds <= epochSeconds) {
987 const BF* mBrokerFactory;
990 mutable uint8_t mNumTransitions = 0;
991 mutable Transition mTransitions[kMaxCacheEntries];
999 basic::BrokerFactory,
1000 basic::ZoneInfoBroker,
1001 basic::ZoneEraBroker,
1002 basic::ZonePolicyBroker,
1003 basic::ZoneRuleBroker> {
1011 basic::BrokerFactory,
1012 basic::ZoneInfoBroker,
1013 basic::ZoneEraBroker,
1014 basic::ZonePolicyBroker,
1015 basic::ZoneRuleBroker>(
1016 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.
uint32_t getZoneId() const override
Return the unique stable zoneId.
bool equalsZoneKey(uintptr_t zoneKey) const override
Return true if ZoneProcessor is associated with the given opaque zoneKey.
bool isLink() const override
Return true if timezone is a Link entry pointing to a Zone entry.
void printTargetNameTo(Print &printer) const override
Print the full identifier (e.g.
FindResult findByEpochSeconds(acetime_t epochSeconds) const override
Return the search results at given epochSeconds.
void printNameTo(Print &printer) const override
Print a human-readable identifier (e.g.
void setZoneKey(uintptr_t zoneKey) override
Set the opaque zoneKey of this object to a new value, reseting any internally cached information.
void printShortNameTo(Print &printer) const override
Print a short human-readable identifier (e.g.
BasicZoneProcessorTemplate(uint8_t type, const BF *brokerFactory, uintptr_t zoneKey)
Constructor.
FindResult findByLocalDateTime(const LocalDateTime &ldt) const override
Return the search results at given LocalDateTime.
basic::TransitionTemplate< ZIB, ZEB, ZPB, ZRB > Transition
Exposed only for testing purposes.
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.
Result of a search for transition at a specific epochSeconds or a specific LocalDateTime.
int16_t dstOffsetMinutes
DST offset of the resulting OffsetDateTime.
int16_t reqStdOffsetMinutes
STD offset of the Transition which matched the epochSeconds requested by findByEpochSeconds(),...
int16_t stdOffsetMinutes
STD offset of the resulting OffsetDateTime.
int16_t reqDstOffsetMinutes
DST offset of the Transition which matched the epochSeconds requested by findByEpochSeconds(),...
const char * abbrev
Pointer to the abbreviation stored in the transient Transition::abbrev variable.
uint8_t type
Result of the findByEpochSeconds() or findByLocalDateTime() search methods.
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 the current AceTime epoch defined by Epoch::currentEpochYear().
static LocalDate forEpochSeconds(acetime_t epochSeconds)
Factory method using the number of seconds since the current epoch year given by currentEpochYear().
static const int16_t kInvalidYear
Sentinel year which indicates one or more of the following conditions:
static OffsetDateTime forLocalDateTimeAndOffset(const LocalDateTime &localDateTime, TimeOffset timeOffset)
Factory method from LocalDateTime and TimeOffset.
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.
static TimeOffset forMinutes(int16_t minutes)
Create TimeOffset from minutes from 00:00.
Base interface for ZoneProcessor classes.
bool isFilled(int16_t year) const
Check if the Transition cache is filled for the given year.
A factory that creates a basic::ZoneInfoBroker.
int32_t acetime_t
Type for the number of seconds from epoch.
Internal identifiers used by implementation code, not intended to be publically exported.
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.
int16_t year
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.