解析“1h 30min”等时间字符串

问题描述 投票:0回答:8

任何人都知道 Java 库可以将“30min”或“2h 15min”或“2d 15h 30min”等时间字符串解析为毫秒(或某种 Duration 对象)。 Joda-Time 可以做这样的事情吗?

(我有一个丑陋的长方法来维护它进行这样的解析,并且想摆脱它/用做得更好的东西替换它。)

java parsing time jodatime
8个回答
37
投票

您可能需要根据自己的格式稍微调整一下,但请尝试以下操作:

PeriodFormatter formatter = new PeriodFormatterBuilder()
    .appendDays().appendSuffix("d ")
    .appendHours().appendSuffix("h ")
    .appendMinutes().appendSuffix("min")
    .toFormatter();

Period p = formatter.parsePeriod("2d 5h 30min");

请注意,如果您需要使其更加灵活,则有一个

appendSuffix
需要
variants
参数。

更新:Joda Time 自此添加了

Period.toStandardDuration()
,从那里您可以使用
getStandardSeconds()
来获取以秒为单位的经过时间
long

如果您使用的是没有这些方法的旧版本,您仍然可以通过假设一天中的标准 24/小时、60 分钟/小时等自行计算时间戳。(在这种情况下,请利用

DateTimeConstants 中的常量) 
类以避免需要幻数。)


36
投票

持续时间解析现已包含在 Java 8 中。使用标准 ISO 8601 格式和

Duration.parse

Duration d = Duration.parse("PT1H30M")

您可以将此持续时间转换为总长度(以毫秒为单位)。请注意,

Duration
的分辨率为纳秒,因此您可能会丢失从纳秒毫秒的数据。

long milliseconds = d.toMillis();

格式与您描述的格式略有不同,但可以轻松地从一种格式转换为另一种格式。


15
投票

我想让日期、小时和分钟可选,这似乎可以做到这一点。 请注意,appendSuffix() 调用在字符后没有空格。

使用 Joda 2.3。

PeriodParser parser = new PeriodFormatterBuilder()
        .appendDays().appendSuffix("d").appendSeparatorIfFieldsAfter(" ")
        .appendHours().appendSuffix("h").appendSeparatorIfFieldsAfter(" ")
        .appendMinutes().appendSuffix("min")
        .toParser();

上面的代码通过了这些测试。

@Test
public void testConvert() {
    DurationConverter c = new DurationConverter();

    Duration d;
    Duration expected;

    d = c.convert("1d");
    expected = Duration.ZERO
            .withDurationAdded(Duration.standardDays(1),1);
    assertEquals(d, expected);

    d = c.convert("1d 1h 1min");
    expected = Duration.ZERO
            .withDurationAdded(Duration.standardDays(1),1)
            .withDurationAdded(Duration.standardHours(1),1)
            .withDurationAdded(Duration.standardMinutes(1),1);
    assertEquals(d, expected);


    d = c.convert("1h 1min");
    expected = Duration.ZERO
            .withDurationAdded(Duration.standardHours(1),1)
            .withDurationAdded(Duration.standardMinutes(1),1);
    assertEquals(d, expected);

    d = c.convert("1h");
    expected = Duration.ZERO
            .withDurationAdded(Duration.standardHours(1),1);
    assertEquals(d, expected);

    d = c.convert("1min");
    expected = Duration.ZERO
            .withDurationAdded(Duration.standardMinutes(1),1);
    assertEquals(d, expected);

}

8
投票

仅供参考,只是写了一个多小时,仅使用

java.time.*
,非常容易理解并根据任何需要进行定制;

此版本适用于以下字符串:

3d12h
2y
9m10d

import java.time.Duration;
import java.time.Instant;
import java.time.Period;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.Locale;
private static final Pattern periodPattern = Pattern.compile("([0-9]+)([hdwmy])");

public static Long parsePeriod(String period){
    if(period == null) return null;
    period = period.toLowerCase(Locale.ENGLISH);
    Matcher matcher = periodPattern.matcher(period);
    Instant instant=Instant.EPOCH;
    while(matcher.find()){
        int num = Integer.parseInt(matcher.group(1));
        String typ = matcher.group(2);
        switch (typ) {
            case "h":
                instant=instant.plus(Duration.ofHours(num));
                break;
            case "d":
                instant=instant.plus(Duration.ofDays(num));
                break;
            case "w":
                instant=instant.plus(Period.ofWeeks(num));
                break;
            case "m":
                instant=instant.plus(Period.ofMonths(num));
                break;
            case "y":
                instant=instant.plus(Period.ofYears(num));
                break;
        }
    }
    return instant.toEpochMilli();
}


