¿Cómo formatear una duración en java? (por ejemplo, formato H: MM: SS)


164

Me gustaría formatear una duración en segundos usando un patrón como H: MM: SS. Las utilidades actuales en Java están diseñadas para formatear una hora pero no una duración.

Respuestas:


84

Si está utilizando una versión de Java anterior a la 8 ... puede usar Joda Time y PeriodFormatter. Si realmente tiene una duración (es decir, una cantidad de tiempo transcurrida, sin referencia a un sistema de calendario), entonces probablemente debería estar utilizando Durationla mayor parte; luego puede llamar toPeriod(especificando lo PeriodTypeque desee para reflejar si se convierten en 25 horas 1 día y 1 hora o no, etc.) para obtener uno Periodque puede formatear.

Si está utilizando Java 8 o posterior: normalmente sugeriría usar java.time.Durationpara representar la duración. Luego puede llamar getSeconds()o similar para obtener un número entero para el formato de cadena estándar según la respuesta de bobince si lo necesita, aunque debe tener cuidado con la situación donde la duración es negativa, ya que probablemente desee un solo signo negativo en la cadena de salida . Entonces algo como:

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

Formatear de esta manera es razonablemente simple, aunque molestamente manual. Para analizar, se convierte en un asunto más difícil en general ... Todavía podría usar Joda Time incluso con Java 8 si lo desea, por supuesto.


¿Seguiría recomendando usar la biblioteca Joda Time cuando use Java 8?
Tim Büthe

1
@ TimBüthe: No, comenzaría a usar java.time en ese caso. (Aunque no puedo encontrar inmediatamente un equivalente de PeriodFormatter ...)
Jon Skeet

1
PeriodFormatter no está incluido. Esa es una de las diferencias entre Java 8 Date Time API y Joda Time.
Tim Büthe

1
@RexKerr: Bueno, todavía hay "usar Joda Time" si quieres, por supuesto. Se editará de nuevo. (Mirando hacia atrás, debería haber estado recomendando Duración de todos modos, por lo que parece. Se requiere una edición significativa ...)
Jon Skeet

44
@MarkJeronimus aún más fácil sería utilizar los Durationmétodos s: duration.toHours(), duration.toMinutesPart()y duration.toSecondsPart()los que se dispone ya de Java 9. Y para obtener el valor absoluto uno puede utilizar duration.abs().
recke96

195

Si no desea arrastrar en las bibliotecas, es lo suficientemente simple como hacerlo usando un formateador o un atajo relacionado, por ejemplo. número entero de segundos dado s:

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

44
No tiene que ser entero. Si tiene dos fechas, simplemente conecte date1.getTime () - date2.getTime () en la ecuación anterior que usa la primitiva larga y aún funciona.
Josh

¿Qué pasa si la diferencia horaria es mayor a 24h?
Rajish

2
@Rajish: ¡tendrías que preguntarle al OP qué querían en ese caso! Por supuesto, puede separarlo s/86400, (s%86400)/3600...por días si es necesario ...
bobince

Mi solución a s > 86400 (un día): "\u221E"- infinito
QED

Si la diferencia horaria es superior a 24 horas, aún formatea bien la duración en horas, minutos y segundos. Para mi caso de uso, esto es como se esperaba.
Audrius Meskauskas

122

Uso el DurationFormatUtils de Apache common así:

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

13
La forma más fácil posible (en estilo apache commons). Incluso tiene el método formatDurationHMS que probablemente usará la mayor parte del tiempo.
Marko

¡Perfecto! También puede agregar otro texto dentro del apóstrofe: DurationFormatUtils.formatDuration (DurationInMillis, "m 'minutos', s 'segundos'")
mihca

29

Esto es más fácil desde Java 9. A Durationtodavía no es formateable, pero se agregan métodos para obtener las horas, minutos y segundos, lo que hace que la tarea sea un poco más sencilla:

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

