在上篇文章Java中的时间和日期(上)里面,简单介绍了Java中的Date类,Calendar类以及用于格式化的SimpleDateFormater类。使用这些的时候我们会明显地感受到其中的不便之处,比如,Calendar类的月份是从0开始计数的;日期格式输出不够友好,很多情况下都需要使用SimpleDateFormater类来格式化;一些简单得日期计算也比较麻烦等等。所以就有了joda-time
这种第三方库来简化java对于时间和日期的操作。为了改变这种情况,java 8中对时间和日期对处理就吸收了joda-time
库的特性。那么新的时间日期处理会带来怎样的便捷呢?这便是本篇文章所要聊的内容。
- Instant——它代表的是时间戳
- LocalDate——不包含具体时间的日期,比如2014-01-14。它可以用来存储生日,周年纪念日,入职日期等。
- LocalTime——它代表的是不含日期的时间
- LocalDateTime——它包含了日期及时间,不过还是没有偏移信息或者说时区。
- ZonedDateTime——这是一个包含时区的完整的日期时间,偏移量是以UTC/格林威治时间为基准的。
1. 月份的枚举类 Enum Month
java.time.Month
在以前使用Java的时候,你一定痛恨了月份的表示和计算,最主要的原因就是因为一月份是从0开始计数的。
在Java 8中为了改变这一现状,增加了一个Month
枚举类来表示月份。使用这个枚举类甚至还可以直接进行月份的加减运算!
-
of(int month)
这是一个静态方法,用于创建一个Month
对象。传入的参数当然是从1开始计数啦,1表示一月,12表示十二月。当传入的参数小于1或者大于12时,就会抛出异常。 -
getValue()
返回该Month
对象当前的值。一月份返回1,二月份返回2,依次类推。 -
minus(long months)
这个是用来做月份的减法计算的。传入的参数表示你想在该Month
对象的基础上减去几个月。如果是1月份减去2个月,返回的当然是11月份。 -
plus(long months)
用来计算月份的加法。传入的参数表示你想在该Month
对象的基础上增加几个月。比如12月加2个月就变成了二月份。 -
maxLength(), minLength()和length(boolean leapYear)
用来获取Month
对象表示的该月的日期数。其中,length(boolean leapYear)
中的参数表示是否为闰年。其实这三个方法返回的结果在很多情况下都是一样的,返回的都是当月的日期数,30或者31。只有二月份除外,当Month
对象表示二月份时,maxLength()
和length(true)
返回29,minLength()
和length(false)
返回28。
下面用代码来说明上述方法的使用:
public static void main(String[] args) {
System.out.println(Month.DECEMBER); // DECEMBER
System.out.println(Month.of(2)); // FEBRUARY
Month month = Month.FEBRUARY;
System.out.println(month.getValue()); // 2
System.out.println(month.minus(3)); // NOVEMBER
System.out.println(month.plus(2)); // APRIL
System.out.println(month.length(false)); // 28
System.out.println(month.length(true)); // 29
System.out.println(month.maxLength()); //29
System.out.println(month.minLength()); //28
}
有时候希望返回月份是中文,而不是英文。毕竟程序员大多都比较懒,能少转化一次自然是很好的。又或者你需要显示的是月份的英文缩写?Java 8都为你想到了。
只要调用 month.getDisplayName(TextStyle, Locale)
方法就行:
第一个参数是文本类型,也就是说你想显示完整的名称还是缩写;
第二个参数表示地区,如果没有特殊要求,传入Locale.getDefault()
就行。就像下面的代码演示的那样:
2. 星期的枚举类Enum DayOfWeek
java.time.DayOfWeek
DayOfWeek枚举类用来表示一个周的七天。常用的方法和 Month
枚举类的几乎一致,包括
of(int dayOfWeek)
静态方法用于创建DayOfWeek
对象;
getValue()
方法用来获取该对象的值;
plus(long days)
和minus(long days)
方法用来进行加减法计算。
getDisplayName(TextStyle style, Locale locale)
来格式化输出。代码演示如下:
public static void main(String[] args) {
System.out.println(DayOfWeek.FRIDAY); // FRIDAY
System.out.println(DayOfWeek.of(7)); // SUNDAY
DayOfWeek dayOfWeek = DayOfWeek.TUESDAY;
System.out.println(dayOfWeek.getValue()); // 2
System.out.println(dayOfWeek.plus(3)); // FRIDAY
System.out.println(dayOfWeek.minus(2)); // SUNDAY
Locale defaultLocal = Locale.getDefault();
System.out.println(dayOfWeek.getDisplayName(TextStyle.FULL, defaultLocal)); // 星期二
System.out.println(dayOfWeek.getDisplayName(TextStyle.SHORT, defaultLocal)); // 星期二
System.out.println(dayOfWeek.getDisplayName(TextStyle.NARROW, defaultLocal)); // 二
Locale locale = Locale.ENGLISH;
System.out.println(dayOfWeek.getDisplayName(TextStyle.FULL, locale)); // Tuesday
System.out.println(dayOfWeek.getDisplayName(TextStyle.SHORT, locale)); // Tue
System.out.println(dayOfWeek.getDisplayName(TextStyle.NARROW, locale)); // T
}
但是,在DayOfWeek
枚举类中,是没有maxLength(), minLength()和length(boolean leapYear)
这三个方法的,相信你们也知道是为什么。
最后说一句,由于Month
和DayOfWeek
只是枚举类,它们并不持有当前时间信息,所以就别妄想使用这两个枚举类来解决”今天是星期几”,”明天是几号”等问题了。
源码中的加减法计算
刚开始学Java的时候,计算月份/星期几乎是必备作业,不过当时用的是Date/Calendar类来计算,相当麻烦,
Java 8使用枚举来表示月份和星期之后,进行相应的加减法计算就变的相对简单了。
由于月份的计算和星期的计算原理是一样的,我们就只看Month
的加减法计算。
private static final Month[] ENUMS = Month.values();
public Month plus(long months) {
int amount = (int) (months % 12);
return ENUMS[(ordinal() + (amount + 12)) % 12];
}
public Month minus(long months) {
return plus(-(months % 12));
}
这里的处理方法很巧妙,减法直接调用加法的处理逻辑,当年我就没想到过,哈哈,值得学习。
3. LocalDate和LocalTime类
java.time.LocalDate
java.time.LocalTime
重头戏来了,现在开始隆重介绍Java 8的常用的时间日期类:LocalDate
和LocalTime
。
使用 LocalDate
可以获取当前日期(注意只是日期,不包含时间),并可以进行相应处理。
使用 LocalTime
可以获取当前时间(注意只是时间,不包含日期)并进行相应处理。这样就更好的符合“单一职责原则”。
构造方法
根据不同的需求,提供了不同的创建方式,主要包括两个静态方法now()
和of()
方法。其实,在后面我们会看到,在Java 8中,创建时间和日期几乎都会用的这两个方法。
public static void main(String[] args) {
LocalDate date1 = LocalDate.now();
LocalDate date2 = LocalDate.of(1998, 2, 4);
LocalDate date3 = LocalDate.ofEpochDay(180);
System.out.println(date1); // 2016-07-11
System.out.println(date2); // 1998-02-04
System.out.println(date3); // 1970-06-30
LocalTime time1 = LocalTime.now();
LocalTime time2 = LocalTime.now().withNano(0);
LocalTime time3 = LocalTime.of(12, 30);
LocalTime time4 = LocalTime.ofSecondOfDay(60 * 60 * 2);
System.out.println(time1); // 10:56:04.772
System.out.println(time2); // 10:56:04
System.out.println(time3); // 12:30
System.out.println(time4); // 02:00
}
withNano()
方法会在后面提及,主要是修改当前对象表示的纳秒数的值。在上面的代码中,有几点需要注意的地方:
ofEpochDay(long epochDay)
方法中的参数,指的是距1970年1月1日那天的时间间隔。- 在Java 8中,时间和日期的格式是按照ISO-8061的时间和日期标准来显示的。年份为4位数,月日时分秒都是2位数,不足两位用0补齐。
常用方法
LocalDate
还记得之前说过的,DayOfWeek
枚举类不持有当前时间信息,所以你无法单独使用它来得到今天是星期几
这种信息。然而如果获取到了当前日期的LocalDate
对象后,问题就迎刃而解了。
LocalDate
提供了大量的方法来进行日期信息的获取和计算。有了这一个LocalDate
对象,你不仅可以知道这个对象是哪年几月几日星期几,还能够对于年月日进行加减法计算,甚至你可以以周为单位进行日期的加减法计算,比如,你可以轻松解决两个周前的今天是几月几日
这类问题。
下面,是常用的方法,请自行查阅官方API文档
方法名 | 返回值类型 | 对该方法的解释 |
---|---|---|
getYear() | int | 获取当前日期的年份 |
getMonth() | Month | 获取当前日期的月份对象 |
getMonthValue() | int | 获取当前日期是第几月 |
getDayOfWeek() | DayOfWeek | 表示该对象表示的日期是星期几 |
getDayOfMonth() | int | 表示该对象表示的日期是这个月第几天 |
getDayOfYear() | int | 表示该对象表示的日期是今年第几天 |
withYear(int year) | LocalDate | 修改当前对象的年份 |
withMonth(int month) | LocalDate | 修改当前对象的月份 |
withDayOfMonth(int dayOfMonth) | LocalDate | 修改当前对象在当月的日期 |
isLeapYear() | boolean | 是否是闰年 |
lengthOfMonth() | int | 这个月有多少天 |
lengthOfYear() | int | 该对象表示的年份有多少天(365或者366) |
plusYears(long yearsToAdd) | LocalDate | 当前对象增加指定的年份数 |
plusMonths(long monthsToAdd) | LocalDate | 当前对象增加指定的月份数 |
plusWeeks(long weeksToAdd) | LocalDate | 当前对象增加指定的周数 |
plusDays(long daysToAdd) | LocalDate | 当前对象增加指定的天数 |
minusYears(long yearsToSubtract) | LocalDate | 当前对象减去指定的年数 |
minusMonths(long monthsToSubtract) | LocalDate | 当前对象减去注定的月数 |
minusWeeks(long weeksToSubtract) | LocalDate | 当前对象减去指定的周数 |
minusDays(long daysToSubtract) | LocalDate | 当前对象减去指定的天数 |
compareTo(ChronoLocalDate other) | int | 比较当前对象和other对象在时间上的大小,返回值如果为正,则当前对象时间较晚, |
isBefore(ChronoLocalDate other) | boolean | 比较当前对象日期是否在other对象日期之前 |
isAfter(ChronoLocalDate other) | boolean | 比较当前对象日期是否在other对象日期之后 |
isEqual(ChronoLocalDate other) | boolean | 比较两个日期对象是否相等 |
列出这么多方法,不是要你死记硬背记住它们,而是要在脑海有个印象,知道有哪些常用方法,可以做什么。概括起来,LocalDate
类中常用的方法有四种:获取日期信息,修改日期信息,加减法运算和日期对象间的比较。记住了这些,以后在工作中就可以查阅使用,而不用自己在造一遍轮子。
有几点需要注意的地方:
- 上面列表里面有一个
ChronoLocalDate
,它是一个接口,LocalDate
类实现了这个接口,所以直接传一个LocalDate
类对象即可。 - isEqual(ChronoLocalDate other)这个方法,如果两个对象是同一个对象,或者这两个对象的值相等(同年同月同日),则返回true,否则返回false。
- 当一个方法返回的是
LocalDate
对象时,便可以使用链式调用
。举个例子,获取昨天的日期,我们可以直接这样写:LocalDate.now().minusDays(1)
。
下面用代码演示几个方法:
public static void main(String[] args) {
LocalDate now = LocalDate.now();
System.out.println(now.getYear()); // 2016
System.out.println(now.getDayOfWeek()); // MONDAY
System.out.println(now.getDayOfMonth()); // 11
System.out.println(now.withMonth(3)); // 2016-03-11
System.out.println(now.minusWeeks(2)); // 2016-06-27
System.out.println(now.plusDays(10)); // 2016-07-21
LocalDate firstDayOfYear = LocalDate.of(2016,1,1);
System.out.println(now.compareTo(firstDayOfYear)); // 6
System.out.println(now.isAfter(firstDayOfYear)); // true
System.out.println(now.isEqual(firstDayOfYear)); // false
}
LocalTime
LocalDate
类中的方法和LocalDate
中的类似,同样可以分为:获取时间信息,修改时间信息,加减法运算和时间对象间的比较。方法的具体描述我就不写了。根据LocalDate
类中列举的常用方法,你也能猜得出在LocalTime
类中有哪些对应的常用方法。下面还是用代码演示几个方法:
public static void main(String[] args) {
LocalTime now = LocalTime.now();
System.out.println(now.getHour()); // 14
System.out.println(now.getMinute()); // 15
System.out.println(now.getSecond()); // 22
System.out.println(now.getNano()); // 881000000
System.out.println(now.withNano(0)); // 14:15:22
System.out.println(now.minusHours(3)); // 11:15:22.881
System.out.println(now.minusMinutes(15)); // 14:00:22.881
System.out.println(now.minusSeconds(20)); // 14:15:02.881
LocalTime halfOfDay = LocalTime.of(12 ,0);
System.out.println(now.compareTo(halfOfDay)); // 1
System.out.println(now.isAfter(halfOfDay)); // true
}
不过有几点需要说明:
LocalTime
中没有isEqual()
方法。- 在
getNano()
中,nano指的是纳秒(毫微秒),1秒等于1亿纳秒。
4. LocalDateTime日期时间类
java.time.LocalDateTime
或许有人觉得,将日期和时间分开处理有些不方便。我想将时间和日期一起处理怎么办?当然可以,Java 8中还提供了LocalDateTime
来满足你的这个需求。
构造方法
和前面的类似,可以使用静态方法now()
和静态方法of()
来创建一个LocalDateTime
对象。比如:
public static void main(String[] args) {
LocalDateTime now = LocalDateTime.now();
System.out.println(now); // 2016-07-11T14:27:20.169
LocalDateTime dateTime1 = LocalDateTime.of(1990, 1, 1, 12, 3);
LocalDateTime dateTime2 = LocalDateTime.of(2000, 2, 4, 8, 4, 20);
System.out.println(dateTime1); // 1990-01-01T12:03
System.out.println(dateTime2); // 2000-02-04T08:04:20
}
通常,你需要在of()
方法中传入6个参数,表示年月日时分秒。关于月份,既可以传入Month对象,也可以传入int值(当然1表示一月份)。也可以将秒这个参数省略了,传入5个参数。也可以增加一个纳秒参数,变为7个参数。
常用方法
这个不想再说了,和LocalDate
及LocalTime
类似。
5. LocalDateTime和LocalDate、LocalTime之间的转化
LocalDateTime既然是“集LocalDate和LocalTime的大成者”,自然能将LocalDateTime转化位LocalDate或者LocalTime,而且方法很简单,只需要调用toLocalDate()
或者toLocalTime()
方法,就像下面演示的那样:
public static void main(String[] args) {
LocalDateTime dateTime = LocalDateTime.now();
System.out.println(dateTime); // 2016-07-11T16:57:41.217
LocalDate date = dateTime.toLocalDate();
LocalTime time = dateTime.toLocalTime();
System.out.println(date); // 2016-07-11
System.out.println(time); // 16:57:41.217
}
6. 解析和格式化
在Date和Calendar统治的时代,想要格式化一个日期,只能用Date来格式化,并且SimpleDateFormat还有线程安全隐患,无疑很麻烦。而现在,在Java 8中,这些问题都不复存在了。
有一点需要再次强调,再Java 8中,时间日期的格式是按照ISO-8061的时间和日期标准来显示的。年份为4位数,月日时分秒都是2位数,不足两位用0补齐,日期之间需要用短横线连接,时间之间要用:
连接。必须按照此规则来进行解析,比如:
public static void main(String[] args) {
LocalDate date = LocalDate.parse("2016-09-08");
LocalTime time = LocalTime.parse("12:24:43");
System.out.println(date); // 2016-09-08
System.out.println(time); // 12:24:43
}
当然,Java是宽容的,如果你不按照ISO-8061
的格式传入,也有解决办法,
可以使用parse(CharSequence text, DateTimeFormatter formatter)
这个方法,
第一个参数传入你要解析的文本。第二个参数传入你所想要的格式类型。
解析一个日期字符串 String--->LocalDateTime
public static void main(String[] args) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
LocalDate date = LocalDate.parse("20160708", formatter);
System.out.println(date); // 2016-07-08
DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
LocalDateTime datetime = LocalDateTime.parse("2013/12/31 23:59:59", formatter2);
System.out.println(datetime); // LocalDateTime 2013-12-31T23:59:59
System.out.println(formatter2.format(datetime)); //String 2013/12/31 23:59:59
}
格式化一个时间日期对象 LocalDateTime ---> String 也很简单,就想下面一样:
public static void main(String[] args) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
LocalDateTime date = LocalDateTime.now();
String dateStr = date.format(formatter);
System.out.println(dateStr); // 2013/12/31 23:59:59
System.out.println(formatter.format(date)); // 2013/12/31 23:59:59
}
7. Clock时钟
java.time.Clock
时钟,类似于钟表的概念,提供了如系统时钟、固定时钟、特定时区的时钟
//时钟提供给我们用于访问某个特定 时区的 瞬时时间、日期 和 时间的。
Clock c1 = Clock.systemUTC(); //系统默认UTC时钟(当前瞬时时间 System.currentTimeMillis())
System.out.println(c1.millis()); //每次调用将返回当前瞬时时间(UTC) 1532179537366
Clock c2 = Clock.systemDefaultZone(); //系统默认时区时钟(当前瞬时时间)
System.out.println(c2.millis()); //每次调用将返回当前瞬时时间(UTC) 1532179594406
Clock c3 = Clock.system(ZoneId.of("Asia/Shanghai"));//上海时区
System.out.println(c3.millis());//每次调用将返回当前瞬时时间(UTC) 1532179594406
8.Instant瞬时时间
java.time.Instant
瞬时时间,等价于以前的System.currentTimeMillis()
//瞬时时间 相当于以前的System.currentTimeMillis()
Instant instant1 = Instant.now();
System.out.println(instant1.getEpochSecond());//精确到秒 得到相对于1970-01-01 00:00:00 UTC的时间 1532179778
System.out.println(instant1.toEpochMilli()); //精确到毫秒 1532179778266
Clock clock1 = Clock.systemUTC(); //获取系统UTC默认时钟
Instant instant2 = Instant.now(clock1);//得到时钟的瞬时时间
System.out.println(instant2.toEpochMilli());
Clock clock2 = Clock.fixed(instant1, ZoneId.systemDefault()); //固定瞬时时间时钟
Instant instant3 = Instant.now(clock2);//得到时钟的瞬时时间
System.out.println(instant3.toEpochMilli());//equals instant1