如何设置 java.util.Date 的时区?
Posted
技术标签:
【中文标题】如何设置 java.util.Date 的时区?【英文标题】:How to set time zone of a java.util.Date? 【发布时间】:2011-02-22 21:32:29 【问题描述】:我已经从 String
解析了一个 java.util.Date
,但它将本地时区设置为 date
对象的时区。
在解析Date
的String
中未指定时区。我想为date
对象设置一个特定的时区。
我该怎么做?
【问题讨论】:
虽然不是真正回答您的问题,但在看到这里提到几次后,我使用了 Joda Time。在我看来,它比标准 API 更合理,并且可以很容易地完成这类事情。 @msandiford 如今,使用 java.time 类而不是 Joda-Time。 Joda-Time 项目现在位于 maintenance mode,团队建议迁移到 java.time 类。见Tutorial by Oracle。 【参考方案1】:使用日期格式。例如,
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");
【讨论】:
如果日期是从日历类创建的,您可以设置日历的时区。 @lwpro2 该声明具有误导性;您可以为 Calendar 对象设置时区,但使用 getTime() 方法从中获取 Date 对象将返回带有主机时区的 Date 对象。 @BrDaHa 是正确的。在调用getTime()
之前,您需要TimeZone.setDefault()
,以便新的日期对象位于您想要的时区。在 JDK 1.8 中,Calendar.getTime()
调用 return new Date(getTimeInMillis());
。
警告:new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");要获取本地格式,请使用 getDateInstance()、getDateTimeInstance() 或 getTimeInstance(),或使用 new SimpleDateFormat(String template, Locale locale),例如 Locale.US 用于 ASCII 日期。【参考方案2】:
请注意,java.util.Date
对象本身不包含任何时区信息 - 您不能在 Date
对象上设置时区。 Date
对象包含的唯一内容是自“纪元”(1970 年 1 月 1 日 00:00:00 UTC)以来的毫秒数。
正如 ZZ Coder 所示,您在 DateFormat
对象上设置时区,以告诉它您要在哪个时区显示日期和时间。
【讨论】:
在谷歌搜索、试验和耗尽之后,我意识到这是对答案的一个精确且有用的补充——值得强调:日期仅包含毫秒值。如果您查看源代码,则几乎只有一个名为fastTime
的 long
字段。 Date.toString()
实际上使用 Calendar
来解释这个毫秒时间。因此,打印出Date
使其看起来具有(默认)时区,从而引发关于如何设置该时区的可理解问题。
在 Date 对象中有时区信息。但它可能是真的,你不能改变它。
@Iwpro2,Jesper 声称(我同意) Date 对象不存储时区。如果您声称时区存储在 java.util.Date 中,请提供参考。
@Jim, this 是 java.util.Date 的源代码。它包含作为 unix 纪元的 fastTime 以及用于支持 fastTime 的 BaseCalendar.Date cdate(如果已定义)。那个包含时区信息。我理解它,因此 Date 实例可以包含时区信息,但它可能不会。
@Jim 我不是说你可以设置它,我是说它包含这些信息。我也看不到任何将其设置为用户的方法。我认为 OP 是正确的,因为它似乎始终是用户/系统默认时区。【参考方案3】:
tl;博士
...解析...从字符串...时区未指定...我想设置一个特定的时区
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.
j.u.Date 中没有时区
正如其他正确答案所述,java.util.Date 没有时区†。它代表UTC/GMT(无时区偏移)。非常令人困惑,因为它的toString
方法在生成字符串表示时应用了 JVM 的默认时区。
避免 j.u.Date
出于这个和许多其他原因,您应该避免使用内置的 java.util.Date & .Calendar & java.text.SimpleDateFormat。它们是出了名的麻烦。
改为使用与Java 8 捆绑的java.time package。
java.time
java.time 类可以通过三种方式表示时间轴上的时刻:
UTC (Instant
)
带有偏移量(OffsetDateTime
和 ZoneOffset
)
带有时区(ZonedDateTime
和 ZoneId
)
Instant
在java.time 中,基本构建块是Instant
,即UTC 时间线上的一个时刻。将Instant
对象用于您的大部分业务逻辑。
Instant instant = Instant.now();
OffsetDateTime
应用offset-from-UTC 以适应某些地区的wall-clock time。
申请ZoneOffset
以获得OffsetDateTime
。
ZoneOffset zoneOffset = ZoneOffset.of( "-04:00" );
OffsetDateTime odt = OffsetDateTime.ofInstant( instant , zoneOffset );
ZonedDateTime
更好的是应用time zone,一个偏移量加上处理异常的规则,例如Daylight Saving Time (DST)。
将ZoneId
应用于Instant
以获得ZonedDateTime
。始终指定proper time zone name。切勿使用既不唯一也不标准化的 3-4 个缩写词,例如 EST
或 IST
。
ZoneId zoneId = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = ZonedDateTime.ofInstant( instant , zoneId );
LocalDateTime
如果输入字符串缺少任何偏移或区域指示符,则解析为LocalDateTime
。
如果您确定预期的时区,请分配 ZoneId
以生成 ZonedDateTime
。请参阅上面 tl;dr 部分中的代码示例。
格式化字符串
在这三个类中的任何一个上调用toString
方法,以生成一个表示标准ISO 8601 格式的日期时间值的字符串。 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.time
java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.Date
、Calendar
和 SimpleDateFormat
。
要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310。
Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。
您可以直接与您的数据库交换 java.time 对象。使用符合JDBC 4.2 或更高版本的JDBC driver。不需要字符串,不需要java.sql.*
类。 Hibernate 5 & JPA 2.2 支持 java.time。
从哪里获取 java.time 类?
Java SE 8、Java SE 9、Java SE 10、Java SE 11 和更高版本 - 具有捆绑实现的标准 Java API 的一部分。 Java 9 带来了一些小功能和修复。 Java SE 6 和 Java SE 7 大部分 java.time 功能在ThreeTen-Backport 中向后移植到 Java 6 和 7。 Android java.time 类的更高版本的 android (26+) 捆绑实现。 对于早期的 Android (API desugaring 的进程带来了最初未内置于 Android 中的 subset of the java.time 功能。 如果脱糖不能满足您的需求,ThreeTenABP 项目会将ThreeTen-Backport(如上所述)适配到 Android。见How to use ThreeTenABP…。乔达时间
虽然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。
String output = dateTimeIndia.toString();
Joda-Time 还提供丰富的功能来生成各种其他字符串格式。
如果需要,您可以将 Joda-Time DateTime 转换为 java.util.Date。
Java.util.Date date = dateTimeIndia.toDate();
在 *** 中搜索“joda date”以找到更多示例,其中一些非常详细。
†实际上有一个嵌入在 java.util.Date 中的时区,用于某些内部功能(请参阅此答案中的 cmets)。但是这个内部时区没有作为属性公开,也不能设置。这个内部时区不是toString
方法在生成日期时间值的字符串表示时使用的时区;而是即时应用 JVM 当前的默认时区。因此,作为简写,我们常说“j.u.Date 没有时区”。令人困惑?是的。避免这些乏味的旧课程的另一个原因。
【讨论】:
“j.u.Date 中没有时区”是错误的。 j.u.Date 中的时区信息存储在其BaseCalendar.Date cdate
属性中(如果已设置)。看看源代码here。您不能设置 j.u.Date 对象的时区,除非通过调用 TimeZone.setDefault(TimeZone.getTimeZone("NEW_TIME_ZONE"));
更改 JVM 的默认时区。因此,存在时区偏移量,您可以通过调用已弃用的方法 j.u.Date.getTimezoneOffset() 来获取偏移量
@blquythai 正确,你做了功课。和我一样,之前看过那个源代码。 是一个时区埋在那里。但出于所有实际目的,该时区被忽略。 java.util.Date 在没有任何时区的情况下工作,实际上是在 UTC 中,而忽略了隐藏的时区。 toString
方法除外,它应用 JVM 当前的默认时区;再次忽略了埋藏的时区。所以为简洁起见,我们说 java.util.Date 没有时区。 Like Art,这是一个说真话的谎言。
@blquythai 至于调用TimeZone.setDefault
,你没有设置java.util.Date对象的时区——Date对象仍然忽略它的隐藏时区,在 UTC 中有效执行。你会影响 Date 的 toString
方法。设置默认值会更改 JVM 的默认时区,该时区通常设置为主机操作系统的时区。 不建议使用该调用,因为它会影响在该 JVM 中运行的所有应用程序的所有线程中的所有代码,并且会在它们执行时即时执行。由于粗鲁和危险,这个电话只能被视为最后的手段。
那个时区经常被使用(用在equals
, hashcode
, getTime
..)如果你看一下equals
方法,它调用getTime()
调用getTimeImpl()
,如果cdate
属性未标准化,则调用normalize()
。在normalize()
方法中,如果cdate
的时区与其运行的当前JVM 环境的时区不同,则最后一个if 条件根据其存储的时区信息重新计算自70 年1 月1 日以来的毫秒数。 (看看sun.util.calendar.AbstractCalendar getCalendarDate(long millis, CalendarDate date)
)
@MichalM 根据您的建议,前段时间我添加了关于嵌入式区域的后记。【参考方案4】:
您还可以在 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 并没有产生任何差异 我认为它更可靠。 (+1) 当心:调用TimeZone.setDefault
相当激烈,因为它会影响整个JVM,影响所有其他对象和线程。请参阅this answer 了解详细信息,包括如果您使用 SecurityManager 运行的更复杂的情况。更复杂的是:如this Question 中所述,这种行为在不同版本的 Java 中发生了变化。
这会在此语句之后生成的所有线程中设置一个公共时区,对吧?
这是一个比接受的问题更好的答案。【参考方案5】:
如果你必须只使用标准的 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;
归功于post。
【讨论】:
时区的偏移量并不总是恒定的。事实上,由于地缘政治原因,它们可能会发生变化。 TimeZone.getDSTSavings() 没有考虑到这一点,并且总是返回 current 偏移量。这意味着,如果您处理的历史日期包含自该日期以来已更改偏移量的 fromTimeZone/toTimeZone,则您可能会得到错误的转换。【参考方案6】:java.util.Calendar
是仅使用 JDK 类来处理时区的常用方法。 Apache Commons 有一些可能有用的替代方案/实用程序。 编辑 Spong 的笔记提醒我,我听说过关于 Joda-Time 的好消息(虽然我自己没有使用过)。
【讨论】:
乔达时间 +1。虽然它没有提供您无法从标准 Java API 获得的任何附加功能(无论如何我都发现了 - 很高兴以其他方式显示),但 Joda Time 确实使一些任务变得更容易。 @JoshuaHutchison Joda-Time 有 吨 的附加功能。示例:用Period
、Duration
和 Interval
类表示时间跨度。这些跨度包括比较方法,例如contains
、abuts
、overlap
和gap
。而PeriodFormatterBuilder
可以构建描述性短语,例如“15 年零 8 个月”。【参考方案7】:
在这里您可以获取“2020-03-11T20:16:17”之类的日期并返回“11/Mar/2020 - 20:16”
private String transformLocalDateTimeBrazillianUTC(String dateJson) throws ParseException
String localDateTimeFormat = "yyyy-MM-dd'T'HH:mm:ss";
SimpleDateFormat formatInput = new SimpleDateFormat(localDateTimeFormat);
//Here is will set the time zone
formatInput.setTimeZone(TimeZone.getTimeZone("UTC-03"));
String brazilianFormat = "dd/MMM/yyyy - HH:mm";
SimpleDateFormat formatOutput = new SimpleDateFormat(brazilianFormat);
Date date = formatInput.parse(dateJson);
return formatOutput.format(date);
【讨论】:
TimeZone.getTimeZone("UTC-03")
生成 GMT。我怀疑这就是你现在的想法。不是你。麻烦的是TImeZone
类。我建议我们不要使用Date
、SimpleDateFormat
或TimeZone
。而是使用 the answer by Basil Bourque 中解释的 java.time。
在我的电脑上你的代码给了11/mar./2020 - 21:16
。注意时间是21:16,是错误的。我建议LocalDateTime.parse("2020-03-11T20:16:17").atZone(ZoneId.of("America/Bahia"))
。它产生2020-03-11T20:16:17-03:00[America/Bahia]
。您当然可以按照您想要的方式对其进行格式化。为此使用DateTimeFormatter
。【参考方案8】:
将日期转换为字符串并使用 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);
【讨论】:
关于 Stack Overflow 的回答预计会有一些讨论或解释。这个网站不仅仅是一个代码 sn-p 库。 感谢@Basil Bourque 的评论。我编辑答案【参考方案9】:这段代码对我正在开发的应用很有帮助:
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);
【讨论】:
【参考方案10】:如果有人需要这个,如果您需要将 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
【讨论】:
【参考方案11】:这个答案可能是最短的,它只使用 Date 类:
long current = new Date().getTime() + 3_600_000; //e.g. your JVM time zone +1 hour (3600000 milliseconds)
System.out.printf("%1$td.%1$tm.%1$tY %1$tH:%1$tM\n", current);//european time format
但是,如果可以,请使用更现代的方法来做同样的事情。
【讨论】:
但是错了。这是假的。您正在格式化一个不同的时间点,以使其看起来它位于不同的时区。 但是,如果可以,请使用更现代的方法来做同样的事情。我非常同意。你可以。如果您没有邪恶的老板强迫您使用 Java 1.4 或更早版本,那么您可以。都在the good answer by Basil Bourque。以上是关于如何设置 java.util.Date 的时区?的主要内容,如果未能解决你的问题,请参考以下文章
java.util.Date和java.sql.Date的区别及应用
java8 LocalDateTime/LocalDate/LocalTimejava.util.Date/java.sql.Date区别及日期转换方法