Wie kann ich eine Dauer in Java „hübsch drucken“?

93

Kennt jemand eine Java-Bibliothek, die eine Zahl in Millisekunden genauso drucken kann wie C #?

Beispielsweise würden 123456 ms als 4d1h3m5s gedruckt.

Phatmanace
quelle
1
Zu Ihrer Information, das Format, das Sie zu beschreiben scheinen, ist Durationin der vernünftigen Norm ISO 8601 definiert : PnYnMnDTnHnMnSDabei Pbedeutet "Punkt" und markiert den Anfang, Ttrennt den Datums- und den Zeitabschnitt und dazwischen sind optionale Vorkommen einer Zahl mit einer einzigen -Briefkürzel. Zum Beispiel PT4H30M= viereinhalb Stunden.
Basil Bourque
1
Wenn alles andere fehlschlägt, ist es sehr einfach, es selbst zu tun. Verwenden Sie einfach aufeinanderfolgende Anwendungen von %und /, um die Anzahl in Teile aufzuteilen. Fast einfacher als einige der vorgeschlagenen Antworten.
Hot Licks
@HotLicks Überlassen Sie es den Bibliotheksmethoden für viel saubereren und klareren Code als die Verwendung von /und %.
Ole VV
Ja, in den letzten 8 Jahren, seit ich diese Frage gestellt habe (!), Bin ich zu joda time übergegangen, was sehr gut zu meinem Anwendungsfall passt
phatmanace
@phatmanace Das Joda-Time- Projekt befindet sich jetzt im Wartungsmodus und empfiehlt die Migration zu den in Java 8 und höher integrierten java.time- Klassen.
Basil Bourque

Antworten:

89

Joda Time hat eine ziemlich gute Möglichkeit, dies mit einem PeriodFormatterBuilder zu tun .

Schneller Gewinn: PeriodFormat.getDefault().print(duration.toPeriod());

z.B

//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
quelle
2
Aus einer Antwort unten geht hervor, dass eine Instanz von Period direkt erstellt werden kann, ohne zuerst eine Duration-Instanz zu erstellen und diese dann in Period zu konvertieren. ZB Periodenperiode = neue Periode (Millis); String formatiert = formatter.print (Punkt);
Basil Vandegriend
7
Achten Sie auf diese Konvertierung von "duration.toPeriod ()". Wenn die Dauer ziemlich groß ist, bleibt der Tagesanteil ab 0. Der Stundenanteil wächst weiter. Sie erhalten 25h10m23s, aber niemals das "d". Der Grund dafür ist, dass es keinen völlig korrekten Weg gibt, Stunden in Tage auf Jodas strenge Weise umzurechnen. In den meisten Fällen können Sie, wenn Sie zwei Zeitpunkte vergleichen und drucken möchten, anstelle der neuen Dauer (t1, t2) .toPeriod () eine neue Periode (t1, t2) ausführen.
Boon
@Boon wissen Sie, wie man die Dauer in Bezug auf Tage in einen Zeitraum umwandelt? Ich verwende die Dauer, ab der ich die Sekunde in meinem Timer ersetzen kann. Ereigniseinstellung PeriodType.daysTime()oder .standard()hat nicht geholfen
murt
4
@murt Wenn Sie wirklich möchten und die Standarddefinition eines Tages akzeptieren können, können Sie im obigen Code "formatter.print (duration.toPeriod (). normalizedStandard ())" verwenden. Habe Spaß!
Boon
74

Ich habe eine einfache Lösung mit Java 8 Duration.toString()und ein bisschen Regex erstellt:

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

Das Ergebnis sieht folgendermaßen aus:

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

Wenn Sie keine Leerzeichen dazwischen möchten, entfernen Sie einfach replaceAll.

lucasls
quelle
6
Sie sollten sich niemals auf die Ausgabe von toStrong () verlassen, da sich dies in weiteren Versionen ändern kann.
Weltraumschaf
12
@Weltraumschaf es ist unwahrscheinlich, dass es sich ändert, da es im javadoc angegeben ist
aditsu beenden, weil SE
9
@Weltraumschaf Es ist unwahrscheinlich, dass sich die Ausgabe von Duration::toStringgemäß der genau definierten und abgenutzten Norm ISO 8601 formatiert .
Basil Bourque
Wenn Sie keine Brüche möchten, fügen Sie diese .replaceAll("\\.\\d+", "")vor dem Aufruf hinzu toLowerCase().
zb226
1
@Weltraumschaf Dein Punkt ist im Allgemeinen richtig und ich würde mich nicht auf die toString () Ausgabe der meisten Klassen verlassen. Aber in diesem speziellen Fall der Methode Definition besagt , dass seine Ausgabe folgt den ISO-8601 - Standard, wie Sie in der Methode sehen können Javadoc . Es ist also kein Implementierungsdetail wie in den meisten Klassen, daher würde ich nicht erwarten, dass sich eine so ausgereifte Sprache wie Java so ändert,
lucasls
13

Apache commons-lang bietet eine nützliche Klasse, um dies ebenfalls zu erledigen. DurationFormatUtils

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

noleto
quelle
9

JodaTime hat eine PeriodKlasse, die solche Größen darstellen kann und (via IsoPeriodFormat) im ISO8601- Format gerendert werden kann PT4D1H3M5S, z

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

