Comment puis-je «joliment imprimer» une durée en Java?

93

Est-ce que quelqu'un connaît une bibliothèque Java qui peut assez imprimer un nombre en millisecondes de la même manière que C #?

Par exemple, 123456 ms en tant que long serait imprimé sous la forme 4d1h3m5s.

phatmanace
la source
1
Pour info, le format que vous semblez décrire est Durationdéfini dans la norme sensible, ISO 8601 : PnYnMnDTnHnMnSPsignifie "Période" et marque le début, Tsépare la partie date de la partie heure, et entre les occurrences facultatives d'un nombre avec un seul -abréviation de lettre. Par exemple, PT4H30M= quatre heures et demie.
Basil Bourque
1
Si tout le reste échoue, il est très simple de le faire vous-même. Utilisez simplement des applications successives de %et /pour diviser le nombre en parties. Presque plus facile que certaines des réponses proposées.
Hot Licks
@HotLicks Laissez-le aux méthodes de bibliothèque pour un code beaucoup plus propre et plus clair que d'utiliser /et %.
Ole VV
Oui, au cours des 8 années écoulées depuis que j'ai posé cette question (!), Je suis passé à joda time qui convient très bien à mon cas d'utilisation
phatmanace
@phatmanace Le projet Joda-Time est maintenant en mode maintenance et conseille la migration vers les classes java.time intégrées à Java 8 et versions ultérieures.
Basil Bourque

Réponses:

91

Heure Joda a un très bon moyen de le faire en utilisant un PeriodFormatterBuilder .

Victoire rapide: PeriodFormat.getDefault().print(duration.toPeriod());

par exemple

//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
la source
2
Il ressort de la réponse ci-dessous qu'une instance de Period peut être créée directement, sans créer d'abord une instance de Duration, puis la convertir en Period. Par exemple, période période = nouvelle période (millisecondes); Chaîne formatée = formatter.print (point);
Basil Vandegriend
7
Méfiez-vous de cette conversion "duration.toPeriod ()". Si la durée est assez grande, la portion du jour restera à 0. La portion des heures continuera de croître. Vous obtiendrez 25h10m23s mais jamais le "d". La raison en est qu'il n'y a pas de moyen totalement correct de convertir des heures en jours de la manière stricte de Joda. Dans la plupart des cas, si vous comparez deux instants et que vous souhaitez l'imprimer, vous pouvez faire une nouvelle période (t1, t2) au lieu d'une nouvelle durée (t1, t2) .toPeriod ().
Boon le
@Boon savez-vous comment convertir la durée en période par rapport aux jours? J'utilise la durée à partir de laquelle je peux remplacer la seconde dans mon sur ma minuterie. Réglage de l'événement PeriodType.daysTime()ou .standard()n'a pas aidé
murt
4
@murt Si vous voulez vraiment, et pouvez accepter la définition standard d'un jour, alors vous pouvez "formatter.print (duration.toPeriod (). normalizedStandard ())" dans le code ci-dessus. S'amuser!
Boon
74

J'ai construit une solution simple, en utilisant Java 8 Duration.toString()et un peu de regex:

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

Le résultat ressemblera à:

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

Si vous ne voulez pas d'espaces entre les deux, supprimez simplement replaceAll.

lucasls
la source
6
Vous ne devez jamais vous fier à la sortie de toStrong () car cela peut changer dans d'autres versions.
Weltraumschaf
14
@Weltraumschaf il est peu probable que cela change, car il est spécifié dans le javadoc
aditsu quit parce que SE est EVIL
9
@Weltraumschaf Peu susceptible de changer car la sortie de Duration::toStringest formatée selon la norme ISO 8601 bien définie et bien utilisée .
Basil Bourque
1
Si vous ne voulez pas de fractions, ajoutez .replaceAll("\\.\\d+", "")avant l'appel à toLowerCase().
zb226
1
@Weltraumschaf Votre point de vue est généralement correct et je ne me fierais pas moi-même à la sortie toString () de la plupart des classes. Mais dans ce cas très spécifique, la définition de la méthode indique que sa sortie suit la norme ISO-8601, comme vous pouvez le voir dans le Javadoc de la méthode . Donc ce n'est pas un détail d'implémentation comme dans la plupart des classes, donc pas quelque chose que je m'attendrais à ce qu'un langage aussi mature que Java change comme ça,
lucasls
13

Apache commons-lang fournit une classe utile pour y parvenir également DurationFormatUtils

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

noleto
la source
9

JodaTime a une Periodclasse qui peut représenter de telles quantités, et peut être rendue (via IsoPeriodFormat) au format ISO8601 , par PT4D1H3M5Sexemple

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

Si ce format n'est pas celui que vous souhaitez, PeriodFormatterBuildervous permet d'assembler des mises en page arbitraires, y compris votre style C # 4d1h3m5s.

