Konwersja łańcucha Java na datę

Jaki jest najlepszy sposób konwersji String w formacie 'January 2, 2010' na Date w Javie?

Ostatecznie chcę wyodrębnić miesiąc, dzień i rok jako liczby całkowite, aby móc użyć

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

do konwersji daty na czas.

Rozwiązanie

To jest trudny sposób, a te metody setera java.util.Date są przestarzałe od Java 1.1 (1997). Po prostu sformatuj datę za pomocą SimpleDateFormat używając wzorca formatu pasującego do łańcucha wejściowego.

W twoim konkretnym przypadku "2 stycznia 2010" jako ciąg wejściowy:

  1. "Styczeń" jest pełnotekstowym miesiącem, więc użyj dla niego wzorca MMMM.
  2. "2" jest krótkim dniem miesiąca, więc użyj dla niego wzorca d.
  3. "2010" jest 4-cyfrowym rokiem, więc użyj dla niego wzoru yyy.
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

Zwróć uwagę na znaczenie jawnego argumentu Locale. Jeśli go pominiesz, to zostanie użyte domyślne locale, które niekoniecznie jest angielskie, jak użyte w nazwie miesiąca łańcucha wejściowego. Jeśli locale nie'pasuje do łańcucha wejściowego, wtedy myląco otrzymasz wyjątek java.text.ParseException, nawet jeśli wzorzec formatu wydaje się poprawny.

Oto'fragment odnośnika z javadoc, wymieniający wszystkie dostępne wzorce formatu:

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

Zauważ, że we wzorcach rozróżniana jest wielkość liter i że wzorce tekstowe składające się z czterech lub więcej znaków reprezentują pełną formę; w przeciwnym razie używana jest forma skrócona lub skrócona, jeśli jest dostępna. Tak więc np. MMMMM lub więcej nie jest konieczne.

Oto kilka przykładów poprawnych wzorców SimpleDateFormat do przetworzenia danego łańcucha na datę:

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

Ważną informacją jest to, że SimpleDateFormat nie jest bezpieczny dla wątków. Innymi słowy, nigdy nie powinieneś deklarować i przypisywać go jako zmiennej statycznej lub instancji, a następnie używać go ponownie w różnych metodach/wątkach. Powinieneś zawsze tworzyć ją od nowa w lokalnym zakresie metody.


Java 8 aktualizacja

Jeśli masz Javę 8 lub nowszą, użyj DateTimeFormatter (także tutaj, kliknij link, aby zobaczyć wszystkie predefiniowane formatery i dostępne wzorce formatów; samouczek jest dostępny tutaj). To nowe API jest inspirowane przez 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

Uwaga: jeśli twój wzorzec formatu zawiera również część czasową, użyj LocalDateTime#parse(text, formatter) zamiast LocalDate#parse(text, formatter). A jeśli twój wzorzec formatu zawiera również strefę czasową, wtedy użyj ZonedDateTime#parse(text, formatter) zamiast [ZonedDateTime#parse(text, formatter)].

Poniżej'znajduje się fragment istotny z javadoc, wymieniający wszystkie dostępne wzorce formatu:

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;

Zwróć uwagę, że ma on kilka predefiniowanych formaterów dla bardziej popularnych wzorców. Więc zamiast np. DateTimeFormatter.ofPattern("EEE, d MMM yyy HH:mm:ss Z", Locale.ENGLISH);, możesz użyć DateTimeFormatter.RFC_1123_DATE_TIME. Jest to możliwe, ponieważ są one, w przeciwieństwie do SimpleDateFormat, bezpieczne dla wątków. Możesz więc również zdefiniować swoje własne, jeśli to konieczne.

Dla konkretnego formatu łańcucha wejściowego, nie trzeba używać jawnego DateTimeFormatter: standardowa ISO 8601 data, jak 2016-09-26T17:44:57Z, może być bezpośrednio parsowana za pomocą LocalDateTime#parse(text), ponieważ już używa formatera ISO_LOCAL_DATE_TIME. Podobnie, LocalDate#parse(text) parsuje datę ISO bez składnika czasu (zobacz ISO_LOCAL_DATE), a ZonedDateTime#parse(text) parsuje datę ISO z przesunięciem i dodaną strefą czasową (zobacz ISO_ZONED_DATE_TIME).

Komentarze (4)

Ach tak, znowu dyskusja o datach w Javie. Aby poradzić sobie z manipulacją datą używamy Date, Calendar, GregorianCalendar i SimpleDateFormat. Na przykład używając twojej styczniowej daty jako danych wejściowych:

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));

Następnie możesz manipulować tym za pomocą czegoś takiego jak:

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));
Komentarze (1)

Podczas pracy z klasą SimpleDateFormat należy pamiętać, że Date nie jest bezpieczna dla wątków i nie można współdzielić pojedynczego obiektu Date z wieloma wątkami.

Istnieje również duża różnica pomiędzy "m" i "M", gdzie mała litera jest używana dla minut, a duża dla miesiąca. Tak samo jest z "d" i "D". Może to powodować subtelne błędy, które często są przeoczone. Zobacz Javadoc lub Guide to Convert String to Date in Java po więcej szczegółów.

Komentarze (2)