Comment analyser / formater des dates avec LocalDateTime? (Java 8)

341

Java 8 a ajouté une nouvelle API java.time pour travailler avec les dates et les heures ( JSR 310 ).

J'ai la date et l'heure sous forme de chaîne (par exemple "2014-04-08 12:30"). Comment puis-je obtenir une LocalDateTimeinstance de la chaîne donnée?

Après avoir fini de travailler avec l' LocalDateTimeobjet: comment puis-je reconvertir l' LocalDateTimeinstance en une chaîne au même format que celui illustré ci-dessus?

micha
la source
11
Pour info, la plupart des gens voudraient la plupart du temps un ZonedDateTimeplutôt qu'un LocalDateTime. Le nom est contre-intuitif; les Localmoyens toute localité en général plutôt qu'une zone de temps spécifique. En tant que tel, un LocalDateTimeobjet n'est pas lié à la chronologie. Pour avoir un sens, pour obtenir un moment précis sur la ligne de temps, vous devez appliquer un fuseau horaire.
Basil Bourque
Voir ma réponse pour une explication de LocalDateTimevs vs ZonedDateTimevs OffsetDateTimevs Instantvs LocalDatevs LocalTime, comment garder son calme sur pourquoi c'est si compliqué et comment le faire dès le premier coup.
Ondra Žižka
1
S'il n'était pas trop long, il LocalDateTimeaurait probablement été nommé ZonelessOffsetlessDateTime.
Ondra Žižka

Réponses:

534

Analyser la date et l'heure

Pour créer un LocalDateTimeobjet à partir d'une chaîne, vous pouvez utiliser la LocalDateTime.parse()méthode statique . Il prend une chaîne et un DateTimeFormatterparamètre as. Le DateTimeFormatterest utilisé pour spécifier le modèle de date / heure.

String str = "1986-04-08 12:30";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
LocalDateTime dateTime = LocalDateTime.parse(str, formatter);

Formatage de la date et de l'heure

Pour créer une chaîne formatée sur un LocalDateTimeobjet, vous pouvez utiliser la format()méthode.

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
LocalDateTime dateTime = LocalDateTime.of(1986, Month.APRIL, 8, 12, 30);
String formattedDateTime = dateTime.format(formatter); // "1986-04-08 12:30"

Notez qu'il existe des formats de date / heure couramment utilisés prédéfinis en tant que constantes dans DateTimeFormatter. Par exemple: utiliser DateTimeFormatter.ISO_DATE_TIMEpour formater l' LocalDateTimeinstance ci-dessus entraînerait la chaîne"1986-04-08T12:30:00" .

Les méthodes parse()et format()sont disponibles pour tous les objets liés à la date / heure (par exemple LocalDateou ZonedDateTime)

micha
la source
77
Juste pour noter que DateTimeFormatter est immuable et thread-safe, et donc l'approche recommandée est de le stocker dans une constante statique lorsque cela est possible.
JodaStephen
@micha et si j'ai "2016-12-31T07: 59: 00.000Z" cette date formatée?
Dawood Ahmed
14
@DawoodAbbasi tryDateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX")
Ray Hulha
1
@Loenix peut-être que c'est parce que vous essayez d'appeler format()la classe LocalDateTime au lieu de l'instance? Du moins, c'est ce que j'ai fait: je me suis confondu DateTimeavec dateTimedans l'exemple ci-dessus.
glaed
2
N'oubliez pas les majuscules sur MM
Wesos de Queso
159

Vous pouvez également utiliser LocalDate.parse()ou LocalDateTime.parse()sur un Stringsans lui fournir un motif, si le formatString est au format ISO-8601 .

par exemple,

String strDate = "2015-08-04";
LocalDate aLD = LocalDate.parse(strDate);
System.out.println("Date: " + aLD);

String strDatewithTime = "2015-08-04T10:11:30";
LocalDateTime aLDT = LocalDateTime.parse(strDatewithTime);
System.out.println("Date with Time: " + aLDT);

Sortie ,

Date: 2015-08-04
Date with Time: 2015-08-04T10:11:30

et à utiliser DateTimeFormatteruniquement si vous devez gérer d'autres modèles de date.

Par exemple, dans l'exemple suivant, jj MMM uuuu représente le jour du mois (deux chiffres), trois lettres du nom du mois (janvier, février, mars, ...) et une année à quatre chiffres:

