/*
 * Copyright 2002-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.convert;

import java.time.Period;
import java.time.temporal.ChronoUnit;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.springframework.util.Assert;

A standard set of Period units.
Author:Eddú Meléndez, Edson Chávez
See Also:
Since:2.3.0
/** * A standard set of {@link Period} units. * * @author Eddú Meléndez * @author Edson Chávez * @since 2.3.0 * @see Period */
public enum PeriodStyle {
Simple formatting, for example '1d'.
/** * Simple formatting, for example '1d'. */
SIMPLE("^" + "(?:([-+]?[0-9]+)Y)?" + "(?:([-+]?[0-9]+)M)?" + "(?:([-+]?[0-9]+)W)?" + "(?:([-+]?[0-9]+)D)?" + "$", Pattern.CASE_INSENSITIVE) { @Override public Period parse(String value, ChronoUnit unit) { try { if (NUMERIC.matcher(value).matches()) { return Unit.fromChronoUnit(unit).parse(value); } Matcher matcher = matcher(value); Assert.state(matcher.matches(), "Does not match simple period pattern"); Assert.isTrue(hasAtLeastOneGroupValue(matcher), () -> "'" + value + "' is not a valid simple period"); int years = parseInt(matcher, 1); int months = parseInt(matcher, 2); int weeks = parseInt(matcher, 3); int days = parseInt(matcher, 4); return Period.of(years, months, Math.addExact(Math.multiplyExact(weeks, 7), days)); } catch (Exception ex) { throw new IllegalArgumentException("'" + value + "' is not a valid simple period", ex); } } boolean hasAtLeastOneGroupValue(Matcher matcher) { for (int i = 0; i < matcher.groupCount(); i++) { if (matcher.group(i + 1) != null) { return true; } } return false; } private int parseInt(Matcher matcher, int group) { String value = matcher.group(group); return (value != null) ? Integer.parseInt(value) : 0; } @Override protected boolean matches(String value) { return NUMERIC.matcher(value).matches() || matcher(value).matches(); } @Override public String print(Period value, ChronoUnit unit) { if (value.isZero()) { return Unit.fromChronoUnit(unit).print(value); } StringBuilder result = new StringBuilder(); append(result, value, Unit.YEARS); append(result, value, Unit.MONTHS); append(result, value, Unit.DAYS); return result.toString(); } private void append(StringBuilder result, Period value, Unit unit) { if (!unit.isZero(value)) { result.append(unit.print(value)); } } },
ISO-8601 formatting.
/** * ISO-8601 formatting. */
ISO8601("^[+-]?P.*$", 0) { @Override public Period parse(String value, ChronoUnit unit) { try { return Period.parse(value); } catch (Exception ex) { throw new IllegalArgumentException("'" + value + "' is not a valid ISO-8601 period", ex); } } @Override public String print(Period value, ChronoUnit unit) { return value.toString(); } }; private static final Pattern NUMERIC = Pattern.compile("^[-+]?[0-9]+$"); private final Pattern pattern; PeriodStyle(String pattern, int flags) { this.pattern = Pattern.compile(pattern, flags); } protected boolean matches(String value) { return this.pattern.matcher(value).matches(); } protected final Matcher matcher(String value) { return this.pattern.matcher(value); }
Parse the given value to a Period.
Params:
  • value – the value to parse
Returns:a period
/** * Parse the given value to a Period. * @param value the value to parse * @return a period */
public Period parse(String value) { return parse(value, null); }
Parse the given value to a period.
Params:
  • value – the value to parse
  • unit – the period unit to use if the value doesn't specify one (null will default to d)
Returns:a period
/** * Parse the given value to a period. * @param value the value to parse * @param unit the period unit to use if the value doesn't specify one ({@code null} * will default to d) * @return a period */
public abstract Period parse(String value, ChronoUnit unit);
Print the specified period.
Params:
  • value – the value to print
Returns:the printed result
/** * Print the specified period. * @param value the value to print * @return the printed result */
public String print(Period value) { return print(value, null); }
Print the specified period using the given unit.
Params:
  • value – the value to print
  • unit – the value to use for printing
Returns:the printed result
/** * Print the specified period using the given unit. * @param value the value to print * @param unit the value to use for printing * @return the printed result */
public abstract String print(Period value, ChronoUnit unit);
Detect the style then parse the value to return a period.
Params:
  • value – the value to parse
Throws:
Returns:the parsed period
/** * Detect the style then parse the value to return a period. * @param value the value to parse * @return the parsed period * @throws IllegalArgumentException if the value is not a known style or cannot be * parsed */
public static Period detectAndParse(String value) { return detectAndParse(value, null); }
Detect the style then parse the value to return a period.
Params:
  • value – the value to parse
  • unit – the period unit to use if the value doesn't specify one (null will default to ms)
Throws:
Returns:the parsed period
/** * Detect the style then parse the value to return a period. * @param value the value to parse * @param unit the period unit to use if the value doesn't specify one ({@code null} * will default to ms) * @return the parsed period * @throws IllegalArgumentException if the value is not a known style or cannot be * parsed */
public static Period detectAndParse(String value, ChronoUnit unit) { return detect(value).parse(value, unit); }
Detect the style from the given source value.
Params:
  • value – the source value
Throws:
Returns:the period style
/** * Detect the style from the given source value. * @param value the source value * @return the period style * @throws IllegalArgumentException if the value is not a known style */
public static PeriodStyle detect(String value) { Assert.notNull(value, "Value must not be null"); for (PeriodStyle candidate : values()) { if (candidate.matches(value)) { return candidate; } } throw new IllegalArgumentException("'" + value + "' is not a valid period"); } private enum Unit {
Days, represented by suffix d.
/** * Days, represented by suffix {@code d}. */
DAYS(ChronoUnit.DAYS, "d", Period::getDays, Period::ofDays),
Weeks, represented by suffix w.
/** * Weeks, represented by suffix {@code w}. */
WEEKS(ChronoUnit.WEEKS, "w", null, Period::ofWeeks),
Months, represented by suffix m.
/** * Months, represented by suffix {@code m}. */
MONTHS(ChronoUnit.MONTHS, "m", Period::getMonths, Period::ofMonths),
Years, represented by suffix y.
/** * Years, represented by suffix {@code y}. */
YEARS(ChronoUnit.YEARS, "y", Period::getYears, Period::ofYears); private final ChronoUnit chronoUnit; private final String suffix; private final Function<Period, Integer> intValue; private final Function<Integer, Period> factory; Unit(ChronoUnit chronoUnit, String suffix, Function<Period, Integer> intValue, Function<Integer, Period> factory) { this.chronoUnit = chronoUnit; this.suffix = suffix; this.intValue = intValue; this.factory = factory; } private Period parse(String value) { return this.factory.apply(Integer.parseInt(value)); } private String print(Period value) { return intValue(value) + this.suffix; } private boolean isZero(Period value) { return intValue(value) == 0; } private int intValue(Period value) { Assert.notNull(this.intValue, () -> "intValue cannot be extracted from " + this.name()); return this.intValue.apply(value); } private static Unit fromChronoUnit(ChronoUnit chronoUnit) { if (chronoUnit == null) { return Unit.DAYS; } for (Unit candidate : values()) { if (candidate.chronoUnit == chronoUnit) { return candidate; } } throw new IllegalArgumentException("Unsupported unit " + chronoUnit); } } }