Jak mogę „ładnie wydrukować” czas trwania w Javie?

95

Czy ktoś zna bibliotekę Java, która może całkiem wydrukować liczbę w milisekundach w taki sam sposób, jak robi to C #?

Np. 123456 ms jako długość byłoby wydrukowane jako 4d1h3m5s.

phatmanace
źródło
1
Do Twojej wiadomości, format, który zdaje się opisywać, jest Durationzdefiniowany w rozsądnym standardzie ISO 8601 : PnYnMnDTnHnMnSgdzie Poznacza „Okres” i oznacza początek, Toddziela część daty od części czasu, a pomiędzy nimi są opcjonalne wystąpienia liczby z pojedynczym -skrót liter. Na przykład PT4H30M= cztery i pół godziny.
Basil Bourque
1
Jeśli wszystko inne zawiedzie, zrób to sam. Wystarczy użyć kolejnych aplikacji %i /podzielić numer na części. Niemal łatwiejsze niż niektóre z proponowanych odpowiedzi.
Hot Licks,
@HotLicks Pozostaw to metodom bibliotecznym, aby uzyskać znacznie czystszy i bardziej przejrzysty kod niż używanie /i %.
Ole VV
Tak, w ciągu ostatnich 8 lat, odkąd zadałem to pytanie (!) Przeniosłem się do czasu joda, który bardzo dobrze pasuje do mojego przypadku użycia
phatmanace
@phatmanace Projekt Joda-Time jest teraz w trybie konserwacji i zaleca migrację do klas java.time wbudowanych w Javę 8 lub nowszą.
Basil Bourque

Odpowiedzi:

91

Joda Time ma całkiem niezły sposób na zrobienie tego za pomocą PeriodFormatterBuilder .

Szybkie zwycięstwo: PeriodFormat.getDefault().print(duration.toPeriod());

na przykład

//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);
Rob Hruska
źródło
2
Z poniższej odpowiedzi wynika, że ​​instancję Period można utworzyć bezpośrednio, bez uprzedniego tworzenia instancji Duration, a następnie konwertowania jej na Period. Np. Okres okresu = nowy okres (milis); Ciąg sformatowany = formatter.print (kropka);
Basil Vandegriend
7
Uważaj na konwersję „duration.toPeriod ()”. Jeśli czas trwania jest dość długi, część dzienna pozostanie równa 0. Część godzinowa będzie rosła. Dostaniesz 25h10m23s, ale nigdy nie dostaniesz „d”. Powodem jest to, że nie ma w pełni poprawnego sposobu przeliczania godzin na dni w ścisły sposób Jody. W większości przypadków, jeśli porównujesz dwa momenty i chcesz je wydrukować, możesz wykonać nowy okres (t1, t2) zamiast nowego czasu trwania (t1, t2) .toPeriod ().
Boon
@Boon Czy wiesz, jak przekonwertować czas trwania na okres w odniesieniu do dni? Używam czasu, od którego mogę zastąpić sekundę w moim zegarze. Ustawienie wydarzenia PeriodType.daysTime()lub .standard()nie pomogło
murt
4
@murt Jeśli naprawdę chcesz i akceptujesz standardową definicję dnia, możesz w powyższym kodzie wpisać „formatter.print (duration.toPeriod (). normalizedStandard ())”. Baw się dobrze!
Boon
78

Zbudowałem proste rozwiązanie, używając Java 8 Duration.toString()i trochę wyrażenia regularnego:

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

Wynik będzie wyglądał następująco:

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

Jeśli nie chcesz między nimi spacji, po prostu usuń replaceAll.

Lucasls
źródło
7
Nigdy nie powinieneś polegać na wynikach funkcji toStrong (), ponieważ może to ulec zmianie w kolejnych wersjach.
Weltraumschaf
14
@Weltraumschaf to raczej się nie zmieni, ponieważ jest określone w javadoc
aditsu zakończyło pracę, ponieważ SE jest ZŁEM.
9
@Weltraumschaf Jest mało prawdopodobne, aby się zmieniło, ponieważ dane wyjściowe Duration::toStringsą sformatowane zgodnie z dobrze zdefiniowaną i znaną normą ISO 8601 .
Basil Bourque
1
Jeśli nie chcesz ułamków, dodaj .replaceAll("\\.\\d+", "")przed wywołaniem toLowerCase().
zb226
1
@Weltraumschaf Twój punkt widzenia jest ogólnie poprawny i sam nie polegałbym na wynikach metody toString () większości klas. Ale w tym bardzo szczególnym przypadku definicja metody stwierdza, że ​​jej dane wyjściowe są zgodne ze standardem ISO-8601, jak widać w Javadoc metody . Więc nie jest to szczegół implementacji, jak w większości klas, dlatego nie spodziewałbym się, że język tak dojrzały jak Java zmieni się w ten sposób,
lucasls
13

Apache commons-lang zapewnia użyteczną klasę do wykonania tego zadania DurationFormatUtils

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

noleto
źródło
9

JodaTime ma Periodklasę, która może reprezentować takie wielkości i może być renderowana (przez IsoPeriodFormat) w formacie ISO8601PT4D1H3M5S , np.

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

