Comment analyser RFC 3339 Datetimes avec Java?
-
14-11-2019 - |
Question
J'essaie d'analyser la date retournée en valeur du HTML5 datetime
champ de saisie. Essayez-le dans Opera pour voir un exemple. La date retournée ressemble à ceci: 2011-05-03T11:58:01Z
.
Je voudrais analyser cela dans une date Java ou un objet calendrier.
Idéalement, une solution devrait avoir les choses suivantes:
- Pas de bibliothèques externes (pots)
- Gère tous les formats RFC 3339 acceptables
- Une chaîne doit pouvoir être facilement validée pour voir s'il s'agit d'une date RFC 3339 valide
La solution
Je viens de trouver que Google a implémenté l'analyseur RFC3339 dans la bibliothèque client Google HTTP
Testé. Il fonctionne bien pour analyser les fragments de temps inférieurs aux secondes.
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import com.google.api.client.util.DateTime;
DateTimeFormatter formatter = DateTimeFormatter
.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
.withZone(ZoneId.of("UTC"));
@Test
public void test1e9Parse() {
String timeStr = "2018-04-03T11:32:26.553955473Z";
DateTime dateTime = DateTime.parseRfc3339(timeStr);
long millis = dateTime.getValue();
String result = formatter.format(new Date(millis).toInstant());
assert result.equals("2018-04-03T11:32:26.553Z");
}
@Test
public void test1e3Parse() {
String timeStr = "2018-04-03T11:32:26.553Z";
DateTime dateTime = DateTime.parseRfc3339(timeStr);
long millis = dateTime.getValue();
String result = formatter.format(new Date(millis).toInstant());
assert result.equals("2018-04-03T11:32:26.553Z");
}
@Test
public void testEpochSecondsParse() {
String timeStr = "2018-04-03T11:32:26Z";
DateTime dateTime = DateTime.parseRfc3339(timeStr);
long millis = dateTime.getValue();
String result = formatter.format(new Date(millis).toInstant());
assert result.equals("2018-04-03T11:32:26.000Z");
}
Autres conseils
tl; dr
Instant.parse( "2011-05-03T11:58:01Z" )
ISO 8601
Réellement, RFC 3339 n'est qu'un simple «profil» autoproclamé de la norme réelle, ISO 8601.
La RFC est différente en ce qu'elle viole délibérément ISO 8601 pour permettre un décalage négatif de zéro heures (-00:00
) et donne une signification sémantique de «décalage inconnu». Ce sémantique me semble être une très mauvaise idée. Je conseille de rester avec les règles ISO 8601 les plus sensibles. Dans ISO 8601, n'ayant pas de décalage du tout signifie que le décalage est inconnu - une signification évidente, tandis que la règle RFC est abstruse.
Le moderne Java.Time Les classes utilisent les formats ISO 8601 par défaut lors des chaînes d'analyse / génération.
Votre chaîne d'entrée représente un moment dans UTC. La Z
À la fin, c'est court pour Zulu
et signifie UTC.
Instant
(ne pas Date
)
La classe moderne Instant
représente un moment dans l'UTC. Cette classe remplace java.util.Date
, et utilise une résolution plus fine des nanosecondes plutôt que des millisecondes.
Instant instant = Instant.parse( "2011-05-03T11:58:01Z" ) ;
ZonedDateTime
(ne pas Calendar
)
Pour voir le même moment à travers le temps mural utilisé par les habitants d'une certaine région (un fuseau horaire), appliquez un ZoneId
Pour obtenir un ZonedDateTime
. Cette classe ZonedDateTime
remplace le java.util.Calendar
classer.
ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = instant.atZone( z ) ; // Same moment, same point on the timeline, different wall-clock time.
Conversion
Je recommande fortement d'éviter les cours de date d'heure hérités lorsque cela est possible. Mais si vous devez interopérer avec un ancien code non encore mis à jour Java.Time, vous pouvez convertir des allers-retours. Appelez de nouvelles méthodes ajoutées au Agé de Des classes.
Instant
remplace java.util.Date
.
java.util.Date myJUDate = java.util.Date.from( instant ) ; // From modern to legacy.
Instant instant = myJUDate.toInstant() ; // From legacy to modern.
ZonedDateTime
remplace GregorianCalendar
.
java.util.GregorianCalendar myGregCal = java.util.GregorianCalendar.from( zdt ) ; // From modern to legacy.
ZonedDateTime zdt = myGregCal.toZonedDateTime() ; // From legacy to modern.
Si tu as un java.util.Calendar
c'est en fait un GregorianCalendar
, moulage.
java.util.GregorianCalendar myGregCal = ( java.util.GregorianCalendar ) myCal ; // Cast to the concrete class.
ZonedDateTime zdt = myGregCal.toZonedDateTime() ; // From legacy to modern.
Préoccupations à puces
Concernant les problèmes spécifiques de votre question…
- Pas de bibliothèques externes (pots)
La Java.Time Les cours sont intégrés à Java 8, 9, 10 et plus tard. Une implémentation est également incluse dans Android ultérieure. Pour Java antérieure et Android plus tôt, consultez la section suivante de cette réponse.
- Gère tous les formats RFC 3339 acceptables
Les différents Java.Time Les cours gèrent chaque format ISO 8601 que je connais. Ils gèrent même des formats qui ont mystérieusement disparu des éditions ultérieures de la norme.
Pour d'autres formats, voir le parse
et toString
Méthodes des différentes classes telles que LocalDate
, OffsetDateTime
, etc. Aussi, recherchez la pile déborde comme il y a de nombreux Exemples et discussions sur ce sujet.
- Une chaîne doit pouvoir être facilement validée pour voir s'il s'agit d'une date RFC 3339 valide
Pour valider les chaînes d'entrée, piéger pour DateTimeParseException
.
try {
Instant instant = Instant.parse( "2011-05-03T11:58:01Z" ) ;
} catch ( DateTimeParseException e ) {
… handle invalid input
}
À propos de Java.Time
La Java.Time Le cadre est intégré à Java 8 et plus tard. Ces classes supplantent l'ancien gênant héritage des cours de date de date tels que java.util.Date
, Calendar
, & SimpleDateFormat
.
La Joda-temps Project, maintenant dans Mode de Maintenance, conseille la migration vers le Java.Time Des classes.
Pour en savoir plus, voir le Tutoriel Oracle. Et la recherche de pile déborde pour de nombreux exemples et explications. La spécification est JSR 310.
Vous pouvez échanger Java.Time objets directement avec votre base de données. Utiliser un Pilote JDBC conforme à JDBC 4.2 ou plus tard. Pas besoin de chaînes, pas besoin de java.sql.*
Des classes.
Où obtenir les classes java.time?
- Java SE 8, Java SE 9, et ensuite
- Intégré.
- Une partie de l'API Java standard avec une implémentation groupée.
- Java 9 ajoute quelques fonctionnalités et correctifs mineures.
- Java SE 6 et Java SE 7
- Une grande partie de la fonctionnalité Java. Treeten-Backport.
- Android
- Versions ultérieures des implémentations Android Bundle des classes Java.Time.
- Pour Android plus tôt (<26), le Trois ans adapts du projet Treeten-Backport (mentionné ci-dessus). Voir Comment utiliser Threetenabp….
La Threeten-Extra Le projet étend Java.time avec des classes supplémentaires. Ce projet est un terrain d'essai pour d'éventuels ajouts futurs à Java.Time. Vous pouvez trouver des cours utiles ici comme Interval
, YearWeek
, YearQuarter
, et Suite.
Donc, en principe, cela serait fait en utilisant différents Simpledateformat motifs.
Ici une liste de modèles pour le Déclarations individuelles dans RFC 3339:
- Date-Filhyear:
yyyy
- Date-mois:
MM
- DATE-MDAY:
dd
- Heure d'heure:
HH
- Temps -minue:
mm
- temps-temps:
ss
- Time-SecFrac:
.SSS
(S
signifie cependant la milliseconde - il n'est pas clair ce qui se passerait s'il y en a plus ou moins de 3 chiffres.) - Time-Numoffset: (comme
+02:00
semble ne pas être pris en charge - mais il prend en charge les formats+0200
,GMT+02:00
et quelques fuseaux horaires nommés en utilisantz
etZ
.) - décalage du temps:
'Z'
(ne pas prendre en charge d'autres fuseaux horaires) - vous devez utiliserformat.setTimezone(TimeZone.getTimeZone("UTC"))
avant de l'utiliser.) - temps partiel:
HH:mm:ss
ouHH:mm:ss.SSS
. - à plein temps:
HH:mm:ss'Z'
ouHH:mm:ss.SSS'Z'
. - date complète:
yyyy-MM-dd
- Date d'heure:
yyyy-MM-dd'T'HH:mm:ss'Z'
ouyyyy-MM-dd'T'HH:mm:ss.SSS'Z'
Comme nous pouvons le voir, cela ne semble pas être en mesure de tout analyser. Ce serait peut-être une meilleure idée de mettre en œuvre un RFC3339DateFormat
à partir de zéro (en utilisant des expressions régulières, pour la simplicité ou l'analyse à la main, pour l'efficacité).
Ici est une méthode simple pour le faire. Cela peut répondre à vos besoins.
Peut-être pas la manière la plus élégante, mais en travaillant certainement une que j'ai récemment faite:
Calendar cal = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss");
cal.setTime(sdf.parse(dateInString.replace("Z", "").replace("T", "-")));
Avec le format, vous avez, par exemple, 2011-05-03T11: 58: 01Z, le code ci-dessous fera l'affaire. Cependant, j'ai récemment essayé HTML5 Datetime dans Chrome et Opera, cela me donne 2011-05-03T11: 58Z -> Je n'ai pas la partie SS qui ne peut pas être gérée par le code ci-dessous.
new Timestamp(javax.xml.datatype.DatatypeFactory.newInstance().newXMLGregorianCalendar(date).toGregorianCalendar().getTimeInMillis());
Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").parse(datetimeInFRC3339format)