繁体   English   中英

如何在 Java 中以 hh:mm:ss.SSS 格式格式化经过的时间间隔?

[英]How to format an elapsed time interval in hh:mm:ss.SSS format in Java?

我正在制作秒表,我使用 Java 的 SimpleDateFormat 将毫秒数转换为漂亮的“hh:mm:ss:SSS”格式。 问题是小时字段中总是有一些随机数。 这是我正在使用的代码:

public static String formatTime(long millis) {
    SimpleDateFormat sdf = new SimpleDateFormat("hh:mm:ss.SSS");

    String strDate = sdf.format(millis);
    return strDate;
}

如果我取下 hh 部分,那么它就可以正常工作。 否则在 hh 部分,即使传入的参数(毫秒数)为零,它也会随机显示类似“07”的内容。

不过,我对 SimpleDateFormat class 了解不多。 谢谢你的帮助。

最新的 JDK 内置了对您想做的事情的支持,其中包含一个鲜为人知的 class,称为TimeUnit

您要使用的是java.util.concurrent.TimeUnit来处理interval

SimpleDateFormat就像它听起来的那样,它格式化java.util.Date的实例,或者在你的情况下,它将long值转换为java.util.Date的上下文,它不知道如何处理间隔这显然是您正在使用的。

您可以轻松地做到这一点,而无需求助于 JodaTime 等外部库。

import java.util.concurrent.TimeUnit;

public class Main
{        
    private static String formatInterval(final long l)
    {
        final long hr = TimeUnit.MILLISECONDS.toHours(l);
        final long min = TimeUnit.MILLISECONDS.toMinutes(l - TimeUnit.HOURS.toMillis(hr));
        final long sec = TimeUnit.MILLISECONDS.toSeconds(l - TimeUnit.HOURS.toMillis(hr) - TimeUnit.MINUTES.toMillis(min));
        final long ms = TimeUnit.MILLISECONDS.toMillis(l - TimeUnit.HOURS.toMillis(hr) - TimeUnit.MINUTES.toMillis(min) - TimeUnit.SECONDS.toMillis(sec));
        return String.format("%02d:%02d:%02d.%03d", hr, min, sec, ms);
    }

    public static void main(final String[] args)
    {
        System.out.println(formatInterval(Long.parseLong(args[0])));
    }
}

output 将被格式化为这样

13:00:00.000

更短的方法是在Apache Commons Lang中使用DurationFormatUtils class :

public static String formatTime(long millis) {
    return DurationFormatUtils.formatDuration(millis, "HH:mm:ss.S");
}

为什么不是这个?

public static String GetFormattedInterval(final long ms) {
    long millis = ms % 1000;
    long x = ms / 1000;
    long seconds = x % 60;
    x /= 60;
    long minutes = x % 60;
    x /= 60;
    long hours = x % 24;

    return String.format("%02d:%02d:%02d.%03d", hours, minutes, seconds, millis);
}

这是我完成的第一个 Joda 工作,它看起来比 JDK 支持更乏味。 请求格式的 Joda 实现(对零字段做出一些假设)是:

public void printDuration(long milliSecs)
{
    PeriodFormatter formatter = new PeriodFormatterBuilder()
        .printZeroIfSupported()
        .appendHours()
        .appendSeparator(":")
        .minimumPrintedDigits(2)
        .appendMinutes()
        .appendSeparator(":")
        .appendSecondsWithMillis()
        .toFormatter();

    System.out.println(formatter.print(new Period(milliSecs)));
}

以下是我的做法,仅使用标准 JDK(通过将StringBuilder改回StringBuffer可以追溯到 Java 1.1):

static public String formatMillis(long val) {
    StringBuilder                       buf=new StringBuilder(20);
    String                              sgn="";

    if(val<0) { sgn="-"; val=Math.abs(val); }

    append(buf,sgn,0,(val/3600000)); val%=3600000;
    append(buf,":",2,(val/  60000)); val%=  60000;
    append(buf,":",2,(val/   1000)); val%=   1000;
    append(buf,".",3,(val        ));
    return buf.toString();
    }

