Bagaimana cara mengatur zona waktu java.util.Date?

225

Saya telah mem-parsing java.util.Datedari Stringtetapi pengaturan zona waktu lokal sebagai zona waktu dateobjek.

Zona waktu tidak ditentukan dalam Stringdari yang Datediuraikan. Saya ingin mengatur zona waktu tertentu dari dateobjek.

Bagaimana saya bisa melakukan itu?

Yatendra Goel
sumber
8
Meskipun bukan jawaban untuk pertanyaan Anda, saya telah menggunakan Joda Time setelah melihatnya disebutkan di sini beberapa kali. Tampaknya lebih rasional bagi saya daripada API standar, dan dapat melakukan hal semacam ini dengan cukup mudah.
clstrfsck
2
@msandiford Saat ini, gunakan kelas java.time daripada Joda-Time. Proyek Joda-Time sekarang dalam mode pemeliharaan , dengan tim menyarankan migrasi ke kelas java.time . Lihat Tutorial oleh Oracle .
Basil Bourque

Jawaban:

315

Gunakan DateFormat. Sebagai contoh,

SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
isoFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
Date date = isoFormat.parse("2010-05-23T09:01:02");
ZZ Coder
sumber
6
jika tanggal dibuat dari kelas Kalender, Anda dapat mengatur zona waktu untuk Kalender.
lwpro2
19
@ lwpro2 pernyataan itu menyesatkan; Anda dapat mengatur zona waktu untuk objek Kalender, tetapi mendapatkan objek Tanggal darinya menggunakan metode getTime () akan mengembalikan objek Tanggal dengan zona waktu komputer host.
BrDaHa
Saya mengalami masalah untuk mengurai 02/20/15 14:44. Adakah yang bisa membantu
MS
@ BrDaHa sudah benar. Anda harus TimeZone.setDefault()memanggil sebelum getTime()agar objek tanggal baru berada di zona waktu yang Anda inginkan. Di JDK 1.8, Calendar.getTime()panggilan return new Date(getTimeInMillis());.
jpllosa
182

Ketahuilah bahwa java.util.Dateobjek tidak mengandung informasi zona waktu sendiri - Anda tidak dapat mengatur zona waktu pada Dateobjek. Satu-satunya benda yang Dateberisi objek adalah sejumlah milidetik sejak "zaman" - 1 Januari 1970, 00:00:00 UTC.

Seperti yang ditunjukkan ZZ Coder, Anda mengatur zona waktu pada DateFormatobjek, untuk memberitahukannya di zona waktu mana Anda ingin menampilkan tanggal dan waktu.

Jesper
sumber
77
Setelah Googling, bereksperimen dan menjelajah, saya menyadari ini adalah tambahan yang tepat dan membantu untuk jawaban - dan layak untuk disorot: Tanggal hanya berisi nilai milidetik . Jika Anda melihat sumbernya, ada cukup banyak longbidang yang disebut fastTime. Date.toString()sebenarnya menggunakan Calendaruntuk menafsirkan waktu milidetik ini. Jadi, mencetak a Datemembuatnya tampak memiliki zona waktu (default), yang mengarah ke pertanyaan yang dapat dimengerti tentang cara mengatur zona waktu itu.
David Carboni
3
ada IS zona waktu di dalam objek Date. Tapi itu mungkin benar, Anda tidak bisa mengubahnya.
lwpro2
3
@ Iwpro2, Jesper mengklaim (dan saya setuju) bahwa objek Date tidak menyimpan zona waktu. Jika Anda mengklaim bahwa zona waktu disimpan di dalam java.util.Date, berikan referensi.
Jim
6
@ Jim, ini adalah kode sumber untuk java.util.Date. Ini berisi fastTime sebagai unix epoch serta BaseCalendar.Date cdate yang digunakan untuk fastTime, jika didefinisikan. Yang itu berisi informasi zona waktu. Saya memahaminya sehingga turunan Tanggal dapat berisi informasi zona waktu, tetapi mungkin juga tidak.
eis
2
@ Jim Saya tidak mengatakan Anda bisa mengaturnya, saya mengatakan bahwa itu berisi informasi ini. Saya juga tidak melihat cara mengaturnya sebagai pengguna. Saya pikir OP benar karena tampaknya selalu menjadi zona waktu default pengguna / sistem.
eis
95

tl; dr

