解析像“1h 30min”这样的时间字符串

发布于 2024-11-15 22:38:45 字数 162 浏览 2 评论 0 原文

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

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

Anyone know of a Java library that can parse time strings such as "30min" or "2h 15min" or "2d 15h 30min" as milliseconds (or some kind of Duration object). Can Joda-Time do something like this?

(I have an ugly long method to maintain that does such parsing and would like to get rid of it / replace it with something that does a better job.)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

自此以后,行同陌路 2024-11-22 22:38:45

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

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()< /a> 获取 long 形式的经过时间(以秒为单位)。

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

You'll probably have to tweak this a bit for your own format, but try something along these lines:

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

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

note that there is a appendSuffix that takes a variants parameter if you need to make it more flexible.

Update: Joda Time has since added Period.toStandardDuration(), and from there you can use getStandardSeconds() to get the elapsed time in seconds as a long.

If you're using an older version without these methods you can still calculate a timestamp yourself by assuming the standard 24/hr in a day, 60min/hr, etc. (In this case, take advantage of the constants in the DateTimeConstants class to avoid the need for magic numbers.)

挽清梦 2024-11-22 22:38:45

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

Duration d = Duration.parse("PT1H30M")

您可以转换此持续时间到总长度(以毫秒为单位)。请注意,Duration 的分辨率为纳秒,因此您可能会丢失纳秒的数据毫秒

long milliseconds = d.toMillis();

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

Duration parsing is now included in Java 8. Use standard ISO 8601 format with Duration.parse.

Duration d = Duration.parse("PT1H30M")

You can convert this duration to the total length in milliseconds. Beware that Duration has a resolution of nanoseconds, so you may have data loss going from nanoseconds to milliseconds.

long milliseconds = d.toMillis();

The format is slightly different than what you describe but could be easily translated from one to another.

年华零落成诗 2024-11-22 22:38:45

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

使用乔达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);

}

I wanted to make the day, hour and minute optional and this seems to work to do that. Note that the appendSuffix() calls do not have a space after the character.

Using Joda 2.3.

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

The above code passes these tests.

@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);

}
再浓的妆也掩不了殇 2024-11-22 22:38:45

仅供参考,只是写了一个多小时,仅使用 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();
}

FYI, Just wrote this for hour+ periods, only uses java.time.*, pretty simple to understand and customize for any need;

This version works with strings like; 3d12h, 2y, 9m10d, etc.

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

栀子花开つ 2024-11-22 22:38:45

java.time

下面引用的是 主页的通知乔达时间

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

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

