J'essaye de convertir un ISO 8601 chaîne formatée en un Java.util.Date
.
J'ai trouvé que le modèle yyyy-MM-dd'T'HH:mm:ssZ
était conforme à ISO8601 s'il était utilisé avec un paramètre régional (exemple de comparaison).
Cependant, avec le Java.text.SimpleDateFormat
, je ne peux pas convertir la chaîne correctement formatée 2010-01-01T12:00:00+01:00
. Je dois d'abord le convertir en 2010-01-01T12:00:00+0100
, sans les deux points.
Donc, la solution actuelle est
SimpleDateFormat ISO8601DATEFORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.GERMANY);
String date = "2010-01-01T12:00:00+01:00".replaceAll("\\+0([0-9]){1}\\:00", "+0$100");
System.out.println(ISO8601DATEFORMAT.parse(date));
ce qui n'est évidemment pas Nice. Est-ce que je manque quelque chose ou y a-t-il une meilleure solution?
Réponse
Grâce au commentaire de JuanZe, j'ai trouvé la magie Joda-Time , c'est aussi décrit ici .
Donc, la solution est
DateTimeFormatter parser2 = ISODateTimeFormat.dateTimeNoMillis();
String jtdate = "2010-01-01T12:00:00+01:00";
System.out.println(parser2.parseDateTime(jtdate));
Ou plus simplement, utilisez l'analyseur par défaut via le constructeur:
DateTime dt = new DateTime( "2010-01-01T12:00:00+01:00" ) ;
Pour moi, c'est Nice.
Malheureusement, les formats de fuseau horaire disponibles pour SimpleDateFormat (Java 6 et versions antérieures) ne sont pas ISO 8601 . SimpleDateFormat comprend les chaînes de fuseau horaire telles que "GMT + 01: 00" ou "+0100", ce dernier étant conforme à RFC # 822 .
Même si Java 7 a pris en charge les descripteurs de fuseau horaire conformément à ISO 8601, SimpleDateFormat n'est toujours pas en mesure d'analyser correctement une chaîne de date complète, car il ne prend pas en charge les éléments facultatifs.
Reformater votre chaîne d'entrée à l'aide de regexp est certainement une possibilité, mais les règles de remplacement ne sont pas aussi simples que dans votre question:
La solution la plus simple consiste éventuellement à utiliser le convertisseur de type de données dans JAXB, car JAXB doit pouvoir analyser la chaîne de date ISO8601 conformément à la spécification de schéma XML. javax.xml.bind.DatatypeConverter.parseDateTime("2010-01-01T12:00:00Z")
vous donnera un objet Calendar
sur lequel vous pouvez simplement utiliser getTime (), si vous avez besoin d'un objet Date
.
Vous pourriez probablement utiliser Joda-Time également, mais je ne sais pas pourquoi vous devriez vous en préoccuper.
La façon dont cela est béni par Java 7 documentation :
DateFormat df1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
String string1 = "2001-07-04T12:08:56.235-0700";
Date result1 = df1.parse(string1);
DateFormat df2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
String string2 = "2001-07-04T12:08:56.235-07:00";
Date result2 = df2.parse(string2);
Vous pouvez trouver plus d'exemples dans la section . Exemples dans SimpleDateFormat javadoc .
D'accord, cette question a déjà reçu une réponse, mais je vais laisser tomber ma réponse quand même. Cela pourrait aider quelqu'un.
Je cherchais une solution pour Android (API 7).
javax.xml
ne fonctionneront pas sur Android API 7.Nous avons fini par implémenter cette classe simple. Il couvre uniquement la forme la plus courante des chaînes ISO 8601, mais cela devrait suffire dans certains cas (lorsque vous êtes certain que l'entrée sera dans ce format).
import Java.text.ParseException;
import Java.text.SimpleDateFormat;
import Java.util.Calendar;
import Java.util.Date;
import Java.util.GregorianCalendar;
/**
* Helper class for handling a most common subset of ISO 8601 strings
* (in the following format: "2008-03-01T13:00:00+01:00"). It supports
* parsing the "Z" timezone, but many other less-used features are
* missing.
*/
public final class ISO8601 {
/** Transform Calendar to ISO 8601 string. */
public static String fromCalendar(final Calendar calendar) {
Date date = calendar.getTime();
String formatted = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
.format(date);
return formatted.substring(0, 22) + ":" + formatted.substring(22);
}
/** Get current date and time formatted as ISO 8601 string. */
public static String now() {
return fromCalendar(GregorianCalendar.getInstance());
}
/** Transform ISO 8601 string to Calendar. */
public static Calendar toCalendar(final String iso8601string)
throws ParseException {
Calendar calendar = GregorianCalendar.getInstance();
String s = iso8601string.replace("Z", "+00:00");
try {
s = s.substring(0, 22) + s.substring(23); // to get rid of the ":"
} catch (IndexOutOfBoundsException e) {
throw new ParseException("Invalid length", 0);
}
Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").parse(s);
calendar.setTime(date);
return calendar;
}
}
Note de performance: J'instancie un nouveau SimpleDateFormat chaque fois afin d'éviter n bogue dans Android 2.1. Si vous êtes aussi étonné que moi, voyez cette énigme . Pour les autres moteurs Java, vous pouvez mettre l'instance en cache dans un champ statique privé (à l'aide de ThreadLocal, pour être thread-safe).
L'API Java.time (intégrée à Java 8 et versions ultérieures) facilite un peu la tâche.
Si vous savez que l'entrée est dans UTC , telle que le Z
(pour le zoulou) à la fin, le Instant
classe peut analyser.
_Java.util.Date date = Date.from( Instant.parse( "2014-12-12T10:39:40Z" ));
_
Si votre entrée peut être une autre valeur décalée par rapport à UTC plutôt que UTC indiqué par Z
(Zulu) à la fin, utilisez la classe OffsetDateTime
à analyser.
_OffsetDateTime odt = OffsetDateTime.parse( "2010-01-01T12:00:00+01:00" );
_
Extrayez ensuite un Instant
et convertissez-le en un Java.util.Date
en appelant from
.
_Instant instant = odt.toInstant(); // Instant is always in UTC.
Java.util.Date date = Java.util.Date.from( instant );
_
La bibliothèque Jackson-databind a aussi classe ISO8601DateFormat qui le fait (implémentation réelle dans ISO8601Utils .
ISO8601DateFormat df = new ISO8601DateFormat();
Date d = df.parse("2010-07-28T22:25:51Z");
_OffsetDateTime.parse ( "2010-01-01T12:00:00+01:00" )
_
Le nouveau package [Java.time] dans Java 8 et versions ultérieures a été inspiré par Joda-Time.
La classe OffsetDateTime
représente un moment sur la timeline avec un offset-à-UTC mais pas un fuseau horaire.
_OffsetDateTime odt = OffsetDateTime.parse ( "2010-01-01T12:00:00+01:00" );
_
L'appel de toString
génère une chaîne au format standard ISO 8601:
2010-01-01T12: 00 + 01: 00
Pour voir la même valeur à travers l'objectif de l'UTC, extrayez un Instant
ou ajustez le décalage de _+01:00
_ à _00:00
_.
_Instant instant = odt.toInstant();
_
…ou…
_OffsetDateTime odtUtc = odt.withOffsetSameInstant( ZoneOffset.UTC );
_
Ajustez dans un fuseau horaire si vous le souhaitez. Un fuseau horaire est un historique des valeurs offset-à-UTC d'une région, avec un ensemble de règles de traitement des anomalies telles que l'heure d'été (DST). Donc, appliquez un fuseau horaire plutôt qu'un simple décalage chaque fois que cela est possible.
_ZonedDateTime zonedDateTimeMontréal = odt.atZoneSameInstant( ZoneId.of( "America/Montreal" ) );
_
Le cadre Java.time est intégré à Java 8 et versions ultérieures. Ces classes supplantent les anciennes classes gênantes legacy date-heure telles que Java.util.Date
, Calendar
, & SimpleDateFormat
=.
Le projet Joda-Time , actuellement en mode maintenance , conseille la migration vers les classes Java.time .
Pour en savoir plus, consultez le Oracle Tutorial . Et recherchez Stack Overflow pour de nombreux exemples et explications. La spécification est JSR 31 .
Vous pouvez échanger Java.time objets directement avec votre base de données. Utilisez un pilote JDBC compatible avec JDBC 4.2 ou une version ultérieure. Pas besoin de chaînes, pas besoin de _Java.sql.*
_ classes.
Où obtenir les classes Java.time?
Le projet ThreeTen-Extra étend Java.time avec des classes supplémentaires. Ce projet est un terrain d’essai pour d’éventuels ajouts à Java.time. Vous pouvez trouver ici quelques classes utiles telles que Interval
, YearWeek
, YearQuarter
, et plus .
Pour Java version 7
Vous pouvez suivre la documentation Oracle: http://docs.Oracle.com/javase/7/docs/api/Java/text/SimpleDateFormat.html
X - est utilisé pour le fuseau horaire ISO 8601
TimeZone tz = TimeZone.getTimeZone("UTC");
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
df.setTimeZone(tz);
String nowAsISO = df.format(new Date());
System.out.println(nowAsISO);
DateFormat df1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
//nowAsISO = "2013-05-31T00:00:00Z";
Date finalResult = df1.parse(nowAsISO);
System.out.println(finalResult);
La solution DatatypeConverter ne fonctionne pas sur tous les ordinateurs virtuels. Ce qui suit fonctionne pour moi:
javax.xml.datatype.DatatypeFactory.newInstance().newXMLGregorianCalendar("2011-01-01Z").toGregorianCalendar().getTime()
J'ai constaté que joda ne fonctionnait pas immédiatement (particulièrement pour l'exemple que j'ai donné ci-dessus avec le fuseau horaire à une date qui devrait être valide)
Je pense que nous devrions utiliser
DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
pour Date 2010-01-01T12:00:00Z
Un autre moyen très simple d’analyser les horodatages ISO8601 consiste à utiliser org.Apache.commons.lang.time.DateUtils
:
import static org.junit.Assert.assertEquals;
import Java.text.ParseException;
import Java.util.Date;
import org.Apache.commons.lang.time.DateUtils;
import org.junit.Test;
public class ISO8601TimestampFormatTest {
@Test
public void parse() throws ParseException {
Date date = DateUtils.parseDate("2010-01-01T12:00:00+01:00", new String[]{ "yyyy-MM-dd'T'HH:mm:ssZZ" });
assertEquals("Fri Jan 01 12:00:00 CET 2010", date.toString());
}
}
La solution de contournement pour Java 7+ utilise SimpleDateFormat:DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX", Locale.US);
Ce code peut analyser le format ISO8601 comme:
2017-05-17T06:01:43.785Z
2017-05-13T02:58:21.391+01:00
Mais sur Java6, SimpleDateFormat
ne comprend pas le caractère X
et jetteraIllegalArgumentException: Unknown pattern character 'X'
Nous devons normaliser la date ISO8601 au format lisible dans Java 6 avec SimpleDateFormat
.
public static Date iso8601Format(String formattedDate) throws ParseException {
try {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX", Locale.US);
return df.parse(formattedDate);
} catch (IllegalArgumentException ex) {
// error happen in Java 6: Unknown pattern character 'X'
if (formattedDate.endsWith("Z")) formattedDate = formattedDate.replace("Z", "+0000");
else formattedDate = formattedDate.replaceAll("([+-]\\d\\d):(\\d\\d)\\s*$", "$1$2");
DateFormat df1 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US);
return df1.parse(formattedDate);
}
}
Méthode ci-dessus pour remplacer [Z
par +0000
] ou [+01:00
par +0100
] en cas d'erreur dans Java 6 (vous pouvez détecter Java version et remplacez try/catch par une instruction if).
Notez que dans Java 8, vous pouvez utiliser la classe Java.time.ZonedDateTime et sa méthode statique parse(CharSequence text)
.
J'ai fait face au problème même et je l'ai résolu par le code suivant.
public static Calendar getCalendarFromISO(String datestring) {
Calendar calendar = Calendar.getInstance(TimeZone.getDefault(), Locale.getDefault()) ;
SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault());
try {
Date date = dateformat.parse(datestring);
date.setHours(date.getHours() - 1);
calendar.setTime(date);
String test = dateformat.format(calendar.getTime());
Log.e("TEST_TIME", test);
} catch (ParseException e) {
e.printStackTrace();
}
return calendar;
}
J'utilisais auparavant SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.getDefault());
Mais plus tard, j'ai trouvé que la cause principale de l'exception était le yyyy-MM-dd'T'HH:mm:ss.SSSZ
,
Donc j'ai utilisé
SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault());
Cela a bien fonctionné pour moi.
Aussi, vous pouvez utiliser la classe suivante -
org.springframework.extensions.surf.util.ISO8601DateFormat
Date date = ISO8601DateFormat.parse("date in iso8601");
Lien vers le Java Doc - Hiérarchie du package org.springframework.extensions.surf.maven.plugin.util
Comme d'autres l'ont mentionné, Android ne dispose pas d'un moyen efficace de prendre en charge l'analyse/le formatage des dates ISO 8601 à l'aide de classes incluses dans le SDK. Comme j'ai écrit ce code plusieurs fois, j'ai finalement créé un Gist qui comprend une classe DateUtils qui prend en charge le formatage et l'analyse des dates ISO 8601 et RFC 1123. The Gist inclut également un scénario de test montrant ce qu’il prend en charge.
Apache Jackrabbit utilise le format ISO 8601 pour les dates persistantes, et une classe auxiliaire permet de les analyser:
org.Apache.jackrabbit.util.ISO8601
Livré avec jackrabbit-jcr-commons .
Java a une douzaine de façons différentes d’analyser une date/heure, comme le montrent les excellentes réponses fournies ici. Mais quelque peu étonnant, aucune des classes de temps de Java n'implémente pleinement l'ISO 8601!
Avec Java 8, je recommanderais:
ZonedDateTime zp = ZonedDateTime.parse(string);
Date date = Date.from(zp.toInstant());
Cela traitera les exemples à la fois en UTC et avec un décalage, comme "2017-09-13T10: 36: 40Z" ou "2017-09-13T10: 36: 40 + 01: 00". Cela conviendra pour la plupart des cas d'utilisation.
Mais il ne gérera pas les exemples tels que "2017-09-13T10: 36: 40 + 01", qui est une date/heure ISO 8601 valide.
Il ne gérera pas uniquement la date, par ex. "2017-09-13".
Si vous devez gérer cela, je suggérerais d'utiliser d'abord une expression régulière pour renifler la syntaxe.
Il existe une belle liste d'exemples ISO 8601 avec de nombreux cas délicats: https://www.myintervals.com/blog/2009/05/20/iso-8601-date-validation-that-doesnt-suck / Je ne connais aucune classe Java capable de les gérer toutes.
SimpleDateFormat pour Java 1.7 a un modèle cool pour le format ISO 8601.
Voici ce que j'ai fait:
Date d = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSSZ",
Locale.ENGLISH).format(System.currentTimeMillis());
Fais-le comme ça:
public static void main(String[] args) throws ParseException {
String dateStr = "2016-10-19T14:15:36+08:00";
Date date = javax.xml.bind.DatatypeConverter.parseDateTime(dateStr).getTime();
System.out.println(date);
}
Voici la sortie:
Mer. 19 oct. 15:15:36 CST 2016
Utilisez une chaîne comme LocalDate.parse(((String) data.get("d_iso8601")),DateTimeFormatter.ISO_DATE)
Je suis surpris de constater que même une bibliothèque Java ne prend pas en charge tous les formats de date ISO 8601 conformément à https://en.wikipedia.org/wiki/ISO_8601 . Joda DateTime prenait en charge la plupart d’entre eux, mais pas tous et c’est pourquoi j’ai ajouté une logique personnalisée permettant de les gérer tous. Voici ma mise en œuvre.
import Java.text.ParseException;
import Java.util.Date;
import org.Apache.commons.lang3.time.DateUtils;
import org.joda.time.DateTime;
public class ISO8601DateUtils {
/**
* It parses all the date time formats from https://en.wikipedia.org/wiki/ISO_8601 and returns Joda DateTime.
* Zoda DateTime does not support dates of format 20190531T160233Z, and hence added custom logic to handle this using SimpleDateFormat.
* @param dateTimeString ISO 8601 date time string
* @return
*/
public static DateTime parse(String dateTimeString) {
try {
return new DateTime( dateTimeString );
} catch(Exception e) {
try {
Date dateTime = DateUtils.parseDate(dateTimeString, JODA_NOT_SUPPORTED_ISO_DATES);
return new DateTime(dateTime.getTime());
} catch (ParseException e1) {
throw new RuntimeException(String.format("Date %s could not be parsed to ISO date", dateTimeString));
}
}
}
private static String[] JODA_NOT_SUPPORTED_ISO_DATES = new String[] {
// upto millis
"yyyyMMdd'T'HHmmssSSS'Z'",
"yyyyMMdd'T'HHmmssSSSZ",
"yyyyMMdd'T'HHmmssSSSXXX",
"yyyy-MM-dd'T'HHmmssSSS'Z'",
"yyyy-MM-dd'T'HHmmssSSSZ",
"yyyy-MM-dd'T'HHmmssSSSXXX",
// upto seconds
"yyyyMMdd'T'HHmmss'Z'",
"yyyyMMdd'T'HHmmssZ",
"yyyyMMdd'T'HHmmssXXX",
"yyyy-MM-dd'T'HHmmss'Z'",
"yyyy-MM-dd'T'HHmmssZ",
"yyyy-MM-dd'T'HHmmssXXX",
// upto minutes
"yyyyMMdd'T'HHmm'Z'",
"yyyyMMdd'T'HHmmZ",
"yyyyMMdd'T'HHmmXXX",
"yyyy-MM-dd'T'HHmm'Z'",
"yyyy-MM-dd'T'HHmmZ",
"yyyy-MM-dd'T'HHmmXXX",
//upto hours is already supported by Joda DateTime
};
}
J'avais un besoin similaire: je devais être capable d'analyser n'importe quelle date conforme à la norme ISO8601 sans connaître le format exact à l'avance, et je voulais une solution légère qui fonctionnerait également sur Android.
Lorsque j'ai recherché mes besoins dans Google, je suis tombé sur cette question et j'ai constaté qu'AFAIU, aucune réponse ne correspondait parfaitement à mes besoins. Alors j'ai développé jISO8601 et l'ai poussé sur maven central.
Ajoutez juste en vous pom.xml
:
<dependency>
<groupId>fr.turri</groupId>
<artifactId>jISO8601</artifactId>
<version>0.2</version>
</dependency>
et puis vous êtes prêt à partir:
import fr.turri.jiso8601.*;
...
Calendar cal = Iso8601Deserializer.toCalendar("1985-03-04");
Date date = Iso8601Deserializer.toDate("1985-03-04T12:34:56Z");
Espère que ça aide.
Pour formater simplement une date comme celle-ci, les éléments suivants ont fonctionné pour moi dans une application basée sur Java 6. Il y a une DateFormat
classe JacksonThymeleafISO8601DateFormat
dans le projet thymeleaf qui insère le colon manquant:
Je l'ai utilisé pour la compatibilité du format de date ECMAScript.
Un petit test qui montre comment analyser une date dans ISO8601 et que LocalDateTime ne gère pas les fichiers DST.
@Test
public void shouldHandleDaylightSavingTimes() throws ParseException {
//ISO8601 UTC date format
SimpleDateFormat utcFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
// 1 hour of difference between 2 dates in UTC happening at the Daylight Saving Time
Date d1 = utcFormat.parse("2019-10-27T00:30:00.000Z");
Date d2 = utcFormat.parse("2019-10-27T01:30:00.000Z");
//Date 2 is before date 2
Assert.assertTrue(d1.getTime() < d2.getTime());
// And there is 1 hour difference between the 2 dates
Assert.assertEquals(1000*60*60, d2.getTime() - d1.getTime());
//Print the dates in local time
SimpleDateFormat localFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm z Z", Locale.forLanguageTag("fr_CH"));
localFormat.setTimeZone(TimeZone.getTimeZone("Europe/Zurich"));
//Both dates are at 02h30 local time (because of DST), but one is CEST +0200 and the other CET +0100 (clock goes backwards)
Assert.assertEquals("2019-10-27 02:30 CEST +0200", localFormat.format(d1));
Assert.assertEquals("2019-10-27 02:30 CET +0100", localFormat.format(d2));
//Small test that shows that LocalDateTime does not handle DST (and should not be used for storing timeseries data)
LocalDateTime ld1 = LocalDateTime.ofInstant(d1.toInstant(), ZoneId.of("Europe/Zurich"));
LocalDateTime ld2 = LocalDateTime.ofInstant(d2.toInstant(), ZoneId.of("Europe/Zurich"));
//Note that a localdatetime does not handle DST, therefore the 2 dates are the same
Assert.assertEquals(ld1, ld2);
//They both have the following local values
Assert.assertEquals(2019, ld1.getYear());
Assert.assertEquals(27, ld1.getDayOfMonth());
Assert.assertEquals(10, ld1.getMonthValue());
Assert.assertEquals(2, ld1.getHour());
Assert.assertEquals(30, ld1.getMinute());
Assert.assertEquals(0, ld1.getSecond());
}