如何在Java中格式化时间?(例如,格式为H:MM:SS)

229

我想使用“H:MM:SS”这样的格式来格式化以秒为单位的时间段。目前Java中的实用工具是设计用来格式化时间而不是持续时间。

22个回答

232

如果您不想使用库,可以使用格式化程序或相关快捷方式自行完成。例如,给定秒数为s:

  String.format("%d:%02d:%02d", s / 3600, (s % 3600) / 60, (s % 60));

4
不一定要是整数。如果你有两个日期,只需将 date1.getTime() - date2.getTime() 插入上述使用 long primitive 的方程,它仍然有效。 - Josh
如果时间差超过24小时怎么办? - Rajish
2
@Rajish:在这种情况下,您需要询问OP他们想要什么!当然,如果需要,您可以将其分离为s / 86400,(s%86400)/ 3600 ...以获取天数... - bobince
我对s > 86400(一天)的解决方案是"\u221E" - 无穷大。 - QED
如果时间差超过24小时,它仍然可以很好地格式化为小时、分钟和秒的持续时间。对于我的使用情况来说,这是预期的结果。 - Audrius Meškauskas

191

我使用Apache common的DurationFormatUtils,用法如下:

DurationFormatUtils.formatDuration(millis, "**H:mm:ss**", true);

21
最简单的方法(采用 Apache Commons 风格)。您甚至可以使用 formatDurationHMS 方法,这可能是您大部分时间都会使用的方法。 - Marko
3
没问题!你也可以在单引号内添加其他文本:DurationFormatUtils.formatDuration(durationInMillis, "m '分钟' , s '秒钟'") - mihca
1
很遗憾,Apache的东西没有本地化,只支持英语。 - frodo2975

97
如果您使用的是Java 8之前的版本...您可以使用Joda TimePeriodFormatter。如果您真的有一个持续时间(即经过的时间,没有参考日历系统),那么大多数情况下应该使用Duration - 然后您可以调用toPeriod(指定任何您想要反映25小时变成1天1小时等的PeriodType)以获取您可以格式化的Period
如果您使用的是Java 8或更高版本:我通常建议使用java.time.Duration来表示持续时间。然后,如果需要,您可以调用getSeconds()之类的函数来获得整数以进行标准字符串格式化,就像bobince的答案一样 - 尽管您应该注意持续时间为负的情况,因为您可能想要在输出字符串中使用一个负号。所以类似这样:
public static String formatDuration(Duration duration) {
    long seconds = duration.getSeconds();
    long absSeconds = Math.abs(seconds);
    String positive = String.format(
        "%d:%02d:%02d",
        absSeconds / 3600,
        (absSeconds % 3600) / 60,
        absSeconds % 60);
    return seconds < 0 ? "-" + positive : positive;
}

如果只是格式化,这种方式相对简单,但是手动操作可能有些烦人。而对于解析来说,一般情况下会更加困难……当然,如果你想要的话,即使在使用Java 8的情况下仍然可以使用Joda Time。


2
@TimBüthe:不,我会在这种情况下开始使用java.time。(虽然我不能立即找到PeriodFormatter的等效物...) - Jon Skeet
1
PeriodFormatter没有包含在其中。这是Java 8日期时间API和Joda Time之间的区别之一。 - Tim Büthe
@RexKerr:并不是说它已经过时了——只是就我所知,Java 8没有提供自定义时间段格式。时间的流逝并没有改变这个事实,就我所知。如果您确实知道在Java 8中以自定义方式格式化时间段的方法,请提供相关信息。 - Jon Skeet
1
@RexKerr:当然,如果你想要的话,仍然可以使用Joda Time。我会再次编辑。回头看,按照听起来的样子,我应该推荐Duration。需要进行重大编辑... - Jon Skeet
8
更简单的方法是使用Duration类的方法:duration.toHours()duration.toMinutesPart()duration.toSecondsPart()。这些方法从Java 9版本开始可用。要获取绝对值,可以使用duration.abs() - recke96
显示剩余11条评论