El resultado de este fragmento es:

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
Ja! Simplemente golpee la pared con la normalización de la zona horaria cuando utilice este método. Tiene que agregar sdf.setTimeZone(TimeZone.getTimeZone("GMT+0"));antes de usar la formatfunción.
Rajish

7

Esto puede ser algo hacky, pero es una buena solución si uno está decidido a lograr esto usando 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();

}

Acabo de intentar esto, y me di cuenta de que si me llama formato para "hh: mm: ss" cuando aún no ha tengo horas o minutos a formato (por ejemplo Duration.ofSeconds(20)), entonces me gustaría obtener una UnsupportedTemporalTypeException). Simplemente eliminé el código para verificar si la diferencia es == 01. Supuse que 01era algún tipo de valor de enmascaramiento que no entiendo completamente, ¿puede explicarlo?
Groostav

Esto realmente funcionó muy bien. En mi caso, incluso agregué milisegundos ... Con respecto al isSupportedmétodo, devuelve la información si hay un campo válido para mostrar en la cadena legible por humanos. De todos modos, el "enmascaramiento" en realidad no es una máscara. El código muestra que return value!=0lno return value!=01. Haga uso de copiar y pegar la próxima vez.
MrJames

1
No TemporalAccessorse supone que la interfaz se use de forma indebida durante mucho tiempo. JSR-310 ha diseñado la interfaz TemporalAmountpara este propósito.
Meno Hochschild

1
Te recomiendo que siempre uses mayúsculas Lpara indicar un longvalor. Es muy fácil leer mal las minúsculas lpara el dígito 1, como se acaba de demostrar.
Ole VV

6

Aquí hay una muestra más de cómo formatear la duración. Tenga en cuenta que esta muestra muestra la duración positiva y negativa como duración 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

Esta respuesta solo usa Durationmétodos y funciona 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

¿Qué tal la siguiente función, que devuelve + 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

Hay un enfoque elegante bastante simple (IMO), al menos para duraciones de menos de 24 horas:

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

Los formateadores necesitan un objeto temporal para formatear, por lo que puede crear uno agregando la duración a un LocalTime de 00:00 (es decir, medianoche). Esto le dará un LocalTime que representa la duración desde la medianoche hasta ese momento, que luego es fácil de formatear en notación estándar HH: mm: ss. Esto tiene la ventaja de no necesitar una biblioteca externa, y utiliza la biblioteca java.time para hacer el cálculo, en lugar de calcular manualmente las horas, minutos y segundos.


3

Esta es una opción de trabajo.

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

Llame al método con

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

Produce algo como:

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

2
Esto fallará por duraciones mayores a 23: 59: 59.999.
Michel Jung

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

0

Mi biblioteca Time4J ofrece una solución basada en patrones (similar Apache DurationFormatUtils, pero más 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

Este código demuestra las capacidades para manejar el desbordamiento de horas y el manejo de signos, consulte también la API del formateador de duración según el patrón .


0

En scala (vi algunos otros intentos, y no me impresionó):

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

Se ve horrible si? Bueno, por eso usamos IDEs para escribir estas cosas para que el método llame a ($toHoursPart etc.) sean de un color diferente.

El f"..."es un interpolador de cadena printf/ String.formatestilo (que es lo que permite $que funcione la inyección de código) Dada una salida de 1 14:06:32.583, la fcadena interpolada sería equivalente aString.format("1 %02d:%02d:%02d.%03d", 14, 6, 32, 583)


0

usando esta función

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

Vaya a github.com/ipserc/duration para obtener una versión empaquetada de esta función
ipserc

-1

En Scala, construir sobre la solución de YourBestBet pero simplificado:

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

en scala, no se necesita biblioteca:

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
Este no es un gran anuncio para Scala? No se necesita biblioteca, pero ¿tanto código ...?
Adam

Me gusta el enfoque recursivo, pero se puede simplificar mucho: stackoverflow.com/a/52992235/3594403
dpoetzsch

-2

No vi este, así que pensé en agregarlo:

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

Solo funciona durante 24 horas, pero eso es lo que generalmente quiero para la duración.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.