Jak parsować / formatować daty za pomocą LocalDateTime? (Java 8)

341

Java 8 dodała nowy interfejs API java.time do pracy z datami i godzinami ( JSR 310 ).

Mam datę i godzinę jako ciąg (np "2014-04-08 12:30".). Jak mogę uzyskać LocalDateTimeinstancję z podanego ciągu?

Po zakończeniu pracy z LocalDateTimeobiektem: jak mogę przekonwertować LocalDateTimeinstancję z powrotem na ciąg o takim samym formacie, jak pokazano powyżej?

Micha
źródło
11
Do Twojej wiadomości, większość ludzi przez większość czasu wolałaby ZonedDateTimeraczej niż LocalDateTime. Nazwa jest sprzeczna z intuicją; Localoznacza dowolną lokalizację w ogóle zamiast strefę określonym czasie. Jako taki LocalDateTimeobiekt nie jest powiązany z linią czasu. Aby mieć znaczenie, aby uzyskać określony moment na linii czasu, musisz zastosować strefę czasową.
Basil Bourque,
Zobacz moją odpowiedź o wyjaśnienie LocalDateTimevs ZonedDateTimevs OffsetDateTimevs Instantvs LocalDatevs LocalTime, jak zachować spokój, dlaczego jest tak skomplikowane i jak zrobić to dobrze na pierwszy strzał.
Ondra Žižka
1
Gdyby nie był niepraktycznie długi, LocalDateTimeprawdopodobnie zostałby nazwany ZonelessOffsetlessDateTime.
Ondra Žižka

Odpowiedzi:

534

Analiza daty i godziny

Aby utworzyć LocalDateTimeobiekt z łańcucha, możesz użyć LocalDateTime.parse()metody statycznej . Wymaga ciągu i DateTimeFormatterparametru jako parametru. DateTimeFormatterJest używany do określenia wzoru data / czas.

String str = "1986-04-08 12:30";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
LocalDateTime dateTime = LocalDateTime.parse(str, formatter);

Formatowanie daty i godziny

Aby utworzyć sformatowany ciąg znaków na LocalDateTimeobiekcie, możesz użyć format()metody.

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
LocalDateTime dateTime = LocalDateTime.of(1986, Month.APRIL, 8, 12, 30);
String formattedDateTime = dateTime.format(formatter); // "1986-04-08 12:30"

Zauważ, że niektóre powszechnie używane formaty daty / godziny są wstępnie zdefiniowane jako stałe w DateTimeFormatter. Na przykład: użycie DateTimeFormatter.ISO_DATE_TIMEdo sformatowania LocalDateTimeinstancji z góry spowoduje powstanie ciągu "1986-04-08T12:30:00".

Te parse()i format()są dostępne dla wszystkich metod / obiektów czasowych związanych data (np LocalDatelub ZonedDateTime)

Micha
źródło
77
Należy tylko zauważyć, że DateTimeFormatter jest niezmienny i bezpieczny dla wątków, dlatego zalecane jest przechowywanie go w stałej statycznej tam, gdzie to możliwe.
JodaStephen
@ micha co jeśli mam "2016-12-31T07: 59: 00.000Z" tę datę sformatować?
Dawood Ahmed
14
@DawoodAbbasi spróbujDateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX")
Ray Hulha
1
@Leenix może dlatego, że próbujesz wywołać format()klasę LocalDateTime zamiast instancji? Przynajmniej tak zrobiłem: pomyliłem DateTimesię dateTimew powyższym przykładzie.
spojrzał
2
Nie zapomnij o wielkich
literach
159

Można również użyć LocalDate.parse()albo LocalDateTime.parse()na zasadzie Stringbez zapewniając jej wzoru, jeśli Stringjest w formacie ISO-8601 .

na przykład,

String strDate = "2015-08-04";
LocalDate aLD = LocalDate.parse(strDate);
System.out.println("Date: " + aLD);

String strDatewithTime = "2015-08-04T10:11:30";
LocalDateTime aLDT = LocalDateTime.parse(strDatewithTime);
System.out.println("Date with Time: " + aLDT);

Wyjście ,

Date: 2015-08-04
Date with Time: 2015-08-04T10:11:30

