Skip to content

Commit

Permalink
Merge pull request #93 from bxparks/develop
Browse files Browse the repository at this point in the history
merge v1.10.0 into master
  • Loading branch information
bxparks authored Jan 18, 2022
2 parents 02ff26c + 1dbb0b3 commit 1a98be0
Show file tree
Hide file tree
Showing 541 changed files with 10,549 additions and 6,774 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/aunit_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ on: [push]
jobs:
build:

runs-on: ubuntu-18.04
runs-on: ubuntu-20.04

steps:
- uses: actions/checkout@v2
Expand Down
15 changes: 6 additions & 9 deletions .github/workflows/validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
# https://github.com/actions/checkout#Checkout-multiple-repos-side-by-side,
# but it looks like GITHUB_WORKSPACE is set to
# /home/runner/work/AceTime/AceTime, so if path is set to 'main' as
# suggested in the article, then the repos is set to
# suggested in the article, then the repo location is set to
# /home/runner/work/Acetime/AceTime/main/AceTime, which is really
# confusing. Instead, use 'cd ..' to go up a level and call 'git clone'
# manually.
Expand Down Expand Up @@ -57,16 +57,13 @@ jobs:
sudo apt update
sudo apt install -y libcurl4-openssl-dev
- name: Build compare_cpp
run: |
make -C ../AceTimeTools/compare_cpp
# Run the validations from AceTimeValidations (BasicHinnantDateTest,
# ExtendedHinnantDateTest, BasicAcetzTest, ExtendedAcetzTest). We don't run
# the others because when a new TZDB version comes out, the Python pytz,
# dateutil, and Java validation tests will fail because they depend
# on the obsolete TZ database on the host Operating System.

# the others because when a new TZDB version comes out, the Python
# zoneinfo, Python pytz, Python dateutil, and Java validation tests will
# fail because they depend on the obsolete TZ database on the host
# Operating System. The 'make validations' will also compile the binary in
# AceTimeValidation/compare_cpp/ as necessary.
- name: AceTimeValidation
run: |
cd ../AceTimeValidation
Expand Down
33 changes: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,39 @@
# Changelog

* Unreleased
* 1.10.0 (2022-01-18, TZDB 2021e)
* MemoryBenchmark: Add memory consumption for `ZoneSorterByName` and
`ZoneSorterByOffsetAndName`.
* AVR: 180-530 bytes of flash
* 32-bit: 120-600 bytes of flash
* Rename internal `TransitionStorage::findTransition()` to
`findTransitionForSeconds()` for better self-documentation.
* Move third party SAMD21 boards to new Tier 3 (May work, but not supported)
level.
* I can no longer upload binaries to these boards using Arduino IDE
1.8.19 and SparkFun SAMD Core 1.8.6.
* Add support for `fold` parameter to control behavior around DST gaps
and overlaps.
* The semantics of the `fold` parameter is intended to be identical to
[Python PEP 495](https://www.python.org/dev/peps/pep-0495).
* Add `LocalTime::fold()`, `LocalDateTime::fold()`,
`OffsetDateTime::fold()`, `ZonedDateTime::fold()`.
* Update `ExtendedZoneProcessor::getOffsetDateTime(acetime_t)` to
calculate the `OffsetDateTime::fold()` as an output parameter.
* Update `ExtendedZoneProcessor::getOffsetDateTime(const
LocalDateTime&)` to handle `LocalDateTime::fold()` as an input
parameter.
* Increases flash usage of `ExtendedZoneProcessor` by around 600 bytes
on AVR, and 400-600 bytes on 32-bit processors.
* Add `toUnixSeconds64()` and `forUnixSeconds64()` methods to
`LocalDate`, `LocalDateTime`, `OffsetDateTime`, `ZonedDateTime`.
* These use 64-bit `int64_t` integers, which allows Unix seconds to be
used up to 2068-01-19T03:14:07Z (which is the limit of these
various classes due to the internal use of 32-bit `acetime_t`).
* These methods make it easier to interoperate with the `time_t` typedef
for `int64_t` on the ESP8266 and ESP32 platforms.
* Add [EspTime](examples/EspTime) app that shows how to integrate
the SNTP client on the ESP8266 and ESP32 platforms with AceTime.
* 1.9.0 (2021-12-02, TZDB 2021e)
* Add `ZoneSorterByName` and `ZoneSorterByOffsetAndName` classes
to sort zone indexes, ids, or names according to 2 pre-defined sorting
Expand Down
84 changes: 56 additions & 28 deletions DEVELOPER.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Here is the dependency diagram among these projects.
AceTimeTools --------
^ ^ ^ \ artransformer.py
creating / | \ creating \ -> bufestimator.py
zonedb[x] / | \ zonedbpy \ -> zone_processor.py
zonedb[x] / | \ zonedb \ -> zone_processor.py
/ | \ v
AceTime | AceTimePython
^ ^ | ^
Expand All @@ -54,7 +54,7 @@ the buffer sizes needed by the C++ `ExtendedZoneProcessor` class. The
module to calculate those buffer sizes.

On the other hand, AceTimePython needs AceTimeTools to generate the zoneinfo
files under `AceTimePython/zonedbpy`, which are consumed by the `acetz.py`
files under `AceTimePython/zonedb`, which are consumed by the `acetz.py`
module. Fortunately, AceTimePython does *not* need AceTimeTools during runtime,
so 3rd party consumers can incorporate AceTimePython without pulling in
AceTimeTools.
Expand Down Expand Up @@ -289,31 +289,60 @@ SAVE (15-min resolution)
<a name="ExtendedZoneProcessor"></a>
## ExtendedZoneProcessor

To save memory, the `ExtendedZoneProcessor` class calculates the `Transition`
objects on the fly when the `initForEpochSeconds(acetime_t)` or
`initForYear(int16_t)` method is called. The alternative used by most Date-Time
libraries to precalculate the Transitions for all Zones, for a certain range of
years. Precalculation is definitely faster, and easier because the logic for
calculating the Transition objects resides in only a single place. However, the
expansion of the Transition objects consumes too much memory on an embedded
microcontrollers.

When a request to create a `ZonedDateTime` object comes in, the
`ExtendedZoneProcessor.findTransition(epochSeconds)` method is called. It scans
the list of Transitions calculated above, looking for a match. The matching
Transition object contains the relevant standard offset and DST offset of that
Zone.

The calculation of the Transitions is very complex, and I find that I can no
longer understand my own code after a few months away of this code base. So here
are some notes to help my future-self. The code is in
`Transition::initForYear(int16_t)` and is organized into 5 steps as described
below.
There are 2 fundamental ways that a `ZonedDateTime` can be constructed:

1) Using its human-readable components and its timezone through the
`ZonedDateTime::forComponents()` factory method. The human-readable date can be:
* An undefined time in a DST gap, or
* A duplicate time during a DST overlap.
2) Using the epochSeconds and its timezone through the
`ZoneDateTime::forEpochSeconds()` factory method.
* The epochSeconds always specifies a well-defined unique time.