Jeśli ten format nie jest tym, którego potrzebujesz, PeriodFormatterBuilderumożliwia tworzenie dowolnych układów, w tym stylu C # 4d1h3m5s.

skaffman
źródło
3
Tak jak uwaga, domyślnie new Period(millis).toString()używa ISOPeriodFormat.standard().
Thomas Beauvais
9

W Javie 8 można również użyć toString()metody java.time.Durationformatowania bez bibliotek zewnętrznych, używając reprezentacji sekundowej ISO 8601, takiej jak PT8H6M12.345S.

Konrad Höffner
źródło
4
Zauważ, że to nie drukuje dni
Wim Deblauwe
61
Nie jestem pewien, czy ten format spełnia moją definicję ładnego druku. :-)
james.garriss
@ james.garriss Aby było trochę ładniej, zobacz odpowiedź erickdeoliveiraleal.
Basil Bourque
8

Oto, jak możesz to zrobić, używając czystego kodu 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()); 
user678573
źródło
7

org.threeten.extra.AmountFormats.wordBased

Projekt ThreeTen-Extra , który jest prowadzony przez Stephena Colebourne'a, autora JSR 310, java.time i Joda-Time , ma AmountFormatsklasę, która współpracuje ze standardowymi klasami daty i czasu Java 8. Jest jednak dość rozwlekły, bez opcji na bardziej kompaktowe wyjście.

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

1 minute, 9 seconds and 86 milliseconds

Adrian Baker
źródło
2
Wow, miło wiedzieć, nie widziałem tej funkcji. Ma jednak jedną poważną wadę: brak przecinka oksfordzkiego .
Basil Bourque
4
@BasilBourque Przecinek oksfordzki jest typowy dla Stanów Zjednoczonych, ale co ciekawe, nie dla Wielkiej Brytanii. Moja biblioteka Time4J (która ma znacznie lepszą internacjonalizację) obsługuje to rozróżnienie w klasie net.time4j.PrettyTime, a także umożliwia kontrolę nad kompaktowym wyjściem, jeśli jest to pożądane.
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 1h 6m 4s

erickdeoliveiraleal
źródło
Jak otrzymujesz „toHoursPart” i wyższe?
Ghoti and Chips
5

Alternatywą dla podejścia konstruktora w Joda-Time byłoby rozwiązanie oparte na wzorcach . To jest oferowane przez moją bibliotekę Time4J . Przykład użycia klasy Duration.Formatter (dodano kilka spacji dla większej czytelności - usunięcie spacji da pożądany styl C #):

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

Ten program formatujący może również drukować czasy trwania java.time-API (jednak funkcje normalizacji tego typu są mniej wydajne):

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

Oczekiwanie OP, że „123456 ms jako długość zostanie wydrukowane jako 4d1h3m5s” jest obliczane w oczywiście nieprawidłowy sposób. Za powód przyjmuję niechlujstwo. Ten sam program formatujący czas trwania zdefiniowany powyżej może być również używany jako parser. Poniższy kod pokazuje, że „4d1h3m5s” odpowiada raczej 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

Innym sposobem jest użycie klasy net.time4j.PrettyTime(co jest również dobre w przypadku zlokalizowanych wyników i drukowania względnych czasów, takich jak „wczoraj”, „następna niedziela”, „4 dni temu” itp.):

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

Szerokość tekstu określa, czy używane są skróty, czy nie. Format listy można również kontrolować, wybierając odpowiednie ustawienie regionalne. Na przykład standardowy angielski używa przecinka Oxform, podczas gdy w Wielkiej Brytanii nie. Najnowsza wersja v5.5 Time4J obsługuje ponad 90 języków i wykorzystuje tłumaczenia oparte na repozytorium CLDR (standard branżowy).

Meno Hochschild
źródło
2
ten PrettyTime jest o wiele lepszy niż ten w drugiej odpowiedzi! szkoda, że ​​nie znalazłem tego jako pierwszy.
ycomp
Nie wiem, dlaczego teraz są dwa głosy przeciw temu dobrze działającemu rozwiązaniu, więc zdecydowałem się rozszerzyć odpowiedź, podając więcej przykładów dotyczących analizowania (przeciwieństwo formatowania) i podkreślając niewłaściwe oczekiwanie PO.
Meno Hochschild
5

Wersja Java 8 oparta na odpowiedzi użytkownika678573 :

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

... ponieważ w Javie 8 nie ma PeriodFormatter ani metod takich jak getHours, getMinutes, ...

Byłbym szczęśliwy, mogąc zobaczyć lepszą wersję dla Java 8.

Torsten
źródło
rzuca java.util.IllegalFormatConversionException: f! = java.lang.Long
erickdeoliveiraleal
z Duration d1 = Duration.ofDays (0); d1 = d1.plusHours (47); d1 = d1.plusMinutes (124); d1 = d1.plusSeconds (124); System.out.println (String.format ("% s dni i% 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 Dzięki za zwrócenie uwagi. Myślę, że pierwotnie pisałem kod groovy, więc mógł działać w tym języku. Poprawiłem to teraz dla javy.
Torsten,
3

Zdaję sobie sprawę, że może to nie pasować dokładnie do twojego przypadku użycia, ale PrettyTime może być tutaj przydatne.

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”
Nacięcie
źródło
4
Czy można mieć tylko „10 minut”?
Johnny