i używaj DateTimeFormattertylko wtedy, gdy masz do czynienia z innymi wzorcami dat.

Na przykład w poniższym przykładzie dd MMM uuuu reprezentuje dzień miesiąca (dwie cyfry), trzy litery nazwy miesiąca (styczeń, luty, mar, ...) i czterocyfrowy rok:

DateTimeFormatter dTF = DateTimeFormatter.ofPattern("dd MMM uuuu");
String anotherDate = "04 Aug 2015";
LocalDate lds = LocalDate.parse(anotherDate, dTF);
System.out.println(anotherDate + " parses to " + lds);

Wynik

04 Aug 2015 parses to 2015-08-04

pamiętaj również, że DateTimeFormatterobiekt jest dwukierunkowy; może zarówno analizować dane wejściowe, jak i formatować dane wyjściowe.

String strDate = "2015-08-04";
LocalDate aLD = LocalDate.parse(strDate);
DateTimeFormatter dTF = DateTimeFormatter.ofPattern("dd MMM uuuu");
System.out.println(aLD + " formats as " + dTF.format(aLD));

Wynik

2015-08-04 formats as 04 Aug 2015

(zobacz pełną listę wzorców formatowania i analizy formatowania daty )

  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;

   p       pad next                    pad modifier      1

   '       escape for text             delimiter
   ''      single quote                literal           '
   [       optional section start
   ]       optional section end
   #       reserved for future use
   {       reserved for future use
   }       reserved for future use
Sufiyan Ghori
źródło
11
Ta odpowiedź dotyczyła ważnego tematu: w miarę możliwości używaj predefiniowanych formatatorów, np. NIE twórz bazy formatyzatora na „rrrr-MM-dd”, zamiast tego użyj DateTimeFormatter.ISO_LOCAL_DATE. Sprawi, że Twój kod będzie wyglądał o wiele czystiej. Co więcej, spróbuj zmaksymalizować użycie formatu ISO8061, w dłuższej perspektywie przyniesie dywidendy.
Christopher Yang,
Chcę przeanalizować datę sprawdzania poprawności, na przykład, 2018-08-09 12:00:08ale kiedy analizuję, widzę, że Tjest dodawany, którego nie potrzebuję. Czy jest na to sposób?
Raghuveer
@Raghuveer T to tylko separator ISO-8061 między datą a godziną. Jeśli zamiast tego masz spację w swoim formacie, możesz po prostu użyć wzorca yyyy-MM-dd hh:mm:ssdo analizy i formatowania. Symbol T będzie zawsze wyświetlany w domyślnym formacie (ISO-8061), ale możesz używać własnych wzorów.
Egor Hans
39

Obie powyższe odpowiedzi bardzo dobrze wyjaśniają pytanie dotyczące wzorów ciągów. Jednak w przypadku pracy z ISO 8601 nie ma potrzeby składania wniosku, DateTimeFormatterponieważ LocalDateTime jest już na to przygotowany:

Konwertuj LocalDateTime na ciąg strefy czasowej ISO8601

LocalDateTime ldt = LocalDateTime.now(); 
ZonedDateTime zdt = ldt.atZone(ZoneOffset.UTC); //you might use a different zone
String iso8601 = zdt.toString();

Konwertuj z ciągu ISO8601 z powrotem na LocalDateTime

String iso8601 = "2016-02-14T18:32:04.150Z";
ZonedDateTime zdt = ZonedDateTime.parse(iso8601);
LocalDateTime ldt = zdt.toLocalDateTime();
Marcio Jasiński
źródło
20

Przetwarzanie ciągu z datą i godziną w określonym momencie (Java nazywa to „ Instant”) jest dość skomplikowane. Java radzi sobie z tym w kilku iteracjach. Najnowszy, java.timei java.time.chronoobejmuje prawie wszystkie potrzeby (z wyjątkiem Dylatacji Czasu :)).

Jednak ta złożoność wprowadza wiele zamieszania.

Kluczem do zrozumienia parsowania daty jest:

