Je lis des chaînes de date qui pourraient être avec ou sans ajustement de fuseau horaire: yyyyMMddHHmmssz
ou yyyyMMddHHmmss
. Quand une chaîne manque une zone, je la traiterai comme GMT. Je ne vois aucun moyen de créer des sections facultatives dans une SimpleDateFormat
, mais peut-être qu'il me manque quelque chose. Existe-t-il un moyen de faire cela avec une SimpleDateFormat
ou devrais-je simplement écrire un nouveau DateFormat
concret pour gérer cela?
Je créerais deux SimpleDateFormat, un avec un fuseau horaire et un sans. Vous pouvez regarder la longueur de la chaîne pour déterminer laquelle utiliser.
On dirait que vous avez besoin d'un DateFormat qui délègue à deux SDF différents.
DateFormat df = new DateFormat() {
static final String FORMAT1 = "yyyyMMddHHmmss";
static final String FORMAT2 = "yyyyMMddHHmmssz";
final SimpleDateFormat sdf1 = new SimpleDateFormat(FORMAT1);
final SimpleDateFormat sdf2 = new SimpleDateFormat(FORMAT2);
@Override
public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) {
throw new UnsupportedOperationException();
}
@Override
public Date parse(String source, ParsePosition pos) {
if (source.length() - pos.getIndex() == FORMAT1.length())
return sdf1.parse(source, pos);
return sdf2.parse(source, pos);
}
};
System.out.println(df.parse("20110102030405"));
System.out.println(df.parse("20110102030405PST"));
JSR-310 est livré avec Java 8, qui offre une prise en charge améliorée de l'analyse des valeurs temporelles, les composants pouvant désormais être optionnels. Vous pouvez non seulement rendre la zone facultative, mais vous pouvez également rendre le composant heure facultatif et renvoyer l'unité temporelle correcte pour la chaîne donnée.
Considérez les cas de test suivants.
public class DateFormatTest {
private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(
"yyyy-MM-dd[[ ]['T']HH:mm[:ss][XXX]]");
private TemporalAccessor parse(String v) {
return formatter.parseBest(v,
ZonedDateTime::from,
LocalDateTime::from,
LocalDate::from);
}
@Test public void testDateTime1() {
assertEquals(LocalDateTime.of(2014, 9, 23, 14, 20, 59),
parse("2014-09-23T14:20:59"));
}
@Test public void testDateTime2() {
assertEquals(LocalDateTime.of(2014, 9, 23, 14, 20),
parse("2014-09-23 14:20"));
}
@Test public void testDateOnly() {
assertEquals(LocalDate.of(2014, 9, 23), parse("2014-09-23"));
}
@Test public void testZonedDateTime() {
assertEquals(ZonedDateTime.of(2014, 9, 23, 14, 20, 59, 0,
ZoneOffset.ofHoursMinutes(10, 30)),
parse("2014-09-23T14:20:59+10:30"));
}
}
Ici, le motif DateTimeFormatter de "yyyy-MM-dd[[ ]['T']HH:mm[:ss][XXX]]"
autorise les options dans les parenthèses carrées, qui peuvent également être imbriquées. Les modèles peuvent également être construits à partir de DateTimeFormatterBuilder , dont le modèle ci-dessus est démontré ici:
private final DateTimeFormatter formatter = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.append(DateTimeFormatter.ISO_LOCAL_DATE)
.optionalStart()
.optionalStart()
.appendLiteral(' ')
.optionalEnd()
.optionalStart()
.appendLiteral('T')
.optionalEnd()
.appendOptional(DateTimeFormatter.ISO_TIME)
.toFormatter();
Cela se traduirait par une expression qui ressemble à ceci:
yyyy-MM-dd[[' ']['T']HH:mm[':'ss[.SSS]]].
Les valeurs facultatives peuvent être imbriquées et sont également automatiquement fermées à la fin si elles sont encore ouvertes. Notez cependant qu’il n’existe aucun moyen de fournir un OR exclusif sur les pièces facultatives. Le format ci-dessus analyserait donc la valeur suivante de manière assez précise:
2018-03-08 T11:12
Notez la capacité très pratique de réutiliser les formateurs existants en tant que parties de notre format actuel.
Je sais que c'est un ancien post mais juste pour l'enregistrement ...
La classe Apache DateUtils peut vous aider.
String[] acceptedFormats = {"dd/MM/yyyy","dd/MM/yyyy HH:mm","dd/MM/yyyy HH:mm:ss"};
Date date1 = DateUtils.parseDate("12/07/2012", acceptedFormats);
Date date2 = DateUtils.parseDate("12/07/2012 23:59:59", acceptedFormats);
Lien vers la bibliothèque Apache Commons Lang dans le référentiel Maven, vous pouvez vérifier quelle est la dernière version:
http://mvnrepository.com/artifact/org.Apache.commons/commons-lang3
Maven v3.4
<dependency>
<groupId>org.Apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
Gradle v3.4
'org.Apache.commons:commons-lang3:3.4'
Si vous pouvez utiliser Joda Datetime, il prend en charge des pièces facultatives dans le formateur, par exemple, "aaaa-MM-jj [hh: mm: ss]"
private DateTimeFormatter fmt = new DateTimeFormatterBuilder()
.append(DateTimeFormat.forPattern("yyyy-MM-dd"))
.appendOptional(
new DateTimeFormatterBuilder()
.appendLiteral(' ')
.append(DateTimeFormat.forPattern("HH:mm:ss"))
.toParser()
.toFormatter();
Je voudrais parcourir la liste d'objets DateFormat
potentiels en utilisant une opération try-catch
pour rompre la boucle lors de la première analyse réussie.
J'ai résolu un problème similaire il y a quelque temps en étendant SimpleDateFormat. Ci-dessous une implémentation grossière pour montrer l'idée de ma solution. Il peut ne pas être complètement complet/optimisé.
public class MySimpleDateFormat extends SimpleDateFormat {
private static final long serialVersionUID = 1L;
private static String FORMAT = "
private static int FORMAT_LEN = "yyyyMMddHHmmss".length();
private static String TZ_ID = "GMT";
public MySimpleDateFormat() {
this(TimeZone.getTimeZone(TZ_ID));
}
public MySimpleDateFormat(TimeZone tz) {
super(FORMAT);
setTimeZone(tz);
}
@Override
public Date parse(String source, ParsePosition pos) {
// TODO: args validation
int offset = pos.getIndex() + FORMAT_LEN;
Date result;
if (offset < source.length()) {
// there maybe is a timezone
result = super.parse(source, pos);
if (result != null) {
return result;
}
if (pos.getErrorIndex() >= offset) {
// there isn't a TZ after all
String part0 = source.substring(0, offset);
String part1 = source.substring(offset);
ParsePosition anotherPos = new ParsePosition(pos.getIndex());
result = super.parse(part0 + TZ_ID + part1, anotherPos);
if(result == null) {
pos.setErrorIndex(anotherPos.getErrorIndex());
} else {
// check SimpleDateFormat#parse javadoc to implement correctly the pos updates
pos.setErrorIndex(-1);
pos.setIndex(offset);
}
return result;
}
// there's something wrong with the first FORMAT_LEN chars
return null;
}
result = super.parse(source + TZ_ID, pos);
if(result != null) {
pos.setIndex(pos.getIndex() - TZ_ID.length());
}
return result;
}
public static void main(String [] args) {
ParsePosition pos = new ParsePosition(0);
MySimpleDateFormat mySdf = new MySimpleDateFormat();
System.out.println(mySdf.parse("20120622131415", pos) + " -- " + pos);
pos = new ParsePosition(0);
System.out.println(mySdf.parse("20120622131415GMT", pos) + " -- " + pos);
pos = new ParsePosition(0);
System.out.println(mySdf.parse("20120622131415xxx", pos) + " -- " + pos);
pos = new ParsePosition(0);
System.out.println(mySdf.parse("20120x22131415xxx", pos) + " -- " + pos);
}
}
En résumé, vous devez vérifier la chaîne d'entrée et "deviner" que le champ TZ est manquant, ajoutez-le si c'est le cas, puis laissez la fonction SimpleDateFormat#parse(String, ParsePosition)
faire le reste. L'implémentation ci-dessus ne met pas à jour ParsePosition conformément au contrat sous javadoc dans SimpleDateFormat#parse(String, ParsePosition)
La classe a un seul ctor par défaut car un seul format est autorisé.
La méthode MySimpleDateFormat#parse(String, ParsePosition)
est invoquée par SimpleDateFormat#parse(String)
et est donc suffisante pour couvrir les deux cas.
Exécuter le main () ceci est la sortie (comme prévu)
Fri Jun 22 14:14:15 BST 2012 -- Java.text.ParsePosition[index=14,errorIndex=-1]
Fri Jun 22 14:14:15 BST 2012 -- Java.text.ParsePosition[index=17,errorIndex=-1]
Fri Jun 22 14:14:15 BST 2012 -- Java.text.ParsePosition[index=14,errorIndex=-1]
null -- Java.text.ParsePosition[index=0,errorIndex=5]
Vous pouvez créer deux SimpleDateFormats
différents comme
public PWMDateTimeFormatter(String aPatternStr)
{
_formatter = DateTimeFormat.forPattern(aPatternStr);
}
public PWMDateTimeFormatter(String aPatternStr, TimeZone aZone)
{
_formatter = DateTimeFormat.forPattern(aPatternStr).withZone(XXDateTime._getTimeZone(aZone));
}