Bagaimana cara mem-parsing / memformat tanggal dengan LocalDateTime? (Java 8)

341

Java 8 menambahkan java.time API baru untuk bekerja dengan tanggal dan waktu ( JSR 310 ).

Saya memiliki tanggal dan waktu sebagai string (mis "2014-04-08 12:30".). Bagaimana saya bisa mendapatkan LocalDateTimeinstance dari string yang diberikan?

Setelah saya selesai bekerja dengan LocalDateTimeobjek: Bagaimana saya bisa mengubah LocalDateTimeinstance kembali ke string dengan format yang sama seperti yang ditunjukkan di atas?

micha
sumber
11
FYI, kebanyakan orang kebanyakan ingin ZonedDateTimelebih daripada LocalDateTime. Namanya kontra-intuitif; yang Localberarti setiap wilayah secara umum daripada zona waktu tertentu. Dengan demikian, suatu LocalDateTimeobjek tidak terikat dengan garis waktu. Untuk memiliki makna, untuk mendapatkan momen tertentu pada garis waktu, Anda harus menerapkan zona waktu.
Basil Bourque
Lihat jawaban saya untuk penjelasan LocalDateTimevs ZonedDateTimevs OffsetDateTimevs Instantvs LocalDatevs LocalTime, bagaimana tetap tenang tentang mengapa begitu rumit dan bagaimana melakukannya dengan benar pada tembakan pertama.
Ondra Žižka
1
Jika tidak lama tidak praktis, LocalDateTimemungkin akan dinamai ZonelessOffsetlessDateTime.
Ondra Žižka

Jawaban:

534

Tanggal dan waktu parsing

Untuk membuat LocalDateTimeobjek dari string, Anda dapat menggunakan LocalDateTime.parse()metode statis . Dibutuhkan string dan DateTimeFormattersebagai parameter. Ini DateTimeFormatterdigunakan untuk menentukan pola tanggal / waktu.

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

Memformat tanggal dan waktu

Untuk membuat string yang diformat keluar dari LocalDateTimeobjek Anda dapat menggunakan format()metode ini.

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"

Perhatikan bahwa ada beberapa format tanggal / waktu yang umum digunakan sebagai konstanta dalam DateTimeFormatter . Sebagai contoh: Menggunakan DateTimeFormatter.ISO_DATE_TIMEuntuk memformat LocalDateTimeinstance dari atas akan menghasilkan string "1986-04-08T12:30:00".

Metode parse()dan format()tersedia untuk semua objek terkait tanggal / waktu (misalnya LocalDateatau ZonedDateTime)

micha
sumber
77
Hanya untuk dicatat bahwa DateTimeFormatter tidak dapat diubah dan aman untuk digunakan, dan karenanya pendekatan yang disarankan adalah menyimpannya dalam konstanta statis jika memungkinkan.
JodaStephen
@micha bagaimana jika saya punya "2016-12-31T07: 59: 00.000Z" format tanggal ini?
Dawood Ahmed
14
@DawoodAbbasi cobaDateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX")
Ray Hulha
1
@Loenix mungkin itu karena Anda mencoba memanggil format()kelas LocalDateTime alih-alih pada contoh? Setidaknya, itulah yang saya lakukan: Saya bingung DateTimedengan dateTimecontoh di atas.
glaed
2
Jangan lupa huruf besar pada MM
Wesos de Queso
159

Anda juga dapat menggunakan LocalDate.parse()atau LocalDateTime.parse()pada Stringtanpa memberikan dengan pola, jika Stringdalam ISO-8601 Format .

sebagai contoh,

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

Keluaran ,

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

dan gunakan DateTimeFormatterhanya jika Anda harus berurusan dengan pola tanggal lainnya.

Misalnya, dalam contoh berikut, dd MMM uuuu mewakili hari dalam sebulan (dua digit), tiga huruf dari nama bulan (Jan, Feb, Mar, ...), dan tahun empat digit:

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

Keluaran

04 Aug 2015 parses to 2015-08-04

juga ingat bahwa DateTimeFormatterobjeknya adalah dua arah; keduanya dapat mengurai input dan memformat output.

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

Keluaran

2015-08-04 formats as 04 Aug 2015

(lihat daftar lengkap Pola untuk Memformat dan Memilah DateFormatter )

  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