Dlaczego Java ma tak wiele sposobów na parsowanie daty

  1. Istnieje kilka systemów pomiaru czasu. Na przykład historyczne kalendarze japońskie pochodzą z przedziałów czasowych panowania danego cesarza lub dynastii. Następnie jest np. Znacznik czasu UNIX. Na szczęście cały świat (biznesowy) wykorzystał to samo.
  2. Historycznie systemy były przełączane z / do, z różnych powodów . Np. Od kalendarza juliańskiego do kalendarza gregoriańskiego w 1582 r. Tak więc „zachodnie” daty muszą być wcześniej potraktowane inaczej.
  3. I oczywiście zmiana nie nastąpiła od razu. Ponieważ kalendarz pochodził z centrali niektórych religii, a inne części Europy wierzyły w inne diety, na przykład Niemcy zmieniły się dopiero w 1700 roku.

... i dlaczego jest LocalDateTime, ZonedDateTimei in. takie skomplikowane

  1. Istnieją strefy czasowe . Strefa czasowa jest w zasadzie „paskiem” * [1] powierzchni Ziemi, którego władze przestrzegają tych samych zasad co do tego, kiedy ma przesunięcie czasowe. Obejmuje to zasady dotyczące czasu letniego.
    Strefy czasowe zmieniają się w czasie dla różnych obszarów, głównie na podstawie tego, kto kogo zwycięża. Z czasem zasady jednej strefy czasowej również się zmieniają .

  2. Istnieją przesunięcia czasowe. Nie jest to to samo, co strefy czasowe, ponieważ strefa czasowa może być np. „Praga”, ale ma przesunięcie czasu letniego i zimowego.
    Jeśli otrzymasz znacznik czasu ze strefą czasową, przesunięcie może się różnić w zależności od tego, w której części roku się znajduje. Podczas godziny przestępnej znacznik czasu może oznaczać 2 różne czasy, więc bez dodatkowych informacji nie można go wiarygodnie nawrócony.
    Uwaga: Przez znacznik czasu rozumiem „ciąg znaków zawierający datę i / lub godzinę, opcjonalnie ze strefą czasową i / lub przesunięciem czasu”.

  3. Kilka stref czasowych może współdzielić to samo przesunięcie czasowe dla niektórych okresów. Na przykład strefa czasowa GMT / UTC jest taka sama jak strefa czasowa „Londyn”, gdy nie obowiązuje przesunięcie czasu letniego.

Aby było to nieco bardziej skomplikowane (ale nie jest to zbyt ważne w przypadku użycia):

  1. Naukowcy obserwują dynamikę Ziemi, która zmienia się w czasie; na tej podstawie dodają sekundy pod koniec poszczególnych lat. ( 2040-12-31 24:00:00Może to być prawidłowa data i godzina). Wymaga to regularnych aktualizacji metadanych używanych przez systemy do prawidłowej konwersji daty. Np. W systemie Linux otrzymujesz regularne aktualizacje pakietów Java, w tym te nowe dane.
  2. Aktualizacje nie zawsze zachowują poprzednie zachowanie zarówno dla historycznych, jak i przyszłych znaczników czasu. Może się więc zdarzyć, że parsowanie dwóch znaczników czasu wokół zmiany strefy czasowej w porównaniu z nimi może dać różne wyniki, gdy uruchomione zostaną różne wersje oprogramowania. Dotyczy to również porównywania między strefą czasową dotkniętą chorobą a inną strefą czasową.

    Jeśli spowoduje to błąd w oprogramowaniu, rozważ użycie znacznika czasu, który nie ma tak skomplikowanych reguł, jak na przykład znacznik czasu UNIX .

  3. Z powodu 7 w przypadku przyszłych dat nie możemy przekonwertować dat dokładnie. Na przykład bieżące parsowanie 8524-02-17 12:00:00może być wyłączone o kilka sekund od przyszłego parsowania.

Interfejsy API JDK do tego ewoluowały wraz ze współczesnymi potrzebami

  • Wczesne wydania Java miały java.util.Datenieco naiwne podejście, zakładając, że jest tylko rok, miesiąc, dzień i godzina. To szybko nie wystarczyło.
  • Również potrzeby baz danych były różne, więc wprowadzono dość wcześnie java.sql.Date, z własnymi ograniczeniami.
  • Ponieważ żaden z nich nie obejmował dobrze różnych kalendarzy i stref czasowych, wprowadzono CalendarAPI.
  • To wciąż nie obejmowało złożoności stref czasowych. A jednak połączenie powyższych API było naprawdę trudnym zadaniem. Gdy programiści Java zaczęli pracować nad globalnymi aplikacjami internetowymi, biblioteki, które były ukierunkowane na większość przypadków użycia, takie jak JodaTime, szybko zyskały popularność. JodaTime był de facto standardem przez około dekadę.
  • Ale JDK nie zintegrował się z JodaTime, więc praca z nim była nieco kłopotliwa. Tak więc, po bardzo długiej dyskusji na temat podejścia do sprawy, JSR-310 został stworzony głównie w oparciu o JodaTime .

