Skip to content

四火的唠叨

一个纯正程序员的啰嗦

Menu
  • 所有文章
  • About Me
  • 关于四火
  • 旅行映像
  • 独立游戏
  • 资源链接
Menu

Java 的日期 API 真烂

Posted on 12/08/201206/23/2019 by 四火

date 记得在我刚学 Java 的时候,真是搞不清楚 Date 和 Calendar 这两个类,后来我渐渐知道,原来不能全怪我啊,Java 日期 API 之烂是公认的(不妨参见这篇文章,Tiago Fernandez 做过一个投票,就是要选举最烂的 Java API,结果 Java 日期 API 排行第二,仅次于臭名远扬的 EJB2,嘿嘿)。

蛋疼的 java.sql.Date

只有 Date 和 Calendar 搞定一切吗?那还好啊。当然不是!光 Date 就有 java.util.Date 和 java.sql.Date,而且关系是 java.sql.Date extends java.util.Date。为了把前者转成后者,我写了这样的代码:

1
2
Date date = new Date();
java.sql.Date d = new java.sql.Date(date.getTime());

居然不支持 Date 参数的构造器,我只好传入 long 类型的时间。接下去,我尝试把当前小时数取出来:

1
System.out.println(d.getHours());

悲剧出现了:

1
2
Exception in thread "main" java.lang.IllegalArgumentException
    at java.sql.Date.getHours(Date.java:177)

一看源码,坑爹啊:

1
2
3
public int getHours() {
    throw new java.lang.IllegalArgumentException();
}

在 java.util.Date 里面好好的方法怎么变成这个鸟样了?

方法注释给出了说明:

This method is deprecated and should not be used because SQL Date values do not have a time component.

也就是说,java.sql.Date 是 SQL 中的单纯的日期类型,哪会有时分秒啊?我觉得它根本不应该设计成 java.util.Date 的子类。如果你把 java.sql.Date 通过 JDBC 插入数据库,你会发现时分秒都丢失了,因此如果你同时需要日期和时间,你应该使用 Timestamp,它也是 java.util.Date 的子类。

另外还有一个 java.util.Date 的子类叫 Time,java.sql 包下面的 Date、Time 和 Timestamp 可以放在一起记忆。Date 只包含年月日信息、Time 只包含时分秒信息,而 Timestamp 则包含时间戳的完整信息。

现在知道人家抛出 IllegalArgumentException 的用心良苦了吧……

坑爹的 year 和 month

看看 Date 类的构造器:

1
public Date(int year, int month, int day)

长得并不奇葩嘛。

好,现在我要输出 2012 年的 1 月 1 号了:

1
2
Date date = new Date(2012,1,1);
System.out.println(date);

结果,我傻眼了:

1
Thu Feb 01 00:00:00 CST 3912

等等,这是啥?3192 年?

原来实际年份是要在你的年份参数上加上个起始年份 1900。

更坑爹的是,月份参数我不是给了 1 吗?怎么输出二月(Feb)了?

Date 里面的月份居然是用 0~11 表示的,换句话说,一月用 0 来表示,二月用 1 来表示。如果不用常量或者枚举,很容易踩到坑里去,对不对?

后来发现 Go 语言的 time.Date 方法,对于月份做了个恶心但是不容易坑人的处理(看奇葩的月份参数啊):

1
func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location)

我甚至怀疑 Google 这样处理是在用极端的方法鄙视 Java(另,据我所知,JavaScript 好像也是这样的,月份从 0 开始)……

坑爹的事情还没完,前面已经说了,构造函数的时间起始基准是 1900 年,可是 getTime() 方法却特立独行,返回的时间是相对于“1970-01-01 00:00:00” 的毫秒数差值……

尝试 Joda 吧

最开始的时候,Date 既要承载日期信息,又要做日期之间的转换,还要做不同日期格式的显示,职责较繁杂,从 JDK 1.1 开始,这三项职责分开了:

  1. 使用 Calendar 类实现日期和时间字段之间转换;
  2. 使用 DateFormat 类来格式化和分析日期字符串;
  3. 而 Date 只用来承载日期和时间信息。

原有 Date 中的相应方法已废弃。不过,无论是 Date,还是 Calendar,都用着太不方便了,这是 API 没有设计好的地方。