74

自Java 9开始,这变得更容易了。虽然Duration仍然不能格式化,但添加了获取小时、分钟和秒的方法,使得该任务变得更加简单:

    LocalDateTime start = LocalDateTime.of(2019, Month.JANUARY, 17, 15, 24, 12);
    LocalDateTime end = LocalDateTime.of(2019, Month.JANUARY, 18, 15, 43, 33);
    Duration diff = Duration.between(start, end);
    String hms = String.format("%d:%02d:%02d", 
                                diff.toHours(), 
                                diff.toMinutesPart(), 
                                diff.toSecondsPart());
    System.out.println(hms);
此片段的输出结果为:

24:19:21


方便复制粘贴的 Kotlin 等效代码如下: "${diff.toHours()}:${diff.toMinutesPart()}:${diff.toSecondsPart()}" - undefined

30
long duration = 4 * 60 * 60 * 1000;
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS", Locale.getDefault());
log.info("Duration: " + sdf.format(new Date(duration - TimeZone.getDefault().getRawOffset())));

14
哈!当我使用这个方法时,我遇到了时区归一化的问题。在使用format函数之前,您需要添加sdf.setTimeZone(TimeZone.getTimeZone("GMT+0")); - Rajish
建议使用 DateFormat.getDateInstance() 而不是具体的类。 - Abhijit Sarkar
2
这个解决方案很脆弱。当小时数达到24小时时,它将翻转到 00:00:00.000。此外,负一秒被格式化为 23:59:59.000 - V.S.
我正在使用以下代码:String.format("Duration %tT", duration - TimeZone.getDefault().getOffset(0)),如果持续时间小于24小时,则可以正常工作。 - Petr Nalevka

16

对于小于24小时的时间段,有一种相当简单且(在我看来)优雅的方法:

DateTimeFormatter.ISO_LOCAL_TIME.format(value.addTo(LocalTime.of(0, 0)))

格式化程序需要一个时间对象才能进行格式化,因此您可以通过将持续时间添加到00:00(即午夜)的本地时间上来创建一个时间对象。这将为您提供从午夜到该时间的本地时间,很容易以标准的HH:mm:ss格式进行格式化。这种方法的优点是不需要外部库,并使用java.time库进行计算,而不是手动计算小时、分钟和秒。


1
好的技巧。对于超过24小时的时间段,这会给你余数模24小时,这不完全是我所需要的,但我同意这是迄今为止最优雅的方法。尽管如此,我还是有些失望,因为Java 8的Duration对象没有一些内置的格式化工具。 - Groostav

13

这个答案只使用Duration方法,并且适用于Java 8:

public static String format(Duration d) {
    long days = d.toDays();
    d = d.minusDays(days);
    long hours = d.toHours();
    d = d.minusHours(hours);
    long minutes = d.toMinutes();
    d = d.minusMinutes(minutes);
    long seconds = d.getSeconds() ;
    return 
            (days ==  0?"":days+" days,")+ 
            (hours == 0?"":hours+" hours,")+ 
            (minutes ==  0?"":minutes+" minutes,")+ 
            (seconds == 0?"":seconds+" seconds,");
}

11

我不确定这是否符合您的要求,但请查看此Android助手类。

import android.text.format.DateUtils

例如:DateUtils.formatElapsedTime()

7

这可能有点粗糙,但如果想使用Java 8的 java.time 完成此任务,这是一个不错的解决方案:

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalField;
import java.time.temporal.UnsupportedTemporalTypeException;

public class TemporalDuration implements TemporalAccessor {
    private static final Temporal BASE_TEMPORAL = LocalDateTime.of(0, 1, 1, 0, 0);

    private final Duration duration;
    private final Temporal temporal;

    public TemporalDuration(Duration duration) {
        this.duration = duration;
        this.temporal = duration.addTo(BASE_TEMPORAL);
    }

