java.util.Date的黑历史 【译文】

译者前言:翻译的是一篇两年前的文章,起因是Stack Overflow上有很多关于java.util.Date的问题,当然其中不少是小白问的,不过也间接的说明这个类是比较坑的,不然不会出这么多问题,包括java.sql.Date的出现,还有它类上的注释,以及下文提到的20年前java.util.Date的大部分方法就被废弃了,都佐证了这一点,这个类的设计是失败的。

原文是英国大神Jon Skeet写的,十四年前就写开始技术博客了,OOP鼻祖,是一位很强的大神,stackoverflow社区说“他的代码如果不能运行,那一定是编译器的错”。下面正文:

很少有像java.util.Date的java类在Stack Overflow上引起如此多的问题。这有四个原因:

  • 日期和时间底层实现相当复杂,同时会遇上各种情况。虽然它是可管理的,但需要花时间去理解它。
  • java.util.Date在很多方面都很糟糕(详见下文)。
  • 一般来说,开发人员对它了解很少。
  • 它被很多类库严重滥用,进一步加剧了混乱。

简而言之

  • 如果可能的话,你应该避免它。尽可能使用java.time.*,如果您还没有使用Java 8 及以上版本,则使用ThreeTen-Backport(基本上是旧版本的java.time)Joda Time
  • 如果你非要用它,就避免使用已废弃的方法。他们中的大多数已被弃用近20年,并且有充分的理由。
  • 如果您真的觉得必须使用已弃用的方法,请确保真正理解这些方法。

一个Date实例表示instant的时间,而不是日期。这意味着Date存在以下问题:

  • 没有时区。
  • 没有格式。
  • 没有日历系统。

现在一条一条的喷java.util.Date

java.util.Date (下文简称Date)是一个糟糕的类型,这解释了为什么它在Java 1.1中被废弃了很多内容(但不幸的是,它仍被使用)。

设计缺陷包括:

  • 它的名字具有误导性:它不代表一个 Date,它代表的是“即时”。所以它的类名其实应该写成Instant,因为它和java.time是等价物。
  • 它不是常量:鼓励继承类更改时间,例如java.sql.Date(意思是代表一个日期,也因为有相同的简称而混淆)
  • 它是可变的:日期/时间类型是自然值,它们由不可变类型有用地建模。Date可变的事实(例如通过该setTime方法)意味着勤奋的开发人员最终会在整个地方创建防御性副本。
  • 它隐含地在许多地方使用系统本地时区 - 包括toString()- 这使许多开发人员感到困惑。
  • 它的月份编号从0开始,是从C复制过来的。这导致了很多很多的错误。
  • 它的年份编号是1900年,也是从C复制的。当Java出来的时候我就有一个想法,这对可读性有害吗?
  • 其方法名称不明确:getDate()返回日期,getDay()返回星期几。给那些更具描述性的名字有多难?
  • 关于它是否支持闰秒是不明确的:“第二个由0到61之间的整数表示; 值60和61仅在闰秒发生,甚至仅在实际正确跟踪闰秒的Java实现中发生。“我强烈怀疑大多数开发人员(包括我自己)做出了大量假设,其范围getSeconds()实际上在0范围内-59包容。
  • 设置的时间不必在合法的范围内; 例如,日期可以指定为1月32日,然后被解释为2月1日,这不是在搞笑吗?

我可以找到更多的问题,但他们会变得更挑剔。这是一个很好的清单。从积极的一面:

  • 它明确地表示单个值:instant,没有关联的日历系统,时区或文本格式,精确到毫秒。

不幸的是,开发人员对这个“好方面”的了解甚少。我们细说......

什么是“instant”?

注意:我相对性的忽略了本文其余部分和闰秒。它们对某些人来说非常重要,但对于大多数读者来说,会搞得更多人看不懂。

当我谈到“即时”时,我正在谈论可用于识别事情何时发生的那种概念。(可能是将来,但最容易考虑过去的情况。)它与时区和日历系统无关,因此多人使用他们的“本地”时间表示可以用不同的方式来讨论它。

让我们使用一个非常具体的例子,说明在某个地方不会使用我们熟悉的任何时区:Neil Armstrong在月球上行走。月球漫步在特定时刻开始,如果来自世界各地的多个人同时观看,他们都会(几乎)同时说“我现在可以看到它”。

如果你在休斯敦的任务控制中观察,你想的那个即时是“1969年7月20日,CDT时间9:56:20”。如果你在伦敦观看,你想的那个即时是“1969年7月21日,BST凌晨3:26:20”。如果你在沙特的利雅得观看,你想的那个即时是“Jumādá7th1389,5:56:20 am(+03)”(使用Umm al-Qura日历)。即使不同的观察者会在他们的时钟上看到不同的时间 - 甚至不同的年份 - 他们仍然会考虑同一时刻。他们只是应用不同的时区和日历系统,从即时转变为更加以人为中心的概念。

那么计算机如何表示即时呢?它们通常在特定时刻之前或之后存储一定量的时间,该时刻实际上是原点。许多系统都使用Unix时代,这是UTC中格里高利历中表示的即时,即1970年1月1日午夜。这并不意味着时代本身就是“在”UTC中 - 同样可以定义Unix时代作为“1969年12月31日晚上7点在纽约的时刻”。

Date班采用“毫秒自Unix纪元” -这是返回的值getTime(),以及在由设置Date(long)构造函数或setTime()方法。由于月球行走发生在Unix时代之前,因此价值为负:它实际上是-14159020000。

为了演示如何Date与系统时区进行交互,让我们展示之前提到的三个时区 - 休斯顿(美国/芝加哥),伦敦(欧洲/伦敦)和利雅得(亚洲/利雅得)。当我们从其划分时间值构建日期时,系统时区是什么并不重要 - 这完全不依赖于当地时区。但是如果我们使用Date.toString(),它会转换为当前的默认时区以显示结果。更改默认时区根本不会更改该Date的值。对象的内部状态完全相同。它仍然代表同一时刻,但方法一样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转换为其他时区?

转不了 - 因为Date没有时区。这是一瞬间的时间。不要输出愚蠢的toString()。那是在默认时区显示的时间。它没有价值。
如果以Date作为输入,则已经发生从“本地时间和时区”到即时的任何转换。(希望它能正确完成...)

如果你打算编写一个下面这样的方法,你只能掉坑里:

// 不管怎么写都是错的
Date convertTimeZone(Date input, TimeZone fromZone, TimeZone toZone)

如何将Date转换为其他格式?

转不了,因为Date没有格式。不要输出愚蠢的toString()。它总是使用相同的格式,如文档所述。 要Date以特定方式格式化,请使用合适的DateFormat(一般用SimpleDateFormat) - 记住将时区设置成你要用的时区。

原文:https://codeblog.jonskeet.uk/2017/04/23/all-about-java-util-date/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,997评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,603评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,359评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,309评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,346评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,258评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,122评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,970评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,403评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,596评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,769评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,464评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,075评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,705评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,848评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,831评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,678评论 2 354