Bagaimana memformat durasi dalam java? (mis. format H: MM: SS)

164

Saya ingin memformat durasi dalam hitungan detik menggunakan pola seperti H: MM: SS. Utilitas saat ini di java dirancang untuk memformat waktu tetapi bukan durasi.

naXa
sumber

Jawaban:

84

Jika Anda menggunakan versi Java sebelum 8 ... Anda dapat menggunakan Joda Time dan PeriodFormatter. Jika Anda benar-benar memiliki durasi (yaitu jumlah waktu yang telah berlalu, tanpa referensi ke sistem kalender) maka Anda mungkin harus menggunakan Durationsebagian besar - Anda kemudian dapat menelepon toPeriod(menentukan apa pun yang PeriodTypeAnda ingin mencerminkan apakah 25 jam menjadi 1 hari dan 1 jam atau tidak, dll) untuk mendapatkan Periodyang dapat Anda format.

Jika Anda menggunakan Java 8 atau lebih baru: Saya biasanya menyarankan menggunakan java.time.Durationuntuk mewakili durasi. Anda kemudian dapat memanggil getSeconds()atau sejenisnya untuk mendapatkan bilangan bulat untuk pemformatan string standar sesuai jawaban bobince jika Anda perlu - meskipun Anda harus berhati-hati dengan situasi di mana durasinya negatif, karena Anda mungkin ingin satu tanda negatif pada string output . Jadi sesuatu seperti:

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

Memformat cara ini cukup sederhana, jika mengganggu manual. Untuk parsing itu menjadi masalah yang lebih sulit secara umum ... Anda masih bisa menggunakan Joda Time bahkan dengan Java 8 jika Anda mau, tentu saja.

Jon Skeet
sumber
Apakah Anda masih merekomendasikan untuk menggunakan perpustakaan Joda Time saat menggunakan Java 8?
Tim Büthe
1
@ TimBüthe: Tidak, saya akan mulai menggunakan java.time dalam kasus itu. (Meskipun saya tidak dapat langsung menemukan yang setara dengan PeriodFormatter ...)
Jon Skeet
1
PeriodFormatter tidak termasuk. Itulah salah satu perbedaan antara Java Date Date API 8 dan Joda Time.
Tim Büthe
1
@RexKerr: Ya, masih ada "gunakan Joda Time" jika Anda mau, tentu saja. Akan diedit lagi. (Melihat ke belakang, saya seharusnya merekomendasikan Duration, oleh suara itu. Diperlukan suntingan yang signifikan ...)
Jon Skeet
4
@MarkJeronimus Bahkan akan lebih mudah untuk menggunakan Durationmetode s: duration.toHours(), duration.toMinutesPart()dan duration.toSecondsPart()yang tersedia sejak Java 9. Dan untuk mendapatkan nilai absolut dapat menggunakan duration.abs().
ingat kembali
195

Jika Anda tidak ingin menyeret pustaka, cukup mudah untuk melakukannya sendiri menggunakan Formatter, atau pintasan terkait misalnya. diberikan jumlah integer detik s:

  String.format("%d:%02d:%02d", s / 3600, (s % 3600) / 60, (s % 60));
bobince
sumber
4
Tidak harus bilangan bulat. Jika Anda memiliki dua tanggal, cukup colok date1.getTime () - date2.getTime () ke dalam persamaan di atas yang menggunakan primitif panjang dan masih berfungsi.
Josh
Bagaimana jika perbedaan waktu lebih lama dari 24 jam?
Rajish
2
@ Rajish: Anda harus bertanya kepada OP apa yang mereka inginkan dalam kasus itu! Tentu saja Anda dapat memisahkannya menjadi s/86400, (s%86400)/3600...berhari-hari jika perlu ...
bobince
Solusi saya untuk s > 86400 (satu hari): "\u221E"- infinity
QED
Jika perbedaan waktu lebih dari 24 jam, itu masih memformat durasi dengan baik dalam jam, menit dan detik. Untuk kasus penggunaan saya ini seperti yang diharapkan.
Audrius Meskauskas
122

Saya menggunakan DurationFormatUtils dari Apache common seperti:

DurationFormatUtils.formatDuration(millis, "**H:mm:ss**", true);
Gili Nachum
sumber
13
Cara termudah yang mungkin (dalam gaya apache commons). Anda bahkan memiliki metode formatDurationHMS itu, Anda mungkin akan menggunakan sebagian besar waktu.
Marko
Sempurna! Anda juga dapat menambahkan teks lain dalam apostrof: DurationFormatUtils.formatDuration (durasiInMillis, "m 'menit', s 'detik'")
mihca
29

Ini lebih mudah sejak Java 9. Yang Durationmasih tidak dapat diformat, tetapi metode untuk mendapatkan jam, menit dan detik ditambahkan, yang membuat tugas agak lebih mudah:

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

Output dari snippet ini adalah:

24:19:21

