ฉันจะ "พิมพ์สวย" a Duration ใน Java ได้อย่างไร


95

มีใครรู้จักไลบรารี Java ที่สามารถพิมพ์ตัวเลขในหน่วยมิลลิวินาทีแบบเดียวกับ C # ได้บ้าง?

เช่น 123456 ms เป็นแบบยาวจะพิมพ์เป็น 4d1h3m5s


1
FYI รูปแบบที่คุณดูเหมือนจะอธิบายเป็นรูปแบบที่Durationกำหนดไว้ในมาตรฐานที่เหมาะสมISO 8601 : PnYnMnDTnHnMnSโดยที่Pหมายถึง "ช่วงเวลา" และทำเครื่องหมายจุดเริ่มต้นTแยกส่วนของวันที่ออกจากส่วนของเวลาและในระหว่างนั้นเป็นส่วนที่เป็นทางเลือกของตัวเลขที่มีค่าเดียว - อักษรย่อ ตัวอย่างเช่นPT4H30M= สี่ชั่วโมงครึ่ง
Basil Bourque

1
หากทุกอย่างล้มเหลวมันเป็นเรื่องง่ายมากที่จะทำด้วยตัวเอง เพียงใช้แอปพลิเคชันต่อเนื่องของ%และ/เพื่อแบ่งหมายเลขออกเป็นส่วน ๆ เกือบจะง่ายกว่าคำตอบที่เสนอ
Hot Licks

@HotLicks ปล่อยให้วิธีการห้องสมุดสำหรับการทำความสะอาดมากและรหัสที่ชัดเจนกว่าการใช้และ/ %
Ole VV

ใช่ในช่วง 8 ปีที่ฉันถามคำถามนี้ (!) ฉันได้ย้ายไปที่เวลา joda ซึ่งเหมาะกับกรณีการใช้งานของฉันเป็นอย่างดี
phatmanace

@phatmanace ตอนนี้โปรเจ็กต์Joda-Timeอยู่ในโหมดการบำรุงรักษาและแนะนำการโอนย้ายไปยังคลาสjava.time ที่สร้างใน Java 8 และใหม่กว่า
Basil Bourque

คำตอบ:


91

Joda เวลามีวิธีที่ดีงามที่จะทำนี้ใช้PeriodFormatterBuilder

ชนะอย่างรวดเร็ว: PeriodFormat.getDefault().print(duration.toPeriod());

เช่น

//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
จากคำตอบด้านล่างนี้สามารถสร้างอินสแตนซ์ของช่วงเวลาได้โดยตรงโดยไม่ต้องสร้างอินสแตนซ์ระยะเวลาก่อนจากนั้นจึงแปลงเป็นช่วงเวลา เช่น Period period = ระยะเวลาใหม่ (มิลลิวินาที); รูปแบบสตริง = formatter.print (จุด);
Basil Vandegriend

7
ระวังการแปลง "duration.toPeriod ()" นี้ หากระยะเวลาค่อนข้างมากส่วนของวันเป็นต้นไปจะยังคงเป็น 0 ส่วนของชั่วโมงจะเพิ่มขึ้นเรื่อย ๆ คุณจะได้รับ 25h10m23s แต่ไม่เคยได้รับ "d" เหตุผลก็คือไม่มีวิธีที่ถูกต้องในการแปลงชั่วโมงเป็นวันด้วยวิธีที่เข้มงวดของ Joda กรณีส่วนใหญ่ถ้าคุณเปรียบเทียบสองอินสแตนซ์และต้องการพิมพ์มันคุณสามารถสร้าง Period ใหม่ (t1, t2) แทน Duration ใหม่ (t1, t2) .toPeriod ()
บุญ

@ บุญคุณรู้วิธีแปลงระยะเวลาเป็นช่วงเวลาเทียบกับวันหรือไม่? ฉันใช้ระยะเวลาที่ฉันสามารถแทนวินาทีในตัวจับเวลาของฉัน การจัดกิจกรรมPeriodType.daysTime()หรือ.standard()ไม่ช่วย
murt

4
@murt หากคุณต้องการและยอมรับคำจำกัดความมาตรฐานของวันคุณสามารถ "formatter.print (duration.toPeriod (). normalizedStandard ())" ในโค้ดด้านบน มีความสุข!
บุญ

78

ฉันได้สร้างโซลูชันง่ายๆโดยใช้ Java 8 Duration.toString()และ regex เล็กน้อย:

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

ผลลัพธ์จะมีลักษณะดังนี้:

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

replaceAllหากคุณไม่ต้องการให้ช่องว่างระหว่างเพียงแค่ลบ


