mirror of
https://github.com/lightbend/config.git
synced 2025-01-28 21:20:07 +08:00
Merge pull request #478 from kag0/parse-period
Add support for getting value as Period
This commit is contained in:
commit
aa6958469a
20
HOCON.md
20
HOCON.md
@ -1296,7 +1296,27 @@ must be lowercase. Exactly these strings are supported:
|
||||
- `m`, `minute`, `minutes`
|
||||
- `h`, `hour`, `hours`
|
||||
- `d`, `day`, `days`
|
||||
|
||||
### Period Format
|
||||
|
||||
Similar to the `getDuration()` method, there is a `getPeriod()` method
|
||||
available for getting time units as a `java.time.Period`.
|
||||
|
||||
This can use the general "units format" described above; bare
|
||||
numbers are taken to be in days, while strings are
|
||||
parsed as a number plus an optional unit string.
|
||||
|
||||
The supported unit strings for period are case sensitive and
|
||||
must be lowercase. Exactly these strings are supported:
|
||||
|
||||
- `d`, `day`, `days`
|
||||
- `w`, `week`, `weeks`
|
||||
- `m`, `mo`, `month`, `months` (note that if you are using `getTemporal()`
|
||||
which may return either a `java.time.Duration` or a `java.time.Period`
|
||||
you will want to use `mo` rather than `m` to prevent your unit being
|
||||
parsed as minutes)
|
||||
- `y`, `year`, `years`
|
||||
|
||||
### Size in bytes format
|
||||
|
||||
Implementations may wish to support a `getBytes()` returning a
|
||||
|
@ -4,6 +4,8 @@
|
||||
package com.typesafe.config;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Period;
|
||||
import java.time.temporal.TemporalAmount;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@ -793,6 +795,44 @@ public interface Config extends ConfigMergeable {
|
||||
*/
|
||||
Duration getDuration(String path);
|
||||
|
||||
/**
|
||||
* Gets a value as a java.time.Period. If the value is
|
||||
* already a number, then it's taken as days; if it's
|
||||
* a string, it's parsed understanding units suffixes like
|
||||
* "10d" or "5w" as documented in the <a
|
||||
* href="https://github.com/typesafehub/config/blob/master/HOCON.md">the
|
||||
* spec</a>. This method never returns null.
|
||||
*
|
||||
* @since 1.3.0
|
||||
*
|
||||
* @param path
|
||||
* path expression
|
||||
* @return the period value at the requested path
|
||||
* @throws ConfigException.Missing
|
||||
* if value is absent or null
|
||||
* @throws ConfigException.WrongType
|
||||
* if value is not convertible to Long or String
|
||||
* @throws ConfigException.BadValue
|
||||
* if value cannot be parsed as a number of the given TimeUnit
|
||||
*/
|
||||
Period getPeriod(String path);
|
||||
|
||||
/**
|
||||
* Gets a value as a java.time.temporal.TemporalAmount.
|
||||
* This method will first try get get the value as a java.time.Duration, and if unsuccessful,
|
||||
* then as a java.time.Period.
|
||||
* This means that values like "5m" will be parsed as 5 minutes rather than 5 months
|
||||
* @param path path expression
|
||||
* @return the temporal value at the requested path
|
||||
* @throws ConfigException.Missing
|
||||
* if value is absent or null
|
||||
* @throws ConfigException.WrongType
|
||||
* if value is not convertible to Long or String
|
||||
* @throws ConfigException.BadValue
|
||||
* if value cannot be parsed as a TemporalAmount
|
||||
*/
|
||||
TemporalAmount getTemporal(String path);
|
||||
|
||||
/**
|
||||
* Gets a list value (with any element type) as a {@link ConfigList}, which
|
||||
* implements {@code java.util.List<ConfigValue>}. Throws if the path is
|
||||
|
@ -7,7 +7,11 @@ import java.io.ObjectStreamException;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.time.DateTimeException;
|
||||
import java.time.Duration;
|
||||
import java.time.Period;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.time.temporal.TemporalAmount;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@ -322,6 +326,21 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
|
||||
return Duration.ofNanos(nanos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Period getPeriod(String path){
|
||||
ConfigValue v = find(path, ConfigValueType.STRING);
|
||||
return parsePeriod((String) v.unwrapped(), v.origin(), path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TemporalAmount getTemporal(String path){
|
||||
try{
|
||||
return getDuration(path);
|
||||
} catch (ConfigException.BadValue e){
|
||||
return getPeriod(path);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> List<T> getHomogeneousUnwrappedList(String path,
|
||||
ConfigValueType expected) {
|
||||
@ -583,6 +602,90 @@ final class SimpleConfig implements Config, MergeableValue, Serializable {
|
||||
return s.substring(i + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a period string. If no units are specified in the string, it is
|
||||
* assumed to be in days. The returned period is in days.
|
||||
* The purpose of this function is to implement the period-related methods
|
||||
* in the ConfigObject interface.
|
||||
*
|
||||
* @param input
|
||||
* the string to parse
|
||||
* @param originForException
|
||||
* origin of the value being parsed
|
||||
* @param pathForException
|
||||
* path to include in exceptions
|
||||
* @return duration in days
|
||||
* @throws ConfigException
|
||||
* if string is invalid
|
||||
*/
|
||||
public static Period parsePeriod(String input,
|
||||
ConfigOrigin originForException, String pathForException) {
|
||||
String s = ConfigImplUtil.unicodeTrim(input);
|
||||
String originalUnitString = getUnits(s);
|
||||
String unitString = originalUnitString;
|
||||
String numberString = ConfigImplUtil.unicodeTrim(s.substring(0, s.length()
|
||||
- unitString.length()));
|
||||
ChronoUnit units;
|
||||
|
||||
// this would be caught later anyway, but the error message
|
||||
// is more helpful if we check it here.
|
||||
if (numberString.length() == 0)
|
||||
throw new ConfigException.BadValue(originForException,
|
||||
pathForException, "No number in period value '" + input
|
||||
+ "'");
|
||||
|
||||
if (unitString.length() > 2 && !unitString.endsWith("s"))
|
||||
unitString = unitString + "s";
|
||||
|
||||
// note that this is deliberately case-sensitive
|
||||
if (unitString.equals("") || unitString.equals("d") || unitString.equals("days")) {
|
||||
units = ChronoUnit.DAYS;
|
||||
|
||||
} else if (unitString.equals("w") || unitString.equals("weeks")) {
|
||||
units = ChronoUnit.WEEKS;
|
||||
|
||||
} else if (unitString.equals("m") || unitString.equals("mo") || unitString.equals("months")) {
|
||||
units = ChronoUnit.MONTHS;
|
||||
|
||||
} else if (unitString.equals("y") || unitString.equals("years")) {
|
||||
units = ChronoUnit.YEARS;
|
||||
|
||||
} else {
|
||||
throw new ConfigException.BadValue(originForException,
|
||||
pathForException, "Could not parse time unit '"
|
||||
+ originalUnitString
|
||||
+ "' (try d, w, mo, y)");
|
||||
}
|
||||
|
||||
try {
|
||||
return periodOf(Integer.parseInt(numberString), units);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new ConfigException.BadValue(originForException,
|
||||
pathForException, "Could not parse duration number '"
|
||||
+ numberString + "'");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static Period periodOf(int n, ChronoUnit unit){
|
||||
if(unit.isTimeBased()){
|
||||
throw new DateTimeException(unit + " cannot be converted to a java.time.Period");
|
||||
}
|
||||
|
||||
switch (unit){
|
||||
case DAYS:
|
||||
return Period.ofDays(n);
|
||||
case WEEKS:
|
||||
return Period.ofWeeks(n);
|
||||
case MONTHS:
|
||||
return Period.ofMonths(n);
|
||||
case YEARS:
|
||||
return Period.ofYears(n);
|
||||
default:
|
||||
throw new DateTimeException(unit + " cannot be converted to a java.time.Period");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a duration string. If no units are specified in the string, it is
|
||||
* assumed to be in milliseconds. The returned duration is in nanoseconds.
|
||||
|
@ -65,6 +65,14 @@
|
||||
"minusLargeNanos" : -4878955355435272204ns
|
||||
},
|
||||
|
||||
"periods" : {
|
||||
"day" : 1d,
|
||||
"dayAsNumber": 2,
|
||||
"week": 3 weeks,
|
||||
"month": 5 mo,
|
||||
"year": 8y
|
||||
},
|
||||
|
||||
"memsizes" : {
|
||||
"meg" : 1M,
|
||||
"megsList" : [1M, 1024K, 1048576],
|
||||
|
@ -3,13 +3,16 @@
|
||||
*/
|
||||
package com.typesafe.config.impl
|
||||
|
||||
import java.time.temporal.{ ChronoUnit, TemporalUnit }
|
||||
|
||||
import org.junit.Assert._
|
||||
import org.junit._
|
||||
import com.typesafe.config._
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
import com.typesafe.config.ConfigResolveOptions
|
||||
import java.util.concurrent.TimeUnit.{ SECONDS, NANOSECONDS, MICROSECONDS, MILLISECONDS, MINUTES, DAYS, HOURS }
|
||||
import java.util.concurrent.TimeUnit.{ DAYS, HOURS, MICROSECONDS, MILLISECONDS, MINUTES, NANOSECONDS, SECONDS }
|
||||
|
||||
class ConfigTest extends TestUtils {
|
||||
|
||||
@ -811,6 +814,13 @@ class ConfigTest extends TestUtils {
|
||||
assertDurationAsTimeUnit(HOURS)
|
||||
assertDurationAsTimeUnit(DAYS)
|
||||
|
||||
// periods
|
||||
assertEquals(1, conf.getPeriod("periods.day").get(ChronoUnit.DAYS))
|
||||
assertEquals(2, conf.getPeriod("periods.dayAsNumber").getDays)
|
||||
assertEquals(3 * 7, conf.getTemporal("periods.week").get(ChronoUnit.DAYS))
|
||||
assertEquals(5, conf.getTemporal("periods.month").get(ChronoUnit.MONTHS))
|
||||
assertEquals(8, conf.getTemporal("periods.year").get(ChronoUnit.YEARS))
|
||||
|
||||
// should get size in bytes
|
||||
assertEquals(1024 * 1024L, conf.getBytes("memsizes.meg"))
|
||||
assertEquals(1024 * 1024L, conf.getBytes("memsizes.megAsNumber"))
|
||||
|
@ -3,6 +3,9 @@
|
||||
*/
|
||||
package com.typesafe.config.impl
|
||||
|
||||
import java.time.{ LocalDate, Period }
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
import org.junit.Assert._
|
||||
import org.junit._
|
||||
import com.typesafe.config._
|
||||
@ -40,6 +43,33 @@ class UnitParserTest extends TestUtils {
|
||||
assertTrue(e2.getMessage.contains("duration number"))
|
||||
}
|
||||
|
||||
@Test
|
||||
def parsePeriod() = {
|
||||
val oneYears = List(
|
||||
"1y", "1 y", "1year", "1 years", " 1y ", " 1 y ",
|
||||
"365", "365d", "365 d", "365 days", " 365 days ", "365day",
|
||||
"12m", "12mo", "12 m", " 12 mo ", "12 months", "12month")
|
||||
val epochDate = LocalDate.ofEpochDay(0)
|
||||
val oneYear = ChronoUnit.DAYS.between(epochDate, epochDate.plus(Period.ofYears(1)))
|
||||
for (y <- oneYears) {
|
||||
val period = SimpleConfig.parsePeriod(y, fakeOrigin(), "test")
|
||||
val dayCount = ChronoUnit.DAYS.between(epochDate, epochDate.plus(period))
|
||||
assertEquals(oneYear, dayCount)
|
||||
}
|
||||
|
||||
// bad units
|
||||
val e = intercept[ConfigException.BadValue] {
|
||||
SimpleConfig.parsePeriod("100 dollars", fakeOrigin(), "test")
|
||||
}
|
||||
assertTrue(s"${e.getMessage} was not the expected error message", e.getMessage.contains("time unit"))
|
||||
|
||||
// bad number
|
||||
val e2 = intercept[ConfigException.BadValue] {
|
||||
SimpleConfig.parsePeriod("1 00 seconds", fakeOrigin(), "test")
|
||||
}
|
||||
assertTrue(s"${e2.getMessage} was not the expected error message", e2.getMessage.contains("time unit 'seconds'"))
|
||||
}
|
||||
|
||||
// https://github.com/typesafehub/config/issues/117
|
||||
// this broke because "1d" is a valid double for parseDouble
|
||||
@Test
|
||||
|
Loading…
Reference in New Issue
Block a user