... diurai ... dari sebuah String ... zona waktu tidak ditentukan ... Saya ingin menetapkan zona waktu tertentu

LocalDateTime.parse( "2018-01-23T01:23:45.123456789" )  // Parse string, lacking an offset-from-UTC and lacking a time zone, as a `LocalDateTime`.
    .atZone( ZoneId.of( "Africa/Tunis" ) )              // Assign the time zone for which you are certain this date-time was intended. Instantiates a `ZonedDateTime` object.

Tidak Ada Zona Waktu di juDate

Seperti yang dinyatakan dengan jawaban benar lainnya, java.util.Date tidak memiliki zona waktu . Ini mewakili UTC / GMT (tidak ada offset zona waktu). Sangat membingungkan karena toStringmetodenya menerapkan zona waktu default JVM saat membuat representasi String.

Hindari menjodohkan

Untuk alasan ini dan banyak lainnya, Anda harus menghindari menggunakan java.util.Date & .Calendar & java.text.SimpleDateFormat bawaan. Mereka terkenal merepotkan.

Sebagai gantinya gunakan paket java.time yang dibundel dengan Java 8 .

waktu java

Kelas java.time dapat mewakili momen di timeline dalam tiga cara:

  • UTC ( Instant)
  • Dengan offset ( OffsetDateTimedengan ZoneOffset)
  • Dengan zona waktu ( ZonedDateTimedengan ZoneId)

Instant

Di java.time , blok pembangun dasar adalah Instant, sesaat di garis waktu di UTC. Gunakan Instantobjek untuk banyak logika bisnis Anda.

Instant instant = Instant.now();

OffsetDateTime

Terapkan offset-dari-UTC untuk menyesuaikan waktu tempuh jam tertentu dengan suatu daerah .

Terapkan ZoneOffsetuntuk mendapatkan OffsetDateTime.

ZoneOffset zoneOffset = ZoneOffset.of( "-04:00" );
OffsetDateTime odt = OffsetDateTime.ofInstant( instant , zoneOffset );

ZonedDateTime

Lebih baik menerapkan zona waktu , offset, dan aturan untuk menangani anomali seperti Daylight Saving Time (DST) .

Oleskan ZoneIdke Instantuntuk mendapatkan ZonedDateTime. Selalu tentukan nama zona waktu yang tepat . Jangan pernah menggunakan 3-4 singkatan seperti ESTatau ISTyang tidak unik atau standar.

ZoneId zoneId = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = ZonedDateTime.ofInstant( instant , zoneId );

LocalDateTime

Jika string input tidak memiliki indikator offset atau zona, parsing sebagai a LocalDateTime.

Jika Anda yakin dengan zona waktu yang dimaksud, tetapkan a ZoneIduntuk menghasilkan a ZonedDateTime. Lihat contoh kode di atas di bagian dr di atas.

String Terformat

Panggil toStringmetode pada salah satu dari tiga kelas ini untuk menghasilkan String yang mewakili nilai tanggal-waktu dalam format standar ISO 8601 . The ZonedDateTimekelas meluas format standar dengan menambahkan nama zona waktu dalam tanda kurung.

String outputInstant = instant.toString(); // Ex: 2011-12-03T10:15:30Z
String outputOdt = odt.toString(); // Ex: 2007-12-03T10:15:30+01:00
String outputZdt = zdt.toString(); // Ex: 2007-12-03T10:15:30+01:00[Europe/Paris]

Untuk format lain gunakan DateTimeFormatterkelas. Umumnya terbaik untuk membiarkan kelas itu menghasilkan format lokal menggunakan bahasa manusia yang diharapkan dan norma-norma budaya. Atau Anda dapat menentukan format tertentu.


Tabel semua tipe tanggal di Jawa, modern dan lawas


Tentang java.time

The java.time kerangka dibangun ke Jawa 8 dan kemudian. Kelas-kelas ini menggantikan tua merepotkan warisan kelas tanggal-waktu seperti java.util.Date, Calendar, & SimpleDateFormat.

Proyek Joda-Time , sekarang dalam mode pemeliharaan , menyarankan migrasi ke kelas java.time .

Untuk mempelajari lebih lanjut, lihat Tutorial Oracle . Dan cari Stack Overflow untuk banyak contoh dan penjelasan. Spesifikasi adalah JSR 310 .

