Làm cách nào để đặt múi giờ của java.util.Date?


225

Tôi đã phân tích cú pháp java.util.Datetừ a Stringnhưng nó đang đặt múi giờ cục bộ là múi giờ của dateđối tượng.

Múi giờ không được chỉ định trong Stringđó Dateđược phân tích cú pháp. Tôi muốn đặt múi giờ cụ thể của dateđối tượng.

Làm thế nào tôi có thể làm điều đó?


8
Mặc dù không thực sự là câu trả lời cho câu hỏi của bạn, tôi đã sử dụng Joda Time sau khi thấy nó được đề cập ở đây một vài lần. Nó có vẻ hợp lý với tôi hơn các API tiêu chuẩn và có thể thực hiện loại điều này khá dễ dàng.
clstrfsck

2
@msandiford Ngày nay, hãy sử dụng các lớp java.time thay vì Joda-Time. Các Joda thời gian dự án hiện đang trong chế độ bảo trì , với đội ngũ tư vấn chuyển đổi sang các java.time lớp. Xem Hướng dẫn của Oracle .
Basil Bourque

Câu trả lời:


315

Sử dụng DateFormat. Ví dụ,

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

6
nếu ngày được tạo từ lớp Lịch, bạn có thể đặt múi giờ cho Lịch.
lwpro2

19
@ lwpro2 tuyên bố đó là sai lệch; Bạn có thể đặt múi giờ cho một đối tượng Lịch, nhưng nhận được một đối tượng Ngày từ nó bằng phương thức getTime () sẽ trả về một đối tượng Ngày với múi giờ của máy chủ.
BrDaHa

Tôi đang gặp khó khăn để phân tích 02/20/15 14:44. Ai có thể giúp đỡ
MS

@BrDaHa là chính xác. Bạn sẽ cần TimeZone.setDefault()trước khi gọi getTime()để đối tượng ngày mới sẽ ở múi giờ mà bạn muốn. Trong JDK 1.8, Calendar.getTime()các cuộc gọi return new Date(getTimeInMillis());.
jpllosa

182

Xin lưu ý rằng java.util.Datecác đối tượng không chứa bất kỳ thông tin múi giờ nào - bạn không thể đặt múi giờ trên một Dateđối tượng. Thứ duy nhất mà một Datevật thể chứa là một số mili giây kể từ "kỷ nguyên" - ngày 1 tháng 1 năm 1970, 00:00:00 UTC.

Như ZZ Coder hiển thị, bạn đặt múi giờ trên DateFormatđối tượng, để cho nó biết múi giờ bạn muốn hiển thị ngày và giờ.


77
Sau khi Google, thử nghiệm và khám phá, tôi nhận ra đây là một bổ sung chính xác và hữu ích cho câu trả lời - và đáng chú ý: Ngày chỉ chứa giá trị mili giây . Nếu bạn nhìn vào nguồn, có khá nhiều longlĩnh vực được gọi là fastTime. Date.toString()thực sự sử dụng một Calendarđể giải thích thời gian mili giây này. Vì vậy, việc in ra Datelàm cho nó dường như có múi giờ (mặc định), dẫn đến các câu hỏi dễ hiểu về cách đặt múi giờ đó.
David Carboni

3
có thông tin múi giờ IS bên trong các đối tượng Date. Nhưng nó có thể đúng, bạn không thể thay đổi nó.
lwpro2

3
@ Iwpro2, Jesper tuyên bố (và tôi đồng ý) rằng đối tượng Date không lưu trữ múi giờ. Nếu bạn cho rằng múi giờ được lưu trữ bên trong java.util.Date, vui lòng cung cấp tài liệu tham khảo.
Jim

6
@Jim, đây là mã nguồn cho java.util.Date. Nó chứa fastTime là unix epoch cũng như cdate BaseCalWiki.Date được sử dụng để ủng hộ fastTime, nếu nó được xác định. Cái đó chứa thông tin múi giờ. Tôi hiểu nó để một thể hiện Ngày có thể chứa thông tin múi giờ, nhưng nó có thể không.
eis

2
@Jim Tôi không nói bạn có thể thiết lập nó, tôi đã nói rằng nó chứa thông tin này. Tôi cũng không thấy bất kỳ cách nào để thiết lập nó là người dùng. Tôi nghĩ OP đúng ở chỗ nó dường như luôn là múi giờ mặc định của người dùng / hệ thống.
eis

95

tl; dr

Phần mềm phân tích cú pháp từ một múi giờ Chuỗi không được chỉ định. Tôi muốn đặt múi giờ cụ thể

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.