Wenn dieses Format nicht das gewünschte ist, PeriodFormatterBuilderkönnen Sie beliebige Layouts zusammenstellen, einschließlich Ihres C # -Stils 4d1h3m5s.

Skaffman
quelle
3
new Period(millis).toString()Verwendet nur als Hinweis die ISOPeriodFormat.standard()Standardeinstellung.
Thomas Beauvais
9

Mit Java 8 können Sie auch die toString()Methode verwenden, java.time.Durationum es ohne externe Bibliotheken mit einer auf ISO 8601 Sekunden basierenden Darstellung wie PT8H6M12.345S zu formatieren.

Konrad Höffner
quelle
4
Beachten Sie, dass dies keine Tage druckt
Wim Deblauwe
56
Ich bin mir nicht sicher, ob dieses Format meiner Definition von hübschem Druck entspricht. :-)
james.garriss
@ james.garriss Um es etwas schöner zu machen, lesen Sie die Antwort von erickdeoliveiraleal.
Basil Bourque
7

So können Sie dies mit reinem JDK-Code tun:

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
quelle
7

org.threeten.extra.AmountFormats.wordBased

Das ThreeTen-Extra- Projekt, das von Stephen Colebourne, dem Autor von JSR 310, java.time und Joda-Time , verwaltet wird, verfügt über eine AmountFormatsKlasse, die mit den Standard-Java 8-Datumszeitklassen funktioniert. Es ist jedoch ziemlich ausführlich und bietet keine Option für eine kompaktere Ausgabe.

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
quelle
2
Wow, schön zu wissen, ich habe diese Funktion nicht gesehen. Hat jedoch einen großen Fehler: Das Oxford-Komma fehlt .
Basil Bourque
4
@BasilBourque Das Oxford-Komma ist typisch für die USA, aber interessanterweise nicht für Großbritannien. Meine lib Time4J (die eine weitaus bessere Internationalisierung aufweist) unterstützt diese Unterscheidung in der Klasse net.time4j.PrettyTime und ermöglicht auf Wunsch auch die Steuerung der kompakten Ausgabe.
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
quelle
Wie kommst du zu "toHoursPart" und höher?
Ghoti and Chips
4

Eine Alternative zum Builder-Ansatz von Joda-Time wäre eine musterbasierte Lösung . Dies wird von meiner Bibliothek Time4J angeboten . Beispiel mit der Klasse Duration.Formatter (einige Leerzeichen zur besseren Lesbarkeit hinzugefügt - das Entfernen der Leerzeichen ergibt den gewünschten C # -Stil):

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

Dieser Formatierer kann auch die Dauer von java.time-API drucken (die Normalisierungsfunktionen dieses Typs sind jedoch weniger leistungsfähig):

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

Die Erwartung des OP, dass "123456 ms so lang wie 4d1h3m5s gedruckt werden", wird offensichtlich falsch berechnet. Ich nehme Schlamperei als Grund an. Der oben definierte Dauerformatierer kann auch als Parser verwendet werden. Der folgende Code zeigt, dass "4d1h3m5s" eher entspricht 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

Eine andere Möglichkeit ist die Verwendung der Klasse net.time4j.PrettyTime(die auch für die lokalisierte Ausgabe und das Drucken relativer Zeiten wie "gestern", "nächsten Sonntag", "vor 4 Tagen" usw. geeignet ist):

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

Die Textbreite steuert, ob Abkürzungen verwendet werden oder nicht. Das Listenformat kann auch durch Auswahl des entsprechenden Gebietsschemas gesteuert werden. In Standard-Englisch wird beispielsweise das Oxform-Komma verwendet, in Großbritannien jedoch nicht. Die neueste Version 5.5 von Time4J unterstützt mehr als 90 Sprachen und verwendet Übersetzungen, die auf dem CLDR-Repository (einem Industriestandard) basieren.

Meno Hochschild
quelle
2
Diese PrettyTime ist viel besser als die in der anderen Antwort! Schade, dass ich das nicht zuerst gefunden habe.
ycomp
Ich weiß nicht, warum es jetzt zwei Abstimmungen für diese gut funktionierende Lösung gibt, deshalb habe ich beschlossen, die Antwort zu erweitern, indem ich weitere Beispiele auch zum Parsen (das Gegenteil von Formatierung) gebe und die falsche Erwartung des OP hervorhole.
Meno Hochschild
4

Eine Java 8- Version basierend auf der Antwort von 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()));
}

... da es in Java 8 keinen PeriodFormatter gibt und keine Methoden wie getHours, getMinutes, ...

Ich würde mich freuen, eine bessere Version für Java 8 zu sehen.

Torsten
quelle
wirft java.util.IllegalFormatConversionException: f! = java.lang.Long
erickdeoliveiraleal
mit Duration d1 = Duration.ofDays (0); d1 = d1.plusHours (47); d1 = d1.plusMinutes (124); d1 = d1.plusSeconds (124); System.out.println (String.format ("% s Tage und% 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 Vielen Dank für den Hinweis. Ich glaube, ich habe ursprünglich den Code für groovy geschrieben, also könnte es in dieser Sprache funktioniert haben. Ich habe es jetzt für Java korrigiert.
Torsten
3

Mir ist klar, dass dies möglicherweise nicht genau zu Ihrem Anwendungsfall passt, aber PrettyTime kann hier hilfreich sein.

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”
Nick
quelle
4
Ist es möglich, nur "10 Minuten" zu haben?
Johnny