您可以将输入字符串转换为 ISO_8601#Duration 格式,然后将其解析为 java.time.Duration 作为 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",
                "2 days 15 hours 30 mins",
                "2 Days 15 Hours 30 Minutes",
                "2days 15hrs 30mins",
                "2 Hours 15 Minutes 10 Seconds"
        ).forEach(s -> System.out.println(s + " => " + toMillis(s) + "ms"));
    }

    static long toMillis(String strDuration) {
        strDuration = strDuration.toUpperCase()
                .replaceAll("\\s+", "")
                .replaceAll("DAYS?", "D")
                .replaceAll("(?:HOURS?)|(?:HRS?)", "H")
                .replaceAll("(?:MINUTES?)|(?:MINS?)", "M")
                .replaceAll("(?:SECONDS?)|(?:SECS?)", "S")
                .replaceAll("(\\d+D)", "P$1T");
        strDuration = strDuration.charAt(0) != 'P' ? "PT" + strDuration : strDuration;
        // System.out.println(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
2 days 15 hours 30 mins => 228600000ms
2 Days 15 Hours 30 Minutes => 228600000ms
2days 15hrs 30mins => 228600000ms
2 Hours 15 Minutes 10 Seconds => 8110000ms

在线演示

正则表达式演示

了解有关现代日期时间 API*< /sup> 来自跟踪:日期时间


* 如果您正在处理 Android 项目,并且您的 Android API 级别仍然不符合 Java-8,请检查 通过脱糖提供的 Java 8+ API。请注意,Android 8.0 Oreo 已提供 java.time 的支持

java.time

Quoted below is a notice from the home page of Joda-Time:

Note that from Java SE 8 onwards, users are asked to migrate to java.time (JSR-310) - a core part of the JDK which replaces this project.

Solution using java.time, the modern Date-Time API:

You can convert the input string into ISO_8601#Duration format and then parse the same into java.time.Duration which was introduced with Java-8 as part of JSR-310 implementation.

Demo:

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",
                "2 days 15 hours 30 mins",
                "2 Days 15 Hours 30 Minutes",
                "2days 15hrs 30mins",
                "2 Hours 15 Minutes 10 Seconds"
        ).forEach(s -> System.out.println(s + " => " + toMillis(s) + "ms"));
    }

    static long toMillis(String strDuration) {
        strDuration = strDuration.toUpperCase()
                .replaceAll("\\s+", "")
                .replaceAll("DAYS?", "D")
                .replaceAll("(?:HOURS?)|(?:HRS?)", "H")
                .replaceAll("(?:MINUTES?)|(?:MINS?)", "M")
                .replaceAll("(?:SECONDS?)|(?:SECS?)", "S")
                .replaceAll("(\\d+D)", "P$1T");
        strDuration = strDuration.charAt(0) != 'P' ? "PT" + strDuration : strDuration;
        // System.out.println(strDuration);
        Duration duration = Duration.parse(strDuration);
        return duration.toMillis();
    }
}

Output:

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

Online Demo

Regex Demo

Learn more about the modern Date-Time API* from Trail: Date Time.


* If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring. Note that Android 8.0 Oreo already provides support for java.time.

星軌x 2024-11-22 22:38:45

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

No, Joda defaults to taking only Durations, Instant intervals, and objects. For the latter it accepts things like Dates or SOAP ISO format. You can add you own converter here for the Duration class, and admittedly that would hide all your ugly code.

万劫不复 2024-11-22 22:38:45

不完全是 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

Not exactly Java - rather Kotlin, and not using Joda but JDK's 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
}
  • Only supports days (since the underlying ISO 8601 does not standardize weeks, months etc.)
  • Does not handle relative in the future, although easy to add.
  • Is not meant as a full-blown implementation of duration parsing, so take it as that.

Taken from this Gist:
https://gist.github.com/OndraZizka/5fd56479ed2f6175703eb8a2e1bb1088

不离久伴 2024-11-22 22:38:45

我意识到这个帖子已经有几年了,但它不断出现在谷歌搜索的顶部,所以我想我们应该分享这个。

这是用 Kotlin 编写的完整示例


import java.time.Duration
import java.util.regex.Pattern


fun Duration.toHumanReadableString(): String {
    val duration = this.truncatedTo(ChronoUnit.MILLIS)

    if (duration == Duration.ZERO) {
        return "0ms"
    }

    val elements = listOf(
        "${duration.toDaysPart()}d",
        "${duration.toHoursPart()}h",
        "${duration.toMinutesPart()}m",
        "${duration.toSecondsPart()}s",
        "${duration.toMillisPart()}ms"
    )
    val start = elements.indexOfFirst { it[0] != '0' }
    val end = elements.indexOfLast { it[0] != '0' }
    return elements.subList(start, end + 1).joinToString(" ")
}


internal val REGEX_DURATION = Pattern.compile("""
    ^(
       (
         ((?<days>\d+)d)?(\s*)
         ((?<hours>(0*(2[0-3]|[0-1]?[0-9])))h)?(\s*)
         ((?<minutes>(0*([0-5]?[0-9])))m)?(\s*)
         ((?<seconds>(0*([0-5]?[0-9])))s)?(\s*)
         ((?<milliseconds>(0*([0-9]|[1-9][0-9]|[1-9][0-9][0-9])))ms)?
      )
    | (
         ((?<days2>\d+)d)
        |((?<hours2>\d+)h)
        |((?<minutes2>\d+)m)
        |((?<seconds2>\d+)s)
        |((?<milliseconds2>\d+)ms)
      )
    )$
""".replace("\\s+".toRegex(), "")
        .trim())