Anda dapat bertukar objek java.time secara langsung dengan database Anda. Gunakan driver JDBC yang sesuai dengan JDBC 4.2 atau yang lebih baru. Tidak perlu untuk string, tidak perlu untuk java.sql.*kelas.

Di mana mendapatkan kelas java.time?

Proyek ThreeTen-Extra memperpanjang java.time dengan kelas tambahan. Proyek ini adalah ajang pembuktian untuk kemungkinan penambahan masa depan ke java.time. Anda mungkin menemukan beberapa kelas berguna di sini seperti Interval, YearWeek, YearQuarter, dan lebih .


Joda-Time

Sementara Joda-Time masih dipelihara secara aktif, pembuatnya telah memberitahu kami untuk bermigrasi ke java.time sesegera mungkin. Saya membiarkan bagian ini tetap utuh sebagai referensi, tetapi saya sarankan menggunakan java.timebagian di atas sebagai gantinya.

Dalam Joda-Time , objek tanggal-waktu ( DateTime) benar-benar mengetahui zona waktu yang ditentukan. Itu berarti offset dari UTC dan aturan serta riwayat Daylight Saving Time (DST) zona waktu itu dan anomali lainnya.

String input = "2014-01-02T03:04:05";
DateTimeZone timeZone = DateTimeZone.forID( "Asia/Kolkata" );
DateTime dateTimeIndia = new DateTime( input, timeZone );
DateTime dateTimeUtcGmt = dateTimeIndia.withZone( DateTimeZone.UTC );

Panggil toStringmetode untuk menghasilkan String dalam format ISO 8601 .

String output = dateTimeIndia.toString();

Joda-Time juga menawarkan kemampuan yang kaya untuk menghasilkan semua jenis format String lainnya.

Jika diperlukan, Anda dapat mengonversi dari Joda-Time DateTime ke java.util.Date.

Java.util.Date date = dateTimeIndia.toDate();

Cari StackOverflow untuk "joda date" untuk menemukan lebih banyak contoh, beberapa cukup detail.


Sebenarnya ada adalah zona waktu tertanam dalam java.util.Date, digunakan untuk beberapa fungsi internal (lihat komentar pada Jawaban ini). Tetapi zona waktu internal ini tidak terekspos sebagai properti, dan tidak dapat ditetapkan. Zona waktu internal ini bukan yang digunakan oleh toStringmetode dalam menghasilkan representasi string dari nilai tanggal-waktu; alih-alih zona waktu default JVM saat ini diterapkan saat itu juga. Jadi, sebagai steno, kita sering mengatakan "juDate tidak memiliki zona waktu". Membingungkan? Iya. Namun alasan lain untuk menghindari kelas lama yang lelah ini.

Basil Bourque
sumber
3
"No Time Zone in juDate" salah. Ada informasi zona waktu di juDate yang disimpan di BaseCalendar.Date cdatepropertinya jika disetel. Lihatlah kode sumber di sini . Anda tidak dapat mengatur zona waktu objek juDate kecuali dengan mengubah zona waktu default JVM dengan memanggil TimeZone.setDefault(TimeZone.getTimeZone("NEW_TIME_ZONE"));. Dengan demikian, ada offset zona waktu dan Anda bisa mendapatkan offset itu dengan memanggil metode yang sudah usang, juDate.getTimezoneOffset ()
Thai Bui
3
@blquythai Benar, Anda sudah mengerjakan pekerjaan rumah. Seperti yang saya lakukan, setelah melihat kode sumber itu sebelumnya. Ada adalah zona waktu dimakamkan di sana. Tetapi untuk semua tujuan praktis, zona waktu diabaikan. Java.util.Date bekerja tanpa zona waktu, yang berarti berada di UTC, sementara mengabaikan zona waktu yang terkubur. Kecuali untuk toStringmetode yang menerapkan zona waktu default JVM saat ini; lagi-lagi mengabaikan zona waktu yang terkubur. Jadi untuk singkatnya, kita katakan java.util.Date tidak memiliki zona waktu. Seperti Seni , itu adalah kebohongan yang mengatakan yang sebenarnya.
Basil Bourque
@blquythai Untuk panggilan TimeZone.setDefault, Anda tidak mengatur zona waktu objek java.util.Date - objek Date masih mengabaikan zona waktu yang dimakamkan, bertindak efektif dalam UTC. Anda akan memengaruhi toStringmetode Date . Pengaturan default mengubah zona waktu standar JVM yang biasanya diatur ke zona waktu sistem operasi host. Panggilan itu tidak disarankan karena memengaruhi semua kode di semua utas dari semua aplikasi yang berjalan di JVM itu, dan melakukannya sambil jalan saat mereka mengeksekusi. Menjadi kasar dan berbahaya, panggilan itu hanya dianggap sebagai upaya terakhir.
Basil Bourque
5
Zona waktu sangat sering digunakan (digunakan dalam equals, hashcode, getTime..) Jika anda melihat pada equalsmetode, itu panggilan getTime()yang panggilan getTimeImpl(), yang panggilan normalize()jika cdateproperti tidak dinormalisasi. Dalam normalize()metode, kondisi terakhir jika menghitung ulang milidetik sejak 1/1/70 berdasarkan informasi zona waktu yang disimpannya jika zona waktu cdateberbeda dari zona waktu lingkungan JVM saat ini. (Lihatlah sun.util.calendar.AbstractCalendar getCalendarDate(long millis, CalendarDate date))
Thai Bui
2
@MichalM Atas saran Anda, beberapa waktu lalu saya menambahkan kata penutup tentang zona tertanam.
Basil Bourque
75

