Java:如何将字符串(HH:MM:SS)转换为持续时间?

23
我想将格式为HH:MM:SS或MM:SS或SS的字符串转换为Duration数据类型。
解决方案:
    private ArrayList<Duration> myCdDuration = new ArrayList<Duration>();

        private void convert(String aDuration) {

            chooseNewDuration(stringToInt(splitDuration(aDuration))); //stringToInt() returns an int[] and splitDuration() returns a String[]
        }

        private void chooseNewDuration(int[] array) {
            int elements = array.length;
            switch (elements) {
            case 1:
                myCdDuration.add(newDuration(true, 0, 0, 0, 0, 0, array[0]));
                break;
            case 2:
                myCdDuration.add(newDuration(true, 0, 0, 0, 0, array[0], array[1]));
                break;
            case 3:
                myCdDuration.add(newDuration(true, 0, 0, 0, array[0], array[1],
                        array[2]));
                break;
            }
        }

感谢您的帮助...有没有更简单的方法来做到这一点?-> 创建自己的Duration类:

public class Duration {
        private int intSongDuration;
        private String printSongDuration;

        public String getPrintSongDuration() {
            return printSongDuration;
        }

        public void setPrintSongDuration(int songDuration) {
            printSongDuration = intToStringDuration(songDuration);
        }

        public int getIntSongDuration() {
            return intSongDuration;
        }

        public void setIntSongDuration(int songDuration) {
            intSongDuration = songDuration;
        }

