Come posso "stampare abbastanza" una durata in Java?


93

Qualcuno sa di una libreria Java che può stampare un numero in millisecondi nello stesso modo in cui lo fa C #?

Ad esempio, 123456 ms come long verrebbe stampato come 4d1h3m5s.


1
Cordiali saluti, il formato che sembri descrivere è Durationdefinito nello standard ragionevole, ISO 8601 : PnYnMnDTnHnMnSdove Psignifica "Periodo" e segna l'inizio, Tsepara la parte della data dalla parte dell'ora e in mezzo ci sono occorrenze opzionali di un numero con un singolo -abbreviazione di lettere. Ad esempio, PT4H30M= quattro ore e mezza.
Basil Bourque

1
Se tutto il resto fallisce, è molto semplice farlo da soli. Basta usare applicazioni successive di %e /per dividere il numero in parti. Quasi più facile di alcune delle risposte proposte.
Hot Licks

@HotLicks Lascia ai metodi di libreria per un codice molto più pulito e chiaro rispetto all'utilizzo di /e %.
Ole VV

Sì, negli 8 anni trascorsi da quando ho posto questa domanda (!) Sono passato a joda time che si adatta molto bene al mio caso d'uso
phatmanace

@phatmanace Il progetto Joda-Time è ora in modalità di manutenzione e consiglia la migrazione alle classi java.time integrate in Java 8 e versioni successive.
Basil Bourque

Risposte:


91

Joda Time ha un ottimo modo per farlo usando un PeriodFormatterBuilder .

Vincita veloce: PeriodFormat.getDefault().print(duration.toPeriod());

per esempio

//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
Da una risposta di seguito risulta che un'istanza di Period può essere creata direttamente, senza prima creare un'istanza di Duration e poi convertirla in Period. Es. Period period = new Period (millis); Stringa formattata = formatter.print (punto);
Basil Vandegriend

7
Attenzione a questa conversione "duration.toPeriod ()". Se la durata è abbastanza grande, la porzione del giorno in poi rimarrà uguale a 0. La porzione delle ore continuerà a crescere. Otterrai 25h10m23s ma non otterrai mai la "d". Il motivo è che non esiste un modo completamente corretto per convertire le ore in giorni nel modo rigoroso di Joda. Nella maggior parte dei casi, se si confrontano due istanti e si desidera stamparli, è possibile eseguire un nuovo periodo (t1, t2) invece di una nuova durata (t1, t2) .toPeriod ().
Boon

@Boon sai come convertire la durata in periodo rispetto ai giorni? Uso la durata dalla quale posso sostituire il secondo nel mio timer. L'impostazione dell'evento PeriodType.daysTime()o .standard()non ha aiutato
murt

4
@murt Se vuoi davvero e puoi accettare la definizione standard di un giorno, puoi "formatter.print (duration.toPeriod (). normalizedStandard ())" nel codice sopra. Divertiti!
Boon

74

Ho costruito una soluzione semplice, usando Java 8 Duration.toString()e un po 'di regex:

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

Il risultato sarà simile a:

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

Se non vuoi spazi tra di loro, rimuovili replaceAll.


6
Non dovresti mai fare affidamento sull'output di toStrong () perché questo potrebbe cambiare in ulteriori versioni.
Weltraumschaf

14
@Weltraumschaf non è probabile che cambi, dal momento che è specificato nel javadoc
aditsu esci perché SE è EVIL

9
@Weltraumschaf È improbabile che cambi in quanto l'output di Duration::toStringè formattato secondo lo standard ISO 8601 ben definito e ben utilizzato.
Basil Bourque

1
Se non vuoi frazioni, aggiungi .replaceAll("\\.\\d+", "")prima della chiamata a toLowerCase().
zb226

1
@Weltraumschaf Il tuo punto in generale è corretto e io stesso non farei affidamento sull'output toString () della maggior parte delle classi. Ma in questo caso molto specifico, la definizione del metodo afferma che il suo output segue lo standard ISO-8601, come puoi vedere nel Javadoc del metodo . Quindi non è un dettaglio di implementazione come nella maggior parte delle classi, quindi non è qualcosa che mi aspetterei che un linguaggio maturo come Java cambi in questo modo,
lucasls

13

