diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d50d9a62..9dfad1ebf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +* Unreleased +* 0.5.2 + * Create `HelloZoneManager` and add it to the `README.md`. + * Recommend using "Arduino MKR ZERO" board or "SparkFun SAMD21 Mini + Breakout" board for the "SAMD21 M0 Mini" boards. + * Pack `basic::ZoneInfo`, `extended:ZoneInfo` and related structs tighter on + 32-bit processors, saving 2.5kB on the Basic zoneinfo files and 5.6kB on + Extended zoneinfo files. + * Pack `basic::Transition` and `extended::Transition` tighter on 32-bit + processors, saving 20-32 bytes on `BasicZoneProcessor` and + `ExtendedZoneProcessor`. + * Test and support ATmega2560 AVR processor. + * Replace all uses of `Serial` with `SERIAL_PORT_MONITOR` for compatibility + with boards (e.g. SAMD boards) which use `SerialUSB` as the serial + monitor. * 0.5.1 (2019-07-24, TZ DB version 2019a, beta) * Add documentation about the ZoneManager into `USER_GUIDE.md`. * Move `DateStrings` string pointers into PROGMEM, saving 42 bytes of RAM. diff --git a/README.md b/README.md index 4527b8367..ac303bc53 100644 --- a/README.md +++ b/README.md @@ -160,20 +160,21 @@ time. The zoneinfo files are stored in flash memory (using the `PROGMEM` compiler directive) if the microcontroller allows it (e.g. AVR, ESP8266) so that they do -not consume static RAM: +not consume static RAM. The [MemoryBenchmark](examples/MemoryBenchmark/) +program shows that: -* 270 timezones supported by `BasicZoneProcessor`consume: - * 14 kB of flash on an 8-bit processor (AVR) - * 21 kB of flash on a 32-bit processor (ESP8266) +* 270 timezones supported by `BasicZoneProcessor`consume about: + * 15 kB of flash on an 8-bit processor (AVR) + * 20 kB of flash on a 32-bit processor (ESP8266) * 387 timezones supported by `ExtendedZoneProcessor` consume: - * 23 kB of flash on an 8-bit processor (AVR) - * 37 kB of flash on a 32-bit processor (ESP8266) + * 24 kB of flash on an 8-bit processor (AVR) + * 33 kB of flash on a 32-bit processor (ESP8266) Normally a small application will use only a small number of timezones. The AceTime library with one timezone using the `BasicZoneProcessor` and the `SystemClock` consumes: -* 8.5 kB of flash and 350 bytes of RAM on an 8-bit Arduino Nano (AVR), -* 11 kB of flash and 850 bytes of RAM on an ESP8266 processor (32-bit). +* 9-10 kB of flash and 350 bytes of RAM on an 8-bit AVR processors, +* 6-22 kB of flash and 900-1800 bytes of RAM on an 32-bit processors. An example of more complex application is the [WorldClock](examples/WorldClock) which has 3 OLED displays over SPI, 3 timezones using `BasicZoneProcessor`, a @@ -185,6 +186,7 @@ inside the 30 kB flash size of an Arduino Pro Micro controller. Conversion from date-time components (year, month, day, etc) to epochSeconds (`ZonedDateTime::toEpochSeconds()`) takes about: * 90 microseconds on an 8-bit AVR processor, +* 14 microseconds on an SAMD21, * 7 microseconds on an ESP8266, * 1.4 microseconds on an ESP32, * 0.5 microseconds on a Teensy 3.2. @@ -192,16 +194,19 @@ Conversion from date-time components (year, month, day, etc) to epochSeconds Conversion from an epochSeconds to date-time components including timezone (`ZonedDateTime::forEpochSeconds()`) takes (assuming cache hits): * 600 microseconds on an 8-bit AVR, -* 25 microseconds on an ESP8266, -* 2.5 microseconds on an ESP32, +* 50 microseconds on an SAMD21, +* 27 microseconds on an ESP8266, +* 2.8 microseconds on an ESP32, * 6 microseconds on a Teensy 3.2. -**Version**: 0.5.1 (2019-07-24, TZ DB version 2019a, beta) +**Version**: 0.5.2 (2019-07-29, TZ DB version 2019a, beta) **Status**: Fully functional. Added `ZoneManager` for dynamic binding of zoneName or zoneId to the TimeZone. -## HelloDateTime +## Examples + +### HelloDateTime Here is a simple program (see [examples/HelloDateTime](examples/HelloDateTime)) which demonstrates how to create and manipulate date and times in different time @@ -313,7 +318,61 @@ pacificTime.compareTo(londonTime): 0 pacificTime == londonTime: false ``` -## HelloSystemClock +### HelloZoneManager + +The [HelloZoneManager](examples/HelloZoneManager) example shows how to load the +entire TZ Database into a `BasicZoneManager`, then create 3 time zones using 3 +different ways: `createForZoneInfo()`, `createForZoneName()`, and +`createForZoneId()`. + +```C++ +#include + +using namespace ace_time; + +// Create a BasicZoneManager with the entire TZ Database. +static const int CACHE_SIZE = 3; +static BasicZoneManager manager( + zonedb::kZoneRegistrySize, zonedb::kZoneRegistry); + +void setup() { + Serial.begin(115200); + while (!Serial); // Wait Serial is ready - Leonardo/Micro + + // Create Los Angeles by ZoneInfo + auto pacificTz = manager.createForZoneInfo(&zonedb::kZoneAmerica_Los_Angeles); + auto pacificTime = ZonedDateTime::forComponents( + 2019, 3, 10, 3, 0, 0, pacificTz); + pacificTime.printTo(Serial); + Serial.println(); + + // Create London by ZoneName + auto londonTz = manager.createForZoneName("Europe/London"); + auto londonTime = pacificTime.convertToTimeZone(londonTz); + londonTime.printTo(Serial); + Serial.println(); + + // Create Sydney by ZoneId + uint32_t syndeyId = BasicZone(&zonedb::kZoneAustralia_Sydney).zoneId(); + auto sydneyTz = manager.createForZoneId(syndeyId); + auto sydneyTime = pacificTime.convertToTimeZone(sydneyTz); + sydneyTime.printTo(Serial); + Serial.println(); +} + +void loop() { +} +``` + +This consumes about 25kB of flash, which means that it can run on an +Arduino Nano or Micro . It produces the following output: +``` +2019-03-10T03:00:00-07:00[America/Los_Angeles] +2019-03-10T10:00:00+00:00[Europe/London] +2019-03-10T21:00:00+11:00[Australia/Sydney] +``` + +### HelloSystemClock This is the example code for using the `SystemClock` taken from [examples/HelloSystemClock](examples/HelloSystemClock). @@ -374,9 +433,10 @@ then printing the system time every 2 seconds: 2019-06-17T19:50:00-07:00[America/Los_Angeles] 2019-06-17T19:50:02-07:00[America/Los_Angeles] 2019-06-17T19:50:04-07:00[America/Los_Angeles] +... ``` -## Example: WorldClock +### WorldClock Here is a photo of the [WorldClock](examples/WorldClock) that supports 3 OLED displays with 3 timezones, and automatically adjusts the DST transitions @@ -433,6 +493,7 @@ The library is extensively tested on the following boards: I will occasionally test on the following hardware as a sanity check: * Teensy 3.2 (72 MHz ARM Cortex-M4) +* Mini Mega 2560 (Arduino Mega 2560 compatible, 16 MHz ATmega2560) ## Changelog diff --git a/USER_GUIDE.md b/USER_GUIDE.md index 4d87389ae..199c9a82a 100644 --- a/USER_GUIDE.md +++ b/USER_GUIDE.md @@ -2,7 +2,7 @@ See the [README.md](README.md) for introductory background. -Version: 0.5.1 (2019-07-24, TZ DB version 2019a, beta) +Version: 0.5.2 (2019-07-29, TZ DB version 2019a, beta) ## Installation @@ -1396,8 +1396,8 @@ database is about 23kB. On 32-bit processors, the `zonedb::` data set is about #include using namespace ace_time; ... -const uint16_t SIZE = 2; -BasicZoneManager zoneManager( +static const uint16_t SIZE = 2; +static BasicZoneManager zoneManager( zonedb::kZoneRegistrySize, zonedb::kZoneRegistry); void someFunction(const char* zoneName) { @@ -1430,7 +1430,8 @@ static const basic::ZoneInfo* const kBasicZoneRegistry[] ACE_TIME_PROGMEM = { static const uint16_t kZoneRegistrySize = sizeof(Controller::kZoneRegistry) / sizeof(basic::ZoneInfo*); -static BasicZoneManager<2> zoneManager(kZoneRegistrySize, kZoneRegistry); +static const uint16_t NUM_ZONES = 2; +static BasicZoneManager zoneManager(kZoneRegistrySize, kZoneRegistry); ``` Here is the equivalent `ExtendedZoneManager` with 4 zones from the `zonedbx::` @@ -1450,7 +1451,8 @@ static const extended::ZoneInfo* const kZoneRegistry[] ACE_TIME_PROGMEM = { static const uint16_t kZoneRegistrySize = sizeof(Controller::kZoneRegistry) / sizeof(extended::ZoneInfo*); -static ExtendedZoneManager<2> zoneManager(kZoneRegistrySize, kZoneRegistry); +static const uint16_t NUM_ZONES = 2; +static ExtendedZoneManager zoneManager(kZoneRegistrySize, kZoneRegistry); ``` The `ACE_TIME_PROGMEM` macro is defined in @@ -1556,7 +1558,7 @@ fully qualified zone name was used. #### createForZoneIndex The `ZoneManager::createForZoneIndex()` creates a `TimeZone` from its integer -index into the Zone registry, from 0 to `registrySize-1`. This is useful when +index into the Zone registry, from 0 to `registrySize - 1`. This is useful when you want to show the user with a menu of zones from the `ZoneManager` and allow the user to select one of the options. @@ -2295,15 +2297,15 @@ sizeof(LocalTime): 3 sizeof(LocalDateTime): 6 sizeof(TimeOffset): 1 sizeof(OffsetDateTime): 7 -sizeof(BasicZoneProcessor): 156 -sizeof(ExtendedZoneProcessor): 500 +sizeof(BasicZoneProcessor): 136 +sizeof(ExtendedZoneProcessor): 468 sizeof(TimeZone): 8 sizeof(ZonedDateTime): 16 sizeof(TimePeriod): 4 sizeof(SystemClock): 24 sizeof(NtpTimeProvider): 88 (ESP8266), 116 (ESP32) -sizeof(SystemClockSyncLoop): 20 -sizeof(SystemClockSyncCoroutine): 52 +sizeof(SystemClockSyncLoop): 16 +sizeof(SystemClockSyncCoroutine): 48 ``` The [MemoryBenchmark](examples/MemoryBenchmark) program gives a more @@ -2311,19 +2313,19 @@ comprehensive answer to the amount of memory taken by this library. Here is a short summary for an 8-bit microcontroller (e.g. Arduino Nano): * Using the `TimeZone` class with a `BasicZoneProcessor` for one timezone takes - about 6140 bytes of flash memory and 193 bytes of static RAM. -* Using 2 timezones with `BasiCZoneProcessorincreases the consumption to 6628 - bytes of flash and 231 bytes of RAM. -* Loading the entire `zonedb::` zoneinfo database consumes 20354 bytes of flash - and 601 bytes of RAM. + about 6 kB of flash memory and 193 bytes of static RAM. +* Using 2 timezones with `BasiCZoneProcessor increases the consumption to + about 7 kB of flash and 207 bytes of RAM. +* Loading the entire `zonedb::` zoneinfo database consumes 21 kB bytes of flash + and 597 bytes of RAM. * Adding the `SystemClock` to the `TimeZone` and `BasicZoneProcessor` with one - timezone consumes 8436 bytes of flash and 344 bytes of RAM. + timezone consumes 8.5 kB bytes of flash and 352 bytes of RAM. These numbers indicate that the AceTime library is useful even on a limited -8-bit controller with only 30-32kB of flash and 2kB of RAM. As a concrete +8-bit controller with only 30-32 kB of flash and 2 kB of RAM. As a concrete example, the [WorldClock](examples/WorldClock) program contains 3 OLED displays over SPI, 2 buttons, one DS3231 chip, and 3 timezones using AceTime, and these -all fit inside a Arduino Pro Micro limit of 30kB flash and 2.5kB of RAM. +all fit inside a Arduino Pro Micro limit of 30 kB flash and 2.5 kB of RAM. ## Comparisons to Other Time Libraries @@ -2549,19 +2551,19 @@ did not think it would fit inside an Arduino controller. emulator. * `zonedb/` and `zonedbx/` zoneinfo files * These statically defined data structures are loaded into flash memory - then copied to RAM when the application starts. Fortunately, most - `ZoneInfo` entries are only 40-60 bytes each and the corresponding - `ZonePolicy` entries are 50-100 bytes each. - * It may be possible to use the `PROGMEM` keyword to store them only on - flash memory, but that will increase the flash memory size due to the code - needed to read these data structures from flash. In some applications, - flash memory may be more precious than RAM so it is not clear that using - `PROGMEM` for these data structures is the appropriate solution. + using the `PROGMEM` keyword. The vast majority of the data structure + fields will stay in flash memory and not copied into RAM. + * The zoneinfo files have *not* been compressed using bit-fields or any + other compression techniques. It may be possible to decrease the size of + the full database using these compression techniques. However, compression + will increase the size of the program file, so for applications that use + only a small number of zones, it is not clear if the zoneinfo file + compression will provide a reduction in the size of the overall program. * The TZ database files `backzone`, `systemv` and `factory` are not processed by the `tzcompiler.py` tool. They don't seem to contain anything worthwhile. - * The datasets and `*ZoneProcessor` classes have been *not* been tested or - validated for years prior to 2000. + * The datasets, `BasicZoneProcessor` and `ExtendedZoneProcessor` classes + have been *not* been tested or validated for years prior to 2000. * TZ Database version 2019b contains the first use of the `{onDayOfWeek<=onDayOfMonth}` syntax that I have seen (specifically `Rule Zion, FROM 2005, TO 2012, IN Apr, ON Fri<=1`). The actual transition date @@ -2580,7 +2582,7 @@ did not think it would fit inside an Arduino controller. "[US/Pacific]". * Arduino Zero and SAMD21 Boards * SAMD21 boards (which all identify themselves as `ARDUINO_SAMD_ZERO`) are - fully supported, but there are some tricky points. + supported, but there are some tricky points. * If you are using an original Arduino Zero and using the "Native USB Port", you may encounter problems with nothing showing up on the Serial Monitor. * The original Arduino Zero has [2 USB @@ -2595,16 +2597,25 @@ did not think it would fit inside an Arduino controller. * You may be able to fix this by setting `ACE_TIME_CLOBBER_SERIAL_PORT_MONITOR` to `1` in `src/ace_time/common/compat.h`. (I do not test this option often, so - it may be broke.) + it may be broken.) * If you are using a SAMD21 development or breakout board, or one of the many clones called something like "Ardunio SAMD21 M0 Mini" (this is what I - have), I have found things working better using the SparkFun - Boards instead of the Arduino Zero board. Download "SparkFun SAMD Boards" - using the Board Manager by following the [SparkFun Boards - Installation](https://github.com/sparkfun/Arduino_Boards), then select the - board labeled "SparkFun SAMD Mini Breakout". These boards have only a - single USB connector, and the `SERIAL_PORT_MONITOR` will be properly - defined to be `SerialUSB`. + have), I have been unable to find a board configuration that is an exact + match. You have a few choices: + * If you are running the [AceTime unit tests](tests/), you need to have + a working `SERIAL_PORT_MONITOR`, so the "Arduino MKR ZERO" board + might work better, instead of the "Arduino Zero (Native USB Port)" + board. + * If you are running an app that requires proper pin configuration, + it seems that the `Arduino MKR ZERO" configuration is not correct for + this clone board. You need to go back to the "Arduino/Genuino Zero + (Native USB Port)" board configuration. + * You may also try installing the [SparkFun + Boards](https://github.com/sparkfun/Arduino_Boards) and select + the "SparkFun SAMD21 Mini Breakout" board. The advantage of using + this configuration is that the `SERIAL_PORT_MONITOR` is configured + properly as well as the port pin numbers. However, I have found that + the USB connection can be a bit flaky. * The SAMD21 microcontroller does *not* provide any EEPROM. Therefore, this feature is disabled in the apps under `examples` (e.g. `CommandLineClock`, `OledClock`, and `WorldClock`) which use this feature. diff --git a/docs/doxygen.cfg b/docs/doxygen.cfg index b4cae7edd..15bb03f36 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 = 0.5.1 +PROJECT_NUMBER = 0.5.2 # 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 93d524718..1262350ce 100644 --- a/docs/html/AceTime_8h_source.html +++ b/docs/html/AceTime_8h_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
@@ -68,7 +68,7 @@
AceTime.h
-
1 /*
2  * MIT License
3  * Copyright (c) 2018 Brian T. Park
4  */
5 
13 #ifndef ACE_TIME_ACE_TIME_H
14 #define ACE_TIME_ACE_TIME_H
15 
16 #include "ace_time/common/compat.h"
17 #include "ace_time/common/common.h"
18 #include "ace_time/common/DateStrings.h"
19 #include "ace_time/internal/ZoneContext.h"
20 #include "ace_time/internal/ZoneInfo.h"
21 #include "ace_time/internal/ZonePolicy.h"
22 #include "ace_time/zonedb/zone_policies.h"
23 #include "ace_time/zonedb/zone_infos.h"
24 #include "ace_time/zonedb/zone_registry.h"
25 #include "ace_time/zonedbx/zone_policies.h"
26 #include "ace_time/zonedbx/zone_infos.h"
27 #include "ace_time/zonedbx/zone_registry.h"
28 #include "ace_time/ZoneRegistrar.h"
29 #include "ace_time/LocalDate.h"
30 #include "ace_time/local_date_mutation.h"
31 #include "ace_time/LocalTime.h"
32 #include "ace_time/LocalDateTime.h"
33 #include "ace_time/TimeOffset.h"
34 #include "ace_time/time_offset_mutation.h"
35 #include "ace_time/OffsetDateTime.h"
36 #include "ace_time/ZoneProcessor.h"
37 #include "ace_time/BasicZoneProcessor.h"
38 #include "ace_time/ExtendedZoneProcessor.h"
39 #include "ace_time/ZoneProcessorCache.h"
40 #include "ace_time/ZoneManager.h"
41 #include "ace_time/TimeZoneData.h"
42 #include "ace_time/TimeZone.h"
43 #include "ace_time/BasicZone.h"
44 #include "ace_time/ExtendedZone.h"
45 #include "ace_time/ZonedDateTime.h"
46 #include "ace_time/zoned_date_time_mutation.h"
47 #include "ace_time/TimePeriod.h"
48 #include "ace_time/time_period_mutation.h"
49 #include "ace_time/clock/TimeProvider.h"
50 #include "ace_time/clock/TimeKeeper.h"
51 #include "ace_time/clock/NtpTimeProvider.h"
52 #include "ace_time/clock/DS3231TimeKeeper.h"
53 #include "ace_time/clock/SystemClock.h"
54 #include "ace_time/clock/SystemClockSyncLoop.h"
55 
56 // activate only if <AceRoutine.h> is included before this header
57 #ifdef ACE_ROUTINE_VERSION
58  #include "ace_time/clock/SystemClockSyncCoroutine.h"
59 #endif
60 
61 // Version format: xxyyzz == "xx.yy.zz"
62 #define ACE_TIME_VERSION 501
63 #define ACE_TIME_VERSION_STRING "0.5.1"
64 
65 #endif
Macros and definitions that provide a consistency layer among the various Arduino boards for compatib...
+
1 /*
2  * MIT License
3  * Copyright (c) 2018 Brian T. Park
4  */
5 
13 #ifndef ACE_TIME_ACE_TIME_H
14 #define ACE_TIME_ACE_TIME_H
15 
16 #include "ace_time/common/compat.h"
17 #include "ace_time/common/common.h"
18 #include "ace_time/common/DateStrings.h"
19 #include "ace_time/internal/ZoneContext.h"
20 #include "ace_time/internal/ZoneInfo.h"
21 #include "ace_time/internal/ZonePolicy.h"
22 #include "ace_time/zonedb/zone_policies.h"
23 #include "ace_time/zonedb/zone_infos.h"
24 #include "ace_time/zonedb/zone_registry.h"
25 #include "ace_time/zonedbx/zone_policies.h"
26 #include "ace_time/zonedbx/zone_infos.h"
27 #include "ace_time/zonedbx/zone_registry.h"
28 #include "ace_time/ZoneRegistrar.h"
29 #include "ace_time/LocalDate.h"
30 #include "ace_time/local_date_mutation.h"
31 #include "ace_time/LocalTime.h"
32 #include "ace_time/LocalDateTime.h"
33 #include "ace_time/TimeOffset.h"
34 #include "ace_time/time_offset_mutation.h"
35 #include "ace_time/OffsetDateTime.h"
36 #include "ace_time/ZoneProcessor.h"
37 #include "ace_time/BasicZoneProcessor.h"
38 #include "ace_time/ExtendedZoneProcessor.h"
39 #include "ace_time/ZoneProcessorCache.h"
40 #include "ace_time/ZoneManager.h"
41 #include "ace_time/TimeZoneData.h"
42 #include "ace_time/TimeZone.h"
43 #include "ace_time/BasicZone.h"
44 #include "ace_time/ExtendedZone.h"
45 #include "ace_time/ZonedDateTime.h"
46 #include "ace_time/zoned_date_time_mutation.h"
47 #include "ace_time/TimePeriod.h"
48 #include "ace_time/time_period_mutation.h"
49 #include "ace_time/clock/TimeProvider.h"
50 #include "ace_time/clock/TimeKeeper.h"
51 #include "ace_time/clock/NtpTimeProvider.h"
52 #include "ace_time/clock/DS3231TimeKeeper.h"
53 #include "ace_time/clock/SystemClock.h"
54 #include "ace_time/clock/SystemClockSyncLoop.h"
55 
56 // activate only if <AceRoutine.h> is included before this header
57 #ifdef ACE_ROUTINE_VERSION
58  #include "ace_time/clock/SystemClockSyncCoroutine.h"
59 #endif
60 
61 // Version format: xxyyzz == "xx.yy.zz"
62 #define ACE_TIME_VERSION 502
63 #define ACE_TIME_VERSION_STRING "0.5.2"
64 
65 #endif
Macros and definitions that provide a consistency layer among the various Arduino boards for compatib...
-
1 /*
2  * MIT License
3  * Copyright (c) 2019 Brian T. Park
4  */
5 
6 #ifndef ACE_TIME_BASIC_ZONE_PROCESSOR_H
7 #define ACE_TIME_BASIC_ZONE_PROCESSOR_H
8 
9 #include <string.h> // strchr()
10 #include <stdint.h>
11 #include "internal/ZonePolicy.h"
12 #include "internal/ZoneInfo.h"
13 #include "internal/Brokers.h"
14 #include "common/logging.h"
15 #include "TimeOffset.h"
16 #include "LocalDate.h"
17 #include "OffsetDateTime.h"
18 #include "ZoneProcessor.h"
19 
20 class BasicZoneProcessorTest_init_primitives;
21 class BasicZoneProcessorTest_init;
22 class BasicZoneProcessorTest_setZoneInfo;
23 class BasicZoneProcessorTest_createAbbreviation;
24 class BasicZoneProcessorTest_calcStartDayOfMonth;
25 class BasicZoneProcessorTest_calcRuleOffsetCode;
26 
27 namespace ace_time {
28 
29 template<uint8_t SIZE, uint8_t TYPE, typename ZS, typename ZI, typename ZIB>
31 
32 namespace basic {
33 
46 struct Transition {
54  static const uint8_t kAbbrevSize = 6 + 1;
55 
62 
74 
76  int8_t yearTiny;
77 
79  acetime_t startEpochSeconds;
80 
86  int8_t offsetCode;
87 
89  int8_t deltaCode;
90 
99 
101  void log() const {
102  if (sizeof(acetime_t) == sizeof(int)) {
103  logging::println("startEpochSeconds: %d", startEpochSeconds);
104  } else {
105  logging::println("startEpochSeconds: %ld", startEpochSeconds);
106  }
107  logging::println("offsetCode: %d", offsetCode);
108  logging::println("abbrev: %s", abbrev);
109  if (rule.isNotNull()) {
110  logging::println("Rule.fromYear: %d", rule.fromYearTiny());
111  logging::println("Rule.toYear: %d", rule.toYearTiny());
112  logging::println("Rule.inMonth: %d", rule.inMonth());
113  logging::println("Rule.onDayOfMonth: %d", rule.onDayOfMonth());
114  }
115  }
116 };
117 
119 struct MonthDay {
120  uint8_t month;
121  uint8_t day;
122 };
123 
124 } // namespace basic
125 
178  public:
183  explicit BasicZoneProcessor(const basic::ZoneInfo* zoneInfo = nullptr):
184  ZoneProcessor(kTypeBasic),
185  mZoneInfo(zoneInfo) {}
186 
188  const void* getZoneInfo() const override {
189  return mZoneInfo.zoneInfo();
190  }
191 
192  uint32_t getZoneId() const override { return mZoneInfo.zoneId(); }
193 
194  TimeOffset getUtcOffset(acetime_t epochSeconds) const override {
195  const basic::Transition* transition = getTransition(epochSeconds);
196  int8_t code = (transition)
197  ? transition->offsetCode : TimeOffset::kErrorCode;
198  return TimeOffset::forOffsetCode(code);
199  }
200 
201  TimeOffset getDeltaOffset(acetime_t epochSeconds) const override {
202  const basic::Transition* transition = getTransition(epochSeconds);
203  int8_t code = (transition)
204  ? transition->deltaCode : TimeOffset::kErrorCode;
205  return TimeOffset::forOffsetCode(code);
206  }
207 
208  const char* getAbbrev(acetime_t epochSeconds) const override {
209  const basic::Transition* transition = getTransition(epochSeconds);
210  return (transition) ? transition->abbrev : "";
211  }
212 
242  OffsetDateTime getOffsetDateTime(const LocalDateTime& ldt) const override {
243  // Only a single local variable of OffsetDateTime used, to allow Return
244  // Value Optimization (and save 20 bytes of flash for WorldClock).
245  OffsetDateTime odt;
246  bool success = init(ldt.localDate());
247  if (success) {
248  // 0) Use the UTC epochSeconds to get intial guess of offset.
249  acetime_t epochSeconds0 = ldt.toEpochSeconds();
250  auto offset0 = getUtcOffset(epochSeconds0);
251 
252  // 1) Use offset0 to get the next epochSeconds and offset.
253  odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset0);
254  acetime_t epochSeconds1 = odt.toEpochSeconds();
255  auto offset1 = getUtcOffset(epochSeconds1);
256 
257  // 2) Use offset1 to get the next epochSeconds and offset.
258  odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset1);
259  acetime_t epochSeconds2 = odt.toEpochSeconds();
260  auto offset2 = getUtcOffset(epochSeconds2);
261 
262  // If offset1 and offset2 are equal, then we have an equilibrium
263  // and odt(1) must equal odt(2), so we can just return the last odt.
264  if (offset1.toOffsetCode() == offset2.toOffsetCode()) {
265  // pass
266  } else {
267  // Pick the later epochSeconds and offset
268  acetime_t epochSeconds;
269  TimeOffset offset;
270  if (epochSeconds1 > epochSeconds2) {
271  epochSeconds = epochSeconds1;
272  offset = offset1;
273  } else {
274  epochSeconds = epochSeconds2;
275  offset = offset2;
276  }
277  odt = OffsetDateTime::forEpochSeconds(epochSeconds, offset);
278  }
279  } else {
280  odt = OffsetDateTime::forError();
281  }
282 
283  return odt;
284  }
285 
286  void printTo(Print& printer) const override;
287 
288  void printShortTo(Print& printer) const override;
289 
291  void log() const {
292  if (!mIsFilled) {
293  logging::println("*not initialized*");
294  return;
295  }
296  logging::println("mYear: %d", mYear);
297  logging::println("mNumTransitions: %d", mNumTransitions);
298  logging::println("---- PrevTransition");
299  mPrevTransition.log();
300  for (int i = 0; i < mNumTransitions; i++) {
301  logging::println("---- Transition: %d", i);
302  mTransitions[i].log();
303  }
304  }
305 
324  static basic::MonthDay calcStartDayOfMonth(int16_t year, uint8_t month,
325  uint8_t onDayOfWeek, int8_t onDayOfMonth) {
326  if (onDayOfWeek == 0) return {month, (uint8_t) onDayOfMonth};
327 
328  if (onDayOfMonth >= 0) {
329  // Convert "last{Xxx}" to "last{Xxx}>={daysInMonth-6}".
330  uint8_t daysInMonth = LocalDate::daysInMonth(year, month);
331  if (onDayOfMonth == 0) {
332  onDayOfMonth = daysInMonth - 6;
333  }
334 
335  auto limitDate = LocalDate::forComponents(year, month, onDayOfMonth);
336  uint8_t dayOfWeekShift = (onDayOfWeek - limitDate.dayOfWeek() + 7) % 7;
337  uint8_t day = (uint8_t) (onDayOfMonth + dayOfWeekShift);
338  if (day > daysInMonth) {
339  // TODO: Support shifting from Dec to Jan of following year.
340  day -= daysInMonth;
341  month++;
342  }
343  return {month, day};
344  } else {
345  onDayOfMonth = -onDayOfMonth;
346  auto limitDate = LocalDate::forComponents(year, month, onDayOfMonth);
347  int8_t dayOfWeekShift = (limitDate.dayOfWeek() - onDayOfWeek + 7) % 7;
348  int8_t day = onDayOfMonth - dayOfWeekShift;
349  if (day < 1) {
350  // TODO: Support shifting from Jan to Dec of the previous year.
351  month--;
352  uint8_t daysInPrevMonth = LocalDate::daysInMonth(year, month);
353  day += daysInPrevMonth;
354  }
355  return {month, (uint8_t) day};
356  }
357  }
358 
359  private:
360  friend class ::BasicZoneProcessorTest_init_primitives;
361  friend class ::BasicZoneProcessorTest_init;
362  friend class ::BasicZoneProcessorTest_setZoneInfo;
363  friend class ::BasicZoneProcessorTest_createAbbreviation;
364  friend class ::BasicZoneProcessorTest_calcStartDayOfMonth;
365  friend class ::BasicZoneProcessorTest_calcRuleOffsetCode;
366 
367  template<uint8_t SIZE, uint8_t TYPE, typename ZS, typename ZI, typename ZIB>
368  friend class ZoneProcessorCacheImpl; // setZoneInfo()
369 
371  static const uint8_t kMaxCacheEntries = 4;
372 
378  static const acetime_t kMinEpochSeconds = INT32_MIN + 1;
379 
380  // Disable copy constructor and assignment operator.
381  BasicZoneProcessor(const BasicZoneProcessor&) = delete;
382  BasicZoneProcessor& operator=(const BasicZoneProcessor&) = delete;
383 
384  bool equals(const ZoneProcessor& other) const override {
385  const auto& that = (const BasicZoneProcessor&) other;
386  return getZoneInfo() == that.getZoneInfo();
387  }
388 
390  void setZoneInfo(const void* zoneInfo) override {
391  if (mZoneInfo.zoneInfo() == zoneInfo) return;
392 
393  mZoneInfo = basic::ZoneInfoBroker((const basic::ZoneInfo*) zoneInfo);
394  mYear = 0;
395  mIsFilled = false;
396  mNumTransitions = 0;
397  }
398 
400  const basic::Transition* getTransition(acetime_t epochSeconds) const {
401  LocalDate ld = LocalDate::forEpochSeconds(epochSeconds);
402  bool success = init(ld);
403  return (success) ? findMatch(epochSeconds) : nullptr;
404  }
405 
434  bool init(const LocalDate& ld) const {
435  int16_t year = ld.year();
436  if (ld.month() == 1 && ld.day() == 1) {
437  year--;
438  }
439  if (isFilled(year)) return true;
440 
441  mYear = year;
442  mNumTransitions = 0; // clear cache
443 
444  if (year < mZoneInfo.startYear() - 1 || mZoneInfo.untilYear() < year) {
445  return false;
446  }
447 
448  addRulePriorToYear(year);
449  addRulesForYear(year);
450  calcTransitions();
451  calcAbbreviations();
452 
453  mIsFilled = true;
454  return true;
455  }
456 
458  bool isFilled(int16_t year) const {
459  return mIsFilled && (year == mYear);
460  }
461 
466  void addRulePriorToYear(int16_t year) const {
467  int8_t yearTiny = year - LocalDate::kEpochYear;
468  int8_t priorYearTiny = yearTiny - 1;
469 
470  // Find the prior Era.
471  const basic::ZoneEraBroker era = findZoneEraPriorTo(year);
472 
473  // If the prior ZoneEra is a simple Era (no zone policy), then create a
474  // Transition using a rule==nullptr. Otherwise, find the latest rule
475  // within the ZoneEra.
476  const basic::ZonePolicyBroker zonePolicy = era.zonePolicy();
477  basic::ZoneRuleBroker latest;
478  if (zonePolicy.isNotNull()) {
479  // Find the latest rule for the matching ZoneEra whose
480  // ZoneRule::toYearTiny < yearTiny. Assume that there are no more than
481  // 1 rule per month.
482  uint8_t numRules = zonePolicy.numRules();
483  for (uint8_t i = 0; i < numRules; i++) {
484  const basic::ZoneRuleBroker rule = zonePolicy.rule(i);
485  // Check if rule is effective prior to the given year
486  if (rule.fromYearTiny() < yearTiny) {
487  if ((latest.isNull()) || compareZoneRule(year, rule, latest) > 0) {
488  latest = rule;
489  }
490  }
491  }
492  }
493 
494  mPrevTransition = createTransition(era, latest, priorYearTiny);
495  }
496 
502  static basic::Transition createTransition(basic::ZoneEraBroker era,
504  int8_t offsetCode;
505  int8_t deltaCode;
506  char letter;
507  if (rule.isNull()) {
508  deltaCode = 0;
509  offsetCode = era.offsetCode();
510  letter = 0;
511  } else {
512  deltaCode = rule.deltaCode();
513  offsetCode = era.offsetCode() + deltaCode;
514  letter = rule.letter();
515  }
516 
517  return {
518  era, rule, yearTiny,
519  0 /*epochSeconds*/,
520  offsetCode,
521  deltaCode,
522  {letter} /*abbrev*/
523  };
524  }
525 
527  static int8_t compareZoneRule(int16_t year,
528  const basic::ZoneRuleBroker a, const basic::ZoneRuleBroker b) {
529  int16_t aYear = effectiveRuleYear(year, a);
530  int16_t bYear = effectiveRuleYear(year, b);
531  if (aYear < bYear) return -1;
532  if (aYear > bYear) return 1;
533  if (a.inMonth() < b.inMonth()) return -1;
534  if (a.inMonth() > b.inMonth()) return 1;
535  return 0;
536  }
537 
542  static int16_t effectiveRuleYear(int16_t year,
543  const basic::ZoneRuleBroker rule) {
544  int8_t yearTiny = year - LocalDate::kEpochYear;
545  if (rule.toYearTiny() < yearTiny) {
546  return rule.toYearTiny() + LocalDate::kEpochYear;
547  }
548  if (rule.fromYearTiny() < yearTiny) {
549  return year - 1;
550  }
551  return 0;
552  }
553 
555  void addRulesForYear(int16_t year) const {
556  const basic::ZoneEraBroker era = findZoneEra(year);
557 
558  // If the ZonePolicy has no rules, then add a Transition which takes
559  // effect at the start time of the current year.
560  const basic::ZonePolicyBroker zonePolicy = era.zonePolicy();
561  if (zonePolicy.isNull()) {
562  addRule(year, era, basic::ZoneRuleBroker());
563  return;
564  }
565 
566  // If the ZonePolicy has rules, find all matching transitions, and add
567  // them to mTransitions, in sorted order according to the
568  // ZoneRule::inMonth field.
569  int8_t yearTiny = year - LocalDate::kEpochYear;
570  uint8_t numRules = zonePolicy.numRules();
571  for (uint8_t i = 0; i < numRules; i++) {
572  const basic::ZoneRuleBroker rule = zonePolicy.rule(i);
573  if ((rule.fromYearTiny() <= yearTiny) &&
574  (yearTiny <= rule.toYearTiny())) {
575  addRule(year, era, rule);
576  }
577  }
578  }
579 
594  void addRule(int16_t year, basic::ZoneEraBroker era,
595  basic::ZoneRuleBroker rule) const {
596 
597  // If a zone needs more transitions than kMaxCacheEntries, the check below
598  // will cause the DST transition information to be inaccurate, and it is
599  // highly likely that this situation would be caught in the
600  // BasicValidationUsingPython or BasicValidationUsingJava unit tests.
601  // Since these unit tests pass, I feel confident that those zones which
602  // need more than kMaxCacheEntries are already filtered out by
603  // tzcompiler.py.
604  //
605  // Ideally, the tzcompiler.py script would explicitly remove those zones
606  // which need more than kMaxCacheEntries Transitions. But this would
607  // require a Python version of the BasicZoneProcessor, and unfortunately,
608  // zone_specifier.py implements only the ExtendedZoneProcessor algorithm
609  // An early version of zone_specifier.py may have implemented something
610  // close to BasicZoneProcessor, and it may be available in the git
611  // history. But it seems like too much work right now to try to dig that
612  // out, just to implement the explicit check for kMaxCacheEntries. It
613  // would mean maintaining another version of zone_specifier.py.
614  if (mNumTransitions >= kMaxCacheEntries) return;
615 
616  // insert new element at the end of the list
617  int8_t yearTiny = year - LocalDate::kEpochYear;
618  mTransitions[mNumTransitions] = createTransition(era, rule, yearTiny);
619  mNumTransitions++;
620 
621  // perform an insertion sort
622  for (uint8_t i = mNumTransitions - 1; i > 0; i--) {
623  basic::Transition& left = mTransitions[i - 1];
624  basic::Transition& right = mTransitions[i];
625  // assume only 1 rule per month
626  if ((left.rule.isNotNull() && right.rule.isNotNull() &&
627  left.rule.inMonth() > right.rule.inMonth())
628  || (left.rule.isNotNull() && right.rule.isNull())) {
629  basic::Transition tmp = left;
630  left = right;
631  right = tmp;
632  }
633  }
634  }
635 
641  const basic::ZoneEraBroker findZoneEra(int16_t year) const {
642  for (uint8_t i = 0; i < mZoneInfo.numEras(); i++) {
643  const basic::ZoneEraBroker era = mZoneInfo.era(i);
644  if (year < era.untilYearTiny() + LocalDate::kEpochYear) return era;
645  }
646  // Return the last ZoneEra if we run off the end.
647  return mZoneInfo.era(mZoneInfo.numEras() - 1);
648  }
649 
661  const basic::ZoneEraBroker findZoneEraPriorTo(int16_t year) const {
662  for (uint8_t i = 0; i < mZoneInfo.numEras(); i++) {
663  const basic::ZoneEraBroker era = mZoneInfo.era(i);
664  if (year <= era.untilYearTiny() + LocalDate::kEpochYear) return era;
665  }
666  // Return the last ZoneEra if we run off the end.
667  return mZoneInfo.era(mZoneInfo.numEras() - 1);
668  }
669 
677  void calcTransitions() const {
678  // Set the initial startEpochSeconds to be -Infinity
679  mPrevTransition.startEpochSeconds = kMinEpochSeconds;
680  const basic::Transition* prevTransition = &mPrevTransition;
681 
682  for (uint8_t i = 0; i < mNumTransitions; i++) {
683  basic::Transition& transition = mTransitions[i];
684  const int16_t year = transition.yearTiny + LocalDate::kEpochYear;
685 
686  if (transition.rule.isNull()) {
687  // If the transition is simple (has no named rule), then the
688  // ZoneEra applies for the entire year (since BasicZoneProcessor
689  // supports only whole year in the UNTIL field). The whole year UNTIL
690  // field has an implied 'w' modifier on 00:00, we don't need to call
691  // calcRuleOffsetCode() with a 'w', we can just use the previous
692  // transition's offset to calculate the startDateTime of this
693  // transition.
694  //
695  // Also, when transition.rule == nullptr, the mNumTransitions should
696  // be 1, since only a single transition is added by
697  // addRulesForYear().
698  const int8_t prevOffsetCode = prevTransition->offsetCode;
700  year, 1, 1, 0, 0, 0,
701  TimeOffset::forOffsetCode(prevOffsetCode));
702  transition.startEpochSeconds = startDateTime.toEpochSeconds();
703  } else {
704  // In this case, the transition points to a named ZonePolicy, which
705  // means that there could be multiple ZoneRules associated with the
706  // given year. For each transition, determine the startEpochSeconds,
707  // and the effective offset code.
708 
709  // Determine the start date of the rule.
710  const basic::MonthDay monthDay = calcStartDayOfMonth(
711  year, transition.rule.inMonth(), transition.rule.onDayOfWeek(),
712  transition.rule.onDayOfMonth());
713 
714  // Determine the offset of the 'atTimeModifier'. The 'w' modifier
715  // requires the offset of the previous transition.
716  const int8_t prevOffsetCode = calcRuleOffsetCode(
717  prevTransition->offsetCode,
718  transition.era.offsetCode(),
719  transition.rule.atTimeModifier());
720 
721  // startDateTime
722  const uint8_t timeCode = transition.rule.atTimeCode();
723  const uint8_t atHour = timeCode / 4;
724  const uint8_t atMinute = (timeCode % 4) * 15;
726  year, monthDay.month, monthDay.day,
727  atHour, atMinute, 0 /*second*/,
728  TimeOffset::forOffsetCode(prevOffsetCode));
729  transition.startEpochSeconds = startDateTime.toEpochSeconds();
730  }
731 
732  prevTransition = &transition;
733  }
734  }
735 
742  static int8_t calcRuleOffsetCode(int8_t prevEffectiveOffsetCode,
743  int8_t currentBaseOffsetCode, uint8_t atModifier) {
744  if (atModifier == 'w') {
745  return prevEffectiveOffsetCode;
746  } else if (atModifier == 's') {
747  return currentBaseOffsetCode;
748  } else { // 'u', 'g' or 'z'
749  return 0;
750  }
751  }
752 
754  void calcAbbreviations() const {
755  calcAbbreviation(&mPrevTransition);
756  for (uint8_t i = 0; i < mNumTransitions; i++) {
757  calcAbbreviation(&mTransitions[i]);
758  }
759  }
760 
762  static void calcAbbreviation(basic::Transition* transition) {
763  createAbbreviation(
764  transition->abbrev,
766  transition->era.format(),
767  transition->deltaCode,
768  transition->abbrev[0]);
769  }
770 
806  static void createAbbreviation(char* dest, uint8_t destSize,
807  const char* format, uint8_t deltaCode, char letter) {
808  // Check if RULES column empty.
809  if (deltaCode == 0 && letter == '\0') {
810  strncpy(dest, format, destSize);
811  dest[destSize - 1] = '\0';
812  return;
813  }
814 
815  // Check if FORMAT contains a '%'.
816  if (strchr(format, '%') != nullptr) {
817  copyAndReplace(dest, destSize, format, '%', letter);
818  } else {
819  // Check if FORMAT contains a '/'.
820  const char* slashPos = strchr(format, '/');
821  if (slashPos != nullptr) {
822  if (deltaCode == 0) {
823  uint8_t headLength = (slashPos - format);
824  if (headLength >= destSize) headLength = destSize - 1;
825  memcpy(dest, format, headLength);
826  dest[headLength] = '\0';
827  } else {
828  uint8_t tailLength = strlen(slashPos+1);
829  if (tailLength >= destSize) tailLength = destSize - 1;
830  memcpy(dest, slashPos+1, tailLength);
831  dest[tailLength] = '\0';
832  }
833  } else {
834  // Just copy the FORMAT disregarding the deltaCode and letter.
835  strncpy(dest, format, destSize);
836  dest[destSize - 1] = '\0';
837  }
838  }
839  }
840 
846  static void copyAndReplace(char* dst, uint8_t dstSize, const char* src,
847  char oldChar, char newChar) {
848  while (*src != '\0' && dstSize > 0) {
849  if (*src == oldChar) {
850  if (newChar == '-') {
851  src++;
852  } else {
853  *dst = newChar;
854  dst++;
855  src++;
856  dstSize--;
857  }
858  } else {
859  *dst++ = *src++;
860  dstSize--;
861  }
862  }
863 
864  if (dstSize == 0) {
865  --dst;
866  }
867  *dst = '\0';
868  }
869 
871  const basic::Transition* findMatch(acetime_t epochSeconds) const {
872  const basic::Transition* closestMatch = &mPrevTransition;
873  for (uint8_t i = 0; i < mNumTransitions; i++) {
874  const basic::Transition* m = &mTransitions[i];
875  if (m->startEpochSeconds <= epochSeconds) {
876  closestMatch = m;
877  }
878  }
879  return closestMatch;
880  }
881 
882  basic::ZoneInfoBroker mZoneInfo;
883 
884  mutable int16_t mYear = 0; // maybe create LocalDate::kInvalidYear?
885  mutable bool mIsFilled = false;
886  mutable uint8_t mNumTransitions = 0;
887  mutable basic::Transition mTransitions[kMaxCacheEntries];
888  mutable basic::Transition mPrevTransition; // previous year's transition
889 };
890 
891 }
892 
893 #endif
Base interface for ZoneProcessor classes.
Definition: ZoneProcessor.h:45
-
ZoneEraBroker era
The ZoneEra that matched the given year.
-
const char * getAbbrev(acetime_t epochSeconds) const override
Return the time zone abbreviation at epochSeconds.
+
1 /*
2  * MIT License
3  * Copyright (c) 2019 Brian T. Park
4  */
5 
6 #ifndef ACE_TIME_BASIC_ZONE_PROCESSOR_H
7 #define ACE_TIME_BASIC_ZONE_PROCESSOR_H
8 
9 #include <string.h> // strchr()
10 #include <stdint.h>
11 #include "internal/ZonePolicy.h"
12 #include "internal/ZoneInfo.h"
13 #include "internal/Brokers.h"
14 #include "common/logging.h"
15 #include "TimeOffset.h"
16 #include "LocalDate.h"
17 #include "OffsetDateTime.h"
18 #include "ZoneProcessor.h"
19 
20 #define ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG 0
21 
22 class BasicZoneProcessorTest_init_primitives;
23 class BasicZoneProcessorTest_init;
24 class BasicZoneProcessorTest_setZoneInfo;
25 class BasicZoneProcessorTest_createAbbreviation;
26 class BasicZoneProcessorTest_calcStartDayOfMonth;
27 class BasicZoneProcessorTest_calcRuleOffsetCode;
28 
29 namespace ace_time {
30 
31 template<uint8_t SIZE, uint8_t TYPE, typename ZS, typename ZI, typename ZIB>
33 
34 namespace basic {
35 
51 struct Transition {
59  static const uint8_t kAbbrevSize = 6 + 1;
60 
67 
79 
81  acetime_t startEpochSeconds;
82 
84  int8_t yearTiny;
85 
91  int8_t offsetCode;
92 
94  int8_t deltaCode;
95 
104 
106  void log() const {
107  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
108  if (sizeof(acetime_t) == sizeof(int)) {
109  logging::println("startEpochSeconds: %d", startEpochSeconds);
110  } else {
111  logging::println("startEpochSeconds: %ld", startEpochSeconds);
112  }
113  logging::println("offsetCode: %d", offsetCode);
114  logging::println("abbrev: %s", abbrev);
115  if (rule.isNotNull()) {
116  logging::println("Rule.fromYear: %d", rule.fromYearTiny());
117  logging::println("Rule.toYear: %d", rule.toYearTiny());
118  logging::println("Rule.inMonth: %d", rule.inMonth());
119  logging::println("Rule.onDayOfMonth: %d", rule.onDayOfMonth());
120  }
121  }
122  }
123 };
124 
126 struct MonthDay {
127  uint8_t month;
128  uint8_t day;
129 };
130 
131 } // namespace basic
132 
185  public:
190  explicit BasicZoneProcessor(const basic::ZoneInfo* zoneInfo = nullptr):
191  ZoneProcessor(kTypeBasic),
192  mZoneInfo(zoneInfo) {}
193 
195  const void* getZoneInfo() const override {
196  return mZoneInfo.zoneInfo();
197  }
198 
199  uint32_t getZoneId() const override { return mZoneInfo.zoneId(); }
200 
201  TimeOffset getUtcOffset(acetime_t epochSeconds) const override {
202  const basic::Transition* transition = getTransition(epochSeconds);
203  int8_t code = (transition)
204  ? transition->offsetCode : TimeOffset::kErrorCode;
205  return TimeOffset::forOffsetCode(code);
206  }
207 
208  TimeOffset getDeltaOffset(acetime_t epochSeconds) const override {
209  const basic::Transition* transition = getTransition(epochSeconds);
210  int8_t code = (transition)
211  ? transition->deltaCode : TimeOffset::kErrorCode;
212  return TimeOffset::forOffsetCode(code);
213  }
214 
215  const char* getAbbrev(acetime_t epochSeconds) const override {
216  const basic::Transition* transition = getTransition(epochSeconds);
217  return (transition) ? transition->abbrev : "";
218  }
219 
249  OffsetDateTime getOffsetDateTime(const LocalDateTime& ldt) const override {
250  // Only a single local variable of OffsetDateTime used, to allow Return
251  // Value Optimization (and save 20 bytes of flash for WorldClock).
252  OffsetDateTime odt;
253  bool success = init(ldt.localDate());
254  if (success) {
255  // 0) Use the UTC epochSeconds to get intial guess of offset.
256  acetime_t epochSeconds0 = ldt.toEpochSeconds();
257  auto offset0 = getUtcOffset(epochSeconds0);
258 
259  // 1) Use offset0 to get the next epochSeconds and offset.
260  odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset0);
261  acetime_t epochSeconds1 = odt.toEpochSeconds();
262  auto offset1 = getUtcOffset(epochSeconds1);
263 
264  // 2) Use offset1 to get the next epochSeconds and offset.
265  odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset1);
266  acetime_t epochSeconds2 = odt.toEpochSeconds();
267  auto offset2 = getUtcOffset(epochSeconds2);
268 
269  // If offset1 and offset2 are equal, then we have an equilibrium
270  // and odt(1) must equal odt(2), so we can just return the last odt.
271  if (offset1.toOffsetCode() == offset2.toOffsetCode()) {
272  // pass
273  } else {
274  // Pick the later epochSeconds and offset
275  acetime_t epochSeconds;
276  TimeOffset offset;
277  if (epochSeconds1 > epochSeconds2) {
278  epochSeconds = epochSeconds1;
279  offset = offset1;
280  } else {
281  epochSeconds = epochSeconds2;
282  offset = offset2;
283  }
284  odt = OffsetDateTime::forEpochSeconds(epochSeconds, offset);
285  }
286  } else {
287  odt = OffsetDateTime::forError();
288  }
289 
290  return odt;
291  }
292 
293  void printTo(Print& printer) const override;
294 
295  void printShortTo(Print& printer) const override;
296 
298  void log() const {
299  if (ACE_TIME_BASIC_ZONE_PROCESSOR_DEBUG) {
300  if (!mIsFilled) {
301  logging::println("*not initialized*");
302  return;
303  }
304  logging::println("mYear: %d", mYear);
305  logging::println("mNumTransitions: %d", mNumTransitions);
306  logging::println("---- PrevTransition");
307  mPrevTransition.log();
308  for (int i = 0; i < mNumTransitions; i++) {
309  logging::println("---- Transition: %d", i);
310  mTransitions[i].log();
311  }
312  }
313  }
314 
333  static basic::MonthDay calcStartDayOfMonth(int16_t year, uint8_t month,
334  uint8_t onDayOfWeek, int8_t onDayOfMonth) {
335  if (onDayOfWeek == 0) return {month, (uint8_t) onDayOfMonth};
336 
337  if (onDayOfMonth >= 0) {
338  // Convert "last{Xxx}" to "last{Xxx}>={daysInMonth-6}".
339  uint8_t daysInMonth = LocalDate::daysInMonth(year, month);
340  if (onDayOfMonth == 0) {
341  onDayOfMonth = daysInMonth - 6;
342  }
343 
344  auto limitDate = LocalDate::forComponents(year, month, onDayOfMonth);
345  uint8_t dayOfWeekShift = (onDayOfWeek - limitDate.dayOfWeek() + 7) % 7;
346  uint8_t day = (uint8_t) (onDayOfMonth + dayOfWeekShift);
347  if (day > daysInMonth) {
348  // TODO: Support shifting from Dec to Jan of following year.
349  day -= daysInMonth;
350  month++;
351  }
352  return {month, day};
353  } else {
354  onDayOfMonth = -onDayOfMonth;
355  auto limitDate = LocalDate::forComponents(year, month, onDayOfMonth);
356  int8_t dayOfWeekShift = (limitDate.dayOfWeek() - onDayOfWeek + 7) % 7;
357  int8_t day = onDayOfMonth - dayOfWeekShift;
358  if (day < 1) {
359  // TODO: Support shifting from Jan to Dec of the previous year.
360  month--;
361  uint8_t daysInPrevMonth = LocalDate::daysInMonth(year, month);
362  day += daysInPrevMonth;
363  }
364  return {month, (uint8_t) day};
365  }
366  }
367 
368  private:
369  friend class ::BasicZoneProcessorTest_init_primitives;
370  friend class ::BasicZoneProcessorTest_init;
371  friend class ::BasicZoneProcessorTest_setZoneInfo;
372  friend class ::BasicZoneProcessorTest_createAbbreviation;
373  friend class ::BasicZoneProcessorTest_calcStartDayOfMonth;
374  friend class ::BasicZoneProcessorTest_calcRuleOffsetCode;
375 
376  template<uint8_t SIZE, uint8_t TYPE, typename ZS, typename ZI, typename ZIB>
377  friend class ZoneProcessorCacheImpl; // setZoneInfo()
378 
380  static const uint8_t kMaxCacheEntries = 4;
381 
387  static const acetime_t kMinEpochSeconds = INT32_MIN + 1;
388 
389  // Disable copy constructor and assignment operator.
390  BasicZoneProcessor(const BasicZoneProcessor&) = delete;
391  BasicZoneProcessor& operator=(const BasicZoneProcessor&) = delete;
392 
393  bool equals(const ZoneProcessor& other) const override {
394  const auto& that = (const BasicZoneProcessor&) other;
395  return getZoneInfo() == that.getZoneInfo();
396  }
397 
399  void setZoneInfo(const void* zoneInfo) override {
400  if (mZoneInfo.zoneInfo() == zoneInfo) return;
401 
402  mZoneInfo = basic::ZoneInfoBroker((const basic::ZoneInfo*) zoneInfo);
403  mYear = 0;
404  mIsFilled = false;
405  mNumTransitions = 0;
406  }
407 
409  const basic::Transition* getTransition(acetime_t epochSeconds) const {
410  LocalDate ld = LocalDate::forEpochSeconds(epochSeconds);
411  bool success = init(ld);
412  return (success) ? findMatch(epochSeconds) : nullptr;
413  }
414 
443  bool init(const LocalDate& ld) const {
444  int16_t year = ld.year();
445  if (ld.month() == 1 && ld.day() == 1) {
446  year--;
447  }
448  if (isFilled(year)) return true;
449 
450  mYear = year;
451  mNumTransitions = 0; // clear cache
452 
453  if (year < mZoneInfo.startYear() - 1 || mZoneInfo.untilYear() < year) {
454  return false;
455  }
456 
457  addRulePriorToYear(year);
458  addRulesForYear(year);
459  calcTransitions();
460  calcAbbreviations();
461 
462  mIsFilled = true;
463  return true;
464  }
465 
467  bool isFilled(int16_t year) const {
468  return mIsFilled && (year == mYear);
469  }
470 
475  void addRulePriorToYear(int16_t year) const {
476  int8_t yearTiny = year - LocalDate::kEpochYear;
477  int8_t priorYearTiny = yearTiny - 1;
478 
479  // Find the prior Era.
480  const basic::ZoneEraBroker era = findZoneEraPriorTo(year);
481 
482  // If the prior ZoneEra is a simple Era (no zone policy), then create a
483  // Transition using a rule==nullptr. Otherwise, find the latest rule
484  // within the ZoneEra.
485  const basic::ZonePolicyBroker zonePolicy = era.zonePolicy();
486  basic::ZoneRuleBroker latest;
487  if (zonePolicy.isNotNull()) {
488  // Find the latest rule for the matching ZoneEra whose
489  // ZoneRule::toYearTiny < yearTiny. Assume that there are no more than
490  // 1 rule per month.
491  uint8_t numRules = zonePolicy.numRules();
492  for (uint8_t i = 0; i < numRules; i++) {
493  const basic::ZoneRuleBroker rule = zonePolicy.rule(i);
494  // Check if rule is effective prior to the given year
495  if (rule.fromYearTiny() < yearTiny) {
496  if ((latest.isNull()) || compareZoneRule(year, rule, latest) > 0) {
497  latest = rule;
498  }
499  }
500  }
501  }
502 
503  mPrevTransition = createTransition(era, latest, priorYearTiny);
504  }
505 
511  static basic::Transition createTransition(basic::ZoneEraBroker era,
513  int8_t offsetCode;
514  int8_t deltaCode;
515  char letter;
516  if (rule.isNull()) {
517  deltaCode = 0;
518  offsetCode = era.offsetCode();
519  letter = 0;
520  } else {
521  deltaCode = rule.deltaCode();
522  offsetCode = era.offsetCode() + deltaCode;
523  letter = rule.letter();
524  }
525 
526  return {
527  era,
528  rule,
529  0 /*epochSeconds*/,
530  yearTiny,
531  offsetCode,
532  deltaCode,
533  {letter} /*abbrev*/
534  };
535  }
536 
538  static int8_t compareZoneRule(int16_t year,
539  const basic::ZoneRuleBroker a, const basic::ZoneRuleBroker b) {
540  int16_t aYear = effectiveRuleYear(year, a);
541  int16_t bYear = effectiveRuleYear(year, b);
542  if (aYear < bYear) return -1;
543  if (aYear > bYear) return 1;
544  if (a.inMonth() < b.inMonth()) return -1;
545  if (a.inMonth() > b.inMonth()) return 1;
546  return 0;
547  }
548 
553  static int16_t effectiveRuleYear(int16_t year,
554  const basic::ZoneRuleBroker rule) {
555  int8_t yearTiny = year - LocalDate::kEpochYear;
556  if (rule.toYearTiny() < yearTiny) {
557  return rule.toYearTiny() + LocalDate::kEpochYear;
558  }
559  if (rule.fromYearTiny() < yearTiny) {
560  return year - 1;
561  }
562  return 0;
563  }
564 
566  void addRulesForYear(int16_t year) const {
567  const basic::ZoneEraBroker era = findZoneEra(year);
568 
569  // If the ZonePolicy has no rules, then add a Transition which takes
570  // effect at the start time of the current year.
571  const basic::ZonePolicyBroker zonePolicy = era.zonePolicy();
572  if (zonePolicy.isNull()) {
573  addRule(year, era, basic::ZoneRuleBroker());
574  return;
575  }
576 
577  // If the ZonePolicy has rules, find all matching transitions, and add
578  // them to mTransitions, in sorted order according to the
579  // ZoneRule::inMonth field.
580  int8_t yearTiny = year - LocalDate::kEpochYear;
581  uint8_t numRules = zonePolicy.numRules();
582  for (uint8_t i = 0; i < numRules; i++) {
583  const basic::ZoneRuleBroker rule = zonePolicy.rule(i);
584  if ((rule.fromYearTiny() <= yearTiny) &&
585  (yearTiny <= rule.toYearTiny())) {
586  addRule(year, era, rule);
587  }
588  }
589  }
590 
605  void addRule(int16_t year, basic::ZoneEraBroker era,
606  basic::ZoneRuleBroker rule) const {
607 
608  // If a zone needs more transitions than kMaxCacheEntries, the check below
609  // will cause the DST transition information to be inaccurate, and it is
610  // highly likely that this situation would be caught in the
611  // BasicValidationUsingPython or BasicValidationUsingJava unit tests.
612  // Since these unit tests pass, I feel confident that those zones which
613  // need more than kMaxCacheEntries are already filtered out by
614  // tzcompiler.py.
615  //
616  // Ideally, the tzcompiler.py script would explicitly remove those zones
617  // which need more than kMaxCacheEntries Transitions. But this would
618  // require a Python version of the BasicZoneProcessor, and unfortunately,
619  // zone_specifier.py implements only the ExtendedZoneProcessor algorithm
620  // An early version of zone_specifier.py may have implemented something
621  // close to BasicZoneProcessor, and it may be available in the git
622  // history. But it seems like too much work right now to try to dig that
623  // out, just to implement the explicit check for kMaxCacheEntries. It
624  // would mean maintaining another version of zone_specifier.py.
625  if (mNumTransitions >= kMaxCacheEntries) return;
626 
627  // insert new element at the end of the list
628  int8_t yearTiny = year - LocalDate::kEpochYear;
629  mTransitions[mNumTransitions] = createTransition(era, rule, yearTiny);
630  mNumTransitions++;
631 
632  // perform an insertion sort
633  for (uint8_t i = mNumTransitions - 1; i > 0; i--) {
634  basic::Transition& left = mTransitions[i - 1];
635  basic::Transition& right = mTransitions[i];
636  // assume only 1 rule per month
637  if ((left.rule.isNotNull() && right.rule.isNotNull() &&
638  left.rule.inMonth() > right.rule.inMonth())
639  || (left.rule.isNotNull() && right.rule.isNull())) {
640  basic::Transition tmp = left;
641  left = right;
642  right = tmp;
643  }
644  }
645  }
646 
652  const basic::ZoneEraBroker findZoneEra(int16_t year) const {
653  for (uint8_t i = 0; i < mZoneInfo.numEras(); i++) {
654  const basic::ZoneEraBroker era = mZoneInfo.era(i);
655  if (year < era.untilYearTiny() + LocalDate::kEpochYear) return era;
656  }
657  // Return the last ZoneEra if we run off the end.
658  return mZoneInfo.era(mZoneInfo.numEras() - 1);
659  }
660 
672  const basic::ZoneEraBroker findZoneEraPriorTo(int16_t year) const {
673  for (uint8_t i = 0; i < mZoneInfo.numEras(); i++) {
674  const basic::ZoneEraBroker era = mZoneInfo.era(i);
675  if (year <= era.untilYearTiny() + LocalDate::kEpochYear) return era;
676  }
677  // Return the last ZoneEra if we run off the end.
678  return mZoneInfo.era(mZoneInfo.numEras() - 1);
679  }
680 
688  void calcTransitions() const {
689  // Set the initial startEpochSeconds to be -Infinity
690  mPrevTransition.startEpochSeconds = kMinEpochSeconds;
691  const basic::Transition* prevTransition = &mPrevTransition;
692 
693  for (uint8_t i = 0; i < mNumTransitions; i++) {
694  basic::Transition& transition = mTransitions[i];
695  const int16_t year = transition.yearTiny + LocalDate::kEpochYear;
696 
697  if (transition.rule.isNull()) {
698  // If the transition is simple (has no named rule), then the
699  // ZoneEra applies for the entire year (since BasicZoneProcessor
700  // supports only whole year in the UNTIL field). The whole year UNTIL
701  // field has an implied 'w' modifier on 00:00, we don't need to call
702  // calcRuleOffsetCode() with a 'w', we can just use the previous
703  // transition's offset to calculate the startDateTime of this
704  // transition.
705  //
706  // Also, when transition.rule == nullptr, the mNumTransitions should
707  // be 1, since only a single transition is added by
708  // addRulesForYear().
709  const int8_t prevOffsetCode = prevTransition->offsetCode;
711  year, 1, 1, 0, 0, 0,
712  TimeOffset::forOffsetCode(prevOffsetCode));
713  transition.startEpochSeconds = startDateTime.toEpochSeconds();
714  } else {
715  // In this case, the transition points to a named ZonePolicy, which
716  // means that there could be multiple ZoneRules associated with the
717  // given year. For each transition, determine the startEpochSeconds,
718  // and the effective offset code.
719 
720  // Determine the start date of the rule.
721  const basic::MonthDay monthDay = calcStartDayOfMonth(
722  year, transition.rule.inMonth(), transition.rule.onDayOfWeek(),
723  transition.rule.onDayOfMonth());
724 
725  // Determine the offset of the 'atTimeModifier'. The 'w' modifier
726  // requires the offset of the previous transition.
727  const int8_t prevOffsetCode = calcRuleOffsetCode(
728  prevTransition->offsetCode,
729  transition.era.offsetCode(),
730  transition.rule.atTimeModifier());
731 
732  // startDateTime
733  const uint8_t timeCode = transition.rule.atTimeCode();
734  const uint8_t atHour = timeCode / 4;
735  const uint8_t atMinute = (timeCode % 4) * 15;
737  year, monthDay.month, monthDay.day,
738  atHour, atMinute, 0 /*second*/,
739  TimeOffset::forOffsetCode(prevOffsetCode));
740  transition.startEpochSeconds = startDateTime.toEpochSeconds();
741  }
742 
743  prevTransition = &transition;
744  }
745  }
746 
753  static int8_t calcRuleOffsetCode(int8_t prevEffectiveOffsetCode,
754  int8_t currentBaseOffsetCode, uint8_t atModifier) {
755  if (atModifier == 'w') {
756  return prevEffectiveOffsetCode;
757  } else if (atModifier == 's') {
758  return currentBaseOffsetCode;
759  } else { // 'u', 'g' or 'z'
760  return 0;
761  }
762  }
763 
765  void calcAbbreviations() const {
766  calcAbbreviation(&mPrevTransition);
767  for (uint8_t i = 0; i < mNumTransitions; i++) {
768  calcAbbreviation(&mTransitions[i]);
769  }
770  }
771 
773  static void calcAbbreviation(basic::Transition* transition) {
774  createAbbreviation(
775  transition->abbrev,
777  transition->era.format(),
778  transition->deltaCode,
779  transition->abbrev[0]);
780  }
781 
817  static void createAbbreviation(char* dest, uint8_t destSize,
818  const char* format, uint8_t deltaCode, char letter) {
819  // Check if RULES column empty.
820  if (deltaCode == 0 && letter == '\0') {
821  strncpy(dest, format, destSize);
822  dest[destSize - 1] = '\0';
823  return;
824  }
825 
826  // Check if FORMAT contains a '%'.
827  if (strchr(format, '%') != nullptr) {
828  copyAndReplace(dest, destSize, format, '%', letter);
829  } else {
830  // Check if FORMAT contains a '/'.
831  const char* slashPos = strchr(format, '/');
832  if (slashPos != nullptr) {
833  if (deltaCode == 0) {
834  uint8_t headLength = (slashPos - format);
835  if (headLength >= destSize) headLength = destSize - 1;
836  memcpy(dest, format, headLength);
837  dest[headLength] = '\0';
838  } else {
839  uint8_t tailLength = strlen(slashPos+1);
840  if (tailLength >= destSize) tailLength = destSize - 1;
841  memcpy(dest, slashPos+1, tailLength);
842  dest[tailLength] = '\0';
843  }
844  } else {
845  // Just copy the FORMAT disregarding the deltaCode and letter.
846  strncpy(dest, format, destSize);
847  dest[destSize - 1] = '\0';
848  }
849  }
850  }
851 
857  static void copyAndReplace(char* dst, uint8_t dstSize, const char* src,
858  char oldChar, char newChar) {
859  while (*src != '\0' && dstSize > 0) {
860  if (*src == oldChar) {
861  if (newChar == '-') {
862  src++;
863  } else {
864  *dst = newChar;
865  dst++;
866  src++;
867  dstSize--;
868  }
869  } else {
870  *dst++ = *src++;
871  dstSize--;
872  }
873  }
874 
875  if (dstSize == 0) {
876  --dst;
877  }
878  *dst = '\0';
879  }
880 
882  const basic::Transition* findMatch(acetime_t epochSeconds) const {
883  const basic::Transition* closestMatch = &mPrevTransition;
884  for (uint8_t i = 0; i < mNumTransitions; i++) {
885  const basic::Transition* m = &mTransitions[i];
886  if (m->startEpochSeconds <= epochSeconds) {
887  closestMatch = m;
888  }
889  }
890  return closestMatch;
891  }
892 
893  basic::ZoneInfoBroker mZoneInfo;
894 
895  mutable int16_t mYear = 0; // maybe create LocalDate::kInvalidYear?
896  mutable bool mIsFilled = false;
897  mutable uint8_t mNumTransitions = 0;
898  mutable basic::Transition mTransitions[kMaxCacheEntries];
899  mutable basic::Transition mPrevTransition; // previous year's transition
900 };
901 
902 }
903 
904 #endif
Base interface for ZoneProcessor classes.
Definition: ZoneProcessor.h:45
+
ZoneEraBroker era
The ZoneEra that matched the given year.
+
const char * getAbbrev(acetime_t epochSeconds) const override
Return the time zone abbreviation at epochSeconds.
-
void log() const
Used only for debugging.
-
A cache of ZoneProcessors that provides a ZoneProcessor to the TimeZone upon request.
-
static const uint8_t kAbbrevSize
Longest abbreviation currently seems to be 5 characters (https://www.timeanddate.com/time/zones/) but...
-
char abbrev[kAbbrevSize]
The calculated effective time zone abbreviation, e.g.
+
void log() const
Used only for debugging.
+
A cache of ZoneProcessors that provides a ZoneProcessor to the TimeZone upon request.
+
static const uint8_t kAbbrevSize
Longest abbreviation currently seems to be 5 characters (https://www.timeanddate.com/time/zones/) but...
+
char abbrev[kAbbrevSize]
The calculated effective time zone abbreviation, e.g.
static OffsetDateTime forLocalDateTimeAndOffset(const LocalDateTime &localDateTime, TimeOffset timeOffset)
Factory method from LocalDateTime and TimeOffset.
-
BasicZoneProcessor(const basic::ZoneInfo *zoneInfo=nullptr)
Constructor.
+
BasicZoneProcessor(const basic::ZoneInfo *zoneInfo=nullptr)
Constructor.
Representation of a given time zone, implemented as an array of ZoneEra records.
Definition: ZoneInfo.h:77
-
The result of calcStartDayOfMonth().
+
The result of calcStartDayOfMonth().
virtual const void * getZoneInfo() const =0
Return the opaque zoneInfo.
-
uint32_t getZoneId() const override
Return the unique stable zoneId.
+
uint32_t getZoneId() const override
Return the unique stable zoneId.
static OffsetDateTime forComponents(int16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second, TimeOffset timeOffset)
Factory method using separated date, time, and UTC offset fields.
-
acetime_t startEpochSeconds
The calculated transition time of the given rule.
+
acetime_t startEpochSeconds
The calculated transition time of the given rule.
uint8_t day() const
Return the day of the month.
Definition: LocalDate.h:237
-
int8_t yearTiny
Year which applies to the ZoneEra or ZoneRule.
+
int8_t yearTiny
Year which applies to the ZoneEra or ZoneRule.
static OffsetDateTime forEpochSeconds(acetime_t epochSeconds, TimeOffset timeOffset)
Factory method.
-
ZoneRuleBroker rule
The Zone transition rule that matched for the the given year.
+
ZoneRuleBroker rule
The Zone transition rule that matched for the the given year.
acetime_t toEpochSeconds() const
Return seconds since AceTime epoch 2000-01-01 00:00:00Z, after assuming that the date and time compon...
const LocalDate & localDate() const
Return the LocalDate.
-
TimeOffset getDeltaOffset(acetime_t epochSeconds) const override
Return the DST delta offset at epochSeconds.
-
const void * getZoneInfo() const override
Return the underlying ZoneInfo.
-
void log() const
Used only for debugging.
+
TimeOffset getDeltaOffset(acetime_t epochSeconds) const override
Return the DST delta offset at epochSeconds.
+
const void * getZoneInfo() const override
Return the underlying ZoneInfo.
+
void log() const
Used only for debugging.
static const int8_t kErrorCode
Sentinel value that represents an error.
Definition: TimeOffset.h:61
-
An implementation of ZoneProcessor that supports a subset of the zones containing in the TZ Database...
+
An implementation of ZoneProcessor that supports a subset of the zones containing in the TZ Database...
static LocalDate forEpochSeconds(acetime_t epochSeconds)
Factory method using the number of seconds since AceTime epoch of 2000-01-01.
Definition: LocalDate.h:145
static TimeOffset forOffsetCode(int8_t offsetCode)
Create TimeOffset from the offset code.
Definition: TimeOffset.h:117
The classes provide a thin layer of indirection for accessing the zoneinfo files stored in the zonedb...
The date (year, month, day), time (hour, minute, second) and offset from UTC (timeOffset).
-
Data structure that defines the start of a specific UTC offset as described by the matching ZoneEra a...
-
OffsetDateTime getOffsetDateTime(const LocalDateTime &ldt) const override
+
Data structure that defines the start of a specific UTC offset as described by the matching ZoneEra a...
+
OffsetDateTime getOffsetDateTime(const LocalDateTime &ldt) const override
acetime_t toEpochSeconds() const
Return seconds since AceTime epoch (2000-01-01 00:00:00Z), taking into account the offset zone...
A thin wrapper that represents a time offset from a reference point, usually 00:00 at UTC...
Definition: TimeOffset.h:58
static LocalDate forComponents(int16_t year, uint8_t month, uint8_t day)
Factory method using separated year, month and day fields.
Definition: LocalDate.h:94
The date (year, month, day) representing the date without regards to time zone.
Definition: LocalDate.h:36
static const int16_t kEpochYear
Base year of epoch.
Definition: LocalDate.h:39
-
int8_t offsetCode
The total effective UTC offsetCode at the start of transition, including DST offset.
+
int8_t offsetCode
The total effective UTC offsetCode at the start of transition, including DST offset.
static uint8_t daysInMonth(int16_t year, uint8_t month)
Return the number of days in the current month.
Definition: LocalDate.h:210
-
int8_t deltaCode
The delta offsetCode from "standard time" at the start of transition.
+
int8_t deltaCode
The delta offsetCode from "standard time" at the start of transition.
uint8_t month() const
Return the month with January=1, December=12.
Definition: LocalDate.h:231
int16_t year() const
Return the full year instead of just the last 2 digits.
Definition: LocalDate.h:219
-
static basic::MonthDay calcStartDayOfMonth(int16_t year, uint8_t month, uint8_t onDayOfWeek, int8_t onDayOfMonth)
Calculate the actual (month, day) of the expresssion (onDayOfWeek >= onDayOfMonth) or (onDayOfWeek <=...
+
static basic::MonthDay calcStartDayOfMonth(int16_t year, uint8_t month, uint8_t onDayOfWeek, int8_t onDayOfMonth)
Calculate the actual (month, day) of the expresssion (onDayOfWeek >= onDayOfMonth) or (onDayOfWeek <=...
Data broker for accessing ZonePolicy in PROGMEM.
Definition: Brokers.h:297
static OffsetDateTime forError()
Factory method that returns an instance whose isError() is true.
Class that holds the date-time as the components (year, month, day, hour, minute, second) without reg...
Definition: LocalDateTime.h:27
-
TimeOffset getUtcOffset(acetime_t epochSeconds) const override
Return the total UTC offset at epochSeconds, including DST offset.
+
TimeOffset getUtcOffset(acetime_t epochSeconds) const override
Return the total UTC offset at epochSeconds, including DST offset.
diff --git a/docs/html/BasicZone_8h_source.html b/docs/html/BasicZone_8h_source.html index 8290cf293..9690c09b8 100644 --- a/docs/html/BasicZone_8h_source.html +++ b/docs/html/BasicZone_8h_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/Brokers_8h.html b/docs/html/Brokers_8h.html index dc1d5e287..ffddf2980 100644 --- a/docs/html/Brokers_8h.html +++ b/docs/html/Brokers_8h.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/Brokers_8h_source.html b/docs/html/Brokers_8h_source.html index db0d82504..979e8d42a 100644 --- a/docs/html/Brokers_8h_source.html +++ b/docs/html/Brokers_8h_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/CrcEeprom_8h_source.html b/docs/html/CrcEeprom_8h_source.html index b7133e0e3..3eb43ab47 100644 --- a/docs/html/CrcEeprom_8h_source.html +++ b/docs/html/CrcEeprom_8h_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/DS3231TimeKeeper_8h_source.html b/docs/html/DS3231TimeKeeper_8h_source.html index 1b9935a79..e83f20f6d 100644 --- a/docs/html/DS3231TimeKeeper_8h_source.html +++ b/docs/html/DS3231TimeKeeper_8h_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/DS3231_8cpp_source.html b/docs/html/DS3231_8cpp_source.html index fdf28a06b..735e76fcb 100644 --- a/docs/html/DS3231_8cpp_source.html +++ b/docs/html/DS3231_8cpp_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/DS3231_8h_source.html b/docs/html/DS3231_8h_source.html index 5addaf90b..4321f43d4 100644 --- a/docs/html/DS3231_8h_source.html +++ b/docs/html/DS3231_8h_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/DateStrings_8cpp_source.html b/docs/html/DateStrings_8cpp_source.html index 4dee37298..96cf375e8 100644 --- a/docs/html/DateStrings_8cpp_source.html +++ b/docs/html/DateStrings_8cpp_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/DateStrings_8h_source.html b/docs/html/DateStrings_8h_source.html index 031db4d40..bafbd65b6 100644 --- a/docs/html/DateStrings_8h_source.html +++ b/docs/html/DateStrings_8h_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/ExtendedZoneProcessor_8cpp_source.html b/docs/html/ExtendedZoneProcessor_8cpp_source.html index 17680f301..ccb42f623 100644 --- a/docs/html/ExtendedZoneProcessor_8cpp_source.html +++ b/docs/html/ExtendedZoneProcessor_8cpp_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
@@ -68,7 +68,7 @@
ExtendedZoneProcessor.cpp
-
1 /*
2  * MIT License
3  * Copyright (c) 2019 Brian T. Park
4  */
5 
6 #include <Print.h>
7 #include "LocalDate.h"
8 #include "ExtendedZone.h"
9 #include "ExtendedZoneProcessor.h"
10 
11 namespace ace_time {
12 
13 const extended::ZoneEra ExtendedZoneProcessor::kAnchorEra ACE_TIME_PROGMEM = {
14  0 /*offsetCode*/,
15  nullptr /*zonePolicy*/,
16  0 /*deltaCode*/,
17  nullptr /*format*/,
18  LocalDate::kInvalidYearTiny /*untilYearTiny*/,
19  1 /*untilMonth*/,
20  1 /*untilDay*/,
21  0 /*untilTimeCode*/,
22  'w' /*untilTimeModifier*/
23 };
24 
25 void ExtendedZoneProcessor::printTo(Print& printer) const {
26  printer.print(ExtendedZone(mZoneInfo.zoneInfo()).name());
27 }
28 
29 void ExtendedZoneProcessor::printShortTo(Print& printer) const {
30  printer.print(ExtendedZone(mZoneInfo.zoneInfo()).shortName());
31 }
32 
33 }
+
1 /*
2  * MIT License
3  * Copyright (c) 2019 Brian T. Park
4  */
5 
6 #include <Print.h>
7 #include "LocalDate.h"
8 #include "ExtendedZone.h"
9 #include "ExtendedZoneProcessor.h"
10 
11 namespace ace_time {
12 
13 const extended::ZoneEra ExtendedZoneProcessor::kAnchorEra ACE_TIME_PROGMEM = {
14  nullptr /*zonePolicy*/,
15  nullptr /*format*/,
16  0 /*offsetCode*/,
17  0 /*deltaCode*/,
18  LocalDate::kInvalidYearTiny /*untilYearTiny*/,
19  1 /*untilMonth*/,
20  1 /*untilDay*/,
21  0 /*untilTimeCode*/,
22  'w' /*untilTimeModifier*/
23 };
24 
25 void ExtendedZoneProcessor::printTo(Print& printer) const {
26  printer.print(ExtendedZone(mZoneInfo.zoneInfo()).name());
27 }
28 
29 void ExtendedZoneProcessor::printShortTo(Print& printer) const {
30  printer.print(ExtendedZone(mZoneInfo.zoneInfo()).shortName());
31 }
32 
33 }
void printTo(Print &printer) const override
Print a human-readable identifier (e.g.
void printShortTo(Print &printer) const override
Print a short human-readable identifier (e.g.
A thin wrapper around an extended::ZoneInfo data structure to provide a stable API access to some use...
Definition: ExtendedZone.h:23
diff --git a/docs/html/ExtendedZoneProcessor_8h_source.html b/docs/html/ExtendedZoneProcessor_8h_source.html index cb1622a30..d22497770 100644 --- a/docs/html/ExtendedZoneProcessor_8h_source.html +++ b/docs/html/ExtendedZoneProcessor_8h_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
@@ -68,96 +68,96 @@
ExtendedZoneProcessor.h
-
1 /*
2  * MIT License
3  * Copyright (c) 2019 Brian T. Park
4  */
5 
6 #ifndef ACE_TIME_EXTENDED_ZONE_PROCESSOR_H
7 #define ACE_TIME_EXTENDED_ZONE_PROCESSOR_H
8 
9 #include <string.h> // memcpy()
10 #include <stdint.h>
11 #include "common/compat.h"
12 #include "internal/ZonePolicy.h"
13 #include "internal/ZoneInfo.h"
14 #include "common/logging.h"
15 #include "TimeOffset.h"
16 #include "LocalDate.h"
17 #include "OffsetDateTime.h"
18 #include "ZoneProcessor.h"
19 #include "BasicZoneProcessor.h"
20 #include "local_date_mutation.h"
21 
22 #define DEBUG 0
23 
24 class ExtendedZoneProcessorTest_compareEraToYearMonth;
25 class ExtendedZoneProcessorTest_compareEraToYearMonth2;
26 class ExtendedZoneProcessorTest_createMatch;
27 class ExtendedZoneProcessorTest_findMatches_simple;
28 class ExtendedZoneProcessorTest_findMatches_named;
29 class ExtendedZoneProcessorTest_findCandidateTransitions;
30 class ExtendedZoneProcessorTest_findTransitionsFromNamedMatch;
31 class ExtendedZoneProcessorTest_getTransitionTime;
32 class ExtendedZoneProcessorTest_createTransitionForYear;
33 class ExtendedZoneProcessorTest_normalizeDateTuple;
34 class ExtendedZoneProcessorTest_expandDateTuple;
35 class ExtendedZoneProcessorTest_calcInteriorYears;
36 class ExtendedZoneProcessorTest_getMostRecentPriorYear;
37 class ExtendedZoneProcessorTest_compareTransitionToMatchFuzzy;
38 class ExtendedZoneProcessorTest_compareTransitionToMatch;
39 class ExtendedZoneProcessorTest_processActiveTransition;
40 class ExtendedZoneProcessorTest_fixTransitionTimes_generateStartUntilTimes;
41 class ExtendedZoneProcessorTest_createAbbreviation;
42 class ExtendedZoneProcessorTest_setZoneInfo;
43 class TransitionStorageTest_getFreeAgent;
44 class TransitionStorageTest_getFreeAgent2;
45 class TransitionStorageTest_addFreeAgentToActivePool;
46 class TransitionStorageTest_reservePrior;
47 class TransitionStorageTest_addFreeAgentToCandidatePool;
48 class TransitionStorageTest_setFreeAgentAsPrior;
49 class TransitionStorageTest_addActiveCandidatesToActivePool;
50 class TransitionStorageTest_resetCandidatePool;
51 
52 namespace ace_time {
53 
54 template<uint8_t SIZE, uint8_t TYPE, typename ZS, typename ZI, typename ZIB>
55 class ZoneProcessorCacheImpl;
56 
57 namespace extended {
58 
59 // NOTE: Consider compressing 'modifier' into 'month' field
64 struct DateTuple {
65  int8_t yearTiny; // [-127, 126], 127 will cause bugs
66  uint8_t month; // [1-12]
67  uint8_t day; // [1-31]
68  int8_t timeCode; // 15-min intervals, negative values allowed
69  uint8_t modifier; // 's', 'w', 'u'
70 
72  void log() const {
73  logging::print("DateTuple(%d-%u-%uT%d'%c')",
74  yearTiny+LocalDate::kEpochYear, month, day, timeCode, modifier);
75  }
76 };
77 
79 inline bool operator<(const DateTuple& a, const DateTuple& b) {
80  if (a.yearTiny < b.yearTiny) return true;
81  if (a.yearTiny > b.yearTiny) return false;
82  if (a.month < b.month) return true;
83  if (a.month > b.month) return false;
84  if (a.day < b.day) return true;
85  if (a.day > b.day) return false;
86  if (a.timeCode < b.timeCode) return true;
87  if (a.timeCode > b.timeCode) return false;
88  return false;
89 }
90 
91 inline bool operator>=(const DateTuple& a, const DateTuple& b) {
92  return ! (a < b);
93 }
94 
95 inline bool operator<=(const DateTuple& a, const DateTuple& b) {
96  return ! (b < a);
97 }
98 
99 inline bool operator>(const DateTuple& a, const DateTuple& b) {
100  return (b < a);
101 }
102 
104 inline bool operator==(const DateTuple& a, const DateTuple& b) {
105  return a.yearTiny == b.yearTiny
106  && a.month == b.month
107  && a.day == b.day
108  && a.timeCode == b.timeCode
109  && a.modifier == b.modifier;
110 }
111 
114  int8_t yearTiny;
115  uint8_t month;
116 };
117 
122 struct ZoneMatch {
125 
128 
131 
132  void log() const {
133  logging::print("ZoneMatch(");
134  logging::print("Start:"); startDateTime.log();
135  logging::print("; Until:"); untilDateTime.log();
136  logging::print("; Era: %snull", (era.isNotNull()) ? "!" : "");
137  logging::print(")");
138  }
139 };
140 
163 struct Transition {
165  static const uint8_t kAbbrevSize = basic::Transition::kAbbrevSize;
166 
168  const ZoneMatch* match;
169 
176 
184 
185  union {
192 
198  };
199 
200  union {
207 
213  };
214 
220 
222  char abbrev[kAbbrevSize];
223 
225  char letterBuf[2];
226 
228  acetime_t startEpochSeconds;
229 
242  bool active;
243 
251  int8_t offsetCode;
252 
254  int8_t deltaCode;
255 
256  //-------------------------------------------------------------------------
257 
258  const char* format() const {
259  return match->era.format();
260  }
261 
267  const char* letter() const {
268  // RULES column is '-' or hh:mm, so return nullptr to indicate this.
269  if (rule.isNull()) {
270  return nullptr;
271  }
272 
273  // RULES point to a named rule, and LETTER is a single, printable
274  // character.
275  char letter = rule.letter();
276  if (letter >= 32) {
277  return letterBuf;
278  }
279 
280  // RULES points to a named rule, and the LETTER is a string. The
281  // rule->letter is a non-printable number < 32, which is an index into
282  // a list of strings given by match->era->zonePolicy->letters[].
283  const ZonePolicyBroker policy = match->era.zonePolicy();
284  uint8_t numLetters = policy.numLetters();
285  if (letter >= numLetters) {
286  // This should never happen unless there is a programming error. If it
287  // does, return an empty string. (createTransitionForYear() sets
288  // letterBuf to a NUL terminated empty string if rule->letter < 32)
289  return letterBuf;
290  }
291 
292  // Return the string at index 'rule->letter'.
293  return policy.letter(letter);
294  }
295 
297  void log() const {
298  logging::print("Transition(");
299  if (sizeof(acetime_t) == sizeof(int)) {
300  logging::print("sE: %d", startEpochSeconds);
301  } else {
302  logging::print("sE: %ld", startEpochSeconds);
303  }
304  logging::print("; match: %snull", (match) ? "!" : "");
305  logging::print("; era: %snull",
306  (match && match->era.isNotNull()) ? "!" : "");
307  logging::print("; oCode: %d", offsetCode);
308  logging::print("; dCode: %d", deltaCode);
309  logging::print("; tt: "); transitionTime.log();
310  if (rule.isNotNull()) {
311  logging::print("; R.fY: %d", rule.fromYearTiny());
312  logging::print("; R.tY: %d", rule.toYearTiny());
313  logging::print("; R.M: %d", rule.inMonth());
314  logging::print("; R.dow: %d", rule.onDayOfWeek());
315  logging::print("; R.dom: %d", rule.onDayOfMonth());
316  }
317  }
318 };
319 
346 template<uint8_t SIZE>
348  public:
351 
353  void init() {
354  for (uint8_t i = 0; i < SIZE; i++) {
355  mTransitions[i] = &mPool[i];
356  }
357  mIndexPrior = 0;
358  mIndexCandidates = 0;
359  mIndexFree = 0;
360  }
361 
363  Transition* getPrior() { return mTransitions[mIndexPrior]; }
364 
366  void swap(Transition** a, Transition** b) {
367  Transition* tmp = *a;
368  *a = *b;
369  *b = tmp;
370  }
371 
381  mIndexCandidates = mIndexPrior;
382  mIndexFree = mIndexPrior;
383  }
384 
385  Transition** getCandidatePoolBegin() {
386  return &mTransitions[mIndexCandidates];
387  }
388  Transition** getCandidatePoolEnd() {
389  return &mTransitions[mIndexFree];
390  }
391 
392  Transition** getActivePoolBegin() { return &mTransitions[0]; }
393  Transition** getActivePoolEnd() { return &mTransitions[mIndexFree]; }
394 
401  // Set the internal high water mark. If that index becomes SIZE,
402  // then we know we have an overflow.
403  if (mIndexFree > mHighWater) {
404  mHighWater = mIndexFree;
405  }
406 
407  if (mIndexFree < SIZE) {
408  return mTransitions[mIndexFree];
409  } else {
410  return mTransitions[SIZE - 1];
411  }
412  }
413 
422  if (mIndexFree >= SIZE) return;
423  mIndexFree++;
424  mIndexPrior = mIndexFree;
425  mIndexCandidates = mIndexFree;
426  }
427 
434  mIndexCandidates++;
435  mIndexFree++;
436  return &mTransitions[mIndexPrior];
437  }
438 
441  swap(&mTransitions[mIndexPrior], &mTransitions[mIndexFree]);
442  }
443 
450  mIndexCandidates--;
451  }
452 
460  if (mIndexFree >= SIZE) return;
461  for (uint8_t i = mIndexFree; i > mIndexCandidates; i--) {
462  Transition* curr = mTransitions[i];
463  Transition* prev = mTransitions[i - 1];
464  if (curr->transitionTime >= prev->transitionTime) break;
465  mTransitions[i] = prev;
466  mTransitions[i - 1] = curr;
467  }
468  mIndexFree++;
469  }
470 
476  if (DEBUG) logging::println("addActiveCandidatesToActivePool()");
477  uint8_t iActive = mIndexPrior;
478  uint8_t iCandidate = mIndexCandidates;
479  for (; iCandidate < mIndexFree; iCandidate++) {
480  if (mTransitions[iCandidate]->active) {
481  if (iActive != iCandidate) {
482  swap(&mTransitions[iActive], &mTransitions[iCandidate]);
483  }
484  ++iActive;
485  }
486  }
487  mIndexPrior = iActive;
488  mIndexCandidates = iActive;
489  mIndexFree = iActive;
490  }
491 
500  const Transition* findTransition(acetime_t epochSeconds) const {
501  if (DEBUG) logging::println(
502  "findTransition(): mIndexFree: %d", mIndexFree);
503 
504  const Transition* match = nullptr;
505  for (uint8_t i = 0; i < mIndexFree; i++) {
506  const Transition* candidate = mTransitions[i];
507  if (candidate->startEpochSeconds > epochSeconds) break;
508  match = candidate;
509  }
510  return match;
511  }
512 
537  const {
538  if (DEBUG) logging::println(
539  "findTransitionForDateTime(): mIndexFree: %d", mIndexFree);
540 
541  // Convert to DateTuple. If the localDateTime is not a multiple of 15
542  // minutes, the comparision (startTime < localDate) will still be valid.
543  DateTuple localDate = { ldt.yearTiny(), ldt.month(), ldt.day(),
544  (int8_t) (ldt.hour() * 4 + ldt.minute() / 15), 'w' };
545  const Transition* match = nullptr;
546  for (uint8_t i = 0; i < mIndexFree; i++) {
547  const Transition* candidate = mTransitions[i];
548  if (candidate->startDateTime > localDate) break;
549  match = candidate;
550  }
551  return match;
552  }
553 
555  void log() const {
556  logging::println("TransitionStorage:");
557  logging::println(" mIndexPrior: %d", mIndexPrior);
558  logging::println(" mIndexCandidates: %d", mIndexCandidates);
559  logging::println(" mIndexFree: %d", mIndexFree);
560  if (mIndexPrior != 0) {
561  logging::println(" Actives:");
562  for (uint8_t i = 0; i < mIndexPrior; i++) {
563  mTransitions[i]->log();
564  logging::println();
565  }
566  }
567  if (mIndexPrior != mIndexCandidates) {
568  logging::print(" Prior: ");
569  mTransitions[mIndexPrior]->log();
570  logging::println();
571  }
572  if (mIndexCandidates != mIndexFree) {
573  logging::println(" Candidates:");
574  for (uint8_t i = mIndexCandidates; i < mIndexFree; i++) {
575  mTransitions[i]->log();
576  logging::println();
577  }
578  }
579  }
580 
582  void resetHighWater() { mHighWater = 0; }
583 
589  uint8_t getHighWater() const { return mHighWater; }
590 
591  private:
592  friend class ::TransitionStorageTest_getFreeAgent;
593  friend class ::TransitionStorageTest_getFreeAgent2;
594  friend class ::TransitionStorageTest_addFreeAgentToActivePool;
595  friend class ::TransitionStorageTest_reservePrior;
596  friend class ::TransitionStorageTest_addFreeAgentToCandidatePool;
597  friend class ::TransitionStorageTest_setFreeAgentAsPrior;
598  friend class ::TransitionStorageTest_addActiveCandidatesToActivePool;
599  friend class ::TransitionStorageTest_resetCandidatePool;
600 
602  Transition* getTransition(uint8_t i) { return mTransitions[i]; }
603 
604  Transition mPool[SIZE];
605  Transition* mTransitions[SIZE];
606  uint8_t mIndexPrior;
607  uint8_t mIndexCandidates;
608  uint8_t mIndexFree;
609 
611  uint8_t mHighWater = 0;
612 };
613 
614 } // namespace extended
615 
645  public:
651  const extended::ZoneInfo* zoneInfo = nullptr):
652  ZoneProcessor(kTypeExtended),
653  mZoneInfo(zoneInfo) {}
654 
656  const void* getZoneInfo() const override {
657  return mZoneInfo.zoneInfo();
658  }
659 
660  uint32_t getZoneId() const override { return mZoneInfo.zoneId(); }
661 
662  TimeOffset getUtcOffset(acetime_t epochSeconds) const override {
663  bool success = init(epochSeconds);
664  if (!success) return TimeOffset::forError();
665  const extended::Transition* transition = findTransition(epochSeconds);
666  return (transition)
668  transition->offsetCode + transition->deltaCode)
670  }
671 
672  TimeOffset getDeltaOffset(acetime_t epochSeconds) const override {
673  bool success = init(epochSeconds);
674  if (!success) return TimeOffset::forError();
675  const extended::Transition* transition = findTransition(epochSeconds);
676  return TimeOffset::forOffsetCode(transition->deltaCode);
677  }
678 
679  const char* getAbbrev(acetime_t epochSeconds) const override {
680  bool success = init(epochSeconds);
681  if (!success) return "";
682  const extended::Transition* transition = findTransition(epochSeconds);
683  return transition->abbrev;
684  }
685 
686  OffsetDateTime getOffsetDateTime(const LocalDateTime& ldt) const override {
687  TimeOffset offset;
688  bool success = init(ldt.localDate());
689  if (success) {
690  const extended::Transition* transition =
691  mTransitionStorage.findTransitionForDateTime(ldt);
692  offset = (transition)
694  transition->offsetCode + transition->deltaCode)
696  } else {
697  offset = TimeOffset::forError();
698  }
699 
700  auto odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset);
701  if (offset.isError()) {
702  return odt;
703  }
704 
705  // Normalize the OffsetDateTime, causing LocalDateTime in the DST
706  // transtion gap to be shifted forward one hour. For LocalDateTime in an
707  // overlap (DST->STD transition), the earlier UTC offset is selected// by
708  // findTransitionForDateTime(). Use that to calculate the epochSeconds,
709  // then recalculate the offset. Use this final offset to determine the
710  // effective OffsetDateTime that will survive a round-trip unchanged.
711  acetime_t epochSeconds = odt.toEpochSeconds();
712  const extended::Transition* transition =
713  mTransitionStorage.findTransition(epochSeconds);
714  offset = (transition)
716  transition->offsetCode + transition->deltaCode)
718  odt = OffsetDateTime::forEpochSeconds(epochSeconds, offset);
719  return odt;
720  }
721 
722  void printTo(Print& printer) const override;
723 
724  void printShortTo(Print& printer) const override;
725 
727  void log() const {
728  logging::println("ExtendedZoneProcessor:");
729  logging::println(" mYear: %d", mYear);
730  logging::println(" mNumMatches: %d", mNumMatches);
731  for (int i = 0; i < mNumMatches; i++) {
732  logging::print(" Match %d: ", i);
733  mMatches[i].log();
734  logging::println();
735  }
736  mTransitionStorage.log();
737  }
738 
741  mTransitionStorage.resetHighWater();
742  }
743 
745  uint8_t getTransitionHighWater() const {
746  return mTransitionStorage.getHighWater();
747  }
748 
749  private:
750  friend class ::ExtendedZoneProcessorTest_compareEraToYearMonth;
751  friend class ::ExtendedZoneProcessorTest_compareEraToYearMonth2;
752  friend class ::ExtendedZoneProcessorTest_createMatch;
753  friend class ::ExtendedZoneProcessorTest_findMatches_simple;
754  friend class ::ExtendedZoneProcessorTest_findMatches_named;
755  friend class ::ExtendedZoneProcessorTest_findCandidateTransitions;
756  friend class ::ExtendedZoneProcessorTest_findTransitionsFromNamedMatch;
757  friend class ::ExtendedZoneProcessorTest_getTransitionTime;
758  friend class ::ExtendedZoneProcessorTest_createTransitionForYear;
759  friend class ::ExtendedZoneProcessorTest_normalizeDateTuple;
760  friend class ::ExtendedZoneProcessorTest_expandDateTuple;
761  friend class ::ExtendedZoneProcessorTest_calcInteriorYears;
762  friend class ::ExtendedZoneProcessorTest_getMostRecentPriorYear;
763  friend class ::ExtendedZoneProcessorTest_compareTransitionToMatchFuzzy;
764  friend class ::ExtendedZoneProcessorTest_compareTransitionToMatch;
765  friend class ::ExtendedZoneProcessorTest_processActiveTransition;
766  friend class ::ExtendedZoneProcessorTest_fixTransitionTimes_generateStartUntilTimes;
767  friend class ::ExtendedZoneProcessorTest_createAbbreviation;
768  friend class ::ExtendedZoneProcessorTest_setZoneInfo;
769 
770  template<uint8_t SIZE, uint8_t TYPE, typename ZS, typename ZI, typename ZIB>
771  friend class ZoneProcessorCacheImpl; // setZoneInfo()
772 
773  // Disable copy constructor and assignment operator.
775  ExtendedZoneProcessor& operator=(const ExtendedZoneProcessor&) = delete;
776 
781  static const uint8_t kMaxMatches = 4;
782 
790  static const uint8_t kMaxTransitions = 8;
791 
796  static const uint8_t kMaxInteriorYears = 4;
797 
799  static const extended::ZoneEra kAnchorEra;
800 
801  bool equals(const ZoneProcessor& other) const override {
802  const auto& that = (const ExtendedZoneProcessor&) other;
803  return getZoneInfo() == that.getZoneInfo();
804  }
805 
807  void setZoneInfo(const void* zoneInfo) override {
808  if (mZoneInfo.zoneInfo() == zoneInfo) return;
809 
810  mZoneInfo = extended::ZoneInfoBroker(
811  (const extended::ZoneInfo*) zoneInfo);
812  mYear = 0;
813  mIsFilled = false;
814  mNumMatches = 0;
815  }
816 
821  const extended::Transition* findTransition(acetime_t epochSeconds) const {
822  return mTransitionStorage.findTransition(epochSeconds);
823  }
824 
826  bool init(acetime_t epochSeconds) const {
827  LocalDate ld = LocalDate::forEpochSeconds(epochSeconds);
828  return init(ld);
829  }
830 
835  bool init(const LocalDate& ld) const {
836  int16_t year = ld.year();
837  if (isFilled(year)) return true;
838  if (DEBUG) logging::println("init(): %d", year);
839 
840  mYear = year;
841  mNumMatches = 0; // clear cache
842  mTransitionStorage.init();
843 
844  if (year < mZoneInfo.startYear() - 1 || mZoneInfo.untilYear() < year) {
845  return false;
846  }
847 
848  extended::YearMonthTuple startYm = {
849  (int8_t) (year - LocalDate::kEpochYear - 1), 12 };
850  extended::YearMonthTuple untilYm = {
851  (int8_t) (year - LocalDate::kEpochYear + 1), 2 };
852 
853  mNumMatches = findMatches(mZoneInfo, startYm, untilYm, mMatches,
854  kMaxMatches);
855  if (DEBUG) log();
856  findTransitions(mTransitionStorage, mMatches, mNumMatches);
857  extended::Transition** begin = mTransitionStorage.getActivePoolBegin();
858  extended::Transition** end = mTransitionStorage.getActivePoolEnd();
859  fixTransitionTimes(begin, end);
860  generateStartUntilTimes(begin, end);
861  calcAbbreviations(begin, end);
862 
863  mIsFilled = true;
864  return true;
865  }
866 
868  bool isFilled(int16_t year) const {
869  return mIsFilled && (year == mYear);
870  }
871 
879  static uint8_t findMatches(const extended::ZoneInfoBroker zoneInfo,
880  const extended::YearMonthTuple& startYm,
881  const extended::YearMonthTuple& untilYm,
882  extended::ZoneMatch* matches, uint8_t maxMatches) {
883  if (DEBUG) logging::println("findMatches()");
884  uint8_t iMatch = 0;
886  for (uint8_t iEra = 0; iEra < zoneInfo.numEras(); iEra++) {
887  const extended::ZoneEraBroker era = zoneInfo.era(iEra);
888  if (eraOverlapsInterval(prev, era, startYm, untilYm)) {
889  if (iMatch < maxMatches) {
890  matches[iMatch] = createMatch(prev, era, startYm, untilYm);
891  iMatch++;
892  }
893  }
894  prev = era;
895  }
896  return iMatch;
897  }
898 
909  static bool eraOverlapsInterval(
910  const extended::ZoneEraBroker prev,
911  const extended::ZoneEraBroker era,
912  const extended::YearMonthTuple& startYm,
913  const extended::YearMonthTuple& untilYm) {
914  return compareEraToYearMonth(prev, untilYm.yearTiny, untilYm.month) < 0
915  && compareEraToYearMonth(era, startYm.yearTiny, startYm.month) > 0;
916  }
917 
919  static int8_t compareEraToYearMonth(const extended::ZoneEraBroker era,
920  int8_t yearTiny, uint8_t month) {
921  if (era.untilYearTiny() < yearTiny) return -1;
922  if (era.untilYearTiny() > yearTiny) return 1;
923  if (era.untilMonth() < month) return -1;
924  if (era.untilMonth() > month) return 1;
925  if (era.untilDay() > 1) return 1;
926  //if (era.untilTimeCode() < 0) return -1; // never possible
927  if (era.untilTimeCode() > 0) return 1;
928  return 0;
929  }
930 
937  static extended::ZoneMatch createMatch(
938  const extended::ZoneEraBroker prev,
939  const extended::ZoneEraBroker era,
940  const extended::YearMonthTuple& startYm,
941  const extended::YearMonthTuple& untilYm) {
942  extended::DateTuple startDate = {
943  prev.untilYearTiny(), prev.untilMonth(), prev.untilDay(),
944  (int8_t) prev.untilTimeCode(), prev.untilTimeModifier()
945  };
946  extended::DateTuple lowerBound = {
947  startYm.yearTiny, startYm.month, 1, 0, 'w'
948  };
949  if (startDate < lowerBound) {
950  startDate = lowerBound;
951  }
952 
953  extended::DateTuple untilDate = {
954  era.untilYearTiny(), era.untilMonth(), era.untilDay(),
955  (int8_t) era.untilTimeCode(), era.untilTimeModifier()
956  };
957  extended::DateTuple upperBound = {
958  untilYm.yearTiny, untilYm.month, 1, 0, 'w'
959  };
960  if (upperBound < untilDate) {
961  untilDate = upperBound;
962  }
963 
964  return {startDate, untilDate, era};
965  }
966 
971  static void findTransitions(
973  extended::ZoneMatch* matches,
974  uint8_t numMatches) {
975  if (DEBUG) logging::println("findTransitions()");
976  for (uint8_t i = 0; i < numMatches; i++) {
977  findTransitionsForMatch(transitionStorage, &matches[i]);
978  }
979  }
980 
982  static void findTransitionsForMatch(
984  const extended::ZoneMatch* match) {
985  if (DEBUG) logging::println("findTransitionsForMatch()");
986  const extended::ZonePolicyBroker policy = match->era.zonePolicy();
987  if (policy.isNull()) {
988  findTransitionsFromSimpleMatch(transitionStorage, match);
989  } else {
990  findTransitionsFromNamedMatch(transitionStorage, match);
991  }
992  }
993 
994  static void findTransitionsFromSimpleMatch(
996  const extended::ZoneMatch* match) {
997  if (DEBUG) logging::println("findTransitionsFromSimpleMatch()");
998  extended::Transition* freeTransition = transitionStorage.getFreeAgent();
999  createTransitionForYear(freeTransition, 0 /*not used*/,
1000  extended::ZoneRuleBroker(nullptr) /*rule*/, match);
1001  transitionStorage.addFreeAgentToActivePool();
1002  }
1003 
1004  static void findTransitionsFromNamedMatch(
1006  const extended::ZoneMatch* match) {
1007  if (DEBUG) logging::println("findTransitionsFromNamedMatch()");
1008  transitionStorage.resetCandidatePool();
1009  if (DEBUG) { match->log(); logging::println(); }
1010  findCandidateTransitions(transitionStorage, match);
1011  if (DEBUG) { transitionStorage.log(); logging::println(); }
1012  fixTransitionTimes(
1013  transitionStorage.getCandidatePoolBegin(),
1014  transitionStorage.getCandidatePoolEnd());
1015  selectActiveTransitions(transitionStorage, match);
1016  if (DEBUG) { transitionStorage.log(); logging::println(); }
1017 
1018  transitionStorage.addActiveCandidatesToActivePool();
1019  if (DEBUG) { transitionStorage.log(); logging::println(); }
1020  }
1021 
1022  static void findCandidateTransitions(
1024  const extended::ZoneMatch* match) {
1025  if (DEBUG) {
1026  logging::print("findCandidateTransitions(): ");
1027  match->log();
1028  logging::println();
1029  }
1030  const extended::ZonePolicyBroker policy = match->era.zonePolicy();
1031  uint8_t numRules = policy.numRules();
1032  int8_t startY = match->startDateTime.yearTiny;
1033  int8_t endY = match->untilDateTime.yearTiny;
1034 
1035  extended::Transition** prior = transitionStorage.reservePrior();
1036  (*prior)->active = false; // indicates "no prior transition"
1037  for (uint8_t r = 0; r < numRules; r++) {
1038  const extended::ZoneRuleBroker rule = policy.rule(r);
1039 
1040  // Add Transitions for interior years
1041  int8_t interiorYears[kMaxInteriorYears];
1042  uint8_t numYears = calcInteriorYears(interiorYears, kMaxInteriorYears,
1043  rule.fromYearTiny(), rule.toYearTiny(), startY, endY);
1044  for (uint8_t y = 0; y < numYears; y++) {
1045  int8_t year = interiorYears[y];
1046  extended::Transition* t = transitionStorage.getFreeAgent();
1047  createTransitionForYear(t, year, rule, match);
1048  int8_t status = compareTransitionToMatchFuzzy(t, match);
1049  if (status < 0) {
1050  setAsPriorTransition(transitionStorage, t);
1051  } else if (status == 1) {
1052  transitionStorage.addFreeAgentToCandidatePool();
1053  }
1054  }
1055 
1056  // Add Transition for prior year
1057  int8_t priorYear = getMostRecentPriorYear(
1058  rule.fromYearTiny(), rule.toYearTiny(), startY, endY);
1059  if (priorYear != LocalDate::kInvalidYearTiny) {
1060  if (DEBUG) logging::println(
1061  "findCandidateTransitions(): priorYear: %d", priorYear);
1062  extended::Transition* t = transitionStorage.getFreeAgent();
1063  createTransitionForYear(t, priorYear, rule, match);
1064  setAsPriorTransition(transitionStorage, t);
1065  }
1066  }
1067 
1068  // Add the reserved prior into the Candidate pool only if 'active' is
1069  // true, meaning that a prior was found.
1070  if ((*prior)->active) {
1071  if (DEBUG) logging::println(
1072  "findCandidateTransitions(): adding prior to Candidate pool");
1073  transitionStorage.addPriorToCandidatePool();
1074  }
1075  }
1076 
1081  static uint8_t calcInteriorYears(int8_t* interiorYears,
1082  uint8_t maxInteriorYears, int8_t fromYear, int8_t toYear,
1083  int8_t startYear, int8_t endYear) {
1084  uint8_t i = 0;
1085  for (int8_t year = startYear; year <= endYear; year++) {
1086  if (fromYear <= year && year <= toYear) {
1087  interiorYears[i] = year;
1088  i++;
1089  if (i >= maxInteriorYears) break;
1090  }
1091  }
1092  return i;
1093  }
1094 
1101  static void createTransitionForYear(extended::Transition* t, int8_t year,
1102  const extended::ZoneRuleBroker rule,
1103  const extended::ZoneMatch* match) {
1104  t->match = match;
1105  t->rule = rule;
1106  t->offsetCode = match->era.offsetCode();
1107  t->letterBuf[0] = '\0';
1108 
1109  if (rule.isNotNull()) {
1110  t->transitionTime = getTransitionTime(year, rule);
1111  t->deltaCode = rule.deltaCode();
1112 
1113  char letter = rule.letter();
1114  if (letter >= 32) {
1115  // If LETTER is a '-', treat it the same as an empty string.
1116  if (letter != '-') {
1117  t->letterBuf[0] = letter;
1118  t->letterBuf[1] = '\0';
1119  }
1120  } else {
1121  // rule->letter is a long string, so is referenced as an offset index
1122  // into the ZonePolicy.letters array. The string cannot fit in
1123  // letterBuf, so will be retrieved by the letter() method below.
1124  }
1125  } else {
1126  t->transitionTime = match->startDateTime;
1127  t->deltaCode = match->era.deltaCode();
1128  }
1129  }
1130 
1136  static int8_t getMostRecentPriorYear(int8_t fromYear, int8_t toYear,
1137  int8_t startYear, int8_t /*endYear*/) {
1138  if (fromYear < startYear) {
1139  if (toYear < startYear) {
1140  return toYear;
1141  } else {
1142  return startYear - 1;
1143  }
1144  } else {
1146  }
1147  }
1148 
1149  static extended::DateTuple getTransitionTime(
1150  int8_t yearTiny, const extended::ZoneRuleBroker rule) {
1152  yearTiny + LocalDate::kEpochYear, rule.inMonth(), rule.onDayOfWeek(),
1153  rule.onDayOfMonth());
1154  return {yearTiny, monthDay.month, monthDay.day,
1155  (int8_t) rule.atTimeCode(), rule.atTimeModifier()};
1156  }
1157 
1168  static int8_t compareTransitionToMatchFuzzy(
1169  const extended::Transition* t, const extended::ZoneMatch* match) {
1170  int16_t ttMonths = t->transitionTime.yearTiny * 12
1171  + t->transitionTime.month;
1172 
1173  int16_t matchStartMonths = match->startDateTime.yearTiny * 12
1174  + match->startDateTime.month;
1175  if (ttMonths < matchStartMonths - 1) return -1;
1176 
1177  int16_t matchUntilMonths = match->untilDateTime.yearTiny * 12
1178  + match->untilDateTime.month;
1179  if (matchUntilMonths + 2 <= ttMonths) return 2;
1180 
1181  return 1;
1182  }
1183 
1185  static void setAsPriorTransition(
1187  extended::Transition* t) {
1188  if (DEBUG) logging::println("setAsPriorTransition()");
1189  extended::Transition* prior = transitionStorage.getPrior();
1190  if (prior->active) {
1191  if (prior->transitionTime < t->transitionTime) {
1192  t->active = true;
1193  transitionStorage.setFreeAgentAsPrior();
1194  }
1195  } else {
1196  t->active = true;
1197  transitionStorage.setFreeAgentAsPrior();
1198  }
1199  }
1200 
1209  static void fixTransitionTimes(
1210  extended::Transition** begin, extended::Transition** end) {
1211  if (DEBUG) logging::println("fixTransitionTimes(): #transitions: %d;",
1212  (int) (end - begin));
1213 
1214  // extend first Transition to -infinity
1215  extended::Transition* prev = *begin;
1216 
1217  for (extended::Transition** iter = begin; iter != end; ++iter) {
1218  extended::Transition* curr = *iter;
1219  if (DEBUG) {
1220  logging::println("fixTransitionTimes(): LOOP");
1221  curr->log();
1222  logging::println();
1223  }
1224  expandDateTuple(&curr->transitionTime,
1225  &curr->transitionTimeS, &curr->transitionTimeU,
1226  prev->offsetCode, prev->deltaCode);
1227  prev = curr;
1228  }
1229  if (DEBUG) logging::println("fixTransitionTimes(): END");
1230  }
1231 
1237  static void expandDateTuple(extended::DateTuple* tt,
1239  int8_t offsetCode, int8_t deltaCode) {
1240  if (DEBUG) logging::println("expandDateTuple()");
1241  if (tt->modifier == 's') {
1242  *tts = *tt;
1243  *ttu = {tt->yearTiny, tt->month, tt->day,
1244  (int8_t) (tt->timeCode - offsetCode), 'u'};
1245  *tt = {tt->yearTiny, tt->month, tt->day,
1246  (int8_t) (tt->timeCode + deltaCode), 'w'};
1247  } else if (tt->modifier == 'u') {
1248  *ttu = *tt;
1249  *tts = {tt->yearTiny, tt->month, tt->day,
1250  (int8_t) (tt->timeCode + offsetCode), 's'};
1251  *tt = {tt->yearTiny, tt->month, tt->day,
1252  (int8_t) (tt->timeCode + offsetCode + deltaCode), 'w'};
1253  } else {
1254  // Explicit set the modifier to 'w' in case it was something else.
1255  tt->modifier = 'w';
1256  *tts = {tt->yearTiny, tt->month, tt->day,
1257  (int8_t) (tt->timeCode - deltaCode), 's'};
1258  *ttu = {tt->yearTiny, tt->month, tt->day,
1259  (int8_t) (tt->timeCode - deltaCode - offsetCode), 'u'};
1260  }
1261 
1262  if (DEBUG) logging::println("expandDateTuple(): normalizeDateTuple(): 1");
1263  normalizeDateTuple(tt);
1264  if (DEBUG) logging::println("expandDateTuple(): normalizeDateTuple(): 2");
1265  normalizeDateTuple(tts);
1266  if (DEBUG) logging::println("expandDateTuple(): normalizeDateTuple(): 3");
1267  normalizeDateTuple(ttu);
1268  }
1269 
1271  static void normalizeDateTuple(extended::DateTuple* dt) {
1272  const int8_t kOneDayAsCode = 4 * 24;
1273  if (dt->timeCode <= -kOneDayAsCode) {
1275  dt->yearTiny, dt->month, dt->day);
1276  local_date_mutation::decrementOneDay(ld);
1277  dt->yearTiny = ld.yearTiny();
1278  dt->month = ld.month();
1279  dt->day = ld.day();
1280  dt->timeCode += kOneDayAsCode;
1281  } else if (kOneDayAsCode <= dt->timeCode) {
1283  dt->yearTiny, dt->month, dt->day);
1284  local_date_mutation::incrementOneDay(ld);
1285  dt->yearTiny = ld.yearTiny();
1286  dt->month = ld.month();
1287  dt->day = ld.day();
1288  dt->timeCode -= kOneDayAsCode;
1289  } else {
1290  // do nothing
1291  }
1292  }
1293 
1298  static void selectActiveTransitions(
1300  const extended::ZoneMatch* match) {
1301  extended::Transition** begin = transitionStorage.getCandidatePoolBegin();
1302  extended::Transition** end = transitionStorage.getCandidatePoolEnd();
1303  if (DEBUG) logging::println("selectActiveTransitions(): #candidates: %d",
1304  (int) (end - begin));
1305  extended::Transition* prior = nullptr;
1306  for (extended::Transition** iter = begin; iter != end; ++iter) {
1307  extended::Transition* transition = *iter;
1308  processActiveTransition(match, transition, &prior);
1309  }
1310 
1311  // If the latest prior transition is found, shift it to start at the
1312  // startDateTime of the current match.
1313  if (prior) {
1314  if (DEBUG) logging::println(
1315  "selectActiveTransitions(): found latest prior");
1316  prior->originalTransitionTime = prior->transitionTime;
1317  prior->transitionTime = match->startDateTime;
1318  }
1319  }
1320 
1328  static void processActiveTransition(
1329  const extended::ZoneMatch* match,
1330  extended::Transition* transition,
1331  extended::Transition** prior) {
1332  int8_t status = compareTransitionToMatch(transition, match);
1333  if (status == 2) {
1334  transition->active = false;
1335  } else if (status == 1) {
1336  transition->active = true;
1337  } else if (status == 0) {
1338  if (*prior) {
1339  (*prior)->active = false;
1340  }
1341  transition->active = true;
1342  (*prior) = transition;
1343  } else { // (status < 0)
1344  if (*prior) {
1345  if ((*prior)->transitionTime < transition->transitionTime) {
1346  (*prior)->active = false;
1347  transition->active = true;
1348  (*prior) = transition;
1349  }
1350  } else {
1351  transition->active = true;
1352  (*prior) = transition;
1353  }
1354  }
1355  }
1356 
1371  static int8_t compareTransitionToMatch(
1372  const extended::Transition* transition,
1373  const extended::ZoneMatch* match) {
1374  const extended::DateTuple* transitionTime;
1375 
1376  const extended::DateTuple& matchStart = match->startDateTime;
1377  if (matchStart.modifier == 's') {
1378  transitionTime = &transition->transitionTimeS;
1379  } else if (matchStart.modifier == 'u') {
1380  transitionTime = &transition->transitionTimeU;
1381  } else { // assume 'w'
1382  transitionTime = &transition->transitionTime;
1383  }
1384  if (*transitionTime < matchStart) return -1;
1385  if (*transitionTime == matchStart) return 0;
1386 
1387  const extended::DateTuple& matchUntil = match->untilDateTime;
1388  if (matchUntil.modifier == 's') {
1389  transitionTime = &transition->transitionTimeS;
1390  } else if (matchUntil.modifier == 'u') {
1391  transitionTime = &transition->transitionTimeU;
1392  } else { // assume 'w'
1393  transitionTime = &transition->transitionTime;
1394  }
1395  if (*transitionTime < matchUntil) return 1;
1396  return 2;
1397  }
1398 
1404  static void generateStartUntilTimes(
1405  extended::Transition** begin, extended::Transition** end) {
1406  if (DEBUG) logging::println(
1407  "generateStartUntilTimes(): #transitions: %d;",
1408  (int) (end - begin));
1409 
1410  extended::Transition* prev = *begin;
1411  bool isAfterFirst = false;
1412 
1413  for (extended::Transition** iter = begin; iter != end; ++iter) {
1414  extended::Transition* const t = *iter;
1415 
1416  // 1) Update the untilDateTime of the previous Transition
1417  const extended::DateTuple& tt = t->transitionTime;
1418  if (isAfterFirst) {
1419  prev->untilDateTime = tt;
1420  }
1421 
1422  // 2) Calculate the current startDateTime by shifting the
1423  // transitionTime (represented in the UTC offset of the previous
1424  // transition) into the UTC offset of the *current* transition.
1425  int8_t code = tt.timeCode - prev->offsetCode - prev->deltaCode
1426  + t->offsetCode + t->deltaCode;
1427  t->startDateTime = {tt.yearTiny, tt.month, tt.day, code, tt.modifier};
1428  normalizeDateTuple(&t->startDateTime);
1429 
1430  // 3) The epochSecond of the 'transitionTime' is determined by the
1431  // UTC offset of the *previous* Transition. However, the
1432  // transitionTime can be represented by an illegal time (e.g. 24:00).
1433  // So, it is better to use the properly normalized startDateTime
1434  // (calculated above) with the *current* UTC offset.
1435  //
1436  // NOTE: We should also be able to calculate this directly from
1437  // 'transitionTimeU' which should still be a valid field, because it
1438  // hasn't been clobbered by 'untilDateTime' yet. Not sure if this saves
1439  // any CPU time though, since we still need to mutiply by 900.
1440  const extended::DateTuple& st = t->startDateTime;
1441  const acetime_t offsetSeconds = (acetime_t) 900
1442  * (st.timeCode - t->offsetCode - t->deltaCode);
1444  st.yearTiny, st.month, st.day);
1445  t->startEpochSeconds = ld.toEpochSeconds() + offsetSeconds;
1446 
1447  prev = t;
1448  isAfterFirst = true;
1449  }
1450 
1451  // The last Transition's until time is the until time of the ZoneMatch.
1452  extended::DateTuple untilTime = prev->match->untilDateTime;
1453  extended::DateTuple untilTimeS; // needed only for expandDateTuple
1454  extended::DateTuple untilTimeU; // needed only for expandDateTuple
1455  expandDateTuple(&untilTime, &untilTimeS, &untilTimeU,
1456  prev->offsetCode, prev->deltaCode);
1457  prev->untilDateTime = untilTime;
1458  }
1459 
1463  static void calcAbbreviations(
1464  extended::Transition** begin, extended::Transition** end) {
1465  if (DEBUG) logging::println("calcAbbreviations(): #transitions: %d;",
1466  (int) (end - begin));
1467  for (extended::Transition** iter = begin; iter != end; ++iter) {
1468  extended::Transition* const t = *iter;
1469  if (DEBUG) logging::println(
1470  "calcAbbreviations(): format:%s, deltaCode:%d, letter:%s",
1471  t->format(), t->deltaCode, t->letter());
1472  createAbbreviation(t->abbrev, extended::Transition::kAbbrevSize,
1473  t->format(), t->deltaCode, t->letter());
1474  }
1475  }
1476 
1511  static void createAbbreviation(char* dest, uint8_t destSize,
1512  const char* format, uint8_t deltaCode, const char* letterString) {
1513  // Check if RULES column is empty. Ignore the deltaCode because if
1514  // letterString is nullptr, we can only just copy the whole thing.
1515  if (letterString == nullptr) {
1516  strncpy(dest, format, destSize);
1517  dest[destSize - 1] = '\0';
1518  return;
1519  }
1520 
1521  // Check if FORMAT contains a '%'.
1522  if (strchr(format, '%') != nullptr) {
1523  copyAndReplace(dest, destSize, format, '%', letterString);
1524  } else {
1525  // Check if FORMAT contains a '/'.
1526  const char* slashPos = strchr(format, '/');
1527  if (slashPos != nullptr) {
1528  if (deltaCode == 0) {
1529  uint8_t headLength = (slashPos - format);
1530  if (headLength >= destSize) headLength = destSize - 1;
1531  memcpy(dest, format, headLength);
1532  dest[headLength] = '\0';
1533  } else {
1534  uint8_t tailLength = strlen(slashPos+1);
1535  if (tailLength >= destSize) tailLength = destSize - 1;
1536  memcpy(dest, slashPos+1, tailLength);
1537  dest[tailLength] = '\0';
1538  }
1539  } else {
1540  // Just copy the FORMAT disregarding deltaCode and letterString.
1541  strncpy(dest, format, destSize);
1542  dest[destSize - 1] = '\0';
1543  }
1544  }
1545  }
1546 
1552  static void copyAndReplace(char* dst, uint8_t dstSize, const char* src,
1553  char oldChar, const char* newString) {
1554  while (*src != '\0' && dstSize > 0) {
1555  if (*src == oldChar) {
1556  while (*newString != '\0' && dstSize > 0) {
1557  *dst++ = *newString++;
1558  dstSize--;
1559  }
1560  src++;
1561  } else {
1562  *dst++ = *src++;
1563  dstSize--;
1564  }
1565  }
1566 
1567  if (dstSize == 0) {
1568  --dst;
1569  }
1570  *dst = '\0';
1571  }
1572 
1573  extended::ZoneInfoBroker mZoneInfo;
1574 
1575  mutable int16_t mYear = 0; // maybe create LocalDate::kInvalidYear?
1576  mutable bool mIsFilled = false;
1577  // NOTE: Maybe move mNumMatches and mMatches into a MatchStorage object.
1578  mutable uint8_t mNumMatches = 0; // actual number of matches
1579  mutable extended::ZoneMatch mMatches[kMaxMatches];
1580  mutable extended::TransitionStorage<kMaxTransitions> mTransitionStorage;
1581 };
1582 
1583 } // namespace ace_time
1584 
1585 #undef DEBUG
1586 
1587 #endif
void addFreeAgentToActivePool()
Immediately add the free agent Transition at index mIndexFree to the Active pool. ...
+
1 /*
2  * MIT License
3  * Copyright (c) 2019 Brian T. Park
4  */
5 
6 #ifndef ACE_TIME_EXTENDED_ZONE_PROCESSOR_H
7 #define ACE_TIME_EXTENDED_ZONE_PROCESSOR_H
8 
9 #include <string.h> // memcpy()
10 #include <stdint.h>
11 #include "common/compat.h"
12 #include "internal/ZonePolicy.h"
13 #include "internal/ZoneInfo.h"
14 #include "common/logging.h"
15 #include "TimeOffset.h"
16 #include "LocalDate.h"
17 #include "OffsetDateTime.h"
18 #include "ZoneProcessor.h"
19 #include "BasicZoneProcessor.h"
20 #include "local_date_mutation.h"
21 
22 #define ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG 0
23 
24 class ExtendedZoneProcessorTest_compareEraToYearMonth;
25 class ExtendedZoneProcessorTest_compareEraToYearMonth2;
26 class ExtendedZoneProcessorTest_createMatch;
27 class ExtendedZoneProcessorTest_findMatches_simple;
28 class ExtendedZoneProcessorTest_findMatches_named;
29 class ExtendedZoneProcessorTest_findCandidateTransitions;
30 class ExtendedZoneProcessorTest_findTransitionsFromNamedMatch;
31 class ExtendedZoneProcessorTest_getTransitionTime;
32 class ExtendedZoneProcessorTest_createTransitionForYear;
33 class ExtendedZoneProcessorTest_normalizeDateTuple;
34 class ExtendedZoneProcessorTest_expandDateTuple;
35 class ExtendedZoneProcessorTest_calcInteriorYears;
36 class ExtendedZoneProcessorTest_getMostRecentPriorYear;
37 class ExtendedZoneProcessorTest_compareTransitionToMatchFuzzy;
38 class ExtendedZoneProcessorTest_compareTransitionToMatch;
39 class ExtendedZoneProcessorTest_processActiveTransition;
40 class ExtendedZoneProcessorTest_fixTransitionTimes_generateStartUntilTimes;
41 class ExtendedZoneProcessorTest_createAbbreviation;
42 class ExtendedZoneProcessorTest_setZoneInfo;
43 class TransitionStorageTest_getFreeAgent;
44 class TransitionStorageTest_getFreeAgent2;
45 class TransitionStorageTest_addFreeAgentToActivePool;
46 class TransitionStorageTest_reservePrior;
47 class TransitionStorageTest_addFreeAgentToCandidatePool;
48 class TransitionStorageTest_setFreeAgentAsPrior;
49 class TransitionStorageTest_addActiveCandidatesToActivePool;
50 class TransitionStorageTest_resetCandidatePool;
51 
52 namespace ace_time {
53 
54 template<uint8_t SIZE, uint8_t TYPE, typename ZS, typename ZI, typename ZIB>
55 class ZoneProcessorCacheImpl;
56 
57 namespace extended {
58 
59 // NOTE: Consider compressing 'modifier' into 'month' field
64 struct DateTuple {
65  int8_t yearTiny; // [-127, 126], 127 will cause bugs
66  uint8_t month; // [1-12]
67  uint8_t day; // [1-31]
68  int8_t timeCode; // 15-min intervals, negative values allowed
69  uint8_t modifier; // 's', 'w', 'u'
70 
72  void log() const {
73  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
74  logging::print("DateTuple(%d-%u-%uT%d'%c')",
75  yearTiny+LocalDate::kEpochYear, month, day, timeCode, modifier);
76  }
77  }
78 };
79 
81 inline bool operator<(const DateTuple& a, const DateTuple& b) {
82  if (a.yearTiny < b.yearTiny) return true;
83  if (a.yearTiny > b.yearTiny) return false;
84  if (a.month < b.month) return true;
85  if (a.month > b.month) return false;
86  if (a.day < b.day) return true;
87  if (a.day > b.day) return false;
88  if (a.timeCode < b.timeCode) return true;
89  if (a.timeCode > b.timeCode) return false;
90  return false;
91 }
92 
93 inline bool operator>=(const DateTuple& a, const DateTuple& b) {
94  return ! (a < b);
95 }
96 
97 inline bool operator<=(const DateTuple& a, const DateTuple& b) {
98  return ! (b < a);
99 }
100 
101 inline bool operator>(const DateTuple& a, const DateTuple& b) {
102  return (b < a);
103 }
104 
106 inline bool operator==(const DateTuple& a, const DateTuple& b) {
107  return a.yearTiny == b.yearTiny
108  && a.month == b.month
109  && a.day == b.day
110  && a.timeCode == b.timeCode
111  && a.modifier == b.modifier;
112 }
113 
116  int8_t yearTiny;
117  uint8_t month;
118 };
119 
124 struct ZoneMatch {
127 
130 
133 
134  void log() const {
135  logging::print("ZoneMatch(");
136  logging::print("Start:"); startDateTime.log();
137  logging::print("; Until:"); untilDateTime.log();
138  logging::print("; Era: %snull", (era.isNotNull()) ? "!" : "");
139  logging::print(")");
140  }
141 };
142 
168 struct Transition {
170  static const uint8_t kAbbrevSize = basic::Transition::kAbbrevSize;
171 
173  const ZoneMatch* match;
174 
181 
189 
190  union {
197 
203  };
204 
205  union {
212 
218  };
219 
225 
227  acetime_t startEpochSeconds;
228 
230  char abbrev[kAbbrevSize];
231 
233  char letterBuf[2];
234 
247  bool active;
248 
256  int8_t offsetCode;
257 
259  int8_t deltaCode;
260 
261  //-------------------------------------------------------------------------
262 
263  const char* format() const {
264  return match->era.format();
265  }
266 
272  const char* letter() const {
273  // RULES column is '-' or hh:mm, so return nullptr to indicate this.
274  if (rule.isNull()) {
275  return nullptr;
276  }
277 
278  // RULES point to a named rule, and LETTER is a single, printable
279  // character.
280  char letter = rule.letter();
281  if (letter >= 32) {
282  return letterBuf;
283  }
284 
285  // RULES points to a named rule, and the LETTER is a string. The
286  // rule->letter is a non-printable number < 32, which is an index into
287  // a list of strings given by match->era->zonePolicy->letters[].
288  const ZonePolicyBroker policy = match->era.zonePolicy();
289  uint8_t numLetters = policy.numLetters();
290  if (letter >= numLetters) {
291  // This should never happen unless there is a programming error. If it
292  // does, return an empty string. (createTransitionForYear() sets
293  // letterBuf to a NUL terminated empty string if rule->letter < 32)
294  return letterBuf;
295  }
296 
297  // Return the string at index 'rule->letter'.
298  return policy.letter(letter);
299  }
300 
302  void log() const {
303  logging::print("Transition(");
304  if (sizeof(acetime_t) == sizeof(int)) {
305  logging::print("sE: %d", startEpochSeconds);
306  } else {
307  logging::print("sE: %ld", startEpochSeconds);
308  }
309  logging::print("; match: %snull", (match) ? "!" : "");
310  logging::print("; era: %snull",
311  (match && match->era.isNotNull()) ? "!" : "");
312  logging::print("; oCode: %d", offsetCode);
313  logging::print("; dCode: %d", deltaCode);
314  logging::print("; tt: "); transitionTime.log();
315  if (rule.isNotNull()) {
316  logging::print("; R.fY: %d", rule.fromYearTiny());
317  logging::print("; R.tY: %d", rule.toYearTiny());
318  logging::print("; R.M: %d", rule.inMonth());
319  logging::print("; R.dow: %d", rule.onDayOfWeek());
320  logging::print("; R.dom: %d", rule.onDayOfMonth());
321  }
322  }
323 };
324 
351 template<uint8_t SIZE>
353  public:
356 
358  void init() {
359  for (uint8_t i = 0; i < SIZE; i++) {
360  mTransitions[i] = &mPool[i];
361  }
362  mIndexPrior = 0;
363  mIndexCandidates = 0;
364  mIndexFree = 0;
365  }
366 
368  Transition* getPrior() { return mTransitions[mIndexPrior]; }
369 
371  void swap(Transition** a, Transition** b) {
372  Transition* tmp = *a;
373  *a = *b;
374  *b = tmp;
375  }
376 
386  mIndexCandidates = mIndexPrior;
387  mIndexFree = mIndexPrior;
388  }
389 
390  Transition** getCandidatePoolBegin() {
391  return &mTransitions[mIndexCandidates];
392  }
393  Transition** getCandidatePoolEnd() {
394  return &mTransitions[mIndexFree];
395  }
396 
397  Transition** getActivePoolBegin() { return &mTransitions[0]; }
398  Transition** getActivePoolEnd() { return &mTransitions[mIndexFree]; }
399 
406  // Set the internal high water mark. If that index becomes SIZE,
407  // then we know we have an overflow.
408  if (mIndexFree > mHighWater) {
409  mHighWater = mIndexFree;
410  }
411 
412  if (mIndexFree < SIZE) {
413  return mTransitions[mIndexFree];
414  } else {
415  return mTransitions[SIZE - 1];
416  }
417  }
418 
427  if (mIndexFree >= SIZE) return;
428  mIndexFree++;
429  mIndexPrior = mIndexFree;
430  mIndexCandidates = mIndexFree;
431  }
432 
439  mIndexCandidates++;
440  mIndexFree++;
441  return &mTransitions[mIndexPrior];
442  }
443 
446  swap(&mTransitions[mIndexPrior], &mTransitions[mIndexFree]);
447  }
448 
455  mIndexCandidates--;
456  }
457 
465  if (mIndexFree >= SIZE) return;
466  for (uint8_t i = mIndexFree; i > mIndexCandidates; i--) {
467  Transition* curr = mTransitions[i];
468  Transition* prev = mTransitions[i - 1];
469  if (curr->transitionTime >= prev->transitionTime) break;
470  mTransitions[i] = prev;
471  mTransitions[i - 1] = curr;
472  }
473  mIndexFree++;
474  }
475 
481  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
482  logging::println("addActiveCandidatesToActivePool()");
483  }
484  uint8_t iActive = mIndexPrior;
485  uint8_t iCandidate = mIndexCandidates;
486  for (; iCandidate < mIndexFree; iCandidate++) {
487  if (mTransitions[iCandidate]->active) {
488  if (iActive != iCandidate) {
489  swap(&mTransitions[iActive], &mTransitions[iCandidate]);
490  }
491  ++iActive;
492  }
493  }
494  mIndexPrior = iActive;
495  mIndexCandidates = iActive;
496  mIndexFree = iActive;
497  }
498 
507  const Transition* findTransition(acetime_t epochSeconds) const {
508  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
509  logging::println( "findTransition(): mIndexFree: %d", mIndexFree);
510  }
511 
512  const Transition* match = nullptr;
513  for (uint8_t i = 0; i < mIndexFree; i++) {
514  const Transition* candidate = mTransitions[i];
515  if (candidate->startEpochSeconds > epochSeconds) break;
516  match = candidate;
517  }
518  return match;
519  }
520 
545  const {
546  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
547  logging::println(
548  "findTransitionForDateTime(): mIndexFree: %d", mIndexFree);
549  }
550 
551  // Convert to DateTuple. If the localDateTime is not a multiple of 15
552  // minutes, the comparision (startTime < localDate) will still be valid.
553  DateTuple localDate = { ldt.yearTiny(), ldt.month(), ldt.day(),
554  (int8_t) (ldt.hour() * 4 + ldt.minute() / 15), 'w' };
555  const Transition* match = nullptr;
556  for (uint8_t i = 0; i < mIndexFree; i++) {
557  const Transition* candidate = mTransitions[i];
558  if (candidate->startDateTime > localDate) break;
559  match = candidate;
560  }
561  return match;
562  }
563 
565  void log() const {
566  logging::println("TransitionStorage:");
567  logging::println(" mIndexPrior: %d", mIndexPrior);
568  logging::println(" mIndexCandidates: %d", mIndexCandidates);
569  logging::println(" mIndexFree: %d", mIndexFree);
570  if (mIndexPrior != 0) {
571  logging::println(" Actives:");
572  for (uint8_t i = 0; i < mIndexPrior; i++) {
573  mTransitions[i]->log();
574  logging::println();
575  }
576  }
577  if (mIndexPrior != mIndexCandidates) {
578  logging::print(" Prior: ");
579  mTransitions[mIndexPrior]->log();
580  logging::println();
581  }
582  if (mIndexCandidates != mIndexFree) {
583  logging::println(" Candidates:");
584  for (uint8_t i = mIndexCandidates; i < mIndexFree; i++) {
585  mTransitions[i]->log();
586  logging::println();
587  }
588  }
589  }
590 
592  void resetHighWater() { mHighWater = 0; }
593 
599  uint8_t getHighWater() const { return mHighWater; }
600 
601  private:
602  friend class ::TransitionStorageTest_getFreeAgent;
603  friend class ::TransitionStorageTest_getFreeAgent2;
604  friend class ::TransitionStorageTest_addFreeAgentToActivePool;
605  friend class ::TransitionStorageTest_reservePrior;
606  friend class ::TransitionStorageTest_addFreeAgentToCandidatePool;
607  friend class ::TransitionStorageTest_setFreeAgentAsPrior;
608  friend class ::TransitionStorageTest_addActiveCandidatesToActivePool;
609  friend class ::TransitionStorageTest_resetCandidatePool;
610 
612  Transition* getTransition(uint8_t i) { return mTransitions[i]; }
613 
614  Transition mPool[SIZE];
615  Transition* mTransitions[SIZE];
616  uint8_t mIndexPrior;
617  uint8_t mIndexCandidates;
618  uint8_t mIndexFree;
619 
621  uint8_t mHighWater = 0;
622 };
623 
624 } // namespace extended
625 
655  public:
661  const extended::ZoneInfo* zoneInfo = nullptr):
662  ZoneProcessor(kTypeExtended),
663  mZoneInfo(zoneInfo) {}
664 
666  const void* getZoneInfo() const override {
667  return mZoneInfo.zoneInfo();
668  }
669 
670  uint32_t getZoneId() const override { return mZoneInfo.zoneId(); }
671 
672  TimeOffset getUtcOffset(acetime_t epochSeconds) const override {
673  bool success = init(epochSeconds);
674  if (!success) return TimeOffset::forError();
675  const extended::Transition* transition = findTransition(epochSeconds);
676  return (transition)
678  transition->offsetCode + transition->deltaCode)
680  }
681 
682  TimeOffset getDeltaOffset(acetime_t epochSeconds) const override {
683  bool success = init(epochSeconds);
684  if (!success) return TimeOffset::forError();
685  const extended::Transition* transition = findTransition(epochSeconds);
686  return TimeOffset::forOffsetCode(transition->deltaCode);
687  }
688 
689  const char* getAbbrev(acetime_t epochSeconds) const override {
690  bool success = init(epochSeconds);
691  if (!success) return "";
692  const extended::Transition* transition = findTransition(epochSeconds);
693  return transition->abbrev;
694  }
695 
696  OffsetDateTime getOffsetDateTime(const LocalDateTime& ldt) const override {
697  TimeOffset offset;
698  bool success = init(ldt.localDate());
699  if (success) {
700  const extended::Transition* transition =
701  mTransitionStorage.findTransitionForDateTime(ldt);
702  offset = (transition)
704  transition->offsetCode + transition->deltaCode)
706  } else {
707  offset = TimeOffset::forError();
708  }
709 
710  auto odt = OffsetDateTime::forLocalDateTimeAndOffset(ldt, offset);
711  if (offset.isError()) {
712  return odt;
713  }
714 
715  // Normalize the OffsetDateTime, causing LocalDateTime in the DST
716  // transtion gap to be shifted forward one hour. For LocalDateTime in an
717  // overlap (DST->STD transition), the earlier UTC offset is selected// by
718  // findTransitionForDateTime(). Use that to calculate the epochSeconds,
719  // then recalculate the offset. Use this final offset to determine the
720  // effective OffsetDateTime that will survive a round-trip unchanged.
721  acetime_t epochSeconds = odt.toEpochSeconds();
722  const extended::Transition* transition =
723  mTransitionStorage.findTransition(epochSeconds);
724  offset = (transition)
726  transition->offsetCode + transition->deltaCode)
728  odt = OffsetDateTime::forEpochSeconds(epochSeconds, offset);
729  return odt;
730  }
731 
732  void printTo(Print& printer) const override;
733 
734  void printShortTo(Print& printer) const override;
735 
737  void log() const {
738  logging::println("ExtendedZoneProcessor:");
739  logging::println(" mYear: %d", mYear);
740  logging::println(" mNumMatches: %d", mNumMatches);
741  for (int i = 0; i < mNumMatches; i++) {
742  logging::print(" Match %d: ", i);
743  mMatches[i].log();
744  logging::println();
745  }
746  mTransitionStorage.log();
747  }
748 
751  mTransitionStorage.resetHighWater();
752  }
753 
755  uint8_t getTransitionHighWater() const {
756  return mTransitionStorage.getHighWater();
757  }
758 
759  private:
760  friend class ::ExtendedZoneProcessorTest_compareEraToYearMonth;
761  friend class ::ExtendedZoneProcessorTest_compareEraToYearMonth2;
762  friend class ::ExtendedZoneProcessorTest_createMatch;
763  friend class ::ExtendedZoneProcessorTest_findMatches_simple;
764  friend class ::ExtendedZoneProcessorTest_findMatches_named;
765  friend class ::ExtendedZoneProcessorTest_findCandidateTransitions;
766  friend class ::ExtendedZoneProcessorTest_findTransitionsFromNamedMatch;
767  friend class ::ExtendedZoneProcessorTest_getTransitionTime;
768  friend class ::ExtendedZoneProcessorTest_createTransitionForYear;
769  friend class ::ExtendedZoneProcessorTest_normalizeDateTuple;
770  friend class ::ExtendedZoneProcessorTest_expandDateTuple;
771  friend class ::ExtendedZoneProcessorTest_calcInteriorYears;
772  friend class ::ExtendedZoneProcessorTest_getMostRecentPriorYear;
773  friend class ::ExtendedZoneProcessorTest_compareTransitionToMatchFuzzy;
774  friend class ::ExtendedZoneProcessorTest_compareTransitionToMatch;
775  friend class ::ExtendedZoneProcessorTest_processActiveTransition;
776  friend class ::ExtendedZoneProcessorTest_fixTransitionTimes_generateStartUntilTimes;
777  friend class ::ExtendedZoneProcessorTest_createAbbreviation;
778  friend class ::ExtendedZoneProcessorTest_setZoneInfo;
779 
780  template<uint8_t SIZE, uint8_t TYPE, typename ZS, typename ZI, typename ZIB>
781  friend class ZoneProcessorCacheImpl; // setZoneInfo()
782 
783  // Disable copy constructor and assignment operator.
785  ExtendedZoneProcessor& operator=(const ExtendedZoneProcessor&) = delete;
786 
791  static const uint8_t kMaxMatches = 4;
792 
800  static const uint8_t kMaxTransitions = 8;
801 
806  static const uint8_t kMaxInteriorYears = 4;
807 
809  static const extended::ZoneEra kAnchorEra;
810 
811  bool equals(const ZoneProcessor& other) const override {
812  const auto& that = (const ExtendedZoneProcessor&) other;
813  return getZoneInfo() == that.getZoneInfo();
814  }
815 
817  void setZoneInfo(const void* zoneInfo) override {
818  if (mZoneInfo.zoneInfo() == zoneInfo) return;
819 
820  mZoneInfo = extended::ZoneInfoBroker(
821  (const extended::ZoneInfo*) zoneInfo);
822  mYear = 0;
823  mIsFilled = false;
824  mNumMatches = 0;
825  }
826 
831  const extended::Transition* findTransition(acetime_t epochSeconds) const {
832  return mTransitionStorage.findTransition(epochSeconds);
833  }
834 
836  bool init(acetime_t epochSeconds) const {
837  LocalDate ld = LocalDate::forEpochSeconds(epochSeconds);
838  return init(ld);
839  }
840 
845  bool init(const LocalDate& ld) const {
846  int16_t year = ld.year();
847  if (isFilled(year)) return true;
848  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
849  logging::println("init(): %d", year);
850  }
851 
852  mYear = year;
853  mNumMatches = 0; // clear cache
854  mTransitionStorage.init();
855 
856  if (year < mZoneInfo.startYear() - 1 || mZoneInfo.untilYear() < year) {
857  return false;
858  }
859 
860  extended::YearMonthTuple startYm = {
861  (int8_t) (year - LocalDate::kEpochYear - 1), 12 };
862  extended::YearMonthTuple untilYm = {
863  (int8_t) (year - LocalDate::kEpochYear + 1), 2 };
864 
865  mNumMatches = findMatches(mZoneInfo, startYm, untilYm, mMatches,
866  kMaxMatches);
867  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
868  log();
869  }
870  findTransitions(mTransitionStorage, mMatches, mNumMatches);
871  extended::Transition** begin = mTransitionStorage.getActivePoolBegin();
872  extended::Transition** end = mTransitionStorage.getActivePoolEnd();
873  fixTransitionTimes(begin, end);
874  generateStartUntilTimes(begin, end);
875  calcAbbreviations(begin, end);
876 
877  mIsFilled = true;
878  return true;
879  }
880 
882  bool isFilled(int16_t year) const {
883  return mIsFilled && (year == mYear);
884  }
885 
893  static uint8_t findMatches(const extended::ZoneInfoBroker zoneInfo,
894  const extended::YearMonthTuple& startYm,
895  const extended::YearMonthTuple& untilYm,
896  extended::ZoneMatch* matches, uint8_t maxMatches) {
897  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
898  logging::println("findMatches()");
899  }
900  uint8_t iMatch = 0;
902  for (uint8_t iEra = 0; iEra < zoneInfo.numEras(); iEra++) {
903  const extended::ZoneEraBroker era = zoneInfo.era(iEra);
904  if (eraOverlapsInterval(prev, era, startYm, untilYm)) {
905  if (iMatch < maxMatches) {
906  matches[iMatch] = createMatch(prev, era, startYm, untilYm);
907  iMatch++;
908  }
909  }
910  prev = era;
911  }
912  return iMatch;
913  }
914 
925  static bool eraOverlapsInterval(
926  const extended::ZoneEraBroker prev,
927  const extended::ZoneEraBroker era,
928  const extended::YearMonthTuple& startYm,
929  const extended::YearMonthTuple& untilYm) {
930  return compareEraToYearMonth(prev, untilYm.yearTiny, untilYm.month) < 0
931  && compareEraToYearMonth(era, startYm.yearTiny, startYm.month) > 0;
932  }
933 
935  static int8_t compareEraToYearMonth(const extended::ZoneEraBroker era,
936  int8_t yearTiny, uint8_t month) {
937  if (era.untilYearTiny() < yearTiny) return -1;
938  if (era.untilYearTiny() > yearTiny) return 1;
939  if (era.untilMonth() < month) return -1;
940  if (era.untilMonth() > month) return 1;
941  if (era.untilDay() > 1) return 1;
942  //if (era.untilTimeCode() < 0) return -1; // never possible
943  if (era.untilTimeCode() > 0) return 1;
944  return 0;
945  }
946 
953  static extended::ZoneMatch createMatch(
954  const extended::ZoneEraBroker prev,
955  const extended::ZoneEraBroker era,
956  const extended::YearMonthTuple& startYm,
957  const extended::YearMonthTuple& untilYm) {
958  extended::DateTuple startDate = {
959  prev.untilYearTiny(), prev.untilMonth(), prev.untilDay(),
960  (int8_t) prev.untilTimeCode(), prev.untilTimeModifier()
961  };
962  extended::DateTuple lowerBound = {
963  startYm.yearTiny, startYm.month, 1, 0, 'w'
964  };
965  if (startDate < lowerBound) {
966  startDate = lowerBound;
967  }
968 
969  extended::DateTuple untilDate = {
970  era.untilYearTiny(), era.untilMonth(), era.untilDay(),
971  (int8_t) era.untilTimeCode(), era.untilTimeModifier()
972  };
973  extended::DateTuple upperBound = {
974  untilYm.yearTiny, untilYm.month, 1, 0, 'w'
975  };
976  if (upperBound < untilDate) {
977  untilDate = upperBound;
978  }
979 
980  return {startDate, untilDate, era};
981  }
982 
987  static void findTransitions(
989  extended::ZoneMatch* matches,
990  uint8_t numMatches) {
991  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
992  logging::println("findTransitions()");
993  }
994  for (uint8_t i = 0; i < numMatches; i++) {
995  findTransitionsForMatch(transitionStorage, &matches[i]);
996  }
997  }
998 
1000  static void findTransitionsForMatch(
1002  const extended::ZoneMatch* match) {
1003  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1004  logging::println("findTransitionsForMatch()");
1005  }
1006  const extended::ZonePolicyBroker policy = match->era.zonePolicy();
1007  if (policy.isNull()) {
1008  findTransitionsFromSimpleMatch(transitionStorage, match);
1009  } else {
1010  findTransitionsFromNamedMatch(transitionStorage, match);
1011  }
1012  }
1013 
1014  static void findTransitionsFromSimpleMatch(
1016  const extended::ZoneMatch* match) {
1017  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1018  logging::println("findTransitionsFromSimpleMatch()");
1019  }
1020  extended::Transition* freeTransition = transitionStorage.getFreeAgent();
1021  createTransitionForYear(freeTransition, 0 /*not used*/,
1022  extended::ZoneRuleBroker(nullptr) /*rule*/, match);
1023  transitionStorage.addFreeAgentToActivePool();
1024  }
1025 
1026  static void findTransitionsFromNamedMatch(
1028  const extended::ZoneMatch* match) {
1029  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1030  logging::println("findTransitionsFromNamedMatch()");
1031  }
1032  transitionStorage.resetCandidatePool();
1033  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1034  match->log(); logging::println();
1035  }
1036  findCandidateTransitions(transitionStorage, match);
1037  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1038  transitionStorage.log(); logging::println();
1039  }
1040  fixTransitionTimes(
1041  transitionStorage.getCandidatePoolBegin(),
1042  transitionStorage.getCandidatePoolEnd());
1043  selectActiveTransitions(transitionStorage, match);
1044  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1045  transitionStorage.log(); logging::println();
1046  }
1047 
1048  transitionStorage.addActiveCandidatesToActivePool();
1049  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1050  transitionStorage.log(); logging::println();
1051  }
1052  }
1053 
1054  static void findCandidateTransitions(
1056  const extended::ZoneMatch* match) {
1057  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1058  logging::print("findCandidateTransitions(): ");
1059  match->log();
1060  logging::println();
1061  }
1062  const extended::ZonePolicyBroker policy = match->era.zonePolicy();
1063  uint8_t numRules = policy.numRules();
1064  int8_t startY = match->startDateTime.yearTiny;
1065  int8_t endY = match->untilDateTime.yearTiny;
1066 
1067  extended::Transition** prior = transitionStorage.reservePrior();
1068  (*prior)->active = false; // indicates "no prior transition"
1069  for (uint8_t r = 0; r < numRules; r++) {
1070  const extended::ZoneRuleBroker rule = policy.rule(r);
1071 
1072  // Add Transitions for interior years
1073  int8_t interiorYears[kMaxInteriorYears];
1074  uint8_t numYears = calcInteriorYears(interiorYears, kMaxInteriorYears,
1075  rule.fromYearTiny(), rule.toYearTiny(), startY, endY);
1076  for (uint8_t y = 0; y < numYears; y++) {
1077  int8_t year = interiorYears[y];
1078  extended::Transition* t = transitionStorage.getFreeAgent();
1079  createTransitionForYear(t, year, rule, match);
1080  int8_t status = compareTransitionToMatchFuzzy(t, match);
1081  if (status < 0) {
1082  setAsPriorTransition(transitionStorage, t);
1083  } else if (status == 1) {
1084  transitionStorage.addFreeAgentToCandidatePool();
1085  }
1086  }
1087 
1088  // Add Transition for prior year
1089  int8_t priorYear = getMostRecentPriorYear(
1090  rule.fromYearTiny(), rule.toYearTiny(), startY, endY);
1091  if (priorYear != LocalDate::kInvalidYearTiny) {
1092  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1093  logging::println(
1094  "findCandidateTransitions(): priorYear: %d", priorYear);
1095  }
1096  extended::Transition* t = transitionStorage.getFreeAgent();
1097  createTransitionForYear(t, priorYear, rule, match);
1098  setAsPriorTransition(transitionStorage, t);
1099  }
1100  }
1101 
1102  // Add the reserved prior into the Candidate pool only if 'active' is
1103  // true, meaning that a prior was found.
1104  if ((*prior)->active) {
1105  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1106  logging::println(
1107  "findCandidateTransitions(): adding prior to Candidate pool");
1108  }
1109  transitionStorage.addPriorToCandidatePool();
1110  }
1111  }
1112 
1117  static uint8_t calcInteriorYears(int8_t* interiorYears,
1118  uint8_t maxInteriorYears, int8_t fromYear, int8_t toYear,
1119  int8_t startYear, int8_t endYear) {
1120  uint8_t i = 0;
1121  for (int8_t year = startYear; year <= endYear; year++) {
1122  if (fromYear <= year && year <= toYear) {
1123  interiorYears[i] = year;
1124  i++;
1125  if (i >= maxInteriorYears) break;
1126  }
1127  }
1128  return i;
1129  }
1130 
1137  static void createTransitionForYear(extended::Transition* t, int8_t year,
1138  const extended::ZoneRuleBroker rule,
1139  const extended::ZoneMatch* match) {
1140  t->match = match;
1141  t->rule = rule;
1142  t->offsetCode = match->era.offsetCode();
1143  t->letterBuf[0] = '\0';
1144 
1145  if (rule.isNotNull()) {
1146  t->transitionTime = getTransitionTime(year, rule);
1147  t->deltaCode = rule.deltaCode();
1148 
1149  char letter = rule.letter();
1150  if (letter >= 32) {
1151  // If LETTER is a '-', treat it the same as an empty string.
1152  if (letter != '-') {
1153  t->letterBuf[0] = letter;
1154  t->letterBuf[1] = '\0';
1155  }
1156  } else {
1157  // rule->letter is a long string, so is referenced as an offset index
1158  // into the ZonePolicy.letters array. The string cannot fit in
1159  // letterBuf, so will be retrieved by the letter() method below.
1160  }
1161  } else {
1162  t->transitionTime = match->startDateTime;
1163  t->deltaCode = match->era.deltaCode();
1164  }
1165  }
1166 
1172  static int8_t getMostRecentPriorYear(int8_t fromYear, int8_t toYear,
1173  int8_t startYear, int8_t /*endYear*/) {
1174  if (fromYear < startYear) {
1175  if (toYear < startYear) {
1176  return toYear;
1177  } else {
1178  return startYear - 1;
1179  }
1180  } else {
1182  }
1183  }
1184 
1185  static extended::DateTuple getTransitionTime(
1186  int8_t yearTiny, const extended::ZoneRuleBroker rule) {
1188  yearTiny + LocalDate::kEpochYear, rule.inMonth(), rule.onDayOfWeek(),
1189  rule.onDayOfMonth());
1190  return {yearTiny, monthDay.month, monthDay.day,
1191  (int8_t) rule.atTimeCode(), rule.atTimeModifier()};
1192  }
1193 
1204  static int8_t compareTransitionToMatchFuzzy(
1205  const extended::Transition* t, const extended::ZoneMatch* match) {
1206  int16_t ttMonths = t->transitionTime.yearTiny * 12
1207  + t->transitionTime.month;
1208 
1209  int16_t matchStartMonths = match->startDateTime.yearTiny * 12
1210  + match->startDateTime.month;
1211  if (ttMonths < matchStartMonths - 1) return -1;
1212 
1213  int16_t matchUntilMonths = match->untilDateTime.yearTiny * 12
1214  + match->untilDateTime.month;
1215  if (matchUntilMonths + 2 <= ttMonths) return 2;
1216 
1217  return 1;
1218  }
1219 
1221  static void setAsPriorTransition(
1223  extended::Transition* t) {
1224  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1225  logging::println("setAsPriorTransition()");
1226  }
1227  extended::Transition* prior = transitionStorage.getPrior();
1228  if (prior->active) {
1229  if (prior->transitionTime < t->transitionTime) {
1230  t->active = true;
1231  transitionStorage.setFreeAgentAsPrior();
1232  }
1233  } else {
1234  t->active = true;
1235  transitionStorage.setFreeAgentAsPrior();
1236  }
1237  }
1238 
1247  static void fixTransitionTimes(
1248  extended::Transition** begin, extended::Transition** end) {
1249  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1250  logging::println("fixTransitionTimes(): #transitions: %d;",
1251  (int) (end - begin));
1252  }
1253 
1254  // extend first Transition to -infinity
1255  extended::Transition* prev = *begin;
1256 
1257  for (extended::Transition** iter = begin; iter != end; ++iter) {
1258  extended::Transition* curr = *iter;
1259  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1260  logging::println("fixTransitionTimes(): LOOP");
1261  curr->log();
1262  logging::println();
1263  }
1264  expandDateTuple(&curr->transitionTime,
1265  &curr->transitionTimeS, &curr->transitionTimeU,
1266  prev->offsetCode, prev->deltaCode);
1267  prev = curr;
1268  }
1269  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1270  logging::println("fixTransitionTimes(): END");
1271  }
1272  }
1273 
1279  static void expandDateTuple(extended::DateTuple* tt,
1281  int8_t offsetCode, int8_t deltaCode) {
1282  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1283  logging::println("expandDateTuple()");
1284  }
1285  if (tt->modifier == 's') {
1286  *tts = *tt;
1287  *ttu = {tt->yearTiny, tt->month, tt->day,
1288  (int8_t) (tt->timeCode - offsetCode), 'u'};
1289  *tt = {tt->yearTiny, tt->month, tt->day,
1290  (int8_t) (tt->timeCode + deltaCode), 'w'};
1291  } else if (tt->modifier == 'u') {
1292  *ttu = *tt;
1293  *tts = {tt->yearTiny, tt->month, tt->day,
1294  (int8_t) (tt->timeCode + offsetCode), 's'};
1295  *tt = {tt->yearTiny, tt->month, tt->day,
1296  (int8_t) (tt->timeCode + offsetCode + deltaCode), 'w'};
1297  } else {
1298  // Explicit set the modifier to 'w' in case it was something else.
1299  tt->modifier = 'w';
1300  *tts = {tt->yearTiny, tt->month, tt->day,
1301  (int8_t) (tt->timeCode - deltaCode), 's'};
1302  *ttu = {tt->yearTiny, tt->month, tt->day,
1303  (int8_t) (tt->timeCode - deltaCode - offsetCode), 'u'};
1304  }
1305 
1306  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1307  logging::println("expandDateTuple(): normalizeDateTuple(): 1");
1308  }
1309  normalizeDateTuple(tt);
1310  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1311  logging::println("expandDateTuple(): normalizeDateTuple(): 2");
1312  }
1313  normalizeDateTuple(tts);
1314  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1315  logging::println("expandDateTuple(): normalizeDateTuple(): 3");
1316  }
1317  normalizeDateTuple(ttu);
1318  }
1319 
1321  static void normalizeDateTuple(extended::DateTuple* dt) {
1322  const int8_t kOneDayAsCode = 4 * 24;
1323  if (dt->timeCode <= -kOneDayAsCode) {
1325  dt->yearTiny, dt->month, dt->day);
1326  local_date_mutation::decrementOneDay(ld);
1327  dt->yearTiny = ld.yearTiny();
1328  dt->month = ld.month();
1329  dt->day = ld.day();
1330  dt->timeCode += kOneDayAsCode;
1331  } else if (kOneDayAsCode <= dt->timeCode) {
1333  dt->yearTiny, dt->month, dt->day);
1334  local_date_mutation::incrementOneDay(ld);
1335  dt->yearTiny = ld.yearTiny();
1336  dt->month = ld.month();
1337  dt->day = ld.day();
1338  dt->timeCode -= kOneDayAsCode;
1339  } else {
1340  // do nothing
1341  }
1342  }
1343 
1348  static void selectActiveTransitions(
1350  const extended::ZoneMatch* match) {
1351  extended::Transition** begin = transitionStorage.getCandidatePoolBegin();
1352  extended::Transition** end = transitionStorage.getCandidatePoolEnd();
1353  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1354  logging::println("selectActiveTransitions(): #candidates: %d",
1355  (int) (end - begin));
1356  }
1357  extended::Transition* prior = nullptr;
1358  for (extended::Transition** iter = begin; iter != end; ++iter) {
1359  extended::Transition* transition = *iter;
1360  processActiveTransition(match, transition, &prior);
1361  }
1362 
1363  // If the latest prior transition is found, shift it to start at the
1364  // startDateTime of the current match.
1365  if (prior) {
1366  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1367  logging::println(
1368  "selectActiveTransitions(): found latest prior");
1369  }
1370  prior->originalTransitionTime = prior->transitionTime;
1371  prior->transitionTime = match->startDateTime;
1372  }
1373  }
1374 
1382  static void processActiveTransition(
1383  const extended::ZoneMatch* match,
1384  extended::Transition* transition,
1385  extended::Transition** prior) {
1386  int8_t status = compareTransitionToMatch(transition, match);
1387  if (status == 2) {
1388  transition->active = false;
1389  } else if (status == 1) {
1390  transition->active = true;
1391  } else if (status == 0) {
1392  if (*prior) {
1393  (*prior)->active = false;
1394  }
1395  transition->active = true;
1396  (*prior) = transition;
1397  } else { // (status < 0)
1398  if (*prior) {
1399  if ((*prior)->transitionTime < transition->transitionTime) {
1400  (*prior)->active = false;
1401  transition->active = true;
1402  (*prior) = transition;
1403  }
1404  } else {
1405  transition->active = true;
1406  (*prior) = transition;
1407  }
1408  }
1409  }
1410 
1425  static int8_t compareTransitionToMatch(
1426  const extended::Transition* transition,
1427  const extended::ZoneMatch* match) {
1428  const extended::DateTuple* transitionTime;
1429 
1430  const extended::DateTuple& matchStart = match->startDateTime;
1431  if (matchStart.modifier == 's') {
1432  transitionTime = &transition->transitionTimeS;
1433  } else if (matchStart.modifier == 'u') {
1434  transitionTime = &transition->transitionTimeU;
1435  } else { // assume 'w'
1436  transitionTime = &transition->transitionTime;
1437  }
1438  if (*transitionTime < matchStart) return -1;
1439  if (*transitionTime == matchStart) return 0;
1440 
1441  const extended::DateTuple& matchUntil = match->untilDateTime;
1442  if (matchUntil.modifier == 's') {
1443  transitionTime = &transition->transitionTimeS;
1444  } else if (matchUntil.modifier == 'u') {
1445  transitionTime = &transition->transitionTimeU;
1446  } else { // assume 'w'
1447  transitionTime = &transition->transitionTime;
1448  }
1449  if (*transitionTime < matchUntil) return 1;
1450  return 2;
1451  }
1452 
1458  static void generateStartUntilTimes(
1459  extended::Transition** begin, extended::Transition** end) {
1460  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1461  logging::println(
1462  "generateStartUntilTimes(): #transitions: %d;",
1463  (int) (end - begin));
1464  }
1465 
1466  extended::Transition* prev = *begin;
1467  bool isAfterFirst = false;
1468 
1469  for (extended::Transition** iter = begin; iter != end; ++iter) {
1470  extended::Transition* const t = *iter;
1471 
1472  // 1) Update the untilDateTime of the previous Transition
1473  const extended::DateTuple& tt = t->transitionTime;
1474  if (isAfterFirst) {
1475  prev->untilDateTime = tt;
1476  }
1477 
1478  // 2) Calculate the current startDateTime by shifting the
1479  // transitionTime (represented in the UTC offset of the previous
1480  // transition) into the UTC offset of the *current* transition.
1481  int8_t code = tt.timeCode - prev->offsetCode - prev->deltaCode
1482  + t->offsetCode + t->deltaCode;
1483  t->startDateTime = {tt.yearTiny, tt.month, tt.day, code, tt.modifier};
1484  normalizeDateTuple(&t->startDateTime);
1485 
1486  // 3) The epochSecond of the 'transitionTime' is determined by the
1487  // UTC offset of the *previous* Transition. However, the
1488  // transitionTime can be represented by an illegal time (e.g. 24:00).
1489  // So, it is better to use the properly normalized startDateTime
1490  // (calculated above) with the *current* UTC offset.
1491  //
1492  // NOTE: We should also be able to calculate this directly from
1493  // 'transitionTimeU' which should still be a valid field, because it
1494  // hasn't been clobbered by 'untilDateTime' yet. Not sure if this saves
1495  // any CPU time though, since we still need to mutiply by 900.
1496  const extended::DateTuple& st = t->startDateTime;
1497  const acetime_t offsetSeconds = (acetime_t) 900
1498  * (st.timeCode - t->offsetCode - t->deltaCode);
1500  st.yearTiny, st.month, st.day);
1501  t->startEpochSeconds = ld.toEpochSeconds() + offsetSeconds;
1502 
1503  prev = t;
1504  isAfterFirst = true;
1505  }
1506 
1507  // The last Transition's until time is the until time of the ZoneMatch.
1508  extended::DateTuple untilTime = prev->match->untilDateTime;
1509  extended::DateTuple untilTimeS; // needed only for expandDateTuple
1510  extended::DateTuple untilTimeU; // needed only for expandDateTuple
1511  expandDateTuple(&untilTime, &untilTimeS, &untilTimeU,
1512  prev->offsetCode, prev->deltaCode);
1513  prev->untilDateTime = untilTime;
1514  }
1515 
1519  static void calcAbbreviations(
1520  extended::Transition** begin, extended::Transition** end) {
1521  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1522  logging::println("calcAbbreviations(): #transitions: %d;",
1523  (int) (end - begin));
1524  }
1525  for (extended::Transition** iter = begin; iter != end; ++iter) {
1526  extended::Transition* const t = *iter;
1527  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
1528  logging::println(
1529  "calcAbbreviations(): format:%s, deltaCode:%d, letter:%s",
1530  t->format(), t->deltaCode, t->letter());
1531  }
1532  createAbbreviation(t->abbrev, extended::Transition::kAbbrevSize,
1533  t->format(), t->deltaCode, t->letter());
1534  }
1535  }
1536 
1571  static void createAbbreviation(char* dest, uint8_t destSize,
1572  const char* format, uint8_t deltaCode, const char* letterString) {
1573  // Check if RULES column is empty. Ignore the deltaCode because if
1574  // letterString is nullptr, we can only just copy the whole thing.
1575  if (letterString == nullptr) {
1576  strncpy(dest, format, destSize);
1577  dest[destSize - 1] = '\0';
1578  return;
1579  }
1580 
1581  // Check if FORMAT contains a '%'.
1582  if (strchr(format, '%') != nullptr) {
1583  copyAndReplace(dest, destSize, format, '%', letterString);
1584  } else {
1585  // Check if FORMAT contains a '/'.
1586  const char* slashPos = strchr(format, '/');
1587  if (slashPos != nullptr) {
1588  if (deltaCode == 0) {
1589  uint8_t headLength = (slashPos - format);
1590  if (headLength >= destSize) headLength = destSize - 1;
1591  memcpy(dest, format, headLength);
1592  dest[headLength] = '\0';
1593  } else {
1594  uint8_t tailLength = strlen(slashPos+1);
1595  if (tailLength >= destSize) tailLength = destSize - 1;
1596  memcpy(dest, slashPos+1, tailLength);
1597  dest[tailLength] = '\0';
1598  }
1599  } else {
1600  // Just copy the FORMAT disregarding deltaCode and letterString.
1601  strncpy(dest, format, destSize);
1602  dest[destSize - 1] = '\0';
1603  }
1604  }
1605  }
1606 
1612  static void copyAndReplace(char* dst, uint8_t dstSize, const char* src,
1613  char oldChar, const char* newString) {
1614  while (*src != '\0' && dstSize > 0) {
1615  if (*src == oldChar) {
1616  while (*newString != '\0' && dstSize > 0) {
1617  *dst++ = *newString++;
1618  dstSize--;
1619  }
1620  src++;
1621  } else {
1622  *dst++ = *src++;
1623  dstSize--;
1624  }
1625  }
1626 
1627  if (dstSize == 0) {
1628  --dst;
1629  }
1630  *dst = '\0';
1631  }
1632 
1633  extended::ZoneInfoBroker mZoneInfo;
1634 
1635  mutable int16_t mYear = 0; // maybe create LocalDate::kInvalidYear?
1636  mutable bool mIsFilled = false;
1637  // NOTE: Maybe move mNumMatches and mMatches into a MatchStorage object.
1638  mutable uint8_t mNumMatches = 0; // actual number of matches
1639  mutable extended::ZoneMatch mMatches[kMaxMatches];
1640  mutable extended::TransitionStorage<kMaxTransitions> mTransitionStorage;
1641 };
1642 
1643 } // namespace ace_time
1644 
1645 #endif
void addFreeAgentToActivePool()
Immediately add the free agent Transition at index mIndexFree to the Active pool. ...
static TimeOffset forError()
Return an error indicator.
Definition: TimeOffset.h:110
Base interface for ZoneProcessor classes.
Definition: ZoneProcessor.h:45
-
ZoneEraBroker era
The ZoneEra that matched the given year.
-
A heap manager which is specialized and tuned to manage a collection of Transitions, keeping track of unused, used, and active states, using a fixed array of Transitions.
+
ZoneEraBroker era
The ZoneEra that matched the given year.
+
A heap manager which is specialized and tuned to manage a collection of Transitions, keeping track of unused, used, and active states, using a fixed array of Transitions.
uint8_t minute() const
Return the minute.
-
void addActiveCandidatesToActivePool()
Add active candidates into the Active pool, and collapse the Candidate pool.
-
DateTuple transitionTimeU
Version of transitionTime in &#39;u&#39; mode, using the UTC offset of the previous transition.
-
void resetCandidatePool()
Empty the Candidate pool by resetting the various indexes.
-
char letterBuf[2]
Storage for the single letter &#39;letter&#39; field if &#39;rule&#39; is not null.
+
void addActiveCandidatesToActivePool()
Add active candidates into the Active pool, and collapse the Candidate pool.
+
DateTuple transitionTimeU
Version of transitionTime in &#39;u&#39; mode, using the UTC offset of the previous transition.
+
void resetCandidatePool()
Empty the Candidate pool by resetting the various indexes.
+
char letterBuf[2]
Storage for the single letter &#39;letter&#39; field if &#39;rule&#39; is not null.
int8_t yearTiny() const
Return the single-byte year offset from year 2000.
Definition: LocalDate.h:225
-
DateTuple startDateTime
The effective start time of the matching ZoneEra.
-
Transition * getFreeAgent()
Return a pointer to the first Transition in the free pool.
-
A cache of ZoneProcessors that provides a ZoneProcessor to the TimeZone upon request.
-
static const uint8_t kAbbrevSize
Longest abbreviation currently seems to be 5 characters (https://www.timeanddate.com/time/zones/) but...
-
const ZoneMatch * match
The match which generated this Transition.
-
uint8_t getHighWater() const
Return the high water mark.
+
DateTuple startDateTime
The effective start time of the matching ZoneEra.
+
Transition * getFreeAgent()
Return a pointer to the first Transition in the free pool.
+
A cache of ZoneProcessors that provides a ZoneProcessor to the TimeZone upon request.
+
static const uint8_t kAbbrevSize
Longest abbreviation currently seems to be 5 characters (https://www.timeanddate.com/time/zones/) but...
+
const ZoneMatch * match
The match which generated this Transition.
+
uint8_t getHighWater() const
Return the high water mark.
bool isError() const
Return true if this TimeOffset represents an error.
Definition: TimeOffset.h:155
- +
static OffsetDateTime forLocalDateTimeAndOffset(const LocalDateTime &localDateTime, TimeOffset timeOffset)
Factory method from LocalDateTime and TimeOffset.
-
DateTuple originalTransitionTime
If the transition is shifted to the beginning of a ZoneMatch, this is set to the transitionTime for d...
+
DateTuple originalTransitionTime
If the transition is shifted to the beginning of a ZoneMatch, this is set to the transitionTime for d...
uint8_t day() const
Return the day of the month.
int8_t yearTiny() const
Return the single-byte year offset from year 2000.
-
The result of calcStartDayOfMonth().
+
The result of calcStartDayOfMonth().
virtual const void * getZoneInfo() const =0
Return the opaque zoneInfo.
-
Represents an interval of time where the time zone obeyed a certain UTC offset and DST delta...
-
void log() const
Verify that the indexes are valid.
-
const void * getZoneInfo() const override
Return the underlying ZoneInfo.
-
void addPriorToCandidatePool()
Add the current prior into the Candidates pool.
+
Represents an interval of time where the time zone obeyed a certain UTC offset and DST delta...
+
void log() const
Verify that the indexes are valid.
+
const void * getZoneInfo() const override
Return the underlying ZoneInfo.
+
void addPriorToCandidatePool()
Add the current prior into the Candidates pool.
-
const char * getAbbrev(acetime_t epochSeconds) const override
Return the time zone abbreviation at epochSeconds.
+
const char * getAbbrev(acetime_t epochSeconds) const override
Return the time zone abbreviation at epochSeconds.
Representation of a given time zone, implemented as an array of ZoneEra records.
Definition: ZoneInfo.h:77
-
void setFreeAgentAsPrior()
Swap the Free agrent transition with the current Prior transition.
-
Transition * getPrior()
Return the current prior transition.
+
void setFreeAgentAsPrior()
Swap the Free agrent transition with the current Prior transition.
+
Transition * getPrior()
Return the current prior transition.
static LocalDate forTinyComponents(int8_t yearTiny, uint8_t month, uint8_t day)
Factory method using components with an int8_t yearTiny.
Definition: LocalDate.h:101
-
TimeOffset getUtcOffset(acetime_t epochSeconds) const override
Return the total UTC offset at epochSeconds, including DST offset.
-
char abbrev[kAbbrevSize]
The calculated effective time zone abbreviation, e.g.
-
const char * letter() const
Return the letter string.
+
TimeOffset getUtcOffset(acetime_t epochSeconds) const override
Return the total UTC offset at epochSeconds, including DST offset.
+
char abbrev[kAbbrevSize]
The calculated effective time zone abbreviation, e.g.
+
const char * letter() const
Return the letter string.
An entry in ZoneInfo which describes which ZonePolicy was being followed during a particular time per...
Definition: ZoneInfo.h:20
uint8_t day() const
Return the day of the month.
Definition: LocalDate.h:237
uint8_t month() const
Return the month with January=1, December=12.
- +
static OffsetDateTime forEpochSeconds(acetime_t epochSeconds, TimeOffset timeOffset)
Factory method.
-
void addFreeAgentToCandidatePool()
Add the free agent Transition at index mIndexFree to the Candidate pool, sorted by transitionTime...
-
DateTuple transitionTime
The original transition time, usually &#39;w&#39; but sometimes &#39;s&#39; or &#39;u&#39;.
+
void addFreeAgentToCandidatePool()
Add the free agent Transition at index mIndexFree to the Candidate pool, sorted by transitionTime...
+
DateTuple transitionTime
The original transition time, usually &#39;w&#39; but sometimes &#39;s&#39; or &#39;u&#39;.
acetime_t toEpochSeconds() const
Return the number of seconds since AceTime epoch (2000-01-01 00:00:00).
Definition: LocalDate.h:320
const LocalDate & localDate() const
Return the LocalDate.
-
static const uint8_t kAbbrevSize
Size of the timezone abbreviation.
-
An implementation of ZoneProcessor that works for all zones defined by the TZ Database (with some zon...
-
const Transition * findTransitionForDateTime(const LocalDateTime &ldt) const
Return the Transition matching the given dateTime.
+
static const uint8_t kAbbrevSize
Size of the timezone abbreviation.
+
An implementation of ZoneProcessor that works for all zones defined by the TZ Database (with some zon...
+
const Transition * findTransitionForDateTime(const LocalDateTime &ldt) const
Return the Transition matching the given dateTime.
uint8_t hour() const
Return the hour.
-
TimeOffset getDeltaOffset(acetime_t epochSeconds) const override
Return the DST delta offset at epochSeconds.
-
DateTuple transitionTimeS
Version of transitionTime in &#39;s&#39; mode, using the UTC offset of the previous Transition.
-
acetime_t startEpochSeconds
The calculated transition time of the given rule.
-
A simple tuple to represent a year/month pair.
+
TimeOffset getDeltaOffset(acetime_t epochSeconds) const override
Return the DST delta offset at epochSeconds.
+
DateTuple transitionTimeS
Version of transitionTime in &#39;s&#39; mode, using the UTC offset of the previous Transition.
+
acetime_t startEpochSeconds
The calculated transition time of the given rule.
+
A simple tuple to represent a year/month pair.
static LocalDate forEpochSeconds(acetime_t epochSeconds)
Factory method using the number of seconds since AceTime epoch of 2000-01-01.
Definition: LocalDate.h:145
static TimeOffset forOffsetCode(int8_t offsetCode)
Create TimeOffset from the offset code.
Definition: TimeOffset.h:117
-
void resetTransitionHighWater()
Reset the TransitionStorage high water mark.
-
bool active
Flag used for 2 slightly different meanings at different stages of init() processing.
-
uint32_t getZoneId() const override
Return the unique stable zoneId.
+
void resetTransitionHighWater()
Reset the TransitionStorage high water mark.
+
bool active
Flag used for 2 slightly different meanings at different stages of init() processing.
+
uint32_t getZoneId() const override
Return the unique stable zoneId.
The date (year, month, day), time (hour, minute, second) and offset from UTC (timeOffset).
void log() const
Used only for debugging.
-
Transition ** reservePrior()
Allocate one Transition just after the Active pool, but before the Candidate pool, to keep the most recent prior Transition.
+
Transition ** reservePrior()
Allocate one Transition just after the Active pool, but before the Candidate pool, to keep the most recent prior Transition.
static const int8_t kInvalidYearTiny
Sentinel yearTiny which indicates an error condition or sometimes a year that &#39;does not exist&#39;...
Definition: LocalDate.h:45
-
void resetHighWater()
Reset the high water mark.
+
void resetHighWater()
Reset the high water mark.
Macros and definitions that provide a consistency layer among the various Arduino boards for compatib...
-
void log() const
Used only for debugging.
+
void log() const
Used only for debugging.
A thin wrapper that represents a time offset from a reference point, usually 00:00 at UTC...
Definition: TimeOffset.h:58
-
int8_t deltaCode
The DST delta code.
-
void swap(Transition **a, Transition **b)
Swap 2 transitions.
+
int8_t deltaCode
The DST delta code.
+
void swap(Transition **a, Transition **b)
Swap 2 transitions.
The date (year, month, day) representing the date without regards to time zone.
Definition: LocalDate.h:36
-
DateTuple untilDateTime
Until time expressed using the UTC offset of the current Transition.
-
void log() const
Used only for debugging.
-
ExtendedZoneProcessor(const extended::ZoneInfo *zoneInfo=nullptr)
Constructor.
-
uint8_t getTransitionHighWater() const
Get the TransitionStorage high water mark.
+
DateTuple untilDateTime
Until time expressed using the UTC offset of the current Transition.
+
void log() const
Used only for debugging.
+
ExtendedZoneProcessor(const extended::ZoneInfo *zoneInfo=nullptr)
Constructor.
+
uint8_t getTransitionHighWater() const
Get the TransitionStorage high water mark.
static const int16_t kEpochYear
Base year of epoch.
Definition: LocalDate.h:39
-
DateTuple untilDateTime
The effective until time of the matching ZoneEra.
-
DateTuple startDateTime
Start time expressed using the UTC offset of the current Transition.
+
DateTuple untilDateTime
The effective until time of the matching ZoneEra.
+
DateTuple startDateTime
Start time expressed using the UTC offset of the current Transition.
uint8_t month() const
Return the month with January=1, December=12.
Definition: LocalDate.h:231
int16_t year() const
Return the full year instead of just the last 2 digits.
Definition: LocalDate.h:219
-
static basic::MonthDay calcStartDayOfMonth(int16_t year, uint8_t month, uint8_t onDayOfWeek, int8_t onDayOfMonth)
Calculate the actual (month, day) of the expresssion (onDayOfWeek >= onDayOfMonth) or (onDayOfWeek <=...
+
static basic::MonthDay calcStartDayOfMonth(int16_t year, uint8_t month, uint8_t onDayOfWeek, int8_t onDayOfMonth)
Calculate the actual (month, day) of the expresssion (onDayOfWeek >= onDayOfMonth) or (onDayOfWeek <=...
A tuple that represents a date and time, using a timeCode that tracks the time component using 15-min...
Data broker for accessing ZonePolicy in PROGMEM.
Definition: Brokers.h:297
-
const Transition * findTransition(acetime_t epochSeconds) const
Return the Transition matching the given epochSeconds.
-
OffsetDateTime getOffsetDateTime(const LocalDateTime &ldt) const override
Return the best estimate of the OffsetDateTime at the given LocalDateTime for the timezone of the cur...
-
int8_t offsetCode
The base offset code, not the total effective UTC offset.
-
ZoneRuleBroker rule
The Zone transition rule that matched for the the given year.
-
Data structure that captures the matching ZoneEra and its ZoneRule transitions for a given year...
+
const Transition * findTransition(acetime_t epochSeconds) const
Return the Transition matching the given epochSeconds.
+
OffsetDateTime getOffsetDateTime(const LocalDateTime &ldt) const override
Return the best estimate of the OffsetDateTime at the given LocalDateTime for the timezone of the cur...
+
int8_t offsetCode
The base offset code, not the total effective UTC offset.
+
ZoneRuleBroker rule
The Zone transition rule that matched for the the given year.
+
Data structure that captures the matching ZoneEra and its ZoneRule transitions for a given year...
Class that holds the date-time as the components (year, month, day, hour, minute, second) without reg...
Definition: LocalDateTime.h:27
diff --git a/docs/html/ExtendedZone_8h_source.html b/docs/html/ExtendedZone_8h_source.html index 3075c96a7..4059717f4 100644 --- a/docs/html/ExtendedZone_8h_source.html +++ b/docs/html/ExtendedZone_8h_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/HardwareDateTime_8cpp_source.html b/docs/html/HardwareDateTime_8cpp_source.html index 0ac561095..1217d6b5e 100644 --- a/docs/html/HardwareDateTime_8cpp_source.html +++ b/docs/html/HardwareDateTime_8cpp_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/HardwareDateTime_8h_source.html b/docs/html/HardwareDateTime_8h_source.html index 8dadb5a0a..f49f0d868 100644 --- a/docs/html/HardwareDateTime_8h_source.html +++ b/docs/html/HardwareDateTime_8h_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/HardwareTemperature_8h_source.html b/docs/html/HardwareTemperature_8h_source.html index 92fd0cdb2..0beea2714 100644 --- a/docs/html/HardwareTemperature_8h_source.html +++ b/docs/html/HardwareTemperature_8h_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/LocalDateTime_8cpp_source.html b/docs/html/LocalDateTime_8cpp_source.html index 8f7400fd6..4de5dfeb2 100644 --- a/docs/html/LocalDateTime_8cpp_source.html +++ b/docs/html/LocalDateTime_8cpp_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/LocalDateTime_8h_source.html b/docs/html/LocalDateTime_8h_source.html index 3c62ad7c2..ba225481f 100644 --- a/docs/html/LocalDateTime_8h_source.html +++ b/docs/html/LocalDateTime_8h_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/LocalDate_8cpp_source.html b/docs/html/LocalDate_8cpp_source.html index 3d188468c..0c796d601 100644 --- a/docs/html/LocalDate_8cpp_source.html +++ b/docs/html/LocalDate_8cpp_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/LocalDate_8h_source.html b/docs/html/LocalDate_8h_source.html index 7f4b5bff5..7beb4a092 100644 --- a/docs/html/LocalDate_8h_source.html +++ b/docs/html/LocalDate_8h_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/LocalTime_8cpp_source.html b/docs/html/LocalTime_8cpp_source.html index 427f68d66..cf17a7c7c 100644 --- a/docs/html/LocalTime_8cpp_source.html +++ b/docs/html/LocalTime_8cpp_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/LocalTime_8h_source.html b/docs/html/LocalTime_8h_source.html index d20013965..d26e99a61 100644 --- a/docs/html/LocalTime_8h_source.html +++ b/docs/html/LocalTime_8h_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/NtpTimeProvider_8cpp_source.html b/docs/html/NtpTimeProvider_8cpp_source.html index 8d98fbca3..8a38acf1f 100644 --- a/docs/html/NtpTimeProvider_8cpp_source.html +++ b/docs/html/NtpTimeProvider_8cpp_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/NtpTimeProvider_8h_source.html b/docs/html/NtpTimeProvider_8h_source.html index 7e4421833..d77c99a8b 100644 --- a/docs/html/NtpTimeProvider_8h_source.html +++ b/docs/html/NtpTimeProvider_8h_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/OffsetDateTime_8cpp_source.html b/docs/html/OffsetDateTime_8cpp_source.html index f259d16a2..092a7b13d 100644 --- a/docs/html/OffsetDateTime_8cpp_source.html +++ b/docs/html/OffsetDateTime_8cpp_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/OffsetDateTime_8h_source.html b/docs/html/OffsetDateTime_8h_source.html index 2c0ffe0bf..aae93c11b 100644 --- a/docs/html/OffsetDateTime_8h_source.html +++ b/docs/html/OffsetDateTime_8h_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/README_8md_source.html b/docs/html/README_8md_source.html index 69b221c20..9f3824691 100644 --- a/docs/html/README_8md_source.html +++ b/docs/html/README_8md_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/SystemClockSyncCoroutine_8h_source.html b/docs/html/SystemClockSyncCoroutine_8h_source.html index 6d6cd958b..866c0b0cb 100644 --- a/docs/html/SystemClockSyncCoroutine_8h_source.html +++ b/docs/html/SystemClockSyncCoroutine_8h_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/SystemClockSyncLoop_8h_source.html b/docs/html/SystemClockSyncLoop_8h_source.html index 0487219f5..96c306bcd 100644 --- a/docs/html/SystemClockSyncLoop_8h_source.html +++ b/docs/html/SystemClockSyncLoop_8h_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/SystemClock_8h_source.html b/docs/html/SystemClock_8h_source.html index 5893ea6e2..34bc53875 100644 --- a/docs/html/SystemClock_8h_source.html +++ b/docs/html/SystemClock_8h_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/TimeKeeper_8h_source.html b/docs/html/TimeKeeper_8h_source.html index 7a6f7fcc3..28e25eca6 100644 --- a/docs/html/TimeKeeper_8h_source.html +++ b/docs/html/TimeKeeper_8h_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/TimeOffset_8cpp_source.html b/docs/html/TimeOffset_8cpp_source.html index 93fd44278..0bbc59189 100644 --- a/docs/html/TimeOffset_8cpp_source.html +++ b/docs/html/TimeOffset_8cpp_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/TimeOffset_8h_source.html b/docs/html/TimeOffset_8h_source.html index f174a6bb9..beb6c6999 100644 --- a/docs/html/TimeOffset_8h_source.html +++ b/docs/html/TimeOffset_8h_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/TimePeriod_8cpp_source.html b/docs/html/TimePeriod_8cpp_source.html index 86460946b..64d060f1a 100644 --- a/docs/html/TimePeriod_8cpp_source.html +++ b/docs/html/TimePeriod_8cpp_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/TimePeriod_8h_source.html b/docs/html/TimePeriod_8h_source.html index e25159e2a..2383d7934 100644 --- a/docs/html/TimePeriod_8h_source.html +++ b/docs/html/TimePeriod_8h_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/TimeProvider_8h_source.html b/docs/html/TimeProvider_8h_source.html index 3ad5ae38f..a1f96db00 100644 --- a/docs/html/TimeProvider_8h_source.html +++ b/docs/html/TimeProvider_8h_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/TimeZoneData_8h_source.html b/docs/html/TimeZoneData_8h_source.html index 6c0bf88ee..56d1d1963 100644 --- a/docs/html/TimeZoneData_8h_source.html +++ b/docs/html/TimeZoneData_8h_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/TimeZone_8cpp_source.html b/docs/html/TimeZone_8cpp_source.html index e52439c1f..8004fc03c 100644 --- a/docs/html/TimeZone_8cpp_source.html +++ b/docs/html/TimeZone_8cpp_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/TimeZone_8h_source.html b/docs/html/TimeZone_8h_source.html index 8e578ccac..82d776519 100644 --- a/docs/html/TimeZone_8h_source.html +++ b/docs/html/TimeZone_8h_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
@@ -94,11 +94,11 @@
virtual void setZoneInfo(const void *zoneInfo)=0
Set the opaque zoneInfo.
TimeZone()
Default constructor creates a UTC TimeZone.
Definition: TimeZone.h:144
virtual uint8_t getType()=0
Return the type of this cache.
-
An implementation of ZoneProcessor that works for all zones defined by the TZ Database (with some zon...
+
An implementation of ZoneProcessor that works for all zones defined by the TZ Database (with some zon...
Returns the TimeZone given the zoneInfo, zoneName, or zoneId.
Definition: TimeZone.h:21
static TimeZone forUtc()
Factory method to create a UTC TimeZone.
Definition: TimeZone.h:94
A thin wrapper around an extended::ZoneInfo data structure to provide a stable API access to some use...
Definition: ExtendedZone.h:23
-
An implementation of ZoneProcessor that supports a subset of the zones containing in the TZ Database...
+
An implementation of ZoneProcessor that supports a subset of the zones containing in the TZ Database...
static TimeOffset forOffsetCode(int8_t offsetCode)
Create TimeOffset from the offset code.
Definition: TimeOffset.h:117
static TimeZone forZoneInfo(const extended::ZoneInfo *zoneInfo, ExtendedZoneProcessor *zoneProcessor)
Factory method to create from a zoneInfo and an associated ExtendedZoneProcessor. ...
Definition: TimeZone.h:130
The date (year, month, day), time (hour, minute, second) and offset from UTC (timeOffset).
diff --git a/docs/html/TimingStats_8h_source.html b/docs/html/TimingStats_8h_source.html index fa6f53356..f988b80f1 100644 --- a/docs/html/TimingStats_8h_source.html +++ b/docs/html/TimingStats_8h_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/ZoneContext_8h_source.html b/docs/html/ZoneContext_8h_source.html index ece70f11d..1f96a18bd 100644 --- a/docs/html/ZoneContext_8h_source.html +++ b/docs/html/ZoneContext_8h_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/ZoneContext_8inc_source.html b/docs/html/ZoneContext_8inc_source.html index a2d805f17..83521ec57 100644 --- a/docs/html/ZoneContext_8inc_source.html +++ b/docs/html/ZoneContext_8inc_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
diff --git a/docs/html/ZoneInfo_8h_source.html b/docs/html/ZoneInfo_8h_source.html index f0d4b6e86..d174c65f8 100644 --- a/docs/html/ZoneInfo_8h_source.html +++ b/docs/html/ZoneInfo_8h_source.html @@ -22,7 +22,7 @@
AceTime -  0.5.1 +  0.5.2
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
@@ -68,7 +68,7 @@
ZoneInfo.h
-
1 /*
2  * MIT License
3  * Copyright (c) 2018 Brian T. Park
4  */
5 
6 #ifndef ACE_TIME_ZONE_INFO_H
7 #define ACE_TIME_ZONE_INFO_H
8 
9 #include <stdint.h>
10 #include "ZoneContext.h"
11 #include "ZonePolicy.h"
12 
13 namespace ace_time {
14 
15 // The data structures in ZoneInfo.inc are #included into the basic and
16 // extended namespaces, instead of subclassing them into the namespaces,
17 // because C++11 does not allow subclassed structs to be initialized using the
18 // curly-brace initializers. I believe C++14 removes this restriction but
19 // Arduino is currently limited to C++11.
20 
21 // Data structures for BasicZoneProcessor
22 namespace basic {
23 #include "ZoneInfo.inc"
24 }
25 
26 // Data structures for ExtendedZoneProcessor
27 namespace extended {
28 #include "ZoneInfo.inc"
29 }
30 
31 }
32 
33 #endif
+
1 /*
2  * MIT License
3  * Copyright (c) 2018 Brian T. Park
4  */
5 
6 #ifndef ACE_TIME_ZONE_INFO_H
7 #define ACE_TIME_ZONE_INFO_H
8 
9 #include <stdint.h>
10 #include "ZoneContext.h"
11 #include "ZonePolicy.h"
12 
13 namespace ace_time {
14 
15 // The data structures in ZoneInfo.inc are #included into the basic and
16 // extended namespaces, instead of subclassing them into the namespaces,
17 // because C++11 does not allow subclassed structs to be initialized using the
18 // curly-brace initializers. I believe C++14 removes this restriction but
19 // Arduino is currently limited to C++11.
20 
21 // Data structures for BasicZoneProcessor
22 namespace basic {
23 #include "ZoneInfo.inc"
24 }
25 
26 // Data structures for ExtendedZoneProcessor
27 namespace extended {
28 #include "ZoneInfo.inc"
29 }
30 
31 }
32 
33 #endif
-
1 /*
2  * MIT License
3  * Copyright (c) 2018 Brian T. Park
4  */
5 
19 struct ZoneEra {
21  static const int8_t kMaxUntilYearTiny = ZoneRule::kMaxYearTiny + 1;
22 
24  int8_t const offsetCode;
25 
30  const ZonePolicy* const zonePolicy;
31 
36  int8_t const deltaCode;
37 
44  const char* const format;
45 
50  int8_t const untilYearTiny;
51 
53  uint8_t const untilMonth;
54 
60  uint8_t const untilDay;
61 
66  uint8_t const untilTimeCode;
67 
69  uint8_t const untilTimeModifier;
70 };
71 
76 struct ZoneInfo {
78  const char* const name;
79 
85  uint32_t const zoneId;
86 
88  const ZoneContext* const zoneContext;
89 
97  uint8_t const transitionBufSize;
98 
100  uint8_t const numEras;
101 
103  const ZoneEra* const eras;
104 };
+
1 /*
2  * MIT License
3  * Copyright (c) 2018 Brian T. Park
4  */
5 
19 struct ZoneEra {
21  static const int8_t kMaxUntilYearTiny = ZoneRule::kMaxYearTiny + 1;
22 
27  const ZonePolicy* const zonePolicy;
28 
35  const char* const format;
36 
38  int8_t const offsetCode;
39 
44  int8_t const deltaCode;
45 
50  int8_t const untilYearTiny;
51 
53  uint8_t const untilMonth;
54 
60  uint8_t const untilDay;
61 
66  uint8_t const untilTimeCode;
67 
69  uint8_t const untilTimeModifier;
70 };
71 
76 struct ZoneInfo {
78  const char* const name;
79 
85  uint32_t const zoneId;
86 
88  const ZoneContext* const zoneContext;
89 
97  uint8_t const transitionBufSize;
98 
100  uint8_t const numEras;
101 
103  const ZoneEra* const eras;
104 };