Java string naar datum conversie

Wat is de beste manier om een String in het formaat '2 januari 2010' om te zetten naar een Datum in Java?

Uiteindelijk wil ik de maand, de dag en het jaar uitsplitsen als gehele getallen, zodat ik

Date date = new Date();
date.setMonth()..
date.setYear()..
date.setDay()..
date.setlong currentTime = date.getTime();

om de datum om te zetten in tijd.

Oplossing

Dat's de harde manier, en die java.util.Date setter methods zijn sinds Java 1.1 (1997) deprecated. Formatteer de datum gewoon met SimpleDateFormat met een format pattern dat overeenkomt met de input string.

In uw specifieke geval van "2 januari 2010" als de input string:

  1. "januari" is de volledige tekstmaand, dus gebruik het MMMM patroon ervoor
  2. "2" is de korte dag-van-de-maand, dus gebruik het d patroon ervoor.
  3. "2010" is het 4-cijferige jaar, dus gebruik het yyyy patroon ervoor.
String string = "January 2, 2010";
DateFormat format = new SimpleDateFormat("MMMM d, yyyy", Locale.ENGLISH);
Date date = format.parse(string);
System.out.println(date); // Sat Jan 02 00:00:00 GMT 2010

Let op het belang van het expliciete Locale argument. Als je het weglaat, dan zal het de standaard locale gebruiken die niet noodzakelijkerwijs Engels is zoals gebruikt in de maandnaam van de input string. Als de locale niet overeenkomt met de input string, dan zou je verwarrend genoeg een java.text.ParseException krijgen, ook al lijkt het format pattern geldig.

Hier's een extract van relevantie uit de javadoc, waarin alle beschikbare formaatpatronen worden opgesomd:

Letter  Date or Time Component  Presentation        Examples
------  ----------------------  ------------------  -------------------------------------
G       Era designator          Text                AD
y       Year                    Year                1996; 96
Y       Week year               Year                2009; 09
M/L     Month in year           Month               July; Jul; 07
w       Week in year            Number              27
W       Week in month           Number              2
D       Day in year             Number              189
d       Day in month            Number              10
F       Day of week in month    Number              2
E       Day in week             Text                Tuesday; Tue
u       Day number of week      Number              1
a       Am/pm marker            Text                PM
H       Hour in day (0-23)      Number              0
k       Hour in day (1-24)      Number              24
K       Hour in am/pm (0-11)    Number              0
h       Hour in am/pm (1-12)    Number              12
m       Minute in hour          Number              30
s       Second in minute        Number              55
S       Millisecond             Number              978
z       Time zone               General time zone   Pacific Standard Time; PST; GMT-08:00
Z       Time zone               RFC 822 time zone   -0800
X       Time zone               ISO 8601 time zone  -08; -0800; -08:00

Merk op dat de patronen hoofdlettergevoelig zijn en dat tekstgebaseerde patronen van vier karakters of meer de volledige vorm weergeven; anders wordt een korte of afgekorte vorm gebruikt indien beschikbaar. Dus b.v. MMMMM of meer is niet nodig.

Hier zijn enkele voorbeelden van geldige SimpleDateFormat patronen om een gegeven string naar datum te parsen:

Input string                            Pattern
------------------------------------    ----------------------------
2001.07.04 AD at 12:08:56 PDT           yyyy.MM.dd G 'at' HH:mm:ss z
Wed, Jul 4, '01                         EEE, MMM d, ''yy
12:08 PM                                h:mm a
12 o'clock PM, Pacific Daylight Time    hh 'o''clock' a, zzzz
0:08 PM, PDT                            K:mm a, z
02001.July.04 AD 12:08 PM               yyyyy.MMMM.dd GGG hh:mm aaa
Wed, 4 Jul 2001 12:08:56 -0700          EEE, d MMM yyyy HH:mm:ss Z
010704120856-0700                       yyMMddHHmmssZ
2001-07-04T12:08:56.235-0700            yyyy-MM-dd'T'HH:mm:ss.SSSZ
2001-07-04T12:08:56.235-07:00           yyyy-MM-dd'T'HH:mm:ss.SSSXXX
2001-W27-3                              YYYY-'W'ww-u

Een belangrijke opmerking is dat SimpleDateFormat niet thread safe is. Met andere woorden, je moet SimpleDateFormat nooit declareren en toewijzen als een statische of instance variabele en deze dan hergebruiken in verschillende methodes/threads. Je moet hem altijd helemaal nieuw aanmaken binnen de methode local scope.


Java 8 update

Als je toevallig op Java 8 of nieuwer zit, gebruik dan DateTimeFormatter (ook hier, klik op de link om alle voorgedefinieerde formatters en beschikbare format patterns te zien; de tutorial is hier beschikbaar). Deze nieuwe API is geïnspireerd door JodaTime.

String string = "January 2, 2010";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMMM d, yyyy", Locale.ENGLISH);
LocalDate date = LocalDate.parse(string, formatter);
System.out.println(date); // 2010-01-02

