/** * Completes the build converting the builder to a set of time-zone rules. * <p> * Calling this method alters the state of the builder. * Further rules should not be added to this builder once this method is called. * * @param string $zoneId the time-zone ID, not null * @param array $deduplicateMap a map for deduplicating the values, not null * @return ZoneRules the zone rules, not null * @throws \LogicException if no windows have been added * @throws \LogicException if there is only one rule defined as being forever for any given window */ public function _toRules($zoneId, &$deduplicateMap) { $this->deduplicateMap = $deduplicateMap; if (empty($this->windowList)) { throw new \LogicException("No windows have been added to the builder"); } /** @var ZoneOffsetTransition[] $standardTransitionList */ $standardTransitionList = []; /** @var ZoneOffsetTransition[] */ $transitionList = []; /** @var ZoneOffsetTransitionRule[] */ $lastTransitionRuleList = []; // initialize the standard offset calculation $firstWindow = $this->windowList[0]; $loopStandardOffset = $firstWindow->standardOffset; $loopSavings = 0; if ($firstWindow->fixedSavingAmountSecs !== null) { $loopSavings = $firstWindow->fixedSavingAmountSecs; } /** @var ZoneOffset $firstWallOffset */ $firstWallOffset = $this->deduplicate(ZoneOffset::ofTotalSeconds($loopStandardOffset->getTotalSeconds() + $loopSavings)); /** @var LocalDateTime $loopWindowStart */ $loopWindowStart = $this->deduplicate(LocalDateTime::of(Year::MIN_VALUE, 1, 1, 0, 0)); $loopWindowOffset = $firstWallOffset; // build the windows and rules to interesting data foreach ($this->windowList as $window) { // tidy the state $window->tidy($loopWindowStart->getYear()); // calculate effective savings at the start of the window $effectiveSavings = $window->fixedSavingAmountSecs; if ($effectiveSavings === null) { // apply rules from this window together with the standard offset and // savings from the last window to find the savings amount applicable // at start of this window $effectiveSavings = 0; foreach ($window->ruleList as $rule) { $trans = $rule->toTransition($loopStandardOffset, $loopSavings); if ($trans->toEpochSecond() > $loopWindowStart->toEpochSecond($loopWindowOffset)) { // previous savings amount found, which could be the savings amount at // the instant that the window starts (hence isAfter) break; } $effectiveSavings = $rule->savingAmountSecs; } } // check if standard offset changed, and update it if ($loopStandardOffset->equals($window->standardOffset) === false) { $standardTransitionList[] = $this->deduplicate(ZoneOffsetTransition::of(LocalDateTime::ofEpochSecond($loopWindowStart->toEpochSecond($loopWindowOffset), 0, $loopStandardOffset), $loopStandardOffset, $window->standardOffset)); $loopStandardOffset = $this->deduplicate($window->standardOffset); } // check if the start of the window represents a transition $effectiveWallOffset = $this->deduplicate(ZoneOffset::ofTotalSeconds($loopStandardOffset->getTotalSeconds() + $effectiveSavings)); if ($loopWindowOffset->equals($effectiveWallOffset) === false) { $trans = $this->deduplicate(ZoneOffsetTransition::of($loopWindowStart, $loopWindowOffset, $effectiveWallOffset)); $transitionList[] = $trans; } $loopSavings = $effectiveSavings; // apply rules within the window foreach ($window->ruleList as $rule) { /** @var ZoneOffsetTransition $trans */ $trans = $this->deduplicate($rule->toTransition($loopStandardOffset, $loopSavings)); if ($trans !== null && $trans->toEpochSecond() < $loopWindowStart->toEpochSecond($loopWindowOffset) === false && $trans->toEpochSecond() < $window->createDateTimeEpochSecond($loopSavings) && $trans->getOffsetBefore()->equals($trans->getOffsetAfter()) === false) { $transitionList[] = $trans; $loopSavings = $rule->savingAmountSecs; } } // calculate last rules foreach ($window->lastRuleList as $lastRule) { $transitionRule = $this->deduplicate($lastRule->toTransitionRule($loopStandardOffset, $loopSavings)); $lastTransitionRuleList[] = $transitionRule; $loopSavings = $lastRule->savingAmountSecs; } // finally we can calculate the true end of the window, passing it to the next window $loopWindowOffset = $this->deduplicate($window->createWallOffset($loopSavings)); $loopWindowStart = $this->deduplicate(LocalDateTime::ofEpochSecond($window->createDateTimeEpochSecond($loopSavings), 0, $loopWindowOffset)); } return ZoneRules::of($firstWindow->standardOffset, $firstWallOffset, $standardTransitionList, $transitionList, $lastTransitionRuleList); }
/** * Gets the associated time-zone rules. * <p> * The rules will always return this offset when queried. * The implementation class is immutable, thread-safe and serializable. * * @return ZoneRules the rules, not null */ public function getRules() { return ZoneRules::ofOffset($this); }
private function checkOffset(ZoneRules $rules, LocalDateTime $dateTime, ZoneOffset $offset, $type) { $validOffsets = $rules->getValidOffsets($dateTime); $this->assertEquals(count($validOffsets), $type); $this->assertEquals($rules->getOffsetDateTime($dateTime), $offset); if ($type === 1) { $this->assertEquals($validOffsets[0], $offset); return null; } else { $zot = $rules->getTransition($dateTime); $this->assertNotNull($zot); $this->assertEquals($zot->isOverlap(), $type == 2); $this->assertEquals($zot->isGap(), $type == 0); $this->assertEquals($zot->isValidOffset($offset), $type == 2); return $zot; } }
/** * SPI method to get the rules for the zone ID. * <p> * This loads the rules for the specified zone ID. * The provider implementation must validate that the zone ID is valid and * available, throwing a {@code ZoneRulesException} if it is not. * The result of the method in the valid case depends on the caching flag. * <p> * If the provider implementation is not dynamic, then the result of the * method must be the non-null set of rules selected by the ID. * <p> * If the provider implementation is dynamic, then the flag gives the option * of preventing the returned rules from being cached in {@link ZoneId}. * When the flag is true, the provider is permitted to return null, where * null will prevent the rules from being cached in {@code ZoneId}. * When the flag is false, the provider must return non-null rules. * * @param string $zoneId the zone ID as defined by {@code ZoneId}, not null * @param bool $forCaching whether the rules are being queried for caching, * true if the returned rules will be cached by {@code ZoneId}, * false if they will be returned to the user without being cached in {@code ZoneId} * @return ZoneRules the rules, null if {@code forCaching} is true and this * is a dynamic provider that wants to prevent caching in {@code ZoneId}, * otherwise not null * @throws ZoneRulesException if rules cannot be obtained for the zone ID */ protected function provideRules($zoneId, $forCaching) { return ZoneRules::ofOffset(ZoneOffset::ofHours(1)); }