public function format(DateTimePrintContext $context, &$buf) { $offsetSecs = $context->getValueField(ChronoField::OFFSET_SECONDS()); if ($offsetSecs === null) { return false; } $gmtText = "GMT"; // TODO: get localized version of 'GMT' if ($gmtText !== null) { $buf .= $gmtText; } $totalSecs = Math::toIntExact($offsetSecs); if ($totalSecs !== 0) { $absHours = Math::abs($totalSecs / 3600 % 100); // anything larger than 99 silently dropped $absMinutes = Math::abs($totalSecs / 60 % 60); $absSeconds = Math::abs($totalSecs % 60); $buf .= $totalSecs < 0 ? "-" : "+"; if ($this->style == TextStyle::FULL()) { $this->appendHMS($buf, $absHours); $buf .= ':'; $this->appendHMS($buf, $absMinutes); if ($absSeconds != 0) { $buf .= ':'; $this->appendHMS($buf, $absSeconds); } } else { if ($absHours >= 10) { $buf .= Math::div($absHours, 10); } $buf .= $absHours % 10; if ($absMinutes != 0 || $absSeconds != 0) { $buf .= ':'; $this->appendHMS($buf, $absMinutes); if ($absSeconds != 0) { $buf .= ':'; self::appendHMS($buf, $absSeconds); } } } } return true; }
/** * Calculates the period between this date and another date as a {@code Period}. * <p> * This calculates the period between two dates in terms of years, months and days. * The start and end points are {@code this} and the specified date. * The result will be negative if the end is before the start. * The negative sign will be the same in each of year, month and day. * <p> * The calculation is performed using the ISO calendar system. * If necessary, the input date will be converted to ISO. * <p> * The start date is included, but the end date is not. * The period is calculated by removing complete months, then calculating * the remaining number of days, adjusting to ensure that both have the same sign. * The number of months is then normalized into years and months based on a 12 month year. * A month is considered to be complete if the end day-of-month is greater * than or equal to the start day-of-month. * For example, from {@code 2010-01-15} to {@code 2011-03-18} is "1 year, 2 months and 3 days". * <p> * There are two equivalent ways of using this method. * The first is to invoke this method. * The second is to use {@link Period#between(LocalDate, LocalDate)}: * <pre> * // these two lines are equivalent * period = start.until(end); * period = Period.between(start, end); * </pre> * The choice should be made based on which makes the code more readable. * * @param ChronoLocalDate $endDateExclusive the end date, exclusive, which may be in any chronology, not null * @return Period the period between this date and the end date, not null */ public function untilDate(ChronoLocalDate $endDateExclusive) { $end = LocalDate::from($endDateExclusive); $totalMonths = $end->getProlepticMonth() - $this->getProlepticMonth(); // safe $days = $end->day - $this->day; if ($totalMonths > 0 && $days < 0) { $totalMonths--; $calcDate = $this->plusMonths($totalMonths); $days = (int) ($end->toEpochDay() - $calcDate->toEpochDay()); // safe } else { if ($totalMonths < 0 && $days > 0) { $totalMonths++; $days -= $end->lengthOfMonth(); } } $years = Math::div($totalMonths, 12); // safe $months = (int) ($totalMonths % 12); // safe return Period::of(Math::toIntExact($years), $months, $days); }
public function test_periodUntil_LocalDate_max() { $years = Math::toIntExact(Year::MAX_VALUE - Year::MIN_VALUE); $this->assertEquals(LocalDate::MIN()->untilDate(LocalDate::MAX()), Period::of($years, 11, 30)); }
protected function resolveYearOfEra(FieldValues $fieldValues, ResolverStyle $resolverStyle) { $yoeLong = $fieldValues->remove(ChronoField::YEAR_OF_ERA()); if ($yoeLong != null) { $eraLong = $fieldValues->remove(ChronoField::ERA()); if ($resolverStyle != ResolverStyle::LENIENT()) { $yoe = $this->range(ChronoField::YEAR_OF_ERA())->checkValidIntValue($yoeLong, ChronoField::YEAR_OF_ERA()); } else { $yoe = Math::toIntExact($yoeLong); } if ($eraLong != null) { $eraObj = $this->eraOf($this->range(ChronoField::ERA())->checkValidIntValue($eraLong, ChronoField::ERA())); self::addFieldValue($fieldValues, ChronoField::YEAR(), $this->prolepticYear($eraObj, $yoe)); } else { if ($fieldValues->has(ChronoField::YEAR())) { $year = $this->range(ChronoField::YEAR())->checkValidIntValue($fieldValues[ChronoField::YEAR()->__toString()][1], ChronoField::YEAR()); $chronoDate = $this->dateYearDay($year, 1); self::addFieldValue($fieldValues, ChronoField::YEAR(), $this->prolepticYear($chronoDate->getEra(), $yoe)); } else { if ($resolverStyle == ResolverStyle::STRICT()) { // do not invent era if strict // reinstate the field removed earlier, no cross-check issues $fieldValues[ChronoField::YEAR_OF_ERA()->__toString()][1] = $yoeLong; } else { $eras = $this->eras(); if (empty($eras)) { self::addFieldValue($fieldValues, ChronoField::YEAR(), $yoe); } else { $eraObj = $eras[count($eras) - 1]; $this->addFieldValue($fieldValues, ChronoField::YEAR(), $this->prolepticYear($eraObj, $yoe)); } } } } } else { if ($fieldValues->has(ChronoField::ERA())) { $this->range(ChronoField::ERA())->checkValidValue($fieldValues[ChronoField::ERA()->__toString()][1], ChronoField::ERA()); // always validated } } return null; }
public function resolve(FieldValues $fieldValues, TemporalAccessor $partialTemporal, ResolverStyle $resolverStyle) { $value = $fieldValues->get($this); $newValue = Math::toIntExact($value); // broad limit makes overflow checking lighter // first convert localized day-of-week to ISO day-of-week // doing this first handles case where both ISO and localized were parsed and might mismatch // day-of-week is always strict as two different day-of-week values makes lenient complex if ($this->rangeUnit == ChronoUnit::WEEKS()) { // day-of-week $checkedValue = $this->range->checkValidIntValue($value, $this); // no leniency as too complex $startDow = $this->weekDef->getFirstDayOfWeek()->getValue(); $isoDow = Math::floorMod($startDow - 1 + ($checkedValue - 1), 7) + 1; $fieldValues->remove($this); $fieldValues->put(CF::DAY_OF_WEEK(), $isoDow); return null; } // can only build date if ISO day-of-week is present if (!$fieldValues->has(CF::DAY_OF_WEEK())) { return null; } $isoDow = CF::DAY_OF_WEEK()->checkValidIntValue($fieldValues->get(CF::DAY_OF_WEEK())); $dow = $this->localizedDayOfWeekNumerical($isoDow); // build date $chrono = AbstractChronology::from($partialTemporal); if ($fieldValues->has(CF::YEAR())) { $year = CF::YEAR()->checkValidIntValue($fieldValues->get(CF::YEAR())); // validate if ($this->rangeUnit == ChronoUnit::MONTHS() && $fieldValues->has(CF::MONTH_OF_YEAR())) { // week-of-month $month = $fieldValues->get(CF::MONTH_OF_YEAR()); // not validated yet return $this->resolveWoM($fieldValues, $chrono, $year, $month, $newValue, $dow, $resolverStyle); } if ($this->rangeUnit == ChronoUnit::YEARS()) { // week-of-year return $this->resolveWoY($fieldValues, $chrono, $year, $newValue, $dow, $resolverStyle); } } else { if (($this->rangeUnit == IsoFields::WEEK_BASED_YEARS() || $this->rangeUnit == ChronoUnit::FOREVER()) && $fieldValues->has($this->weekDef->weekBasedYear) && $fieldValues->has($this->weekDef->weekOfWeekBasedYear)) { // week-of-week-based-year and year-of-week-based-year return $this->resolveWBY($fieldValues, $chrono, $dow, $resolverStyle); } } return null; }
/** * Returns a copy of this period with the years and months normalized. * <p> * This normalizes the years and months units, leaving the days unit unchanged. * The months unit is adjusted to have an absolute value less than 11, * with the years unit being adjusted to compensate. For example, a period of * "1 Year and 15 months" will be normalized to "2 years and 3 months". * <p> * The sign of the years and months units will be the same after normalization. * For example, a period of "1 year and -25 months" will be normalized to * "-1 year and -1 month". * <p> * This instance is immutable and unaffected by this method call. * * @return Period a {@code Period} based on this period with excess months normalized to years, not null * @throws ArithmeticException if numeric overflow occurs */ public function normalized() { $totalMonths = $this->toTotalMonths(); $splitYears = Math::div($totalMonths, 12); $splitMonths = $totalMonths % 12; // no overflow if ($splitYears === $this->years && $splitMonths === $this->months) { return $this; } return $this->create(Math::toIntExact($splitYears), $splitMonths, $this->days); }