不论作什么系统的开发,处理时间都是咱们没法绕不过去的一道坎。java
在 Java 世界里,java.util.Date 类应该是大多数人最先学习和使用的处理日期时间 API。这个类诞生于 Java 1.0 ,负责承载日期信息、日期之间的转换、不一样日期格式的显示等多项功能,同时因为该类的设计过于简单粗暴,以致于其中的大部分方法在一年后发布的 Java 1.1 中就废弃了,并引入新的日历类 Calendar 做为部分功能的接班人,并明确了工做职责:git
惋惜的是,Calendar 类使用起来很复杂,一样没有被大众接受。github
几个吐槽比较多的点: 一、Java的日期/时间类的定义并不一致,在 java.util 和 java.sql 的包中都有日期类,此外用于格式化和解析的类在 java.text 包中定义; 二、java.util.Date 同时包含日期和时间,而 java.sql.Date 仅包含日期,将其归入 java.sql 包并不合理。另外这两个类都有相同的名字,这自己就是一个很是糟糕的设计; 三、api 难用,举一个简单的例子,还有更多比这个还难用的sql
Date date = new Date(2016,1,1); System.out.println(date); // 输出Thu Feb 01 00:00:00 CST 3916
输出结果的 年为3912,是2016+1900,而月,并非咱们给的月份参数,而是参数加1。api
实际应用中,咱们几乎都在使用一个开源的时间/日期库 Joda-Time,这个库流行到什么程度呢? Java 8 几乎原封不动的引入了这个库,这也是最受Java 开发者追捧的变化之一。下面就主要介绍一下来自Joda-Time 的新Java API,java.time包。网络
一个Instant对象表明时间轴上的一个点。原点(元年)被规定为1970年1月1日0点0分0秒,UNIX/POSIX 时间一样也是这样的约定。从原点开始,天天按照86400秒进行计算,向前向后分别以纳秒为单位。框架
静态方法 Instant.now() 会返回当前瞬间的时间点。学习
一个Duration对象表示两个瞬间(Instant)之间的时间量,一个经常使用的场景是计算某方法的执行时间:设计
Instant from = Instant.now(); // do something Thread.sleep(1000); Duration duration = Duration.between(from, Instant.now()); System.out.println(duration.toMillis());
Instant(Duration)对象内部以一个 long 型变量维护秒值,而纳秒值由另外一个int 保存。其中纳秒值是基于秒值的,因此纳秒值的int 变量永远不会大于999,999,999。code
/** * The number of seconds from the epoch of 1970-01-01T00:00:00Z. */ private final long seconds; /** * The number of nanoseconds, later along the time-line, from the seconds field. * This is always positive, and never exceeds 999,999,999. */ private final int nanos;
咱们输出一个 Instant 对象的值来看一下,
Instant now = Instant.now(); System.out.println(now.getEpochSecond()); // 输出秒值 1460821922 System.out.println(now.getNano()); // 输出纳秒值 349000000
即当前时间时间轴上的时间点是从原点前进1460821922349000000纳秒的这个时刻。
使用绝对时间是最清晰、最简单不过的了。可是问题在于人,若是咱们能够跟别人说:“1460821922 全体集合,别晚了”,是最能准备表达时间点的,惋惜没有人理解的了。
这三个类联系很密切,能够一块儿介绍。LocalDate 表示一个日期,LocalTime 表示一天中的时间、LocalDateTime 包含了LocalDate 和 LocalTime 两部分,表示一个日期和时间。它们不关联任什么时候区信息。例如2015-08-27(用户端、兼职端上线日期)就是一个本地日期。因为没有时区信息,因此它没法与时间轴上一个准确的瞬时点对应。
你可使用now 或 of 方法建立这三个类的实例,以日期为例:
LocalDate today = LocalDate.now(); LocalDate online = LocalDate.of(2015, 8, 27); online = LocalDate.of(2015, Month.AUGUST, 27);
注意的是,Unix 和 java.util.Date 中年份从1900开始,月份从0开始。与之不符合生活的使用方式不一样,如今咱们可使用人类习惯的表达方式,数字几就是几月份,即1表示一月份。
星期的表达上也有相似的区别,java.util.Calendar 中,每周从周日开始,即周六值为7, 周日值为1。 如今咱们一样遵循人类表达方式,即每周从周一开始。
LocalDate wuyi = LocalDate.of(2016, 5, 1); System.out.println(wuyi.getDayOfWeek()); // 输出 SUNDAY System.out.println(wuyi.getDayOfWeek().getValue()); // 输出 7
Period 与 Duration 相似,它表示一段逝去的年、月和日。在Period 内部使用三个变量维护这段逝去的时间,
/** * The number of years. */ private final int years; /** * The number of months. */ private final int months; /** * The number of days. */ private final int days;
因此,Period的 getYears、getMonths、getDays 方法实际获取的值是两个日期之间时段的一部分。
LocalDate wuyi = LocalDate.of(2016, 5, 1); LocalDate shiyi = LocalDate.of(2016, 10, 1); Period period = Period.between(wuyi, shiyi); System.out.println(period.getYears()); // 输出 0 System.out.println(period.getMonths()); // 输出 5 System.out.println(period.getDays()); // 输出 0
要获取两个日期(只能是LocalDate)之间的天数,通常不会经过Period.between获取,由于这个方法返回的是几年几月几天,意义并不大。而是使用 until 方法, Period的between其实也是用的until 方法。
LocalDate wuyi = LocalDate.of(2016, 5, 1); LocalDate shiyi = LocalDate.of(2016, 10, 1); System.out.println(wuyi.until(shiyi, ChronoUnit.DAYS)); // 输出 153。即5.1到10.1有153天
时间调节器的工做已经基本被Qrartz、Spring Schedule这些框架给排挤的没有用武之地了。举例说一下,了解就好,有兴趣能够看TemporalAdjusters 的api。
例1:计算2016年1月第一个周二
LocalDate firstTuesday = LocalDate.of(2016, 1, 1).with(TemporalAdjusters.nextOrSame(DayOfWeek.TUESDAY));
例二:计算父亲节和母亲节 父亲节:每一年六月的第三个星期日 母亲节:每一年五月的第二个星期日
LocalDate faDay = LocalDate.of(2016, 6, 1).with(TemporalAdjusters.dayOfWeekInMonth(3, DayOfWeek.SUNDAY)); System.out.println(faDay); // 2016-06-19 LocalDate maDay = LocalDate.of(2016, 5, 1).with(TemporalAdjusters.dayOfWeekInMonth(2, DayOfWeek.SUNDAY)); System.out.println(maDay); // 2016-05-08
绝大多数应用场景下是不须要时区的,而且考虑时区会大大增长复杂性,好比处理夏令时。因此咱们不须要也并不推荐使用时区时间。一样这里仅做简单介绍。
带时区时间的类是ZoneDateTime,还有一个重要的类是时区标识ZoneId类,咱们能够直接经过ZoneDateTime.of(..省略年月日时分秒., ZoneId) 方法建立一个ZoneDateTime对象,也能够经过 LocalDateTime.now().atZone(ZoneId) 将一个本地类型转为带时区的类型。
这个表示带有偏移量(基于UTC计算)的时间,可是没有时区规则。该类专门用于一些不须要时区规则的应用程序,好比某些网络协议。
在Java 8 以前的版本,格式化时间咱们会使用 SimpleDateFormat, Java 8 中与之对应的是 DateTimeFormatter,该类提供三种方式格式化日期时间
贴一段代码简单看一下,更多使用方法能够参考 redscarf-common 的 DateUtils 类。
LocalDateTime now = LocalDateTime.now(); DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME; // 格式化 String formatNow = formatter.format(now); System.out.println(formatNow); // 输出 2016-04-18T23:43:37.154 // 解析 LocalDateTime newNow = LocalDateTime.parse(formatNow, formatter); System.out.println(newNow); // 输出 2016-04-18T23:43:37.154
列举几个典型的转换。若是用到其它类型间转换能够参考,而后本身尝试
/* * java.time.Instant 和 java.util.Date 互转 */ Instant instant = Instant.now(); Date date = Date.from(instant); instant = date.toInstant(); /* * java.time.Instant 和 java.sql.Timestamp 互转 */ Timestamp timestamp = Timestamp.from(instant); instant = timestamp.toInstant(); /* * java.time.LocalDateTime 和 java.sql.Timestamp 互转 */ LocalDateTime localDateTime = LocalDateTime.now(); timestamp = Timestamp.valueOf(localDateTime); localDateTime = timestamp.toLocalDateTime();
一张图片总结一下各类类型的格式:
<img src="https://raw.githubusercontent.com/miaoxg/static/master/datetime.png" width = "500" /> ## 3.7.2 一点强调 全部类型都是不可变的,无论什么操做,总会返回一个新的实例,原实例不变
java.time 包的 API 提供了大量相关的方法,这些方法通常有一致的方法前缀:
of:静态工厂方法。 parse:静态工厂方法,关注于解析。 get:获取某些东西的值。 is:检查某些东西的是不是true。 with:不可变的setter等价物。 plus:加一些量到某个对象。 minus:从某个对象减去一些量。 to:转换到另外一个类型。 at:把这个对象与另外一个对象组合起来,例如: date.atTime(time)。
这一部份内容难度很小,虽然写的内容挺多,可是也没作什么真正讲解说明,更多的是体系和 API 的介绍。目的是明确几个重要类的概念和做用,让咱们在开发中用的更顺手。