DateTimeFormatter dTF = DateTimeFormatter.ofPattern("dd MMM uuuu");
String anotherDate = "04 Aug 2015";
LocalDate lds = LocalDate.parse(anotherDate, dTF);
System.out.println(anotherDate + " parses to " + lds);

Production

04 Aug 2015 parses to 2015-08-04

rappelez-vous également que l' DateTimeFormatterobjet est bidirectionnel; il peut à la fois analyser l'entrée et formater la sortie.

String strDate = "2015-08-04";
LocalDate aLD = LocalDate.parse(strDate);
DateTimeFormatter dTF = DateTimeFormatter.ofPattern("dd MMM uuuu");
System.out.println(aLD + " formats as " + dTF.format(aLD));

Production

2015-08-04 formats as 04 Aug 2015

(voir la liste complète des modèles de formatage et d'analyse de DateFormatter )

  Symbol  Meaning                     Presentation      Examples
  ------  -------                     ------------      -------
   G       era                         text              AD; Anno Domini; A
   u       year                        year              2004; 04
   y       year-of-era                 year              2004; 04
   D       day-of-year                 number            189
   M/L     month-of-year               number/text       7; 07; Jul; July; J
   d       day-of-month                number            10

   Q/q     quarter-of-year             number/text       3; 03; Q3; 3rd quarter
   Y       week-based-year             year              1996; 96
   w       week-of-week-based-year     number            27
   W       week-of-month               number            4
   E       day-of-week                 text              Tue; Tuesday; T
   e/c     localized day-of-week       number/text       2; 02; Tue; Tuesday; T
   F       week-of-month               number            3

   a       am-pm-of-day                text              PM
   h       clock-hour-of-am-pm (1-12)  number            12
   K       hour-of-am-pm (0-11)        number            0
   k       clock-hour-of-am-pm (1-24)  number            0

   H       hour-of-day (0-23)          number            0
   m       minute-of-hour              number            30
   s       second-of-minute            number            55
   S       fraction-of-second          fraction          978
   A       milli-of-day                number            1234
   n       nano-of-second              number            987654321
   N       nano-of-day                 number            1234000000

   V       time-zone ID                zone-id           America/Los_Angeles; Z; -08:30
   z       time-zone name              zone-name         Pacific Standard Time; PST
   O       localized zone-offset       offset-O          GMT+8; GMT+08:00; UTC-08:00;
   X       zone-offset 'Z' for zero    offset-X          Z; -08; -0830; -08:30; -083015; -08:30:15;
   x       zone-offset                 offset-x          +0000; -08; -0830; -08:30; -083015; -08:30:15;
   Z       zone-offset                 offset-Z          +0000; -0800; -08:00;

   p       pad next                    pad modifier      1

   '       escape for text             delimiter
   ''      single quote                literal           '
   [       optional section start
   ]       optional section end
   #       reserved for future use
   {       reserved for future use
   }       reserved for future use
Soufiyan Ghori
la source
11
Cette réponse a touché un sujet important: utilisez des formateurs prédéfinis dans la mesure du possible, par exemple NE PAS créer une base de formatage sur "aaaa-MM-jj", utilisez plutôt DateTimeFormatter.ISO_LOCAL_DATE. Cela rendra votre code beaucoup plus propre. De plus, essayez de maximiser l'utilisation du format ISO8061, cela rapportera des dividendes à long terme.
Christopher Yang
Je veux analyser une date pour la validation, 2018-08-09 12:00:08mais quand j'analyse, je vois un Tajout dont je n'ai pas besoin. Y a-t-il un moyen de le faire?
Raghuveer
@Raghuveer Le T n'est que le délimiteur ISO-8061 entre la date et l'heure. Si vous avez un espace dans votre format à la place, vous pouvez simplement utiliser le modèle yyyy-MM-dd hh:mm:sspour l'analyse et le formatage. Le T s'affichera toujours au format par défaut (ISO-8061), mais vous pouvez utiliser vos propres motifs.
Egor Hans
39

Les deux réponses ci-dessus expliquent très bien la question concernant les modèles de chaînes. Cependant, juste au cas où vous travaillez avec ISO 8601, il n'est pas nécessaire de l'appliquer DateTimeFormattercar LocalDateTime est déjà préparé pour cela:

Convertir LocalDateTime en chaîne de fuseau horaire ISO8601

LocalDateTime ldt = LocalDateTime.now(); 
ZonedDateTime zdt = ldt.atZone(ZoneOffset.UTC); //you might use a different zone
String iso8601 = zdt.toString();

Convertir de la chaîne ISO8601 en un LocalDateTime

String iso8601 = "2016-02-14T18:32:04.150Z";
ZonedDateTime zdt = ZonedDateTime.parse(iso8601);
LocalDateTime ldt = zdt.toLocalDateTime();
Marcio Jasinski
la source
20

Analyser une chaîne avec la date et l'heure dans un moment particulier (Java l'appelle un " Instant") est assez compliqué. Java a résolu ce problème en plusieurs itérations. Le dernier, java.timeet java.time.chrono, couvre presque tous les besoins (sauf Time Dilation :)).