sumber
11
Jawaban ini menyentuh subjek penting: sedapat mungkin gunakan format yang telah ditentukan sebelumnya misalnya JANGAN membuat basis formatter pada "yyyy-MM-dd", gunakan DateTimeFormatter.ISO_LOCAL_DATE sebagai gantinya. Ini akan membuat kode Anda terlihat jauh lebih bersih. Selanjutnya, cobalah untuk memaksimalkan penggunaan format ISO8061, itu akan membayar dividen dalam jangka panjang.
Christopher Yang
Saya ingin menguraikan tanggal untuk validasi seperti 2018-08-09 12:00:08tetapi ketika saya menguraikan saya melihat Tditambahkan yang saya tidak perlu. Apakah ada cara untuk melakukannya ?
Raghuveer
@ Raghuveer T hanyalah pembatas ISO-8061 antara tanggal dan waktu. Jika Anda memiliki ruang dalam format Anda, Anda bisa menggunakan pola yyyy-MM-dd hh:mm:ssuntuk parsing dan format. T akan selalu ditampilkan dalam format default (ISO-8061), tetapi Anda dapat menggunakan pola Anda sendiri.
Egor Hans
39

Kedua jawaban di atas menjelaskan dengan sangat baik pertanyaan tentang pola string. Namun, untuk berjaga-jaga jika Anda bekerja dengan ISO 8601 tidak perlu diterapkan DateTimeFormatterkarena LocalDateTime sudah siap untuk itu:

Konversi LocalDateTime ke Zona Waktu String ISO8601

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

Konversi dari ISO8601 String kembali ke LocalDateTime

String iso8601 = "2016-02-14T18:32:04.150Z";
ZonedDateTime zdt = ZonedDateTime.parse(iso8601);
LocalDateTime ldt = zdt.toLocalDateTime();
Marcio Jasinski
sumber
20

Parsing string dengan tanggal dan waktu ke titik waktu tertentu (Java menyebutnya " Instant") cukup rumit. Java telah menangani ini dalam beberapa iterasi. Yang terbaru, java.timedan java.time.chrono, mencakup hampir semua kebutuhan (kecuali Pelebaran Waktu :)).

Namun, kerumitan itu membawa banyak kebingungan.

Kunci untuk memahami penguraian tanggal adalah:

Mengapa Java memiliki banyak cara untuk menguraikan tanggal

  1. Ada beberapa sistem untuk mengukur waktu. Misalnya, kalender Jepang historis berasal dari rentang waktu masa pemerintahan kaisar atau dinasti masing-masing. Lalu ada misalnya cap waktu UNIX. Untungnya, seluruh dunia (bisnis) berhasil menggunakan hal yang sama.
  2. Secara historis, sistem sedang beralih dari / ke, karena berbagai alasan . Misalnya dari kalender Julian ke kalender Gregorian pada 1582. Jadi tanggal 'barat' sebelum itu perlu diperlakukan berbeda.
  3. Dan tentu saja perubahan itu tidak terjadi sekaligus. Karena kalender berasal dari kantor pusat beberapa agama dan bagian lain Eropa percaya pada diet lain, misalnya Jerman tidak beralih sampai tahun 1700.

... dan mengapa LocalDateTime, ZonedDateTimeet al. sangat rumit

  1. Ada zona waktu . Zona waktu pada dasarnya adalah "garis" * [1] dari permukaan bumi yang otoritasnya mengikuti aturan yang sama tentang kapan zona waktu itu diimbangi. Ini termasuk aturan waktu musim panas.
    Zona waktu berubah dari waktu ke waktu untuk berbagai area, sebagian besar didasarkan pada siapa yang menaklukkan siapa. Dan aturan satu zona waktu juga berubah seiring waktu .

  2. Ada offset waktu. Itu tidak sama dengan zona waktu, karena zona waktu mungkin misalnya "Praha", tetapi memiliki offset waktu musim panas dan waktu musim dingin.
    Jika Anda mendapatkan stempel waktu dengan zona waktu, offset dapat bervariasi, tergantung pada bagian tahun apa yang digunakan. Selama jam kabisat, stempel waktu dapat berarti 2 waktu yang berbeda, jadi tanpa informasi tambahan, itu tidak dapat diandalkan. dikonversi.
    Catatan: Dengan timestamp yang saya maksud "string yang berisi tanggal dan / atau waktu, opsional dengan zona waktu dan / atau offset waktu."

  3. Beberapa zona waktu dapat berbagi offset waktu yang sama untuk periode tertentu. Misalnya, zona waktu GMT / UTC sama dengan zona waktu "London" ketika offset waktu musim panas tidak berlaku.