skaffman
la source
3
Juste comme note, new Period(millis).toString()utilise le ISOPeriodFormat.standard()par défaut.
Thomas Beauvais
9

Avec Java 8, vous pouvez également utiliser la toString()méthode de java.time.Durationpour le formater sans bibliothèques externes en utilisant une représentation basée sur ISO 8601 secondes telle que PT8H6M12.345S.

Konrad Höffner
la source
4
Notez que cela n'imprime pas les jours
Wim Deblauwe
58
Je ne suis pas sûr que ce format corresponde à ma définition du joli imprimé. :-)
james.garriss
@ james.garriss Pour le rendre un peu plus joli, voir la réponse de erickdeoliveiraleal.
Basil Bourque
7

Voici comment vous pouvez le faire en utilisant du code JDK pur:

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
la source
7

org.threeten.extra.AmountFormats.wordBased

Le projet ThreeTen-Extra , qui est maintenu par Stephen Colebourne, l'auteur de JSR 310, java.time et Joda-Time , a une AmountFormatsclasse qui fonctionne avec les classes de date standard Java 8. C'est assez verbeux cependant, sans option pour une sortie plus compacte.

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
la source
2
Wow, bon à savoir, je n'ai pas vu cette fonctionnalité. A un défaut majeur cependant: il manque la virgule d'Oxford .
Basil Bourque
4
@BasilBourque La virgule d'Oxford est typique des États-Unis, mais curieusement pas pour le Royaume-Uni. Ma lib Time4J (qui a une bien meilleure internationalisation) prend en charge cette distinction dans la classe net.time4j.PrettyTime et permet également de contrôler la sortie compacte si vous le souhaitez.
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 j 1h 6m 4s

érickdéoliveiraleal
la source
Comment obtenez-vous "toHoursPart" et plus?
Ghoti and Chips
4

Une alternative à l'approche constructeur de Joda-Time serait une solution basée sur des modèles . Ceci est offert par ma bibliothèque Time4J . Exemple utilisant la classe Duration.Formatter (ajout de quelques espaces pour plus de lisibilité - la suppression des espaces donnera le style C # souhaité):

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

Ce formateur peut également imprimer des durées de java.time-API (cependant, les fonctionnalités de normalisation de ce type sont moins puissantes):

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

L'espoir de l'OP selon lequel "123456 ms en tant que long serait imprimé comme 4d1h3m5s" est calculé d'une manière manifestement erronée. Je suppose que la négligence est une raison. Le même formateur de durée défini ci-dessus peut également être utilisé comme analyseur. Le code suivant montre que "4d1h3m5s" correspond plutôt à 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

Une autre méthode consiste à utiliser la classe net.time4j.PrettyTime(qui est également utile pour la sortie localisée et l'impression des heures relatives telles que "hier", "dimanche prochain", "il y a 4 jours", etc.):

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 largeur du texte contrôle si les abréviations sont utilisées ou non. Le format de la liste peut également être contrôlé en choisissant les paramètres régionaux appropriés. Par exemple, l'anglais standard utilise la virgule Oxform, contrairement au Royaume-Uni. La dernière version v5.5 de Time4J prend en charge plus de 90 langues et utilise des traductions basées sur le référentiel CLDR (une norme de l'industrie).

Meno Hochschild
la source
2
ce PrettyTime est bien meilleur que celui de l'autre réponse! dommage que je ne l'ai pas trouvé en premier.
ycomp
Je ne sais pas pourquoi il y a maintenant deux votes négatifs pour cette solution qui fonctionne bien, j'ai donc décidé d'étendre la réponse en donnant plus d'exemples également sur l'analyse (le contraire du formatage) et de mettre en évidence la mauvaise attente de l'OP.
Meno Hochschild le
4

Une version Java 8 basée sur la réponse de 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()));
}

... car il n'y a pas de PeriodFormatter dans Java 8 et aucune méthode comme getHours, getMinutes, ...

Je serais heureux de voir une meilleure version pour Java 8.

Torsten
la source
lance java.util.IllegalFormatConversionException: f! = java.lang.Long
erickdeoliveiraleal
avec Durée d1 = Durée. des jours (0); d1 = d1.plusHours (47); d1 = d1.plusMinutes (124); d1 = d1.plusSeconds (124); System.out.println (String.format ("% s jours et% 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 Merci de l'avoir signalé. Je pense que j'écrivais à l'origine le code de groovy, donc cela aurait pu fonctionner dans ce langage. Je l'ai corrigé pour java maintenant.
Torsten
3

Je me rends compte que cela peut ne pas correspondre exactement à votre cas d'utilisation, mais PrettyTime peut être utile ici.

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”
pseudo
la source
4
Est-il possible de n'avoir que "10 minutes"?
Johnny