Bagaimana saya bisa "mencetak" sebuah Durasi di Java?


95

Adakah yang tahu tentang pustaka Java yang cukup bisa mencetak angka dalam milidetik dengan cara yang sama seperti C #?

Misalnya, 123456 md sepanjang akan dicetak sebagai 4d1h3m5s.


1
FYI, format yang tampaknya Anda gambarkan adalah sebuah Durationdefinisi dalam standar yang masuk akal, ISO 8601 : di PnYnMnDTnHnMnSmana Pberarti "Periode" dan menandai awal, Tmemisahkan bagian tanggal dari bagian waktu, dan di antaranya adalah kemunculan opsional angka dengan satu singkatan huruf. Misalnya, PT4H30M= empat setengah jam.
Basil Bourque

1
Jika semuanya gagal, itu masalah yang sangat sederhana untuk melakukannya sendiri. Cukup gunakan aplikasi yang berurutan dari %dan /untuk membagi nomor menjadi beberapa bagian. Hampir lebih mudah daripada beberapa jawaban yang diajukan.
Hot Licks

@HotLicks Serahkan pada metode perpustakaan untuk kode yang lebih bersih dan lebih jelas daripada menggunakan /dan %.
Ole VV

Ya, dalam selang waktu 8 tahun sejak saya mengajukan pertanyaan ini (!) Saya telah pindah ke waktu joda yang sangat sesuai dengan kasus penggunaan saya
phatmanace

@phatmanace Proyek Joda-Time sekarang dalam mode pemeliharaan, dan menyarankan migrasi ke kelas java.time dibangun ke dalam Java 8 dan yang lebih baru.
Basil Bourque

Jawaban:


91

Joda Time memiliki cara yang cukup bagus untuk melakukan ini menggunakan PeriodFormatterBuilder .

Menang cepat: PeriodFormat.getDefault().print(duration.toPeriod());

misalnya

//import org.joda.time.format.PeriodFormatter;
//import org.joda.time.format.PeriodFormatterBuilder;
//import org.joda.time.Duration;

Duration duration = new Duration(123456); // in milliseconds
PeriodFormatter formatter = new PeriodFormatterBuilder()
     .appendDays()
     .appendSuffix("d")
     .appendHours()
     .appendSuffix("h")
     .appendMinutes()
     .appendSuffix("m")
     .appendSeconds()
     .appendSuffix("s")
     .toFormatter();
String formatted = formatter.print(duration.toPeriod());
System.out.println(formatted);

2
Tampaknya dari jawaban di bawah ini bahwa instance Periode dapat dibuat secara langsung, tanpa terlebih dahulu membuat instance Duration dan kemudian mengubahnya menjadi Periode. Misalnya Periode periode = Periode baru (milis); String formatted = formatter.print (titik);
Basil Vandegriend tanggal

7
Waspadalah terhadap konversi "durasi.toPeriod ()" ini. Jika durasinya cukup besar, porsi hari dan seterusnya akan tetap 0. Porsi jam akan terus bertambah. Anda akan mendapatkan 25h10m23s tetapi tidak pernah mendapatkan "d". Alasannya adalah tidak ada cara yang sepenuhnya benar untuk mengubah jam menjadi hari dengan cara yang ketat dari Joda. Sebagian besar kasus, jika Anda membandingkan dua instan dan ingin mencetaknya, Anda dapat melakukan Periode baru (t1, t2) alih-alih Durasi baru (t1, t2) .toPeriod ().
Boon

@Boon, apakah Anda tahu cara mengubah durasi ke periode sehubungan dengan hari? Saya menggunakan durasi dari mana saya bisa mengganti detik di timer saya. Pengaturan acara PeriodType.daysTime()atau .standard()tidak membantu
murt

4
@murt Jika Anda benar-benar ingin, dan dapat menerima definisi standar hari, maka Anda dapat "formatter.print (durasi.toPeriod (). normalizedStandard ())" dalam kode di atas. Selamat bersenang-senang!
Boon

77

Saya telah membangun solusi sederhana, menggunakan Java 8 Duration.toString()dan sedikit regex:

public static String humanReadableFormat(Duration duration) {
    return duration.toString()
            .substring(2)
            .replaceAll("(\\d[HMS])(?!$)", "$1 ")
            .toLowerCase();
}

Hasilnya akan terlihat seperti:

- 5h
- 7h 15m
- 6h 50m 15s
- 2h 5s
- 0.1s

Jika Anda tidak ingin spasi di antaranya, hapus saja replaceAll.


6
Anda tidak boleh mengandalkan output toStrong () karena ini dapat berubah di versi selanjutnya.
Weltraumschaf

14
@Weltraumschaf itu tidak mungkin berubah, karena itu ditentukan dalam javadoc
aditsu berhenti karena SE JAHAT

