如何设置java.util.Date的时区?


225

java.util.Date从a 解析了一个,String但是它将本地时区设置为date对象的时区。

时区没有指定String从中Date解析。我想设置date对象的特定时区。

我怎样才能做到这一点?


8
虽然不是您问题的真正答案,但我在这里多次提到它后,就使用了Joda Time。在我看来,这比标准API更合理,并且可以很轻松地完成这种事情。
clstrfsck,2010年

2
@msandiford现在,使用java.time类而不是Joda-Time。该乔达时间的项目现在处于维护模式,与团队的建议迁移java.time类。请参见Oracle教程
罗勒·布尔克

Answers:


315

使用DateFormat。例如,

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
如果日期是从Calendar类创建的,则可以设置Calendar的时区。
lwpro2

19
@ lwpro2该陈述具有误导性;您可以为Calendar对象设置时区,但是使用getTime()方法从其中获取Date对象时,将返回带有主机计算机时区的Date对象。
BrDaHa 2014年

我在解析02/20/15 14:44时遇到麻烦。谁能帮助
MS

@BrDaHa是正确的。TimeZone.setDefault()在调用之前getTime(),您需要这样做,以便新的日期对象将位于所需的时区中。在JDK 1.8中,Calendar.getTime()调用return new Date(getTimeInMillis());
jpllosa

182

请注意,java.util.Date对象本身不包含任何时区信息-您无法在Date对象上设置时区。Date对象包含的唯一内容是自“历元”(1970年1月1日UTC时间00:00:00)以来的毫秒数。

如ZZ Coder所示,您在DateFormat对象上设置了时区,以告诉它要在哪个时区中显示日期和时间。


77
经过谷歌搜索,试验和删除之后,我意识到这是对答案的精确且有用的补充-并且值得强调:日期包含毫秒值。如果您查看来源,则几乎只有一个long名为的字段fastTimeDate.toString()实际上使用了一个Calendar毫秒来解释。因此,打印出一个Date使它似乎具有一个(默认)时区,从而导致有关如何设置该时区的可理解的问题。
大卫·卡波尼

3
在Date对象中有时区信息。但这可能是事实,您无法更改。
lwpro2

3
@ Iwpro2,Jesper声称(并且我同意)Date对象不存储时区。如果您声称时区存储在java.util.Date中,请提供参考。
吉姆(Jim)

6
@Jim,是java.util.Date的源代码。它包含unix纪元的fastTime以及支持fastTime(如果已定义)的BaseCalendar.Date cdate。那个包含时区信息。我理解它,以便Date实例可以包含时区信息,但可能没有。
2013年

2
@Jim我不是说您可以设置它,而是说它包含此信息。我也看不到将其设置为用户的任何方式。我认为OP是正确的,因为它似乎始终是用户/系统的默认时区。
2013年

95

tl; dr

…从字符串中解析…未指定时区…我想设置一个特定的时区

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.

juDate中没有时区

正如其他正确答案所述,java.util.Date没有时区。它代表UTC / GMT(无时区偏移)。非常令人困惑,因为toString在生成String表示形式时,其方法应用了JVM的默认时区。

避免juDate

由于这个原因和许多其他原因,您应该避免使用内置的java.util.Date和.Calendar和java.text.SimpleDateFormat。他们出了名的麻烦。

而是使用与Java 8捆绑在一起的java.time包

java.time