Cependant, cette complexité apporte beaucoup de confusion.

La clé pour comprendre l'analyse de date est:

Pourquoi Java a-t-il tant de façons d'analyser une date

  1. Il existe plusieurs systèmes pour mesurer un temps. Par exemple, les calendriers historiques japonais ont été dérivés des plages de temps du règne de l'empereur ou de la dynastie respectifs. Ensuite, il y a par exemple l'horodatage UNIX. Heureusement, tout le monde (des affaires) a réussi à utiliser la même chose.
  2. Historiquement, les systèmes étaient passés de / à, pour diverses raisons . Par exemple, du calendrier julien au calendrier grégorien en 1582. Les dates «occidentales» antérieures doivent donc être traitées différemment.
  3. Et bien sûr, le changement ne s'est pas produit tout de suite. Parce que le calendrier provenait des quartiers généraux de certaines religions et d'autres parties de l'Europe croyaient en d'autres dieux, par exemple l'Allemagne n'a pas changé avant l'année 1700.

... et pourquoi le LocalDateTime, ZonedDateTimeet al. si compliqué

  1. Il y a fuseaux horaires . Un fuseau horaire est fondamentalement une "bande" * [1] de la surface de la Terre dont les autorités suivent les mêmes règles de quand a-t-elle quel décalage horaire. Cela inclut les règles de l'heure d'été.
    Les fuseaux horaires changent au fil du temps pour divers domaines, principalement en fonction de qui conquiert qui. Et les règles d'un fuseau horaire changent également au fil du temps .

  2. Il y a des décalages horaires. Ce n'est pas la même chose que les fuseaux horaires, car un fuseau horaire peut être par exemple "Prague", mais qui a un décalage d'heure d'été et un décalage d'heure d'hiver.
    Si vous obtenez un horodatage avec un fuseau horaire, le décalage peut varier, selon la partie de l'année dans laquelle il se trouve. Pendant l'heure bissextile, l'horodatage peut signifier 2 heures différentes, donc sans informations supplémentaires, il ne peut pas être fiable converti.
    Remarque: Par horodatage, je veux dire "une chaîne qui contient une date et / ou une heure, éventuellement avec un fuseau horaire et / ou un décalage horaire".

  3. Plusieurs fuseaux horaires peuvent partager le même décalage horaire pour certaines périodes. Par exemple, le fuseau horaire GMT / UTC est le même que le fuseau horaire "Londres" lorsque le décalage horaire d'été n'est pas en vigueur.

Pour le rendre un peu plus compliqué (mais ce n'est pas trop important pour votre cas d'utilisation):

  1. Les scientifiques observent la dynamique de la Terre, qui change avec le temps; sur cette base, ils ajoutent des secondes à la fin des années individuelles. (Alors2040-12-31 24:00:00 peut s'agir d'une date-heure valide.) Cela nécessite des mises à jour régulières des métadonnées que les systèmes utilisent pour que les conversions de date soient correctes. Par exemple, sous Linux, vous obtenez des mises à jour régulières des packages Java, y compris ces nouvelles données.
  2. Les mises à jour ne conservent pas toujours le comportement précédent pour les horodatages historiques et futurs. Il peut donc arriver que l'analyse des deux horodatages autour d'un changement de fuseau horaire les compare puisse donner des résultats différents lors de l'exécution sur différentes versions du logiciel. Cela s'applique également à la comparaison entre le fuseau horaire affecté et un autre fuseau horaire.

    Si cela provoque un bogue dans votre logiciel, envisagez d'utiliser un horodatage qui n'a pas de règles aussi compliquées, comme l' horodatage UNIX .

  3. À cause de 7, pour les dates futures, nous ne pouvons pas convertir les dates exactement avec certitude. Ainsi, par exemple, l'analyse actuelle de 8524-02-17 12:00:00peut être désactivée à quelques secondes de l'analyse future.