The call stack of the first method looks like this:

```
ZoneDateTime::forComponents()
-> TimeZone::getOffsetDateTime(LocalDateTime&)
-> ExtendeZoneProcessor::getOffsetDateTime(LocalDateTime&)
-> TransitionStorage::findTransitionForDateTime(LocalDateTime&)
```

The call stack of the second method looks like this:

```
ZoneDateTime::forEpochSeconds(acetime_t)
-> TimeZone::getUtcOffset(acetime_t)
-> ExtendedZoneProcessor::getUtcOffset(acetime_t)
-> TransitionStorage::findTransitionForSeconds(acetime_t)
```

Both the `findTransitionForDateTime()` and `findTransitionForSeconds()` methods
search the list of Transitions of the specified TimeZone for a matching
Transition. Most Date-Time libraries precalculate these Transitions for all
Zones, for a certain range of years. Precalculation is definitely faster, and
easier because the logic for calculating the Transition objects resides in only
a single place.

The precalculation of Transition objects for all zones and years consumes too
much memory on an embedded microcontrollers. To save memory, the
`ExtendedZoneProcessor` class in the AceTime library calculates the `Transition`
objects lazily, for only a single zone and for a single year (technically, for a
14-month interval covering the 12 months of the specified year) when the
`initForEpochSeconds(acetime_t)` or `initForYear(int16_t)` method is called. The
list of Transitions for a single year is cached, so that subsequent requests in
the same year avoid the work of recalculating the Transitions.

The calculation of the Transitions for a given zone and year is very complex,
and I find that I can no longer understand my own code after a few months away
of this code base. So here are some notes to help my future-self. The code is in
`ExtendedZoneProcessor::initForYear(int16_t)` and is organized into 5 steps as
described below.

<a name="Step1FindMatches"></a>
### Step 1: Find Matches

The `ExtendedZoneProcessor.findMatches()` finds all `ZoneEra` objects which
The `ExtendedZoneProcessor::findMatches()` finds all `ZoneEra` objects which
overlap with the 14-month interval from Dec 1 of the prior year until Feb 1 of
the following year. For example, `initForYear(2010)` means that the interval is
from 2009-12-01T00:00 until 2011-02-01T00:00.
Expand Down Expand Up @@ -574,12 +603,11 @@ available.
* `$ make`
* `$ ./ExtendedHinnantDateTest.out | grep failed`
* There should be no failures.
* Update the zoneinfo files for AceTimePython (needed by BasicAcetzTest and
* Update the zonedb files for AceTimePython (needed by BasicAcetzTest and
ExtendedAcetzTest):
* ``zonedbpy`
* `$ cd AceTimePython/src/acetime/zonedbpy`
* Edit the `Makefile` and update the `TZ_VERSION`.
* `$ make`
* `$ cd AceTimePython/src/acetime/zonedb`
* Edit the `Makefile` and update the `TZ_VERSION`.
* `$ make`
* Verify that the `AceTime` library and the `AceTimePython` library agree with
each other using the same TZDB version. This requires going into the
[AceTimeValidation](https://github.com/bxparks/AceTimeValidation) project.
Expand Down
Loading

0 comments on commit 1a98be0

Please sign in to comment.