6
คุณไม่ควรพึ่งพาผลลัพธ์ของ toStrong () เนื่องจากอาจมีการเปลี่ยนแปลงในเวอร์ชันอื่น ๆ
Weltraumschaf

14
@Weltraumschaf มันไม่น่าจะเปลี่ยนแปลงเนื่องจากระบุไว้ใน javadoc
aditsu เลิกเพราะ SE คือ EVIL

9
@Weltraumschaf ไม่น่าเปลี่ยนแปลงเนื่องจากผลลัพธ์ของDuration::toStringถูกจัดรูปแบบตามมาตรฐาน ISO 8601 ที่กำหนดไว้อย่างดีและสวมใส่ได้ดี
Basil Bourque

1
หากคุณไม่ต้องการที่เศษส่วนเพิ่มก่อนที่จะเรียกร้องให้.replaceAll("\\.\\d+", "") toLowerCase()
zb226

1
@Weltraumschaf ประเด็นของคุณถูกต้องโดยทั่วไปและฉันจะไม่พึ่งพาเอาต์พุต toString () ของคลาสส่วนใหญ่ แต่ในกรณีที่เฉพาะเจาะจงมากนี้วิธีการนิยามระบุว่าผลผลิตของตนดังนี้ ISO-8601 มาตรฐานที่คุณสามารถดูในวิธีการJavadoc ดังนั้นจึงไม่ใช่รายละเอียดการใช้งานเหมือนในชั้นเรียนส่วนใหญ่ดังนั้นจึงไม่ใช่สิ่งที่ฉันคาดหวังว่าภาษาที่โตเต็มที่เช่น Java จะเปลี่ยนแปลงเช่นนั้น
lucasls

13

Apache commons-lang มีคลาสที่มีประโยชน์ในการทำสิ่งนี้เช่นกันDurationFormatUtils

เช่น DurationFormatUtils.formatDurationHMS( 15362 * 1000 ) )=> 4: 16: 02.000 (H: m: s.millis) DurationFormatUtils.formatDurationISO( 15362 * 1000 ) )=> P0Y0M0DT4H16M2.000S, cf ISO8601


9

JodaTimeมีPeriodคลาสที่สามารถแสดงปริมาณดังกล่าวและสามารถแสดงผล (ผ่านIsoPeriodFormat) ในรูปแบบISO8601เช่นPT4D1H3M5Sเช่น

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

หากรูปแบบนั้นไม่ใช่รูปแบบที่คุณต้องการPeriodFormatterBuilderให้คุณประกอบเลย์เอาต์ตามอำเภอใจรวมถึงสไตล์ C # ของ4d1h3m5sคุณ


3
เช่นเดียวกับบันทึกย่อnew Period(millis).toString()ใช้ISOPeriodFormat.standard()โดยค่าเริ่มต้น
Thomas Beauvais

9

ด้วย Java 8 คุณสามารถใช้toString()วิธีjava.time.Durationการจัดรูปแบบโดยไม่มีไลบรารีภายนอกโดยใช้การแทนค่าตาม ISO 8601 วินาทีเช่น PT8H6M12.345S


4
โปรดทราบว่านี่ไม่ใช่วันที่พิมพ์
Wim Deblauwe

60
ไม่แน่ใจว่ารูปแบบนี้ตรงตามคำจำกัดความของงานพิมพ์สวย ๆของฉัน :-)
james.garriss

@ james.garriss เพื่อให้ดูสวยขึ้นดูคำตอบโดย erickdeoliveiraleal
Basil Bourque

8

นี่คือวิธีที่คุณสามารถทำได้โดยใช้รหัส JDK แท้:

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

โครงการThreeTen-Extraซึ่งดูแลโดย Stephen Colebourne ผู้เขียน JSR 310, java.timeและJoda-TimeมีAmountFormatsคลาสที่ทำงานร่วมกับคลาสวันที่ของ Java 8 มาตรฐาน แม้ว่ามันจะค่อนข้างละเอียด แต่ไม่มีตัวเลือกสำหรับเอาต์พุตที่กะทัดรัดมากขึ้น

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
ว้าวยินดีที่ได้รู้จักฉันไม่เห็นฟีเจอร์นั้น มีหนึ่งข้อบกพร่องที่สำคัญแม้ว่า: ที่ขาดหายไปจุลภาคฟอร์ด
Basil Bourque

4
@BasilBourque จุลภาค Oxford เป็นเรื่องปกติสำหรับสหรัฐอเมริกา แต่ไม่น่าสนใจสำหรับสหราชอาณาจักร lib Time4J ของฉัน (ซึ่งมีความเป็นสากลที่ดีกว่ามาก) รองรับความแตกต่างนี้ในคลาสnet.time4j.PrettyTimeและยังอนุญาตให้ควบคุมเอาต์พุตขนาดกะทัดรัดได้หากต้องการ
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 วัน 1 ชั่วโมง 6 นาที 4 วินาที


