为什么在Java中(不仅仅是Java),UTC(不是时区)被认为是一个时区?

10

鉴于UTC不是一个时区,而是一个时间标准(例如如此声明,此处),为什么在我的Java应用程序中我可以像使用时区一样使用UTC(请参见下面的代码片段)?

SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
format.setTimeZone(TimeZone.getTimeZone("UTC"));
如果UTC不是一个时区,那么为什么TimeZone.getTimeZone("UTC")能够返回时间区域对象呢?
顺便提一下,在我的Windows机器上,UTC也在时区列表中(见截图)。
实际上,“UTC不是时区”的说法是错误的吗?

UTC是Windows中的一个时区


4
Java 8引入的Java日期和时间API已回溯到Java 6和7,它将“偏移量”(如UTC或UTC-5:00)和“时区”(如Europe/London或Asia/Kolkata)区分开来。然后通过将ZoneOffset声明为ZoneId的子类来“欺骗”,因此它声称偏移量也是一种时区。请注意,本翻译尽可能保留原文意思,同时使语言更加通俗易懂。 - Ole V.V.
我想说的是,因为必须有一个,无论哪一个。如果有人被选中,它就成为了标准。 - tsh
1
SimpleDateFormat 是一种可怕的日期时间类之一,早在现代 java.time 类出现多年前就被取代了。这些遗留类是由那些不理解日期时间处理复杂性和微妙之处的人构建的。时区和UTC偏移量之间的区别是遗留类设计者不理解的重要点之一。不要从遗留类中学习任何课程;只需忽略它们并转向 java.time,就像 Sun、Oracle 和 JCP 社区一样。 - Basil Bourque
@BasilBourque,我完全同意您的观点,传统的日期时间API有很多缺点。然而,如果我使用现代API尝试相同的操作,我仍然可以看到UTC被视为一个时区:ZoneId.of("UTC")返回一个有效的ZoneId对象。 - yaskovdev
@yaskovdev 实际上,ZoneId 的子类 ZoneOffset 声明了一个常量 ZoneOffset.UTC。该常量仅是一个偏移量,其值为零小时、零分钟和零秒钟。可能让您感到困惑的是,“UTC”一词经常用作该短语“一个偏移量,其值为零小时、零分钟和零秒”的缩写。这个词“UTC”有两个含义:(a) 缩写短语,(b) 一个正式定义的时间系统。 - Basil Bourque
2个回答

13

因为将UTC视为一个时区比把它视为其他东西要简单得多。

这是一种“严格来说不是”的情况。除了“这是哪个地区观察到的?”之外,您可以将UTC视为一个时区,并且它可以正常工作。所以稍微弯曲一下比拥有一个完全独立的概念更简单。

如果您将时区视为从“时间点”到“UTC偏移量”(或类似于从“时间点”到“当地观察到的时间”)的映射,则将UTC视为一个时区就很好 - 这是我们在软件中做的大部分工作。

如果您将时区视为一个地理区域以及该映射,则它的效果不如此好-但在软件中更少使用。 (而且您可以通过说它是一个空区域来伪造它:)


我看了这个问题,想着往下滚动写出与你所写非常相似的内容……结果发现那里已经有了。一方面感觉很尴尬(Jon Skeet怎么可能知道我3分钟后在想什么),另一方面,我感到很荣幸你决定表达我所思考的东西;-) - GhostCat
在西非难道不是有一堆国家永久使用协调世界时(UTC)吗?而且由于靠近赤道,它们不使用夏令时。 - Alnitak
1
@Alnitak:他们现在可能是这样,但我不想将其标记为“UTC时区”。特别是,转换为UTC的用户并不是试图找出那些地方的本地时间...他们正试图有效地转换到一个“位置中立”的区域。 - Jon Skeet
@GhostCat:同样地,我很高兴知道我所写的有意义 :) - Jon Skeet
那么更准确地说,这些地方是在格林威治标准时间上吗? - Alnitak
4
@Alnitak:不,更准确的做法是给这些地方设立他们自己的时区(或者一系列的时区),这些时区总是具有UTC偏移量为0。请注意,历史上可能存在某些时期他们没有UTC偏移量为0的情况,这也是给它们特定的ID很有用的原因之一。 - Jon Skeet