java.time类可以通过三种方式在时间轴上表示时刻:

  • 世界标准时间(Instant
  • 有偏移量(OffsetDateTimeZoneOffset
  • 随着时区(ZonedDateTimeZoneId

Instant

java.time中,基本构造块是InstantUTC时间轴上的时刻。Instant在大多数业务逻辑中使用对象。

Instant instant = Instant.now();

OffsetDateTime

应用UTC偏移量以适应当地的挂钟时间

申请ZoneOffset获得OffsetDateTime

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

ZonedDateTime

更好的方法是应用时区,偏移量以及处理异常的规则,例如夏令时(DST)

套用ZoneIdInstant获得ZonedDateTime。始终指定正确的时区名称。切勿使用3-4的缩写,例如ESTIST既不是唯一的也不是标准化的。

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

LocalDateTime

如果输入字符串缺少偏移量或区域指示符,请解析为LocalDateTime

如果您确定要使用的时区,请分配一个ZoneId以产生ZonedDateTime。请参见上面的tl; dr部分中的代码示例。

格式化字符串

toString在这三个类中的任何一个上调用方法,以生成表示标准ISO 8601格式的日期时间值的String 。该ZonedDateTime班通过附加括号中的时区的名称,扩展了标准格式。

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]

对于其他格式,请使用DateTimeFormatter该类。通常最好让该类使用用户期望的人类语言和文化规范生成本地化格式。或者,您可以指定特定格式。


Java中所有日期和时间类型的表,包括现代的和传统的


关于java.time

java.time框架是建立在Java 8和更高版本。这些类取代麻烦的老传统日期时间类,如java.util.DateCalendar,和SimpleDateFormat

现在处于维护模式Joda-Time项目建议迁移到java.time类。

要了解更多信息,请参见Oracle教程。并在Stack Overflow中搜索许多示例和说明。规格为JSR 310

您可以直接与数据库交换java.time对象。使用与JDBC 4.2或更高版本兼容的JDBC驱动程序。不需要字符串,不需要类。java.sql.*

在哪里获取java.time类?

ThreeTen-额外项目与其他类扩展java.time。该项目为将来可能在java.time中添加内容提供了一个试验场。你可能在这里找到一些有用的类,比如IntervalYearWeekYearQuarter,和更多


乔达时代

尽管仍积极维护Joda-Time,但其制造商已告诉我们在方便时尽快迁移到java.time。我保留本节的内容作为参考,但我建议改用java.time上面的节。

Joda-Time中,日期时间对象(DateTime)确实知道其分配的时区。这意味着与UTC 以及该时区的夏令时(DST)的规则和历史记录以及其他此类异常情况有所偏差。

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

调用该toString方法以生成ISO 8601格式的字符串。

String output = dateTimeIndia.toString();

Joda-Time还提供了用于生成各种其他String格式的丰富功能。

如果需要,可以将Joda-Time DateTime转换为java.util.Date。

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

在StackOverflow中搜索“ joda date”,以找到更多示例,其中一些非常详细。


其实有嵌入在java.util.Date一个时区,用于一些内部功能(请参阅本答案的评论)。但是,此内部时区未公开为属性,因此无法设置。此内部时区不是toString方法用于生成日期时间值的字符串表示形式的时区;相反,JVM的当前默认时区是即时应用的。因此,作为简写,我们经常说“ juDate没有时区”。令人困惑?是。避免这些疲倦的老阶级的另一个原因。


3
“ juDate中没有时区”是错误的。BaseCalendar.Date cdate如果已设置,则juDate中会存储在其属性中的时区信息。在这里看一下源代码。您无法设置juDate对象的时区,除非通过调用来更改JVM的默认时区TimeZone.setDefault(TimeZone.getTimeZone("NEW_TIME_ZONE"));。因此,存在时区偏移量,您可以通过调用不赞成使用的方法juDate.getTimezoneOffset()
Thai Bui

3
@blquythai是的,您做了功课。和我一样,之前已经看过该源代码。还有就是埋在那里的时区。但是出于所有实际目的,时区将被忽略。一个java.util.Date可以在没有任何时区的情况下工作,实际上是在UTC中,而忽略了该时区。除了toString采用JVM当前默认时区的方法外;再次忽略了时区。因此,为了简便起见,我们说java.util.Date没有时区。像艺术一样,这是谎言,说实话。
罗勒·布尔克

@blquythai至于调用TimeZone.setDefault,您没有设置java.util.Date对象的时区-Date 对象仍然忽略其掩藏的时区,在UTC中有效。您将影响Date的toString方法。设置默认值会更改JVM的默认时区,通常将其设置为主机操作系统的时区。不建议使用该调用,因为它会影响该JVM中运行的所有应用程序的所有线程中的所有代码,并且在执行过程中会即时执行。由于粗鲁和危险,该呼叫仅应视为万不得已。
罗勒·布尔克

5
该时区的使用非常普遍(在使用equalshashcodegetTime。)如果你在看看equals方法,它调用getTime()该电话getTimeImpl(),该电话normalize()如果cdate属性不归。在normalize()方法中,如果的时区cdate不同于运行它的当前JVM环境的时区,则上一个if条件会根据其存储的时区信息重新计算自1/1/70以来的毫秒数。(看一看sun.util.calendar.AbstractCalendar getCalendarDate(long millis, CalendarDate date)
Thai Bui 2015年

2
@MichalM根据您的建议,前段时间我添加了有关嵌入式区域的后记。
罗勒·布尔克

75

您还可以在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);

输出:

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

这对我有帮助。在SDF中设置timeZone并没有造成任何差异
Vishnu viswanath

我认为它更可靠。(+1)
foob​​ar

23
当心:调用TimeZone.setDefault相当激烈,因为它会影响整个JVM,并影响所有其他对象和线程。如果使用SecurityManager运行,请参阅此答案以获取详细信息,包括更多的复杂性。更增加了复杂性:如本Question所述,此行为在Java的各种版本中已更改。
罗勒·布尔克

这将在此语句之后产生的所有线程之间设置一个公共时区,对吗?
Jaydev '16

这个问题比公认的问题更好的答案。
francogrex

10

如果必须仅使用标准JDK类,则可以使用以下方法:

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

幸得此职位


1
时区的偏移量并不总是恒定的。实际上,它们可能由于地缘政治原因而发生变化。TimeZone.getDSTSavings()并未考虑到这一点,而是始终返回当前偏移量。这意味着,如果您要处理的历史日期的fromTimeZone / toTimeZone自该日期以来已更改了偏移量,则可能会导致错误的转换。
andrerobot

6

java.util.Calendar是仅使用JDK类处理时区的常用方法。Apache Commons还有一些其他的替代品/实用程序可能会有所帮助。编辑 Spong的笔记提醒我,我听说过有关Joda-Time的真正好消息(尽管我自己并未使用过)。


乔达时间+1。尽管它没有提供您无法从标准Ja​​va API中获得的任何其他功能(无论如何,我都很高兴找到它,否则我们将很高兴看到它),但Joda Time确实使某些任务变得更加容易。
Joshua Hutchison

2
@JoshuaHutchison Joda-Time具有大量附加功能。例如:表示与所述类别的时间跨度PeriodDurationInterval。那些跨距包括比较方法,例如containsabutsoverlap,和gap。并且PeriodFormatterBuilder可以建立描述性短语,例如“ 15年零8个月”。
罗勒·布尔克

1

将Date转换为String并使用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
关于堆栈溢出的答案应该进行一些讨论或解释。该站点不只是一个代码段库。
罗勒·布尔克

谢谢@Basil Bourque的评论。我编辑答案
阿维斯珀

0

如果有人需要,如果您需要将XMLGregorianCalendar时区从UTC 转换为当前时区,那么您所需要做的就是将时区设置为0,然后调用toGregorianCalendar()-它会保持相同的时区,但是Date知道如何将其转换为您的,因此您可以从那里获取数据。

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

结果:

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

0

该代码对我正在使用的应用程序很有帮助:

    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);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.