Anda juga bisa mengatur zona waktu di level JVM

Date date1 = new Date();
System.out.println(date1);

TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
// or pass in a command line arg: -Duser.timezone="UTC"

Date date2 = new Date();
System.out.println(date2);

keluaran:

Thu Sep 05 10:11:12 EDT 2013
Thu Sep 05 14:11:12 UTC 2013
starmer
sumber
Ini membantu saya. pengaturan timeZone di SDF tidak membuat perbedaan
vishnu viswanath
Saya pikir ini lebih dapat diandalkan. (+1)
foobar
23
Hati-hati: Memanggil TimeZone.setDefaultagak drastis, karena memengaruhi seluruh JVM, memengaruhi semua objek dan utas lainnya. Lihat jawaban ini untuk perincian termasuk bahkan lebih banyak komplikasi jika Anda menjalankan dengan SecurityManager. Menambahkan lebih banyak komplikasi: Perilaku ini telah berubah di berbagai versi Java, seperti yang dibahas dalam Pertanyaan ini .
Basil Bourque
Ini menetapkan zona waktu umum di semua utas yang muncul setelah pernyataan ini, bukan?
Jaydev
Ini adalah jawaban yang lebih baik untuk pertanyaan daripada yang diterima.
francogrex
10

Jika Anda harus bekerja dengan hanya kelas JDK standar, Anda dapat menggunakan ini:

/**
 * Converts the given <code>date</code> from the <code>fromTimeZone</code> to the
 * <code>toTimeZone</code>.  Since java.util.Date has does not really store time zome
 * information, this actually converts the date to the date that it would be in the
 * other time zone.
 * @param date
 * @param fromTimeZone
 * @param toTimeZone
 * @return
 */
public static Date convertTimeZone(Date date, TimeZone fromTimeZone, TimeZone toTimeZone)
{
    long fromTimeZoneOffset = getTimeZoneUTCAndDSTOffset(date, fromTimeZone);
    long toTimeZoneOffset = getTimeZoneUTCAndDSTOffset(date, toTimeZone);

    return new Date(date.getTime() + (toTimeZoneOffset - fromTimeZoneOffset));
}

/**
 * Calculates the offset of the <code>timeZone</code> from UTC, factoring in any
 * additional offset due to the time zone being in daylight savings time as of
 * the given <code>date</code>.
 * @param date
 * @param timeZone
 * @return
 */
private static long getTimeZoneUTCAndDSTOffset(Date date, TimeZone timeZone)
{
    long timeZoneDSTOffset = 0;
    if(timeZone.inDaylightTime(date))
    {
        timeZoneDSTOffset = timeZone.getDSTSavings();
    }

    return timeZone.getRawOffset() + timeZoneDSTOffset;
}

Kredit masuk ke pos ini .

Diadyne
sumber
1
Offset zona waktu tidak selalu konstan. Bahkan, mereka dapat berubah karena alasan geopolitik. TimeZone.getDSTSavings () tidak memperhitungkan ini, dan selalu mengembalikan offset saat ini . Ini berarti bahwa Anda mungkin mendapatkan konversi yang salah jika Anda berhadapan dengan tanggal historis dengan fromTimeZone / toTimeZone yang telah mengubah offset sejak tanggal tersebut.
andrerobot
6

