AceTime  2.4.0
Date and time classes for Arduino that support timezones from the TZ Database.
Transition.h
1 /*
2  * MIT License
3  * Copyright (c) 2019 Brian T. Park
4  */
5 
6 #ifndef ACE_TIME_EXTENDED_TRANSITION_H
7 #define ACE_TIME_EXTENDED_TRANSITION_H
8 
9 #include <stdint.h> // uint8_t
10 #include "common/logging.h"
11 #include "local_date_mutation.h"
12 #include "DateTuple.h"
13 
14 class TransitionStorageTest_getFreeAgent;
15 class TransitionStorageTest_getFreeAgent2;
16 class TransitionStorageTest_addFreeAgentToActivePool;
17 class TransitionStorageTest_reservePrior;
18 class TransitionStorageTest_addPriorToCandidatePool;
19 class TransitionStorageTest_addFreeAgentToCandidatePool;
20 class TransitionStorageTest_setFreeAgentAsPriorIfValid;
21 class TransitionStorageTest_addActiveCandidatesToActivePool;
22 class TransitionStorageTest_findTransitionForDateTime;
23 class TransitionStorageTest_resetCandidatePool;
24 
25 class Print;
26 
27 #ifndef ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG
28 #define ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG 0
29 #endif
30 
31 namespace ace_time {
32 namespace extended {
33 
34 inline bool isCompareStatusActive(CompareStatus status) {
35  return status == CompareStatus::kExactMatch
36  || status == CompareStatus::kWithinMatch
37  || status == CompareStatus::kPrior;
38 }
39 
46 template<typename ZEB>
53 
56 
58  ZEB era;
59 
62 
65 
68 
69  void log() const {
70  logging::printf("MatchingEra(");
71  logging::printf("start="); startDateTime.log();
72  logging::printf("; until="); untilDateTime.log();
73  logging::printf("; era=%c", (era.isNull()) ? '-' : '*');
74  logging::printf("; prevMatch=%c", (prevMatch) ? '*' : '-');
75  logging::printf(")");
76  }
77 };
78 
79 //---------------------------------------------------------------------------
80 
113 template <typename ZEB, typename ZPB, typename ZRB>
115 
118 
119 #if ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG
126  ZRB rule;
127 #endif
128 
136 
137  union {
144 
150  };
151 
152  union {
159 
165  };
166 
167 #if ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG
172  DateTuple originalTransitionTime;
173 #endif
174 
177 
179  int32_t offsetSeconds;
180 
182  int32_t deltaSeconds;
183 
190  char abbrev[internal::kAbbrevSize];
191 
192  union {
200 
205  CompareStatus compareStatus;
206  };
207 
208  const char* format() const {
209  return match->era.format();
210  }
211 
213  void log() const {
214  logging::printf("Transition(");
215  if (sizeof(acetime_t) <= sizeof(int)) {
216  logging::printf("start=%d", startEpochSeconds);
217  } else {
218  logging::printf("start=%ld", startEpochSeconds);
219  }
220  logging::printf("; status=%d", compareStatus);
221  logging::printf("; UTC");
224  logging::printf("; tt="); transitionTime.log();
225  logging::printf("; tts="); transitionTimeS.log();
226  logging::printf("; ttu="); transitionTimeU.log();
227  #if ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG
228  if (rule.isNull()) {
229  logging::printf("; rule=-");
230  } else {
231  logging::printf("; rule=");
232  logging::printf("[%d,%d]", rule.fromYear(), rule.toYear());
233  }
234  #endif
235  }
236 
238  static void logHourMinuteSecond(int32_t seconds) {
239  char sign;
240  if (seconds < 0) {
241  sign = '-';
242  seconds = -seconds;
243  } else {
244  sign = '+';
245  }
246  uint16_t minutes = seconds / 60;
247  uint8_t second = seconds - minutes * int32_t(60);
248  uint8_t hour = minutes / 60;
249  uint8_t minute = minutes - hour * 60;
250  if (second == 0) {
251  logging::printf("%c%02u:%02u", sign, (unsigned) hour, (unsigned) minute);
252  } else {
253  logging::printf("%c%02u:%02u:%02u",
254  sign, (unsigned) hour, (unsigned) minute, (unsigned) second);
255  }
256  }
257 
258 #ifdef ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG
260  static void printTransitions(
261  const char* prefix,
262  const TransitionTemplate* const* begin,
263  const TransitionTemplate* const* end) {
264  for (const TransitionTemplate* const* iter = begin; iter != end; ++iter) {
265  logging::printf(prefix);
266  (*iter)->log();
267  logging::printf("\n");
268  }
269  }
270 #endif
271 };
272 
280 template <typename ZEB, typename ZPB, typename ZRB>
284 
286  uint8_t fold;
287 
294  uint8_t num;
295 };
296 
310 template <typename ZEB, typename ZPB, typename ZRB>
318 
320  uint8_t num;
321 };
322 
354 template<uint8_t SIZE, typename ZEB, typename ZPB, typename ZRB>
356  public:
362 
369 
376 
379 
386  void init() {
387  for (uint8_t i = 0; i < SIZE; i++) {
388  mTransitions[i] = &mPool[i];
389  }
390  mIndexPrior = 0;
391  mIndexCandidates = 0;
392  mIndexFree = 0;
393  }
394 
397  return mTransitions[mIndexPrior];
398  }
399 
409  mIndexCandidates = mIndexPrior;
410  mIndexFree = mIndexPrior;
411  }
412 
413  Transition** getCandidatePoolBegin() {
414  return &mTransitions[mIndexCandidates];
415  }
416  Transition** getCandidatePoolEnd() {
417  return &mTransitions[mIndexFree];
418  }
419 
420  Transition** getActivePoolBegin() {
421  return &mTransitions[0];
422  }
423  Transition** getActivePoolEnd() {
424  return &mTransitions[mIndexFree];
425  }
426 
433  if (mIndexFree < SIZE) {
434  // Allocate a free transition.
435  if (mIndexFree >= mAllocSize) {
436  mAllocSize = mIndexFree + 1;
437  }
438  return mTransitions[mIndexFree];
439  } else {
440  // No more transition available in the buffer, so just return the last
441  // one. This will probably cause a bug in the timezone calculations, but
442  // I think this is better than triggering undefined behavior by running
443  // off the end of the mTransitions buffer.
444  return mTransitions[SIZE - 1];
445  }
446  }
447 
456  if (mIndexFree >= SIZE) return;
457  mIndexFree++;
458  mIndexPrior = mIndexFree;
459  mIndexCandidates = mIndexFree;
460  }
461 
471  getFreeAgent(); // allocate a new Transition
472 
473  mIndexCandidates++;
474  mIndexFree++;
475  return &mTransitions[mIndexPrior];
476  }
477 
480  Transition* ft = mTransitions[mIndexFree];
481  Transition* prior = mTransitions[mIndexPrior];
482  if ((prior->isValidPrior && prior->transitionTime < ft->transitionTime)
483  || !prior->isValidPrior) {
484  ft->isValidPrior = true;
485  prior->isValidPrior = false;
486  internal::swap(mTransitions[mIndexPrior], mTransitions[mIndexFree]);
487  }
488  }
489 
496  mIndexCandidates--;
497  }
498 
506  if (mIndexFree >= SIZE) return;
507 
508  // This implementation makes pair-wise swaps to shift the current
509  // Transition leftwards into its correctly sorted position. At first
510  // glance, this seem inefficient compared to the alternative
511  // implementation where we save the current Transition, then slide all the
512  // elements to the left by one position rightwards. However,
513  // MemoryBenchmark shows that this implementation is 46 bytes smaller on
514  // an AVR processor.
515  for (uint8_t i = mIndexFree; i > mIndexCandidates; i--) {
516  Transition* curr = mTransitions[i];
517  Transition* prev = mTransitions[i - 1];
518  if (curr->transitionTime >= prev->transitionTime) break;
519  mTransitions[i] = prev;
520  mTransitions[i - 1] = curr;
521  }
522  mIndexFree++;
523  }
524 
532  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
533  logging::printf("addActiveCandidatesToActivePool()\n");
534  }
535 
536  // Shift active candidates to the left into the Active pool.
537  uint8_t iActive = mIndexPrior;
538  uint8_t iCandidate = mIndexCandidates;
539  for (; iCandidate < mIndexFree; iCandidate++) {
540  if (isCompareStatusActive(mTransitions[iCandidate]->compareStatus)) {
541  if (iActive != iCandidate) {
542  // Must use swap(), because we are moving pointers instead of the
543  // actual Transition objects.
544  internal::swap(mTransitions[iActive], mTransitions[iCandidate]);
545  }
546  ++iActive;
547  }
548  }
549 
550  mIndexPrior = iActive;
551  mIndexCandidates = iActive;
552  mIndexFree = iActive;
553 
554  return mTransitions[iActive - 1];
555  }
556 
566  const {
567  if (ACE_TIME_EXTENDED_ZONE_PROCESSOR_DEBUG) {
568  logging::printf(
569  "findTransitionForSeconds(): mIndexFree: %d\n", mIndexFree);
570  }
571 
572  const Transition* prev = nullptr;
573  const Transition* curr = nullptr;
574  const Transition* next = nullptr;
575  for (uint8_t i = 0; i < mIndexFree; i++) {
576  next = mTransitions[i];
577  if (next->startEpochSeconds > epochSeconds) break;
578  prev = curr;
579  curr = next;
580  next = nullptr;
581  }
582 
583  uint8_t fold;
584  uint8_t num;
585  calcFoldAndOverlap(&fold, &num, prev, curr, next, epochSeconds);
586  //fprintf(stderr, "prev=%p;curr=%p;next=%p;fold=%d;num=%d\n",
587  // prev, curr, next, fold, num);
588  return TransitionForSeconds{curr, fold, num};
589  }
590 
592  static void calcFoldAndOverlap(
593  uint8_t* fold,
594  uint8_t* num,
595  const Transition* prev,
596  const Transition* curr,
597  const Transition* next,
598  acetime_t epochSeconds) {
599 
600  if (curr == nullptr) {
601  *fold = 0;
602  *num = 0;
603  return;
604  }
605 
606  // Check if within forward overlap shadow from prev
607  bool isOverlap;
608  if (prev == nullptr) {
609  isOverlap = false;
610  } else {
611  // Extract the shift from prev transition. Can be 0 in some cases where
612  // the zone changed from DST of one zone to the STD into another zone,
613  // causing the overall UTC offset to remain unchanged.
614  acetime_t shiftSeconds = subtractDateTuple(
615  curr->startDateTime, prev->untilDateTime);
616  if (shiftSeconds >= 0) {
617  // spring forward, or unchanged
618  isOverlap = false;
619  } else {
620  // Check if within the forward overlap shadow from prev
621  isOverlap = epochSeconds - curr->startEpochSeconds < -shiftSeconds;
622  }
623  }
624  if (isOverlap) {
625  *fold = 1; // epochSeconds selects the second match
626  *num = 2;
627  return;
628  }
629 
630  // Check if within backward overlap shawdow from next
631  if (next == nullptr) {
632  isOverlap = false;
633  } else {
634  // Extract the shift to next transition. Can be 0 in some cases where
635  // the zone changed from DST of one zone to the STD into another zone,
636  // causing the overall UTC offset to remain unchanged.
637  acetime_t shiftSeconds = subtractDateTuple(
638  next->startDateTime, curr->untilDateTime);
639  if (shiftSeconds >= 0) {
640  // spring forward, or unchanged
641  isOverlap = false;
642  } else {
643  // Check if within the backward overlap shadow from next
644  isOverlap = next->startEpochSeconds - epochSeconds <= -shiftSeconds;
645  }
646  }
647  if (isOverlap) {
648  *fold = 0; // epochSeconds selects the first match
649  *num = 2;
650  return;
651  }
652 
653  // Normal single match, no overlap.
654  *fold = 0;
655  *num = 1;
656  }
657 
664  const LocalDateTime& ldt) const {
665  // Convert LocalDateTime to DateTuple.
666  DateTuple localDate{
667  ldt.year(),
668  ldt.month(),
669  ldt.day(),
670  ((ldt.hour() * int32_t(60) + ldt.minute()) * 60 + ldt.second()),
672  };
673 
674  // Examine adjacent pairs of Transitions, looking for an exact match, gap,
675  // or overlap.
676  const Transition* prev = nullptr;
677  const Transition* curr = nullptr;
678  uint8_t num = 0;
679  for (uint8_t i = 0; i < mIndexFree; i++) {
680  curr = mTransitions[i];
681 
682  const DateTuple& startDateTime = curr->startDateTime;
683  const DateTuple& untilDateTime = curr->untilDateTime;
684  bool isExactMatch = (startDateTime <= localDate)
685  && (localDate < untilDateTime);
686 
687  if (isExactMatch) {
688  // Check for a previous exact match to detect an overlap.
689  if (num == 1) {
690  num++;
691  break;
692  }
693 
694  // Loop again to detect an overlap.
695  num = 1;
696  } else if (startDateTime > localDate) {
697  // Exit loop since no more candidate transition.
698  break;
699  }
700 
701  prev = curr;
702 
703  // Set the curr to nullptr so that if the loop runs off the end of the
704  // list of Transitions, the curr is marked as nullptr.
705  curr = nullptr;
706  }
707 
708  // Check if the prev was an exact match, and set the curr to be identical.
709  // avoid confusion.
710  if (num == 1) {
711  curr = prev;
712  }
713 
714  // This should get optimized by RVO.
715  return TransitionForDateTime{prev, curr, num};
716  }
717 
719  void log() const {
720  logging::printf("TransitionStorage: ");
721  logging::printf("SIZE=%d, mAllocSize=%d\n", SIZE, mAllocSize);
722  int nActives = mIndexPrior;
723  int nPrior = mIndexCandidates - mIndexPrior;
724  int nCandidates = mIndexFree - mIndexCandidates;
725  int nAllocFree = mAllocSize - mIndexFree;
726  int nVirginFree = SIZE - mAllocSize;
727 
728  logging::printf(" Actives: %d\n", nActives);
730  " ", &mTransitions[0], &mTransitions[mIndexPrior]);
731 
732  logging::printf(" Prior: %d\n", nPrior);
734  " ", &mTransitions[mIndexPrior], &mTransitions[mIndexCandidates]);
735 
736  logging::printf(" Candidates: %d\n", nCandidates);
738  " ", &mTransitions[mIndexCandidates], &mTransitions[mIndexFree]);
739 
740  logging::printf(" Allocated Free: %d\n", nAllocFree);
741  logging::printf(" Virgin Free: %d\n", nVirginFree);
742  }
743 
745  void resetAllocSize() { mAllocSize = 0; }
746 
752  uint8_t getAllocSize() const { return mAllocSize; }
753 
754  private:
755  friend class ::TransitionStorageTest_getFreeAgent;
756  friend class ::TransitionStorageTest_getFreeAgent2;
757  friend class ::TransitionStorageTest_addFreeAgentToActivePool;
758  friend class ::TransitionStorageTest_reservePrior;
759  friend class ::TransitionStorageTest_addPriorToCandidatePool;
760  friend class ::TransitionStorageTest_addFreeAgentToCandidatePool;
761  friend class ::TransitionStorageTest_setFreeAgentAsPriorIfValid;
762  friend class ::TransitionStorageTest_addActiveCandidatesToActivePool;
763  friend class ::TransitionStorageTest_findTransitionForDateTime;
764  friend class ::TransitionStorageTest_resetCandidatePool;
765 
767  Transition* getTransition(uint8_t i) {
768  return mTransitions[i];
769  }
770 
771  Transition mPool[SIZE];
772  Transition* mTransitions[SIZE];
773  uint8_t mIndexPrior;
774  uint8_t mIndexCandidates;
775  uint8_t mIndexFree;
776 
778  uint8_t mAllocSize = 0;
779 };
780 
781 } // namespace extended
782 } // namespace ace_time
783 
784 #endif
Class that holds the date-time as the components (year, month, day, hour, minute, second) without reg...
Definition: LocalDateTime.h:30
uint8_t day() const
Return the day of the month.
uint8_t month() const
Return the month with January=1, December=12.
uint8_t second() const
Return the second.
uint8_t minute() const
Return the minute.
uint8_t hour() const
Return the hour.
int16_t year() const
Return the year.
A heap manager which is specialized and tuned to manage a collection of Transitions,...
Definition: Transition.h:355
void resetAllocSize()
Reset the current allocation size.
Definition: Transition.h:745
void addFreeAgentToActivePool()
Immediately add the free agent Transition at index mIndexFree to the Active pool.
Definition: Transition.h:455
TransitionForSecondsTemplate< ZEB, ZPB, ZRB > TransitionForSeconds
Template instantiation of TransitionForSecondsTemplate used by this class.
Definition: Transition.h:368
TransitionForDateTimeTemplate< ZEB, ZPB, ZRB > TransitionForDateTime
Template instantiation of TransitionForDateTimeTemplate used by this class.
Definition: Transition.h:375
void init()
Initialize all pools to 0 size, usually when a new year is initialized.
Definition: Transition.h:386
void setFreeAgentAsPriorIfValid()
Set the free agent transition as the most recent prior.
Definition: Transition.h:479
Transition * getFreeAgent()
Return a pointer to the first Transition in the free pool.
Definition: Transition.h:432
TransitionTemplate< ZEB, ZPB, ZRB > Transition
Template instantiation of TransitionTemplate used by this class.
Definition: Transition.h:361
void resetCandidatePool()
Empty the Candidate pool by resetting the various indexes.
Definition: Transition.h:408
static void calcFoldAndOverlap(uint8_t *fold, uint8_t *num, const Transition *prev, const Transition *curr, const Transition *next, acetime_t epochSeconds)
Calculate the fold and num parameters of TransitionForSecond.
Definition: Transition.h:592
uint8_t getAllocSize() const
Return the maximum number of transitions which was allocated.
Definition: Transition.h:752
void addFreeAgentToCandidatePool()
Add the free agent Transition at index mIndexFree to the Candidate pool, sorted by transitionTime.
Definition: Transition.h:505
Transition * getPrior()
Return the current prior transition.
Definition: Transition.h:396
TransitionForDateTime findTransitionForDateTime(const LocalDateTime &ldt) const
Return the candidate Transitions matching the given dateTime.
Definition: Transition.h:663
Transition * addActiveCandidatesToActivePool()
Add active candidates into the Active pool, and collapse the Candidate pool.
Definition: Transition.h:531
void addPriorToCandidatePool()
Add the current prior into the Candidates pool.
Definition: Transition.h:495
TransitionForSeconds findTransitionForSeconds(acetime_t epochSeconds) const
Return the Transition matching the given epochSeconds.
Definition: Transition.h:565
Transition ** reservePrior()
Allocate a free Transition then add it to the Prior pool.
Definition: Transition.h:470
void log() const
Verify that the indexes are valid.
Definition: Transition.h:719
int32_t acetime_t
Type for the number of seconds from epoch.
Definition: common.h:24
A tuple that represents a date and time.
Definition: DateTuple.h:36
void log() const
Used only for debugging.
Definition: DateTuple.h:50
Data structure that captures the matching ZoneEra and its ZoneRule transitions for a given year.
Definition: Transition.h:47
MatchingEraTemplate * prevMatch
The previous MatchingEra, needed to interpret startDateTime.
Definition: Transition.h:61
ZEB era
The ZoneEra that matched the given year.
Definition: Transition.h:58
DateTuple untilDateTime
The effective until time of the matching ZoneEra.
Definition: Transition.h:55
int32_t lastOffsetSeconds
The STD offset of the last Transition in this MatchingEra.
Definition: Transition.h:64
DateTuple startDateTime
The effective start time of the matching ZoneEra, which uses the UTC offsets of the previous matching...
Definition: Transition.h:52
int32_t lastDeltaSeconds
The DST offset of the last Transition in this MatchingEra.
Definition: Transition.h:67
The result of the findTransitionForDateTime(const LocalDatetime& ldt) method which can return 0,...
Definition: Transition.h:311
uint8_t num
Number of matches: 0, 1, 2.
Definition: Transition.h:320
const TransitionTemplate< ZEB, ZPB, ZRB > * prev
The previous transition.
Definition: Transition.h:314
const TransitionTemplate< ZEB, ZPB, ZRB > * curr
The matching transition, or null if not found or in gap.
Definition: Transition.h:317
Tuple of a matching Transition and its 'fold'.
Definition: Transition.h:281
uint8_t fold
1 if corresponding datetime occurred the second time
Definition: Transition.h:286
const TransitionTemplate< ZEB, ZPB, ZRB > * curr
The matching transition, or null if not found.
Definition: Transition.h:283
uint8_t num
Number of occurrences of the resulting LocalDateTime: 0, 1, or 2.
Definition: Transition.h:294
Represents an interval of time where the time zone obeyed a certain UTC offset and DST delta.
Definition: Transition.h:114
static void printTransitions(const char *prefix, const TransitionTemplate *const *begin, const TransitionTemplate *const *end)
Print an iterable of Transitions from 'begin' to 'end'.
Definition: Transition.h:260
int32_t offsetSeconds
The standard time offset seconds, not the total offset.
Definition: Transition.h:179
bool isValidPrior
During findCandidateTransitions(), this flag indicates whether the current transition is a valid "pri...
Definition: Transition.h:199
DateTuple transitionTime
The original transition time, usually 'w' but sometimes 's' or 'u'.
Definition: Transition.h:135
acetime_t startEpochSeconds
The calculated transition time of the given rule.
Definition: Transition.h:176
DateTuple transitionTimeS
Version of transitionTime in 's' mode, using the UTC offset of the previous Transition.
Definition: Transition.h:143
DateTuple untilDateTime
Until time expressed using the UTC offset of the current Transition.
Definition: Transition.h:164
void log() const
Used only for debugging.
Definition: Transition.h:213
DateTuple transitionTimeU
Version of transitionTime in 'u' mode, using the UTC offset of the previous transition.
Definition: Transition.h:158
CompareStatus compareStatus
During processTransitionCompareStatus(), this flag indicates how the transition falls within the time...
Definition: Transition.h:205
int32_t deltaSeconds
The DST delta seconds.
Definition: Transition.h:182
DateTuple startDateTime
Start time expressed using the UTC offset of the current Transition.
Definition: Transition.h:149
char abbrev[internal::kAbbrevSize]
The calculated effective time zone abbreviation, e.g.
Definition: Transition.h:190
static void logHourMinuteSecond(int32_t seconds)
Print seconds as [+/-]hh:mm[:ss].
Definition: Transition.h:238
const MatchingEraTemplate< ZEB > * match
The match which generated this Transition.
Definition: Transition.h:117
static const uint8_t kSuffixW
Represents 'w' or wall time.
Definition: ZoneInfoLow.h:80