Không có múi giờ trong juDate

Như câu trả lời đúng khác tuyên bố, một java.util.Date không có múi giờ . Nó đại diện cho UTC / GMT (không có bù múi giờ). Rất khó hiểu vì toStringphương thức của nó áp dụng múi giờ mặc định của JVM khi tạo biểu diễn Chuỗi.

Tránh juDate

Vì lý do này và nhiều lý do khác, bạn nên tránh sử dụng java.util.Date & .CalWiki & java.text.SimpleDateFormat tích hợp. Họ nổi tiếng là rắc rối.

Thay vào đó, hãy sử dụng gói java.time đi kèm với Java 8 .

java.time

Các lớp java.time có thể biểu thị một khoảnh khắc trên dòng thời gian theo ba cách:

  • UTC ( Instant)
  • Với phần bù ( OffsetDateTimeZoneOffset)
  • Với múi giờ ( ZonedDateTimevới ZoneId)

Instant

Trong java.time , khối xây dựng cơ bản là Instantmột khoảnh khắc trên dòng thời gian trong UTC. Sử dụng Instantcác đối tượng cho phần lớn logic kinh doanh của bạn.

Instant instant = Instant.now();

OffsetDateTime

Áp dụng offset-từ-UTC để điều chỉnh thời gian đồng hồ treo tường của một số địa phương .

Áp dụng một ZoneOffsetđể có được một OffsetDateTime.

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

ZonedDateTime

Tốt hơn là áp dụng múi giờ , bù trừ cộng với các quy tắc để xử lý các bất thường như Giờ tiết kiệm ánh sáng ban ngày (DST) .

Áp dụng một ZoneIdmột Instantđể có được mộtZonedDateTime . Luôn chỉ định một tên múi giờ thích hợp . Không bao giờ sử dụng 3-4 chữ viết tắt như ESThoặc ISTkhông độc đáo cũng không được tiêu chuẩn hóa.

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

LocalDateTime

Nếu chuỗi đầu vào thiếu bất kỳ chỉ báo nào về offset hoặc vùng, hãy phân tích thành a LocalDateTime.

Nếu bạn chắc chắn về múi giờ dự định, chỉ định a ZoneIdđể sản xuất a ZonedDateTime. Xem ví dụ mã ở trên trong phần tl; dr ở trên cùng.

Chuỗi định dạng

Gọi toString phương thức trên bất kỳ lớp nào trong ba lớp này để tạo Chuỗi biểu thị giá trị thời gian theo định dạng ISO 8601 tiêu chuẩn . Các ZonedDateTimelớp mở rộng định dạng chuẩn bằng cách thêm tên của múi giờ trong ngoặc đơn.

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]

Đối với các định dạng khác sử dụng DateTimeFormatterlớp. Nói chung, tốt nhất để cho lớp đó tạo các định dạng cục bộ bằng cách sử dụng các chuẩn mực văn hóa và ngôn ngữ của con người. Hoặc bạn có thể chỉ định một định dạng cụ thể.


Bảng của tất cả các loại thời gian trong Java, cả hiện đại và di sản


Giới thiệu về java.time

Các java.time khung được xây dựng vào Java 8 và sau đó. Các lớp này thay thế các lớp thời gian cũ kế thừa rắc rối như java.util.Date,Calendar , & SimpleDateFormat.

Các Joda thời gian dự án, bây giờ trong chế độ bảo trì , khuyên chuyển đổi sang các java.time lớp.

Để tìm hiểu thêm, hãy xem Hướng dẫn của Oracle . Và tìm kiếm Stack Overflow cho nhiều ví dụ và giải thích. Đặc điểm kỹ thuật là JSR 310 .

Bạn có thể trao đổi các đối tượng java.time trực tiếp với cơ sở dữ liệu của bạn. Sử dụng trình điều khiển JDBC tương thích với JDBC 4.2 trở lên. Không cần chuỗi, không cần java.sql.*lớp.

Nơi để có được các lớp java.time?

Các ThreeTen-Extra dự án mở rộng java.time với các lớp bổ sung. Dự án này là một nền tảng chứng minh cho các bổ sung có thể trong tương lai cho java.time. Bạn có thể tìm thấy một số các lớp học hữu ích ở đây chẳng hạn như Interval, YearWeek, YearQuarter, và nhiều hơn nữa .


Joda-Thời gian

Trong khi Joda-Time vẫn được duy trì tích cực, các nhà sản xuất của nó đã bảo chúng tôi chuyển sang java.time ngay khi thuận tiện. Tôi để phần này nguyên vẹn như một tài liệu tham khảo, nhưng tôi đề nghị sử dụng java.timephần trên thay thế.