java.util.Calendaradalah cara biasa untuk menangani zona waktu hanya menggunakan kelas JDK. Apache Commons memiliki beberapa alternatif / utilitas lebih lanjut yang mungkin bermanfaat. Sunting Catatan Spong mengingatkan saya bahwa saya telah mendengar hal-hal yang sangat baik tentang Joda-Time (meskipun saya belum menggunakannya sendiri).

TJ Crowder
sumber
+1 untuk Joda Time. Meskipun tidak memberikan fungsionalitas tambahan yang tidak dapat Anda dapatkan dari API Java standar (yang saya temukan dalam kasus apa pun - senang ditunjukkan sebaliknya), Joda Time memang membuat beberapa tugas lebih mudah.
Joshua Hutchison
2
@JoshuaHutchison Joda-Time memiliki banyak fungsi tambahan. Contoh: Mewakili rentang waktu dengan kelas Period, Durationdan Interval. Mereka bentang termasuk metode perbandingan seperti contains, abuts, overlap, dan gap. Dan PeriodFormatterBuilderdapat membangun frase deskriptif seperti "15 tahun dan 8 bulan".
Basil Bourque
1

Konversikan Date to String dan lakukan dengan SimpleDateFormat.

    SimpleDateFormat readFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
    readFormat.setTimeZone(TimeZone.getTimeZone("GMT" + timezoneOffset));
    String dateStr = readFormat.format(date);
    SimpleDateFormat writeFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
    Date date = writeFormat.parse(dateStr);
avisper
sumber
1
Jawaban tentang Stack Overflow diharapkan memiliki beberapa diskusi atau penjelasan. Situs ini dimaksudkan lebih dari pustaka cuplikan kode.
Basil Bourque
terima kasih @ Basil Bourque atas komentar Anda. Saya mengedit jawaban
avisper
0

Jika ada yang membutuhkan ini, jika Anda perlu mengubah XMLGregorianCalendarzona waktu ke zona waktu Anda saat ini dari UTC, maka yang perlu Anda lakukan adalah mengatur zona waktu 0, lalu menelepon toGregorianCalendar()- ia akan tetap berada di zona waktu yang sama, tetapi yang Datetahu bagaimana mengubahnya menjadi milik Anda, sehingga Anda bisa mendapatkan data dari sana.

XMLGregorianCalendar xmlStartTime = DatatypeFactory.newInstance()
    .newXMLGregorianCalendar(
        ((GregorianCalendar)GregorianCalendar.getInstance());
xmlStartTime.setTimezone(0);
GregorianCalendar startCalendar = xmlStartTime.toGregorianCalendar();
Date startDate = startCalendar.getTime();
XMLGregorianCalendar xmlStartTime = DatatypeFactory.newInstance()
    .newXMLGregorianCalendar(startCalendar);
xmlStartTime.setHour(startDate.getHours());
xmlStartTime.setDay(startDate.getDate());
xmlStartTime.setMinute(startDate.getMinutes());
xmlStartTime.setMonth(startDate.getMonth()+1);
xmlStartTime.setTimezone(-startDate.getTimezoneOffset());
xmlStartTime.setSecond(startDate.getSeconds());
xmlStartTime.setYear(startDate.getYear() + 1900);
System.out.println(xmlStartTime.toString());

Hasil:

2015-08-26T12:02:27.183Z
2015-08-26T14:02:27.183+02:00
EpicPandaForce
sumber
0

Kode ini bermanfaat dalam aplikasi yang sedang saya kerjakan:

    Instant date = null;
    Date sdf = null;
    String formatTemplate = "EEE MMM dd yyyy HH:mm:ss";
    try {
        SimpleDateFormat isoFormat = new SimpleDateFormat("EEE MMM dd yyyy HH:mm:ss");
        isoFormat.setTimeZone(TimeZone.getTimeZone(ZoneId.of("US/Pacific")));
        sdf = isoFormat.parse(timeAtWhichToMakeAvailable);
        date = sdf.toInstant();

    } catch (Exception e) {
        System.out.println("did not parse: " + timeAtWhichToMakeAvailable);
    }

    LOGGER.info("timeAtWhichToMakeAvailable: " + timeAtWhichToMakeAvailable);
    LOGGER.info("sdf: " + sdf);
    LOGGER.info("parsed to: " + date);
VikR
sumber