public function testMinDateEpochSec() { $offset = ZoneOffset::ofTotalSeconds(0); $seconds = LocalDateTime::MIN()->toEpochSecond($offset); $new = LocalDateTime::ofEpochSecond($seconds, 0, $offset); $this->assertEquals(LocalDateTime::MIN(), $new); }
/** * Converts this to a transition. * * @param ZoneOffset $standardOffset the active standard offset, not null * @param int $savingsBeforeSecs the active savings in seconds * @return ZoneOffsetTransition the transition, not null */ function toTransition(ZoneOffset $standardOffset, $savingsBeforeSecs) { // copy of code in ZoneOffsetTransitionRule to avoid infinite loop $date = $this->toLocalDate(); $date = ZoneRulesBuilder::deduplicate($date); $ldt = ZoneRulesBuilder::deduplicate(LocalDateTime::ofDateAndTime($date, $this->time)); /** @var ZoneOffset $wallOffset */ $wallOffset = ZoneRulesBuilder::deduplicate(ZoneOffset::ofTotalSeconds($standardOffset->getTotalSeconds() + $savingsBeforeSecs)); $dt = ZoneRulesBuilder::deduplicate($this->timeDefinition->createDateTime($ldt, $standardOffset, $wallOffset)); $offsetAfter = ZoneRulesBuilder::deduplicate(ZoneOffset::ofTotalSeconds($standardOffset->getTotalSeconds() + $this->savingAmountSecs)); return new ZoneOffsetTransition($dt, $wallOffset, $offsetAfter); }
/** * 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); }
/** * @dataProvider data_withFieldLong */ public function test_with_adjuster_ensureZoneOffsetConsistent(ZonedDateTime $base, TemporalField $setField, $setValue, ZonedDateTime $expected) { if ($setField == CF::OFFSET_SECONDS()) { $this->assertEquals($base->adjust(ZoneOffset::ofTotalSeconds($setValue)), $expected); } }
/** * A query for {@code ZoneOffset} returning null if not found. * @param TemporalAccessor $temporal * @return null|ZoneOffset * @throws DateTimeException */ public static function _offset(TemporalAccessor $temporal) { if ($temporal->isSupported(ChronoField::OFFSET_SECONDS())) { return ZoneOffset::ofTotalSeconds($temporal->get(ChronoField::OFFSET_SECONDS())); } return null; }
/** * Creates the wall offset for the local date-time at the end of the window. * * @param int $savingsSecs the amount of savings in use in seconds * @return ZoneOffset the created date-time epoch second in the wall offset, not null */ function createWallOffset($savingsSecs) { return ZoneOffset::ofTotalSeconds($this->standardOffset->getTotalSeconds() + $savingsSecs); }
/** * Parse an offset following a prefix and set the ZoneId if it is valid. * To matching the parsing of ZoneId.of the values are not normalized * to ZoneOffsets. * * @param DateTimeParseContext $context the parse context * @param string $text the input text * @param int $prefixPos start of the prefix * @param int $position start of text after the prefix * @param OffsetIdPrinterParser $parser parser for the value after the prefix * @return int the position after the parse */ private function parseOffsetBased(DateTimeParseContext $context, $text, $prefixPos, $position, OffsetIdPrinterParser $parser) { $prefix = strtoupper(substr($text, $prefixPos, $position - $prefixPos)); if ($position >= strlen($text)) { $context->setParsedZone(ZoneId::of($prefix)); return $position; } // '0' or 'Z' after prefix is not part of a valid ZoneId; use bare prefix if ($text[$position] === '0' || $context->charEquals($text[$position], 'Z')) { $context->setParsedZone(ZoneId::of($prefix)); return $position; } $newContext = $context->copy(); $endPos = $parser->parse($newContext, $text, $position); try { if ($endPos < 0) { if ($parser == OffsetIdPrinterParser::INSTANCE_ID_Z()) { return ~$prefixPos; } $context->setParsedZone(ZoneId::of($prefix)); return $position; } $offset = $newContext->getParsed(ChronoField::OFFSET_SECONDS()); $zoneOffset = ZoneOffset::ofTotalSeconds($offset); $context->setParsedZone(ZoneId::ofOffset($prefix, $zoneOffset)); return $endPos; } catch (DateTimeException $dte) { return ~$prefixPos; } }
public function test_isBeforeIsAfterIsEqual2nanos() { $a = OffsetTime::of(11, 30, 59, 4, ZoneOffset::ofTotalSeconds(self::OFFSET_PONE()->getTotalSeconds() + 1)); $b = OffsetTime::of(11, 30, 59, 3, self::OFFSET_PONE()); // a is before b due to offset $this->assertEquals($a->isBefore($b), true); $this->assertEquals($a->isEqual($b), false); $this->assertEquals($a->isAfter($b), false); $this->assertEquals($b->isBefore($a), false); $this->assertEquals($b->isEqual($a), false); $this->assertEquals($b->isAfter($a), true); $this->assertEquals($a->isBefore($a), false); $this->assertEquals($b->isBefore($b), false); $this->assertEquals($a->isEqual($a), true); $this->assertEquals($b->isEqual($b), true); $this->assertEquals($a->isAfter($a), false); $this->assertEquals($b->isAfter($b), false); }
private function parseOffset($str) { $secs = $this->parseSecs($str); return ZoneOffset::ofTotalSeconds($secs); }
private function resolveInstant() { // add instant seconds if we have date, time and zone if ($this->date !== null && $this->time !== null) { if ($this->zone !== null) { $instant = $this->date->atTime($this->time)->atZone($this->zone)->getLong(CF::INSTANT_SECONDS()); $this->fieldValues->put(CF::INSTANT_SECONDS(), $instant); } else { $offsetSecs = $this->fieldValues->get(CF::OFFSET_SECONDS()); if ($offsetSecs !== null) { $offset = ZoneOffset::ofTotalSeconds($offsetSecs); $instant = $this->date->atTime($this->time)->atZone($offset)->getLong(CF::INSTANT_SECONDS()); $this->fieldValues->put(CF::INSTANT_SECONDS(), $instant); } } } }
public function with(TemporalField $field, $newValue) { if ($field instanceof ChronoField) { $f = $field; switch ($f) { case ChronoField::INSTANT_SECONDS(): return $this->plus($newValue - $this->toEpochSecond(), ChronoUnit::SECONDS()); case ChronoField::OFFSET_SECONDS(): $offset = ZoneOffset::ofTotalSeconds($f->checkValidIntValue($newValue)); return $this->create($this->dateTime->toInstant($offset), $this->zone); } return $this->ofBest($this->dateTime->with($field, $newValue), $this->zone, $this->offset); } return ChronoZonedDateTimeImpl::ensureValid($this->getChronology(), $field->adjustInto($this, $newValue)); }
public function test_nullPrefixOfOffset() { TestHelper::assertNullException($this, function () { ZoneId::ofOffset(null, ZoneOffset::ofTotalSeconds(1)); }); }
/** * Returns a copy of this date-time with the specified field set to a new value. * <p> * This returns a {@code ZonedDateTime}, based on this one, with the value * for the specified field changed. * This can be used to change any supported field, such as the year, month or day-of-month. * If it is not possible to set the value, because the field is not supported or for * some other reason, an exception is thrown. * <p> * In some cases, changing the specified field can cause the resulting date-time to become invalid, * such as changing the month from 31st January to February would make the day-of-month invalid. * In cases like this, the field is responsible for resolving the date. Typically it will choose * the previous valid date, which would be the last valid day of February in this example. * <p> * If the field is a {@link ChronoField} then the adjustment is implemented here. * <p> * The {@code INSTANT_SECONDS} field will return a date-time with the specified instant. * The zone and nano-of-second are unchanged. * The result will have an offset derived from the new instant and original zone. * If the new instant value is outside the valid range then a {@code DateTimeException} will be thrown. * <p> * The {@code OFFSET_SECONDS} field will typically be ignored. * The offset of a {@code ZonedDateTime} is controlled primarily by the time-zone. * As such, changing the offset does not generally make sense, because there is only * one valid offset for the local date-time and zone. * If the zoned date-time is in a daylight savings overlap, then the offset is used * to switch between the two valid offsets. In all other cases, the offset is ignored. * If the new offset value is outside the valid range then a {@code DateTimeException} will be thrown. * <p> * The other {@link #isSupported(TemporalField) supported fields} will behave as per * the matching method on {@link LocalDateTime#with(TemporalField, long) LocalDateTime}. * The zone is not part of the calculation and will be unchanged. * When converting back to {@code ZonedDateTime}, if the local date-time is in an overlap, * then the offset will be retained if possible, otherwise the earlier offset will be used. * If in a gap, the local date-time will be adjusted forward by the length of the gap. * <p> * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. * <p> * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.adjustInto(Temporal, long)} * passing {@code this} as the argument. In this case, the field determines * whether and how to adjust the instant. * <p> * This instance is immutable and unaffected by this method call. * * @param TemporalField $field the field to set in the result, not null * @param int $newValue the new value of the field in the result * @return ZonedDateTime a {@code ZonedDateTime} based on {@code this} with the specified field set, not null * @throws DateTimeException if the field cannot be set * @throws UnsupportedTemporalTypeException if the field is not supported * @throws ArithmeticException if numeric overflow occurs */ public function with(TemporalField $field, $newValue) { if ($field instanceof ChronoField) { $f = $field; switch ($f) { case ChronoField::INSTANT_SECONDS(): return self::create($newValue, $this->getNano(), $this->zone); case ChronoField::OFFSET_SECONDS(): $offset = ZoneOffset::ofTotalSeconds($f->checkValidIntValue($newValue)); return $this->resolveOffset($offset); } return $this->resolveLocal($this->dateTime->with($field, $newValue)); } return $field->adjustInto($this, $newValue); }
/** * Returns a copy of this date-time with the specified field set to a new value. * <p> * This returns an {@code OffsetDateTime}, based on this one, with the value * for the specified field changed. * This can be used to change any supported field, such as the year, month or day-of-month. * If it is not possible to set the value, because the field is not supported or for * some other reason, an exception is thrown. * <p> * In some cases, changing the specified field can cause the resulting date-time to become invalid, * such as changing the month from 31st January to February would make the day-of-month invalid. * In cases like this, the field is responsible for resolving the date. Typically it will choose * the previous valid date, which would be the last valid day of February in this example. * <p> * If the field is a {@link ChronoField} then the adjustment is implemented here. * <p> * The {@code INSTANT_SECONDS} field will return a date-time with the specified instant. * The offset and nano-of-second are unchanged. * If the new instant value is outside the valid range then a {@code DateTimeException} will be thrown. * <p> * The {@code OFFSET_SECONDS} field will return a date-time with the specified offset. * The local date-time is unaltered. If the new offset value is outside the valid range * then a {@code DateTimeException} will be thrown. * <p> * The other {@link #isSupported(TemporalField) supported fields} will behave as per * the matching method on {@link LocalDateTime#with(TemporalField, long) LocalDateTime}. * In this case, the offset is not part of the calculation and will be unchanged. * <p> * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. * <p> * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.adjustInto(Temporal, long)} * passing {@code this} as the argument. In this case, the field determines * whether and how to adjust the instant. * <p> * This instance is immutable and unaffected by this method call. * * @param TemporalField $field the field to set in the result, not null * @param int $newValue the new value of the field in the result * @return OffsetDateTime an {@code OffsetDateTime} based on {@code this} with the specified field set, not null * @throws DateTimeException if the field cannot be set * @throws UnsupportedTemporalTypeException if the field is not supported * @throws ArithmeticException if numeric overflow occurs */ public function with(TemporalField $field, $newValue) { if ($field instanceof ChronoField) { $f = $field; switch ($f) { case ChronoField::INSTANT_SECONDS(): return $this->ofInstant(Instant::ofEpochSecond($newValue, $this->getNano()), $this->offset); case ChronoField::OFFSET_SECONDS(): return $this->_with($this->dateTime, ZoneOffset::ofTotalSeconds($f->checkValidIntValue($newValue))); } return $this->_with($this->dateTime->with($field, $newValue), $this->offset); } return $field->adjustInto($this, $newValue); }
public static function init() { self::$SECONDS_CACHE = []; self::$ID_CACHE = []; self::$UTC = ZoneOffset::ofTotalSeconds(0); self::$MIN = ZoneOffset::ofTotalSeconds(-self::MAX_SECONDS); self::$MAX = ZoneOffset::ofTotalSeconds(self::MAX_SECONDS); }
public function test_getTotalSeconds() { $offset = ZoneOffset::ofTotalSeconds(60 * 60 + 1); $this->assertEquals($offset->getTotalSeconds(), 60 * 60 + 1); }
/** * Returns a copy of this time with the specified field set to a new value. * <p> * This returns an {@code OffsetTime}, based on this one, with the value * for the specified field changed. * This can be used to change any supported field, such as the hour, minute or second. * If it is not possible to set the value, because the field is not supported or for * some other reason, an exception is thrown. * <p> * If the field is a {@link ChronoField} then the adjustment is implemented here. * <p> * The {@code OFFSET_SECONDS} field will return a time with the specified offset. * The local time is unaltered. If the new offset value is outside the valid range * then a {@code DateTimeException} will be thrown. * <p> * The other {@link #isSupported(TemporalField) supported fields} will behave as per * the matching method on {@link LocalTime#with(TemporalField, long)} LocalTime}. * In this case, the offset is not part of the calculation and will be unchanged. * <p> * All other {@code ChronoField} instances will throw an {@code UnsupportedTemporalTypeException}. * <p> * If the field is not a {@code ChronoField}, then the result of this method * is obtained by invoking {@code TemporalField.adjustInto(Temporal, long)} * passing {@code this} as the argument. In this case, the field determines * whether and how to adjust the instant. * <p> * This instance is immutable and unaffected by this method call. * * @param TemporalField $field the field to set in the result, not null * @param int $newValue the new value of the field in the result * @return OffsetTime an {@code OffsetTime} based on {@code this} with the specified field set, not null * @throws DateTimeException if the field cannot be set * @throws UnsupportedTemporalTypeException if the field is not supported * @throws ArithmeticException if numeric overflow occurs */ public function with(TemporalField $field, $newValue) { if ($field instanceof ChronoField) { if ($field == ChronoField::OFFSET_SECONDS()) { $f = $field; return $this->_with($this->time, ZoneOffset::ofTotalSeconds($f->checkValidIntValue($newValue))); } return $this->_with($this->time->with($field, $newValue), $this->offset); } return $field->adjustInto($this, $newValue); }