Jak sformatować czas trwania w Javie? (np. format H: MM: SS)

165

Chciałbym sformatować czas trwania w sekundach za pomocą wzorca takiego jak H: MM: SS. Obecne narzędzia w Javie są zaprojektowane do formatowania czasu, ale nie czasu trwania.

naXa
źródło

Odpowiedzi:

84

Jeśli używasz wersji Java starszej niż 8 ... możesz użyć Joda Time i PeriodFormatter. Jeśli naprawdę masz czas trwania (tj. Czas, który upłynął, bez odniesienia do systemu kalendarza), prawdopodobnie powinieneś używać Durationw większości - możesz wtedy zadzwonić toPeriod(określając cokolwiek PeriodTypechcesz, aby odzwierciedlić, czy 25 godzin stanie się 1 dzień i 1 godzinę lub nie itd.), Aby uzyskać plik, Periodktóry można sformatować.

Jeśli używasz Java 8 lub nowszej: zwykle sugeruję użycie java.time.Durationdo reprezentowania czasu trwania. Następnie możesz wywołać getSeconds()lub coś podobnego, aby uzyskać liczbę całkowitą do standardowego formatowania ciągu, zgodnie z odpowiedzią bobince, jeśli potrzebujesz - chociaż powinieneś uważać na sytuację, w której czas trwania jest ujemny, ponieważ prawdopodobnie chcesz mieć pojedynczy znak ujemny w ciągu wyjściowym . Więc coś takiego:

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

Formatowanie w ten sposób jest dość proste, choć irytująco ręczne. W przypadku analizowania jest to ogólnie trudniejsze ... Jeśli chcesz, nadal możesz używać Joda Time nawet z Javą 8, jeśli chcesz.

Jon Skeet
źródło
Czy nadal zalecamy korzystanie z biblioteki Joda Time podczas korzystania z języka Java 8?
Tim Büthe
1
@ TimBüthe: Nie, w takim przypadku zacząłbym używać java.time. (Chociaż nie mogę od razu znaleźć odpowiednika PeriodFormatter ...)
Jon Skeet
1
PeriodFormatter nie jest uwzględniony. To jedna z różnic między Java 8 Date Time API a Joda Time.
Tim Büthe
1
@RexKerr: Cóż, jeśli chcesz, nadal jest "użyj czasu Joda". Będzie edytować ponownie. (Patrząc wstecz, i tak powinienem był polecić Czas trwania, biorąc pod uwagę jego dźwięki. Wymagana znaczna edycja ...)
Jon Skeet
4
@MarkJeronimus jeszcze łatwiej byłoby użyć Durationmetody S: duration.toHours(), duration.toMinutesPart()i duration.toSecondsPart()które są dostępne od Java 9. I aby uzyskać jedną wartość bezwzględna może używać duration.abs().
recke96
195

Jeśli nie chcesz przeciągać bibliotek, wystarczy samemu zrobić to za pomocą programu Formatter lub powiązanego skrótu, np. podana całkowita liczba sekund s:

  String.format("%d:%02d:%02d", s / 3600, (s % 3600) / 60, (s % 60));
bobince
źródło
4
Nie musi być liczbą całkowitą. Jeśli masz dwie daty, po prostu podłącz date1.getTime () - date2.getTime () do powyższego równania, które używa długiego prymitywu i nadal działa.
Josh
A jeśli różnica czasu przekracza 24 godziny?
Rajish
2
@Rajish: musiałbyś zapytać OP, czego chcieli w takim przypadku! Oczywiście możesz to rozdzielić s/86400, (s%86400)/3600...na kilka dni, jeśli to konieczne ...
bobince
Moje rozwiązanie na s > 86400 (jeden dzień): "\u221E"- nieskończoność
QED
Jeśli różnica czasu przekracza 24 godziny, nadal ładnie formatuje czas trwania w godzinach, minutach i sekundach. W moim przypadku jest to zgodne z oczekiwaniami.
Audrius Meskauskas
123

Używam DurationFormatUtils Apache common w następujący sposób:

DurationFormatUtils.formatDuration(millis, "**H:mm:ss**", true);
Gili Nachum
źródło
13
Najłatwiejszy sposób (w stylu apache commons). Masz nawet metodę formatDurationHMS, której prawdopodobnie będziesz używać przez większość czasu.
Marko
Idealny! Możesz także dodać inny tekst w apostrofie: DurationFormatUtils.formatDuration (durationInMillis, "m 'minuty', s 'sekundy'")
mihca
29

Jest to łatwiejsze, ponieważ Java 9. A Durationnadal nie jest formatowalna, ale dodawane są metody pobierania godzin, minut i sekund, co czyni zadanie nieco prostszym:

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

Wynik tego fragmentu kodu to:

24:19:21

Ole VV
źródło
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
źródło
14
Ha! Po prostu uderz w ścianę z normalizacją strefy czasowej, gdy używasz tej metody. Musisz dodać sdf.setTimeZone(TimeZone.getTimeZone("GMT+0"));przed użyciem formatfunkcji.
Rajish,
7