3
投票

java.time

以下引用来自Joda-Time主页的通知:

请注意,从 Java SE 8 开始,用户被要求迁移到 java.time (JSR-310) - JDK 的核心部分,它将取代该项目。

使用现代日期时间 API java.time

 的解决方案:

您可以将输入字符串转换为

ISO_8601#Duration 格式,然后将其解析为 java.time.Duration

,这是随 Java-8 作为 JSR-310 实现的一部分引入的。

演示:

import java.time.Duration; import java.util.stream.Stream; public class Main { public static void main(String[] args) { // Test Stream.of( "30min", "2h 15min", "2d 15h 30min", "30sec", "2h 15min 10sec", "2day 15hour 30min", "2days 15hours 30mins" ).forEach(s -> System.out.println(s + " => " + toMillis(s) + "ms")); } static long toMillis(String strDuration) { strDuration = strDuration.replaceAll("\\s+", "") .replaceFirst("day(?:s)?", "d") .replaceFirst("hour(?:s)?", "h") .replaceFirst("min(?:s)?", "m") .replaceFirst("sec(?:s)?", "s") .replaceFirst("(\\d+d)", "P$1T"); strDuration = strDuration.charAt(0) != 'P' ? "PT" + strDuration : strDuration; Duration duration = Duration.parse(strDuration); return duration.toMillis(); } }

输出:

30min => 1800000ms 2h 15min => 8100000ms 2d 15h 30min => 228600000ms 30sec => 30000ms 2h 15min 10sec => 8110000ms 2day 15hour 30min => 228600000ms 2days 15hours 30mins => 228600000ms

在线演示

Trail:日期时间了解有关 现代日期时间 API *的更多信息。


* 如果您正在从事 Android 项目,并且您的 Android API 级别仍然不符合 Java-8,请检查 通过脱糖可用的 Java 8+ API。请注意,Android 8.0 Oreo 已经提供了对 java.time

支持。


0
投票


0
投票

我们在几个项目中也需要此功能,因此我们为其创建了这个小型库:

https://github.com/blueanvil/kotlin-duration-string

它是用 Kotlin 编写的,但您也可以从 Java 代码中使用它(请参阅

JavaTest.java

): Duration duration = toDuration("24d 3h 45m"); ... toHumanReadableString(duration);



0
投票
java.time

:

val RELAXED_FORMATTER = DateTimeFormatter.ofPattern("yyyy[-MM[-dd[' 'HH:mm[:ss[.SSS]]]]]")

fun parseTemporalInput(input: String): LocalDateTime? {
    var result = LocalDateTime.MAX.withNano(0)

    if (input.lowercase() == "now")
        return LocalDateTime.now()

    try {
        val parsed = RELAXED_FORMATTER.parse(input)
        for (field in listOf(YEAR, MONTH_OF_YEAR, DAY_OF_MONTH, HOUR_OF_DAY, MINUTE_OF_HOUR, SECOND_OF_MINUTE)) {
            try {
                result = result.with(field, parsed.getLong(field))
            } catch (ex: UnsupportedTemporalTypeException) {
                result = result.with(field, if (field.isDateBased) 1 else 0)
            }
        }
        return result
    } 
    catch (parseEx: DateTimeParseException) {
        try {
            val inputToIso8601 = "P" + input.uppercase().replace("-","").replace(" ", "").replace("D", "DT").removeSuffix("T")
            // Expected format:  "PnDTnHnMn.nS"
            val duration = Duration.parse(inputToIso8601)

            val base = LocalDateTime.now().let {
                if (!inputToIso8601.contains("D")) it
                else it.truncatedTo(ChronoUnit.DAYS)
            }

            return base.minus(duration)
        }
        catch (ex: DateTimeParseException) {
            return null
        }
    }
    return null
}

仅支持天(因为底层 ISO 8601 没有标准化周、月等)
  • 虽然很容易添加,但将来不会处理相对关系。
  • 并不意味着持续时间解析的完整实现,所以就这样吧。
  • 摘自此要点:
https://gist.github.com/OndraZizka/5fd56479ed2f6175703eb8a2e1bb1088

© www.soinside.com 2019 - 2024. All rights reserved.