Trong Joda-Time , một đối tượng thời gian ( DateTime) thực sự biết múi giờ được chỉ định của nó. Điều đó có nghĩa là sự bù đắp từ UTC các quy tắc và lịch sử của Giờ tiết kiệm ánh sáng ban ngày (DST) của múi giờ đó và các dị thường khác.

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

Gọi toStringphương thức để tạo Chuỗi theo định dạng ISO 8601 .

String output = dateTimeIndia.toString();

Joda-Time cũng cung cấp các khả năng phong phú để tạo tất cả các loại định dạng Chuỗi khác.

Nếu được yêu cầu, bạn có thể chuyển đổi từ Joda-Time DateTime sang java.util.Date.

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

Tìm kiếm StackOverflow cho "ngày joda" để tìm thêm nhiều ví dụ, một số khá chi tiết.


Trên thực tế có một múi giờ nhúng trong một java.util.Date, được sử dụng cho một số chức năng nội bộ (xem bình luận trên trả lời này). Nhưng múi giờ nội bộ này không được hiển thị như một tài sản và không thể được đặt. Múi giờ nội bộ này không phải là múi giờ được sử dụng bởi toStringphương thức trong việc tạo biểu diễn chuỗi của giá trị thời gian ngày; thay vào đó, múi giờ mặc định hiện tại của JVM được áp dụng nhanh chóng. Vì vậy, như cách viết tắt, chúng ta thường nói là ju juDate không có múi giờ. Gây nhầm lẫn? Đúng. Một lý do khác để tránh những lớp học cũ mệt mỏi.


3
"Không có múi giờ trong juDate" là sai. Có một thông tin múi giờ trong juDate được lưu trữ trong thuộc tính của nó BaseCalendar.Date cdatenếu được đặt. Hãy xem mã nguồn ở đây . Bạn không thể đặt múi giờ của đối tượng juDate trừ khi thay đổi múi giờ mặc định của JVM bằng cách gọi TimeZone.setDefault(TimeZone.getTimeZone("NEW_TIME_ZONE"));. Do đó, có phần bù múi giờ và bạn có thể lấy phần bù bằng cách gọi phương thức không dùng nữa juDate.getTimezone Offerset ()
Thai Bùi

3
@blquythai Đúng, bạn đã làm bài tập về nhà của bạn. Cũng như tôi, đã thấy mã nguồn trước đó. Có một múi giờ chôn ở đó. Nhưng đối với tất cả các mục đích thực tế mà múi giờ bị bỏ qua. Một java.util.Date hoạt động mà không có bất kỳ múi giờ nào, thực tế là trong UTC, trong khi bỏ qua múi giờ bị chôn vùi đó. Ngoại trừ toStringphương thức áp dụng múi giờ mặc định hiện tại của JVM; lại bỏ qua múi giờ bị chôn vùi. Vì vậy, để cho ngắn gọn, chúng tôi nói java.util.Date không có múi giờ. Giống như nghệ thuật , đó là một lời nói dối nói lên sự thật.
Basil Bourque

@blquythai Đối với cách gọi TimeZone.setDefault, bạn không đặt múi giờ của đối tượng java.util.Date - đối tượng Date vẫn bỏ qua múi giờ bị chôn vùi, hoạt động hiệu quả trong UTC. Bạn sẽ ảnh hưởng đến toStringphương pháp của Date . Đặt mặc định thay đổi múi giờ mặc định của JVM thường được đặt thành múi giờ của hệ điều hành máy chủ. Cuộc gọi đó không được khuyến khích vì nó ảnh hưởng đến tất cả các mã trong tất cả các luồng của tất cả các ứng dụng đang chạy trong JVM đó và thực hiện ngay khi chúng đang thực thi. Là thô lỗ và nguy hiểm, cuộc gọi đó chỉ nên được coi là phương sách cuối cùng.
Basil Bourque

5
Múi giờ đó được sử dụng rất thường xuyên (sử dụng trong equals, hashcode, getTime..) Nếu bạn có một cái nhìn tại các equalsphương pháp, nó gọi getTime()mà các cuộc gọi getTimeImpl(), các cuộc gọi mà normalize()nếu cdatebất động sản không được bình thường hóa. Trong normalize()phương thức, điều kiện cuối cùng nếu tính lại mili giây kể từ 1/1/70 dựa trên thông tin múi giờ được lưu trữ của nó nếu múi giờ cdatekhác với múi giờ của môi trường JVM hiện tại mà nó đang chạy. (Hãy xem sun.util.calendar.AbstractCalendar getCalendarDate(long millis, CalendarDate date))
Thai Bùi