Apache commons-lang fornisce una classe utile per ottenere questo risultato anche DurationFormatUtils

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


9

JodaTime ha una Periodclasse che può rappresentare tali quantità e può essere renderizzata (tramite IsoPeriodFormat) in formato ISO8601 , ad PT4D1H3M5Ses.

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

Se il formato non è quello desiderato, è PeriodFormatterBuilderpossibile assemblare layout arbitrari, incluso lo stile C # 4d1h3m5s.


3
Proprio come una nota, new Period(millis).toString()utilizza ISOPeriodFormat.standard()per impostazione predefinita.
Thomas Beauvais


7

Ecco come puoi farlo usando il codice JDK puro:

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

Il progetto ThreeTen-Extra , gestito da Stephen Colebourne, l'autore di JSR 310, java.time e Joda-Time , ha una AmountFormatsclasse che funziona con le classi di data e ora standard di Java 8. Tuttavia, è abbastanza prolisso, senza opzioni per un output più compatto.

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, bello sapere, non ho visto quella caratteristica. Ha però un grosso difetto: manca la virgola di Oxford .
Basil Bourque

4
@BasilBourque La virgola di Oxford è tipica degli Stati Uniti, ma, cosa interessante, non del Regno Unito. La mia lib Time4J (che ha una internazionalizzazione di gran lunga migliore) supporta questa distinzione nella classe net.time4j.PrettyTime e consente anche il controllo sull'output compatto, se lo si desidera.
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 giorni 1h 6m 4s


Come stai ottenendo "toHoursPart" e versioni successive?
Ghoti and Chips

4

Un'alternativa all'approccio builder di Joda-Time sarebbe una soluzione basata su modelli . Questo è offerto dalla mia libreria Time4J . Esempio di utilizzo della classe Duration.Formatter (aggiunti alcuni spazi per una maggiore leggibilità - la rimozione degli spazi produrrà lo stile C # desiderato):

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

Questo formattatore può anche stampare durate di java.time-API (tuttavia, le funzionalità di normalizzazione di quel tipo sono meno potenti):

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

L'aspettativa dell'OP che "123456 ms come long sarebbe stampato come 4d1h3m5s" è calcolata in modo ovviamente sbagliato. Presumo la sciatteria come ragione. Lo stesso formattatore di durata definito sopra può essere utilizzato anche come parser. Il codice seguente mostra che "4d1h3m5s" corrisponde piuttosto a 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

Un altro modo è usare la classe net.time4j.PrettyTime(che è utile anche per l'output localizzato e la stampa di tempi relativi come "ieri", "domenica prossima", "4 giorni fa" ecc.):

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

La larghezza del testo controlla se le abbreviazioni vengono utilizzate o meno. Anche il formato dell'elenco può essere controllato scegliendo la lingua appropriata. Ad esempio, l'inglese standard utilizza la virgola Oxform, mentre il Regno Unito no. L'ultima versione v5.5 di Time4J supporta più di 90 lingue e utilizza traduzioni basate sul repository CLDR (uno standard del settore).


2
questo PrettyTime è molto meglio di quello nell'altra risposta! peccato non averlo trovato prima.
ycomp

Non so perché ora ci siano due voti negativi per questa soluzione ben funzionante, quindi ho deciso di estendere la risposta fornendo più esempi anche sull'analisi (l'opposto della formattazione) e per evidenziare l'aspettativa sbagliata dell'OP.
Meno Hochschild,

4

Una versione Java 8 basata sulla risposta di 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()));
}

... poiché non c'è PeriodFormatter in Java 8 e nessun metodo come getHours, getMinutes, ...

Sarei felice di vedere una versione migliore per Java 8.


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

con Durata d1 = Durata.diGiorni (0); d1 = d1.plusHours (47); d1 = d1.plusMinutes (124); d1 = d1.plusSeconds (124); System.out.println (String.format ("% s days and% 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 Grazie per averlo sottolineato. Penso che originariamente stavo scrivendo il codice per Groovy, quindi potrebbe aver funzionato in quella lingua. L'ho corretto per Java ora.
Torsten

3

Mi rendo conto che questo potrebbe non adattarsi esattamente al tuo caso d'uso, ma PrettyTime potrebbe essere utile qui.

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
È possibile avere solo "10 minuti"?
Johnny
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.