Como formatar uma duração em java? (por exemplo, formato H: MM: SS)

164

Eu gostaria de formatar uma duração em segundos usando um padrão como H: MM: SS. Os utilitários atuais em java foram projetados para formatar um horário, mas não uma duração.

naXa
fonte

Respostas:

84

Se você estiver usando uma versão do Java anterior a 8 ... poderá usar o Joda Time e PeriodFormatter. Se você realmente tem uma duração (isto é, uma quantidade de tempo decorrido, sem referência a um sistema de calendário), provavelmente deve estar usando Durationa maior parte do tempo - pode ligar toPeriod(especificando o PeriodTypeque deseja refletir se 25 horas se tornar 1 dia e 1 hora ou não, etc) para obter um Periodformato que você possa formatar.

Se você estiver usando o Java 8 ou posterior: normalmente sugiro usar java.time.Durationpara representar a duração. Em seguida, você pode ligar getSeconds()ou algo semelhante para obter um número inteiro para a formatação de sequência padrão, conforme a resposta de bobince, se necessário - embora você deva ter cuidado com a situação em que a duração é negativa, pois provavelmente deseja um único sinal negativo na sequência de saída . Então, 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;
}

Formatar dessa maneira é razoavelmente simples, embora irritantemente manual. Para analisar , torna-se um assunto mais difícil em geral ... Você ainda pode usar o Joda Time, mesmo com o Java 8, se quiser, é claro.

Jon Skeet
fonte
Você ainda recomendaria usar a biblioteca Joda Time ao usar o Java 8?
Tim Büthe
1
@ TimBüthe: Não, eu começaria a usar java.time nesse caso. (Embora eu não possa imediatamente encontrar um equivalente de PeriodFormatter ...)
Jon Skeet
1
PeriodFormatter não está incluído. Essa é uma das diferenças entre a API do Java 8 Date Time e a Joda Time.
Tim Büthe
1
@RexKerr: Bem, ainda há "use Joda Time" se você quiser, é claro. Editará novamente. (Olhando para trás, eu deveria ter sido recomendando a duração de qualquer maneira, pelos sons do que significativa editar necessário ....)
Jon Skeet
4
@MarkJeronimus ainda mais fácil seria usar os Durationmétodos s: duration.toHours(), duration.toMinutesPart()e duration.toSecondsPart()que estão disponíveis desde Java 9. E para obter o valor de um absoluto pode usar duration.abs().
recke96
195

Se você não deseja arrastar as bibliotecas, é simples o suficiente para fazer você mesmo usando um Formatador ou um atalho relacionado, por exemplo. dado número inteiro de segundos s:

  String.format("%d:%02d:%02d", s / 3600, (s % 3600) / 60, (s % 60));
bobince
fonte
4
Não precisa ser inteiro. Se você tiver duas datas, basta conectar date1.getTime () - date2.getTime () na equação acima, que usa a primitiva longa e ainda funciona.
285 Josh Josh
E se a diferença horária for superior a 24h?
Rajish
2
@ Rajish: você teria que perguntar ao OP o que eles queriam naquele caso! Claro que você pode separá-lo para fora s/86400, (s%86400)/3600...por dias, se necessário ...
bobince
Minha solução para s > 86400 (um dia): "\u221E"- infinito
QED
Se a diferença horária for superior a 24 horas, ele ainda formata a duração em horas, minutos e segundos. Para o meu caso de uso, é o esperado.
Audrius Meskauskas
122

Eu uso o DurationFormatUtils do Apache common assim:

DurationFormatUtils.formatDuration(millis, "**H:mm:ss**", true);
Gili Nachum
fonte
13
A maneira mais fácil possível (no estilo apache commons). Você ainda tem o método formatDurationHMS que provavelmente usará a maior parte do tempo.
Marko
Perfeito! Você também pode adicionar outro texto no apóstrofo: DurationFormatUtils.formatDuration (durationInMillis, "m 'minutos', s 'segundos'")
mihca 18/02
29

Isso é mais fácil desde o Java 9. A Durationainda não é formalizável, mas são adicionados métodos para obter horas, minutos e segundos, o que torna a tarefa um pouco mais direta:

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

A saída desse snippet é:

24:19:21

Ole VV
fonte
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())));
Mihai Vasilache
fonte
14
Hah! Apenas bata na parede com normalização de fuso horário quando usar esse método. Você precisa adicionar sdf.setTimeZone(TimeZone.getTimeZone("GMT+0"));antes de usar a formatfunção.
Rajish
7