9
@Weltraumschaf Kemungkinan tidak berubah karena keluaran dari Duration::toStringdiformat sesuai dengan standar ISO 8601 yang ditentukan dengan baik dan sudah usang .
Basil Bourque

1
Jika Anda tidak menginginkan pecahan, tambahkan .replaceAll("\\.\\d+", "")sebelum panggilan ke toLowerCase().
zb226

1
@Weltraumschaf Poin Anda secara umum benar dan saya sendiri tidak akan bergantung pada output toString () dari sebagian besar kelas. Tetapi dalam kasus yang sangat spesifik ini, definisi metode menyatakan bahwa outputnya mengikuti standar ISO-8601, seperti yang Anda lihat di Javadoc metode . Jadi ini bukan detail implementasi seperti di kebanyakan kelas, oleh karena itu bukan sesuatu yang saya harapkan bahasa yang matang seperti Java berubah seperti itu,
lucasls

13

Apache commons-lang menyediakan kelas yang berguna untuk menyelesaikannya juga DurationFormatUtils

misal DurationFormatUtils.formatDurationHMS( 15362 * 1000 ) )=> 4: 16: 02.000 (H: m: s.millis) DurationFormatUtils.formatDurationISO( 15362 * 1000 ) )=> P0Y0M0DT4H16M2.000S, lih. ISO8601


9

JodaTime memiliki Periodkelas yang dapat mewakili kuantitas seperti itu, dan dapat dirender (melalui IsoPeriodFormat) dalam format ISO8601 , mis.PT4D1H3M5S , mis.

Period period = new Period(millis);
String formatted = ISOPeriodFormat.standard().print(period);

Jika format itu bukan yang Anda inginkan, maka PeriodFormatterBuildermemungkinkan Anda menyusun tata letak sewenang-wenang, termasuk gaya C # Anda 4d1h3m5s.


3
Sekadar catatan, new Period(millis).toString()gunakan ISOPeriodFormat.standard()secara default.
Thomas Beauvais


8

Inilah cara Anda melakukannya menggunakan kode JDK murni:

import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.Duration;

long diffTime = 215081000L;
Duration duration = DatatypeFactory.newInstance().newDuration(diffTime);

System.out.printf("%02d:%02d:%02d", duration.getDays() * 24 + duration.getHours(), duration.getMinutes(), duration.getSeconds()); 

7

org.threeten.extra.AmountFormats.wordBased

Proyek ThreeTen-Extra , yang dikelola oleh Stephen Colebourne, penulis JSR 310, java.time , dan Joda-Time , memiliki AmountFormatskelas yang bekerja dengan kelas waktu tanggal 8 Java standar. Ini cukup bertele-tele, tanpa opsi untuk output yang lebih kompak.

Duration d = Duration.ofMinutes(1).plusSeconds(9).plusMillis(86);
System.out.println(AmountFormats.wordBased(d, Locale.getDefault()));

1 minute, 9 seconds and 86 milliseconds


2
Wow, senang mengetahuinya, saya belum melihat fitur itu. Namun memiliki satu kelemahan utama: Kehilangan koma Oxford .
Basil Bourque

4
@BasilBourque Koma Oxford adalah tipikal untuk AS, tetapi menariknya tidak untuk Inggris. Lib Time4J saya (yang memiliki internasionalisasi yang jauh lebih baik) mendukung perbedaan ini di kelas net.time4j.PrettyTime dan juga memungkinkan kontrol atas keluaran ringkas jika diinginkan.
Meno Hochschild

6

Java 9+

Duration d1 = Duration.ofDays(0);
        d1 = d1.plusHours(47);
        d1 = d1.plusMinutes(124);
        d1 = d1.plusSeconds(124);
System.out.println(String.format("%s d %sh %sm %ss", 
                d1.toDaysPart(), 
                d1.toHoursPart(), 
                d1.toMinutesPart(), 
                d1.toSecondsPart()));

2 d 1j 6m 4s


Bagaimana Anda mendapatkan "toHoursPart" dan yang lebih baru?
Ghoti dan Chips

5

