Come formattare una durata in Java? (es. formato H: MM: SS)


165

Vorrei formattare una durata in secondi usando un modello come H: MM: SS. Le attuali utility in Java sono progettate per formattare un tempo ma non una durata.

Risposte:


84

Se stai usando una versione di Java precedente alla 8 ... puoi usare Joda Time e PeriodFormatter. Se hai davvero una durata (cioè un periodo di tempo trascorso, senza alcun riferimento a un sistema di calendario), probabilmente dovresti utilizzare Durationper la maggior parte - puoi quindi chiamare toPeriod(specificando qualsiasi cosa PeriodTypetu voglia riflettere se diventa 25 ore 1 giorno e 1 ora o meno, ecc.) Per ottenere un Periodformato che è possibile formattare.

Se stai usando Java 8 o successivo: normalmente suggerirei di usare java.time.Durationper rappresentare la durata. Puoi quindi chiamare getSeconds()o simili per ottenere un numero intero per la formattazione di stringhe standard secondo la risposta di bobince se necessario - anche se dovresti fare attenzione alla situazione in cui la durata è negativa, poiché probabilmente vuoi un singolo segno negativo nella stringa di output . Quindi qualcosa del tipo:

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

La formattazione in questo modo è ragionevolmente semplice, anche se fastidiosamente manuale. Per l' analisi diventa una cosa più difficile in generale ... Potresti comunque usare Joda Time anche con Java 8 se vuoi, ovviamente.


Consiglieresti comunque di usare la libreria Joda Time quando usi Java 8?
Tim Büthe

1
@ TimBüthe: No, inizierei a usare java.time in quel caso. (Anche se non riesco a trovare immediatamente un equivalente di PeriodFormatter ...)
Jon Skeet il

1
PeriodFormatter non è incluso. Quella delle differenze tra l'API Java 8 Date Time e Joda Time.
Tim Büthe

1
@RexKerr: Beh, c'è ancora "usa Joda Time" se vuoi, ovviamente. Modificherà di nuovo. (Guardando indietro, avrei dovuto consigliare Durata comunque, a quanto pare. È necessaria una modifica significativa ...)
Jon Skeet,

4
@MarkJeronimus Ancora più facile sarebbe quella di utilizzare i Durationmetodi s: duration.toHours(), duration.toMinutesPart()e duration.toSecondsPart()che sono disponibili dal Java 9. E per ottenere il valore assoluto può usare duration.abs().
recke96,

195

Se non vuoi trascinare le librerie, è abbastanza semplice fare te stesso usando un Formatter o un collegamento correlato, ad es. dato il numero intero di secondi s:

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

4
Non deve essere un numero intero. Se hai due date, basta inserire date1.getTime () - date2.getTime () nell'equazione sopra che usa la primitiva lunga e funziona ancora.
Josh,

Cosa succede se la differenza oraria è maggiore di 24 ore?
Rajish,

2
@Rajish: dovresti chiedere all'OP cosa volevano in quel caso! Ovviamente puoi separarlo s/86400, (s%86400)/3600...per giorni se necessario ...
Bobince,

La mia soluzione a s > 86400 (un giorno): "\u221E"- infinito
QED

Se la differenza oraria è superiore a 24 ore, formatta comunque bene la durata in ore, minuti e secondi. Per il mio caso d'uso questo è come previsto.
Audrius Meskauskas,

123

Uso DurationFormatUtils di Apache Common in questo modo:

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

13
Il modo più semplice possibile (in stile apache commons). Hai anche il metodo formatDurationHMS che probabilmente utilizzerai la maggior parte delle volte.
Marko,

Perfetto! Puoi anche aggiungere altro testo all'interno dell'apostrofo: DurationFormatUtils.formatDuration (durationInMillis, "m 'minuti", s' secondi "")
mihca,

29

Questo è più facile da Java 9. Un Durationmetodo non è ancora formattabile, ma vengono aggiunti metodi per ottenere ore, minuti e secondi, il che rende l'attività un po 'più semplice:

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

L'output di questo snippet è:

24:19:21


26
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
Hah! Basta colpire il muro con la normalizzazione del fuso orario quando utilizzato questo metodo. Devi aggiungere sdf.setTimeZone(TimeZone.getTimeZone("GMT+0"));prima di utilizzare la formatfunzione.
Rajish,

7

Questo potrebbe essere un po 'confuso, ma è una buona soluzione se si è intenzionati a farlo utilizzando 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();

}

Ho appena provato questo, e ho notato che se ho chiamato formato per "HH: MM: SS" quando ho didnt ho nessun ore o minuti in formato (ad esempio Duration.ofSeconds(20)), quindi mi piacerebbe avere una UnsupportedTemporalTypeException). Ho semplicemente rimosso il codice verificando se la differenza è == 01. Ho pensato che 01fosse una sorta di valore di mascheramento che non capisco completamente, puoi spiegarlo?
Groostav,