/** Append a right-aligned and zero-padded numeric value to a `StringBuilder`. */
static private void append(StringBuilder tgt, String pfx, int dgt, long val) {
    tgt.append(pfx);
    if(dgt>1) {
        int pad=(dgt-1);
        for(long xa=val; xa>9 && pad>0; xa/=10) { pad--;           }
        for(int  xa=0;   xa<pad;        xa++  ) { tgt.append('0'); }
        }
    tgt.append(val);
    }

查看其他答案,我想出了这个 function ......

public static String formatInterval(final long interval, boolean millisecs )
{
    final long hr = TimeUnit.MILLISECONDS.toHours(interval);
    final long min = TimeUnit.MILLISECONDS.toMinutes(interval) %60;
    final long sec = TimeUnit.MILLISECONDS.toSeconds(interval) %60;
    final long ms = TimeUnit.MILLISECONDS.toMillis(interval) %1000;
    if( millisecs ) {
        return String.format("%02d:%02d:%02d.%03d", hr, min, sec, ms);
    } else {
        return String.format("%02d:%02d:%02d", hr, min, sec );
    }
}

这是发生了什么。 当您传递毫秒时,该数字相对于 1970 年 1 月 1 日。当您传递 0 时,它采用该日期并将其转换为您的本地时区。 如果您在中部时间,那么恰好是晚上 7 点。 如果你运行它,那么这一切都是有道理的。

new SimpleDateFormat().format(0) => 12/31/69 7:00 PM

编辑,我认为您想要做的是经过时间。 为此,我建议使用已经做得很好的JodaTime 你做类似的事情

PeriodFormatter formatter = new PeriodFormatterBuilder()
    .appendHours()
    .appendSuffix(" hour", " hours")
    .appendSeparator(" and ")
    .appendMinutes()
    .appendSuffix(" minute", " minutes")
    .appendSeparator(" and ")
    .appendSeconds()
    .appendSuffix(" second", " seconds")
    .toFormatter();

String formattedText = formatter.print(new Period(elapsedMilliSeconds));

这是另一种方法。 完全独立且完全向后兼容。 不限天数。

private static String slf(double n) {
  return String.valueOf(Double.valueOf(Math.floor(n)).longValue());
}

public static String timeSpan(long timeInMs) {
  double t = Double.valueOf(timeInMs);
  if(t < 1000d)
    return slf(t) + "ms";
  if(t < 60000d)
    return slf(t / 1000d) + "s " +
      slf(t % 1000d) + "ms";
  if(t < 3600000d)
    return slf(t / 60000d) + "m " +
      slf((t % 60000d) / 1000d) + "s " +
      slf(t % 1000d) + "ms";
  if(t < 86400000d)
    return slf(t / 3600000d) + "h " +
      slf((t % 3600000d) / 60000d) + "m " +
      slf((t % 60000d) / 1000d) + "s " +
      slf(t % 1000d) + "ms";
  return slf(t / 86400000d) + "d " +
    slf((t % 86400000d) / 3600000d) + "h " +
    slf((t % 3600000d) / 60000d) + "m " +
    slf((t % 60000d) / 1000d) + "s " +
    slf(t % 1000d) + "ms";
}

tl;博士

LocalTime                     // Represents a time-of-day, without date and without time zone.
.ofNanoOfDay(                 // Convert a count of nanoseconds into a time-of-day in a generic 24-hour day, ignoring real-world anomalies such as Daylight Saving Time (DST). 
    Duration                  // Class that represents a span-of-time not attached to the timeline.
    .of( milliseconds )       // Parse your count of milliseconds as a span-of-time.
    .toNanos()                // Extract total number of nanoseconds in this span-of-time.
)                             // Returns a `LocalDate` object.
.toString()                   // Generate text in standard ISO 8601 format for a time-of-day (*not* recommended by me). 

java.time.Duration

正确的 class 以小时-分钟-秒的比例表示未附加到时间线的时间跨度是Duration 对于年-月-日的比例,请使用Period

Duration d = Duration.of( milliseconds ) ;

ISO 8601 格式

我建议您避免使用 HH:MM:SS 的时间格式报告时间跨度。 我已经看到固有的模棱两可导致现实世界商业应用程序中的误解和混乱。