Jak sobie z tym poradzić w Javie java.time

Określ, do jakiego typu parsować znacznik czasu

Kiedy konsumujesz ciąg znacznika czasu, musisz wiedzieć, jakie informacje zawiera. To jest kluczowy punkt. Jeśli nie uda ci się tego zrobić, możesz uzyskać tajemnicze wyjątki, takie jak „Nie można utworzyć błyskawicznego” lub „Brak przesunięcia strefy” lub „nieznany identyfikator strefy” itp.

Czy zawiera datę i godzinę?

  1. Czy ma przesunięcie czasowe?
    Przesunięcie czasu jest +hh:mmczęścią. Czasami +00:00może być zastąpiony przez Z„czas Zulu”, UTCjako koordynowany czas uniwersalny lub GMTjako czas uniwersalny Greenwich. Ustawiają one także strefę czasową.
    Dla tych znaczników czasu używasz OffsetDateTime.

  2. Czy ma strefę czasową?
    Dla tych znaczników czasu używasz ZonedDateTime.
    Strefa jest określona przez

    • nazwa („Praga”, „czas pacyficzny standardowy”, „PST”) lub
    • „identyfikator strefy” („America / Los_Angeles”, „Europe / London”), reprezentowany przez java.time.ZoneId .

    Lista stref czasowych jest tworzona przez „bazę danych TZ” , wspieraną przez ICAAN.

    Zgodnie z ZoneIdjavadoc, identyfikator strefy można również jakoś określić jako Zi offset. Nie jestem pewien, jak to mapuje do prawdziwych stref. Jeśli znacznik czasu, który ma tylko TZ, wpada w godzinę przestępną zmiany przesunięcia czasowego, to jest on niejednoznaczny, a interpretacja jest przedmiotem ResolverStyle, patrz poniżej.

  3. Jeśli nie ma żadnego , wówczas brakujący kontekst jest przyjmowany lub zaniedbywany. I konsument musi zdecydować. Dlatego musi zostać przeanalizowany jako LocalDateTimei przekonwertowany OffsetDateTimeprzez dodanie brakujących informacji:

    • Możesz założyć , że jest to czas UTC. Dodaj przesunięcie UTC 0 godzin.
    • Możesz założyć , że jest to czas miejsca, w którym następuje konwersja. Przekształć go, dodając strefę czasową systemu.
    • Możesz zaniedbać i po prostu używać go takim, jaki jest. Jest to przydatne np. Do porównania lub odjęcia dwa razy (patrz Duration) lub gdy nie wiesz i nie ma to tak naprawdę znaczenia (np. Rozkład jazdy autobusów lokalnych).

Informacje o niepełnym wymiarze godzin

  • Na podstawie tego, co zawiera znacznik czasu, można przyjąć LocalDate, LocalTime, OffsetTime, MonthDay, Year, lub YearMonthz niego.

Jeśli masz pełne informacje, możesz uzyskać java.time.Instant. Jest to również wewnętrznie wykorzystywane do konwersji między OffsetDateTimei ZonedDateTime.

Dowiedz się, jak to przeanalizować

Istnieje obszerna dokumentacja, w DateTimeFormatterktórej można parsować ciąg znacznika czasu i formatować go.

The wstępnie utworzone DateTimeFormatters powinien obejmować więcejmniej wszystkie standardowe formaty datownik. Na przykład ISO_INSTANTmożna parsować 2011-12-03T10:15:30.123457Z.

Jeśli masz jakiś specjalny format, możesz utworzyć własny DateTimeFormatter (który jest również analizatorem składni).

