Làm cách nào để tôi có thể “in đẹp” một Thời lượng trong Java?


93

Có ai biết về một thư viện Java có thể in một số trong mili giây giống như cách mà C # làm không?

Ví dụ: 123456 ms dài sẽ được in dưới dạng 4d1h3m5s.


1
FYI, định dạng mà bạn dường như đang mô tả được Durationxác định trong tiêu chuẩn hợp lý, ISO 8601 : PnYnMnDTnHnMnStrong đó Pcó nghĩa là "Khoảng thời gian" và đánh dấu sự bắt đầu, Tphân tách phần ngày khỏi phần thời gian và ở giữa là các lần xuất hiện tùy chọn của một số với một -chữ viết tắt. Ví dụ, PT4H30M= bốn giờ rưỡi.
Basil Bourque

1
Nếu vẫn thất bại thì việc tự làm là một vấn đề rất đơn giản. Chỉ cần sử dụng các ứng dụng liên tiếp của %/để chia số thành các phần. Gần như dễ dàng hơn một số câu trả lời được đề xuất.
Hot Licks

@HotLicks Để nó vào các phương thức thư viện để có mã rõ ràng và gọn gàng hơn nhiều so với việc sử dụng /%.
Ole VV

Vâng, trong 8 năm can thiệp kể từ khi tôi hỏi câu hỏi này, tôi đã chuyển sang Joda Thời gian nào thích hợp với trường hợp sử dụng của tôi rất tốt (!)
phatmanace

@phatmanace Dự án Joda-Time hiện đang ở chế độ bảo trì và khuyên bạn nên di chuyển sang các lớp java.time được tích hợp trong Java 8 trở lên.
Basil Bourque

Câu trả lời:


91

Joda Time có một cách khá hay để thực hiện việc này bằng cách sử dụng PeriodFormatterBuilder .

Chiến thắng nhanh chóng: PeriodFormat.getDefault().print(duration.toPeriod());

ví dụ

//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
Từ một câu trả lời bên dưới có thể tạo trực tiếp phiên bản của Khoảng thời gian mà không cần tạo phiên bản Thời lượng trước rồi chuyển đổi nó thành Khoảng thời gian. Vd: Khoảng thời gian = Kỳ mới (mili); Chuỗi định dạng = formatter.print (dấu chấm);
Basil Vandegriend

7
Hãy cẩn thận với chuyển đổi "thời lượng.toPeriod ()" này. Nếu thời lượng khá lớn, phần ngày trở đi sẽ vẫn là 0. Phần giờ sẽ tiếp tục tăng lên. Bạn sẽ nhận được 25h10m23s nhưng không bao giờ nhận được "d". Lý do là không có cách hoàn toàn chính xác để chuyển đổi giờ thành ngày theo những cách nghiêm ngặt của Joda. Hầu hết các trường hợp, nếu bạn đang so sánh hai phiên bản và muốn in nó, bạn có thể tạo Khoảng thời gian mới (t1, t2) thay vì Khoảng thời gian mới (t1, t2) .toPeriod ().
Boon

@Boon bạn có biết cách chuyển đổi thời lượng thành khoảng thời gian đối với ngày không? Tôi sử dụng thời lượng mà tôi có thể thay thế thứ hai trong bộ đếm thời gian của mình. Thiết lập sự kiện PeriodType.daysTime()hoặc .standard()không giúp được gì
murt

4
@murt Nếu bạn thực sự muốn và có thể chấp nhận định nghĩa tiêu chuẩn của một ngày, thì bạn có thể "formatter.print (time.toPeriod (). normalizedStandard ())" trong đoạn mã trên. Chúc vui vẻ!
Boon

74

Tôi đã xây dựng một giải pháp đơn giản, sử dụng Java 8 Duration.toString()và một chút regex:

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

Kết quả sẽ như sau:

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

Nếu bạn không muốn có khoảng cách giữa, chỉ cần xóa replaceAll.


6
Bạn không nên dựa vào đầu ra của toStrong () vì điều này có thể thay đổi trong các phiên bản tiếp theo.
Weltraumschaf

14
@Weltraumschaf đó là không có khả năng thay đổi, vì nó được xác định trong javadoc
aditsu bỏ vì SE là EVIL

9
@Weltraumschaf Không có khả năng thay đổi vì đầu ra của Duration::toStringđược định dạng theo tiêu chuẩn ISO 8601 được xác định rõ ràng và đã được sử dụng tốt .
Basil Bourque

1
Nếu bạn không muốn phân số, hãy thêm vào .replaceAll("\\.\\d+", "")trước cuộc gọi tới toLowerCase().
zb226

1
@Weltraumschaf Quan điểm của bạn nói chung là đúng và bản thân tôi sẽ không dựa vào đầu ra toString () của hầu hết các lớp. Nhưng trong trường hợp rất cụ thể này, định nghĩa phương pháp nói rằng đầu ra của nó tuân theo tiêu chuẩn ISO-8601, như bạn có thể thấy trong Javadoc của phương pháp . Vì vậy, nó không phải là một chi tiết triển khai như trong hầu hết các lớp, do đó không phải là điều tôi mong đợi một ngôn ngữ trưởng thành như Java sẽ thay đổi như vậy,
lucasls

13

Apache commons-lang cung cấp một lớp hữu ích để hoàn thành việc này DurationFormatUtils

ví dụ DurationFormatUtils.formatDurationHMS( 15362 * 1000 ) )=> 4: 16: 02.000 (H: m: s.millis) DurationFormatUtils.formatDurationISO( 15362 * 1000 ) )=> P0Y0M0DT4H16M2.000S, cf. ISO8601


9

