java.util.Date 在Stack Overflow 上有非常多的相似问题,对于这个情况这里做一些说明。
重点
对于java.util.Date需要记住的事:
- 如果可以的话应该使用
java.time.*,非Java 8的情况使用 threeten 或者 Joda Time 库 - Date实例代表的是一个瞬间,而不是一个日期,这意味着:
- 它没有时区
- 它没有格式
- 它没有日历系统
细节说明
java.util.Date 的设计缺陷:
- 名字本身具有诱导性,它不代表日期,它只代表一个瞬间。类似与 java.time.Instant 。
- 非 final的,放任了不良的继承,比如
java.sql.Date(也代表日期,且有相同的名字) - 它是可变的 (mutable) ,日期是自然值,通常先进的设计都会把它设计成不可变类型。因为
setTime方法变的可变。 - 很多地方都会隐式的使用了本地时区,比如 toString() ,这让人费解, 为什么瞬时会有时区。
- 它的月份表示是从0开始的(参考的C语言)。
- 它的年份是基于1900的(也是参考的C语言)。
- 方法命名不明确,
getDate()返回的是月中的第几天,而getDay()返回的是星期的天数,完命名全看不明白。 - 支持闰秒,但很不明确。文档中有写:秒是0-61之间的整数,60与61只出现在闰秒中。
getSeconds()正常人都认为是0-59的范围。 - API设计没有理由的过于宽容,比如日期可以指定为1月32日,代表2月1号。
需要再次明确的java.util.Date 代表的是一个瞬时,没有日历系统、时区、文本格式,只精确到毫秒。
瞬时的说明
瞬时一半用做表示什么时候事情发生了,它独立于时区与日历系统,所以人们用他们当地的时间来描述,描述同一个瞬时可能是不一样的。
就比如直播中阿姆斯特朗开始在月球上行走的时候,全世界的人都看到的是同一个时刻,这就是一个瞬时,然而在休斯敦、伦敦、利雅得 的人对这个瞬时的描述却是不一样的时间。
所以计算机如何描述一个瞬时呢?一般我们记录一个定时之前或之后的一个时间量。比较经典的方式是Unix时间纪元,它是 1970 年 1 月 1 日午夜在 UTC 公历中表示的时刻。
Date类使用 getTime() 方法返回与Unix时间纪元隔的毫秒数,同时可以通过构造函数 Date(long) 或者 setTime(long) 方法设置。在Unix时间纪元之前的就使用负数。
为了演示 Date 如何被系统时区影响,我们展示之前提到的三个时区——休斯顿(美国/芝加哥)、伦敦(欧洲/伦敦)和利雅得(亚洲/利雅得)。
当我们从它的 epoch-millis 值构造日期时,系统时区是什么并不重要——这根本不依赖于本地时区。 但是,如果我们使用 Date.toString(),则会转换为当前默认时区以显示结果。
更改默认时区根本不会更改日期值,Date 对象的内部状态完全相同 它仍然表示相同的时刻,但 toString()、getMonth() 和 getDate() 等方法却受到了影响。
下面是示例代码来说明:
1 | import java.util.Date; |
输出
1 | Sun Jul 20 21:56:20 CDT 1969 |
常见问题
- 如何把Date转成不同的时区值?
不要这么做,Date没有时区,不要被toString()的输出误导了。如果用了Date作为输入,那么已经隐含的表达了使用当前的时区(不一定正确)。 - 如何把Date转成不同的日期格式?
不要这么做,它没有格式,通常使用DateFormat做输出格式转化,但是别忘了设置时区信息。
出处
读完发现确实Date的设计存在缺陷,也没有文中提的那么夸张,主要还是语义不清,默认加了时区,可变等问题。所以后续能不用还是不用了。