private static final DateTimeFormatter TIMESTAMP_PARSER = new DateTimeFormatterBuilder()
   .parseCaseInsensitive()
   .append(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SX"))
   .toFormatter();

Polecam zajrzeć do kodu źródłowego DateTimeFormatter i zainspirować się, jak zbudować taki przy użyciu DateTimeFormatterBuilder. Podczas gdy tam jesteś, sprawdź również, ResolverStylektóry kontroluje, czy analizator składni jest LENIENT, SMART czy STRICT dla formatów i niejednoznacznych informacji.

TemporalAccessor

Teraz częstym błędem jest wchodzenie w złożoność TemporalAccessor. Wynika to ze sposobu, w jaki programiści byli przyzwyczajeni do pracy SimpleDateFormatter.parse(String). Racja, DateTimeFormatter.parse("...")daje ciTemporalAccessor .

// No need for this!
TemporalAccessor ta = TIMESTAMP_PARSER.parse("2011-... etc");

Ale dzięki wiedzy z poprzedniej sekcji możesz wygodnie parsować wybrany typ:

OffsetDateTime myTimestamp = OffsetDateTime.parse("2011-12-03T10:15:30.123457Z", TIMESTAMP_PARSER);

Tak naprawdę nie potrzebujesz DateTimeFormatterani jednego, ani drugiego. Typy, które chcesz przeanalizować, mają parse(String)metody.

OffsetDateTime myTimestamp = OffsetDateTime.parse("2011-12-03T10:15:30.123457Z");

Jeżeli chodzi o TemporalAccessor , możesz go użyć, jeśli masz niejasne pojęcie o tym, jakie informacje znajdują się w ciągu i chcesz zdecydować w czasie wykonywania.

Mam nadzieję, że rzuciłem trochę zrozumienia na twoją duszę :)

Uwaga: Istnieje backport java.timedo Java 6 i 7: ThreeTen-Backport . Na Androida ma ThreeTenABP .

[1] Nie tylko to, że nie są paskami, ale są też dziwne skrajności. Na przykład niektóre sąsiednie wyspy Pacyfiku mają strefy czasowe +14: 00 i -11: 00. Oznacza to, że będąc na jednej wyspie, jest 1 maja 3 po południu, na innej wyspie jeszcze nie jest jeszcze 30 kwietnia 12 po południu (jeśli poprawnie policzyłem :))

Ondra Žižka
źródło
3

UZYSKAJ BIEŻĄCY CZAS UTC W WYMAGANYM FORMACIE

// Current UTC time
        OffsetDateTime utc = OffsetDateTime.now(ZoneOffset.UTC);

        // GET LocalDateTime 
        LocalDateTime localDateTime = utc.toLocalDateTime();
        System.out.println("*************" + localDateTime);

        // formated UTC time
        DateTimeFormatter dTF = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
        System.out.println(" formats as " + dTF.format(localDateTime));

        //GET UTC time for current date
        Date now= new Date();
        LocalDateTime utcDateTimeForCurrentDateTime = Instant.ofEpochMilli(now.getTime()).atZone(ZoneId.of("UTC")).toLocalDateTime();
        DateTimeFormatter dTF2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
        System.out.println(" formats as " + dTF2.format(utcDateTimeForCurrentDateTime));
Santhosh Hirekerur
źródło
0

Uważam, że wspaniale jest obejmować wiele wariantów formatu daty i godziny, takich jak ten:

final DateTimeFormatterBuilder dtfb = new DateTimeFormatterBuilder();
dtfb.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSS"))
    .appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"))
    .appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SS"))
    .appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.S"))
    .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
    .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
    .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0);
flowgrad
źródło
1
`` `publiczny końcowy statyczny DateTimeFormatter TIMESTAMP_XX = nowy DateTimeFormatterBuilder (). appendPattern („ [[uuuu] [- MM] [- dd]] [[HH] [: mm] [: ss] [. SSS]] ”). parseDefaulting (ChronoField.YEAR, 2020) .parseDefaulting (ChronoField.MONTH_OF_YEAR, 1) .parseDefaulting (ChronoField.DAY_OF_MONTH, 1) .parseDefaulting (ChronoField.HOUR_OF_DAY, 0) .parse , 0) .parseDefaulting (ChronoField.NANO_OF_SECOND, 0) .toFormatter (); ``
Alan Stewart