Untuk membuatnya sedikit lebih rumit (tapi itu tidak terlalu penting untuk kasus penggunaan Anda):

  1. Para ilmuwan mengamati dinamika Bumi, yang berubah seiring waktu; berdasarkan itu, mereka menambahkan detik pada akhir tahun individual. (Jadi 2040-12-31 24:00:00mungkin tanggal-waktu yang valid.) Ini membutuhkan pembaruan rutin metadata yang digunakan sistem agar konversi tanggal tepat. Misalnya di Linux, Anda mendapatkan pembaruan rutin ke paket Java termasuk data baru ini.
  2. Pembaruan tidak selalu menjaga perilaku sebelumnya untuk cap waktu historis dan masa depan. Jadi mungkin terjadi penguraian dua stempel waktu di sekitar perubahan zona waktu yang membandingkannya mungkin memberikan hasil yang berbeda saat dijalankan pada versi perangkat lunak yang berbeda. Itu juga berlaku untuk membandingkan antara zona waktu yang terpengaruh dan zona waktu lainnya.

    Jika ini menyebabkan bug pada perangkat lunak Anda, pertimbangkan untuk menggunakan beberapa stempel waktu yang tidak memiliki aturan yang rumit, seperti stempel waktu UNIX .

  3. Karena 7, untuk tanggal mendatang, kami tidak dapat mengonversi tanggal dengan pasti. Jadi, misalnya, penguraian saat ini 8524-02-17 12:00:00mungkin tidak aktif beberapa detik dari penguraian masa depan.

API JDK untuk ini berkembang dengan kebutuhan kontemporer

  • Rilis Java awal baru saja java.util.Datememiliki pendekatan yang agak naif, dengan asumsi bahwa hanya ada tahun, bulan, hari, dan waktu. Ini dengan cepat tidak cukup.
  • Juga, kebutuhan database berbeda, jadi cukup awal, java.sql.Datediperkenalkan, dengan keterbatasan sendiri.
  • Karena tidak mencakup kalender dan zona waktu yang berbeda dengan baik, CalendarAPI diperkenalkan.
  • Ini masih belum mencakup kompleksitas zona waktu. Namun, campuran API di atas benar-benar menyebalkan. Jadi ketika pengembang Java mulai bekerja pada aplikasi web global, perpustakaan yang menargetkan sebagian besar kasus penggunaan, seperti JodaTime, menjadi cepat populer. JodaTime adalah standar de-facto selama sekitar satu dekade.
  • Tetapi JDK tidak berintegrasi dengan JodaTime, jadi bekerja dengannya agak rumit. Jadi, setelah diskusi yang sangat panjang tentang bagaimana mendekati masalah ini, JSR-310 dibuat terutama berdasarkan JodaTime .

Bagaimana menghadapinya di Jawa java.time

Tentukan jenis yang akan diurai timestamp untuk

Saat Anda menggunakan string cap waktu, Anda perlu mengetahui informasi apa yang dikandungnya. Ini adalah poin krusial. Jika Anda tidak melakukan ini dengan benar, Anda berakhir dengan pengecualian samar seperti "Tidak dapat membuat Instan" atau "Zona offset hilang" atau "id zona tidak dikenal" dll.

Apakah ini berisi tanggal dan waktu?

  1. Apakah ada offset waktu?
    Offset waktu adalah +hh:mmbagiannya. Kadang-kadang, +00:00dapat diganti dengan Z'Zulu time', UTCsebagai Universal Time Coordinated, atau GMTsebagai Greenwich Mean Time. Ini juga mengatur zona waktu.
    Untuk cap waktu ini, Anda gunakan OffsetDateTime.

  2. Apakah ada zona waktu?
    Untuk cap waktu ini, Anda gunakan ZonedDateTime.
    Zona ditentukan baik oleh

    • nama ("Praha", "Waktu Standar Pasifik", "PST"), atau
    • "ID zona" ("Amerika / Los_Angeles", "Eropa / London"), diwakili oleh java.time.ZoneId .

    Daftar zona waktu dikompilasi oleh "database TZ" , didukung oleh ICAAN.

    Menurut ZoneIdjavadoc, id zona juga dapat ditentukan Zdan diimbangi. Saya tidak yakin bagaimana ini memetakan ke zona nyata. Jika cap waktu, yang hanya memiliki TZ, jatuh ke dalam lompatan waktu dari perubahan offset, maka itu ambigu, dan interpretasinya adalah subjek ResolverStyle, lihat di bawah.

  3. Jika tidak ada , maka konteks yang hilang diasumsikan atau diabaikan. Dan konsumen harus memutuskan. Jadi itu perlu diurai LocalDateTimedan dikonversi OffsetDateTimedengan menambahkan info yang hilang:

    • Anda dapat berasumsi bahwa ini adalah waktu UTC. Tambahkan offset UTC 0 jam.
    • Anda dapat berasumsi bahwa ini adalah waktu di mana konversi terjadi. Konversikan dengan menambahkan zona waktu sistem.
    • Anda dapat mengabaikan dan hanya menggunakannya apa adanya. Itu berguna misalnya untuk membandingkan atau mengurangi dua kali (lihat Duration), atau ketika Anda tidak tahu dan itu tidak masalah (misalnya jadwal bus lokal).