    @Override
    public boolean isSupported(TemporalField field) {
        if(!temporal.isSupported(field)) return false;
        long value = temporal.getLong(field)-BASE_TEMPORAL.getLong(field);
        return value!=0L;
    }

    @Override
    public long getLong(TemporalField field) {
        if(!isSupported(field)) throw new UnsupportedTemporalTypeException(new StringBuilder().append(field.toString()).toString());
        return temporal.getLong(field)-BASE_TEMPORAL.getLong(field);
    }

    public Duration getDuration() {
        return duration;
    }

    @Override
    public String toString() {
        return dtf.format(this);
    }

    private static final DateTimeFormatter dtf = new DateTimeFormatterBuilder()
            .optionalStart()//second
            .optionalStart()//minute
            .optionalStart()//hour
            .optionalStart()//day
            .optionalStart()//month
            .optionalStart()//year
            .appendValue(ChronoField.YEAR).appendLiteral(" Years ").optionalEnd()
            .appendValue(ChronoField.MONTH_OF_YEAR).appendLiteral(" Months ").optionalEnd()
            .appendValue(ChronoField.DAY_OF_MONTH).appendLiteral(" Days ").optionalEnd()
            .appendValue(ChronoField.HOUR_OF_DAY).appendLiteral(" Hours ").optionalEnd()
            .appendValue(ChronoField.MINUTE_OF_HOUR).appendLiteral(" Minutes ").optionalEnd()
            .appendValue(ChronoField.SECOND_OF_MINUTE).appendLiteral(" Seconds").optionalEnd()
            .toFormatter();

}

我刚试了一下,发现如果我在没有小时或分钟要格式化的情况下(例如Duration.ofSeconds(20)),调用“hh:mm:ss”的格式,则会出现“UnsupportedTemporalTypeException”。我只是删除了检查差异是否为“== 01”的代码。我想“01”是某种掩码值,我不完全理解,你能解释一下吗? - Groostav
这真的很有效。在我的情况下,我甚至添加了毫秒...关于 isSupported 方法,它返回信息是否有有效字段可显示在人类可读字符串上。无论如何,“掩码”实际上并不是掩码。代码显示 return value!=0l 而不是 return value!=01。下次请使用复制粘贴。 - MrJames
1
接口TemporalAccessor不应被误用于表示持续时间。JSR-310专门为此设计了接口TemporalAmount - Meno Hochschild
2
我建议您始终使用大写的 L 来表示 long 值。很容易将小写的 l 误读为数字 1,就像刚才演示的那样。 - Ole V.V.

7

还有另一种方法可以使其适用于Java8。不过,它仅在持续时间不超过24小时时才有效。

public String formatDuration(Duration duration) {
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("h:mm.SSS");
    return LocalTime.ofNanoOfDay(duration.toNanos()).format(formatter);
}

这个运行得很好,但是这是什么魔法?哈哈为什么以及如何实际起作用?ofNanoOfDay,toNanos... 为什么是LocalTime。在持续时间的上下文中这毫无意义 :-\ 然而它确实可以解决问题。 - t3chb0t
1
第一次投票!你今天让我很开心 :) Duration 需要转换为 LocalTime 才能使用其 format 方法。至于纳秒,它们用于获取毫秒数,但我们也可以编写类似于 LocalTime.ofSecondOfDay(duration.getSeconds()).format(formatter) 的代码。 - stanley
太棒了!秒表感觉更自然 :-] 是的,在这里赢得声望要么需要大量的工作,要么就必须非常幸运地获得数百个问题或答案的投票。人们对投票持相当谨慎态度。有时候只需要更多的时间让别人注意到你的答案。玩得开心,再次感谢您提供的简短解决方案。其他的方案都过于复杂 :) - t3chb0t
2
这是一个糟糕的答案,如果持续时间超过24小时,它将失败! - Enerccio

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接