Ole VV
sumber
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
sumber
14
Hah! Cukup pukul dinding dengan normalisasi zona waktu saat menggunakan metode ini. Anda harus menambahkan sdf.setTimeZone(TimeZone.getTimeZone("GMT+0"));sebelum menggunakan formatfungsi.
Rajish
7

Ini mungkin semacam peretasan, tetapi ini adalah solusi yang bagus jika seseorang bertekad untuk mencapai ini menggunakan 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();

}
patrick
sumber
Saya baru saja mencoba ini, dan saya perhatikan bahwa jika saya memanggil format untuk "hh: mm: ss" ketika saya tidak punya jam atau menit untuk memformat (misalnya Duration.ofSeconds(20)), maka saya akan mendapatkan UnsupportedTemporalTypeException). Saya hanya menghapus kode memeriksa apakah perbedaannya == 01. Saya pikir 01itu semacam nilai masking yang saya tidak sepenuhnya mengerti, bisakah Anda menjelaskannya?
Groostav
Ini sebenarnya bekerja dengan sangat baik. Pada kasus saya, saya bahkan menambahkan milidetik ... Mengenai isSupportedmetode ini mengembalikan informasi jika ada bidang yang valid untuk ditampilkan pada string yang dapat dibaca manusia. Pokoknya "masking" sebenarnya bukan topeng. Kode return value!=0ltidak menunjukkan return value!=01. Silakan gunakan copy-paste lain kali.
MrJames
1
Antarmuka TemporalAccessortidak seharusnya disalahgunakan untuk jangka waktu. JSR-310 telah mendesain antarmuka TemporalAmountuntuk tujuan ini.
Meno Hochschild
1
Saya sarankan Anda selalu menggunakan huruf besar Luntuk menandakan longnilai. Sangat mudah untuk salah membaca huruf kecil luntuk digit 1, seperti yang baru saja ditunjukkan.
Ole VV
6

Berikut adalah satu lagi contoh cara memformat durasi. Perhatikan bahwa sampel ini menunjukkan durasi positif dan negatif sebagai durasi positif.

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
sumber
5

Jawaban ini hanya menggunakan Durationmetode dan berfungsi dengan 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
sumber
4

Bagaimana dengan fungsi berikut, yang mengembalikan + H: MM: SS atau + 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
sumber
4

Ada pendekatan yang cukup sederhana dan elegan (IMO), setidaknya untuk jangka waktu kurang dari 24 jam:

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

Pemformat memerlukan objek temporal untuk diformat, sehingga Anda dapat membuatnya dengan menambahkan durasi ke LocalTime 00:00 (yaitu tengah malam). Ini akan memberi Anda LocalTime yang menunjukkan durasi dari tengah malam hingga waktu itu, yang kemudian mudah diformat dalam notasi HH: mm: ss standar. Ini memiliki keuntungan karena tidak perlu perpustakaan eksternal, dan menggunakan perpustakaan java.time untuk melakukan perhitungan, daripada secara manual menghitung jam, menit dan detik.

ctg
sumber
3

Ini adalah opsi yang berfungsi.

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

Panggil metode dengan

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

Menghasilkan sesuatu seperti:

otherTime: 09:30
otherTime: 09:30:00
11:31:27.463
11:31:27.463
sbclint
sumber
2
Ini akan gagal untuk durasi yang lebih besar dari 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
sumber
0

Perpustakaan saya Time4J menawarkan solusi berbasis pola (mirip dengan Apache DurationFormatUtils, tetapi lebih fleksibel):

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

Kode ini menunjukkan kemampuan untuk menangani overflow jam dan menangani tanda tangan, lihat juga API formatter berdasarkan durasi .

Meno Hochschild
sumber
0

Dalam scala (saya melihat beberapa upaya lain, dan tidak terkesan):

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

Terlihat mengerikan ya? Nah itu sebabnya kami menggunakan IDE untuk menulis hal ini sehingga metode ini memanggil ($toHoursPart dll) berbeda warna.

The f"..."adalah printf/ String.formatinterpolator gaya tali (yang adalah apa yang memungkinkan $kode injeksi bekerja) Mengingat output 1 14:06:32.583, yang ftali interpolasi akan setara denganString.format("1 %02d:%02d:%02d.%03d", 14, 6, 32, 583)

Stephen
sumber
0

menggunakan fungsi ini

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
sumber
Pergi ke github.com/ipserc/duration untuk mendapatkan versi paket dari fungsi ini
ipserc
-1

Di Scala, membangun solusi YourBestBet tetapi disederhanakan:

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
sumber
-2

dalam scala, tidak perlu perpustakaan:

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
sumber
3
Ini bukan iklan yang bagus untuk Scala? Tidak perlu perpustakaan, tetapi hanya kode sebanyak ...?
Adam
Saya suka pendekatan rekursif, tetapi bisa disederhanakan banyak: stackoverflow.com/a/52992235/3594403
dpoetzsch
-2

Saya tidak melihat yang ini jadi saya pikir saya akan menambahkannya:

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

Ini hanya bekerja selama 24 jam tapi itu yang biasanya saya inginkan untuk durasi.

Ronald Wentworth
sumber