In realtà ha funzionato molto bene. Nel mio caso ho anche aggiunto millisecondi ... Per quanto riguarda il isSupportedmetodo, restituisce le informazioni se esiste un campo valido da mostrare sulla stringa leggibile dall'uomo. Comunque il "mascheramento" non è in realtà una maschera. Il codice mostra di return value!=0lno return value!=01. Per favore, usa copia-incolla la prossima volta.
MrJames,

1
L'interfaccia TemporalAccessornon dovrebbe essere utilizzata in modo improprio per periodi di tempo prolungati. JSR-310 ha progettato l'interfaccia TemporalAmountper questo scopo.
Meno Hochschild,

1
Ti consiglio di usare sempre lettere maiuscole Lper indicare un longvalore. È così facile interpretare male le lettere minuscole lper la cifra 1, come appena dimostrato.
Ole VV,

6

Ecco un altro esempio su come formattare la durata. Si noti che questo esempio mostra sia la durata positiva che negativa come durata positiva.

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

5

Questa risposta utilizza solo Durationmetodi e funziona con 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,");
}

4

Questa è un'opzione di lavoro.

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

Chiama il metodo con

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

Produce qualcosa di simile:

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

2
Ciò non funzionerà per durate superiori a 23: 59: 59.999.
Michel Jung,

4

Che ne dici della seguente funzione, che restituisce + H: MM: SS o + 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 );
    }
}

4

Esiste un approccio abbastanza semplice e (IMO) elegante, almeno per durate inferiori a 24 ore:

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

I formattatori hanno bisogno di un oggetto temporale per formattare, quindi puoi crearne uno aggiungendo la durata a un LocalTime di 00:00 (cioè mezzanotte). Questo ti darà un LocalTime che rappresenta la durata da mezzanotte a quel momento, che è quindi facile da formattare in notazione standard HH: mm: ss. Questo ha il vantaggio di non aver bisogno di una libreria esterna e usa la libreria java.time per fare il calcolo, piuttosto che calcolare manualmente ore, minuti e secondi.


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

1

usando questa funzione

private static String strDuration(long duration) {
    int ms, s, m, h, d;
    double dec;
    double time = duration * 1.0;

    time = (time / 1000.0);
    dec = time % 1;
    time = time - dec;
    ms = (int)(dec * 1000);

    time = (time / 60.0);
    dec = time % 1;
    time = time - dec;
    s = (int)(dec * 60);

    time = (time / 60.0);
    dec = time % 1;
    time = time - dec;
    m = (int)(dec * 60);

    time = (time / 24.0);
    dec = time % 1;
    time = time - dec;
    h = (int)(dec * 24);
    
    d = (int)time;
    
    return (String.format("%d d - %02d:%02d:%02d.%03d", d, h, m, s, ms));
}

Vai su github.com/ipserc/duration per ottenere una versione in pacchetto di questa funzione
ipserc

Questo era il codice che stavo cercando, la risposta accettata è ottima, ma manca i giorni di cui avevo bisogno
Filnor

0

La mia libreria Time4J offre una soluzione basata su pattern (simile a Apache DurationFormatUtils, ma più flessibile):

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

Questo codice dimostra le capacità per gestire l'overflow delle ore e la gestione dei segni, vedere anche l'API del formatter di durata basato sul modello .


0

In scala (ho visto altri tentativi e non sono rimasto colpito):

def formatDuration(duration: Duration): String = {
  import duration._ // get access to all the members ;)
  f"$toDaysPart $toHoursPart%02d:$toMinutesPart%02d:$toSecondsPart%02d:$toMillisPart%03d"
}

Sembra orribile sì? Ecco perché usiamo gli IDE per scrivere queste cose in modo che il metodo chiami ($toHoursPart ecc.) Abbiano un colore diverso.

Si f"..."tratta di un interpolatore di stringhe in stile printf/ String.format(che è ciò che consente il funzionamento $dell'iniezione di codice) Dato un output di 1 14:06:32.583, la fstringa interpolata sarebbe equivalente aString.format("1 %02d:%02d:%02d.%03d", 14, 6, 32, 583)


-1

In Scala, sviluppando la soluzione YourBestBet ma semplificando:

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

-2

in scala, nessuna libreria necessaria:

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

3
Questo non è un grande annuncio per Scala? Nessuna libreria necessaria, ma altrettanto codice ...?
Adam,

Mi piace l'approccio ricorsivo, ma può essere semplificato molto: stackoverflow.com/a/52992235/3594403
dpoetzsch

-2

Non l'ho visto, quindi ho pensato di aggiungerlo:

Date started=new Date();
SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
task
long duration=new Date().getTime()-started.getTime();
System.out.println(format.format(new Date(duration));

Funziona solo per 24 ore, ma è quello che di solito voglio per la durata.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.