Les API de JDK pour cela ont évolué avec les besoins contemporains

  • Les premières versions de Java venaient java.util.Date une approche un peu naïve, en supposant qu'il n'y avait que l'année, le mois, le jour et l'heure. Cela n'a pas suffi rapidement.
  • De plus, les besoins des bases de données étaient différents, donc assez tôt, java.sql.Date été introduit, avec ses propres limites.
  • Parce que ni l'un ni l'autre ne couvrait bien différents calendriers et fuseaux horaires, l' CalendarAPI a été introduite.
  • Cela ne couvrait toujours pas la complexité des fuseaux horaires. Et pourtant, le mélange des API ci-dessus était vraiment difficile à travailler. Alors que les développeurs Java ont commencé à travailler sur des applications Web mondiales, les bibliothèques qui ciblaient la plupart des cas d'utilisation, comme JodaTime, sont rapidement devenues populaires. JodaTime était la norme de facto depuis environ une décennie.
  • Mais le JDK ne s'est pas intégré à JodaTime, donc travailler avec lui était un peu lourd. Ainsi, après une très longue discussion sur la façon d'aborder la question, JSR-310 a été créé principalement sur la base de JodaTime .

Comment y faire face dans Java java.time

Déterminer à quel type analyser un horodatage

Lorsque vous consommez une chaîne d'horodatage, vous devez savoir quelles informations elle contient. C'est le point crucial. Si vous n'obtenez pas ce droit, vous vous retrouvez avec des exceptions cryptiques comme "Impossible de créer instantanément" ou "Décalage de zone manquant" ou "ID de zone inconnue" etc.

Contient-il la date et l'heure?

  1. At-il un décalage horaire?
    Un décalage horaire est la +hh:mmpièce. Parfois, il +00:00peut être remplacé par Zle «temps zoulou», UTCle temps universel coordonné ou GMTle temps moyen de Greenwich. Celles-ci définissent également le fuseau horaire.
    Pour ces horodatages, vous utilisez OffsetDateTime.

  2. At-il un fuseau horaire?
    Pour ces horodatages, vous utilisez ZonedDateTime.
    La zone est spécifiée soit par

    • nom ("Prague", "Pacific Standard Time", "PST"), ou
    • "zone ID" ("America / Los_Angeles", "Europe / London"), représenté par java.time.ZoneId .

    La liste des fuseaux horaires est compilée par une "base de données TZ" , soutenue par l'ICAAN.

    Selon ZoneIdjavadoc, les identifiants de zone peuvent également être spécifiés en quelque sorte Zet offset. Je ne sais pas comment cela correspond aux zones réelles. Si l'horodatage, qui n'a qu'un TZ, tombe dans une heure bissextile de décalage horaire, alors il est ambigu et l'interprétation est sujette ResolverStyle, voir ci-dessous.

  3. S'il n'a ni l'un ni l'autre , alors le contexte manquant est supposé ou négligé. Et le consommateur doit décider. Il doit donc être analysé LocalDateTimeet converti en OffsetDateTimeen ajoutant les informations manquantes:

    • Vous pouvez supposer qu'il s'agit d'une heure UTC. Ajoutez le décalage UTC de 0 heure.
    • Vous pouvez supposer que c'est un moment de l'endroit où la conversion se produit. Convertissez-le en ajoutant le fuseau horaire du système.
    • Vous pouvez négliger et simplement l'utiliser tel quel. Cela est utile, par exemple, pour comparer ou soustraire deux fois (voir Duration), ou lorsque vous ne savez pas et que cela n'a pas vraiment d'importance (par exemple, les horaires des bus locaux).

Informations sur le temps partiel

  • D' après ce que l'horodatage contient, vous pouvez prendre LocalDate, LocalTime, OffsetTime, MonthDay, Yearou YearMonthhors de celui - ci.

Si vous avez les informations complètes, vous pouvez obtenir un java.time.Instant. Ceci est également utilisé en interne pour convertir entre OffsetDateTimeet ZonedDateTime.

Découvrez comment l'analyser

Il existe une documentation complète sur DateTimeFormatterlaquelle peut à la fois analyser une chaîne d'horodatage et formater en chaîne.