Informasi waktu parsial

  • Berdasarkan apa timestamp mengandung, Anda dapat mengambil LocalDate, LocalTime, OffsetTime, MonthDay, Year, atau YearMonthkeluar dari itu.

Jika Anda memiliki informasi lengkap, Anda bisa mendapatkan java.time.Instant. Ini juga digunakan secara internal untuk mengkonversi antara OffsetDateTimedan ZonedDateTime.

Cari tahu bagaimana cara menguraikannya

Ada dokumentasi yang luas DateTimeFormatteryang dapat mengurai string stempel waktu dan memformat ke string.

The pre-diciptakan DateTimeFormatters harus mencakup moreless semua format timestamp standar. Misalnya ISO_INSTANTbisa parse 2011-12-03T10:15:30.123457Z.

Jika Anda memiliki beberapa format khusus, maka Anda dapat membuat DateTimeFormatter Anda sendiri (yang juga merupakan parser).

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

Saya merekomendasikan untuk melihat kode sumber DateTimeFormatterdan mendapatkan inspirasi tentang cara membangunnya menggunakan DateTimeFormatterBuilder. Saat Anda berada di sana, lihat juga ResolverStyleyang mengontrol apakah parser itu LENIENT, SMART, atau STRICT untuk format dan informasi yang ambigu.

TemporalAccessor

Sekarang, kesalahan yang sering terjadi adalah masuk ke kompleksitas TemporalAccessor. Ini berasal dari bagaimana para pengembang dulu bekerja dengan SimpleDateFormatter.parse(String). Benar, DateTimeFormatter.parse("...")memberimu TemporalAccessor.

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

Tetapi, dilengkapi dengan pengetahuan dari bagian sebelumnya, Anda dapat dengan mudah menguraikan ke dalam jenis yang Anda butuhkan:

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

Anda sebenarnya tidak perlu ke DateTimeFormatterkeduanya. Tipe yang ingin Anda parse memiliki parse(String)metode.

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

Mengenai TemporalAccessor, Anda dapat menggunakannya jika Anda memiliki gagasan yang tidak jelas tentang informasi apa yang ada di string, dan ingin memutuskan saat runtime.

Saya harap saya memberi sedikit pengertian pada jiwa Anda :)

Catatan: Ada backport java.timeke Java 6 dan 7: ThreeTen-Backport . Untuk Android memiliki ThreeTenABP .

[1] Tidak hanya itu bukan garis-garis, tetapi ada juga beberapa ekstrem aneh. Misalnya, beberapa pulau pasifik yang berdekatan memiliki zona waktu +14: 00 dan -11: 00. Itu berarti, bahwa sementara di satu pulau, ada 1 Mei 3 sore, di pulau lain tidak begitu jauh, itu masih 30 April 12 PM (jika saya menghitung dengan benar :))

Ondra Žižka
sumber
3

DAPATKAN WAKTU UTC SAAT INI DALAM FORMAT YANG DIBUTUHKAN

// 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
sumber
0

Saya menemukan itu bagus untuk mencakup beberapa varian format waktu tanggal seperti ini:

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
sumber
1
`` `DateTimeFormatter statis final publik TIMESTAMP_XX = DateTimeFormatterBuilder (). appendPattern baru (" [[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) .parseDefaulting (ChronoField.MINUTE_OF_HOUR, 0) .parseDefaulting (ChronoField.SECOND_OF_MINUTE , 0) .parseDefaulting (ChronoField.NANO_OF_SECOND, 0) .toFormatter (); `` `
Alan Stewart