比如 Calendar 的 getInstance 方法,并未提供一个指定年月日和时分秒的重载方法,每次要指定特定的日期时间,必须先获取一个表示当前时间的 Calendar 实例,再去设值,比如:

1
2
3
Calendar c = Calendar.getInstance();
c.set(2012, 0, 1, 11, 11, 11);
System.out.println(c.getTime());

注意上面代码中对于年份的传值—— 是的,和 Date 不一样的是,Calendar 年份的传值不需要减去 1900(当然月份的定义和 Date 还是一样),这种不一致真是让人抓狂!

打印:

1
Sun Jan 01 11:11:11 CST 2012

有很多开源库都在努力弥补 Java 的这一问题,比如 Joda-Time,获取 Calendar 对象和设置时间完全可以合成一步完成:

1
DateTime dateTime = new DateTime(2012, 1, 1, 11, 11, 11, 0);

而且,一月份总是可以传 1 来表示了。

再如,如果要给上述时间增加 3 天再按格式输出的话,使用 Joda 更加便捷:

1
System.out.println(dateTime.plusDays(3).toString("E MM/dd/yyyy HH:mm:ss");

有兴趣的话请阅读此文,并下载 Joda-Time 使用。

JSR-310

众所周知 Java 的规范就是多、而且啰嗦,这帮老大们(Export Group 中除了有 Oracle 的人,还有 IBM、Google 和 RedHat 的人)终于再也无法忍受 Java 那么烂的日期 API 了,于是就有了 JSR-310(感兴趣的请移步),官方的描述叫做“This JSR will provide a new and improved date and time API for Java.”,目前的阶段还在“Early Draft Review 2”,有得等。

JSR-310 将解决许多现有 Java 日期 API 的设计问题。比如 Date 和 Calendar 目前是可变对象,你可以随意改变对象的日期或者时间,而 Joda 就将 DateTime 对象设计成 String 对象一样地不可变,能够带来线程安全等等的好处,因此这一点也将被 JSR-310 采纳。

很多 JSR 规范都是在程序员的诋毁和谩骂声中萌芽的,然后会有开源项目来尝试解决 Java 的这些弊端,最后就轮到 JSR 就去抄他们的实现。除了新的日期 API,再比如 JCache(JSR-107),你知道它抄了多少 EhCache 的东西么……

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《四火的唠叨》

×Scan to share with WeChat

你可能也喜欢看:

  1. java.util.concurrent 并发包诸类概览
  2. 从 Java 和 JavaScript 来学习 Haskell 和 Groovy(元编程)
  3. 泛型趣谈
  4. 你真的精通 Java 吗?
  5. 动手实现随机验证码

3 thoughts on “Java 的日期 API 真烂”

  1. 李德强 says:
    05/16/2017 at 5:19 PM

    您好:朋友介绍的,看了你的文章,写的很不错,很多观点不谋而合。
    但是在日期这一篇里有一点问题,跟您讨论一下。
    我认为在 Date 中取得月份时值范围从 0 到 11 这样的设计是非常高明的设计理念。
    垃圾的程序员会这么显示月份:1、2、3;
    优秀的程序员会这么显示月份:一月、二月、三月……
    January February March……
    如果在显示时我们定义这样的数组
    monthName = [“ 一月”, “ 二月”, “ 三月” … “ 十二月”]
    那么我们从 Date 中取得的月份 0~11 刚好是这个数组的下标,可以不加任何处理直接取得它们的名字 monthName[m]
    在大大简化代码的同时,使展现方式变得非常人性化。
    所以我认为这是一个非常高明的设计理念,非常高明!

    Reply
    1. 四火 says:
      05/17/2017 at 12:58 PM

      其实 monthName[0] 可以放别的,放 null 也可以啊
      这样 monthName[1] = “January”, 这不就解决了你的问题了么

      Reply
  2. ayanamist says:
    12/09/2012 at 12:55 AM

    java.sql.Date 的设计是这样的,因为数据库里确实是这样设计的,怪不得 Java。另外月份从 0 开始则是延续了数组的作风,也怪不得 Java,虽然可读性不好。

    Reply

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

订阅·联系

四火,啰嗦的程序员一枚,现居西雅图

Amazon Google Groovy Hadoop Haskell Java JavaScript LeetCode Oracle Spark 互联网 亚马逊 前端 华为 历史 同步 团队 图解笔记 基础设施 工作 工作流 工具 工程师 应用系统 异步 微博 思考 技术 数据库 曼联 测试 生活 眼界 程序员 管理 系统设计 缓存 编程范型 美股 英语 西雅图 设计 问题 面向对象 面试

分类

  • Algorithm and Data Structure (30)
  • Concurrency and Asynchronization (6)
  • System Architecture and Design (43)
  • Distributed System (18)
  • Tools Frameworks and Libs (13)
  • Storage and Data Access (8)
  • Front-end Development (33)
  • Programming Languages and Paradigms (55)
  • Testing and Quality Assurance (4)
  • Network and Communication (6)
  • Authentication and Authorization (6)
  • Automation and Operation Excellence (13)
  • Machine Learning and Artificial Intelligence (6)
  • Product Design (7)
  • Hiring and Interviews (14)
  • Project and Team Management (14)
  • Engineering Culture (17)
  • Critical Thinking (25)
  • Career Growth (57)
  • Life Experience and Thoughts (45)

推荐文章

  • 谈谈分布式锁
  • 常见分布式系统设计图解(汇总)
  • 系统设计中的快速估算技巧
  • 从链表存在环的问题说起
  • 技术面试中,什么样的问题才是好问题?
  • 从物理时钟到逻辑时钟
  • 近期面试观摩的一些思考
  • RSA 背后的算法
  • 谈谈 Ops(汇总 + 最终篇):工具和实践
  • 不要让业务牵着鼻子走
  • 倔强的程序员
  • 谈谈微信的信息流
  • 评审的艺术——谈谈现实中的代码评审
  • Blog 安全问题小记
  • 求第 K 个数的问题
  • 一些前端框架的比较(下)——Ember.js 和 React
  • 一些前端框架的比较(上)——GWT、AngularJS 和 Backbone.js
  • 工作流系统的设计
  • Spark 的性能调优
  • “残酷” 的事实
  • 七年工作,几个故事
  • 从 Java 和 JavaScript 来学习 Haskell 和 Groovy(汇总)
  • 一道随机数题目的求解
  • 层次
  • Dynamo 的实现技术和去中心化
  • 也谈谈全栈工程师
  • 多重继承的演变
  • 编程范型:工具的选择
  • GWT 初体验
  • java.util.concurrent 并发包诸类概览
  • 从 DCL 的对象安全发布谈起
  • 不同团队的困惑
  • 不适合 Hadoop 解决的问题
  • 留心那些潜在的系统设计问题
  • 再谈大楼扔鸡蛋的问题
  • 几种华丽无比的开发方式
  • 我眼中的工程师文化
  • 观点的碰撞
  • 谈谈盗版软件问题
  • 对几个软件开发传统观点的质疑和反驳
  • MVC 框架的映射和解耦
  • 编程的未来
  • DAO 的演进
  • 致那些自嘲码农的苦逼程序员
  • Java 多线程发展简史
  • 珍爱生命,远离微博
  • 网站性能优化的三重境界
  • OSCache 框架源码解析
  • “ 你不适合做程序员”
  • 画圆画方的故事

近期评论

  • panshenlian.com on 初涉 ML Workflow 系统:Kubeflow Pipelines、Flyte 和 Metaflow
  • panzhixiang on 关于近期求职的近况和思考
  • Anonymous on 闲聊投资:亲自体验和护城河
  • 四火 on 关于近期求职的近况和思考
  • YC on 关于近期求职的近况和思考
  • mafulong on 常见分布式基础设施系统设计图解(四):分布式工作流系统
  • 四火 on 常见分布式基础设施系统设计图解(八):分布式键值存储系统
  • Anonymous on 我裸辞了
  • https://umlcn.com on 资源链接
  • Anonymous on 我裸辞了
© 2025 四火的唠叨 | Powered by Minimalist Blog WordPress Theme
Menu
  • 所有文章
  • About Me
  • 关于四火
  • 旅行映像
  • 独立游戏
  • 资源链接