        public Duration(int songDuration) {

            setIntSongDuration(songDuration);
        }

将整数值转换为字符串以进行输出/打印:

private String intToStringDuration(int aDuration) {
    String result = "";

    int hours = 0, minutes = 0, seconds = 0;

    hours = aDuration / 3600;
    minutes = (aDuration - hours * 3600) / 60;
    seconds = (aDuration - (hours * 3600 + minutes * 60));

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

1
作为编程语言,你不能仅仅将字符串强制转换为另一个对象。或许你想展示下Duration类的样子?它也许有一个可以接受Date对象、字符串或整型参数的构造函数? - Hovercraft Full Of Eels
http://docs.oracle.com/javase/1.5.0/docs/api/javax/xml/datatype/Duration.html - Frank
3
用于XML的Duration类...不得在其他情况下使用... - Michael
8个回答

42

简述

不需要定义自己的Duration类,因为Java已经提供了一个。

Duration.between (                  // Represent a span of time of hours, minutes, seconds. 
    LocalTime.MIN ,                 // 00:00:00
    LocalTime.parse ( "08:30:00" )  // Parse text as a time-of-day. 
)                                   // Returns a `Duration` object, a span-of-time. 
.toString()                         // Generate a `String` with text in standard ISO 8601 format. 

PT8H30M

并解析标准的 ISO 8601 格式的文本。

Duration.parse( "PT8H30M" )   // Parse standard ISO 8601 text yo get a `Duration` object. 

Table of span-of-time classes in Java and in the ThreeTen-Extra project


避免使用 HH:MM:SS 格式

如果你想表达的是“八个半小时”而不是“早上八点半”,那么请避免使用 HH:MM:SS 这种格式。这种格式很容易让人误解为时间,造成歧义。相反,应该使用下面讨论的标准 ISO 8601 格式。

持续时间和时间点是两个完全不同的概念。你必须清楚地了解它们,并在脑海中将它们区分开来。使用模糊的 HH:MM:SS 格式会使这种区分更加困难(所以要避免使用这种格式!)。

java.time

现代的做法是使用java.time类。

LocalTime

首先将字符串解析为LocalTime。此类表示不带日期和时区的时间,没有时区意味着这些对象基于通用的 24 小时时钟,而不考虑 夏令时(DST)等异常情况。

实际上,我们并不需要一个LocalTime,因为您的输入字符串表示的是一段时间跨度,而不是一天中的某个时间点。但这只是第一步。

LocalTime lt = LocalTime.parse ( "08:30:00" );

持续时间

为了表示所需的时间跨度,我们需要使用Duration类。此类用于与时间轴无关的时间跨度。我们可以通过获取从当天时钟的开始时间00:00:00.0LocalTime.MIN以及我们刚刚实例化的lt来创建一个时间跨度。

Duration d = Duration.between ( LocalTime.MIN , lt );

编辑输入字符串

上述使用LocalTime的方法仅适用于您的输入字符串表示少于24小时的持续时间。如果超过24小时,则需要自己解析输入字符串。

可以使用以下类似的代码。当然,实际的解析取决于解决特定输入字符串的歧义。例如,50:00是指五十个小时还是五十分钟?(这种歧义是尽可能避免使用此混乱格式并坚持使用ISO 8601格式的强烈原因。)

String input = "50:00";  // Or "50:00:00" (fifty hours, either way)

String[] parts = input.split ( ":" );
Duration d = Duration.ZERO;
if ( parts.length == 3 ) {
    int hours = Integer.parseInt ( parts[ 0 ] );
    int minutes = Integer.parseInt ( parts[ 1 ] );
    int seconds = Integer.parseInt ( parts[ 2 ] );
    d = d.plusHours ( hours ).plusMinutes ( minutes ).plusSeconds ( seconds );
} else if ( parts.length == 2 ) {
    int hours = Integer.parseInt ( parts[ 0 ] );
    int minutes = Integer.parseInt ( parts[ 1 ] );
    d = d.plusHours ( hours ).plusMinutes ( minutes );
} else {
    System.out.println ( "ERROR - Unexpected input." );
}

ISO 8601

通过调用 Duration::toString 方法,我们可以生成一个符合标准的ISO 8601持续时间格式的字符串来查看结果。在解析/生成字符串时,java.time 类默认使用 ISO 8601 格式。对于持续时间,标准格式为 PnYnMnDTnHnMnS,其中 P 标记开始,T 将年-月-日部分与小时-分钟-秒部分分开。因此,我们的8个半小时将显示为 PT8H30M

System.out.println ( "d.toString(): " + d );

d.toString(): PT8H30M

收集 Duration 对象

您可以创建一个包含 Duration 类型元素的列表。

List<Duration> durations = new ArrayList<>( 3 );  // Initial capacity of 3 elements.
durations.add( d ) ;
durations.add( Duration.between ( LocalTime.MIN , LocalTime.parse ( "03:00:00" ) ) ) ;
durations.add( Duration.between ( LocalTime.MIN , LocalTime.parse ( "01:15:00" ) ) ) ;

durations.toString(): [PT8H30M, PT3H, PT1H15M]

请记住,您在输出中看到的字符串(如PT8H30M)只是生成的字符串的输出。 Duration 类型不是一个简单的字符串,而是通过其 toString 方法生成一个 String 对象。

如果您坚持使用 ISO 8601 格式,您可以轻松解析和生成这样的字符串。无需执行我们在本答案开头执行的 LocalTime 转换操作。

Duration d = Duration.parse( "PT8H30M" );

查看此示例 在IdeOne.com上的代码实时演示


关于 java.time

java.time 框架已内置于 Java 8 及更高版本中。这些类替代了老旧且问题多多的 遗留 日期时间类,如 java.util.DateCalendarSimpleDateFormat

Joda-Time 项目现处于 维护模式,建议迁移至 java.time 类。

要了解更多,请参阅Oracle教程。并在Stack Overflow上搜索许多示例和解释。规范是JSR 310
在哪里获取java.time类? 这个ThreeTen-Extra项目扩展了java.time的功能,提供了额外的类。该项目是java.time可能未来添加内容的试验场。您可能会在这里找到一些有用的类,例如Interval, YearWeek, YearQuarter, 以及更多

那些使用Spring的人可能还想看一下DurationStyle::detectAndParse,它除了ISO-8601格式外,还允许更可读的格式,例如30ms。(参考文献:https://docs.spring.io/spring-boot/docs/2.0.4.RELEASE/reference/htmlsingle/#boot-features-external-config-conversion) - aksh1618

20

我假设你最终想要实现的是计算CD的持续时间(以秒为单位)。

有几种方法可以实现这一点,但我认为最简单直接的方法是通过在:上分割来获取小时、分钟和秒字段,然后手动计算持续时间:

String timestampStr = "14:35:06";
String[] tokens = timestampStr.split(":");
int hours = Integer.parseInt(tokens[0]);
int minutes = Integer.parseInt(tokens[1]);
int seconds = Integer.parseInt(tokens[2]);
int duration = 3600 * hours + 60 * minutes + seconds;

在我们了解什么是Duration之前就作答,肯定会得到错误的答案。 - Hovercraft Full Of Eels
1
尽管我不知道Frank在使用javax.xml.datatype.Duration时想要做什么,但我认为他的最终目标从主题上是很清楚的。但我理解你的观点,我可能是错的。 :P 我编辑了我的答案来说明我的假设。 - Mansoor Siddiqui
那么现在呢?他会尝试将int强制转换为Duration吗?关键是要理解Duration数据类型,然后使用它找到正确的构造方式--就像andri所示的那样,使用DataTypeFactory(给他加1分)。 - Hovercraft Full Of Eels
正如Alex所指出的那样,谁知道Duration类是否合适呢?Frank的代码中没有XML相关的内容,因此仅仅关注他试图实现的更广泛的目标可能会更有帮助。 - Mansoor Siddiqui
@Hovercraft Full Of Eels 他需要从字符串中解析时间量(或者进入你的气垫船去疯人院!!;) - Michael
@Michael:我已经在那里了!但是,我同意你的观点,这也是我取消对Mansoor答案的踩票的原因。我认为主要问题在于这是一个写得很差的问题。 - Hovercraft Full Of Eels

4

java.time.Duration

已经有几个答案提到了使用现代Java日期和时间API中的Duration类来处理持续时间。有一些方法可以将字符串解析为Duration,但并不清楚哪种方法最好。下面是我的建议。

基本用法

从简单的格式开始,例如hh:mm:ss,例如01:30:41表示1小时30分钟41秒。

    String durationString = "01:30:41";
    
    String iso = durationString.replaceFirst(
            "^(\\d{2}):(\\d{2}):(\\d{2})$", "PT$1H$2M$3S");
    Duration dur = Duration.parse(iso);
    
    System.out.format("%-10s  Total %2d minutes or %4d seconds%n",
            dur, dur.toMinutes(), dur.toSeconds());

到目前为止的输出结果是:

PT1H30M41S  Total 90 minutes or 5441 seconds
Duration.parse 方法需要 ISO 8601 格式,例如 PT1H30M41S 表示 1 小时 30 分钟 41 秒。我通过正则表达式将您的字符串转换为这种格式。替换字符串中的 $1 等将被正则表达式中圆括号组中匹配的内容代替。 因此,durationString.replaceFirst() 将您的字符串转换为 PT01H30M41S,可以被 Duration 解析。 三种格式 您要求转换为 HH:MM:SS 或 MM:SS 或 SS 格式。修改上述方法非常简单:我们只需要调用三次 replaceFirst() 即可。其中一个调用会成功替换任何东西。其他两个将返回未更改的字符串。
    String[] durationStrings = { "01:32:43", "26:31", "14" };
    
    for (String durationString : durationStrings) {
        String iso = durationString.replaceFirst("^(\\d{2}):(\\d{2}):(\\d{2})$", "PT$1H$2M$3S")
                .replaceFirst("^(\\d{2}):(\\d{2})$", "PT$1M$2S")
                .replaceFirst("^(\\d{2})$", "PT$1S");
        Duration dur = Duration.parse(iso);
        
        System.out.format("%-10s  Total %2d minutes or %4d seconds%n",
                dur, dur.toMinutes(), dur.toSeconds());
    }
PT1H32M43S  Total 92 minutes or 5563 seconds
PT26M31S    Total 26 minutes or 1591 seconds
PT14S       Total  0 minutes or   14 seconds

Time4J和net.time4j.Duration

如果您可以接受外部依赖,那么Time4J库提供了一种更加优雅的方式来将字符串解析为持续时间对象。我们首先声明一个格式化程序:

private static final Duration.Formatter<ClockUnit> FORMATTER 
        = Duration.formatter(ClockUnit.class, "[[hh:]mm:]ss");

该格式字符串中的方括号用于包含可选部分,因此此格式化程序接受所有 hh:mm:ss、mm:ss 和仅 ss 的格式。
    for (String durationString : durationStrings) {
        Duration<ClockUnit> dur = FORMATTER.parse(durationString);

        long minutes = dur.with(ClockUnit.MINUTES.only())
                .getPartialAmount(ClockUnit.MINUTES);
        long seconds = dur.with(ClockUnit.SECONDS.only())
                .getPartialAmount(ClockUnit.SECONDS);
        
        System.out.format("%-10s  Total %2d minutes or %4d seconds%n",
                dur, minutes, seconds);
    }

输出与之前相同:

PT1H32M43S  Total 92 minutes or 5563 seconds
PT26M31S    Total 26 minutes or 1591 seconds
PT14S       Total  0 minutes or   14 seconds

链接


1
在过去的几天里,我看到你发布了Time4J解决方案以及java.time解决方案。这确实是一个很大的进步,因为Time4J也很受欢迎,特别是在Android开发者中间。 - Arvind Kumar Avinash

2
  1. 你的myCdDuration很令人困惑。你是想要一个单独的Duration对象,其等同于字符串中指定的内容,还是想要一个Duration对象的列表,其中第一个包含小时,第二个包含分钟等等?

  2. 你不能只是将一个String强制转换为其他对象。你应该将值解析成数值类型,并使用DataTypeFactory来构造Duration对象。


1:一个列表...元素0表示小时等,但仔细一看,只创建一个对象会更容易,因为持续时间类有像getHours()、getMinutes()和getSeconds()这样的方法。 2:谢谢,我会尝试的。 - Frank
感谢2. ...它正在工作,但在我的程序代码中看起来很糟糕,需要将string[]转换为int[]并使用switch/case语句创建newDuration,因为有许多不同的参数。 - Frank
你确定需要首先使用javax.xml.datatype.Duration类吗?正如其他人所说,它是为Java XML库设计的。看着你的另一个问题,如果你只想存储CD曲目的长度,我会使用普通的int并以秒为单位存储持续时间,就像Mansoor的答案一样(或者使用http://joda-time.sourceforge.net/库,如果我预计需要编写大量处理时间、日期和/或持续时间的代码)。 - andri
好的...谢谢大家。我创建了一个名为Duration的类,其中包含私有类变量intSongDuration和String printSongDuration。该类还具有一个方法public String intToStringDuration(intaDuration),它将int aDuraation转换为hh:mm:ss字符串。 - Frank

2
我建议不要使用javax.xml.datatype.Duration,因为它与XML Java API相关,如果您不处理XML,则使用它会很困惑。此外,它是一个抽象类,在Java SE中没有非抽象的已记录实现,所以您必须创建自己的非抽象实现或以某种方式获取实例(可能需要使用XML API)。
在Java中,您可以使用DateCalendar类来管理时间和日期。要将String转换为Date/Calendar,您可以使用DateFormatSimpleDateFormat。这将让您执行持续时间算术运算,但并不完全美观。
Mansoor提供了一种手动使用字符串操作和将持续时间处理为数值的方法- 如果您只做简单的事情,那么可能更直接。
如果您必须执行更复杂的操作,您可能需要查看http://joda-time.sourceforge.net/

如果这是作业的话,那么Frank可能别无选择,只能使用Duration。 - Costis Aivalis
@Frank 不不不!! 只使用 XML 的持续时间 - Michael

2
我已经在我的工具类中编写了这个方法,用于解析各种类型的持续时间字符串。它非常灵活:
public static int getSecondsFromFormattedDuration(String duration){
        if(duration==null)
            return 0;
        try{

            Pattern patternDuration = Pattern.compile("\\d+(?::\\d+){0,2}");

            int hours = 0;
            int minutes = 0;
            int seconds = 0;
            if(patternDuration.matcher(duration).matches()){
                String[] tokens = duration.split(":");
                if(tokens.length==1){
                    seconds = Integer.parseInt(tokens[0]);
                }else if(tokens.length == 2){
                    minutes = Integer.parseInt(tokens[0]);
                    seconds = Integer.parseInt(tokens[1]);
                }else{
                    hours = Integer.parseInt(tokens[0]);
                    minutes = Integer.parseInt(tokens[1]);
                    seconds = Integer.parseInt(tokens[2]);
                }

                return 3600 * hours + 60 * minutes + seconds;
            }else
                return 0;

        }catch (NumberFormatException ignored){
            return 0;
        }

}

这是它解析这些持续时间的方式:
"1"   -->  1
"10"  -->  10
"10:"   -->  0  (not a valid duration)
"10:07"   -->  607
"06:08"   -->  368
"7:22"   -->  442
":22"   -->  0  (not a valid duration)
"10:32:33"   -->  37953
"2:33:22"   -->  9202
"2:2:02"   -->  7322
"2:33:43:32"   -->  0  (not a valid duration)
"33ff"   -->  0  (not a valid duration)
"2d:33"   -->  0  (not a valid duration)

1

使用Java 8和Java.time.Duration,只要字符串格式为HH:MM:SS或MM:SS或SS,就可以完成此操作。

Duration.ofSeconds(Arrays.stream(runtime.split(":"))
                  .mapToInt(n -> Integer.parseInt(n))
                  .reduce(0, (n, m) -> n * 60 + m));

2
有趣的方法,但是像“777:888:999999”这样的值怎么办?它们可以使用此提议,但它们实际上符合“HH:MM:SS”的格式吗? - Software Craftsman
没有理由不工作,因为777小时,888分钟和999999秒的数据是有意义的。将前一个值乘以60并加上当前值。所以你最终得到的结果是(((0 * 60 + 777) * 60 + 888) * 60 + 999999),并将其转换为基于结果的持续时间。 如果每个字段的长度只有2的要求,则可以添加一个peek或map来处理检查。 - Asthor

0

示例,将当前日期时间转换为Java 7中的持续时间。

DatatypeFactory.newInstance().newDuration(Calendar.getInstance().getTimeInMillis())

输出 -

P48Y5M13DT19H59M24.658S


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