To może być trochę hakerskie, ale jest dobrym rozwiązaniem, jeśli ktoś chce to osiągnąć za pomocą 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
źródło
Właśnie to wypróbowałem i zauważyłem, że jeśli wywołałem format „hh: mm: ss”, gdy nie miałem żadnych godzin ani minut na sformatowanie (np. Duration.ofSeconds(20)), Otrzymałem UnsupportedTemporalTypeException). Po prostu usunąłem kod, sprawdzając, czy różnica jest == 01. Pomyślałem, że jest 01to jakaś wartość maskująca, której nie do końca rozumiem, czy możesz to wyjaśnić?
Groostav
To faktycznie działało bardzo dobrze. W moim przypadku dodałem nawet milisekundy ... Jeśli chodzi o isSupportedmetodę, zwraca informacje, jeśli istnieje prawidłowe pole do wyświetlenia w ciągu czytelnym dla człowieka. W każdym razie „maskowanie” w rzeczywistości nie jest maską. Kod pokazuje, że return value!=0lnie return value!=01. Następnym razem skorzystaj z opcji kopiuj-wklej.
MrJames
1
Interfejs TemporalAccessornie powinien być nadużywany przez określony czas. JSR-310 zaprojektował interfejs TemporalAmountdo tego celu.
Meno Hochschild
1
Zalecam, aby zawsze używać wielkich liter Ldo oznaczania longwartości. Tak łatwo jest błędnie odczytać małe litery ldla cyfry 1, jak właśnie pokazano.
Ole VV
6

Oto jeszcze jeden przykład formatowania czasu trwania. Zauważ, że ta próbka pokazuje zarówno pozytywny, jak i negatywny czas trwania jako pozytywny czas trwania.

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
źródło
5

Ta odpowiedź używa tylko Durationmetod i działa z Javą 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
źródło
4

A co z następującą funkcją, która zwraca + H: MM: SS lub + 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
źródło
4

Istnieje dość proste i eleganckie (IMO) podejście, przynajmniej przez okres krótszy niż 24 godziny:

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

Elementy formatujące potrzebują obiektu czasowego do sformatowania, więc możesz go utworzyć, dodając czas trwania do LocalTime równego 00:00 (tj. Północ). To da ci LocalTime reprezentujący czas trwania od północy do tego czasu, który można następnie łatwo sformatować w standardowej notacji GG: mm: ss. Ma to tę zaletę, że nie wymaga zewnętrznej biblioteki i do obliczeń używa biblioteki java.time zamiast ręcznego obliczania godzin, minut i sekund.

ctg
źródło
3

To działa opcja.

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

Wywołaj metodę za pomocą

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

Produkuje coś takiego:

otherTime: 09:30
otherTime: 09:30:00
11:31:27.463
11:31:27.463
sbclint
źródło
2
To się nie powiedzie na czas trwania większy niż 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();
}
Bax
źródło
1

używając tej funkcji

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
źródło
Wejdź na github.com/ipserc/duration, aby pobrać spakowaną wersję tej funkcji
ipserc,
To był kod, którego szukałem, przyjęta odpowiedź jest świetna, ale brakuje mi dni, których potrzebowałem
Filnor
0

Moja biblioteka Time4J oferuje rozwiązanie oparte na wzorcach (podobne do Apache DurationFormatUtils, ale bardziej elastyczne):

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

Ten kod demonstruje możliwości obsługi przepełnienia godzin i obsługi znaków, zobacz także interfejs API programu formatującego czas trwania oparty na wzorcu .

Meno Hochschild
źródło
0

W scali (widziałem kilka innych prób i nie byłem pod wrażeniem):

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

Wygląda okropnie, tak? Cóż, dlatego używamy IDE do pisania tych rzeczy, aby wywołania metod ( $toHoursPartitp.) Miały inny kolor.

Jest f"..."to interpolator ciągów w stylu printf/ String.format(który umożliwia działanie $wstrzyknięcia kodu). Biorąc pod uwagę wynik 1 14:06:32.583, finterpolowany ciąg byłby równoważnyString.format("1 %02d:%02d:%02d.%03d", 14, 6, 32, 583)

Stephen
źródło
-1

W Scali, opierając się na rozwiązaniu YourBestBet, ale uproszczonym:

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
źródło
-2

w scali, biblioteka nie jest potrzebna:

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
źródło
3
To nie jest świetna reklama dla Scali? Żadna biblioteka nie jest potrzebna, ale tylko tyle kodu ...?
Adam
Podoba mi się podejście rekurencyjne, ale można je bardzo uprościć: stackoverflow.com/a/52992235/3594403
dpoetzsch
-2

Nie widziałem tego, więc pomyślałem, że go dodam:

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

Działa tylko przez 24 godziny, ale zwykle tego chcę.

Ronald Wentworth
źródło