23

i want to convert a string with a format of HH:MM:SS or MM:SS or SS into a datatype of Duration.

solution:

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

thanks for help ... any easier way to do that ? -> create your own Duration class:

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

Converts the int value into a String for output/print:

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;
}
Frank
  • 1,113
  • 3
  • 14
  • 27
  • 1
    You can't just cast a String as any another object as programming doesn't work that way. Perhaps you want to show us what the Duration class looks like. Perhaps it has a constructor that takes a Date object or that takes Strings or ints? – Hovercraft Full Of Eels Nov 24 '11 at 13:16
  • http://docs.oracle.com/javase/1.5.0/docs/api/javax/xml/datatype/Duration.html – Frank Nov 24 '11 at 13:19
  • 3
    Duration class for the use in XML...not to be used other wise... – Michael Nov 24 '11 at 13:53

8 Answers8

42

tl;dr

No need to define your own Duration class, as Java provides one.

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

And parse standard ISO 8601 formatted text.

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


Avoid HH:MM:SS format

If by the string 08:30:00 you mean "eight and a half hours" span of time rather than a time-of-day “half-past eight in the morning”, then avoid that format of HH:MM:SS. That format ambiguous, appearing to be a time-of-day. Instead use the standard ISO 8601 format discussed below.

Duration and time-of-day are two very different concepts. You must be clear on them, each should be distinct in your mind. Using the ambiguous format of HH:MM:SS makes that distinction all the more difficult (so avoid that format!).

java.time

The modern way is with the java.time classes.

LocalTime

First parse your string as a LocalTime. This class represents a time-of-day without a date and without a time zone. Having no time zone means these objects are based on a generic 24-hour clock without regard for anomalies such as Daylight Saving Time (DST).

We do not really want a LocalTime as your input string represents a span of time rather than a time-of-day. But this is just the first step.

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

Duration

To represent the desired span-of-time, we want the Duration class. This class is for spans of time not attached to the timeline. We can create one by converting that LocalTime via getting the amount of time from the beginning of the time-of-day clock, 00:00:00.0 or LocalTime.MIN, and the lt we just instantiated.

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

Editing the input string

The approach above using LocalTime only works if your input strings represent a duration of less than 24 hours. If over 24 hours, you will parse the input string yourself.

Something like the following code. Of course the actual parsing depends on resolving the ambiguity of your particular input string. Is 50:00 meant to be fifty hours or fifty minutes? (This ambiguity is a strong reason to avoid this confusing format whenever possible, and stick with ISO 8601 formats.)

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

We can see the result by generating a String in standard ISO 8601 format for durations by simply calling Duration::toString. The java.time classes use ISO 8601 by default when parsing/generating strings. For durations, the standard format is PnYnMnDTnHnMnS where the P marks the beginning and the T separates the years-months-days portion from the hours-minutes-seconds portion. So, our eight-and-a-half hours will appear as PT8H30M.

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

d.toString(): PT8H30M

Collecting Duration objects

You can make a List holding elements of the type 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]

Remember that the strings you see in that output like PT8H30M are just that: output of generated strings. The Duration type is not a simple string but rather generates a String object by its toString method.

If you stick to the ISO 8601 formats, you can easily parse as well as generate such strings. No need to go through the LocalTime conversion rigamarole we performed at the top of this Answer.

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

See this example code live in IdeOne.com.


About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

Where to obtain the java.time classes?

The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • Those using spring might also want to look at `DurationStyle::detectAndParse` which also allows for a more readable format like `30ms` in addition to ISO-8601. ([Reference](https://docs.spring.io/spring-boot/docs/2.0.4.RELEASE/reference/htmlsingle/#boot-features-external-config-conversion), sort of) – aksh1618 May 27 '20 at 21:44
20

I assume what you're ultimately trying to achieve is to compute the duration of the CD in seconds.

There are several ways to do this, but I think the most straightforward is to just split on : to get the hours, minutes, and seconds fields, then to compute the duration manually:

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;
Mansoor Siddiqui
  • 20,853
  • 10
  • 48
  • 67
  • Answering before we even know what Duration is is bound to get you the wrong answer. – Hovercraft Full Of Eels Nov 24 '11 at 13:23
  • 1
    Despite not knowing what Frank is trying to do with `javax.xml.datatype.Duration`, I thought his ultimate goal was pretty clear from the subject. But I do see your point, I could be wrong. :P I edited to my answer to state my assumptions. – Mansoor Siddiqui Nov 24 '11 at 13:29
  • So now what? He'll try to cast the int as a Duration? The key was to understand the Duration data type and then with it find the proper way to construct it -- here with a DataTypeFactory as shown by andri (1+ to him). – Hovercraft Full Of Eels Nov 24 '11 at 13:31
  • As alex pointed out, who knows whether the `Duration` class is even appropriate? There's no XML in or around Frank's code, so it might be useful to just look at the broader goal that he's trying to achieve. – Mansoor Siddiqui Nov 24 '11 at 13:58
  • @Hovercraft Full Of Eels He need to parse amount of time from the string (or get inside of your hovercraft to go to the loony bin!! ;) – Michael Nov 24 '11 at 14:26
  • @Michael: I'm already there! But yeah, I agree, and that's why I removed my down-vote on Mansoor's answer. I think the main problem here is that it was a poorly written question. – Hovercraft Full Of Eels Nov 24 '11 at 14:30
4

java.time.Duration

A couple of other answers have already mentioned that the Duration class from java.time, the modern Java date and time API, is the class to use for a duration. There are some ways to parse your strings into a Duration, and it’s honestly not clear which one is the best. I’d like to present my way.

Basic

I start simple with just one possible format, hh:mm:ss, for example 01:30:41 for 1 hour 30 minutes 41 seconds.

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

Output so far is:

PT1H30M41S  Total 90 minutes or 5441 seconds

The Duration.parse method requires ISO 8601 format. It goes like PT1H30M41S for 1 hour 30 minutes 41 seconds. So what I do is I convert your string into this format through a regular expression. The $1, etc., in my replacement string will be substituted by what was matched by the groups in round brackets in the regular expression. So durationString.replaceFirst() converts your string to PT01H30M41S, which Duration can parse.

Three formats

You asked for conversion of HH:MM:SS or MM:SS or SS. The modification to the above is actually quite simple: we just need three calls to replaceFirst() instead of one. Exactly one of them will succeed in replacing anything. The other two will just return the string unchanged.

    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 and net.time4j.Duration

In case you’re fine with an external dependency, the Time4J library offers a much more elegant way of parsing your strings to duration objects. We first declare a formatter:

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

The square brackets in the format pattern string surround optional parts, so this formatter accepts all of hh:mm:ss, mm:ss and just 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);
    }

