我正在努力解决时间问题,我偶然发现Java中的某些东西让我感到有些困惑。拿这个示例代码:
public static void main(String[] args)
{
//Calendar set to 12:00 AM of the current day (Eastern Daylight Time)
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT-4"));
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
/////
//Calendar set to 12:00 AM of the current day (UTC time)
Calendar utcCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
utcCal.set(Calendar.HOUR_OF_DAY, 0);
utcCal.set(Calendar.MINUTE, 0);
utcCal.set(Calendar.SECOND, 0);
utcCal.set(Calendar.MILLISECOND, 0);
/////
long oneHourMilliseconds = 3600000;
System.out.println((cal.getTimeInMillis() - utcCal.getTimeInMillis()) / oneHourMilliseconds);
}
我可视化计算cal
所代表的时间的算法,采用以下两种形式之一:
Epoch - 4 * oneHourMilliseconds
)的毫秒数。这两种算法都应该产生比utcCal
落后4小时的结果,但运行代码会返回4
。
有人可以向我解释为什么cal
虽然被设置为比utcCal
落后4小时的时区,但最终在utcCal之后4小时达到了毫秒值?代码不应该返回-4
吗?
这是一段不幸的历史。 ID为“GMT-4”的时区是您期望的“UTC + 4”,即它比UTC早4个小时。
来自tzdb的etcetera文件:
# We use POSIX-style signs in the Zone names and the output abbreviations,
# even though this is the opposite of what many people expect.
# POSIX has positive signs west of Greenwich, but many people expect
# positive signs east of Greenwich. For example, TZ='Etc/GMT+4' uses
# the abbreviation "GMT+4" and corresponds to 4 hours behind UTC
# (i.e. west of Greenwich) even though many people would expect it to
# mean 4 hours ahead of UTC (i.e. east of Greenwich).
如果您可以完全管理它,请避免定义和使用GMT时区......
日历cal
设置为时区2012-04-18 00:00:00
的GMT-4
。
那个时刻对应于2012-04-18 04:00:00
的UTC
(换句话说,当它在时区GMT-4
上午12点时,它是在UTC
上午4点)。
日历utcCal
设置为时区2012-04-18 00:00:00
的UTC
。
2012-04-18 04:00:00
和2012-04-18 00:00:00
之间的差异是4小时,所以你看到4被打印。
其他答案是正确的。在各种情况下,与UTC值偏移的+
/ -
具有相反的含义。
在Java 8及更高版本中内置的现代java.time类的上下文中:
+
-
因此,目前在印度(Asia/Kolkata
)使用的偏移量为+05:30
,比UTC早5个半小时。在加拿大的America/Regina
地区,目前使用的偏移是-06:00
,比UTC晚六个小时。
这里的一个重要提示是,每当知道时,总是使用proper time zone names而不是仅仅偏移。
offset-from-UTC只需要几小时,几分钟和几秒钟;仅此而已。 time zone是某个地区人民所使用的抵消的过去,现在和未来变化的历史。因此区域总是优于偏移。
以proper time zone name的格式指定continent/region
,例如America/Montreal
,Africa/Casablanca
或Pacific/Auckland
。切勿使用诸如EST
或IST
之类的3-4字母缩写,因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。
ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
时区对于确定日期至关重要。对于任何给定的时刻,日期在全球范围内因地区而异。例如,在Paris France午夜过后几分钟是新的一天,而在Montréal Québec仍然是“昨天”。
LocalDate ld = LocalDate.now( z ) ;
比UTC早4个小时,请使用Asia/Dubai
等时区。
ZonedDateTime zdt = ZonedDateTime.now( ZoneId.of( `Asia/Dubai` ) ) ;
zdt.toString():2018-03-01T02:39:18.801642 + 04:00 [亚洲/迪拜]
比UTC晚四个小时,使用时区如America/Blanc-Sablon
。
ZonedDateTime zdt = ZonedDateTime.now( ZoneId.of( `America/Blanc-Sablon` ) ) ;
zdt.toString():2018-02-28T18:39:18.801642-04:00 [America / Blanc-Sablon]
ZoneOffset
如果您不确定预期的时区,并且只有UTC的偏移量,请使用ZoneOffset
类。
ZoneOffset offset = ZoneOffset.ofHoursMinutes( 4 , 30 ) ; // Positive hours is ahead of UTC (east), while negative hours is behind UTC (west).
对于UTC本身(偏移量为零),请使用常量ZoneOffset.UTC
。
java.time框架内置于Java 8及更高版本中。这些类取代了麻烦的旧legacy日期时间类,如java.util.Date
,Calendar
和SimpleDateFormat
。
现在在Joda-Time的maintenance mode项目建议迁移到java.time班。
要了解更多信息,请参阅Oracle Tutorial。并搜索Stack Overflow以获取许多示例和解释。规格是JSR 310。
您可以直接与数据库交换java.time对象。使用符合JDBC driver或更高版本的JDBC 4.2。不需要字符串,不需要java.sql.*
类。
从哪里获取java.time类?
ThreeTen-Extra项目使用其他类扩展了java.time。该项目是未来可能添加到java.time的试验场。你可能会在这里找到一些有用的类,如Interval
,YearWeek
,YearQuarter
和more。