【问题标题】:Parsing time strings like "1h 30min"解析时间字符串,如“1h 30min”
【发布时间】:2011-06-19 17:09:55
【问题描述】:

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

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

【问题讨论】:

标签: java parsing time jodatime


【解决方案1】:

您可能需要根据自己的格式对其进行一些调整,但请尝试以下方式:

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 类来避免需要幻数。)

【讨论】:

  • 谢谢!只需稍加调整,我就可以完成我想要的大部分工作,而且它比我遇到的 150 行正则表达式/状态机怪物要好得多。 (顺便说一句,对于 Period -> millis 转换,Joda 的 DateTimeConstants 包含有用的常量,例如 MILLIS_PER_DAY。)
  • 有关使用 java.time 处理问题中提到的确切格式的一种方法,请参阅this answer。虽然我想知道 java.time 中是否有任何类似于 PeriodFormatterBuilder 的东西,但在这个答案中提供了一个类似的、更简单的解决方案。
【解决方案2】:

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

Duration d = Duration.parse("PT1H30M")

您可以convert this duration 以毫秒为单位的总长度。请注意Duration 的分辨率为纳秒级,因此您可能会丢失从nanosecondsmilliseconds 的数据。

long milliseconds = d.toMillis();

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

【讨论】:

  • 这也增加了持续支持 ISO8601 标准的好处。 ISO 字符串必须匹配 P[yY][mM][dD][T[hH][mM][s[.s]S]] 的形式。它使用 Java 8 中的标准 Java 类,还有 Java 6 和 7 的反向移植,请参阅:threeten.org/threetenbp
  • 我不确定它是否支持整个 ISO8601 标准,特别是一个带有周数的字符串:例如 P2W 是不可解析的,但我相信它符合标准。 Duration 的文档说:“从 PnDTnHnMn.nS 等文本字符串中获取持续时间。”
  • 当问题是关于“例如“30min”或“2h 15min”或“2d 15h 30min”时,这有什么帮助?
  • ...它似乎适用于某些示例...太糟糕了,它不适用于 ISO 8601 的维基百科文章中提到的示例!...(“P3Y6M4DT12H30M5S”)-(Java. io.Duration 版本55.0,Java 11)
  • 值得一提:如果您正在使用诸如“P3Y6M4D”之类的时间段,其中添加到某个时间点的实际值取决于该时间点(因此添加 1 个月将产生相同的下个月的日期而不是标准天数),您可以改用Period.parse。但是你不会得到比天更小的时间字段的支持。
【解决方案3】:

我想让天、小时和分钟成为可选的,这似乎可以做到这一点。请注意,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);

}

【讨论】:

  • PeriodParser 主要用作内部接口。 PeriodFormatter 是面向用户的 API。
  • DurationConverter 定义在哪里?
  • 虽然我最初认为这很有用,但它不能正确处理无效输入。或者至少可以,但那是在我们看不到的 DurationConverter 的实现中。
【解决方案4】:

仅供参考,刚刚写了几个小时,只使用java.time.*,很容易理解和定制任何需要;

此版本适用于以下字符串; 3d12h2y9m10d

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();
}

【讨论】:

  • 是的,很好的答案
  • 是:需要提一下DateTimeFormatter不适用于TemporalAmount,所以在java.time API中没有实现,标准的Duration解析是不可配置的(还)还有Duration实现依赖这种模式(最终私有不可修改......):([-+]?)P(?:([-+]?[0-9]+)D)?(T(?:([-+]?[0-9]+)H)?(?:([-+]?[0-9]+)M)?(?:([-+]?[0-9]+)(?:[.,]([0-9]{0,9}))?S)?)?
【解决方案5】:

java.time

以下引用来自home page of Joda-Time的通知:

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

使用现代日期时间 API java.time 的解决方案:

您可以将输入字符串转换为ISO_8601#Duration 格式,然后将其解析为java.time.Duration,这是在Java-8 中作为JSR-310 implementation 的一部分引入的。

演示:

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"
        ).forEach(s -> System.out.println(s + " => " + toMillis(s) + "ms"));
    }

    static long toMillis(String strDuration) {
        strDuration = strDuration.replaceAll("\\s+", "").replaceFirst("(\\d+d)", "P$1T");
        strDuration = strDuration.charAt(0) != 'P' ? "PT" + strDuration.replace("min", "m")
                : strDuration.replace("min", "m");
        Duration duration = Duration.parse(strDuration);
        return duration.toMillis();
    }
}

输出:

30min => 1800000ms
2h 15min => 8100000ms
2d 15h 30min => 228600000ms

ONLINE DEMO

通过 Trail: Date Time 了解有关 modern Date-Time API* 的更多信息。


* 如果您正在为一个 Android 项目工作,并且您的 Android API 级别仍然不符合 Java-8,请检查 Java 8+ APIs available through desugaring。请注意,Android 8.0 Oreo 已经提供了support for java.time

【讨论】:

    【解决方案6】:

    不,Joda 默认只采用持续时间、即时间隔和对象。对于后者,它接受日期或 SOAP ISO 格式等内容。你可以在这里为 Duration 类添加你自己的转换器,诚然这会隐藏你所有丑陋的代码。

    【讨论】:

      【解决方案7】:

      我知道这个帖子已经有几年的历史了,但它一直在谷歌搜索的顶部弹出,所以我想我们会分享这个。

      我们在几个项目中也需要此功能,因此我们为其创建了这个小型库:https://github.com/blueanvil/kotlin-duration-string

      它是用 Kotlin 编写的,但您也可以在 Java 代码中使用它(参见 JavaTest.java):

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

      【讨论】:

        【解决方案8】:

        不完全是 Java - 而是 Kotlin,不是使用 Joda,而是使用 JDK 的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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-08-22
          • 1970-01-01
          相关资源
          最近更新 更多