public function parse(DateTimeParseContext $context, $text, $position) { $length = strlen($text); if ($position === $length) { return ~$position; } if ($position < 0 || $position >= $length) { throw new \OutOfRangeException(); } $sign = $text[$position]; $negative = false; $positive = false; if ($sign === $context->getDecimalStyle()->getPositiveSign()) { if ($this->signStyle->parse(true, $context->isStrict(), $this->minWidth === $this->maxWidth) === false) { return ~$position; } $positive = true; $position++; } else { if ($sign === $context->getDecimalStyle()->getNegativeSign()) { if ($this->signStyle->parse(false, $context->isStrict(), $this->minWidth === $this->maxWidth) === false) { return ~$position; } $negative = true; $position++; } else { if ($this->signStyle == SignStyle::ALWAYS() && $context->isStrict()) { return ~$position; } } } $effMinWidth = $context->isStrict() || $this->isFixedWidth($context) ? $this->minWidth : 1; $minEndPos = $position + $effMinWidth; if ($minEndPos > $length) { return ~$position; } $effMaxWidth = ($context->isStrict() || $this->isFixedWidth($context) ? $this->maxWidth : 9) + Math::max($this->subsequentWidth, 0); $total = 0; $totalBig = null; $pos = $position; for ($pass = 0; $pass < 2; $pass++) { $maxEndPos = Math::min($pos + $effMaxWidth, $length); while ($pos < $maxEndPos) { $ch = $text[$pos++]; $digit = $context->getDecimalStyle()->convertToDigit($ch); if ($digit < 0) { $pos--; if ($pos < $minEndPos) { return ~$position; // need at least min width digits } break; } if ($pos - $position > 18) { if ($totalBig === null) { $totalBig = \gmp_init($total); } $totalBig = \gmp_add(\gmp_mul($totalBig, "10"), \gmp_init($digit)); } else { $total = $total * 10 + $digit; } } if ($this->subsequentWidth > 0 && $pass === 0) { // re-parse now we know the correct width $parseLen = $pos - $position; $effMaxWidth = Math::max($effMinWidth, $parseLen - $this->subsequentWidth); $pos = $position; $total = 0; $totalBig = null; } else { break; } } if ($negative) { if ($totalBig !== null) { if (\gmp_cmp($totalBig, "0") === 0 && $context->isStrict()) { return ~($position - 1); // minus zero not allowed } $totalBig = \gmp_neg($totalBig); } else { if ($total === 0 && $context->isStrict()) { return ~($position - 1); // minus zero not allowed } $total = -$total; } } else { if ($this->signStyle == SignStyle::EXCEEDS_PAD() && $context->isStrict()) { $parseLen = $pos - $position; if ($positive) { if ($parseLen <= $this->minWidth) { return ~($position - 1); // '+' only parsed if minWidth exceeded } } else { if ($parseLen > $this->minWidth) { return ~$position; // '+' must be parsed if minWidth exceeded } } } } if ($totalBig !== null) { if (gmp_cmp($totalBig, "-9223372036854775808") < 0 || gmp_cmp($totalBig, "9223372036854775807") > 0) { // overflow, parse 1 less digit $totalBig = gmp_div($totalBig, "10"); $pos--; } return $this->setValue($context, gmp_intval($totalBig), $position, $pos); } return $this->setValue($context, $total, $position, $pos); }
/** * Resolves the date, resolving days past the end of month. * * @param int $year the year to represent, validated from MIN_YEAR to MAX_YEAR * @param int $month the month-of-year to represent, validated from 1 to 12 * @param int $day the day-of-month to represent, validated from 1 to 31 * @return LocalDate the resolved date, not null */ private static function resolvePreviousValid($year, $month, $day) { switch ($month) { case 2: $day = Math::min($day, IsoChronology::INSTANCE()->isLeapYear($year) ? 29 : 28); break; case 4: case 6: case 9: case 11: $day = Math::min($day, 30); break; } return new LocalDate($year, $month, $day); }
function resolveYMD(FieldValues $fieldValues, ResolverStyle $resolverStyle) { $y = CF::YEAR()->checkValidIntValue($fieldValues->remove(CF::YEAR())); if ($resolverStyle == ResolverStyle::LENIENT()) { $months = Math::subtractExact($fieldValues->remove(CF::MONTH_OF_YEAR()), 1); $days = Math::subtractExact($fieldValues->remove(CF::DAY_OF_MONTH()), 1); return LocalDate::of($y, 1, 1)->plusMonths($months)->plusDays($days); } $moy = CF::MONTH_OF_YEAR()->checkValidIntValue($fieldValues->remove(CF::MONTH_OF_YEAR())); $dom = CF::DAY_OF_MONTH()->checkValidIntValue($fieldValues->remove(CF::DAY_OF_MONTH())); if ($resolverStyle == ResolverStyle::SMART()) { // previous valid if ($moy == 4 || $moy == 6 || $moy == 9 || $moy == 11) { $dom = Math::min($dom, 30); } else { if ($moy == 2) { $dom = Math::min($dom, Month::FEBRUARY()->length(Year::isLeapYear($y))); } } } return LocalDate::of($y, $moy, $dom); }
public function parse(DateTimeParseContext $context, $text, $position) { $effectiveMin = $context->isStrict() ? $this->minWidth : 0; $effectiveMax = $context->isStrict() ? $this->maxWidth : 9; $length = strlen($text); if ($position === $length) { // valid if whole field is optional, invalid if minimum width return $effectiveMin > 0 ? ~$position : $position; } if ($this->decimalPoint) { if ($text[$position] != $context->getDecimalStyle()->getDecimalSeparator()) { // valid if whole field is optional, invalid if minimum width return $effectiveMin > 0 ? ~$position : $position; } $position++; } $minEndPos = $position + $effectiveMin; if ($minEndPos > $length) { return ~$position; // need at least min width digits } $maxEndPos = Math::min($position + $effectiveMax, $length); $total = 0; // can use int because we are only parsing up to 9 digits $pos = $position; while ($pos < $maxEndPos) { $ch = $text[$pos++]; $digit = $context->getDecimalStyle()->convertToDigit($ch); if ($digit < 0) { if ($pos < $minEndPos) { return ~$position; // need at least min width digits } $pos--; break; } $total = $total * 10 + $digit; } $div = 1 . str_repeat('0', 9 - ($pos - $position)); $fraction = gmp_mul($total, $div); $value = $this->convertFromFraction($fraction); return $context->setParsedField($this->field, $value, $position, $pos); }
/** * @dataProvider data_weekFields */ public function test_withWeekOfWeekBasedYear(DayOfWeek $firstDayOfWeek, $minDays) { $day = LocalDate::of(2012, 12, 31); $week = WeekFields::of($firstDayOfWeek, $minDays); $dowField = $week->dayOfWeek(); $wowbyField = $week->weekOfWeekBasedYear(); $yowbyField = $week->weekBasedYear(); $dowExpected = ($day->get($dowField) - 1) % 7 + 1; $dowDate = $day->with($dowField, $dowExpected); $dowResult = $dowDate->get($dowField); $this->assertEquals($dowResult, $dowExpected, "Localized DayOfWeek not correct; " . $day . " -->" . $dowDate); $weekExpected = $day->get($wowbyField) + 1; $range = $day->range($wowbyField); $weekExpected = ($weekExpected - 1) % (int) $range->getMaximum() + 1; $weekDate = $day->with($wowbyField, $weekExpected); $weekResult = $weekDate->get($wowbyField); $this->assertEquals($weekResult, $weekExpected, "Localized WeekOfWeekBasedYear not correct; " . $day . " -->" . $weekDate); $yearExpected = $day->get($yowbyField) + 1; $yearDate = $day->with($yowbyField, $yearExpected); $yearResult = $yearDate->get($yowbyField); $this->assertEquals($yearResult, $yearExpected, "Localized WeekBasedYear not correct; " . $day . " --> " . $yearDate); $range = $yearDate->range($wowbyField); $weekExpected = Math::min($day->get($wowbyField), $range->getMaximum()); $weekActual = $yearDate->get($wowbyField); $this->assertEquals($weekActual, $weekExpected, "Localized WeekOfWeekBasedYear week should not change; " . $day . " --> " . $yearDate . ", actual: " . $weekActual . ", weekExpected: " . $weekExpected); }
/** * Adjusts the specified temporal object to have this month-day. * <p> * This returns a temporal object of the same observable type as the input * with the month and day-of-month changed to be the same as this. * <p> * The adjustment is equivalent to using {@link Temporal#with(TemporalField, long)} * twice, passing {@link ChronoField#MONTH_OF_YEAR} and * {@link ChronoField#DAY_OF_MONTH} as the fields. * If the specified temporal object does not use the ISO calendar system then * a {@code DateTimeException} is thrown. * <p> * In most cases, it is clearer to reverse the calling pattern by using * {@link Temporal#with(TemporalAdjuster)}: * <pre> * // these two lines are equivalent, but the second approach is recommended * temporal = thisMonthDay.adjustInto(temporal); * temporal = temporal.with(thisMonthDay); * </pre> * <p> * This instance is immutable and unaffected by this method call. * * @param Temporal $temporal the target object to be adjusted, not null * @return Temporal the adjusted object, not null * @throws DateTimeException if unable to make the adjustment * @throws ArithmeticException if numeric overflow occurs */ public function adjustInto(Temporal $temporal) { if (AbstractChronology::from($temporal)->equals(IsoChronology::INSTANCE()) == false) { throw new DateTimeException("Adjustment only supported on ISO date-time"); } $temporal = $temporal->with(ChronoField::MONTH_OF_YEAR(), $this->month); return $temporal->with(ChronoField::DAY_OF_MONTH(), Math::min($temporal->range(ChronoField::DAY_OF_MONTH())->getMaximum(), $this->day)); }
/** * Return a new week-based-year date of the Chronology, year, week-of-year, * and dow of week. * @param Chronology $chrono The chronology of the new date * @param int $yowby the year of the week-based-year * @param int $wowby the week of the week-based-year * @param int $dow the day of the week * @return ChronoLocalDate a ChronoLocalDate for the requested year, week of year, and day of week */ private function ofWeekBasedYear(Chronology $chrono, $yowby, $wowby, $dow) { $date = $chrono->date($yowby, 1, 1); $ldow = $this->localizedDayOfWeek($date); $offset = $this->startOfWeekOffset(1, $ldow); // Clamp the week of year to keep it in the same year $yearLen = $date->lengthOfYear(); $newYearWeek = $this->computeWeek($offset, $yearLen + $this->weekDef->getMinimalDaysInFirstWeek()); $wowby = Math::min($wowby, $newYearWeek - 1); $days = -$offset + ($dow - 1) + ($wowby - 1) * 7; return $date->plus($days, ChronoUnit::DAYS()); }