JodaTime có một Periodlớp có thể đại diện cho các số lượng như vậy và có thể được hiển thị (thông qua IsoPeriodFormat) ở định dạng ISO8601 , ví PT4D1H3M5Sdụ:

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

Nếu định dạng đó không phải là định dạng bạn muốn, thì PeriodFormatterBuildercho phép bạn tập hợp các bố cục tùy ý, bao gồm cả kiểu C # của bạn 4d1h3m5s.


3
Cũng giống như một lưu ý, new Period(millis).toString()sử dụng ISOPeriodFormat.standard()theo mặc định.
Thomas Beauvais

9

Với Java 8, bạn cũng có thể sử dụng toString()phương pháp java.time.Durationđịnh dạng nó mà không cần thư viện bên ngoài bằng cách sử dụng biểu diễn dựa trên ISO 8601 giây như PT8H6M12.345S.


4
Lưu ý rằng điều này không in ngày
Wim Deblauwe

58
Không chắc định dạng này đáp ứng định nghĩa của tôi về bản in đẹp . :-)
james.garriss

@ james.garriss Để làm cho nó đẹp hơn một chút, hãy xem Câu trả lời của erickdeoliveiraleal.
Basil Bourque

7

Đây là cách bạn có thể làm điều đó bằng cách sử dụng mã JDK thuần túy:

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

Các ThreeTen-Extra dự án, được duy trì bởi Stephen Colebourne, tác giả của JSR 310, java.time , và Joda thời gian , có một AmountFormatslớp học mà làm việc với Java 8 lớp thời gian ngày chuẩn. Tuy nhiên, nó khá dài dòng, không có tùy chọn cho đầu ra nhỏ gọn hơn.

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
Ồ, rất vui được biết, tôi chưa thấy tính năng đó. Mặc dù vậy, có một lỗi lớn: Thiếu dấu phẩy Oxford .
Basil Bourque

4
@BasilBourque Dấu phẩy Oxford đặc trưng cho Hoa Kỳ, nhưng thú vị là không phải cho Vương quốc Anh. Lib Time4J của tôi (đã được quốc tế hóa tốt hơn nhiều) hỗ trợ sự khác biệt này trong lớp net.time4j.PrettyTime và cũng cho phép kiểm soát đầu ra nhỏ gọn nếu muốn.
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 ngày 1 giờ 6 phút 4 giây


Làm thế nào để bạn nhận được "toHoursPart" trở lên?
Ghoti and Chips

4

Một giải pháp thay thế cho phương pháp tiếp cận người xây dựng của Joda-Time sẽ là một giải pháp dựa trên mẫu . Điều này được cung cấp bởi thư viện Time4J của tôi . Ví dụ sử dụng lớp Duration.Formatter (đã thêm một số khoảng trắng để dễ đọc hơn - xóa các khoảng trắng sẽ mang lại kiểu C # mong muốn):

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

Bộ định dạng này cũng có thể in thời lượng java.time-API (tuy nhiên, các tính năng chuẩn hóa của loại đó kém mạnh mẽ hơn):

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

Kỳ vọng của OP rằng "123456 ms dài sẽ được in dưới dạng 4d1h3m5s" được tính theo một cách rõ ràng là sai. Tôi cho rằng sự cẩu thả là lý do. Trình định dạng thời lượng tương tự được xác định ở trên cũng có thể được sử dụng làm trình phân tích cú pháp. Đoạn mã sau cho thấy rằng "4d1h3m5s" tương ứng với 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

Một cách khác là sử dụng lớp net.time4j.PrettyTime(cũng tốt cho đầu ra được bản địa hóa và in thời gian tương đối như "hôm qua", "chủ nhật tới", "4 ngày trước", v.v.):

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

Độ rộng văn bản kiểm soát xem có sử dụng chữ viết tắt hay không. Định dạng danh sách cũng có thể được kiểm soát bằng cách chọn ngôn ngữ thích hợp. Ví dụ, tiếng Anh chuẩn sử dụng dấu phẩy Oxform, trong khi ở Anh thì không. Phiên bản v5.5 mới nhất của Time4J hỗ trợ hơn 90 ngôn ngữ và sử dụng các bản dịch dựa trên kho lưu trữ CLDR (một tiêu chuẩn công nghiệp).


2
PrettyTime này tốt hơn một trong những câu trả lời khác! quá tệ, tôi đã không tìm thấy điều này đầu tiên.
ycomp

Tôi không biết tại sao bây giờ có hai phiếu phản đối cho giải pháp hoạt động tốt này vì vậy tôi đã quyết định mở rộng câu trả lời bằng cách đưa ra thêm các ví dụ về phân tích cú pháp (ngược lại với định dạng) và để làm nổi bật kỳ vọng sai của OP.
Meno Hochschild

4

Một Java 8 phiên bản dựa trên câu trả lời của 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()));
}

... vì không có PeriodFormatter trong Java 8 và không có các phương thức như getHours, getMinutes, ...

Tôi rất vui khi thấy một phiên bản tốt hơn cho Java 8.


ném java.util.IllegalFormatConversionException: f! = java.lang.Long
erickdeoliveiraleal

với Duration d1 = Duration.ofDays (0); d1 = d1.plusHours (47); d1 = d1.plusMinutes (124); d1 = d1.plusSeconds (124); System.out.println (String.format ("% s days và% 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 Cảm ơn bạn đã chỉ ra điều đó. Tôi nghĩ rằng ban đầu tôi đang viết mã cho groovy, vì vậy nó có thể đã hoạt động bằng ngôn ngữ đó. Tôi đã sửa nó cho java bây giờ.
Torsten

3

Tôi nhận thấy điều này có thể không phù hợp chính xác với trường hợp sử dụng của bạn, nhưng PrettyTime có thể hữu ích ở đây.

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
Có thể chỉ có "10 phút"?
Johnny
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.