Sebuah alternatif untuk pendekatan pembangun dari Joda-Waktu akan menjadi solusi berbasis pola . Ini ditawarkan oleh perpustakaan saya Time4J . Contoh menggunakan kelas Duration.Formatter (menambahkan beberapa spasi agar lebih mudah dibaca - menghapus spasi akan menghasilkan gaya C # yang diinginkan):

IsoUnit unit = ClockUnit.MILLIS;
Duration<IsoUnit> dur = // normalized duration with multiple components
  Duration.of(123456, unit).with(Duration.STD_PERIOD);
Duration.Formatter<IsoUnit> f = // create formatter/parser with optional millis
  Duration.Formatter.ofPattern("D'd' h'h' m'm' s[.fff]'s'");

System.out.println(f.format(dur)); // output: 0d 0h 2m 3.456s

Pemformat ini juga dapat mencetak durasi java.time-API (namun, fitur normalisasi jenis itu kurang kuat):

System.out.println(f.format(java.time.Duration.ofMillis(123456))); // output: 0d 0h 2m 3.456s

Perkiraan OP bahwa "123456 ms sepanjang akan dicetak sebagai 4d1h3m5s" dihitung dengan cara yang jelas salah. Saya menganggap kecerobohan sebagai alasan. Pemformat durasi yang sama yang ditentukan di atas juga dapat digunakan sebagai parser. Kode berikut menunjukkan bahwa "4d1h3m5s" agak sesuai dengan 349385000 = 1000 * (4 * 86400 + 1 * 3600 + 3 * 60 + 5):

System.out.println(
  f.parse("4d 1h 3m 5s")
   .toClockPeriodWithDaysAs24Hours()
   .with(unit.only())
   .getPartialAmount(unit)); // 349385000

Cara lain adalah menggunakan kelas net.time4j.PrettyTime(yang juga bagus untuk keluaran yang dilokalkan dan mencetak waktu relatif seperti "kemarin", "Minggu depan", "4 hari yang lalu", dll.):

String s = PrettyTime.of(Locale.ENGLISH).print(dur, TextWidth.NARROW);
System.out.println(s); // output: 2m 3s 456ms

s = PrettyTime.of(Locale.ENGLISH).print(dur, TextWidth.WIDE);
System.out.println(s); // output: 2 minutes, 3 seconds, and 456 milliseconds

s = PrettyTime.of(Locale.UK).print(dur, TextWidth.WIDE);
System.out.println(s); // output: 2 minutes, 3 seconds and 456 milliseconds

Kontrol lebar teks apakah singkatan digunakan atau tidak. Format daftar juga dapat dikontrol dengan memilih lokal yang sesuai. Misalnya, bahasa Inggris standar menggunakan koma Oxform, sedangkan Inggris tidak. Versi terbaru v5.5 dari Time4J mendukung lebih dari 90 bahasa dan menggunakan terjemahan berdasarkan repositori CLDR (standar industri).


2
PrettyTime ini jauh lebih baik daripada yang ada di jawaban lain! Sayang sekali saya tidak menemukan ini dulu.
ycomp

Saya tidak tahu mengapa sekarang ada dua suara negatif untuk solusi yang berfungsi dengan baik ini jadi saya telah memutuskan untuk memperluas jawaban dengan memberikan lebih banyak contoh juga tentang penguraian (kebalikan dari pemformatan) dan untuk menyoroti harapan OP yang salah.
Meno Hochschild

4

Versi Java 8 berdasarkan jawaban pengguna678573 :

private static String humanReadableFormat(Duration duration) {
    return String.format("%s days and %sh %sm %ss", duration.toDays(),
            duration.toHours() - TimeUnit.DAYS.toHours(duration.toDays()),
            duration.toMinutes() - TimeUnit.HOURS.toMinutes(duration.toHours()),
            duration.getSeconds() - TimeUnit.MINUTES.toSeconds(duration.toMinutes()));
}

... karena tidak ada PeriodFormatter di Java 8 dan tidak ada metode seperti getHours, getMinutes, ...

Saya akan senang melihat versi yang lebih baik untuk Java 8.


throws java.util.IllegalFormatConversionException: f! = java.lang.Long
erickdeoliveiraleal

dengan Durasi d1 = Duration.ofDays (0); d1 = d1.plusHours (47); d1 = d1.plusMinutes (124); d1 = d1.plusSeconds (124); System.out.println (String.format ("% s hari dan% sh% sm% 1.0fs", d1.toDays (), d1.toHours () - TimeUnit.DAYS.toHours (d1.toDays ()), d1 .toMinutes () - TimeUnit.HOURS.toMinutes (d1.toHours ()), d1.toSeconds () - TimeUnit.MINUTES.toSeconds (d1.toMinutes ())));
erickdeoliveiraleal

@erickdeoliveiraleal Terima kasih telah menunjukkannya. Saya rasa saya awalnya menulis kode untuk groovy, jadi itu mungkin berhasil dalam bahasa itu. Saya mengoreksinya untuk java sekarang.
Torsten

3

Saya menyadari ini mungkin tidak sesuai dengan kasus penggunaan Anda, tetapi PrettyTime mungkin berguna di sini.

PrettyTime p = new PrettyTime();
System.out.println(p.format(new Date()));
//prints: “right now”

System.out.println(p.format(new Date(1000*60*10)));
//prints: “10 minutes from now”

4
Apakah mungkin hanya memiliki "10 menit"?
Johnny
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.