Skip to content

Commit

Permalink
{Basic,Extended}ZoneProcessor.h: replace range checks against startYe…
Browse files Browse the repository at this point in the history
…ar(), untilYear()

After the recent clean up of tiny years, and the addition
ZoneContext.startYearAccurate() and ZoneContext.untilYearAccurate(), the range
checks against ZoneContext.startYear() and ZoneContext.untilYear() are not
valid. The zone processors will return something reasonable for all years, even
if the DST transitions are not accurate. Instead, use the hardcoded
LocalDate::kMinYear and LocalDate::kMaxYear, mostly for formatting reasons
(negative years, and years using more than 4 digits).
  • Loading branch information
bxparks committed Jun 15, 2023
1 parent 2a25dcc commit f0027c1
Show file tree
Hide file tree
Showing 8 changed files with 43 additions and 32 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Expand Up @@ -37,6 +37,16 @@
* Add `zonedbc` database which is valid from `[1800,10000)`, which
includes all transitions in the TZDB.
* Add `--scope complete` to access the `zonedbc` database.
* Zone Processors
* Remove range checks against `ZoneContext.startYear()` and
`ZoneContext.untilYear()`
* Replace with hardcoded `LocalDate::kMinYear` and
`LocalDate::kMaxYear`, mostly for formatting reasons (prevent negative
years, and years with more than 4 digits).
* The zone processors will always return something reasonble across the
entire `int16_t` range.
* Only the accuracy suffers outside of the `startYearAccurate()` and
`untilYearAccurate()` limits.
* Rename `src/tzonedb*` directories to `src/zonedb*testing` for consistency
with other acetime libraries.
* Move `ZoneContext`, `letters[]`, `fragments[]` into PROGMEM.
Expand Down
3 changes: 1 addition & 2 deletions src/ace_time/BasicZoneProcessor.h
Expand Up @@ -472,8 +472,7 @@ class BasicZoneProcessorTemplate: public ZoneProcessor {
mEpochYear = Epoch::currentEpochYear();
mNumTransitions = 0; // clear cache

if (year < mZoneInfoBroker.zoneContext().startYear() - 1
|| mZoneInfoBroker.zoneContext().untilYear() < year) {
if (year < LocalDate::kMinYear || LocalDate::kMaxYear < year) {
return false;
}

Expand Down
7 changes: 2 additions & 5 deletions src/ace_time/ExtendedZoneProcessor.h
Expand Up @@ -321,14 +321,11 @@ class ExtendedZoneProcessorTemplate: public ZoneProcessor {
mNumMatches = 0; // clear cache
mTransitionStorage.init();

if (year < mZoneInfoBroker.zoneContext().startYear() - 1
|| mZoneInfoBroker.zoneContext().untilYear() < year) {
if (year < LocalDate::kMinYear || LocalDate::kMaxYear < year) {
if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
logging::printf(
"initForYear(): Year %d out of valid range [%d, %d)\n",
year,
mZoneInfoBroker.zoneContext().startYear(),
mZoneInfoBroker.zoneContext().untilYear());
year, LocalDate::kMinYear, LocalDate::kMaxYear);
}
return false;
}
Expand Down
24 changes: 13 additions & 11 deletions tests/BasicZoneProcessorTest/BasicZoneProcessorTest.ino
Expand Up @@ -414,28 +414,30 @@ test(BasicZoneProcessorTest, findByEpochSeconds_outOfBounds) {
EpochYearContext context(2000); // set epoch year to 2000 temporarily
OffsetDateTime dt;
acetime_t epochSeconds;
FindResult result;

ZoneContextBroker broker = ZoneContextBroker(&kZoneContext);
assertEqual(1980, broker.startYear());
assertEqual(2200, broker.untilYear());
ZoneContextBroker zoneContextBroker = ZoneContextBroker(&kZoneContext);
assertEqual(1980, zoneContextBroker.startYear());
assertEqual(2200, zoneContextBroker.untilYear());
assertEqual(1980, zoneContextBroker.startYearAccurate());
assertEqual(ZoneContext::kMaxUntilYear,
zoneContextBroker.untilYearAccurate());

// 1970 > LocalDate::kMinYear so dt is valid, and
// 1970 > LocalDate::kMinYear so we can create an OffsetDateTime.
dt = OffsetDateTime::forComponents(1970, 3, 11, 1, 59, 59,
TimeOffset::forHours(-8));
assertFalse(dt.isError());
// 1970 is within roughly 50 years of Epoch::currentEpochYear() of 2050
// so toEpochSeconds() still works.
// 1970 is less than 68 years (INT32_MAX seconds) away from
// Epoch::currentEpochYear() of 2000 so toEpochSeconds() works.
epochSeconds = dt.toEpochSeconds();
assertNotEqual(epochSeconds, LocalDate::kInvalidEpochSeconds);
// But 1998 < ZoneContext.startYear, so FindResult not found.
result = zoneProcessor.findByEpochSeconds(epochSeconds);
assertEqual((int)result.type, (int)FindResult::kTypeNotFound);
// FindResult still works, but since 1970 < startYearAccurate(), the
// DST transitions may not be accurate.
FindResult result = zoneProcessor.findByEpochSeconds(epochSeconds);
assertEqual((int)result.type, (int)FindResult::kTypeExact);

// 10001 is beyond LocalDate::kMaxYear so should fail.
dt = OffsetDateTime::forComponents(10001, 2, 1, 1, 0, 0,
TimeOffset::forHours(-8));
// 10001 > LocalDate::kMaxYear, so fails
assertTrue(dt.isError());
// toEpochSeconds() returns invalid seconds
epochSeconds = dt.toEpochSeconds();
Expand Down
15 changes: 9 additions & 6 deletions tests/ExtendedZoneProcessorTest/ExtendedZoneProcessorTest.ino
Expand Up @@ -920,18 +920,22 @@ test(ExtendedZoneProcessorTest, findByEpochSeconds_outOfBounds) {
ZoneContextBroker zoneContextBroker(&kZoneContext);
assertEqual(1980, zoneContextBroker.startYear());
assertEqual(2200, zoneContextBroker.untilYear());
assertEqual(1980, zoneContextBroker.startYearAccurate());
assertEqual(ZoneContext::kMaxUntilYear,
zoneContextBroker.untilYearAccurate());

// 1970 > LocalDate::kMinYear so dt is valid, and
// 1970 > LocalDate::kMinYear so we can create an OffsetDateTime.
dt = OffsetDateTime::forComponents(1970, 3, 11, 1, 59, 59,
TimeOffset::forHours(-8));
assertFalse(dt.isError());
// 1998 is within roughly 50 years of Epoch::currentEpochYear() of 2050 so
// toEpochSeconds() still works.
// 1970 is less than 68 years (INT32_MAX seconds) away from
// Epoch::currentEpochYear() of 2000 so toEpochSeconds() still works.
epochSeconds = dt.toEpochSeconds();
assertNotEqual(epochSeconds, LocalDate::kInvalidEpochSeconds);
// But 1998 < ZoneContext.startYear, so FindResult not found.
// FindResult still works, but since 1970 < startYearAccurate(), the
// DST transitions may not be accurate.
FindResult result = zoneProcessor.findByEpochSeconds(epochSeconds);
assertEqual(result.type, FindResult::kTypeNotFound);
assertEqual(result.type, FindResult::kTypeExact);

// 10001 is beyond LocalDate::kMaxYear so should fail.
dt = OffsetDateTime::forComponents(10001, 2, 1, 1, 0, 0,
Expand All @@ -941,7 +945,6 @@ test(ExtendedZoneProcessorTest, findByEpochSeconds_outOfBounds) {
// toEpochSeconds() returns invalid seconds
epochSeconds = dt.toEpochSeconds();
assertEqual(epochSeconds, LocalDate::kInvalidEpochSeconds);

// findByEpochSeconds() results NotFound for kInvalidEpochSeconds
result = zoneProcessor.findByEpochSeconds(epochSeconds);
assertEqual(result.type, FindResult::kTypeNotFound);
Expand Down
4 changes: 2 additions & 2 deletions tests/ZonedDateTimeBasicTest/ZonedDateTimeBasicTest.ino
Expand Up @@ -51,8 +51,8 @@ test(ZonedDateTimeBasicTest, printTo) {
test(ZonedDateTimeBasicTest, forComponents_isError) {
TimeZone tz = basicZoneManager.createForZoneInfo(&kZoneAmerica_Los_Angeles);

// outside [1980, 10000) range, should generate error
ZonedDateTime dt = ZonedDateTime::forComponents(1970, 3, 11, 1, 59, 0, tz);
// outside [0, 10000) range, should generate error
ZonedDateTime dt = ZonedDateTime::forComponents(-100, 3, 11, 1, 59, 0, tz);
assertTrue(dt.isError());
dt = ZonedDateTime::forComponents(10001, 3, 11, 1, 59, 0, tz);
assertTrue(dt.isError());
Expand Down
6 changes: 3 additions & 3 deletions tests/ZonedDateTimeCompleteTest/ZonedDateTimeCompleteTest.ino
Expand Up @@ -59,15 +59,15 @@ test(ZonedDateTimeCompleteTest, forComponents_isError) {
TimeZone tz = completeZoneManager.createForZoneInfo(
&kZoneAmerica_Los_Angeles);

// Cannot create dates roughly before ZoneContext.startYear.
ZonedDateTime dt = ZonedDateTime::forComponents(1970, 3, 11, 1, 59, 59, tz);
// outside [0, 10000) range, should generate error
ZonedDateTime dt = ZonedDateTime::forComponents(-200, 3, 11, 1, 59, 59, tz);
const OffsetDateTime &odt = dt.offsetDateTime();
assertTrue(odt.isError());
const LocalDateTime &ldt = dt.localDateTime();
assertTrue(ldt.isError());
assertTrue(dt.isError());

// Cannot create dates roughly after ZoneContext.untilYear.
// outside [0, 10000) range, should generate error
dt = ZonedDateTime::forComponents(10001, 3, 11, 1, 59, 59, tz);
assertTrue(dt.isError());
}
Expand Down
6 changes: 3 additions & 3 deletions tests/ZonedDateTimeExtendedTest/ZonedDateTimeExtendedTest.ino
Expand Up @@ -57,15 +57,15 @@ test(ZonedDateTimeExtendedTest, forComponents_isError) {
TimeZone tz = extendedZoneManager.createForZoneInfo(
&kZoneAmerica_Los_Angeles);

// Cannot create dates roughly before ZoneContext.startYear.
ZonedDateTime dt = ZonedDateTime::forComponents(1970, 3, 11, 1, 59, 59, tz);
// outside [0, 10000) range, should generate error
ZonedDateTime dt = ZonedDateTime::forComponents(-200, 3, 11, 1, 59, 59, tz);
const OffsetDateTime &odt = dt.offsetDateTime();
assertTrue(odt.isError());
const LocalDateTime &ldt = dt.localDateTime();
assertTrue(ldt.isError());
assertTrue(dt.isError());

// Cannot create dates roughly after ZoneContext.untilYear.
// outside [0, 10000) range, should generate error
dt = ZonedDateTime::forComponents(10001, 3, 11, 1, 59, 59, tz);
assertTrue(dt.isError());
}
Expand Down

0 comments on commit f0027c1

Please sign in to comment.