有一个报告此类值的标准,在ISO 8601定义PnYnMnDTnHnMnS P标志着开始,而T将任何年-月-日部分与任何时-分-秒部分分开。

java.time类在解析/生成字符串时默认使用 ISO 8601 标准格式。 这包括Duration class。

Duration d = Duration.ofMillis( 5_025_678L ) ;
String output = d.toString() ;

请参阅在 IdeOne.com 上实时运行的代码

PT1H23M45.678S

这些 ISO 8601 字符串可以被解析和生成。

Duration d = Duration.parse( "PT1H23M45.678S" ) ;

时间格式

但是,如果您坚持在持续时间中使用时间格式,则可以通过调用Duration object 上的to…Part方法来组合这样的刺痛。

Duration d = Duration.ofMillis( 5_025_678L ) ;
String output = d.toHoursPart() + ":" + d.toMinutesPart() + ":" + d.toSecondsPart() + "." + TimeUnit.NANOSECONDS.toMillis( d.toNanosPart() ) ;

1:23:45.678

或者我们可以滥用LocalTime class 来创建您的字符串。

Duration d = Duration.ofMillis( 5_025_678L ) ;
long nanoOfDay = d.toNanos() ;
LocalTime localTimeBogus = LocalTime.ofNanoOfDay( nanoOfDay ) ;
String output = localTimeBogus.toString() ;

同样,您可以看到此代码在 IdeOne.com 上实时运行

01:23:45.678

org.apache.commons.lang.time.DurationFormatUtils.formatDuration(stopWatch.getTime(), "HH:mm:ss")

格式根据您当地的时区发生,因此如果您传递 0,则假定为 0 GMT,然后将其转换为您当地的时区。

您要格式化的是持续时间,而不是日期时间

您有一个持续时间(经过的毫秒数)不同于日期时间,即时间轴上的瞬间。 您使用SimpleDateFormat来格式化java.util.Date ,它表示时间轴上的一个瞬间。 通过以下示例可以更好地理解差异:

  1. 这台机器2021-09-30'T'10:20:30 开始运行。
  2. 这台机器已经运行3 天 4 小时 5 分钟。

前者代表时间轴上的一个瞬间,而后者代表一个持续时间。

java.time

java.util日期时间 API 及其格式 API, SimpleDateFormat已过时且容易出错。 建议完全停止使用它们并切换到现代日期时间 API *

另外,下面引用的是来自Joda-Time主页的通知:

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

使用java.time的解决方案,现代日期时间 API:我建议您使用java.time.Duration ,它以ISO-8601 标准为模型,并作为JSR-310 实现的一部分与Java-8一起引入。 Java-9中,引入了一些更方便的方法。

演示:

import java.time.Duration;

public class Main {
    public static void main(String[] args) {
        // A sample input
        long millis = 123456789L;
        Duration duration = Duration.ofMillis(millis);
        // Default format
        System.out.println(duration);

        // Custom format
        // ####################################Java-8####################################
        String formattedElapsedTime = String.format("%2d:%02d:%02d.%d", duration.toHours() % 24,
                duration.toMinutes() % 60, duration.toSeconds() % 60, duration.toMillis() % 1000);
        System.out.println(formattedElapsedTime);
        // ##############################################################################

        // ####################################Java-9####################################
        formattedElapsedTime = String.format("%2d:%02d:%02d.%d", duration.toHoursPart(), duration.toMinutesPart(),
                duration.toSecondsPart(), duration.toMillisPart());
        System.out.println(formattedElapsedTime);
        // ##############################################################################
    }
}

Output:

PT34H17M36.789S
10:17:36.789
10:17:36.789

在线演示

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


*出于任何原因,如果您必须坚持Java 6或Java 7 7,您可以使用Threeten- Backport,以备份java888.TIME功能的大部分功能。级别仍然不符合 Java-8,检查Java 8+ APIs available through desugaringHow to use ThreeTenABP in Android Project Android 8.0 Oreo 已经提供了java.time的支持

使用普通的 Java Calendar间隔最多一天(24 小时),请参阅我对问题的回答:如何在 Java 中格式化时间间隔?

变体:长达 24 小时