Output is the same as before:

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

Links

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
  • 1
    In the last few days, I have seen you posting Time4J solution along with `java.time` solution. This is indeed a great step forward because Time4J is also popular especially among Android developers. – Arvind Kumar Avinash Aug 05 '21 at 17:32
2

I have written this method in my utils class to parse various kind of duration strings. It is quite flexible :

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

}

This is how it parsed these durations :

"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)
Rahul Verma
  • 2,140
  • 3
  • 21
  • 31
2
  1. Your myCdDuration is confusing. Do you want one Duration object equivalent to whatever was specified in the string, or a list of Duration objects where the first contains the hours, the second minutes etc?

  2. You can't just cast a String into some other object. You should parse the value into an numeric type and use DataTypeFactory to construct the Duration object.

andri
  • 11,171
  • 2
  • 38
  • 49
  • to 1: a list...element 0 for hours etc.. but on a second look it will be easier to create only 1 object because the duration class got methods like getHouts(), getMinutes() and getSeconds() to 2: thanks..ill try it – Frank Nov 24 '11 at 13:27
  • thanks for 2. ...its workin but looks awful in my program code, converting from string[] to int[] and creating the newDuration with a switch/case statement because of all those different parameters. – Frank Nov 24 '11 at 14:18
  • Are you *sure* you need to use the `javax.xml.datatype.Duration` class in the first place? As others have stated, it's designed to be used with the Java XML library. Looking at your other question, if you want to just store the length of a CD track, I'd use a plain old `int` and store the duration in seconds, much like Mansoor's answer (or use the http://joda-time.sourceforge.net/ library, if I expect that I'll need to write a lot of code dealing with times, dates and/or durations). – andri Nov 24 '11 at 14:31
  • well...thanks everyone. i created a class Duration with private classvariable int intSongDuration and String printSongDuration. that class also has a method public String intToStringDuration(intaDuration) which converts the int aDuraation into a hh:mm:ss String. – Frank Nov 25 '11 at 14:55
2

I would suggest not using javax.xml.datatype.Duration, as its related to the XML Java API and it's confusing to use it if you are not dealing with XML. Moreover, it is an abstract class, and there's no non-abstract documented implementation of it in Java SE, so you'd have to either create your own non-abstract implementation or obtain an instance somehow (probably, playing with the XML API).

You manage time and dates in Java using the Date and Calendar classes. To convert Strings to Date/Calendar you use DateFormat or SimpleDateFormat. That will let you perform your duration arithmetic, although that's not 100% pretty.

Mansoor provides a way to do stuff manually using String manipulation and handling durations as numeric values- if you only do simple stuff, it might be more straightforward to do that.

If you have to perform more complex stuff, you might want to look into http://joda-time.sourceforge.net/

alex
  • 5,213
  • 1
  • 24
  • 33
1

With Java 8 and Java.time.Duration you can do this given that the string is of the format HH:MM:SS or MM:SS or SS

Duration.ofSeconds(Arrays.stream(runtime.split(":"))
                  .mapToInt(n -> Integer.parseInt(n))
                  .reduce(0, (n, m) -> n * 60 + m));
Asthor
  • 598
  • 4
  • 17
  • 2
    Interesting approach, but what about values like "777:888:999999"? They would work with this proposal, but do they actually conform to the format "HH:MM:SS"? – Software Craftsman Jan 12 '21 at 11:49
  • No reason why it shouldn't work, given that the data of 777 hours, 888 minutes and 999999 seconds makes sense. Multiples the previous value with 60 and adds the current values. So you'd end up with the result of (((0 * 60 + 777) * 60 + 888) * 60 + 999999) and makes that into a duration based on the result. If there is a requirement on that the length per field is only 2, you'd could potentially add a peek or map that handles the checkl. – Asthor Jan 12 '21 at 14:06
0

Sample, Convert Current DateTime to Duration in Java 7.

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

Output -

P48Y5M13DT19H59M24.658S

Ajay Kumar
  • 4,864
  • 1
  • 41
  • 44