Isso pode ser meio hacky, mas é uma boa solução se alguém se empenhar em fazer isso usando o 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();

}
patrick
fonte
Eu apenas tentei isso e percebi que, se eu chamasse o formato "hh: mm: ss" quando não tinha horas ou minutos para formatar (por exemplo Duration.ofSeconds(20)), obteria um UnsupportedTemporalTypeException). Eu simplesmente removi o código, verificando se a diferença é == 01. Achei que 01era algum tipo de valor mascarado que ainda não entendi, você pode explicar?
Groostav
Isso realmente funcionou muito bem. No meu caso, eu até adicionei milissegundos ... Quanto ao isSupportedmétodo está retornando as informações se houver um campo válido para mostrar na sequência legível por humanos. De qualquer forma, o "mascaramento" não é realmente uma máscara. O código return value!=0lnão mostra return value!=01. Por favor, use copiar e colar da próxima vez.
MrJames
1
A interface TemporalAccessornão deve ser mal utilizada por durações. O JSR-310 projetou a interface TemporalAmountpara esse fim.
Meno Hochschild
1
Eu recomendo que você sempre use letras maiúsculas Lpara significar um longvalor. É tão fácil interpretar em minúsculas o ldígito 1, como demonstrado.
VV Ole VV
6

Aqui está mais um exemplo de como formatar a duração. Observe que esta amostra mostra duração positiva e negativa como duração 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());
    }
}
Pavel_H
fonte
5

Esta resposta usa apenas Durationmétodos e funciona com 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,");
}
lauhub
fonte
4

Que tal a seguinte função, que retorna + H: MM: SS ou + 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 );
    }
}
mksteve
fonte
4

Há uma abordagem bastante simples e elegante (IMO), pelo menos por períodos inferiores a 24 horas:

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

Os formatadores precisam de um objeto temporal para formatar, para que você possa criar um adicionando a duração a um LocalTime de 00:00 (ou seja, meia-noite). Isso fornecerá um LocalTime representando a duração da meia-noite até a hora, que é fácil de formatar na notação HH: mm: ss padrão. Isso tem a vantagem de não precisar de uma biblioteca externa e usa a biblioteca java.time para fazer o cálculo, em vez de calcular manualmente as horas, minutos e segundos.

ctg
fonte
3

Esta é uma opção de trabalho.

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

Chame o método com

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

Produz algo como:

otherTime: 09:30
otherTime: 09:30:00
11:31:27.463
11:31:27.463
sbclint
fonte
2
Isso falhará por durações maiores que 23: 59: 59.999.
10136 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();
}
Bax
fonte
0

Minha biblioteca Time4J oferece uma solução baseada em padrões (semelhante a Apache DurationFormatUtils, mas mais flexível):

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

Esse código demonstra os recursos para lidar com excesso de horas e manipulação de sinais; consulte também a API do formatador de duração com base no padrão .

Meno Hochschild
fonte
0

Em scala (vi outras tentativas e não fiquei impressionado):

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

Parece horrível, sim? Bem, é por isso que usamos IDEs para escrever essas coisas, para que as chamadas de método ( $toHoursPartetc) sejam de cores diferentes.

O f"..."é um printf/String.format interpolador de cadeia de caracteres style (que é o que permite que a $injeção de código funcione) Dada uma saída de 1 14:06:32.583, a fcadeia de caracteres interpolada seria equivalente aString.format("1 %02d:%02d:%02d.%03d", 14, 6, 32, 583)

Stephen
fonte
0

usando esta função

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));
}
ipserc
fonte
Vá para github.com/ipserc/duration para obter uma versão empacotada dessa função
ipserc 05/07
-1

Na Scala, desenvolvendo a solução da YourBestBet, mas 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
dpoetzsch
fonte
-2

no scala, nenhuma biblioteca é necessária:

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("")
YourBestBet
fonte
3
Este não é um ótimo anúncio para Scala? Nenhuma biblioteca é necessária, mas o mesmo código ...?
Adam
Eu gosto da abordagem recursiva, mas pode ser muito simplificado: stackoverflow.com/a/52992235/3594403
dpoetzsch
-2

Não vi este, então pensei em adicioná-lo:

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

Só funciona por 24 horas, mas é o que eu normalmente quero por duração.

Ronald Wentworth
fonte