-->

How to format a duration in java? (e.g format H:MM

2018-12-31 14:45发布

问题:

I\'d like to format a duration in seconds using a pattern like H:MM:SS. The current utilities in java are designed to format a time but not a duration.

回答1:

If you\'re using a version of Java prior to 8... you can use Joda Time and PeriodFormatter. If you\'ve really got a duration (i.e. an elapsed amount of time, with no reference to a calendar system) then you should probably be using Duration for the most part - you can then call toPeriod (specifying whatever PeriodType you want to reflect whether 25 hours becomes 1 day and 1 hour or not, etc) to get a Period which you can format.

If you\'re using Java 8 or later: I\'d normally suggest using java.time.Duration to represent the duration. You can then call getSeconds() or the like to obtain an integer for standard string formatting as per bobince\'s answer if you need to - although you should be careful of the situation where the duration is negative, as you probably want a single negative sign in the output string. So something like:

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

Formatting this way is reasonably simple, if annoyingly manual. For parsing it becomes a harder matter in general... You could still use Joda Time even with Java 8 if you want to, of course.



回答2:

If you don\'t want to drag in libraries, it\'s simple enough to do yourself using a Formatter, or related shortcut eg. given integer number of seconds s:

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


回答3:

I use Apache common\'s DurationFormatUtils like so:

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


回答4:

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


回答5:

This will be easier with Java 9. A Duration still isn’t formattable (what I know of), but methods for getting the hours, minutes and seconds are added, which makes the task somewhat more straightforward:

        Duration diff = // ...;
        String hms = String.format(\"%d:%02d:%02d\", 
                                   diff.toHoursPart(),
                                   diff.toMinutesPart(), 
                                   diff.toSecondsPart());


回答6:

This is going to be one of the new features in java 7

JSR-310



回答7:

Here is one more sample how to format duration. Note that this sample shows both positive and negative duration as positive duration.

import static java.time.temporal.ChronoUnit.DAYS;
import static java.time.temporal.ChronoUnit.HOURS;
import static java.time.temporal.ChronoUnit.MINUTES;
import static java.time.temporal.ChronoUnit.SECONDS;

import java.time.Duration;

public class DurationSample {
    public static void main(String[] args) {
        //Let\'s say duration of 2days 3hours 12minutes and 46seconds
        Duration d = Duration.ZERO.plus(2, DAYS).plus(3, HOURS).plus(12, MINUTES).plus(46, SECONDS);

        //in case of negative duration
        if(d.isNegative()) d = d.negated();

        //format DAYS HOURS MINUTES SECONDS 
        System.out.printf(\"Total duration is %sdays %shrs %smin %ssec.\\n\", d.toDays(), d.toHours() % 24, d.toMinutes() % 60, d.getSeconds() % 60);

        //or format HOURS MINUTES SECONDS 
        System.out.printf(\"Or total duration is %shrs %smin %sec.\\n\", d.toHours(), d.toMinutes() % 60, d.getSeconds() % 60);

        //or format MINUTES SECONDS 
        System.out.printf(\"Or total duration is %smin %ssec.\\n\", d.toMinutes(), d.getSeconds() % 60);

        //or format SECONDS only 
        System.out.printf(\"Or total duration is %ssec.\\n\", d.getSeconds());
    }
}


回答8:

This might be kind of hacky, but it is a good solution if one is bent on accomplishing this using Java 8\'s 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();

}


回答9:

This is a working option.

public static String showDuration(LocalTime otherTime){          
    DateTimeFormatter df = DateTimeFormatter.ISO_LOCAL_TIME;
    LocalTime now = LocalTime.now();
    System.out.println(\"now: \" + now);
    System.out.println(\"otherTime: \" + otherTime);
    System.out.println(\"otherTime: \" + otherTime.format(df));

    Duration span = Duration.between(otherTime, now);
    LocalTime fTime = LocalTime.ofNanoOfDay(span.toNanos());
    String output = fTime.format(df);

    System.out.println(output);
    return output;
}

Call the method with

System.out.println(showDuration(LocalTime.of(9, 30, 0, 0)));

Produces something like:

otherTime: 09:30
otherTime: 09:30:00
11:31:27.463
11:31:27.463


回答10:

How about the following function, which returns either +H:MM:SS or +H:MM:SS.sss

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


回答11:

String duration(Temporal from, Temporal to) {
    final StringBuilder builder = new StringBuilder();
    for (ChronoUnit unit : new ChronoUnit[]{YEARS, MONTHS, WEEKS, DAYS, HOURS, MINUTES, SECONDS}) {
        long amount = unit.between(from, to);
        if (amount == 0) {
            continue;
        }
        builder.append(\' \')
                .append(amount)
                .append(\' \')
                .append(unit.name().toLowerCase());
        from = from.plus(amount, unit);
    }
    return builder.toString().trim();
}


回答12:

My library Time4J offers a pattern-based solution (similar to Apache DurationFormatUtils, but more flexible):

Duration<ClockUnit> duration =
    Duration.of(-573421, ClockUnit.SECONDS) // input in seconds only
    .with(Duration.STD_CLOCK_PERIOD); // performs normalization to h:mm:ss-structure
String fs = Duration.formatter(ClockUnit.class, \"+##h:mm:ss\").format(duration);
System.out.println(fs); // output => -159:17:01

This code demonstrates the capabilities to handle hour overflow and sign handling, see also the API of duration-formatter based on pattern.



回答13:

in scala, no library needed:

def prettyDuration(str:List[String],seconds:Long):List[String]={
  seconds match {
    case t if t < 60 => str:::List(s\"${t} seconds\")
    case t if (t >= 60 && t< 3600 ) => List(s\"${t / 60} minutes\"):::prettyDuration(str, t%60)
    case t if (t >= 3600 && t< 3600*24 ) => List(s\"${t / 3600} hours\"):::prettyDuration(str, t%3600)
    case t if (t>= 3600*24 ) => List(s\"${t / (3600*24)} days\"):::prettyDuration(str, t%(3600*24))
  }
}
val dur = prettyDuration(List.empty[String], 12345).mkString(\"\")


回答14:

This answer only uses Duration methods and works with 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+\" jours,\")+ 
            (hours == 0?\"\":hours+\" heures,\")+ 
            (minutes ==  0?\"\":minutes+\" minutes,\")+ 
            (seconds == 0?\"\":seconds+\" secondes,\");
}


回答15:

In Scala, building up on YourBestBet\'s solution but simplified:

def prettyDuration(seconds: Long): List[String] = seconds match {
  case t if t < 60      => List(s\"${t} seconds\")
  case t if t < 3600    => s\"${t / 60} minutes\" :: prettyDuration(t % 60)
  case t if t < 3600*24 => s\"${t / 3600} hours\" :: prettyDuration(t % 3600)
  case t                => s\"${t / (3600*24)} days\" :: prettyDuration(t % (3600*24))
}

val dur = prettyDuration(12345).mkString(\", \") // => 3 hours, 25 minutes, 45 seconds