Java 中的时间日期 API
自从 14 年发布 Java 8 以后,我们古老 java.util.Date 终于不再是我们 Java 里操作日期时间的唯一的选择。
其实 Java 里的日期时间的相关 API 一直为世猿诟病,不仅在于它设计分上工不明确,往往一个类既能处理日期又能处理时间,很混乱,还在于某些年月日期的数值映射存储反人类,例如:0 对应月份一月,11 对应月份十二月,118 对应年份 2018(1900 + 118)等。
往往我们得到某个年月值还需要再做相应的运算才能得到准确的年月日信息,直到我们的 Java 8 ,借鉴了第三方开源库 Joda-Time 的优秀设计,重新设计了一个日期时间 API,相比之前,可以说好用百倍,相关 API 接口全部位于包 java.time 下。
古老的日期时间接口
表示时刻信息的 Date
世界上所有的计算机内部存储时间都使用一个 long 类型的整数,而这个整数的值就是相对于英国格林尼治标准时间(1970年1月1日0时0分0秒)的毫秒数。例如:
public static void main(String[] args){
//January 1, 1970 00:00:00 GMT.
Date date = new Date(1000);
System.out.println(date);
}
输出结果:
//1970-1-1 8:00:01
Thu Jan 01 08:00:01 CST 1970
很多人可能会疑惑,1000 表示的是距离标准时间往后 1 秒,那为什么时间却多走了 八个小时?
这和「时区」有关系,如果你位于英国的格林尼治区,那么结果会如预想一样,但是我们位于中国东八区,时间要早八个小时,所以不同时区基于的基础值不同。
Date 这个类以前真的扮演过很多角色,从它的源码就可以看出来,有可以操作时刻的方法,有可以操作年月日的方法,甚至它还能管时区。可以说,日期时间的相关操作有它一个人就足够了。
但这个世界就是这样,你管的东西多了,自然就不能面面俱到,Date 中很多方法的设计并不是很合理,之前我们也说了,甚至有点反人类。所以,现在的 Date 类中接近百分之八十的方法都已废弃,被标记为 @Deprecated。
sun 公司给 Date 的目前定位是,唯一表示一个时刻,所以它的内部应该围绕着那个整型的毫秒,而不再着重于各种年历时区等信息。
Date 允许通过以下两种构造器实例化一个对象:
private transient long fastTime;
public Date() {
this(System.currentTimeMillis());
}
public Date(long date) {
fastTime = date;
}
这里的 fastTime 属性存储的就是时刻所对应的毫秒数,两个构造器还是很简单,如果调用的是无参构造器,那么虚拟机将以系统当前的时刻值对 fastTime 进行赋值。
还有几个为数不多没有被废弃的方法:
- public long getTime() :返回内部存储的毫秒数
- public void setTime(long time):重新设置内存的毫秒数
- public boolean before(Date when):比较给定的时刻是否早于当前 Date 实例
- public boolean after(Date when):比较给定的时刻是否晚于当前 Date 实例
还有两个方法是 jdk1.8 以后新增的,用于向 Java 8 新增接口的转换,待会介绍。
描述年历的 Calendar
Calendar 用于表示年月日等日期信息,它是一个抽象类,所以一般通过以下四种工厂方法获取它的实例对象。
public static Calendar getInstance()
public static Calendar getInstance(TimeZone zone)
public static Calendar getInstance(Locale aLocale)
public static Calendar getInstance(TimeZone zone,Locale aLocale)
其实内部最终会调用同一个内部方法:
private static Calendar createCalendar(TimeZone zone,Locale aLocale)
该方法需要两个参数,一个是时区,一个是国家和语言,也就是说,构建一个 Calendar 实例最少需要提供这两个参数信息,否则将会使用系统默认的时区或语言信息。
因为不同的时区与国家语言对于时刻和年月日信息的输出是不同的,所以这也是为什么一个 Calendar 实例必须传入时区和国家信息的一个原因。看个例子:
public static void main(String[] args){
Calendar calendar = Calendar.getInstance();
System.out.println(calendar.getTime());
Calendar calendar1 = Calendar.getInstance
(TimeZone.getTimeZone("GMT"), Locale.ENGLISH);
System.out.println( calendar1.get(Calendar.YEAR) + ":" +
calendar1.get(Calendar.HOUR) + ":" +
calendar1.get(Calendar.MINUTE));
}
输出结果:
Sat Apr 21 10:32:20 CST 2018
2018:2:32
可以看到,第一个输出为我们系统默认时区与国家的当前时间,而第二个 Calendar 实例我们指定了它位于格林尼治时区(0 时区),结果也显而易见了,相差了八个小时,那是因为我们位于东八区,时间早于 0 时区八个小时。
可能有人会疑惑了,为什么第二个 Calendar 实例的输出要如此复杂的拼接,而不像第一个 Calendar 实例那样直接调用 getTime 方法简洁呢?
这涉及到 Calendar 的内部实现,我们一起看看:
protected long time;
public final Date getTime() {
return new Date(getTimeInMillis());
}
和 Date 一样,Calendar 的内部也维护着一个时刻信息,而 getTime 方法实际上是根据这个时刻构建了一个 Date 对象并返回的。
而一般我们构建 Calendar 实例的时候都不会传入一个时刻信息,所以这个 time 的值在实例初始化的时候,程序会根据系统默认的时区和当前时间计算得到一个毫秒数并赋值给 time。
所以,所有未手动修改 time 属性值的 Calendar 实例的内部,time 的值都是当时系统默认时区的时刻数值。也就是说,getTime 的输出结果是不会理会当前实例所对应的时区信息的,这也是我觉得 Calendar 设计的一个缺陷所在,因为这样会导致两个不同时区 Calendar 实例的 getTime 输出值只取决于实例初始化时系统的运行时刻。
Calendar 中也定义了很多静态常量和一些属性数组:
public final static int ERA = 0;
public final static int YEAR = 1;
public final static int MONTH = 2;
public final static int WEEK_OF_YEAR = 3;
public final static int WEEK_OF_MONTH = 4;
public final static int DATE = 5;
....
protected int fields[];
protected boolean isSet[];
...
有关日期的所有相关信息都存储在属性数组中,而这些静态常量的值往往表示的就是一个索引值,通过 get 方法,我们传入一个属性索引,返回得到该属性的值。例如:
Calendar myCalendar = Calendar.getInstance();
int year = myCalendar.get(Calendar.YEAR);
这里的 get 方法实际上就是直接取的 fields[1] 作为返回值,而 fields 属性数组在 Calendar 实例初始化的时候就已经由系统根据时区和语言计算并赋值了,注意,这里会根据你指定的时区进行计算,它不像 time 始终是依照的系统默认时区。
个人觉得 Calendar 的设计有优雅的地方,也有不合理的地方,毕竟是个「古董」了,终将被替代。
DateFormat 格式化转换
从我们之前的一个例子中可以看到,Calendar 想要输出一个预期格式的日期信息是很麻烦的,需要自己手动拼接。而我们的 DateFormat 就是用来处理格式化字符串和日期时间之间的转换操作的。
DateFormat 和 Calendar 一样,也是一个抽象类,我们需要通过工厂方式产生其实例对象,主要有以下几种工厂方法:
//只处理时间的转换
public final static DateFormat getTimeInstance()
//只处理日期的转换
public final static DateFormat getDateInstance()
//既可以处理时间,也可以处理日期
public final static DateFormat getDateTimeInstance()
当然,它们各自都有各自的重载方法,具体的我们待会儿看。
DateFormat 有两类方法,format 和 parse。
public final String format(Date date)
public Date parse(String source)
format 方法用于将一个日期对象格式化为字符串,parse 方法用于将一个格式化的字符串装换为一个日期对象。例如:
public static void main(String[] args){
Calendar calendar = Calendar.getInstance();
DateFormat dateFormat = DateFormat.getDateTimeInstance();
System.out.println(dateFormat.format(calendar.getTime()));
}
输出结果:
2018-4-21 16:58:09
显然,使用工厂构造的 DateFormat 实例并不能够自定义输出格式化内容,即输出的字符串格式是固定的,不能满足某些情况下的特殊需求。一般我们会直接使用它的一个实现类,SimpleDateFormat。
SimpleDateFormat 允许在构造实例的时候传入一个 pattern 参数,自定义日期字符的输出格式。例如:
public static void main(String[] args){
DateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日");
System.out.println(dateFormat.format(new Date()));
}
输出结果:
2018年04月21日
其中,
- yyyy:年份用四位进行输出
- MM:月份用两位进行输出
- dd:两位表示日信息
- HH:两位来表示小时数
- mm:两位表示分钟数
- ss:两位来表示秒数
- E:表示周几,如果 Locale 在中国则会输出 星期x,如果在美国或英国则会输出英文的星期
- a:表示上午或下午
当然,对于字符串转日期也是很方便的,允许自定义模式,但必须遵守自己制定的模式,否则程序将无法成功解析。例如:
public static void main(String[] args){
String str = "2018年4月21日 17点17分 星期六";
DateFormat sDateFormat = new SimpleDateFormat("yyyy年M月dd日 HH点mm分 E");
sDateFormat.parse(str);
System.out.println(sDateFormat.getCalendar().getTime());
}
输出结果:
Sat Apr 21 17:17:00 CST 2018
显然,程序是正确的解析的我们的字符串并转换为 Calendar 对象存储在 DateFormat 内部的。
总的来说,Date、Calendar 和 DateFormat 已经能够处理一般的时间日期问题了,但是不可避免的是,它们依然很繁琐,不好用。
限于篇幅,我们下篇将对比 Java 8 的新式日期时间 API,你会发现它更加优雅的设计和简单的操作性。
文章中的所有代码、图片、文件都云存储在我的 GitHub 上:
(https://github.com/SingleYam/overview_java)
欢迎关注微信公众号:扑在代码上的高尔基,所有文章都将同步在公众号上。
Java 中的时间日期 API的更多相关文章
- Java中的时间日期处理
程序就是输入——>处理——>输出.对数据的处理是程序员需要着重注意的地方,快速.高效的对数据进行处理时我们的追求.其中,时间日期的处理又尤为重要和平凡,此次,我将把Java中的时间日期处理 ...
- Java 8 的时间日期 API
上一篇文章『Java 的时间日期 API』中,我们学习了由 Date.Calendar,DateFormat 等组成的「传统时间日期 API」,但是传统的处理接口设计并不是很友好,不易使用.终于,Ja ...
- Java中关于时间日期格式保存到mysql的问题
首先在设置数据库的时间日期字段的时候要先确定好采用何种类型,DATETIME. TIMESTAMP.DATE.TIME.YEAR. 其中datetime.time用的比较多,对应java中生成的poj ...
- Java中的时间日期Date和Calendar
日期时间类 Date: Date类的构造方法: 可以发现Date类的toString方法被重写了. Date类的方法: SimpleDateFormat 它提供了解决Date输出问题的解决方案--格式 ...
- Spring Boot和Feign中使用Java 8时间日期API(LocalDate等)的序列化问题【转】
Spring Boot和Feign中使用Java 8时间日期API(LocalDate等)的序列化问题 http://blog.didispace.com/Spring-Boot-And-Feign- ...
- Spring Boot和Feign中使用Java 8时间日期API(LocalDate等)的序列化问题
LocalDate.LocalTime.LocalDateTime是Java 8开始提供的时间日期API,主要用来优化Java 8以前对于时间日期的处理操作.然而,我们在使用Spring Boot或使 ...
- Java中的时间处理
日期时间组件使用 java.util.Date:实现类,其对象具有时间.日期组件.java.util.Calendar:抽象类,其对象具有时间.日期组件.java.sql.Date:实现类,其对象具有 ...
- Java 8 新的时间日期 API
1. 概述 1.1 简介 Java 8 引入了一套全新的时间日期API,操作起来更简便.简单介绍下,LocalDate和LocalTime和LocalDateTime的使用: java.util.Da ...
- JAVASE(十二) Java常用类: 包装类、String类、StringBuffer类、时间日期API、其他类
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 1.包装类 1 .1 八个包装类 1. 2 基本数据类型,包装类,String者之间的转换 2. ...
随机推荐
- Git简单图文教程
环境: Windows [版本 10.0.15063]64位 Git-2.14.1 64位[下载] TortoiseGit-2.5.0.0 64位[下载],这是一个Git 客户端,外号"乌龟 ...
- 201621123062《java程序设计》第二周学习总结
1.本周学习总结 本周学习重点: 1.java的基本数据类型(类似于C,特有boolean),java的引用类型(类似指针),其他常用类. 2.字符串类型String(比c简单),String的不变性 ...
- Java中的Integer
包装类---Integer Integer 类在对象中包装了一个基本类型int的值.Integer类型的对象包含一个 int 类型的字段.此外,该类提供了多个方法,能在 int 类型和 String ...
- 学生ID查询
var http = require("http"); var server = http.createServer(function(req,res){ //得到url var ...
- 将数组写入Plist文件中
-(void)writeToPlist:(NSArray *)uploadingfiles Name:(NSString *)name { NSMutableArr ...
- STL之queue
描述 使用STL中的queue,完成入队.出队.获取队首.获取队尾等基本操作. 部分代码已经给出,请补充完整,提交时请勿包含已经给出的代码. int main() { queue<int> ...
- 【Learning】 多项式的相关计算
约定的记号 对于一个多项式\(A(x)\),若其最高次系数不为零的项是\(x^k\),则该多项式的次数为\(k\). 记为\(deg(A)=k\). 对于\(x\in(k,+ \infty)\),称\ ...
- 详解JavaScript对象继承方式
一.对象冒充 其原理如下:构造函数使用 this 关键字给所有属性和方法赋值(即采用类声明的构造函数方式).因为构造函数只是一个函数,所以可使 Parent 构造函数成为 Children 的方法,然 ...
- Mybatis框架入门
Mybaits框架 一.什么是Mybatis MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了googl ...
- Hadoop完全分布式安装教程
一.软件版本 Hadoop版本号:hadoop-2.6.0.tar: VMWare版本号:VMware-workstation-full-11.0.0-2305329 Ubuntu版本号:ubuntu ...