Les créé pré- DateTimeFormatters devraient couvrir plusmoins tous les formats d'horodatage standard. Par exemple, ISO_INSTANTpeut analyser 2011-12-03T10:15:30.123457Z.

Si vous avez un format spécial, vous pouvez créer votre propre DateTimeFormatter (qui est également un analyseur).

private static final DateTimeFormatter TIMESTAMP_PARSER = new DateTimeFormatterBuilder()
   .parseCaseInsensitive()
   .append(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SX"))
   .toFormatter();

Je vous recommande de regarder le code source de DateTimeFormatteret de vous inspirer sur la façon d'en créer un en utilisant DateTimeFormatterBuilder. Pendant que vous y êtes, regardez également ResolverStylequi contrôle si l'analyseur est LENIENT, SMART ou STRICT pour les formats et les informations ambiguës.

TemporalAccessor

Maintenant, l'erreur fréquente est d'entrer dans la complexité de TemporalAccessor. Cela vient de la façon dont les développeurs étaient habitués à travailler SimpleDateFormatter.parse(String). D'accord, DateTimeFormatter.parse("...")vous donne TemporalAccessor.

// No need for this!
TemporalAccessor ta = TIMESTAMP_PARSER.parse("2011-... etc");

Mais, équipé des connaissances de la section précédente, vous pouvez facilement analyser le type dont vous avez besoin:

OffsetDateTime myTimestamp = OffsetDateTime.parse("2011-12-03T10:15:30.123457Z", TIMESTAMP_PARSER);

Vous n'avez en fait pas besoin DateTimeFormatternon plus. Les types que vous souhaitez analyser ont les parse(String)méthodes.

OffsetDateTime myTimestamp = OffsetDateTime.parse("2011-12-03T10:15:30.123457Z");

En ce qui concerne TemporalAccessor, vous pouvez l'utiliser si vous avez une vague idée des informations contenues dans la chaîne et souhaitez décider au moment de l'exécution.

J'espère avoir jeté une lumière de compréhension sur votre âme :)

Remarque: il existe un backport de java.timeJava 6 et 7: ThreeTen-Backport . Pour Android, il a ThreeTenABP .

[1] Non seulement ce ne sont pas des rayures, mais il y a aussi des extrêmes étranges. Par exemple, certaines îles voisines du Pacifique ont des fuseaux horaires +14: 00 et -11: 00. Cela signifie que si sur une île, il y a le 1er mai 15h, sur une autre île pas si loin, c'est toujours le 30 avril 12h (si j'ai bien compté :))

Ondra Žižka
la source
3

OBTENEZ LE TEMPS UTC ACTUEL DANS LE FORMAT REQUIS

// Current UTC time
        OffsetDateTime utc = OffsetDateTime.now(ZoneOffset.UTC);

        // GET LocalDateTime 
        LocalDateTime localDateTime = utc.toLocalDateTime();
        System.out.println("*************" + localDateTime);

        // formated UTC time
        DateTimeFormatter dTF = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
        System.out.println(" formats as " + dTF.format(localDateTime));

        //GET UTC time for current date
        Date now= new Date();
        LocalDateTime utcDateTimeForCurrentDateTime = Instant.ofEpochMilli(now.getTime()).atZone(ZoneId.of("UTC")).toLocalDateTime();
        DateTimeFormatter dTF2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
        System.out.println(" formats as " + dTF2.format(utcDateTimeForCurrentDateTime));
Santhosh Hirekerur
la source
0

J'ai trouvé cela merveilleux de couvrir plusieurs variantes de format de date et heure comme ceci:

final DateTimeFormatterBuilder dtfb = new DateTimeFormatterBuilder();
dtfb.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSS"))
    .appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"))
    .appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SS"))
    .appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.S"))
    .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
    .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
    .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0);
flowgrad
la source
1
`` `public final static DateTimeFormatter TIMESTAMP_XX = new DateTimeFormatterBuilder (). appendPattern (" [[uuuu] [- MM] [- dd]] [[HH] [: mm] [: ss] [. SSS]] "). parseDefaulting (ChronoField.YEAR, 2020) .parseDefaulting (ChronoField.MONTH_OF_YEAR, 1) .parseDefaulting (ChronoField.DAY_OF_MONTH, 1) .parseDefaulting (ChronoField.HOUR_OF_DAY, 0) .parseDefaultHO_FDOF , 0) .parseDefaulting (ChronoField.NANO_OF_SECOND, 0) .toFormatter (); ``
Alan Stewart