Hello World

吞风吻雨葬落日 欺山赶海踏雪径

0%

关于 java.util.Date

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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());
}
}

输出

1
2
3
4
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

常见问题

  1. 如何把Date转成不同的时区值?
    不要这么做,Date没有时区,不要被toString()的输出误导了。如果用了Date作为输入,那么已经隐含的表达了使用当前的时区(不一定正确)。
  2. 如何把Date转成不同的日期格式?
    不要这么做,它没有格式,通常使用 DateFormat 做输出格式转化,但是别忘了设置时区信息。

出处

ALL ABOUT JAVA.UTIL.DATE

读完发现确实Date的设计存在缺陷,也没有文中提的那么夸张,主要还是语义不清,默认加了时区,可变等问题。所以后续能不用还是不用了。