2
@MichalM Theo lời khuyên của bạn, một thời gian trước tôi đã thêm một lời bạt về khu vực nhúng.
Basil Bourque

75

Bạn cũng có thể đặt múi giờ ở mức 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);

đầu ra:

Thu Sep 05 10:11:12 EDT 2013
Thu Sep 05 14:11:12 UTC 2013

Điều này đã giúp tôi. thiết lập timeZone trong SDF đã không tạo ra bất kỳ sự khác biệt nào
vishnu viswanath

Tôi nghĩ rằng nó đáng tin cậy hơn. (+1)
foobar

23
Cẩn thận: Gọi điện TimeZone.setDefaultkhá quyết liệt, vì nó ảnh hưởng đến toàn bộ JVM, ảnh hưởng đến tất cả các đối tượng và luồng khác. Xem câu trả lời này để biết chi tiết bao gồm cả các biến chứng thậm chí nhiều hơn nếu bạn đang chạy với SecurityManager. Thêm sự phức tạp hơn nữa: Hành vi này đã thay đổi trong các phiên bản khác nhau của Java, như được thảo luận trong Câu hỏi này .
Basil Bourque

Điều này đặt một múi giờ chung trên tất cả các luồng sinh ra sau tuyên bố này, phải không?
Jaydev

Đây là một câu trả lời tốt hơn cho câu hỏi hơn là câu hỏi được chấp nhận.
francogrex

10

Nếu bạn phải làm việc với chỉ các lớp JDK tiêu chuẩn, bạn có thể sử dụng điều này:

/**
 * 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;
}

Tín dụng đi đến bài này .


1
Độ lệch của các múi giờ không phải lúc nào cũng là hằng số. Trên thực tế, chúng có thể thay đổi vì lý do địa chính trị. TimeZone.getDSTSavings () không tính đến điều này và luôn trả về giá trị bù hiện tại . Điều này có nghĩa là bạn có thể nhận được một chuyển đổi sai trong trường hợp bạn đang xử lý một ngày lịch sử với fromTimeZone / toTimeZone đã thay đổi bù trừ kể từ ngày đó.
andrerobot

6

java.util.Calendarlà cách thông thường để xử lý các múi giờ chỉ bằng các lớp JDK. Apache Commons có một số lựa chọn / tiện ích khác có thể hữu ích. Chỉnh sửa ghi chú của Spong nhắc nhở tôi rằng tôi đã nghe những điều thực sự hay về Joda-Time (mặc dù tôi đã không sử dụng nó).


+1 cho Thời gian Joda. Mặc dù nó không cung cấp bất kỳ chức năng bổ sung nào mà bạn không thể có được từ API Java tiêu chuẩn (mà tôi đã tìm thấy trong mọi trường hợp - rất vui khi được hiển thị theo cách khác), Joda Time thực hiện một số nhiệm vụ dễ dàng hơn.
Joshua Hutchison

2
@JoshuaHutchison Joda-Time có hàng tấn chức năng bổ sung. Ví dụ: Đại diện cho nhịp của thời gian với các lớp học Period, DurationInterval. Những nhịp bao gồm phương pháp so sánh như contains, abuts, overlap, và gap. Và PeriodFormatterBuildercó thể xây dựng các cụm từ mô tả như "15 năm 8 tháng".
Basil Bourque

1

Chuyển đổi Date thành String và thực hiện với 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);

1
Câu trả lời trên Stack Overflow dự kiến ​​sẽ có một số thảo luận hoặc giải thích. Trang web này có nghĩa là nhiều hơn một thư viện đoạn mã.
Basil Bourque

cảm ơn bạn @Basil Bourque vì bình luận của bạn. Tôi chỉnh sửa câu trả lời
avisper

0

Nếu bất cứ ai cần điều này, nếu bạn cần chuyển đổi XMLGregorianCalendarmúi giờ thành múi giờ hiện tại của mình từ UTC, thì tất cả những gì bạn cần làm là đặt múi giờ thành 0, sau đó gọi toGregorianCalendar()- nó sẽ giữ nguyên múi giờ, nhưng Datebiết cách chuyển đổi nó thành của bạn, vì vậy bạn có thể lấy dữ liệu từ đó.

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

Kết quả:

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

0

Mã này rất hữu ích trong một ứng dụng tôi đang làm việc:

    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);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.