คุณได้รับ "toHoursPart" ขึ้นไปอย่างไร
Ghoti and Chips

5

ทางเลือกในการสร้างแนวทางของ Joda เวลาจะเป็นวิธีการแก้ปัญหารูปแบบตาม นี้ถูกนำเสนอโดยห้องสมุดของฉันTime4J ตัวอย่างการใช้คลาสDuration.Formatter (เพิ่มช่องว่างบางส่วนเพื่อให้อ่านง่ายขึ้น - การลบช่องว่างจะทำให้ได้ C # -style ที่ต้องการ):

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

ฟอร์แมตเตอร์นี้ยังสามารถพิมพ์ระยะเวลาของjava.time-API (อย่างไรก็ตามคุณสมบัติการทำให้เป็นมาตรฐานของประเภทนั้นมีประสิทธิภาพน้อยกว่า):

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

ความคาดหวังของ OP ที่ "123456 ms as a long จะพิมพ์เป็น 4d1h3m5s" นั้นคำนวณผิดอย่างเห็นได้ชัด ฉันถือว่าความชุ่ยเป็นเหตุผล ตัวจัดรูปแบบระยะเวลาเดียวกันที่กำหนดไว้ข้างต้นยังสามารถใช้เป็นตัวแยกวิเคราะห์ได้ รหัสต่อไปนี้แสดงให้เห็นว่า "4d1h3m5s" ค่อนข้างตรงกับ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

อีกวิธีหนึ่งคือการใช้คลาสnet.time4j.PrettyTime(ซึ่งเหมาะสำหรับเอาต์พุตที่แปลเป็นภาษาท้องถิ่นและการพิมพ์เวลาสัมพัทธ์เช่น "เมื่อวาน" "วันอาทิตย์หน้า" "4 วันที่แล้ว" เป็นต้น):

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

ความกว้างของข้อความจะควบคุมว่ามีการใช้ตัวย่อหรือไม่ รูปแบบรายการสามารถควบคุมได้เช่นกันโดยเลือกโลแคลที่เหมาะสม ตัวอย่างเช่นภาษาอังกฤษมาตรฐานใช้เครื่องหมายจุลภาค Oxform ในขณะที่สหราชอาณาจักรไม่ใช้ เวอร์ชันล่าสุด v5.5 ของ Time4J รองรับมากกว่า 90 ภาษาและใช้การแปลตามที่เก็บ CLDR (มาตรฐานอุตสาหกรรม)


2
PrettyTime นี้ดีกว่าคำตอบอื่น ๆ ! แย่จังที่ฉันไม่พบสิ่งนี้ก่อน
ycomp

ฉันไม่รู้ว่าทำไมตอนนี้จึงมีการโหวตลงสองรายการสำหรับโซลูชันที่ใช้งานได้ดีนี้ดังนั้นฉันจึงตัดสินใจขยายคำตอบโดยให้ตัวอย่างเพิ่มเติมเกี่ยวกับการแยกวิเคราะห์ (ตรงกันข้ามกับการจัดรูปแบบ) และเพื่อเน้นความคาดหวังที่ไม่ถูกต้องของ OP
Meno Hochschild

4

Java 8รุ่นขึ้นอยู่กับคำตอบของ user678573 :

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

... เนื่องจากไม่มี PeriodFormatter ใน Java 8 และไม่มีเมธอดเช่น getHours, getMinutes, ...

ฉันยินดีที่จะเห็นเวอร์ชันที่ดีกว่าสำหรับ Java 8


พ่น java.util.IllegalFormatConversionException: f! = java.lang.Long
erickdeoliveiraleal

ด้วย Duration d1 = Duration.ofDays (0); d1 = d1.plusHours (47); d1 = d1.plusMinutes (124); d1 = d1.plusSeconds (124); System.out.println (String.format ("% s วันและ% 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 ขอบคุณที่ชี้ให้เห็น ฉันคิดว่าตอนแรกฉันเขียนโค้ดสำหรับ groovy ดังนั้นมันอาจจะใช้งานได้ในภาษานั้น ฉันแก้ไขมันสำหรับ java แล้ว
Torsten

3

ฉันตระหนักดีว่าสิ่งนี้อาจไม่เหมาะกับกรณีการใช้งานของคุณ แต่PrettyTimeอาจมีประโยชน์ที่นี่

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
เป็นไปได้ไหมที่จะมีเวลาเพียง "10 นาที"
Johnny
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.