如何解析 java 中基于时间的标记?(1m 1M 1d 1Y 1W 1S)
How to parse time based tokens in java ?(1m 1M 1d 1Y 1W 1S)
我从 FE 得到以下字符串:
1m
5M
3D
30m
2h
1Y
3W
对应1分钟5个月3天30分钟2小时1年3周
java中是否有解析的意思?
我想用 Instant(或 LocalDatetTime)操作(add/minus)。 java有办法吗?
我猜你应该使用 java.util.concurrent.TimeUnit 创建你自己的解析器方法,然后你可以使用任何框架来添加秒数
private static long convertToSeconds(int value, char unit)
{
long ret = value;
switch (unit)
{
case 'd':
ret = TimeUnit.DAYS.toSeconds(value);
break;
case 'h':
ret = TimeUnit.HOURS.toSeconds(value);
break;
case 'm':
ret = TimeUnit.MINUTES.toSeconds(value);
break;
case 's':
break;
default:
// fail
break;
}
return ret;
}
正如@Achinta Jha 所建议的那样,您可以自己实现一个简单的解析逻辑,例如
public static void main(String[] args) {
final String[] inputs = {"1m", "5M", "3D"/*, "30m", "2h", "1Y", "3W"*/};
Arrays.stream(inputs)
.map(x -> Parse(x))
.forEach(System.out::println);
}
private static final Map<Character, Function<String, Duration>> parsers = new HashMap<>();
static {
parsers.put('m', txt -> Duration.ofMinutes(Integer.parseInt(txt)));
parsers.put('M', txt -> Duration.ofDays(30 * Integer.parseInt(txt)));
parsers.put('D', txt -> Duration.ofDays(Integer.parseInt(txt)));
}
private static Duration Parse(String txt) {
Character last = txt.charAt(txt.length() - 1);
Function<String, Duration> parser = parsers.get(last);
return parser.apply(txt.substring(0, txt.length() - 1));
}
这是一个简单的解释性实现,既没有错误/null
s 处理,也没有完全支持建议的输入。
请注意,Duration
不支持静态 ofMonths
方法:我假设您打算 30 days
,但您可以根据需要的语义调整代码。
为了 add/subtract Duration
,您可以使用 Duration
class.
的非静态方法
Spring 已经这样做了,但不是所有与您相同的选项。 Their implementation is
private static Duration simpleParse(String rawTime) {
if (rawTime == null || rawTime.isEmpty())
return null;
if (!Character.isDigit(rawTime.charAt(0)))
return null;
String time = rawTime.toLowerCase();
return tryParse(time, "ns", Duration::ofNanos)
.orElseGet(() -> tryParse(time, "ms", Duration::ofMillis).orElseGet(
() -> tryParse(time, "s", Duration::ofSeconds).orElseGet(
() -> tryParse(time, "m", Duration::ofMinutes).orElseGet(
() -> tryParse(time, "h", Duration::ofHours)
.orElseGet(() -> tryParse(time, "d",
Duration::ofDays)
.orElse(null))))));
}
private static Optional<Duration> tryParse(String time, String unit,
Function<Long, Duration> toDuration) {
if (time.endsWith(unit)) {
String trim = time.substring(0, time.lastIndexOf(unit)).trim();
try {
return Optional.of(toDuration.apply(Long.parseLong(trim)));
}
catch (NumberFormatException ignore) {
return Optional.empty();
}
}
return Optional.empty();
}
由于年和分钟都包括在内,我建议您使用评论中提到的一对 Duration
和 Period
解析。使用静态方法 Period::parse(CharSequence text)
and Duration.parse(CharSequence text)
及其默认格式(PnDTnHnMn.nS
用于 Duration
和 PnYnMnD
用于 Period
),因为它们不提供翻译自定义表达式的方法就这样。
对于此用例,您必须在表达式和给定格式之间创建自己的映射。
没有直接解析,但你可以自己做:
// TODO: Proper variable/method names, comments, logging, code format, etc.
java.time.LocalDateTime changeDateTimeByString(java.time.LocalDateTime current, String part,
boolean increaseOrDecrease){
java.time.LocalDateTime newDateTime = null;
Integer amount = Integer.parseInt(part.replaceAll("\D",""));
if(part.contains("S"))
newDateTime = increaseOrDecrease ? current.plusSeconds(amount) : current.minusSeconds(amount);
else if(part.contains("m"))
newDateTime = increaseOrDecrease ? current.plusMinutes(amount) : current.minusMinutes(amount);
else if(part.contains("h"))
newDateTime = increaseOrDecrease ? current.plusHours(amount) : current.minusHours(amount);
else if(part.contains("D"))
newDateTime = increaseOrDecrease ? current.plusDays(amount) : current.minusDays(amount);
else if(part.contains("W"))
newDateTime = increaseOrDecrease ? current.plusDays(amount * 7) : current.minusDays(amount * 7);
else if(part.contains("M"))
newDateTime = increaseOrDecrease ? current.plusMonths(amount) : current.minusMonths(amount);
else if(part.contains("Y"))
newDateTime = increaseOrDecrease ? current.plusYears(amount) : current.minusYears(amount);
else
System.err.println("Unknown format: " + part);
System.out.println("New dateTime after "+(increaseOrDecrease?"adding":"removing")+" "+part+": "+newDateTime);
return newDateTime;
}
该方法的参数是您在问题中指定的字符串;一个布尔值是否要添加或移动它;以及您要修改的输入日期。然后它将 return 修改日期(在这种情况下使用 Java 8+ java.time.LocalDateTime
)。即:changeDateTimeByString(dateTime, "1m", true);
将增加 1 分钟; changeDateTimeByString(dateTime, "2Y", false);
将删除 2 年。
Period
& Duration
我认为以下解决方案简单且非常通用(不完全通用)。
public static TemporalAmount parse(String feString) {
if (Character.isUpperCase(feString.charAt(feString.length() - 1))) {
return Period.parse("P" + feString);
} else {
return Duration.parse("PT" + feString);
}
}
您的日期单位(年、月、周、日)似乎用大写缩写表示(Y
、M
、W
和 D
) 而基于时间的(小时和分钟)是小写的(h
和 m
)。所以我测试字符串最后一个字符的大小写来决定是解析成Period
还是Duration
。我利用了 Period.parse
和 Duration.parse
都接受字母的事实。
您想在 Instant
或 LocalDateTime
之间添加或减去持续时间。这适用于 大多数 情况。让我们看看:
String[] timeAmountStrings = { "1m", "5M", "3D", "30m", "2h", "1Y", "3W" };
LocalDateTime base = LocalDateTime.of(2019, Month.MARCH, 1, 0, 0);
for (String tas : timeAmountStrings) {
TemporalAmount amount = parse(tas);
System.out.println("String: " + tas + " parsed: " + amount + " added: " + base.plus(amount));
try {
System.out.println("Added to Instant: " + Instant.EPOCH.plus(amount));
} catch (DateTimeException dte) {
System.out.println("Adding to Instant didn’t work: " + tas + ' ' + dte);
}
System.out.println();
}
输出:
String: 1m parsed: PT1M added: 2019-03-01T00:01
Added to Instant: 1970-01-01T00:01:00Z
String: 5M parsed: P5M added: 2019-08-01T00:00
Adding to Instant didn’t work: 5M java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Months
String: 3D parsed: P3D added: 2019-03-04T00:00
Added to Instant: 1970-01-04T00:00:00Z
String: 30m parsed: PT30M added: 2019-03-01T00:30
Added to Instant: 1970-01-01T00:30:00Z
String: 2h parsed: PT2H added: 2019-03-01T02:00
Added to Instant: 1970-01-01T02:00:00Z
String: 1Y parsed: P1Y added: 2020-03-01T00:00
Adding to Instant didn’t work: 1Y java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Years
String: 3W parsed: P21D added: 2019-03-22T00:00
Added to Instant: 1970-01-22T00:00:00Z
我们看到添加到 LocalDateTime
在所有情况下都有效。添加到 Instant
在大多数情况下都有效,只是我们不能添加几个月或几年的时间段。
是正确的,聪明的,做得很好。我会更进一步。
PeriodDuration
ThreeTen-Extra project adds functionality to the java.time classes built into Java 8 and later. Among its offerings is the PeriodDuration
class,结合了 Period
(年-月-日)和 Duration
(时-分-秒-毫微秒)。
顺便提一下,我提醒您不要将这两个概念结合起来。虽然直觉上这看起来不错,但如果您仔细考虑一下,您可能会认为它存在问题,具体取决于您的业务逻辑。
无论如何,让我们修改另一个答案中看到的代码。
- 如果输入是大写字母,我们将在标准 ISO 8601 duration format, and parse it as a
Period
中创建一个字符串,将该结果添加到我们的 PeriodDuration
对象中。
- 如果输入是小写字母,我们将创建标准 ISO 8601 持续时间格式的字符串,将其解析为
Duration
,并将结果添加到我们的 PeriodDuration
对象。
代码:
List < String > inputs = List.of( "1m" , "5M" , "3D" , "30m" , "2h" , "1Y" , "3W" );
PeriodDuration pd = PeriodDuration.ZERO;
for ( String input : inputs )
{
String s = input.trim();
String lastLetter = s.substring( s.length() - 1 );
int codepoint = Character.codePointAt( lastLetter , 0 );
if ( Character.isUpperCase( codepoint ) )
{
String x = "P" + s;
Period p = Period.parse( x );
pd = pd.plus( p );
} else
{ // Else, lowercase.
String x = "PT".concat( s ).toUpperCase();
Duration d = Duration.parse(x );
pd = pd.plus( d );
}
}
System.out.println( "pd.toString(): " + pd );
P1Y5M24DT2H31M
我从 FE 得到以下字符串:
1m
5M
3D
30m
2h
1Y
3W
对应1分钟5个月3天30分钟2小时1年3周
java中是否有解析的意思?
我想用 Instant(或 LocalDatetTime)操作(add/minus)。 java有办法吗?
我猜你应该使用 java.util.concurrent.TimeUnit 创建你自己的解析器方法,然后你可以使用任何框架来添加秒数
private static long convertToSeconds(int value, char unit)
{
long ret = value;
switch (unit)
{
case 'd':
ret = TimeUnit.DAYS.toSeconds(value);
break;
case 'h':
ret = TimeUnit.HOURS.toSeconds(value);
break;
case 'm':
ret = TimeUnit.MINUTES.toSeconds(value);
break;
case 's':
break;
default:
// fail
break;
}
return ret;
}
正如@Achinta Jha 所建议的那样,您可以自己实现一个简单的解析逻辑,例如
public static void main(String[] args) {
final String[] inputs = {"1m", "5M", "3D"/*, "30m", "2h", "1Y", "3W"*/};
Arrays.stream(inputs)
.map(x -> Parse(x))
.forEach(System.out::println);
}
private static final Map<Character, Function<String, Duration>> parsers = new HashMap<>();
static {
parsers.put('m', txt -> Duration.ofMinutes(Integer.parseInt(txt)));
parsers.put('M', txt -> Duration.ofDays(30 * Integer.parseInt(txt)));
parsers.put('D', txt -> Duration.ofDays(Integer.parseInt(txt)));
}
private static Duration Parse(String txt) {
Character last = txt.charAt(txt.length() - 1);
Function<String, Duration> parser = parsers.get(last);
return parser.apply(txt.substring(0, txt.length() - 1));
}
这是一个简单的解释性实现,既没有错误/null
s 处理,也没有完全支持建议的输入。
请注意,Duration
不支持静态 ofMonths
方法:我假设您打算 30 days
,但您可以根据需要的语义调整代码。
为了 add/subtract Duration
,您可以使用 Duration
class.
Spring 已经这样做了,但不是所有与您相同的选项。 Their implementation is
private static Duration simpleParse(String rawTime) {
if (rawTime == null || rawTime.isEmpty())
return null;
if (!Character.isDigit(rawTime.charAt(0)))
return null;
String time = rawTime.toLowerCase();
return tryParse(time, "ns", Duration::ofNanos)
.orElseGet(() -> tryParse(time, "ms", Duration::ofMillis).orElseGet(
() -> tryParse(time, "s", Duration::ofSeconds).orElseGet(
() -> tryParse(time, "m", Duration::ofMinutes).orElseGet(
() -> tryParse(time, "h", Duration::ofHours)
.orElseGet(() -> tryParse(time, "d",
Duration::ofDays)
.orElse(null))))));
}
private static Optional<Duration> tryParse(String time, String unit,
Function<Long, Duration> toDuration) {
if (time.endsWith(unit)) {
String trim = time.substring(0, time.lastIndexOf(unit)).trim();
try {
return Optional.of(toDuration.apply(Long.parseLong(trim)));
}
catch (NumberFormatException ignore) {
return Optional.empty();
}
}
return Optional.empty();
}
由于年和分钟都包括在内,我建议您使用评论中提到的一对 Duration
和 Period
解析。使用静态方法 Period::parse(CharSequence text)
and Duration.parse(CharSequence text)
及其默认格式(PnDTnHnMn.nS
用于 Duration
和 PnYnMnD
用于 Period
),因为它们不提供翻译自定义表达式的方法就这样。
对于此用例,您必须在表达式和给定格式之间创建自己的映射。
没有直接解析,但你可以自己做:
// TODO: Proper variable/method names, comments, logging, code format, etc.
java.time.LocalDateTime changeDateTimeByString(java.time.LocalDateTime current, String part,
boolean increaseOrDecrease){
java.time.LocalDateTime newDateTime = null;
Integer amount = Integer.parseInt(part.replaceAll("\D",""));
if(part.contains("S"))
newDateTime = increaseOrDecrease ? current.plusSeconds(amount) : current.minusSeconds(amount);
else if(part.contains("m"))
newDateTime = increaseOrDecrease ? current.plusMinutes(amount) : current.minusMinutes(amount);
else if(part.contains("h"))
newDateTime = increaseOrDecrease ? current.plusHours(amount) : current.minusHours(amount);
else if(part.contains("D"))
newDateTime = increaseOrDecrease ? current.plusDays(amount) : current.minusDays(amount);
else if(part.contains("W"))
newDateTime = increaseOrDecrease ? current.plusDays(amount * 7) : current.minusDays(amount * 7);
else if(part.contains("M"))
newDateTime = increaseOrDecrease ? current.plusMonths(amount) : current.minusMonths(amount);
else if(part.contains("Y"))
newDateTime = increaseOrDecrease ? current.plusYears(amount) : current.minusYears(amount);
else
System.err.println("Unknown format: " + part);
System.out.println("New dateTime after "+(increaseOrDecrease?"adding":"removing")+" "+part+": "+newDateTime);
return newDateTime;
}
该方法的参数是您在问题中指定的字符串;一个布尔值是否要添加或移动它;以及您要修改的输入日期。然后它将 return 修改日期(在这种情况下使用 Java 8+ java.time.LocalDateTime
)。即:changeDateTimeByString(dateTime, "1m", true);
将增加 1 分钟; changeDateTimeByString(dateTime, "2Y", false);
将删除 2 年。
Period
& Duration
我认为以下解决方案简单且非常通用(不完全通用)。
public static TemporalAmount parse(String feString) {
if (Character.isUpperCase(feString.charAt(feString.length() - 1))) {
return Period.parse("P" + feString);
} else {
return Duration.parse("PT" + feString);
}
}
您的日期单位(年、月、周、日)似乎用大写缩写表示(Y
、M
、W
和 D
) 而基于时间的(小时和分钟)是小写的(h
和 m
)。所以我测试字符串最后一个字符的大小写来决定是解析成Period
还是Duration
。我利用了 Period.parse
和 Duration.parse
都接受字母的事实。
您想在 Instant
或 LocalDateTime
之间添加或减去持续时间。这适用于 大多数 情况。让我们看看:
String[] timeAmountStrings = { "1m", "5M", "3D", "30m", "2h", "1Y", "3W" };
LocalDateTime base = LocalDateTime.of(2019, Month.MARCH, 1, 0, 0);
for (String tas : timeAmountStrings) {
TemporalAmount amount = parse(tas);
System.out.println("String: " + tas + " parsed: " + amount + " added: " + base.plus(amount));
try {
System.out.println("Added to Instant: " + Instant.EPOCH.plus(amount));
} catch (DateTimeException dte) {
System.out.println("Adding to Instant didn’t work: " + tas + ' ' + dte);
}
System.out.println();
}
输出:
String: 1m parsed: PT1M added: 2019-03-01T00:01 Added to Instant: 1970-01-01T00:01:00Z String: 5M parsed: P5M added: 2019-08-01T00:00 Adding to Instant didn’t work: 5M java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Months String: 3D parsed: P3D added: 2019-03-04T00:00 Added to Instant: 1970-01-04T00:00:00Z String: 30m parsed: PT30M added: 2019-03-01T00:30 Added to Instant: 1970-01-01T00:30:00Z String: 2h parsed: PT2H added: 2019-03-01T02:00 Added to Instant: 1970-01-01T02:00:00Z String: 1Y parsed: P1Y added: 2020-03-01T00:00 Adding to Instant didn’t work: 1Y java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Years String: 3W parsed: P21D added: 2019-03-22T00:00 Added to Instant: 1970-01-22T00:00:00Z
我们看到添加到 LocalDateTime
在所有情况下都有效。添加到 Instant
在大多数情况下都有效,只是我们不能添加几个月或几年的时间段。
PeriodDuration
ThreeTen-Extra project adds functionality to the java.time classes built into Java 8 and later. Among its offerings is the PeriodDuration
class,结合了 Period
(年-月-日)和 Duration
(时-分-秒-毫微秒)。
顺便提一下,我提醒您不要将这两个概念结合起来。虽然直觉上这看起来不错,但如果您仔细考虑一下,您可能会认为它存在问题,具体取决于您的业务逻辑。
无论如何,让我们修改另一个答案中看到的代码。
- 如果输入是大写字母,我们将在标准 ISO 8601 duration format, and parse it as a
Period
中创建一个字符串,将该结果添加到我们的PeriodDuration
对象中。 - 如果输入是小写字母,我们将创建标准 ISO 8601 持续时间格式的字符串,将其解析为
Duration
,并将结果添加到我们的PeriodDuration
对象。
代码:
List < String > inputs = List.of( "1m" , "5M" , "3D" , "30m" , "2h" , "1Y" , "3W" );
PeriodDuration pd = PeriodDuration.ZERO;
for ( String input : inputs )
{
String s = input.trim();
String lastLetter = s.substring( s.length() - 1 );
int codepoint = Character.codePointAt( lastLetter , 0 );
if ( Character.isUpperCase( codepoint ) )
{
String x = "P" + s;
Period p = Period.parse( x );
pd = pd.plus( p );
} else
{ // Else, lowercase.
String x = "PT".concat( s ).toUpperCase();
Duration d = Duration.parse(x );
pd = pd.plus( d );
}
}
System.out.println( "pd.toString(): " + pd );
P1Y5M24DT2H31M