diff --git a/.github/workflows/validation.yml b/.github/workflows/validation.yml deleted file mode 100644 index 6c4bb7e76..000000000 --- a/.github/workflows/validation.yml +++ /dev/null @@ -1,79 +0,0 @@ -# See https://docs.github.com/en/actions/guides for documentation about GitHub -# Actions. - -name: Validation Tests - -# Run on all branches. -on: [push] - -jobs: - build: - - runs-on: ubuntu-latest - - # Tried to use - # 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 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. - steps: - - name: Checkout AceTime - uses: actions/checkout@v4 - - - name: Checkout Additional Repos - run: | - cd .. - git clone --depth 1 https://github.com/HowardHinnant/date - git clone --depth 1 https://github.com/bxparks/AUnit - git clone --depth 1 https://github.com/bxparks/AceCommon - git clone --depth 1 https://github.com/bxparks/AceSorting - git clone --depth 1 https://github.com/bxparks/AceTimeTools - git clone --depth 1 https://github.com/bxparks/AceTimeValidation - git clone --depth 1 https://github.com/bxparks/EpoxyDuino - git clone --depth 1 https://github.com/bxparks/acetimec - git clone --depth 1 https://github.com/bxparks/acetimepy - git clone https://github.com/eggert/tz - - - name: Set up Python 3.10 - uses: actions/setup-python@v5 - with: - python-version: '3.10' - - - name: Display Python version - run: python -c "import sys; print(sys.version)" - - - name: Install Python Dependencies - run: | - python -m pip install --upgrade pip - pip install -r ../AceTimeTools/requirements.txt - pip install -e ../AceTimeTools - pip install -r ../acetimepy/requirements.txt - pip install -e ../acetimepy - - - name: Install libcurl4-openssl-dev - run: | - sudo apt update - sudo apt install -y libcurl4-openssl-dev - - - name: Build Compare tools - run: | - make -C ../acetimec/src - make -C ../AceTimeValidation/tools/compare_acetime - make -C ../AceTimeValidation/tools/compare_acetimec - make -C ../AceTimeValidation/tools/compare_libc - make -C ../AceTimeValidation/tools/compare_hinnant - - # Run the 'tests' target in AceTimeValidations which runs HinnantBasicTest, - # HinnantExtendedTest, AcetzBasicTest, and AcetzExtendedTest. We don't run - # the others because when a new TZDB version comes out, the Python - # zoneinfo, Python pytz, Python dateutil, Java tests, and others will fail - # because they depend on the obsolete TZ database on the host Operating - # System. - - name: AceTimeValidation - run: | - cd ../AceTimeValidation - make -j2 -C tests tests - make -C tests runtests diff --git a/CHANGELOG.md b/CHANGELOG.md index e8b54a427..1b29cdc47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,25 @@ # Changelog -- Unreleased +- unreleased +- 3.0.0 (2025-04-25, TZDB version 2025b) + - [upgrade to TZDB 2025a](https://lists.iana.org/hyperkitty/list/tz-announce@iana.org/thread/MWII7R3HMCEDNUCIYQKSSTYYR7UWK4OQ/) + - Paraguay adopts permanent -03 starting spring 2024. + - Improve pre-1991 data for the Philippines. + - Etc/Unknown is now reserved. + - [upgrade to TZDB 2025b](https://lists.iana.org/hyperkitty/list/tz-announce@iana.org/thread/6JVHNHLB6I2WAYTQ75L6KEPEQHFXAJK3/) + - New zone for Aysén Region in Chile which moves from -04/-03 to -03. + (Creates new zone named America/Coyhaique) + - **breaking** add ZoneInfo data classes and their brokers into `Info` + container class + - allows selection of parallel class hierarchies using the `Info` + container class + - `basic::ZoneInfo` class moves to `basic::Info::ZoneInfo` + - `extended::ZoneInfo` class moves to `extended::Info::ZoneInfo` + - `complete::ZoneInfo` class moves to `complete::Info::ZoneInfo` + - **breaking** move `daysUntil(LocalDate, month, day)` to + `LocalDate::daysUntil(month, day)` for simplicity + - See [Migrating to v3.0](MIGRATING.md#MigratingToVersion300) for more + details. - 2.4.0 (2024-12-13, TZDB version 2024b) - Support new `%z` value in FORMAT column. - Upgrade TZDB to 2024b diff --git a/DEVELOPER.md b/DEVELOPER.md index 15904f164..59e07bc9e 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -5,76 +5,34 @@ library. ## Table of Contents -* [Project/Repo Dependency](#ProjectRepoDependency) -* [Namespace Dependency](#NamespaceDependency) -* [Zone Info Database](#ZoneInfoDatabase) - * [Template Layer](#TemplateLayer) - * [Storage Layer](#StorageLayer) - * [Broker Layer](#BrokerLayer) - * [ZoneDb Files](#ZoneDbFiles) - * [ZoneContext](#ZoneContext) - * [ZoneInfo and ZoneEra](#ZoneInfoZoneEra) - * [ZonePolicy and ZoneRule](#ZonePolicyZoneRule) - * [ZoneRegistry](#ZoneRegistry) - * [Offset Encoding](#OffsetEncoding) - * [TinyYear Encoding](#TinyYearEncoding) -* [BasicZoneProcessor](#BasicZoneProcessor) -* [ExtendedZoneProcessor](#ExtendedZoneProcessor) - * [Search by Component or EpochSeconds](#SearchByComponentOrEpochSeconds) - * [Stea 1: Find Matches](#Step1FindMatches) - * [Step 2: Create Transitions](#Step2CreateTransitions) - * [Step 2A: Transition Time versus StartDateTime](#Step2ATransitionTimeAndStartDateTime) - * [Step 3: Fix Transition Times](#Step3FixTransitionTimes) - * [Step 4: Generate Start Until Times](#Step4GenerateStartUntilTimes) - * [Step 5: Calculate Abbreviations](#Step5CalculateAbbreviations) -* [Upgrading TZDB](#UpgradingZoneInfoFiles) -* [Release Process](#ReleaseProcess) - - -## Project/Repo Dependency - -On 2021-08-25, the scripts under `./tools` were moved into the -[AceTimeTools](https://github.com/bxparks/AceTimeTools/) project, and the -integration tests under `./tests/validation` were moved into the -[AceTimeValidation](https://github.com/bxparks/AceTimeValidation) project. Then -on 2021-09-08, the Python timezone classes (`zone_processor.py`, `acetz.py`, -etc) were moved into the -[acetimepy](https://github.com/bxparks/acetimepy) project. - -Here is the dependency diagram among these projects. +- [Project Dependency](#project-dependency) +- [Namespace Dependency](#namespace-dependency) +- [Zone Info Database](#zone-info-database) + - [Template Layer](#template-layer) + - [Storage Layer](#storage-layer) + - [Broker Layer](#broker-layer) + - [ZoneDb Files](#zonedb-files) + - [ZoneContext](#zonecontext) + - [ZoneInfo and ZoneEra](#zoneinfo-and-zoneera) + - [ZonePolicy and ZoneRule](#zonepolicy-and-zonerule) + - [ZoneRegistry](#zoneregistry) + - [Offset Encoding](#offset-encoding) + - [TinyYear Encoding](#tinyyear-encoding) +- [BasicZoneProcessor](#basiczoneprocessor) +- [ExtendedZoneProcessor](#extendedzoneprocessor) + - [Search by Component or EpochSeconds](#search-by-component-or-epochseconds) + - [Stea 1: Find Matches](#step-1-find-matches) + - [Step 2: Create Transitions](#step-2-create-transitions) + - [Step 2A: Transition Time versus StartDateTime](#step-2a-transition-time-versus-startdatetime) + - [Step 3: Fix Transition Times](#step-3-fix-transition-times) + - [Step 4: Generate Start Until Times](#step-4-generate-until-times) + - [Step 5: Calculate Abbreviations](#step-5-calculate-abbreviations) + +## Project Dependency + +This repo was programmatically generated from the +[AceTimeSuite](https://github.com/bxparks/AceTimeSuite) project. -``` - AceTimeTools -------- - ^ ^ ^ \ artransformer.py - creating / | \ creating \ -> bufestimator.py - zonedb* / | \ zonedb / -> zone_processor.py - / | \ v - AceTime | acetimepy - ^ ^ | ^ - / \ | / - / \ | / -AceTimeClock AceTimeValidation -``` - -There is slight circular dependency between `AceTimeTools` and `acetimepy`. - -AceTimeTools needs acetimepy when generating the C++ zoneinfo files under -`AceTime/src/zonedb[x]`. The `tzcompiler.py` calls `bufestimator.py` to generate -the buffer sizes needed by the C++ `ExtendedZoneProcessor` class. The -`AceTimeTools/bufestimator.py` module needs `acetimepy/zone_processor.py` -module to calculate those buffer sizes. - -On the other hand, acetimepy needs AceTimeTools to generate the zoneinfo -files under `acetimepy/zonedb/`, which are consumed by the `acetz` and -`ZoneManager` classes. Fortunately, acetimepy does *not* need AceTimeTools -during runtime, so 3rd party consumers can incorporate acetimepy without pulling -in AceTimeTools. - -Both AceTime and acetimepy can be used as runtime libraries **without** -pulling in the dependency to AceTimeTools (which is required only to generated -the zoneinfo database files). - - ## Namespace Dependency The various AceTime namespaces are related in the following way, where the arrow @@ -105,10 +63,8 @@ ace_time::hw ace_time:: ace_time::clock ace_time::testing ``` - ## Zone Info Database - ### Template Layer The template layer provides low-level building blocks that define the storage @@ -187,7 +143,6 @@ All of these classes are templatized, so that custom instantiations can be created for different zoneinfo databases, which can be verified by the compiler to be used together in the proper way. - ### Storage Layer This is the actual storage layer used by library, instantiated from the template @@ -218,7 +173,6 @@ exception is the `const xxx::ZoneInfo*` pointer, which is used as an opaque identifier for a timezone. From this pointer, a `TimeZone` object can be created. - ### Broker Layer This is the actual broker layer used by library, instantiated from the template @@ -258,12 +212,11 @@ into a `ZoneInfoBroker` object, and use the broker object as the timezone identifier. This would completely hide the low-level `const ZoneInfo*` pointer from the client application.) - ### ZoneDb Files The AceTime library comes with 3 sets of zoneinfo files, which were programmatically generated by the scripts in -[AceTimeTools](https://github.com/bxparks/AceTimeTools/) from the [IANA TZ +`AceTimeSuite/compiler/tzcompiler.sh` from the [IANA TZ Data](https://www.iana.org/time-zones): * `src/zonedb/*` @@ -285,7 +238,6 @@ Each `zonedb*/` directory contains the following files: * `zone_policies.h`, `zone_policies.cpp` * `zone_registry.h`, `zone_registry.cpp` - #### ZoneContext There is a single `ZoneContext kZoneContext` record included in the @@ -330,7 +282,6 @@ implementations and comparing the flash and static memory usage patterns to determine if it's worth moving the `format` strings to `formats[]` array in the `ZoneContext`. - #### ZoneInfo and ZoneEra The `zone_infos.h` and `zone_infos.cpp` files contain a `ZoneInfo` record for @@ -348,7 +299,6 @@ Near end of the `zone_info.h` file, we list the zones which were deliberately excluded by the tool. Also at the end of the `zone_info.h` file, there may be warnings about known inaccuracies for a particular zone. - #### ZonePolicy and ZoneRule The `zone_policies.h` and `zone_policies.cpp` hole the `RULE` entries from the @@ -358,7 +308,6 @@ entry in the TZ database. A `ZoneEra` record may hold a pointer to a `ZonePolicy` record. For example, the `kZoneAmerica_Los_Angeles` has a pointer to a `kZonePolicyUS` record. - #### Zone Registry The `zone_registry.h` and `zone_registry.cpp` files contain 2 pre-defined @@ -376,7 +325,6 @@ applications which may want to use the smaller `kZoneRegistry` to achieve complete coverage of all timezones with the same set of rules, without duplicates. - ### Offset Encoding The `zoneinfolow` storage format was optimized for small size. A number of @@ -467,7 +415,6 @@ SAVE (15-min resolution) +--------------------+ ``` - ### TinyYear Encoding The `zoneinfolow` storage format also implements a space saving measure by @@ -486,7 +433,6 @@ range needs to be extended, then the `baseYear` field in `ZoneContext` can be updated, the `zonedb` and `zonedbx` databases can be either regenerated or new versions of them can be created. - ## BasicZoneProcessor **TBD**: Add information about how the `BasicZoneProcessor` works. @@ -496,7 +442,6 @@ versions of them can be created. - able to detect gap conditions - *not* able to detect overlap conditions - ## ExtendedZoneProcessor The `CompleteZoneProcessor` is currently *identical* to the @@ -511,7 +456,6 @@ by these 2 classes are actually signficantly different (using `zoneinfolow` and Broker layer (i.e. the `ZoneXxxBroker` classes) allow the 2 databases to be processed by the exactly the same C++ code. - ### Search By Component or EpochSeconds There are 2 ways that a `ZonedDateTime` can be constructed: @@ -564,7 +508,6 @@ 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. - ### Step 1: Find Matches The `ExtendedZoneProcessor::findMatches()` finds all `ZoneEra` objects which @@ -580,7 +523,6 @@ in the following year after correcting for UTC offset. A `MatchingEra` is a wrapper around a `ZoneEra`, with its startDateTime and untilDateTime truncated to be within the 14-month interval of interest. - ### Step 2: Create Transitions The class creates an array of `Transition` objects spanning 14 months that @@ -702,7 +644,6 @@ y+1 R2| ) | +---------)-----------------------------------------------------+ ``` - ### Step 2A: Transition Time versus StartDateTime Many time stamps in the zonedb database are tricky to handle because they @@ -763,7 +704,6 @@ However, if T2c is determined to happen *after* U1/S2, then an extra Transition ----------------)|-------)|--)|----------)|------------------------> ``` - ### Step 3: Fix Transition Times After obtaining the combined list of Transitions, a final pass converts the @@ -771,7 +711,6 @@ transitionTime of all Transition (with any additional truncations and adjustments) into the wall time using the UTC offset of the *previous* Transition. - ### Step 4: Generate Start Until Times After the list of Transitions is created, the `Transition.startDateTime` @@ -782,204 +721,16 @@ and `Transition.untilDateTime` created using the transtionTime field. * The `startDateTime` of the current Transition is the current `transitionTime` shifted into the UTC offset of the *current* Transition. - ### Step 5: Calculate Abbreviations Finally, the time zone abbreviations (e.g. "PST", "CET") are calculated for each -Transition and recorded into the `Transition.abbrev` fixed sized array. The TZDB -specification says that the maximum length of an abbreviation is 6, so this -field is a static array of 7 characters (to account for the terminating NUL -character). - - -## Upgrading TZDB - -About 2-4 times a year, a new TZDB version is released. Here are some notes -(mostly for myself) on how to create a new release after a new TZDB version is -available. - -- Update the TZDB repo (https://github.com/eggert/tz). This should be a - sibling to the `AceTime` repo (since it will cause the least problems - for various internal scripts): - - `$ cd ../tz` - - `$ git pull` - - Check that the correct tag is pulled (e.g. `2020c` tag if that's the - version that we want to upgrade to). -- Update the Hinnant `date` repo (https://github.com:HowardHinnant/date). This - should be a sibling to the `AceTime` repo: - - `$ cd ../date` - - `$ git pull` -- Update the zonedb files for `acetimepy` (needed by AcetzBasicTest and - AcetzExtendedTest): - - `$ cd acetimepy` - - `$ vi src/zonedb*/Makefile` - - Update the `TZ_VERSION` variable in the various makefiles. - - `$ make zonedbs` - - `$ make all` -- Update the zonedb files for `acetimec` (needed by AcetimecBasicTest and - AcetimecExtendedTest) - - `$ cd acetimec/src` - - `$ vi zonedb*/Makefile` - - Update the `TZ_VERSION` variable in the various makefiles. - - `$ make zonedbs` - - `$ make` to update the `acetimec.a` lib file - - `$ cd ../tests` - - `$ make -j2` - - `$ make runtests` -- Update the zonedb files for `acetimego` - - `$ cd acetimego` - - `$ vi zonedb*/Makefile` - - Update the `TZ_VERSION` variable in the various makefiles. - - `$ make zonedbs` - - `$ make all` - - `$ make test` -- Update the zonedb files for `AceTime`: - - `$ cd AceTime/src` - - `$ vi zonedb*/Makefile` - - Update the `TZ_VERSION` variable in the makefiles. - - `$ make zonedbs` - - `$ cd ../tests` - - `$ make clean` - - `$ make -j2 tests` - - `$ make runtests` -- Recompile the binaries in `AceTimeValidation` tools - - `$ cd AceTimeValidation/tools` - - `$ make clean` - - `$ make -j2` -- Verify that `AceTimeValidation/tests` pass. - - `$ cd AceTimeValidation/tests` - - `$ make clean` - - `$ vi {Acetimec,Acetz,Hinnant}{Basic,Extended}*/Makefile` - - Update the `TZ_VERSION` variable in the following: - - `AcetimecBasicTest/Makefile` - - `AcetimecExtendedTest/Makefile` - - `AcetzBasicTest/Makefile` - - `AcetzExtendedTest/Makefile` - - `HinnantBasicTest/Makefile` - - `HinnantExtendedTest/Makefile` - - Validate against the other libraries: - - `$ make -j2 tests` - - `$ make runtests` - - (Debugging) To validate against one library, e.g. acetimec: - - `$ make -C AcetimecExtendedTest clean` - - `$ make -C AcetimecExtendedTest all` - - `$ make -C AcetimecExtendedTest run` -- Verify that `AceTimeValidation/validation` passes. - - `$ cd AceTimeValidation/validation` - - `$ make clean` - - `$ make -j2 validation` -- Update CHANGELOGs - - Copy a summary of the TZDB release notes from - https://mm.icann.org/pipermail/tz-announce/ to the various CHANGELOG.md - files. - - AceTime/CHANGELOG.md - - acetimec/CHANGELOG.md - - acetimego/CHANGELOG.md - - acetimepy/CHANGELOG.md - - AceTimeValidation/CHANGELOG.md -- Commit and push the changes for the following repos: - - AceTime - - acetimec - - acetimego - - acetimepy - - AceTimeValidation - -There are 12 other validation tests in the AceTimeValidation project that -compare AceTime with various other third party libraries: - -- `DateUtilBasicTest` -- `DateUtilExtendedTest` -- `GoBasicTest` -- `GoExtendedTest` -- `JavaBasicTest` -- `JavaExtendedTest` -- `NodaBasicTest` -- `NodaExtendedTest` -- `PytzBasicTest` -- `PytzExtendedTest` -- `ZoneInfoBasicTest` -- `ZoneInfoExtendedTest` - -Unfortunately, they all seem to use the underlying TZDB version provided by the -Operating System, and I have not been able to figure out how to manually update -this dependency manually. When a new TZDB is released, all of these tests will -fail until the underlying timezone database of the OS is updated. - - -## Release Process - -- Update `examples/MemoryBenchmark` and `examples/AutoBenchmark`. -- Update and commit the version numbers in various files: - - `src/AceTime.h` - - `README.md` - - `USER_GUIDE.md` - - `MIGRATING.md` - - `CHANGELOG.md` - - `docs/doxygen.cfg` - - `library.properties` - - `$ git commit -m "..."` - - `$ git push` -- Update and commit the Doxygen docs. This is done as a separate git commit - because the Doxygen changes are often so large that they obscure all other - important changes to the code base: - - `$ cd docs` - - `$ make clean` - - `$ make` - - `$ git add .` - - `$ git commit -m "..."` - - `$ git push` -- (Optional) Create a new Release of acetimepy - - (This should be done first, before AceTime) - - Go to https://github.com/bxparks/acetimepy - - Bump version number on `develop`. - - Merge `develop` into `master`. - - Click on "Releases" - - Click on "Draft a new release" - - Enter a tag version (e.g. `v1.2`), targeting the `master` branch. - - Enter the release title. - - Enter the release notes. I normally just copy and paste the latest changes - from `CHANGELOG.md`. - - Click Publish release. -- (Optional) Create a new Release of AceTimeTools - - (Depends on acetimepy) - - Go to https://github.com/bxparks/AceTimeTools - - Click on "Releases" - - Click on "Draft a new release" - - Enter a tag version (e.g. `v1.2`), targeting the `master` branch. - - Enter the release title. - - Enter the release notes. I normally just copy and paste the latest changes - from `CHANGELOG.md`. - - Click Publish release. -- (Optional) Create a new Release of AceTimeValidation. - - (Depends on acetimepy) - - Go to https://github.com/bxparks/AceTimeTools - - Click on "Releases" - - Click on "Draft a new release" - - Enter a tag version (e.g. `v1.2`), targeting the `master` branch. - - Enter the release title. - - Enter the release notes. I normally just copy and paste the latest changes - from `CHANGELOG.md`. - - Click Publish release. -- Create a new Release of AceTime (third, depends on AceTimeValidation). - - (Depends on acetimepy, AceTimeValidation) - - Go to https://github.com/bxparks/AceTime - - Merge the `develop` branch into `master` by creating a Pull Request. - - Approve and merge the PR. - - Click on "Releases" - - Click on "Draft a new release" - - Enter a tag version (e.g. `v1.2`), targeting the `master` branch. - - Enter the release title. - - Enter the release notes. I normally just copy and paste the latest changes - from `CHANGELOG.md`. - - Click Publish release. -- Add corresponding tags on acetimepy, AceTimeTools and AceTimeValidation - for reference. - - acetimepy - - `$ git tag -a 'atX.Y.Z' -m 'AceTime vX.Y.Z'` - - `$ git push --tags` - - AceTimeTools - - `$ git tag -a 'atX.Y.Z' -m 'AceTime vX.Y.Z'` - - `$ git push --tags` - - AceTimeValidation - - `$ git tag -a 'atX.Y.Z' -m 'AceTime vX.Y.Z'` - - `$ git push --tags` +Transition and recorded into the `Transition.abbrev` fixed sized array. + +The original TZDB specification used to say that the abbreviation could be 3-6 +characters, so this field used to be an array 7 characters (to account for the +terminating NUL character). That blub about 3-6 characters no longer seems to +exist. + +When support for `%z` was added, we needed to support formats of the form +`(+/-)hh[mm[ss]]` which can be 7 characters long. So the size of `abbrev` is now +8 (as specified in [kAbbrevSize](src/ace_time/common/common.h)). diff --git a/MIGRATING.md b/MIGRATING.md index a8740bc27..002ded71b 100644 --- a/MIGRATING.md +++ b/MIGRATING.md @@ -2,6 +2,7 @@ ## Table of Contents +* [Migrating to v3.0.0](#MigratingToVersion300) * [Migrating to v2.3.0](#MigratingToVersion220) * [Migrating to v2.2.0](#MigratingToVersion220) * [Migrating to v2.1.0](#MigratingToVersion210) @@ -20,6 +21,39 @@ * [Migrating the DS3231Clock](#MigratingTheDS3231Clock) * [Migrating to LinkManagers](#MigratingToLinkManagers) + +## Migrating to v3.0.0 + +### Info Container Class + +The various `ZoneInfo` classes are now nested inside an `Info` container class. +This greatly simplifies the maintenance of the code. But it introduces a small +breaking change. Instead of + +```C++ +const basic::ZoneInfo*` +const extended::ZoneInfo*` +const complete::ZoneInfo*` +``` +we need to use + +```C++ +const basic::Info::ZoneInfo*` +const extended::Info::ZoneInfo*` +const complete::Info::ZoneInfo*` +``` + +### Remove 'internal' Namespace + +The `ace_time::internal` namespace has been removed and all of its classes and +symbols have been lifted into the `ace_time` namespace. This makes the code +easier to understand and maintain. + +Being "internal", most of the objects were not documented. However, one symbol +which may have leaked out is `ace_time::internal::kAbbrevSize` which is the size +of the string buffer needed to hold the longest TimeZone abbreviation (e.g. +"PST"). This constant is now at `ace_time::kAbbrevSize`. + ## Migrating to v2.3.0 diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..15d9764b4 --- /dev/null +++ b/Makefile @@ -0,0 +1,6 @@ +zonedbs: + $(MAKE) -C src zonedbs + +clean: + $(MAKE) -C examples clean + $(MAKE) -C tests clean diff --git a/README.md b/README.md index e595b788c..bd361e2c3 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # AceTime [![AUnit Tests](https://github.com/bxparks/AceTime/actions/workflows/aunit_tests.yml/badge.svg)](https://github.com/bxparks/AceTime/actions/workflows/aunit_tests.yml) -[![Validation Tests](https://github.com/bxparks/AceTime/actions/workflows/validation.yml/badge.svg)](https://github.com/bxparks/AceTime/actions/workflows/validation.yml) The AceTime library provides Date, Time, and TimeZone classes which can convert "epoch seconds" from the AceTime Epoch (default 2050-01-01 UTC) to @@ -71,12 +70,12 @@ This library can be an alternative to the Arduino Time (https://github.com/PaulStoffregen/Time) and Arduino Timezone (https://github.com/JChristensen/Timezone) libraries. -**Major Changes in v2.3**: Add `CompleteZoneProcessor`, `CompleteZoneManager`, -and the `zonedbc` database to support all timezones, for all transitions defined -in the IANA TZ database (`[1844,2087]`), and extending the validity of timezone -calculations from `[2000,10000)` to `[0001,10000)`. +**Major Changes in v3.0**: Move `basic::ZoneInfo` to `basic::Info::ZoneInfo`, +`extended::ZoneInfo` to `extended::Info::ZoneInfo`, and `complete::ZoneInfo` to +`complete::Info::ZoneInfo`. Upgrade to TZDB 2025b. See [Migrating to +v3.0](MIGRATING.md#MigratingToVersion300) for more details. -**Version**: 2.4.0 (2024-12-13, TZDB version 2024b) +**Version**: 3.0.0 (2025-04-25, TZDB 2025b) **Changelog**: [CHANGELOG.md](CHANGELOG.md) @@ -87,11 +86,9 @@ calculations from `[2000,10000)` to `[0001,10000)`. **See Also**: * AceTimeClock (https://github.com/bxparks/AceTimeClock) -* AceTimeValidation (https://github.com/bxparks/AceTimeValidation) -* AceTimeTools (https://github.com/bxparks/AceTimeTools) -* acetimepy (https://github.com/bxparks/acetimepy) * acetimec (https://github.com/bxparks/acetimec) * acetimego (https://github.com/bxparks/acetimego) +* acetimepy (https://github.com/bxparks/acetimepy) ## Table of Contents @@ -164,13 +161,13 @@ The source files are organized as follows: * zone databases * `src/zonedb/` - files generated from TZ Database for `BasicZoneProcessor` (`ace_time::zonedb` namespace) - * `src/zonedbtesting/` - limited subset of `zonedb` for unit tests * `src/zonedbx/` - files generated from TZ Database for `ExtendedZoneProcessor` (`ace_time::zonedbx` namespace) - * `src/zonedbxtesting/` - limited subset of `zonedbx` for unit tests * `src/zonedbc/` - files generated from TZ Database for `CompleteZoneProcessor` (`ace_time::zonedbc` namespace) - * `src/zonedbctesting/` - limited subset of `zonedbc` for unit tests + * `src/testingzonedb/` - limited subset of `zonedb` for unit tests + * `src/testingzonedbx/` - limited subset of `zonedbx` for unit tests + * `src/testingzonedbc/` - limited subset of `zonedbc` for unit tests * `tests/ * unit tests using [AUnit](https://github.com/bxparks/AUnit) * `examples/` - example programs and benchmarks @@ -484,7 +481,7 @@ The full documentation of the following classes are given in the * ZoneName: unique human-readable string (e.g. "America/Los_Angeles") * Basic (not usually recommended) * 448 zones and links as of 2023c - * ZoneInfo (`const ace_time::basic::ZoneInfo*`) + * ZoneInfo (`const ace_time::basic::Info::ZoneInfo*`) * `ace_time::zonedb::kZoneAfrica_Abidjan` * ... * `ace_time::zonedb::kZonePacific_Wallis` @@ -529,7 +526,7 @@ The full documentation of the following classes are given in the ## Validation The details of how the Date, Time and TimeZone classes are validated are given -in [AceTimeValidation](https://github.com/bxparks/AceTimeValidation). +in `AceTimeSuite/validation/`. The ZoneInfo Database and the algorithms in this library were tested against other date/time libraries written in different programming languages. The @@ -541,11 +538,11 @@ contained bugs which prevented exact conformance with AceTime. **Conformant** -These libraries match the AceTime library (using the -`CompleteZoneProcessor`/`CompleteZoneManager` with the `zonedbc` database) -exactly: every DST transition, DST offset, STD offset, the epochsecond of the -transition, the converted data-time components, and the abbreviation. The match -was verified for all time zones and links, from 1800 until 2200. +These libraries match the AceTime library (using the `CompleteZoneManager` with +the `zonedbc` database) exactly: every DST transition, DST offset, STD offset, +the epochsecond of the transition, the converted data-time components, and the +abbreviation. The match was verified for all time zones and links, from 1800 +until 2200. - [C++11/14/17 Hinnant date](https://github.com/HowardHinnant/date) library - [GNU libc time](https://www.gnu.org/software/libc/libc.html) library @@ -597,44 +594,44 @@ sizeof(OffsetDateTime): 12 sizeof(TimeZone): 5 sizeof(TimeZoneData): 5 sizeof(ZonedDateTime): 17 -sizeof(ZonedExtra): 24 +sizeof(ZonedExtra): 25 sizeof(TimePeriod): 4 Basic: - sizeof(basic::ZoneContext): 20 - sizeof(basic::ZoneEra): 11 - sizeof(basic::ZoneInfo): 13 - sizeof(basic::ZoneRule): 9 - sizeof(basic::ZonePolicy): 3 + sizeof(basic::Info::ZoneContext): 20 + sizeof(basic::Info::ZoneEra): 11 + sizeof(basic::Info::ZoneInfo): 13 + sizeof(basic::Info::ZoneRule): 9 + sizeof(basic::Info::ZonePolicy): 3 sizeof(basic::ZoneRegistrar): 5 - sizeof(BasicZoneProcessor): 143 - sizeof(BasicZoneProcessorCache<1>): 147 + sizeof(BasicZoneProcessor): 148 + sizeof(BasicZoneProcessorCache<1>): 152 sizeof(BasicZoneManager): 7 - sizeof(BasicZoneProcessor::Transition): 26 + sizeof(BasicZoneProcessor::Transition): 27 Extended: - sizeof(extended::ZoneContext): 20 - sizeof(extended::ZoneEra): 11 - sizeof(extended::ZoneInfo): 13 - sizeof(extended::ZoneRule): 9 - sizeof(extended::ZonePolicy): 3 + sizeof(extended::Info::ZoneContext): 20 + sizeof(extended::Info::ZoneEra): 11 + sizeof(extended::Info::ZoneInfo): 13 + sizeof(extended::Info::ZoneRule): 9 + sizeof(extended::Info::ZonePolicy): 3 sizeof(extended::ZoneRegistrar): 5 - sizeof(ExtendedZoneProcessor): 553 - sizeof(ExtendedZoneProcessorCache<1>): 557 + sizeof(ExtendedZoneProcessor): 561 + sizeof(ExtendedZoneProcessorCache<1>): 565 sizeof(ExtendedZoneManager): 7 - sizeof(ExtendedZoneProcessor::Transition): 49 - sizeof(ExtendedZoneProcessor::TransitionStorage): 412 + sizeof(ExtendedZoneProcessor::Transition): 50 + sizeof(ExtendedZoneProcessor::TransitionStorage): 420 sizeof(ExtendedZoneProcessor::MatchingEra): 32 Complete: - sizeof(complete::ZoneContext): 20 - sizeof(complete::ZoneEra): 15 - sizeof(complete::ZoneInfo): 13 - sizeof(complete::ZoneRule): 12 - sizeof(complete::ZonePolicy): 3 + sizeof(complete::Info::ZoneContext): 20 + sizeof(complete::Info::ZoneEra): 15 + sizeof(complete::Info::ZoneInfo): 13 + sizeof(complete::Info::ZoneRule): 12 + sizeof(complete::Info::ZonePolicy): 3 sizeof(complete::ZoneRegistrar): 5 - sizeof(CompleteZoneProcessor): 553 - sizeof(CompleteZoneProcessorCache<1>): 557 + sizeof(CompleteZoneProcessor): 561 + sizeof(CompleteZoneProcessorCache<1>): 565 sizeof(CompleteZoneManager): 7 - sizeof(CompleteZoneProcessor::Transition): 49 - sizeof(CompleteZoneProcessor::TransitionStorage): 412 + sizeof(CompleteZoneProcessor::Transition): 50 + sizeof(CompleteZoneProcessor::TransitionStorage): 420 sizeof(CompleteZoneProcessor::MatchingEra): 32 ``` @@ -650,44 +647,44 @@ sizeof(OffsetDateTime): 12 sizeof(TimeZone): 12 sizeof(TimeZoneData): 8 sizeof(ZonedDateTime): 24 -sizeof(ZonedExtra): 24 +sizeof(ZonedExtra): 28 sizeof(TimePeriod): 4 Basic: - sizeof(basic::ZoneContext): 28 - sizeof(basic::ZoneEra): 16 - sizeof(basic::ZoneInfo): 24 - sizeof(basic::ZoneRule): 9 - sizeof(basic::ZonePolicy): 8 + sizeof(basic::Info::ZoneContext): 28 + sizeof(basic::Info::ZoneEra): 16 + sizeof(basic::Info::ZoneInfo): 24 + sizeof(basic::Info::ZoneRule): 9 + sizeof(basic::Info::ZonePolicy): 8 sizeof(basic::ZoneRegistrar): 8 sizeof(BasicZoneProcessor): 208 sizeof(BasicZoneProcessorCache<1>): 216 sizeof(BasicZoneManager): 12 sizeof(BasicZoneProcessor::Transition): 36 Extended: - sizeof(extended::ZoneContext): 28 - sizeof(extended::ZoneEra): 16 - sizeof(extended::ZoneInfo): 24 - sizeof(extended::ZoneRule): 9 - sizeof(extended::ZonePolicy): 8 + sizeof(extended::Info::ZoneContext): 28 + sizeof(extended::Info::ZoneEra): 16 + sizeof(extended::Info::ZoneInfo): 24 + sizeof(extended::Info::ZoneRule): 9 + sizeof(extended::Info::ZonePolicy): 8 sizeof(extended::ZoneRegistrar): 8 - sizeof(ExtendedZoneProcessor): 720 - sizeof(ExtendedZoneProcessorCache<1>): 728 + sizeof(ExtendedZoneProcessor): 752 + sizeof(ExtendedZoneProcessorCache<1>): 760 sizeof(ExtendedZoneManager): 12 - sizeof(ExtendedZoneProcessor::Transition): 60 - sizeof(ExtendedZoneProcessor::TransitionStorage): 516 + sizeof(ExtendedZoneProcessor::Transition): 64 + sizeof(ExtendedZoneProcessor::TransitionStorage): 548 sizeof(ExtendedZoneProcessor::MatchingEra): 44 Complete: - sizeof(complete::ZoneContext): 28 - sizeof(complete::ZoneEra): 20 - sizeof(complete::ZoneInfo): 24 - sizeof(complete::ZoneRule): 12 - sizeof(complete::ZonePolicy): 8 + sizeof(complete::Info::ZoneContext): 28 + sizeof(complete::Info::ZoneEra): 20 + sizeof(complete::Info::ZoneInfo): 24 + sizeof(complete::Info::ZoneRule): 12 + sizeof(complete::Info::ZonePolicy): 8 sizeof(complete::ZoneRegistrar): 8 - sizeof(CompleteZoneProcessor): 720 - sizeof(CompleteZoneProcessorCache<1>): 728 + sizeof(CompleteZoneProcessor): 752 + sizeof(CompleteZoneProcessorCache<1>): 760 sizeof(CompleteZoneManager): 12 - sizeof(CompleteZoneProcessor::Transition): 60 - sizeof(CompleteZoneProcessor::TransitionStorage): 516 + sizeof(CompleteZoneProcessor::Transition): 64 + sizeof(CompleteZoneProcessor::TransitionStorage): 548 sizeof(CompleteZoneProcessor::MatchingEra): 44 ``` @@ -743,23 +740,23 @@ Arduino Nano: | ZonedDateTime | 1444/ 30 | 970/ 19 | | Manual ZoneManager | 1406/ 13 | 932/ 2 | |----------------------------------------+--------------+--------------| -| Basic TimeZone (1 zone) | 7624/ 208 | 7150/ 197 | -| Basic TimeZone (2 zones) | 8170/ 357 | 7696/ 346 | -| BasicZoneManager (1 zone) | 7834/ 219 | 7360/ 208 | -| BasicZoneManager (all zones) | 19686/ 599 | 19212/ 588 | -| BasicZoneManager (all zones+links) | 25066/ 599 | 24592/ 588 | +| Basic TimeZone (1 zone) | 8238/ 225 | 7764/ 214 | +| Basic TimeZone (2 zones) | 8784/ 379 | 8310/ 368 | +| BasicZoneManager (1 zone) | 8448/ 236 | 7974/ 225 | +| BasicZoneManager (all zones) | 19508/ 386 | 19034/ 375 | +| BasicZoneManager (all zones+links) | 25116/ 386 | 24642/ 375 | |----------------------------------------+--------------+--------------| -| Basic ZoneSorterByName [1] | 8608/ 221 | 774/ 2 | -| Basic ZoneSorterByOffsetAndName [1] | 8740/ 221 | 906/ 2 | +| Basic ZoneSorterByName [1] | 9222/ 238 | 774/ 2 | +| Basic ZoneSorterByOffsetAndName [1] | 9344/ 238 | 896/ 2 | |----------------------------------------+--------------+--------------| -| Extended TimeZone (1 zone) | 11326/ 623 | 10852/ 612 | -| Extended TimeZone (2 zones) | 11924/ 1187 | 11450/ 1176 | -| ExtendedZoneManager (1 zone) | 11506/ 629 | 11032/ 618 | -| ExtendedZoneManager (all zones) | 34508/ 1111 | 34034/ 1100 | -| ExtendedZoneManager (all zones+links) | 40540/ 1111 | 40066/ 1100 | +| Extended TimeZone (1 zone) | 12072/ 643 | 11598/ 632 | +| Extended TimeZone (2 zones) | 12670/ 1215 | 12196/ 1204 | +| ExtendedZoneManager (1 zone) | 12252/ 649 | 11778/ 638 | +| ExtendedZoneManager (all zones) | 34740/ 847 | 34266/ 836 | +| ExtendedZoneManager (all zones+links) | 41000/ 847 | 40526/ 836 | |----------------------------------------+--------------+--------------| -| Extended ZoneSorterByName [2] | 12276/ 631 | 770/ 2 | -| Extended ZoneSorterByOffsetAndName [2] | 12360/ 631 | 854/ 2 | +| Extended ZoneSorterByName [2] | 13022/ 651 | 770/ 2 | +| Extended ZoneSorterByOffsetAndName [2] | 13116/ 651 | 864/ 2 | |----------------------------------------+--------------+--------------| | Complete TimeZone (1 zone) | -1/ -1 | -1/ -1 | | Complete TimeZone (2 zones) | -1/ -1 | -1/ -1 | @@ -784,32 +781,32 @@ ESP8266: | ZonedDateTime | 261573/27928 | 1484/ 36 | | Manual ZoneManager | 261553/27900 | 1464/ 8 | |----------------------------------------+--------------+--------------| -| Basic TimeZone (1 zone) | 267117/28528 | 7028/ 636 | -| Basic TimeZone (2 zones) | 267533/28736 | 7444/ 844 | -| BasicZoneManager (1 zone) | 267277/28552 | 7188/ 660 | -| BasicZoneManager (all zones) | 283613/28552 | 23524/ 660 | -| BasicZoneManager (all zones+links) | 292141/28552 | 32052/ 660 | +| Basic TimeZone (1 zone) | 267625/28292 | 7536/ 400 | +| Basic TimeZone (2 zones) | 268025/28500 | 7936/ 608 | +| BasicZoneManager (1 zone) | 267785/28316 | 7696/ 424 | +| BasicZoneManager (all zones) | 283305/28316 | 23216/ 424 | +| BasicZoneManager (all zones+links) | 292201/28316 | 32112/ 424 | |----------------------------------------+--------------+--------------| -| Basic ZoneSorterByName [1] | 268029/28552 | 752/ 0 | -| Basic ZoneSorterByOffsetAndName [1] | 268157/28552 | 880/ 0 | +| Basic ZoneSorterByName [1] | 268521/28316 | 736/ 0 | +| Basic ZoneSorterByOffsetAndName [1] | 268665/28316 | 880/ 0 | |----------------------------------------+--------------+--------------| -| Extended TimeZone (1 zone) | 269653/29152 | 9564/ 1260 | -| Extended TimeZone (2 zones) | 270069/29880 | 9980/ 1988 | -| ExtendedZoneManager (1 zone) | 269813/29160 | 9724/ 1268 | -| ExtendedZoneManager (all zones) | 301077/29160 | 40988/ 1268 | -| ExtendedZoneManager (all zones+links) | 310645/29160 | 50556/ 1268 | +| Extended TimeZone (1 zone) | 270073/28900 | 9984/ 1008 | +| Extended TimeZone (2 zones) | 270489/29660 | 10400/ 1768 | +| ExtendedZoneManager (1 zone) | 270217/28908 | 10128/ 1016 | +| ExtendedZoneManager (all zones) | 301129/28908 | 41040/ 1016 | +| ExtendedZoneManager (all zones+links) | 311065/28908 | 50976/ 1016 | |----------------------------------------+--------------+--------------| -| Extended ZoneSorterByName [2] | 270549/29160 | 736/ 0 | -| Extended ZoneSorterByOffsetAndName [2] | 270613/29160 | 800/ 0 | +| Extended ZoneSorterByName [2] | 270969/28908 | 752/ 0 | +| Extended ZoneSorterByOffsetAndName [2] | 271033/28908 | 816/ 0 | |----------------------------------------+--------------+--------------| -| Complete TimeZone (1 zone) | 270145/29508 | 10056/ 1616 | -| Complete TimeZone (2 zones) | 271425/30236 | 11336/ 2344 | -| CompleteZoneManager (1 zone) | 270289/29516 | 10200/ 1624 | -| CompleteZoneManager (all zones) | 350449/29516 | 90360/ 1624 | -| CompleteZoneManager (all zones+links) | 360017/29516 | 99928/ 1624 | +| Complete TimeZone (1 zone) | 270393/29092 | 10304/ 1200 | +| Complete TimeZone (2 zones) | 271673/29852 | 11584/ 1960 | +| CompleteZoneManager (1 zone) | 270537/29100 | 10448/ 1208 | +| CompleteZoneManager (all zones) | 350473/29100 | 90384/ 1208 | +| CompleteZoneManager (all zones+links) | 360425/29100 | 100336/ 1208 | |----------------------------------------+--------------+--------------| -| Complete ZoneSorterByName [3] | 271041/29516 | 752/ 0 | -| Complete ZoneSorterByOffsetAndName [3] | 271089/29516 | 800/ 0 | +| Complete ZoneSorterByName [3] | 271289/29100 | 752/ 0 | +| Complete ZoneSorterByOffsetAndName [3] | 271337/29100 | 800/ 0 | +---------------------------------------------------------------------+ ``` @@ -826,50 +823,50 @@ Arduino Nano: +--------------------------------------------------+----------+ | Method | micros | |--------------------------------------------------+----------| -| EmptyLoop | 5.000 | +| EmptyLoop | 3.000 | |--------------------------------------------------+----------| -| LocalDate::forEpochDays() | 241.000 | -| LocalDate::toEpochDays() | 52.000 | -| LocalDate::dayOfWeek() | 49.000 | +| LocalDate::forEpochDays() | 243.000 | +| LocalDate::toEpochDays() | 51.000 | +| LocalDate::dayOfWeek() | 50.000 | |--------------------------------------------------+----------| -| OffsetDateTime::forEpochSeconds() | 361.000 | -| OffsetDateTime::toEpochSeconds() | 78.000 | +| OffsetDateTime::forEpochSeconds() | 363.000 | +| OffsetDateTime::toEpochSeconds() | 77.000 | |--------------------------------------------------+----------| | ZonedDateTime::toEpochSeconds() | 75.000 | -| ZonedDateTime::toEpochDays() | 63.000 | -| ZonedDateTime::forEpochSeconds(UTC) | 391.000 | +| ZonedDateTime::toEpochDays() | 62.000 | +| ZonedDateTime::forEpochSeconds(UTC) | 393.000 | |--------------------------------------------------+----------| -| ZonedDateTime::forEpochSeconds(Basic_nocache) | 1710.000 | -| ZonedDateTime::forEpochSeconds(Basic_cached) | 707.000 | +| ZonedDateTime::forEpochSeconds(Basic_nocache) | 1728.000 | +| ZonedDateTime::forEpochSeconds(Basic_cached) | 708.000 | | ZonedDateTime::forEpochSeconds(Extended_nocache) | -1.000 | | ZonedDateTime::forEpochSeconds(Extended_cached) | -1.000 | | ZonedDateTime::forEpochSeconds(Complete_nocache) | -1.000 | | ZonedDateTime::forEpochSeconds(Complete_cached) | -1.000 | |--------------------------------------------------+----------| -| ZonedDateTime::forComponents(Basic_nocache) | 2252.000 | +| ZonedDateTime::forComponents(Basic_nocache) | 2270.000 | | ZonedDateTime::forComponents(Basic_cached) | 1254.000 | | ZonedDateTime::forComponents(Extended_nocache) | -1.000 | | ZonedDateTime::forComponents(Extended_cached) | -1.000 | | ZonedDateTime::forComponents(Complete_nocache) | -1.000 | | ZonedDateTime::forComponents(Complete_cached) | -1.000 | |--------------------------------------------------+----------| -| ZonedExtra::forEpochSeconds(Basic_nocache) | 1386.000 | -| ZonedExtra::forEpochSeconds(Basic_cached) | 378.000 | +| ZonedExtra::forEpochSeconds(Basic_nocache) | 1405.000 | +| ZonedExtra::forEpochSeconds(Basic_cached) | 380.000 | | ZonedExtra::forEpochSeconds(Extended_nocache) | -1.000 | | ZonedExtra::forEpochSeconds(Extended_cached) | -1.000 | | ZonedExtra::forEpochSeconds(Complete_nocache) | -1.000 | | ZonedExtra::forEpochSeconds(Complete_cached) | -1.000 | |--------------------------------------------------+----------| -| ZonedExtra::forComponents(Basic_nocache) | 2276.000 | -| ZonedExtra::forComponents(Basic_cached) | 1277.000 | +| ZonedExtra::forComponents(Basic_nocache) | 2295.000 | +| ZonedExtra::forComponents(Basic_cached) | 1279.000 | | ZonedExtra::forComponents(Extended_nocache) | -1.000 | | ZonedExtra::forComponents(Extended_cached) | -1.000 | | ZonedExtra::forComponents(Complete_nocache) | -1.000 | | ZonedExtra::forComponents(Complete_cached) | -1.000 | |--------------------------------------------------+----------| -| BasicZoneRegistrar::findIndexForName(binary) | 118.000 | -| BasicZoneRegistrar::findIndexForIdBinary() | 47.000 | -| BasicZoneRegistrar::findIndexForIdLinear() | 299.000 | +| BasicZoneRegistrar::findIndexForName(binary) | 121.000 | +| BasicZoneRegistrar::findIndexForIdBinary() | 48.000 | +| BasicZoneRegistrar::findIndexForIdLinear() | 295.000 | |--------------------------------------------------+----------| | ExtendedZoneRegistrar::findIndexForName(binary) | -1.000 | | ExtendedZoneRegistrar::findIndexForIdBinary() | -1.000 | @@ -888,58 +885,58 @@ ESP8266: +--------------------------------------------------+----------+ | Method | micros | |--------------------------------------------------+----------| -| EmptyLoop | 4.500 | +| EmptyLoop | 5.000 | |--------------------------------------------------+----------| -| LocalDate::forEpochDays() | 7.500 | -| LocalDate::toEpochDays() | 4.000 | +| LocalDate::forEpochDays() | 7.000 | +| LocalDate::toEpochDays() | 3.500 | | LocalDate::dayOfWeek() | 3.500 | |--------------------------------------------------+----------| -| OffsetDateTime::forEpochSeconds() | 12.000 | +| OffsetDateTime::forEpochSeconds() | 12.500 | | OffsetDateTime::toEpochSeconds() | 7.000 | |--------------------------------------------------+----------| | ZonedDateTime::toEpochSeconds() | 6.500 | -| ZonedDateTime::toEpochDays() | 5.500 | -| ZonedDateTime::forEpochSeconds(UTC) | 13.500 | +| ZonedDateTime::toEpochDays() | 5.000 | +| ZonedDateTime::forEpochSeconds(UTC) | 14.000 | |--------------------------------------------------+----------| -| ZonedDateTime::forEpochSeconds(Basic_nocache) | 141.500 | +| ZonedDateTime::forEpochSeconds(Basic_nocache) | 146.000 | | ZonedDateTime::forEpochSeconds(Basic_cached) | 21.500 | -| ZonedDateTime::forEpochSeconds(Extended_nocache) | 354.500 | +| ZonedDateTime::forEpochSeconds(Extended_nocache) | 364.000 | | ZonedDateTime::forEpochSeconds(Extended_cached) | 28.000 | -| ZonedDateTime::forEpochSeconds(Complete_nocache) | 407.000 | +| ZonedDateTime::forEpochSeconds(Complete_nocache) | 447.000 | | ZonedDateTime::forEpochSeconds(Complete_cached) | 28.000 | |--------------------------------------------------+----------| -| ZonedDateTime::forComponents(Basic_nocache) | 159.000 | -| ZonedDateTime::forComponents(Basic_cached) | 46.000 | -| ZonedDateTime::forComponents(Extended_nocache) | 241.500 | +| ZonedDateTime::forComponents(Basic_nocache) | 163.500 | +| ZonedDateTime::forComponents(Basic_cached) | 45.500 | +| ZonedDateTime::forComponents(Extended_nocache) | 273.000 | | ZonedDateTime::forComponents(Extended_cached) | 2.500 | -| ZonedDateTime::forComponents(Complete_nocache) | 354.000 | -| ZonedDateTime::forComponents(Complete_cached) | 2.500 | +| ZonedDateTime::forComponents(Complete_nocache) | 417.000 | +| ZonedDateTime::forComponents(Complete_cached) | 49.500 | |--------------------------------------------------+----------| -| ZonedExtra::forEpochSeconds(Basic_nocache) | 134.500 | +| ZonedExtra::forEpochSeconds(Basic_nocache) | 139.500 | | ZonedExtra::forEpochSeconds(Basic_cached) | 11.000 | -| ZonedExtra::forEpochSeconds(Extended_nocache) | 308.000 | -| ZonedExtra::forEpochSeconds(Extended_cached) | 18.000 | -| ZonedExtra::forEpochSeconds(Complete_nocache) | 396.000 | +| ZonedExtra::forEpochSeconds(Extended_nocache) | 329.500 | +| ZonedExtra::forEpochSeconds(Extended_cached) | 17.500 | +| ZonedExtra::forEpochSeconds(Complete_nocache) | 436.500 | | ZonedExtra::forEpochSeconds(Complete_cached) | 17.500 | |--------------------------------------------------+----------| -| ZonedExtra::forComponents(Basic_nocache) | 184.500 | -| ZonedExtra::forComponents(Basic_cached) | 48.500 | -| ZonedExtra::forComponents(Extended_nocache) | 268.000 | -| ZonedExtra::forComponents(Extended_cached) | 29.000 | -| ZonedExtra::forComponents(Complete_nocache) | 332.500 | +| ZonedExtra::forComponents(Basic_nocache) | 166.500 | +| ZonedExtra::forComponents(Basic_cached) | 48.000 | +| ZonedExtra::forComponents(Extended_nocache) | 252.000 | +| ZonedExtra::forComponents(Extended_cached) | 5.000 | +| ZonedExtra::forComponents(Complete_nocache) | 348.500 | | ZonedExtra::forComponents(Complete_cached) | 5.000 | |--------------------------------------------------+----------| -| BasicZoneRegistrar::findIndexForName(binary) | 18.000 | -| BasicZoneRegistrar::findIndexForIdBinary() | 6.500 | -| BasicZoneRegistrar::findIndexForIdLinear() | 43.000 | +| BasicZoneRegistrar::findIndexForName(binary) | 17.500 | +| BasicZoneRegistrar::findIndexForIdBinary() | 7.000 | +| BasicZoneRegistrar::findIndexForIdLinear() | 42.500 | |--------------------------------------------------+----------| -| ExtendedZoneRegistrar::findIndexForName(binary) | 24.500 | -| ExtendedZoneRegistrar::findIndexForIdBinary() | 6.000 | -| ExtendedZoneRegistrar::findIndexForIdLinear() | 50.500 | +| ExtendedZoneRegistrar::findIndexForName(binary) | 18.500 | +| ExtendedZoneRegistrar::findIndexForIdBinary() | 6.500 | +| ExtendedZoneRegistrar::findIndexForIdLinear() | 42.500 | |--------------------------------------------------+----------| -| CompleteZoneRegistrar::findIndexForName(binary) | 18.500 | -| CompleteZoneRegistrar::findIndexForIdBinary() | 6.500 | -| CompleteZoneRegistrar::findIndexForIdLinear() | 43.000 | +| CompleteZoneRegistrar::findIndexForName(binary) | 19.000 | +| CompleteZoneRegistrar::findIndexForIdBinary() | 7.000 | +| CompleteZoneRegistrar::findIndexForIdLinear() | 42.500 | +--------------------------------------------------+----------+ Iterations_per_run: 2000 ``` @@ -1328,15 +1325,13 @@ environment because: and cumbersome to use for the simple use cases targeted by the AceTime library. -The Hinnant date libraries were invaluable for writing the -[HinnantBasicTest](https://github.com/bxparks/AceTimeValidation/tree/master/tests/HinnantBasicTest) -and -[HinnantExtendedTest](https://github.com/bxparks/AceTimeValidation/tree/master/tests/HinnantExtendedTest) -validation tests which compare the AceTime algorithms to the Hinnant Date -algorithms. For all times zones between the years 2000 until 2100, the AceTime -date-time components (`ZonedDateTime`) and abbreviations (`ZonedExtra`) -calculated from the given epochSeconds match the results from the Hinnant Date -libraries. +The Hinnant date libraries are used as the reference library in +`AceTimeSuite/validation/tests/HinnantBasicTest` and +`AceTimeSuite/validation/tests/HinnantExtendedTest` which compare the AceTime +algorithms to the Hinnant Date algorithms. For all times zones between the years +2000 until 2100, the AceTime date-time components (`ZonedDateTime`) and +abbreviations (`ZonedExtra`) calculated from the given epochSeconds match the +results from the Hinnant Date libraries. ### Google cctz diff --git a/USER_GUIDE.md b/USER_GUIDE.md index 55882f358..891d3362d 100644 --- a/USER_GUIDE.md +++ b/USER_GUIDE.md @@ -18,7 +18,7 @@ The IANA TZ database is programmatically generated into 3 predefined databases: databases have different accuracy ranges, and are designed to work with different `ZoneProcessor` and `ZoneManager` classes. -**Version**: 2.4.0 (2024-12-13, TZDB 2024b) +**Version**: 3.0.0 (2025-04-25, TZDB 2025b) **Related Documents**: @@ -235,17 +235,17 @@ information are defined in the following namespaces. They are not expected to be used by application developers under normal circumstances, so these are listed here for reference: -* `ace_time::basic::ZoneXxx` -* `ace_time::extended::ZoneXxx` -* `ace_time::complete::ZoneXxx` +* `ace_time::basic::Info::ZoneXxx` +* `ace_time::extended::Info::ZoneXxx` +* `ace_time::complete::Info::ZoneXxx` -The `basic::ZoneInfo` and `extended::ZoneInfo` classes (and their associated -`ZoneProcessor` classes) have a resolution of 1 minute, which is sufficient to -represent all UTC offsets and DST shifts of all timezones after 1972 -(Africa/Monrovia seems like the last timezone to conform to a one-minute -resolution on Jan 7, 1972). The `complete::ZoneInfo` classes have a resolution -of 1 second, which allows them to represent all timezones, for all years in the -TZ database, over the years `[0001,10000)`. +The `basic::Info::ZoneInfo` and `extended::Info::ZoneInfo` classes (and their +associated `ZoneProcessor` classes) have a resolution of 1 minute, which is +sufficient to represent all UTC offsets and DST shifts of all timezones after +1972 (Africa/Monrovia seems like the last timezone to conform to a one-minute +resolution on Jan 7, 1972). The `complete::Info::ZoneInfo` classes have a +resolution of 1 second, which allows them to represent all timezones, for all +years in the TZ database, over the years `[0001,10000)`. It is expected that most applications using AceTime will use only a small number of timezones at the same time (1 to 4 zones have been extensively tested) and @@ -982,15 +982,15 @@ class TimeZone { int8_t dstMinute = 0); static TimeZone forZoneInfo( - const basic::ZoneInfo* zoneInfo, + const basic::Info::ZoneInfo* zoneInfo, BasicZoneProcessor* zoneProcessor); static TimeZone forZoneInfo( - const extended::ZoneInfo* zoneInfo, + const extended::Info::ZoneInfo* zoneInfo, ExtendedZoneProcessor* zoneProcessor); static TimeZone forZoneInfo( - const complete::ZoneInfo* zoneInfo, + const complete::Info::ZoneInfo* zoneInfo, CompleteZoneProcessor* zoneProcessor); static TimeZone forUtc(); @@ -1200,7 +1200,7 @@ void someFunction() { #### Extended TimeZone (kTypeExtended) This TimeZone is created using two objects: -* the `extended::ZoneInfo` data objects contained in +* the `extended::Info::ZoneInfo` data objects contained in [zonedbx/zone_infos.h](src/zonedbx/zone_infos.h) * an external instance of `ExtendedZoneProcessor` needed for calculating zone transitions @@ -1651,7 +1651,8 @@ methods on the `ZonedExtra` class: uint8_t fold = 0)` Often the `ZonedDateTime` will be created first from the epochSeconds, then the -`ZonedExtra` will be created to access additional information about the time zone at that particular epochSeconds (e.g. abbreviation): +`ZonedExtra` will be created to access additional information about the time +zone at that particular epochSeconds (e.g. abbreviation): ```C++ ExtendedZoneProcessor zoneProcessor; @@ -1741,7 +1742,7 @@ class BasicZoneManager { public: BasicZoneManager( uint16_t registrySize, - const basic::ZoneInfo* const* zoneRegistry, + const basic::Info::ZoneInfo* const* zoneRegistry, BasicZoneProcessorCacheBase& zoneProcessorCache ); @@ -1751,7 +1752,7 @@ class BasicZoneManager { TimeZone createForZoneId(uint32_t id); TimeZone createForZoneIndex(uint16_t index); TimeZone createForTimeZoneData(const TimeZoneData& d); - TimeZone createForZoneInfo(const basic::ZoneInfo* zoneInfo); + TimeZone createForZoneInfo(const basic::Info::ZoneInfo* zoneInfo); uint16_t indexForZoneName(const char* name) const; uint16_t indexForZoneId(uint32_t id) const; @@ -1764,17 +1765,17 @@ class ExtendedZoneManager { public: ExtendedZoneManager( uint16_t registrySize, - const extended::ZoneInfo* const* zoneRegistry, + const extended::Info::ZoneInfo* const* zoneRegistry, ExtendedZoneProcessorCacheBase& zoneProcessorCache ); uint16_t zoneRegistrySize() const; - TimeZone createForZoneInfo(const extended::ZoneInfo* zoneInfo); + TimeZone createForZoneInfo(const extended::Info::ZoneInfo* zoneInfo); TimeZone createForZoneId(uint32_t id); TimeZone createForZoneIndex(uint16_t index); TimeZone createForTimeZoneData(const TimeZoneData& d); - TimeZone createForZoneInfo(const extended::ZoneInfo* zoneInfo); + TimeZone createForZoneInfo(const extended::Info::ZoneInfo* zoneInfo); uint16_t indexForZoneName(const char* name) const; uint16_t indexForZoneId(uint32_t id) const; @@ -1787,17 +1788,17 @@ class CompleteZoneManager { public: CompleteZoneManager( uint16_t registrySize, - const extended::ZoneInfo* const* zoneRegistry, + const extended::Info::ZoneInfo* const* zoneRegistry, CompleteZoneProcessorCacheBase& zoneProcessorCache ); uint16_t zoneRegistrySize() const; - TimeZone createForZoneInfo(const extended::ZoneInfo* zoneInfo); + TimeZone createForZoneInfo(const extended::Info::ZoneInfo* zoneInfo); TimeZone createForZoneId(uint32_t id); TimeZone createForZoneIndex(uint16_t index); TimeZone createForTimeZoneData(const TimeZoneData& d); - TimeZone createForZoneInfo(const extended::ZoneInfo* zoneInfo); + TimeZone createForZoneInfo(const extended::Info::ZoneInfo* zoneInfo); uint16_t indexForZoneName(const char* name) const; uint16_t indexForZoneId(uint32_t id) const; @@ -2022,11 +2023,11 @@ Another useful feature of `ZoneManager::createForZoneId()` over `ZoneManager` interface. In contrast, there are 2 different versions of `createForZoneInfo()` which live in the corresponding ZoneManager implementation classes because each version needs a different `ZoneInfo` type -(`basic::ZoneInfo` and `extended::ZoneInfo`). If your code has a reference or -pointer to the top-level `ZoneManager` interface, then it will be far easier to -create a `TimeZone` using `createForZoneId()`. You do pay a penalty in -efficiency because `createForZoneId()` must scan the database, where as -`createForZoneInfo()` does not perform a search since it has direct access to +(`basic::Info::ZoneInfo` and `extended::Info::ZoneInfo`). If your code has a +reference or pointer to the top-level `ZoneManager` interface, then it will be +far easier to create a `TimeZone` using `createForZoneId()`. You do pay a +penalty in efficiency because `createForZoneId()` must scan the database, where +as `createForZoneInfo()` does not perform a search since it has direct access to the `ZoneInfo` data structure. @@ -2399,9 +2400,9 @@ defined in the `zoneinfo/ZoneInfoXxx.h` header files: In v2.3, three versions of these ZoneInfo records were created, to support the 3 different zonedb types: -* `ace_time::basic::ZoneXxx` - used with `BasicZoneProcessor` -* `ace_time::extended::ZoneXxx` - used with `ExtendedZoneProcessor` -* `ace_time::complete::ZoneXxx` - used with `CompleteZoneProcessor` +* `ace_time::basic::Info::ZoneXxx` - used with `BasicZoneProcessor` +* `ace_time::extended::Info::ZoneXxx` - used with `ExtendedZoneProcessor` +* `ace_time::complete::Info::ZoneXxx` - used with `CompleteZoneProcessor` Information stored in `PROGMEM` must be retrieved using special functions (e.g. `pgm_read_byte()`, `pgm_read_word()`, etc). A thin layer of indirection is @@ -2419,9 +2420,9 @@ abstraction layer is provided by `zoneinfo/Brokers.h`: There are 3 sets of these broker classes, duplicated into 2 different C++ namespaces: -* `ace_time::basic::ZoneXxxBroker` -* `ace_time::extended::ZoneXxxBroker` -* `ace_time::complete::ZoneXxxBroker` +* `ace_time::basic::Info::ZoneXxxBroker` +* `ace_time::extended::Info::ZoneXxxBroker` +* `ace_time::complete::Info::ZoneXxxBroker` The separate namespaces allows compile-time verification that the correct `zonedb*` database is used with the correct `BasicZoneProcessor`, @@ -2439,9 +2440,9 @@ general consumption: These 3 are meant for unit tests: -* `zonedbtesting` for `BasicZoneProcessor` -* `zonedbxtesting` for `ExtendedZoneProcessor` -* `zonedbctesting` for `CompleteZoneProcessor` +* `testingzonedb` for `BasicZoneProcessor` +* `testingzonedbx` for `ExtendedZoneProcessor` +* `testingzonedbc` for `CompleteZoneProcessor` #### Basic zonedb @@ -2570,8 +2571,8 @@ was intended to be used by the client application: using namespace ace_time; void printStartAndUntilYears() { - ace_time::extended::ZoneInfoBroker info(&zonedbx::kZoneAmerica_Los_Angeles); - extended::ZoneContextBroker context = info.zoneContext(); + ace_time::extended::Info::ZoneInfoBroker info(&zonedbx::kZoneAmerica_Los_Angeles); + extended::Info::ZoneContextBroker context = info.zoneContext(); Serial.print("startYear: "); Serial.print(context.startYear()); // e.g. 2000 @@ -2596,12 +2597,12 @@ timezone were filtered out of the zone database. #### External Zone Classes -The `basic::ZoneInfo`, `extended::ZoneInfo`, and `complete::ZoneInfo` objects -are meant to be used as *opaque* pointers and simply passed into the `TimeZone` -class (which in turn, passes the pointer into the corresponding `ZoneProcessor` -objects.) The internal formats of the `ZoneInfo` structures may change without -warning, and users of this library should not access its internal data members -directly. +The `basic::Info::ZoneInfo`, `extended::Info::ZoneInfo`, and +`complete::Info::ZoneInfo` objects are meant to be used as *opaque* pointers and +simply passed into the `TimeZone` class (which in turn, passes the pointer into +the corresponding `ZoneProcessor` objects.) The internal formats of the +`ZoneInfo` structures may change without warning, and users of this library +should not access its internal data members directly. Instead, client applications should use the `BasicZone`, `ExtendedZone`, and `CompleteZone` classes which aims to provide stable external access to some of @@ -2612,7 +2613,7 @@ namespace ace_time { class BasicZone { public: - BasicZone(const basic::ZoneInfo* zoneInfo); + BasicZone(const basic::Info::ZoneInfo* zoneInfo); bool isNull() const; uint32_t zoneId() const; @@ -2625,7 +2626,7 @@ class BasicZone { class ExtendedZone { public: - ExtendedZone(const extended::ZoneInfo* zoneInfo); + ExtendedZone(const extended::Info::ZoneInfo* zoneInfo); bool isNull() const; uint32_t zoneId() const; @@ -2675,7 +2676,7 @@ These objects are meant to be used transiently, created on the stack then thrown away. For example: ```C++ -const extended::ZoneInfo* zoneInfo = ...; +const extended::Info::ZoneInfo* zoneInfo = ...; ExtendedZone(zoneInfo).printNameTo(Serial); Serial.println(); ``` @@ -2694,7 +2695,7 @@ zone name into the memory buffer, then extract the string from the buffer: using ace_common::PrintStr; ... -const extended::ZoneInfo* zoneInfo = ...; +const extended::Info::ZoneInfo* zoneInfo = ...; PrintStr<32> printStr; // buffer of 32 bytes on the stack ExtendedZone(zoneInfo).printNameTo(printStr); @@ -2770,7 +2771,7 @@ zones from the `zonedbx::` data set: #include using namespace ace_time; ... -static const extended::ZoneInfo* const kCustomRegistry[] ACE_TIME_PROGMEM = { +static const extended::Info::ZoneInfo* const kCustomRegistry[] ACE_TIME_PROGMEM = { &zonedbx::kZoneAmerica_Los_Angeles, &zonedbx::kZoneAmerica_Denver, &zonedbx::kZoneAmerica_Chicago, @@ -2778,7 +2779,7 @@ static const extended::ZoneInfo* const kCustomRegistry[] ACE_TIME_PROGMEM = { }; static const uint16_t kCustomRegistrySize = - sizeof(kCustomRegistry) / sizeof(extended::ZoneInfo*); + sizeof(kCustomRegistry) / sizeof(extended::Info::ZoneInfo*); static const uint16_t CACHE_SIZE = 2; static ExtendedZoneProcessorCache zoneProcessorCache; @@ -2810,7 +2811,7 @@ C++ code representing these custom zone registries from a list of zones.) (**TBD**: It might also be useful for app developers to create custom datasets with different range of years. The tools are all here, but not explicitly documented currently. Examples of how to this do exist inside the various -`Makefile` files in the AceTimeValidation project.) +`Makefile` files under `AceTimeSuite/validation/tests/*/Makefile`.) ## Zone Sorting diff --git a/docs/doxygen.cfg b/docs/doxygen.cfg index fe18e1720..b6e5ed102 100644 --- a/docs/doxygen.cfg +++ b/docs/doxygen.cfg @@ -38,7 +38,7 @@ PROJECT_NAME = AceTime # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2.4.0 +PROJECT_NUMBER = 3.0.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/docs/html/AceTime_8h_source.html b/docs/html/AceTime_8h_source.html index 8ac70b621..070b11f6d 100644 --- a/docs/html/AceTime_8h_source.html +++ b/docs/html/AceTime_8h_source.html @@ -5,7 +5,7 @@ -AceTime: /home/brian/src/AceTime/src/AceTime.h Source File +AceTime: /home/brian/src/AceTimeSuite/libraries/AceTimeLib/src/AceTime.h Source File @@ -22,7 +22,7 @@
AceTime -  2.4.0 +  3.0.0
Date and time classes for Arduino that support timezones from the TZ Database.
@@ -88,7 +88,7 @@
26 
27 #include "zoneinfo/compat.h"
28 #include "zoneinfo/infos.h"
-
29 #include "zoneinfo/brokers.h"
+
29 //
30 #include "ace_time/common/common.h"
31 #include "ace_time/common/DateStrings.h"
32 #include "ace_time/common/DateConv.h"
@@ -103,24 +103,24 @@
42 #include "ace_time/OffsetDateTime.h"
-
44 #include "ace_time/ZoneProcessor.h"
-
45 #include "ace_time/BasicZoneProcessor.h"
-
46 #include "ace_time/ExtendedZoneProcessor.h"
-
47 #include "ace_time/CompleteZoneProcessor.h"
-
48 #include "ace_time/ZoneProcessorCache.h"
-
49 #include "ace_time/ZoneRegistrar.h"
-
50 #include "ace_time/ZoneManager.h"
-
51 #include "ace_time/ZoneSorterByName.h"
-
52 #include "ace_time/ZoneSorterByOffsetAndName.h"
-
53 #include "ace_time/TimeZoneData.h"
-
54 #include "ace_time/TimeZone.h"
-
55 #include "ace_time/BasicZone.h"
-
56 #include "ace_time/ExtendedZone.h"
+
44 //
+
45 #include "ace_time/ZoneProcessor.h"
+
46 #include "ace_time/BasicZoneProcessor.h"
+
47 #include "ace_time/ExtendedZoneProcessor.h"
+
48 #include "ace_time/CompleteZoneProcessor.h"
+
49 #include "ace_time/ZoneProcessorCache.h"
+
50 #include "ace_time/ZoneRegistrar.h"
+
51 #include "ace_time/Zone.h"
+
52 #include "ace_time/ZoneManager.h"
+
53 #include "ace_time/ZoneSorterByName.h"
+
54 #include "ace_time/ZoneSorterByOffsetAndName.h"
+
55 #include "ace_time/TimeZoneData.h"
+
56 #include "ace_time/TimeZone.h"
57 #include "ace_time/ZonedDateTime.h"
59 #include "ace_time/TimePeriod.h"
-
61 #include "ace_time/ace_time_utils.h"
+
61 //
62 #include "zonedb/zone_policies.h"
63 #include "zonedb/zone_infos.h"
64 #include "zonedb/zone_registry.h"
@@ -132,11 +132,10 @@
70 #include "zonedbc/zone_registry.h"
71 
72 // Version format: xxyyzz == "xx.yy.zz"
-
73 #define ACE_TIME_VERSION 20400
-
74 #define ACE_TIME_VERSION_STRING "2.4.0"
+
73 #define ACE_TIME_VERSION 30000
+
74 #define ACE_TIME_VERSION_STRING "3.0.0"
75 
76 #endif
-
The brokers in the basic:: and extended:: namespaces are identical in code.
Identifiers used by implementation code which need to be publically exported.
Macros and definitions that provide a consistency layer among the various Arduino boards for compatib...
Methods that mutate an OffsetDateTime object.
diff --git a/docs/html/BasicZoneProcessor_8h_source.html b/docs/html/BasicZoneProcessor_8h_source.html index fe4bdecd4..b8f9182c0 100644 --- a/docs/html/BasicZoneProcessor_8h_source.html +++ b/docs/html/BasicZoneProcessor_8h_source.html @@ -5,7 +5,7 @@ -AceTime: /home/brian/src/AceTime/src/ace_time/BasicZoneProcessor.h Source File +AceTime: /home/brian/src/AceTimeSuite/libraries/AceTimeLib/src/ace_time/BasicZoneProcessor.h Source File @@ -22,7 +22,7 @@
AceTime -  2.4.0 +  3.0.0
Date and time classes for Arduino that support timezones from the TZ Database.
@@ -82,367 +82,375 @@
9 #include <stdint.h>
10 #include <AceCommon.h> // strncpy_T()
11 #include "../zoneinfo/infos.h"
-
12 #include "../zoneinfo/brokers.h"
-
13 #include "common/common.h" // kAbbrevSize
-
14 #include "common/logging.h"
-
15 #include "TimeOffset.h"
-
16 #include "LocalDate.h"
-
17 #include "OffsetDateTime.h"
-
18 #include "ZoneProcessor.h"
-
19 
-
20 #ifndef ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG
-
21 #define ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG 0
-
22 #endif
-
23 
-
24 class BasicZoneProcessorTest_priorYearOfRule;
-
25 class BasicZoneProcessorTest_compareRulesBeforeYear;
-
26 class BasicZoneProcessorTest_findLatestPriorRule;
-
27 class BasicZoneProcessorTest_findZoneEra;
-
28 class BasicZoneProcessorTest_init_primitives;
-
29 class BasicZoneProcessorTest_initForLocalDate;
-
30 class BasicZoneProcessorTest_setZoneKey;
-
31 class BasicZoneProcessorTest_calcRuleOffsetMinutes;
-
32 
-
33 class Print;
-
34 
-
35 namespace ace_time {
-
36 namespace basic {
-
37 
-
58 template <typename ZIB, typename ZEB, typename ZPB, typename ZRB>
- -
65  ZEB era;
-
66 
-
76  ZRB rule;
-
77 
- -
80 
-
85  int16_t offsetMinutes;
-
86 
-
88  int16_t deltaMinutes;
-
89 
-
91  int16_t year;
-
92 
-
98  uint8_t month;
-
99 
-
107  char abbrev[internal::kAbbrevSize];
-
108 
-
110  void log() const {
-
111  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
-
112  logging::printf("(%d/%d)", year, month);
-
113  if (sizeof(acetime_t) == sizeof(int)) {
-
114  logging::printf("; stEps: %d", startEpochSeconds);
-
115  } else {
-
116  logging::printf("; stEps: %ld", startEpochSeconds);
-
117  }
-
118  logging::printf("; offMin: %d", offsetMinutes);
-
119  logging::printf("; abbrev: %s", abbrev);
-
120  if (! rule.isNull()) {
-
121  logging::printf("; r.fromYear: %d", rule.fromYear());
-
122  logging::printf("; r.toYear: %d", rule.toYear());
-
123  logging::printf("; r.inMonth: %d", rule.inMonth());
-
124  logging::printf("; r.onDayOfMonth: %d", rule.onDayOfMonth());
-
125  }
-
126  logging::printf("\n");
-
127  }
-
128  }
-
129 };
-
130 
-
132 inline int8_t compareYearMonth(int16_t aYear, uint8_t aMonth,
-
133  int16_t bYear, uint8_t bMonth) {
-
134  if (aYear < bYear) return -1;
-
135  if (aYear > bYear) return 1;
-
136  if (aMonth < bMonth) return -1;
-
137  if (aMonth > bMonth) return 1;
-
138  return 0;
-
139 }
-
140 
-
141 } // namespace basic
-
142 
-
200 template <typename ZIS, typename ZIB, typename ZEB, typename ZPB, typename ZRB>
- -
202  public:
- -
205 
-
206  bool isLink() const override {
-
207  return ! mZoneInfoBroker.targetInfo().isNull();
-
208  }
-
209 
-
210  uint32_t getZoneId() const override {
-
211  return mZoneInfoBroker.zoneId();
-
212  }
-
213 
- -
243  const LocalDateTime& ldt) const override {
-
244  FindResult result;
-
245  bool success = initForLocalDate(ldt.localDate());
-
246  if (!success) return result;
-
247 
-
248  // 0) Use the UTC epochSeconds to get intial guess of offset.
-
249  acetime_t epochSeconds0 = ldt.toEpochSeconds();
-
250  auto result0 = findByEpochSeconds(epochSeconds0);
-
251  if (result0.type == FindResult::kTypeNotFound) return result;
-
252  auto offset0 = TimeOffset::forSeconds(
-
253  result0.reqStdOffsetSeconds + result0.reqDstOffsetSeconds);
-
254 
-
255  // 1) Use offset0 to get the next epochSeconds and offset.
-
256  auto odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset0);
-
257  acetime_t epochSeconds1 = odt.toEpochSeconds();
-
258  auto result1 = findByEpochSeconds(epochSeconds1);
-
259  if (result1.type == FindResult::kTypeNotFound) return result;
-
260  auto offset1 = TimeOffset::forSeconds(
-
261  result1.reqStdOffsetSeconds + result1.reqDstOffsetSeconds);
-
262 
-
263  // 2) Use offset1 to get the next epochSeconds and offset.
-
264  odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset1);
-
265  acetime_t epochSeconds2 = odt.toEpochSeconds();
-
266  auto result2 = findByEpochSeconds(epochSeconds2);
-
267  if (result2.type == FindResult::kTypeNotFound) return result;
-
268  auto offset2 = TimeOffset::forSeconds(
-
269  result2.reqStdOffsetSeconds + result2.reqDstOffsetSeconds);
-
270 
-
271  // If offset1 and offset2 are equal, then we have an equilibrium
-
272  // and odt(1) must equal odt(2).
-
273  if (offset1 == offset2) {
-
274  // I think this happens for kTypeExact or kTypeOverlap, but the current
-
275  // algorithm cannot distinguish between the two, so let's pretend that
-
276  // it is kTypeExact. Pick either of result1 or result2.
-
277  result = result1;
-
278  } else {
-
279  // If the offsets don't match, then I think we have a kTypeGap.
-
280  // Pick the stdOffset and dstOffset that generate the later epochSeconds
-
281  // (the earlier transition), but convert into the LocalDateTime of the
-
282  // earlier epochSeconds (the later transition).
-
283  if (epochSeconds1 > epochSeconds2) {
-
284  result = result1;
-
285  } else {
-
286  result = result2;
-
287  }
-
288  result.type = FindResult::kTypeGap;
-
289  }
-
290 
-
291  return result;
-
292  }
-
293 
-
294  FindResult findByEpochSeconds(acetime_t epochSeconds) const override {
-
295  FindResult result;
-
296  const Transition* transition = getTransition(epochSeconds);
-
297  if (!transition) return result;
+
12 #include "common/common.h" // kAbbrevSize
+
13 #include "common/logging.h"
+
14 #include "TimeOffset.h"
+
15 #include "LocalDate.h"
+
16 #include "OffsetDateTime.h"
+
17 #include "ZoneProcessor.h"
+
18 
+
19 #ifndef ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG
+
20 #define ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG 0
+
21 #endif
+
22 
+
23 class BasicZoneProcessorTest_priorYearOfRule;
+
24 class BasicZoneProcessorTest_compareRulesBeforeYear;
+
25 class BasicZoneProcessorTest_findLatestPriorRule;
+
26 class BasicZoneProcessorTest_findZoneEra;
+
27 class BasicZoneProcessorTest_init_primitives;
+
28 class BasicZoneProcessorTest_initForLocalDate;
+
29 class BasicZoneProcessorTest_setZoneKey;
+
30 class BasicZoneProcessorTest_calcRuleOffsetMinutes;
+
31 
+
32 class Print;
+
33 
+
34 namespace ace_time {
+
35 namespace basic {
+
36 
+
54 template <typename D>
+ +
61  typename D::ZoneEraBroker era;
+
62 
+
72  typename D::ZoneRuleBroker rule;
+
73 
+ +
76 
+
81  int16_t offsetMinutes;
+
82 
+
84  int16_t deltaMinutes;
+
85 
+
87  int16_t year;
+
88 
+
94  uint8_t month;
+
95 
+ +
104 
+
106  void log() const {
+
107  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
+
108  logging::printf("(%d/%d)", year, month);
+
109  if (sizeof(acetime_t) == sizeof(int)) {
+
110  logging::printf("; stEps: %d", startEpochSeconds);
+
111  } else {
+
112  logging::printf("; stEps: %ld", startEpochSeconds);
+
113  }
+
114  logging::printf("; offMin: %d", offsetMinutes);
+
115  logging::printf("; abbrev: %s", abbrev);
+
116  if (! rule.isNull()) {
+
117  logging::printf("; r.fromYear: %d", rule.fromYear());
+
118  logging::printf("; r.toYear: %d", rule.toYear());
+
119  logging::printf("; r.inMonth: %d", rule.inMonth());
+
120  logging::printf("; r.onDayOfMonth: %d", rule.onDayOfMonth());
+
121  }
+
122  logging::printf("\n");
+
123  }
+
124  }
+
125 };
+
126 
+
128 inline int8_t compareYearMonth(int16_t aYear, uint8_t aMonth,
+
129  int16_t bYear, uint8_t bMonth) {
+
130  if (aYear < bYear) return -1;
+
131  if (aYear > bYear) return 1;
+
132  if (aMonth < bMonth) return -1;
+
133  if (aMonth > bMonth) return 1;
+
134  return 0;
+
135 }
+
136 
+
137 } // namespace basic
+
138 
+
190 template <typename D>
+ +
192  public:
+ +
195 
+
196  bool isLink() const override {
+
197  return ! mZoneInfoBroker.targetInfo().isNull();
+
198  }
+
199 
+
200  uint32_t getZoneId() const override {
+
201  return mZoneInfoBroker.zoneId();
+
202  }
+
203 
+ +
233  const LocalDateTime& ldt) const override {
+
234  FindResult result;
+
235  bool success = initForLocalDate(ldt.localDate());
+
236  if (!success) return result;
+
237 
+
238  // 0) Use the UTC epochSeconds to get intial guess of offset.
+
239  acetime_t epochSeconds0 = ldt.toEpochSeconds();
+
240  auto result0 = findByEpochSeconds(epochSeconds0);
+
241  if (result0.type == FindResult::kTypeNotFound) return result;
+
242  auto offset0 = TimeOffset::forSeconds(
+
243  result0.reqStdOffsetSeconds + result0.reqDstOffsetSeconds);
+
244 
+
245  // 1) Use offset0 to get the next epochSeconds and offset.
+
246  auto odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset0);
+
247  acetime_t epochSeconds1 = odt.toEpochSeconds();
+
248  auto result1 = findByEpochSeconds(epochSeconds1);
+
249  if (result1.type == FindResult::kTypeNotFound) return result;
+
250  auto offset1 = TimeOffset::forSeconds(
+
251  result1.reqStdOffsetSeconds + result1.reqDstOffsetSeconds);
+
252 
+
253  // 2) Use offset1 to get the next epochSeconds and offset.
+
254  odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset1);
+
255  acetime_t epochSeconds2 = odt.toEpochSeconds();
+
256  auto result2 = findByEpochSeconds(epochSeconds2);
+
257  if (result2.type == FindResult::kTypeNotFound) return result;
+
258  auto offset2 = TimeOffset::forSeconds(
+
259  result2.reqStdOffsetSeconds + result2.reqDstOffsetSeconds);
+
260 
+
261  // If offset1 and offset2 are equal, then we have an equilibrium
+
262  // and odt(1) must equal odt(2).
+
263  if (offset1 == offset2) {
+
264  // I think this happens for kTypeExact or kTypeOverlap, but the current
+
265  // algorithm cannot distinguish between the two, so let's pretend that
+
266  // it is kTypeExact. Pick either of result1 or result2.
+
267  result = result1;
+
268  } else {
+
269  // If the offsets don't match, then I think we have a kTypeGap.
+
270  // Pick the stdOffset and dstOffset that generate the later epochSeconds
+
271  // (the earlier transition), but convert into the LocalDateTime of the
+
272  // earlier epochSeconds (the later transition).
+
273  if (epochSeconds1 > epochSeconds2) {
+
274  result = result1;
+
275  } else {
+
276  result = result2;
+
277  }
+
278  result.type = FindResult::kTypeGap;
+
279  }
+
280 
+
281  return result;
+
282  }
+
283 
+
284  FindResult findByEpochSeconds(acetime_t epochSeconds) const override {
+
285  FindResult result;
+
286  const Transition* transition = getTransition(epochSeconds);
+
287  if (!transition) return result;
+
288 
+
289  result.dstOffsetSeconds = transition->deltaMinutes * kSecPerMin;
+
290  result.stdOffsetSeconds = transition->offsetMinutes * kSecPerMin;
+
291  result.reqStdOffsetSeconds = result.stdOffsetSeconds;
+
292  result.reqDstOffsetSeconds = result.dstOffsetSeconds;
+
293  result.type = FindResult::kTypeExact;
+
294  result.abbrev = transition->abbrev;
+
295 
+
296  return result;
+
297  }
298 
-
299  result.dstOffsetSeconds = transition->deltaMinutes * kSecPerMin;
-
300  result.stdOffsetSeconds = transition->offsetMinutes * kSecPerMin;
-
301  result.reqStdOffsetSeconds = result.stdOffsetSeconds;
-
302  result.reqDstOffsetSeconds = result.dstOffsetSeconds;
-
303  result.type = FindResult::kTypeExact;
-
304  result.abbrev = transition->abbrev;
-
305 
-
306  return result;
-
307  }
-
308 
-
309  void printNameTo(Print& printer) const override {
-
310  mZoneInfoBroker.printNameTo(printer);
+
299  void printNameTo(Print& printer) const override {
+
300  mZoneInfoBroker.printNameTo(printer);
+
301  }
+
302 
+
303  void printShortNameTo(Print& printer) const override {
+
304  mZoneInfoBroker.printShortNameTo(printer);
+
305  }
+
306 
+
307  void printTargetNameTo(Print& printer) const override {
+
308  if (isLink()) {
+
309  mZoneInfoBroker.targetInfo().printNameTo(printer);
+
310  }
311  }
312 
-
313  void printShortNameTo(Print& printer) const override {
-
314  mZoneInfoBroker.printShortNameTo(printer);
-
315  }
+
313  void setZoneKey(uintptr_t zoneKey) override {
+
314  if (! mZoneInfoStore) return;
+
315  if (mZoneInfoBroker.equals(zoneKey)) return;
316 
-
317  void printTargetNameTo(Print& printer) const override {
-
318  if (isLink()) {
-
319  mZoneInfoBroker.targetInfo().printNameTo(printer);
-
320  }
-
321  }
-
322 
-
323  void setZoneKey(uintptr_t zoneKey) override {
-
324  if (! mZoneInfoStore) return;
-
325  if (mZoneInfoBroker.equals(zoneKey)) return;
-
326 
-
327  mZoneInfoBroker = mZoneInfoStore->createZoneInfoBroker(zoneKey);
- -
329  mNumTransitions = 0;
-
330  }
-
331 
-
332  bool equalsZoneKey(uintptr_t zoneKey) const override {
-
333  return mZoneInfoBroker.equals(zoneKey);
-
334  }
-
335 
-
337  void log() const {
-
338  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
-
339  logging::printf("BasicZoneProcessor:\n");
-
340  logging::printf(" mEpochYear: %d\n", mEpochYear);
-
341  logging::printf(" mYear: %d\n", mYear);
-
342  logging::printf(" mNumTransitions: %d\n", mNumTransitions);
-
343  for (int i = 0; i < mNumTransitions; i++) {
-
344  logging::printf(" mT[%d]=", i);
-
345  mTransitions[i].log();
-
346  }
-
347  }
+
317  mZoneInfoBroker = mZoneInfoStore->createZoneInfoBroker(zoneKey);
+ +
319  mNumTransitions = 0;
+
320  }
+
321 
+
322  bool equalsZoneKey(uintptr_t zoneKey) const override {
+
323  return mZoneInfoBroker.equals(zoneKey);
+
324  }
+
325 
+
327  void log() const {
+
328  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
+
329  logging::printf("BasicZoneProcessor:\n");
+
330  logging::printf(" mEpochYear: %d\n", mEpochYear);
+
331  logging::printf(" mYear: %d\n", mYear);
+
332  logging::printf(" mNumTransitions: %d\n", mNumTransitions);
+
333  for (int i = 0; i < mNumTransitions; i++) {
+
334  logging::printf(" mT[%d]=", i);
+
335  mTransitions[i].log();
+
336  }
+
337  }
+
338  }
+
339 
+
346  void setZoneInfoStore(const typename D::ZoneInfoStore* zoneInfoStore) {
+
347  mZoneInfoStore = zoneInfoStore;
348  }
349 
-
356  void setZoneInfoStore(const ZIS* zoneInfoStore) {
-
357  mZoneInfoStore = zoneInfoStore;
-
358  }
-
359 
-
360  protected:
-
361 
- -
373  uint8_t type,
-
374  const ZIS* zoneInfoStore /*nullable*/,
-
375  uintptr_t zoneKey
-
376  ) :
-
377  ZoneProcessor(type),
-
378  mZoneInfoStore(zoneInfoStore)
-
379  {
-
380  setZoneKey(zoneKey);
-
381  }
-
382 
-
383  private:
-
384  friend class ::BasicZoneProcessorTest_priorYearOfRule;
-
385  friend class ::BasicZoneProcessorTest_compareRulesBeforeYear;
-
386  friend class ::BasicZoneProcessorTest_findLatestPriorRule;
-
387  friend class ::BasicZoneProcessorTest_findZoneEra;
-
388  friend class ::BasicZoneProcessorTest_init_primitives;
-
389  friend class ::BasicZoneProcessorTest_initForLocalDate;
-
390  friend class ::BasicZoneProcessorTest_setZoneKey;
-
391  friend class ::BasicZoneProcessorTest_calcRuleOffsetMinutes;
-
392 
-
403  static const uint8_t kMaxCacheEntries = 5;
-
404 
-
410  static const acetime_t kMinEpochSeconds = INT32_MIN + 1;
-
411 
-
412  // Disable copy constructor and assignment operator.
- - -
415  delete;
-
416 
-
417  bool equals(const ZoneProcessor& other) const override {
-
418  return mZoneInfoBroker.equals(
-
419  ((const BasicZoneProcessorTemplate&) other).mZoneInfoBroker);
-
420  }
-
421 
-
423  const Transition* getTransition(acetime_t epochSeconds) const {
-
424  bool success = initForEpochSeconds(epochSeconds);
-
425  return (success) ? findMatch(epochSeconds) : nullptr;
-
426  }
-
427 
-
456  bool initForLocalDate(const LocalDate& ld) const {
-
457  int16_t year = ld.year();
-
458  if (ld.month() == 1 && ld.day() == 1) {
-
459  year--;
-
460  }
-
461  // Restrict to [1,9999], even though LocalDate should be able to handle
-
462  // [0,10000].
-
463  if (year <= LocalDate::kMinYear || LocalDate::kMaxYear <= year) {
-
464  return false;
-
465  }
-
466 
-
467  if (isFilled(year)) return true;
-
468  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
-
469  logging::printf("initForLocalDate(): %d (new year %d)\n",
-
470  ld.year(), year);
-
471  }
-
472 
-
473  mYear = year;
- -
475  mNumTransitions = 0; // clear cache
-
476 
-
477  ZEB priorEra = addTransitionPriorToYear(year);
-
478  ZEB currentEra = addTransitionsForYear(year, priorEra);
-
479  addTransitionAfterYear(year, currentEra);
-
480  calcTransitions();
-
481  calcAbbreviations();
-
482 
-
483  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
-
484  log();
-
485  }
-
486 
-
487  return true;
-
488  }
-
489 
-
495  bool initForEpochSeconds(acetime_t epochSeconds) const {
-
496  LocalDate ld = LocalDate::forEpochSeconds(epochSeconds);
-
497  return initForLocalDate(ld);
-
498  }
-
499 
-
506  ZEB addTransitionPriorToYear(int16_t year) const {
-
507  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
-
508  logging::printf("addTransitionPriorToYear(): %d\n", year);
-
509  }
-
510 
-
511  const ZEB era = findZoneEra(mZoneInfoBroker, year - 1);
-
512 
-
513  // If the prior ZoneEra has a ZonePolicy), then find the latest rule
-
514  // within the ZoneEra. Otherwise, add a Transition using a rule==nullptr.
-
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");
-
520  } else {
-
521  logging::printf("ZR[%d,%d]\n", latest.fromYear(), latest.toYear());
-
522  }
-
523  }
-
524  addTransition(year - 1, 0 /*month*/, era, latest);
-
525 
-
526  return era;
-
527  }
-
528 
-
534  static ZRB findLatestPriorRule(const ZPB& zonePolicy, int16_t year) {
-
535  ZRB latest;
-
536  if (zonePolicy.isNull()) return latest;
-
537 
-
538  uint8_t numRules = zonePolicy.numRules();
-
539  for (uint8_t i = 0; i < numRules; i++) {
-
540  const ZRB rule = zonePolicy.rule(i);
-
541  // Check if rule is effective prior to the given year
-
542  if (rule.fromYear() < year) {
-
543  if ((latest.isNull()) ||
-
544  compareRulesBeforeYear(year, rule, latest) > 0) {
-
545  latest = rule;
-
546  }
-
547  }
-
548  }
-
549 
-
550  return latest;
-
551  }
-
552 
-
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());
-
559  }
-
560 
-
569  static int16_t priorYearOfRule(int16_t year, const ZRB& rule) {
-
570  if (rule.toYear() < year) {
-
571  return rule.toYear();
-
572  }
-
573  return year - 1;
-
574  }
-
575 
-
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);
-
583  }
-
584 
-
585  const ZEB era = findZoneEra(mZoneInfoBroker, year);
-
586 
-
587  // If the ZonePolicy has no rules, then add a Transition which takes
-
588  // effect at the start time of the current 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",
-
593  era.untilYear());
-
594  }
-
595  addTransition(year, 0 /*month*/, era, ZRB());
-
596  return era;
-
597  }
-
598 
-
599  if (! era.equals(priorEra)) {
-
600  // The ZoneEra has changed, so we need to find the Rule in effect at
-
601  // the start of the current year of the current ZoneEra. This may be a
-
602  // rule far in the past, but shift the rule forward to {year, 1, 1}.
-
603  ZRB latestPrior = findLatestPriorRule(era.zonePolicy(), year);
+
350  protected:
+
351 
+ +
364  uint8_t type,
+
365  const typename D::ZoneInfoStore* zoneInfoStore /*nullable*/,
+
366  uintptr_t zoneKey
+
367  ) :
+
368  ZoneProcessor(type),
+
369  mZoneInfoStore(zoneInfoStore)
+
370  {
+
371  setZoneKey(zoneKey);
+
372  }
+
373 
+
374  private:
+
375  friend class ::BasicZoneProcessorTest_priorYearOfRule;
+
376  friend class ::BasicZoneProcessorTest_compareRulesBeforeYear;
+
377  friend class ::BasicZoneProcessorTest_findLatestPriorRule;
+
378  friend class ::BasicZoneProcessorTest_findZoneEra;
+
379  friend class ::BasicZoneProcessorTest_init_primitives;
+
380  friend class ::BasicZoneProcessorTest_initForLocalDate;
+
381  friend class ::BasicZoneProcessorTest_setZoneKey;
+
382  friend class ::BasicZoneProcessorTest_calcRuleOffsetMinutes;
+
383 
+
394  static const uint8_t kMaxCacheEntries = 5;
+
395 
+
401  static const acetime_t kMinEpochSeconds = INT32_MIN + 1;
+
402 
+
403  // Disable copy constructor and assignment operator.
+ + +
406  delete;
+
407 
+
408  bool equals(const ZoneProcessor& other) const override {
+
409  return mZoneInfoBroker.equals(
+
410  ((const BasicZoneProcessorTemplate&) other).mZoneInfoBroker);
+
411  }
+
412 
+
414  const Transition* getTransition(acetime_t epochSeconds) const {
+
415  bool success = initForEpochSeconds(epochSeconds);
+
416  return (success) ? findMatch(epochSeconds) : nullptr;
+
417  }
+
418 
+
447  bool initForLocalDate(const LocalDate& ld) const {
+
448  int16_t year = ld.year();
+
449  if (ld.month() == 1 && ld.day() == 1) {
+
450  year--;
+
451  }
+
452  // Restrict to [1,9999], even though LocalDate should be able to handle
+
453  // [0,10000].
+
454  if (year <= LocalDate::kMinYear || LocalDate::kMaxYear <= year) {
+
455  return false;
+
456  }
+
457 
+
458  if (isFilled(year)) return true;
+
459  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
+
460  logging::printf("initForLocalDate(): %d (new year %d)\n",
+
461  ld.year(), year);
+
462  }
+
463 
+
464  mYear = year;
+ +
466  mNumTransitions = 0; // clear cache
+
467 
+
468  typename D::ZoneEraBroker priorEra = addTransitionPriorToYear(year);
+
469  typename D::ZoneEraBroker currentEra =
+
470  addTransitionsForYear(year, priorEra);
+
471  addTransitionAfterYear(year, currentEra);
+
472  calcTransitions();
+
473  calcAbbreviations();
+
474 
+
475  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
+
476  log();
+
477  }
+
478 
+
479  return true;
+
480  }
+
481 
+
487  bool initForEpochSeconds(acetime_t epochSeconds) const {
+
488  LocalDate ld = LocalDate::forEpochSeconds(epochSeconds);
+
489  return initForLocalDate(ld);
+
490  }
+
491 
+
498  typename D::ZoneEraBroker addTransitionPriorToYear(int16_t year) const {
+
499  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
+
500  logging::printf("addTransitionPriorToYear(): %d\n", year);
+
501  }
+
502 
+
503  const typename D::ZoneEraBroker era =
+
504  findZoneEra(mZoneInfoBroker, year - 1);
+
505 
+
506  // If the prior ZoneEra has a ZonePolicy), then find the latest rule
+
507  // within the ZoneEra. Otherwise, add a Transition using a rule==nullptr.
+
508  typename D::ZoneRuleBroker latest =
+
509  findLatestPriorRule(era.zonePolicy(), year);
+
510  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
+
511  logging::printf("addTransitionsPriorToYear(): adding latest prior ");
+
512  if (latest.isNull()) {
+
513  logging::printf("ZR(null)\n");
+
514  } else {
+
515  logging::printf("ZR[%d,%d]\n", latest.fromYear(), latest.toYear());
+
516  }
+
517  }
+
518  addTransition(year - 1, 0 /*month*/, era, latest);
+
519 
+
520  return era;
+
521  }
+
522 
+
528  static typename D::ZoneRuleBroker findLatestPriorRule(
+
529  const typename D::ZonePolicyBroker& zonePolicy, int16_t year) {
+
530  typename D::ZoneRuleBroker latest;
+
531  if (zonePolicy.isNull()) return latest;
+
532 
+
533  uint8_t numRules = zonePolicy.numRules();
+
534  for (uint8_t i = 0; i < numRules; i++) {
+
535  const typename D::ZoneRuleBroker rule = zonePolicy.rule(i);
+
536  // Check if rule is effective prior to the given year
+
537  if (rule.fromYear() < year) {
+
538  if ((latest.isNull()) ||
+
539  compareRulesBeforeYear(year, rule, latest) > 0) {
+
540  latest = rule;
+
541  }
+
542  }
+
543  }
+
544 
+
545  return latest;
+
546  }
+
547 
+
549  static int8_t compareRulesBeforeYear(
+
550  int16_t year,
+
551  const typename D::ZoneRuleBroker& a,
+
552  const typename D::ZoneRuleBroker& b) {
+
553  return basic::compareYearMonth(
+
554  priorYearOfRule(year, a), a.inMonth(),
+
555  priorYearOfRule(year, b), b.inMonth());
+
556  }
+
557 
+
566  static int16_t priorYearOfRule(int16_t year,
+
567  const typename D::ZoneRuleBroker& rule) {
+
568  if (rule.toYear() < year) {
+
569  return rule.toYear();
+
570  }
+
571  return year - 1;
+
572  }
+
573 
+
578  typename D::ZoneEraBroker addTransitionsForYear(
+
579  int16_t year, const typename D::ZoneEraBroker& priorEra) const {
+
580  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
+
581  logging::printf("addTransitionsForYear(): %d\n", year);
+
582  }
+
583 
+
584  const typename D::ZoneEraBroker era = findZoneEra(mZoneInfoBroker, year);
+
585 
+
586  // If the ZonePolicy has no rules, then add a Transition which takes
+
587  // effect at the start time of the current year.
+
588  const typename D::ZonePolicyBroker zonePolicy = era.zonePolicy();
+
589  if (zonePolicy.isNull()) {
+
590  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
+
591  logging::printf("addTransitionsForYear(): adding ZE.untilY=%d\n",
+
592  era.untilYear());
+
593  }
+
594  addTransition(year, 0 /*month*/, era, typename D::ZoneRuleBroker());
+
595  return era;
+
596  }
+
597 
+
598  if (! era.equals(priorEra)) {
+
599  // The ZoneEra has changed, so we need to find the Rule in effect at
+
600  // the start of the current year of the current ZoneEra. This may be a
+
601  // rule far in the past, but shift the rule forward to {year, 1, 1}.
+
602  typename D::ZoneRuleBroker latestPrior =
+
603  findLatestPriorRule(era.zonePolicy(), year);
604  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
605  logging::printf(
606  "addTransitionsForYear(): adding latest prior ");
@@ -461,7 +469,7 @@
619  // according to the ZoneRule::inMonth field.
620  uint8_t numRules = zonePolicy.numRules();
621  for (uint8_t i = 0; i < numRules; i++) {
-
622  const ZRB rule = zonePolicy.rule(i);
+
622  const typename D::ZoneRuleBroker rule = zonePolicy.rule(i);
623  if ((rule.fromYear() <= year) && (year <= rule.toYear())) {
624  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
625  logging::printf(
@@ -479,280 +487,279 @@
637  return era;
638  }
639 
-
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);
-
644  }
-
645 
-
646  const ZEB eraAfter = findZoneEra(mZoneInfoBroker, year + 1);
-
647 
-
648  // If the current era is the same as the following year, then we'll just
-
649  // assume that the latest ZoneRule carries over to Jan 1st of the next
-
650  // year. tzcompiler.py guarantees no ZoneRule occurs on Jan 1st.
-
651  if (currentEra.equals(eraAfter)) {
-
652  return;
-
653  }
-
654 
-
655  // If the ZoneEra did change, find the latest transition prior to
-
656  // {year + 1, 1, 1}, then shift that Transition to Jan 1st of the
-
657  // following year.
-
658  ZRB latest = findLatestPriorRule(eraAfter.zonePolicy(), year + 1);
-
659  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
-
660  logging::printf(
-
661  "addTransitionsAfterYear(): adding latest prior ");
-
662  if (latest.isNull()) {
-
663  logging::printf("ZR(null)\n");
-
664  } else {
-
665  logging::printf("ZR[%d,%d]\n", latest.fromYear(), latest.toYear());
-
666  }
-
667  }
-
668  addTransition(year + 1, 1 /*month*/, eraAfter, latest);
-
669  }
-
670 
-
694  void addTransition(int16_t year, uint8_t month, const ZEB& era,
-
695  const ZRB& rule) const {
-
696 
-
697  // If a zone needs more transitions than kMaxCacheEntries, the check below
-
698  // will cause the DST transition information to be inaccurate, and it is
-
699  // highly likely that this situation would be caught in the
-
700  // AceTimeValidation tests. Since these integration tests pass, I feel
-
701  // confident that those zones which need more than kMaxCacheEntries are
-
702  // already filtered out by tzcompiler.py.
-
703  //
-
704  // Ideally, the tzcompiler.py script would explicitly remove those zones
-
705  // which need more than kMaxCacheEntries Transitions. But this would
-
706  // require a Python version of the BasicZoneProcessor, and unfortunately,
-
707  // zone_processor.py implements only the ExtendedZoneProcessor algorithm
-
708  // An early version of zone_processor.py may have implemented something
-
709  // close to BasicZoneProcessor, and it may be available in the git
-
710  // history. But it seems like too much work right now to try to dig that
-
711  // out, just to implement the explicit check for kMaxCacheEntries. It
-
712  // would mean maintaining another version of zone_processor.py.
-
713  if (mNumTransitions >= kMaxCacheEntries) return;
-
714 
-
715  // Insert new element at the end of the list.
-
716  // NOTE: It is probably tempting to pass a pointer (or reference) to
-
717  // mTransitions[mNumTransitions] into createTransition(), instead of
-
718  // returning it by value. However, MemoryBenchmark shows that directly
-
719  // updating the Transition through the pointer increases flash memory
-
720  // consumption by ~110 bytes on AVR processors. It seems that creating a
-
721  // local copy of Transition on the stack, filling it, and then copying it
-
722  // by value takes fewer instructions.
-
723  mTransitions[mNumTransitions] = createTransition(year, month, era, rule);
-
724  mNumTransitions++;
-
725 
-
726  // perform an insertion sort based on ZoneRule.inMonth()
-
727  for (uint8_t i = mNumTransitions - 1; i > 0; i--) {
-
728  Transition& left = mTransitions[i - 1];
-
729  Transition& right = mTransitions[i];
-
730  // assume only 1 rule per month
-
731  if (basic::compareYearMonth(left.year, left.month,
-
732  right.year, right.month) > 0) {
-
733  Transition tmp = left;
-
734  left = right;
-
735  right = tmp;
-
736  }
-
737  }
-
738  }
-
739 
-
745  static Transition createTransition(int16_t year, uint8_t month,
-
746  const ZEB& era, const ZRB& rule) {
-
747 
-
748  Transition transition;
-
749  int16_t deltaMinutes;
-
750  uint8_t mon;
-
751  if (rule.isNull()) {
-
752  mon = 1; // RULES is either '-' or 'hh:mm' so takes effect in Jan
-
753  deltaMinutes = era.deltaSeconds() / kSecPerMin;
-
754  transition.abbrev[0] = '\0';
-
755  } else {
-
756  mon = rule.inMonth();
-
757  deltaMinutes = rule.deltaSeconds() / kSecPerMin;
-
758  ace_common::strncpy_T(
-
759  transition.abbrev, rule.letter(), internal::kAbbrevSize - 1);
-
760  transition.abbrev[internal::kAbbrevSize - 1] = '\0';
-
761  }
-
762  // Clobber the month if specified.
-
763  if (month != 0) {
-
764  mon = month;
-
765  }
-
766  int16_t offsetMinutes = era.offsetSeconds() / kSecPerMin;
-
767 
-
768  transition.era = era;
-
769  transition.rule = rule;
-
770  transition.startEpochSeconds = 0;
-
771  transition.offsetMinutes = offsetMinutes;
-
772  transition.deltaMinutes = deltaMinutes;
-
773  transition.year = year;
-
774  transition.month = mon;
-
775  return transition;
-
776  }
-
777 
-
782  static ZEB findZoneEra(const ZIB& info, int16_t year) {
-
783  for (uint8_t i = 0; i < info.numEras(); i++) {
-
784  const ZEB era = info.era(i);
-
785  if (year < era.untilYear()) return era;
-
786  }
-
787  // Return the last ZoneEra if we run off the end.
-
788  return info.era(info.numEras() - 1);
-
789  }
-
790 
-
798  void calcTransitions() const {
-
799  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
-
800  logging::printf("calcTransitions():\n");
-
801  }
-
802 
-
803  // Set the initial startEpochSeconds to be -Infinity
-
804  Transition* prevTransition = &mTransitions[0];
-
805  prevTransition->startEpochSeconds = kMinEpochSeconds;
-
806 
-
807  for (uint8_t i = 1; i < mNumTransitions; i++) {
-
808  Transition& transition = mTransitions[i];
-
809  const int16_t year = transition.year;
-
810 
-
811  if (transition.rule.isNull()) {
-
812  // If the transition is simple (has no named rule), then the
-
813  // ZoneEra applies for the entire year (since BasicZoneProcessor
-
814  // supports only whole year in the UNTIL field). The whole year UNTIL
-
815  // field has an implied 'w' suffix on 00:00, we don't need to call
-
816  // calcRuleOffsetMinutes() with a 'w', we can just use the previous
-
817  // transition's offset to calculate the startDateTime of this
-
818  // transition.
-
819  //
-
820  // Also, when transition.rule == nullptr, the mNumTransitions should
-
821  // be 1, since only a single transition is added by
-
822  // addTransitionsForYear().
-
823  const int16_t prevTotalOffsetMinutes = prevTransition->offsetMinutes
-
824  + prevTransition->deltaMinutes;
-
825  OffsetDateTime startDateTime = OffsetDateTime::forComponents(
-
826  year, 1, 1, 0, 0, 0,
-
827  TimeOffset::forMinutes(prevTotalOffsetMinutes));
-
828  transition.startEpochSeconds = startDateTime.toEpochSeconds();
-
829  } else {
-
830  // In this case, the transition points to a named ZonePolicy, which
-
831  // means that there could be multiple ZoneRules associated with the
-
832  // given year. For each transition, determine the startEpochSeconds,
-
833  // and the effective offset time.
-
834 
-
835  // Determine the start date of the rule.
-
836  const internal::MonthDay monthDay = internal::calcStartDayOfMonth(
-
837  year, transition.month, transition.rule.onDayOfWeek(),
-
838  transition.rule.onDayOfMonth());
-
839 
-
840  // Determine the offset of the 'atTimeSuffix'. The 'w' suffix
-
841  // requires the offset of the previous transition.
-
842  const int16_t prevTotalOffsetMinutes = calcRuleOffsetMinutes(
-
843  prevTransition->offsetMinutes + prevTransition->deltaMinutes,
-
844  transition.era.offsetSeconds() / kSecPerMin,
-
845  transition.rule.atTimeSuffix());
-
846 
-
847  // startDateTime
-
848  const uint16_t minutes = transition.rule.atTimeSeconds() / 60;
-
849  const uint8_t atHour = minutes / 60;
-
850  const uint8_t atMinute = minutes % 60;
-
851  OffsetDateTime startDateTime = OffsetDateTime::forComponents(
-
852  year, monthDay.month, monthDay.day,
-
853  atHour, atMinute, 0 /*second*/,
-
854  TimeOffset::forMinutes(prevTotalOffsetMinutes));
-
855  transition.startEpochSeconds = startDateTime.toEpochSeconds();
-
856  }
-
857 
-
858  prevTransition = &transition;
-
859  }
-
860  }
-
861 
-
868  static int16_t calcRuleOffsetMinutes(int16_t prevTotalOffsetMinutes,
-
869  int16_t currentBaseOffsetMinutes, uint8_t atSuffix) {
-
870  if (atSuffix == basic::ZoneContext::kSuffixW) {
-
871  return prevTotalOffsetMinutes;
-
872  } else if (atSuffix == basic::ZoneContext::kSuffixS) {
-
873  return currentBaseOffsetMinutes;
-
874  } else { // 'u', 'g' or 'z'
-
875  return 0;
-
876  }
-
877  }
-
878 
-
880  void calcAbbreviations() const {
-
881  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
-
882  logging::printf("calcAbbreviations():\n");
-
883  }
-
884 
-
885  for (uint8_t i = 0; i < mNumTransitions; i++) {
-
886  Transition* transition = &mTransitions[i];
-
887  internal::createAbbreviation(
-
888  transition->abbrev,
-
889  internal::kAbbrevSize,
-
890  transition->era.format(),
-
891  transition->offsetMinutes * kSecPerMin,
-
892  transition->deltaMinutes * kSecPerMin,
-
893  transition->abbrev);
-
894  }
-
895  }
-
896 
-
898  const Transition* findMatch(acetime_t epochSeconds) const {
-
899  const Transition* closestMatch = nullptr;
-
900  for (uint8_t i = 0; i < mNumTransitions; i++) {
-
901  const Transition* m = &mTransitions[i];
-
902  if (closestMatch == nullptr || m->startEpochSeconds <= epochSeconds) {
-
903  closestMatch = m;
-
904  }
-
905  }
-
906  return closestMatch;
-
907  }
-
908 
-
909  private:
-
910  static const int32_t kSecPerMin = 60;
-
911 
-
912  const ZIS* mZoneInfoStore; // nullable
-
913  ZIB mZoneInfoBroker;
-
914 
-
915  mutable uint8_t mNumTransitions = 0;
-
916  mutable Transition mTransitions[kMaxCacheEntries];
-
917 };
-
918 
- -
924  basic::ZoneInfoStore,
-
925  basic::ZoneInfoBroker,
-
926  basic::ZoneEraBroker,
-
927  basic::ZonePolicyBroker,
-
928  basic::ZoneRuleBroker> {
-
929 
-
930  public:
-
932  static const uint8_t kTypeBasic = 3;
+
641  void addTransitionAfterYear(
+
642  int16_t year, const typename D::ZoneEraBroker& currentEra) const {
+
643  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
+
644  logging::printf("addTransitionAfterYear(): %d\n", year);
+
645  }
+
646 
+
647  const typename D::ZoneEraBroker eraAfter =
+
648  findZoneEra(mZoneInfoBroker, year + 1);
+
649 
+
650  // If the current era is the same as the following year, then we'll just
+
651  // assume that the latest ZoneRule carries over to Jan 1st of the next
+
652  // year. tzcompiler.py guarantees no ZoneRule occurs on Jan 1st.
+
653  if (currentEra.equals(eraAfter)) {
+
654  return;
+
655  }
+
656 
+
657  // If the ZoneEra did change, find the latest transition prior to
+
658  // {year + 1, 1, 1}, then shift that Transition to Jan 1st of the
+
659  // following year.
+
660  typename D::ZoneRuleBroker latest =
+
661  findLatestPriorRule(eraAfter.zonePolicy(), year + 1);
+
662  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
+
663  logging::printf(
+
664  "addTransitionsAfterYear(): adding latest prior ");
+
665  if (latest.isNull()) {
+
666  logging::printf("ZR(null)\n");
+
667  } else {
+
668  logging::printf("ZR[%d,%d]\n", latest.fromYear(), latest.toYear());
+
669  }
+
670  }
+
671  addTransition(year + 1, 1 /*month*/, eraAfter, latest);
+
672  }
+
673 
+
697  void addTransition(int16_t year, uint8_t month,
+
698  const typename D::ZoneEraBroker& era,
+
699  const typename D::ZoneRuleBroker& rule) const {
+
700 
+
701  // If a zone needs more transitions than kMaxCacheEntries, the check below
+
702  // will cause the DST transition information to be inaccurate, and it is
+
703  // highly likely that this situation would be caught in the
+
704  // AceTimeValidation tests. Since these integration tests pass, I feel
+
705  // confident that those zones which need more than kMaxCacheEntries are
+
706  // already filtered out by tzcompiler.py.
+
707  //
+
708  // Ideally, the tzcompiler.py script would explicitly remove those zones
+
709  // which need more than kMaxCacheEntries Transitions. But this would
+
710  // require a Python version of the BasicZoneProcessor, and unfortunately,
+
711  // zone_processor.py implements only the ExtendedZoneProcessor algorithm
+
712  // An early version of zone_processor.py may have implemented something
+
713  // close to BasicZoneProcessor, and it may be available in the git
+
714  // history. But it seems like too much work right now to try to dig that
+
715  // out, just to implement the explicit check for kMaxCacheEntries. It
+
716  // would mean maintaining another version of zone_processor.py.
+
717  if (mNumTransitions >= kMaxCacheEntries) return;
+
718 
+
719  // Insert new element at the end of the list.
+
720  // NOTE: It is probably tempting to pass a pointer (or reference) to
+
721  // mTransitions[mNumTransitions] into createTransition(), instead of
+
722  // returning it by value. However, MemoryBenchmark shows that directly
+
723  // updating the Transition through the pointer increases flash memory
+
724  // consumption by ~110 bytes on AVR processors. It seems that creating a
+
725  // local copy of Transition on the stack, filling it, and then copying it
+
726  // by value takes fewer instructions.
+
727  mTransitions[mNumTransitions] = createTransition(year, month, era, rule);
+
728  mNumTransitions++;
+
729 
+
730  // perform an insertion sort based on ZoneRule.inMonth()
+
731  for (uint8_t i = mNumTransitions - 1; i > 0; i--) {
+
732  Transition& left = mTransitions[i - 1];
+
733  Transition& right = mTransitions[i];
+
734  // assume only 1 rule per month
+
735  if (basic::compareYearMonth(left.year, left.month,
+
736  right.year, right.month) > 0) {
+
737  Transition tmp = left;
+
738  left = right;
+
739  right = tmp;
+
740  }
+
741  }
+
742  }
+
743 
+
749  static Transition createTransition(
+
750  int16_t year,
+
751  uint8_t month,
+
752  const typename D::ZoneEraBroker& era,
+
753  const typename D::ZoneRuleBroker& rule) {
+
754 
+
755  Transition transition;
+
756  int16_t deltaMinutes;
+
757  uint8_t mon;
+
758  if (rule.isNull()) {
+
759  mon = 1; // RULES is either '-' or 'hh:mm' so takes effect in Jan
+
760  deltaMinutes = era.deltaSeconds() / kSecPerMin;
+
761  transition.abbrev[0] = '\0';
+
762  } else {
+
763  mon = rule.inMonth();
+
764  deltaMinutes = rule.deltaSeconds() / kSecPerMin;
+
765  ace_common::strncpy_T(
+
766  transition.abbrev, rule.letter(), kAbbrevSize - 1);
+
767  transition.abbrev[kAbbrevSize - 1] = '\0';
+
768  }
+
769  // Clobber the month if specified.
+
770  if (month != 0) {
+
771  mon = month;
+
772  }
+
773  int16_t offsetMinutes = era.offsetSeconds() / kSecPerMin;
+
774 
+
775  transition.era = era;
+
776  transition.rule = rule;
+
777  transition.startEpochSeconds = 0;
+
778  transition.offsetMinutes = offsetMinutes;
+
779  transition.deltaMinutes = deltaMinutes;
+
780  transition.year = year;
+
781  transition.month = mon;
+
782  return transition;
+
783  }
+
784 
+
789  static typename D::ZoneEraBroker findZoneEra(
+
790  const typename D::ZoneInfoBroker& info,
+
791  int16_t year) {
+
792  for (uint8_t i = 0; i < info.numEras(); i++) {
+
793  const typename D::ZoneEraBroker era = info.era(i);
+
794  if (year < era.untilYear()) return era;
+
795  }
+
796  // Return the last ZoneEra if we run off the end.
+
797  return info.era(info.numEras() - 1);
+
798  }
+
799 
+
807  void calcTransitions() const {
+
808  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
+
809  logging::printf("calcTransitions():\n");
+
810  }
+
811 
+
812  // Set the initial startEpochSeconds to be -Infinity
+
813  Transition* prevTransition = &mTransitions[0];
+
814  prevTransition->startEpochSeconds = kMinEpochSeconds;
+
815 
+
816  for (uint8_t i = 1; i < mNumTransitions; i++) {
+
817  Transition& transition = mTransitions[i];
+
818  const int16_t year = transition.year;
+
819 
+
820  if (transition.rule.isNull()) {
+
821  // If the transition is simple (has no named rule), then the
+
822  // ZoneEra applies for the entire year (since BasicZoneProcessor
+
823  // supports only whole year in the UNTIL field). The whole year UNTIL
+
824  // field has an implied 'w' suffix on 00:00, we don't need to call
+
825  // calcRuleOffsetMinutes() with a 'w', we can just use the previous
+
826  // transition's offset to calculate the startDateTime of this
+
827  // transition.
+
828  //
+
829  // Also, when transition.rule == nullptr, the mNumTransitions should
+
830  // be 1, since only a single transition is added by
+
831  // addTransitionsForYear().
+
832  const int16_t prevTotalOffsetMinutes = prevTransition->offsetMinutes
+
833  + prevTransition->deltaMinutes;
+
834  OffsetDateTime startDateTime = OffsetDateTime::forComponents(
+
835  year, 1, 1, 0, 0, 0,
+
836  TimeOffset::forMinutes(prevTotalOffsetMinutes));
+
837  transition.startEpochSeconds = startDateTime.toEpochSeconds();
+
838  } else {
+
839  // In this case, the transition points to a named ZonePolicy, which
+
840  // means that there could be multiple ZoneRules associated with the
+
841  // given year. For each transition, determine the startEpochSeconds,
+
842  // and the effective offset time.
+
843 
+
844  // Determine the start date of the rule.
+
845  const MonthDay monthDay = calcStartDayOfMonth(
+
846  year, transition.month, transition.rule.onDayOfWeek(),
+
847  transition.rule.onDayOfMonth());
+
848 
+
849  // Determine the offset of the 'atTimeSuffix'. The 'w' suffix
+
850  // requires the offset of the previous transition.
+
851  const int16_t prevTotalOffsetMinutes = calcRuleOffsetMinutes(
+
852  prevTransition->offsetMinutes + prevTransition->deltaMinutes,
+
853  transition.era.offsetSeconds() / kSecPerMin,
+
854  transition.rule.atTimeSuffix());
+
855 
+
856  // startDateTime
+
857  const uint16_t minutes = transition.rule.atTimeSeconds() / 60;
+
858  const uint8_t atHour = minutes / 60;
+
859  const uint8_t atMinute = minutes % 60;
+
860  OffsetDateTime startDateTime = OffsetDateTime::forComponents(
+
861  year, monthDay.month, monthDay.day,
+
862  atHour, atMinute, 0 /*second*/,
+
863  TimeOffset::forMinutes(prevTotalOffsetMinutes));
+
864  transition.startEpochSeconds = startDateTime.toEpochSeconds();
+
865  }
+
866 
+
867  prevTransition = &transition;
+
868  }
+
869  }
+
870 
+
877  static int16_t calcRuleOffsetMinutes(int16_t prevTotalOffsetMinutes,
+
878  int16_t currentBaseOffsetMinutes, uint8_t atSuffix) {
+
879  if (atSuffix == basic::Info::ZoneContext::kSuffixW) {
+
880  return prevTotalOffsetMinutes;
+
881  } else if (atSuffix == basic::Info::ZoneContext::kSuffixS) {
+
882  return currentBaseOffsetMinutes;
+
883  } else { // 'u', 'g' or 'z'
+
884  return 0;
+
885  }
+
886  }
+
887 
+
889  void calcAbbreviations() const {
+
890  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
+
891  logging::printf("calcAbbreviations():\n");
+
892  }
+
893 
+
894  for (uint8_t i = 0; i < mNumTransitions; i++) {
+
895  Transition* transition = &mTransitions[i];
+
896  createAbbreviation(
+
897  transition->abbrev,
+
898  kAbbrevSize,
+
899  transition->era.format(),
+
900  transition->offsetMinutes * kSecPerMin,
+
901  transition->deltaMinutes * kSecPerMin,
+
902  transition->abbrev);
+
903  }
+
904  }
+
905 
+
907  const Transition* findMatch(acetime_t epochSeconds) const {
+
908  const Transition* closestMatch = nullptr;
+
909  for (uint8_t i = 0; i < mNumTransitions; i++) {
+
910  const Transition* m = &mTransitions[i];
+
911  if (closestMatch == nullptr || m->startEpochSeconds <= epochSeconds) {
+
912  closestMatch = m;
+
913  }
+
914  }
+
915  return closestMatch;
+
916  }
+
917 
+
918  private:
+
919  static const int32_t kSecPerMin = 60;
+
920 
+
921  const typename D::ZoneInfoStore* mZoneInfoStore; // nullable
+
922  typename D::ZoneInfoBroker mZoneInfoBroker;
+
923 
+
924  mutable uint8_t mNumTransitions = 0;
+
925  mutable Transition mTransitions[kMaxCacheEntries];
+
926 };
+
927 
+
932 class BasicZoneProcessor: public BasicZoneProcessorTemplate<basic::Info> {
933 
-
934  explicit BasicZoneProcessor(const basic::ZoneInfo* zoneInfo = nullptr)
- -
936  basic::ZoneInfoStore,
-
937  basic::ZoneInfoBroker,
-
938  basic::ZoneEraBroker,
-
939  basic::ZonePolicyBroker,
-
940  basic::ZoneRuleBroker>(
-
941  kTypeBasic, &mZoneInfoStore, (uintptr_t) zoneInfo)
-
942  {}
-
943 
-
944  private:
-
945  basic::ZoneInfoStore mZoneInfoStore;
-
946 };
-
947 
-
948 } // namespace ace_time
-
949 
-
950 #endif
-
An implementation of ZoneProcessor that supports a subset of the zones containing in the TZ Database.
-
bool isLink() const override
Return true if timezone is a Link entry pointing to a Zone entry.
-
basic::TransitionTemplate< ZIB, 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.
-
void printNameTo(Print &printer) const override
Print a human-readable identifier (e.g.
-
FindResult findByEpochSeconds(acetime_t epochSeconds) const override
Return the search results at given epochSeconds.
-
void setZoneInfoStore(const ZIS *zoneInfoStore)
Set the zone info store at runtime.
-
void log() const
Used only for debugging.
-
BasicZoneProcessorTemplate(uint8_t type, const ZIS *zoneInfoStore, uintptr_t zoneKey)
Constructor.
-
void printShortNameTo(Print &printer) 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.
-
FindResult findByLocalDateTime(const LocalDateTime &ldt) const override
Return the search results at given LocalDateTime.
-
uint32_t getZoneId() const override
Return the unique stable zoneId.
-
void printTargetNameTo(Print &printer) const override
Print the full identifier (e.g.
-
A specific implementation of BasicZoneProcessorTemplate that uses ZoneXxxBrokers which read from zone...
-
static const uint8_t kTypeBasic
Unique TimeZone type identifier for BasicZoneProcessor.
+
934  public:
+
936  static const uint8_t kTypeBasic = 3;
+
937 
+
938  explicit BasicZoneProcessor(const basic::Info::ZoneInfo* zoneInfo = nullptr)
+
939  : BasicZoneProcessorTemplate<basic::Info>(
+
940  kTypeBasic, &mZoneInfoStore, (uintptr_t) zoneInfo)
+
941  {}
+
942 
+
943  private:
+
944  basic::Info::ZoneInfoStore mZoneInfoStore;
+
945 };
+
946 
+
947 } // namespace ace_time
+
948 
+
949 #endif
+
An implementation of ZoneProcessor that supports a subset of the zones containing in the TZ Database.
+
FindResult findByEpochSeconds(acetime_t epochSeconds) const override
Return the search results at given epochSeconds.
+
void log() const
Used only for debugging.
+
void setZoneInfoStore(const typename D::ZoneInfoStore *zoneInfoStore)
Set the zone info store at runtime.
+
basic::TransitionTemplate< D > Transition
Exposed only for testing purposes.
+
bool equalsZoneKey(uintptr_t zoneKey) const override
Return true if ZoneProcessor is associated with the given opaque zoneKey.
+
FindResult findByLocalDateTime(const LocalDateTime &ldt) const override
Return the search results at given LocalDateTime.
+
uint32_t getZoneId() const override
Return the unique stable zoneId.
+
void printShortNameTo(Print &printer) const override
Print a short human-readable identifier (e.g.
+
void printTargetNameTo(Print &printer) const override
Print the full identifier (e.g.
+
void printNameTo(Print &printer) const override
Print a human-readable identifier (e.g.
+
BasicZoneProcessorTemplate(uint8_t type, const typename D::ZoneInfoStore *zoneInfoStore, uintptr_t zoneKey)
Constructor.
+
void setZoneKey(uintptr_t zoneKey) override
Set the opaque zoneKey of this object to a new value, reseting any internally cached information.
+
bool isLink() const override
Return true if timezone is a Link entry pointing to a Zone entry.
+
A specific implementation of BasicZoneProcessorTemplate that uses ZoneXxxBrokers which read from zone...
+
static const uint8_t kTypeBasic
Unique TimeZone type identifier for BasicZoneProcessor.
static int16_t currentEpochYear()
Get the current epoch year.
Definition: Epoch.h:27
Result of a search for transition at a specific epochSeconds or a specific LocalDateTime.
Definition: ZoneProcessor.h:23
int32_t stdOffsetSeconds
STD offset of the resulting OffsetDateTime.
Definition: ZoneProcessor.h:79
@@ -772,26 +779,27 @@
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 forSeconds(int32_t seconds)
Create TimeOffset from seconds from 00:00.
Definition: TimeOffset.h:96
static TimeOffset forMinutes(int16_t minutes)
Create TimeOffset from minutes from 00:00.
Definition: TimeOffset.h:91
+
A storage object that creates an ZoneInfoBroker from a key that identifies the ZoneInfo.
Definition: ZoneInfoLow.h:807
Base interface for ZoneProcessor classes.
int16_t mYear
Year that was used to calculate the transitions in the current cache.
bool isFilled(int16_t year) const
Check if the Transition cache is filled for the given year and current epochYear.
int16_t mEpochYear
Epoch year that was used to calculate the transitions in the current cache.
-
Identifiers used by implementation code which need to be publically exported.
+
const uint8_t kAbbrevSize
Size of the c-string buffer needed to hold a time zone abbreviation.
Definition: common.h:44
int32_t acetime_t
Type for the number of seconds from epoch.
Definition: common.h:24
-
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 standard time offset minutes at the start of transition, not 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 kSuffixS
Represents 's' or standard time.
Definition: ZoneInfoLow.h:83
-
static const uint8_t kSuffixW
Represents 'w' or wall time.
Definition: ZoneInfoLow.h:80
-
Representation of a given time zone, implemented as an array of ZoneEra records.
Definition: ZoneInfoLow.h:320
+
static const uint8_t kSuffixW
Represents 'w' or wall time.
Definition: ZoneInfoLow.h:88
+
static const uint8_t kSuffixS
Represents 's' or standard time.
Definition: ZoneInfoLow.h:91
+
Representation of a given time zone, implemented as an array of ZoneEra records.
Definition: ZoneInfoLow.h:324
+
Data structure that defines the start of a specific UTC offset as described by the matching ZoneEra a...
+
acetime_t startEpochSeconds
The calculated transition time of the given rule.
+
uint8_t month
Month of the transition.
+
D::ZoneRuleBroker rule
The Zone transition rule that matched for the the given year.
+
int16_t deltaMinutes
The deltaMinutes from "standard time" at the start of transition.
+
void log() const
Used only for debugging.
+
char abbrev[kAbbrevSize]
The calculated effective time zone abbreviation, e.g.
+
int16_t offsetMinutes
The standard time offset minutes at the start of transition, not including DST offset.
+
int16_t year
Year of the Transition.
+
D::ZoneEraBroker era
The ZoneEra that matched the given year.