9
"UTC不是一个时区"这种说法实际上并不准确。UTC是一个标准,而非时区(正如您已经链接的那样)。
时区对应于世界上的某个地区,并具有许多关于该地区的规则:
- 在夏令时和非夏令时中UTC偏移量(与UTC的差异)是多少 - 夏令时何时开始和结束 - 该地区在其历史上所有偏移和夏令时的更改
例如:1985年,巴西阿克里州的标准偏移量为UTC-05:00(夏令时为UTC-04:00),然后在1988年没有夏令时的情况下为UTC-05:00,然后在2008年标准偏移量改为UTC-04:00(无夏令时),自2013年以来它又回到了UTC-05:00,没有夏令时。
虽然时区跟踪所有这些变化,但UTC没有这样的规则。您可以以许多不同的方式考虑UTC: - 一个“基本”日期/时间,每个人都相对于此进行 - 与UTC的差异称为“偏移量”。今天,圣保罗在 UTC-03:00 (偏移量为减去3小时,或比UTC晚3小时),而东京在 UTC+09:00 (偏移量为加9小时,或比UTC早9小时)。 - 一个“特殊”的时区,从不变化。它始终处于相同的偏移量(零),永远不会更改,也不会有夏令时转换。 由于“UTC的偏移量”(不确定这个术语在技术上是否准确)始终为零,通常将其写为 UTC + 00:00 Z

UTC和时区的另一个区别是,时区由政府和法律定义,并且可以随时/任何地方更改。如上所述,阿克里所有的更改都是由政治家们出于当时的某些原因而定义的。(因此,即使今天某个地区在其时区中遵循UTC,也无法保证将来它会保持不变,这就是为什么即使这些地区看起来多余,它们也拥有自己的时区)。

但是,无论政治家们更改他们地区的偏移多少次,它们必须相对于UTC(当然,直到出现新标准)。


现在,当你看到像 TimeZone.getTimeZone("UTC") 这样的实现时,你可以用两种不同的方式来思考它:
  • 一种是设计缺陷,因为它混淆了两个不同的概念并导致人们认为它们是相同的。
  • 另一种是快捷方式/简化/巧妙的技巧/解决方法,使事情变得更容易(正如@JonSkeet在他的答案中解释的那样)。
对我来说,这是两者的混合(五五开)。

新的java.time API将概念分为两个类:ZoneRegionZoneOffset(实际上两者都是ZoneId的子类,但是ZoneRegion不是公共的,因此我们实际上使用ZoneIdZoneOffset):

  • 如果你使用带有IANA时区名称ZoneId(始终以大陆/城市格式,如America/Sao_PauloEurope/Berlin),它将创建一个ZoneRegion对象——一个“真正”的时区,包含其历史上的所有DST规则和偏移量。因此,在这个ZoneId中使用不同的日期,可以有不同的偏移量。
  • 如果你使用ZoneOffset(带有ZUTC+03:00等),它只会返回一个表示偏移量的对象:与UTC的差异,但没有任何DST规则。无论你使用这个对象的日期是什么,它与UTC的差异始终相同。
因此,ZoneId(实际上是ZoneRegion)与某个区域(在某个时区中)的偏移随时间变化(由于DST规则、政治家改变事物等原因)保持一致。而ZoneOffset表示与UTC的差异的概念,它没有DST规则且永远不会更改。

还有一个特殊的常量ZoneOffset.UTC,它表示与UTC的差异(即UTC本身)。请注意,新API采用了不同的方法:它并没有说所有东西都是时区,而UTC是一种特殊类型,而是说UTC是一个ZoneOffset,其偏移量为零。

您仍然可以认为这是一种"错误"的设计决策或使事情变得更简单的简化(或两者混合)。在我看来,与旧的java.util.TimeZone相比,这个决定是一个巨大的改进,因为它清楚地表明UTC不是一个时区(在它没有DST规则且永远不会更改的意义上),它只是与UTC标准的零差异(一种非常技术性的说法就是"它是UTC")。

同时,它也将时区和偏移量的概念分开(虽然它们非常相关)。我认为将UTC定义为特殊偏移量是一种“实现细节”。创建另一个类来处理UTC将是冗余且令人困惑的,将其保留为ZoneOffset是一个很好的决定,简化了事情并没有混乱API(对我而言,是一个公平的权衡)。
我相信许多其他系统采取类似的方法(这解释了为什么Windows在时区列表中有UTC)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接