Opmerking: als je format pattern toevallig ook het tijdsdeel bevat, gebruik dan LocalDateTime#parse(text, formatter) in plaats van LocalDate#parse(text, formatter). En, als je format pattern toevallig ook de tijdzone bevat, gebruik dan ZonedDateTime#parse(text, formatter) in plaats van ZonedDateTime#parse(text, formatter).

Hier's een relevant uittreksel uit de javadoc, met een lijst van alle beschikbare formaat patronen:

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;

Merk op dat het verschillende voorgedefinieerde formatters heeft voor de meer populaire patronen. Dus in plaats van bijvoorbeeld DateTimeFormatter.ofPattern("EEE, d MMM yyyy HH:mm:ss Z", Locale.ENGLISH);, zou je DateTimeFormatter.RFC_1123_DATE_TIME kunnen gebruiken. Dit is mogelijk omdat ze, in tegenstelling tot SimpleDateFormat, thread safe zijn. Je zou dus ook je eigen kunnen definiëren, indien nodig.

Voor een bepaald input string formaat hoef je geen expliciete DateTimeFormatter te gebruiken: een standaard ISO 8601 datum, zoals 2016-09-26T17:44:57Z, kan direct met LocalDateTime#parse(text) worden geparseerd, omdat die al de ISO_LOCAL_DATE_TIME formatter gebruikt. Op dezelfde manier parseert LocalDate#parse(text) een ISO datum zonder de tijdcomponent (zie ISO_LOCAL_DATE), en parseert ZonedDateTime#parse(text) een ISO datum met een offset en toegevoegde tijdzone (zie ISO_ZONED_DATE_TIME).

Commentaren (4)

Ah ja, de Java Datum discussie, alweer. Om met datummanipulatie om te gaan gebruiken we Date, Calendar, GregorianCalendar, en SimpleDateFormat. Bijvoorbeeld, met de datum van januari als invoer:

Calendar mydate = new GregorianCalendar();
String mystring = "January 2, 2010";
Date thedate = new SimpleDateFormat("MMMM d, yyyy", Locale.ENGLISH).parse(mystring);
mydate.setTime(thedate);
//breakdown
System.out.println("mydate -> "+mydate);
System.out.println("year   -> "+mydate.get(Calendar.YEAR));
System.out.println("month  -> "+mydate.get(Calendar.MONTH));
System.out.println("dom    -> "+mydate.get(Calendar.DAY_OF_MONTH));
System.out.println("dow    -> "+mydate.get(Calendar.DAY_OF_WEEK));
System.out.println("hour   -> "+mydate.get(Calendar.HOUR));
System.out.println("minute -> "+mydate.get(Calendar.MINUTE));
System.out.println("second -> "+mydate.get(Calendar.SECOND));
System.out.println("milli  -> "+mydate.get(Calendar.MILLISECOND));
System.out.println("ampm   -> "+mydate.get(Calendar.AM_PM));
System.out.println("hod    -> "+mydate.get(Calendar.HOUR_OF_DAY));

Dan kun je dat manipuleren met iets als:

Calendar now = Calendar.getInstance();
mydate.set(Calendar.YEAR,2009);
mydate.set(Calendar.MONTH,Calendar.FEBRUARY);
mydate.set(Calendar.DAY_OF_MONTH,25);
mydate.set(Calendar.HOUR_OF_DAY,now.get(Calendar.HOUR_OF_DAY));
mydate.set(Calendar.MINUTE,now.get(Calendar.MINUTE));
mydate.set(Calendar.SECOND,now.get(Calendar.SECOND));
// or with one statement
//mydate.set(2009, Calendar.FEBRUARY, 25, now.get(Calendar.HOUR_OF_DAY), now.get(Calendar.MINUTE), now.get(Calendar.SECOND));
System.out.println("mydate -> "+mydate);
System.out.println("year   -> "+mydate.get(Calendar.YEAR));
System.out.println("month  -> "+mydate.get(Calendar.MONTH));
System.out.println("dom    -> "+mydate.get(Calendar.DAY_OF_MONTH));
System.out.println("dow    -> "+mydate.get(Calendar.DAY_OF_WEEK));
System.out.println("hour   -> "+mydate.get(Calendar.HOUR));
System.out.println("minute -> "+mydate.get(Calendar.MINUTE));
System.out.println("second -> "+mydate.get(Calendar.SECOND));
System.out.println("milli  -> "+mydate.get(Calendar.MILLISECOND));
System.out.println("ampm   -> "+mydate.get(Calendar.AM_PM));
System.out.println("hod    -> "+mydate.get(Calendar.HOUR_OF_DAY));
Commentaren (1)

Terwijl je bezig bent met de SimpleDateFormat klasse, is het belangrijk te onthouden dat Date niet thread-safe is en dat je een enkel Date object niet kunt delen met meerdere threads.

Er is ook een groot verschil tussen "m" en "M" waar kleine letters worden gebruikt voor minuten en hoofdletters voor maand. Hetzelfde geldt voor "d" en "D". Dit kan subtiele bugs veroorzaken die vaak over het hoofd worden gezien. Zie Javadoc of Guide to Convert String to Date in Java voor meer details.

Commentaren (2)