internal const val MS = 1L
internal const val SEC = 1000 * MS
internal const val MIN = 60 * SEC
internal const val HOUR = 60 * MIN
internal const val DAY = 24 * HOUR

internal val GROUP_UNIT_CONVERTERS = mapOf(
        "days" to DAY,
        "hours" to HOUR,
        "minutes" to MIN,
        "seconds" to SEC,
        "milliseconds" to MS,
        "days2" to DAY,
        "hours2" to HOUR,
        "minutes2" to MIN,
        "seconds2" to SEC,
        "milliseconds2" to MS
)

fun String.toDuration(): Duration? {
    val matcher = REGEX_DURATION.matcher(this.trim())
    return if (matcher.matches()) {

        var durationNs = 0L

        GROUP_UNIT_CONVERTERS.forEach { (groupName, multiplier) ->
            val amount = matcher.group(groupName)?.toLong()
            if (amount != null)
                durationNs += amount * multiplier
        }
        Duration.ofMillis(durationNs)
    } else {
        null
    }
}

I realise this thread is a few years old but it keeps popping up at the top of a Google search so I thought we'd share this.

Here's a complete example written in Kotlin


import java.time.Duration
import java.util.regex.Pattern


fun Duration.toHumanReadableString(): String {
    val duration = this.truncatedTo(ChronoUnit.MILLIS)

    if (duration == Duration.ZERO) {
        return "0ms"
    }

    val elements = listOf(
        "${duration.toDaysPart()}d",
        "${duration.toHoursPart()}h",
        "${duration.toMinutesPart()}m",
        "${duration.toSecondsPart()}s",
        "${duration.toMillisPart()}ms"
    )
    val start = elements.indexOfFirst { it[0] != '0' }
    val end = elements.indexOfLast { it[0] != '0' }
    return elements.subList(start, end + 1).joinToString(" ")
}


internal val REGEX_DURATION = Pattern.compile("""
    ^(
       (
         ((?<days>\d+)d)?(\s*)
         ((?<hours>(0*(2[0-3]|[0-1]?[0-9])))h)?(\s*)
         ((?<minutes>(0*([0-5]?[0-9])))m)?(\s*)
         ((?<seconds>(0*([0-5]?[0-9])))s)?(\s*)
         ((?<milliseconds>(0*([0-9]|[1-9][0-9]|[1-9][0-9][0-9])))ms)?
      )
    | (
         ((?<days2>\d+)d)
        |((?<hours2>\d+)h)
        |((?<minutes2>\d+)m)
        |((?<seconds2>\d+)s)
        |((?<milliseconds2>\d+)ms)
      )
    )$
""".replace("\\s+".toRegex(), "")
        .trim())

internal const val MS = 1L
internal const val SEC = 1000 * MS
internal const val MIN = 60 * SEC
internal const val HOUR = 60 * MIN
internal const val DAY = 24 * HOUR

internal val GROUP_UNIT_CONVERTERS = mapOf(
        "days" to DAY,
        "hours" to HOUR,
        "minutes" to MIN,
        "seconds" to SEC,
        "milliseconds" to MS,
        "days2" to DAY,
        "hours2" to HOUR,
        "minutes2" to MIN,
        "seconds2" to SEC,
        "milliseconds2" to MS
)

fun String.toDuration(): Duration? {
    val matcher = REGEX_DURATION.matcher(this.trim())
    return if (matcher.matches()) {

        var durationNs = 0L

        GROUP_UNIT_CONVERTERS.forEach { (groupName, multiplier) ->
            val amount = matcher.group(groupName)?.toLong()
            if (amount != null)
                durationNs += amount * multiplier
        }
        Duration.ofMillis(durationNs)
    } else {
        null
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文