经过时间少于 24 小时的简单格式化。 超过 24 小时,代码将仅显示第二天内的小时数,并且不会将经过的日期添加到小时数中。

public static String formatElapsedTime(long milliseconds) {

    SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
    sdf.setTimeZone(TimeZone.getTimeZone("UTC"));

    return sdf.format(milliseconds);
}

示例代码中缺少的功能:

  • 用“UTC”消除时区
  • 使用 24 小时格式“HH”

变体:超过 24 小时

public static String formatElapsedTimeOver24h(long milliseconds) {

    // Compiler will take care of constant arithmetics
    if (24 * 60 * 60 * 1000 > milliseconds) {
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
        sdf.setTimeZone(TimeZone.getTimeZone("UTC"));

        return sdf.format(milliseconds);

    } else {
        SimpleDateFormat sdf = new SimpleDateFormat(":mm:ss.SSS");
        sdf.setTimeZone(TimeZone.getTimeZone("UTC"));

        // Keep long data type
        // Compiler will take care of constant arithmetics
        long hours = milliseconds / (60L * 60L * 1000L);

        return hours + sdf.format(milliseconds);
    }
}

这个确实有效,但似乎我正在调整该方法的意图:-)。

public static String formatTime(long millis) {
    SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");

    String strDate = sdf.format(millis - 3600000);
    return strDate;
}

对于那些真正了解其工作原理的人,您可能会发现一些警告。

您可以在 DateUtils class 中使用formatElapsedTimeformatSameDayTime方法。

此方法必须适用于所有不太旧的 Java 版本)

public static String formatNanoSeconds(long duration) {
    StringBuilder sb = new StringBuilder(20);
    long quotient = duration / 1_000_000;
    long remainder = quotient % 1_000;
    sb.insert(0, String.format(".%03d", remainder));
    quotient = quotient / 1_000;
    remainder = quotient % 60;
    sb.insert(0, String.format(":%02d", remainder));
    quotient = quotient / 60;
    remainder = quotient % 60;
    sb.insert(0, String.format(":%02d", remainder));
    quotient = quotient / 60;
    remainder = quotient % 24;
    sb.insert(0, String.format(" %02d", remainder));
    quotient = quotient / 24;
    sb.insert(0, String.format("%d", quotient));
    return sb.toString();
}

StringBuilder.insert()虽然完成数组复制,但仍必须比字符串连接更好。

这是测试:

@Test
void formatNanoSeconds() {
    long m = 1_000_000;
    assertEquals("0 00:00:00.000", formatNanoSeconds(0));
    assertEquals("0 00:00:00.000", formatNanoSeconds(1));
    assertEquals("0 00:00:00.000", formatNanoSeconds(999_999));
    assertEquals("0 00:00:00.001", formatNanoSeconds(1_000_000));
    assertEquals("0 00:00:00.999", formatNanoSeconds(999 * m));
    assertEquals("0 00:00:01.000", formatNanoSeconds(1000 * m));
    assertEquals("0 00:00:59.000", formatNanoSeconds(59_000 * m));
    assertEquals("0 00:01:00.000", formatNanoSeconds(60_000 * m));
    assertEquals("0 00:01:01.000", formatNanoSeconds(61_000 * m));
    assertEquals("0 00:59:00.000", formatNanoSeconds(59 * 60_000 * m));
    assertEquals("0 01:00:00.000", formatNanoSeconds(60 * 60_000 * m));
    assertEquals("0 01:01:00.000", formatNanoSeconds(61 * 60_000 * m));
    assertEquals("0 23:00:00.000", formatNanoSeconds(23 * 60 * 60_000 * m));
    assertEquals("1 00:00:00.000", formatNanoSeconds(24 * 60 * 60_000 * m));
    assertEquals("1 01:00:00.000", formatNanoSeconds(25 * 60 * 60_000 * m));
    assertEquals("125 17:28:58.819", formatNanoSeconds(
            ((((125L * 24 * 3600) + (17 * 3600) + (28 * 60) + 58) * 1000) + 819) * m + 1));
    assertEquals("90 08:05:04.009", formatNanoSeconds(
            ((((90L * 24 * 3600) + (8 * 3600) + (5 * 60) + 4) * 1000) + 9) * m + 358));
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM