很少有类能像java.util.Date
那样在堆栈溢出方面引起如此多的类似问题,有四个原因:
- 日期和时间工作基本上相当复杂,而且充满了拐弯抹角的情况。这是可以管理的,但你确实需要花一些时间来理解它。
java.util.Date
类在很多方面都很糟糕(详情如下)。- 一般来说,开发人员对它的理解很差。
- 它被库作者严重滥用,进一步加剧了混乱。
了解java.util.Date
最重要的事情为:
- 如果可能的话,你应该避免。使用
java.time.*
如果可能的话,也可以使用ThreeTen Backport(基本上是旧版本的java.time)或Joda time(如果您还没有使用java 8)。 - 如果您被迫使用它,请避免使用不推荐使用的成员。近20年来,它们中的大多数都被弃用了,这是有充分理由的。
- 如果您真的觉得必须使用不推荐的成员,请确保您真正理解他们。
- Date实例表示时间上的一个瞬间,而不是日期。重要的是,这意味着:
- 它没有时区。
- 它没有格式。
- 它没有日历系统。
现在,关于细节…
java.util.Date有什么问题?
java.util.Date
(从现在开始就是Date)是一种糟糕的类型,这解释了为什么Java 1.1中有这么多的Date被弃用(但不幸的是,它仍然在使用)。
设计缺陷包括:
- 它的名字令人误解:它不代表日期,它代表的是时间上的一个瞬间。因此,它应该被称为Instant,就像它的
java.time
一样等效。 - 这是非最终结果:这鼓励了java等继承的不当使用。
java.sql.Date
(表示日期,由于具有相同的短名称,因此也容易混淆) - 它是可变的:日期/时间类型是由不可变类型有效建模的自然值。日期是可变的(例如通过
setTime
方法),这意味着勤奋的开发人员最终会在各地创建防御副本。 - 它在许多地方隐式使用系统本地时区,包括
toString()
,这让许多开发人员感到困惑。 - 它的月份编号是以0为基础的,是从C复制的。这导致了很多很多错误。
- 它的年份编号是基于1900年的,也是从C中复制的。当然,当Java问世时,我们已经有了这样一个想法,即这对可读性不利?
- 其方法的名称不明确:
getDate()
返回月份的第几天,getDay()
返回星期几。给这些更具描述性的名字有多难? - 它是否支持闰秒是不明确的:“一秒由0到61之间的整数表示;值60和61只在闰秒中出现,甚至仅在实际正确跟踪闰秒的Java实现中出现。”我强烈怀疑大多数开发人员(包括我自己)都做了大量假设,认为
getSeconds()
的范围实际上在0-59(包括0-59)之间。 - 没有明显的理由,它是宽大的:“在所有情况下,为这些目的而给出的方法的参数都不需要在指定的范围内;例如,日期可以指定为1月32日,也可以解释为2月1日。”这是否有用?
我可以发现更多的问题,但他们会越来越挑剔。这是一个非常丰富的列表。好的一面是:
- 它明确地表示一个值:时间上的一个瞬间,没有相关的日历系统、时区或文本格式,精确到毫秒。
不幸的是,开发人员对这一“好方面”的理解也很差。让我们把它打开…
什么是“瞬间”?
注意:在这篇文章的其余部分,我忽略了相对论和闰秒。它们对一些人来说非常重要,但对大多数读者来说,它们只会带来更多的困惑。
当我谈论“瞬间”时,我指的是可以用来识别什么时候发生了什么的概念。(这可能发生在将来,但最容易从过去的事情来考虑。)它独立于时区和日历系统,因此多个使用“本地”时间表示的人可以用不同的方式谈论它。
让我们用一个非常具体的例子来说明发生在不使用我们熟悉的时区的地方的事情:尼尔·阿姆斯特朗在月球上行走。月球行走开始于一个特定的时刻——如果来自世界各地的多个人同时观看,他们会(几乎)同时说“我现在可以看到它正在发生”。
如果你在休斯顿的任务控制中心观看,你可能会想到那一刻是“1969年7月20日,CDT晚上9:56:20”。如果你在伦敦观看,你可能会想到那一刻是“1969年7月21日,英国夏令时凌晨3:26:20”。如果你在利雅得观看,你可能会认为那一刻是“1389年7月7日上午5:56:20(+03)”(使用《古拉经》日历)。尽管不同的观察者会在他们的时钟上看到不同的时间,甚至不同的年份,但他们仍然会考虑同一时刻。他们只是在应用不同的时区和日历系统,将即时转换为更人性化的概念。
那么,计算机如何表示实例呢?它们通常在一个特定的瞬间之前或之后存储一定量的时间,而这个瞬间实际上是一个原点。许多系统使用Unix纪元,这是格里高历中以UTC表示的1970年1月1日开始的午夜。这并不意味着纪元本质上是“在”UTC中的——Unix纪元同样可以定义为“1969年12月31日纽约下午7点的那一刻”。
Date
类使用“自Unix纪元以来的毫秒数”——这是getTime()
返回的值,由Date(long)
构造函数或setTime()
方法设置。由于月球行走发生在Unix纪元之前,因此该值为负值:实际上是-14159020000
。
为了演示日期如何与系统时区交互,让我们展示前面提到的三个时区–休斯顿(美国/芝加哥)、伦敦(欧洲/伦敦)和利雅得(亚洲/利雅得)。当我们从历元毫秒值构建日期时,系统时区是什么并不重要,这根本不取决于本地时区。但如果我们使用Date.toString()
,它转换为当前默认时区以显示结果。更改默认时区根本不会更改日期值。对象的内部状态完全相同。它仍然表示相同的瞬间,但toString()
、getMonth()
和getDate()
等方法将受到影响。下面的示例代码显示:
import java.util.Date;
import java.util.TimeZone;
public class Test {
public static void main(String[] args) {
// The default time zone makes no difference when constructing
// a Date from a milliseconds-since-Unix-epoch value
Date date = new Date(-14159020000L);
// Display the instant in three different time zones
TimeZone.setDefault(TimeZone.getTimeZone("America/Chicago"));
System.out.println(date);
TimeZone.setDefault(TimeZone.getTimeZone("Europe/London"));
System.out.println(date);
TimeZone.setDefault(TimeZone.getTimeZone("Asia/Riyadh"));
System.out.println(date);
// Prove that the instant hasn't changed...
System.out.println(date.getTime());
}
}
输出如下:
Sun Jul 20 21:56:20 CDT 1969
Mon Jul 21 03:56:20 GMT 1969
Mon Jul 21 05:56:20 AST 1969
-14159020000
这里的输出中的“GMT”和“AST”缩写非常不幸——java.util.TimeZone
并非在所有情况下都具有1970年之前值的正确名称。不过时机已经成熟。
常见问题
如何将Date日期转换为其他时区?
你没有——因为约会没有时区。这是一个瞬间。不要被toString()
的输出所愚弄。这会显示默认时区中的瞬间。这不是值的一部分。
如果代码以日期作为输入,则已经发生了从“本地时区”到即时的任何转换。(希望操作正确……)
如果您开始编写一个带有这样签名的方法,那么您并没有帮助自己:
// A method like this is always wrong
Date convertTimeZone(Date input, TimeZone fromZone, TimeZone toZone)
如何将Date日期转换为其他格式?
你没有——因为日期没有格式。不要被toString()
的输出所愚弄。始终使用相同的格式,如文档所述。
要以特定方式格式化日期,请使用合适的日期格式(可能是SimpleDataFormat
)——记住将时区设置为适合您使用的适当区域。
除特别注明外,本站所有文章均为老K的Java博客原创,转载请注明出处来自https://javakk.com/2663.html
暂无评论