最近开发过程中遇到了很多时间类处理,由于对Calender类不熟悉,我说这个类设计的烂,谁赞成,谁反对?也被推荐过joda-time类库,鉴于项目用的都是java 8了,是时候了解一下java.time包下的类了
导言
java 8 日期类的优势
用完java 8的API之后,只有一个感觉:爽,没有啰嗦的方法,很多静态工厂方法,of,from见名知意,用到后面,一些api自己都可以猜出来了,同时api更人性化,例如对比之前的获取月份方法,是从0开始,机械化的思维,反观java 8,我能通过getMonthValue直接获取,无须+1
对于joda-time,从个人角度讲,实在不想再去记忆一套api,同时从项目角度来说,我能用jdk实现的,为什么要依赖第三方jar包,这点对于实际开发来说更重要。但对于还在使用java 8以下版本的同学joda-time还是值得推荐的
API的记忆方法
在Effective java读书笔记一文中,静态方法相较构造方法有更多的优势,尤其是在提供给开发者使用时。java 8这方面做得很好,of,from,parse,format,minus,plus等等都是见名知意的方法
时间分类
java 8提供了3个基础时间类:LocalDate, LocalDateTime, LocalTime,分别代表日期,日期+时间,时间(时分秒)
同时三者之间可以部分转换,之所以称之为部分,很简单的例子是日期无法直接转换为具体的日期+时间,因为它缺少时分秒,这可以理解为一种精度损失,当然你可以通过默认值来补全
Instant表示瞬时时间,精确到毫秒,可用于记录时间戳
java 8支持通过时区id,时区偏移量来获取时间
在实际开发中我们关注的有以下几个方面的时间:
- 时间戳,既有毫秒,也有秒,秒主要是PHP等服务返回的标准时间戳
- Date,不要忘了数据库对应的实体中,使用的时间对象仍是Date
- 待格式化的字符串,很常见的需求,将字符串解析为时间,或是将时间格式化为文本
这几个方面时间的互相转换也需要关注
API
获取时间
now
now方法用于获取日期类的当前值,例如LocalDate获取当前日期,LocalTime获取当前时间的时分秒等信息
获取年月日
根据常识,仅LocalDate, LocalDateTime可以获取年月日,分别由getYear,getMonthValue,getDayOfMonth获取,时分秒的获取方式同理LocalDateTime
获取自1970-01-01T00:00:00的毫秒数,秒数,天数
不用死记硬背,只要想清楚这几个类分别代表了什么类型的时间即可推断出api
1 2 3
| Instant.now().toEpochMilli(); LocalDateTime.now().toEpochSecond(ZoneOffset.ofHours(8)); LocalDate.now().toEpochDay();
|
ZoneOffset.ofHours(8)可以理解为北京时间位于东八区
获取前一天,一个月,一年等等
记住两个单词即可,minus表示减,plus表示加
至于你想加减些什么,首先要确定要加减的单位是什么,比如分钟,那肯定是在LocalDateTime,LocalTime里找,加年,加月同理,剩下的api就不啰嗦了,授人以鱼不如授人以渔,读者有兴趣自己探索。
时间转换
文本与时间之间的相互转换
parse用于处理从文本到日期的转换,根据DateTimeFormatter的格式解析成日期
1
| LocalDate date = LocalDate.parse("2022-01-11", DateTimeFormatter.ofPattern("yyyy-MM-dd"))
|
yyyy-MM-dd是默认的格式,可以省略第二个参数,类似的HH:mm:ss在转换为时分秒时也可以省略
将日期格式化为文本
1
| String text = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))
|
时间戳与时间之间的转换
注意两点:
- Instant用于表示瞬时值,它和秒,毫秒是相关联的,再将Instant转换为LocalDateTime
1 2 3
| long time = 1548154964271L; Instant instant = Instant.ofEpochMilli(time); LocalDateTime dateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
|
- 需要判断时间戳是毫秒还是秒,PHP等语言只能精确到秒,请求这类接口时需注意
给出一个转换方法示例
1 2 3 4 5 6 7 8 9 10 11 12 13
| public static final long MAX_TIME_STAMP = 10000_000_000L;
public static final long MIN_TIME_STAMP = 1000_000_000L;
private static Instant transToInstant(long time) { if (time < MAX_TIME_STAMP && time > MIN_TIME_STAMP) { return Instant.ofEpochSecond(time); } if (time < MAX_TIME_STAMP * 1000 && time > MIN_TIME_STAMP * 1000){ return Instant.ofEpochMilli(time); } throw new IllegalArgumentException("illegal time value"); }
|
与Date类的转换
记住一点,旧版的Date与java 8中的日期类的转换桥梁是Instant
1 2 3
| Date date = Date.from(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant());
LocalDateTime dateTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
|
获取时间段
Period和ChronoUnit都可以做到计算时间段,但又有区别,下面以计算两个时间时间的天数为例
1 2
| Period period = Period.between(LocalDate.of(2017, 1, 19), LocalDate.of(2019, 5, 10)); long days = ChronoUnit.DAYS.between(LocalDate.of(2017, 1, 19), LocalDate.of(2019, 5, 10));
|
Period返回的是两个日期之间相差几年几月几日,以上面两个日期为例,以下三个方法分别返回: 2年, 3个月, 21天
period.getYears(),period.getMonths(),period.getDays()
ChronoUnit则计算出了两个日期直接具体的天数,结果为841天
与周相关的几个API
1 2 3 4 5 6 7 8 9
| LocalDate now = LocalDate.now();
DayOfWeek dayOfWeek = now.getDayOfWeek();
LocalDate nextMonday = now.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
LocalDate lastDayofMonth = now.with(TemporalAdjusters.lastDayOfMonth());
int weekth = now.get(ChronoField.ALIGNED_WEEK_OF_MONTH);
|
打印输出为:
1 2 3 4
| dayOfWeek = TUESDAY nextMonday = 2022-05-06 lastDayofMonth = 2022-04-30 weekth = 5
|
以前是笔者开发中遇到的api,还有很多有用的api未介绍,大家可以用到时查阅文档
结束语
java 8时间类的使用写到这里告一段落,关于时区的使用,也见缝插针的介绍了下,写这篇文章对笔者最大的挑战是表达能力,api很多,我想表达的是有规律的使用api,而不是死记硬背,最后分享一个自己写的DateUtils
DateUtils
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
|
public class DateUtils {
public static LocalDate today = LocalDate.now();
public static LocalDateTime now = LocalDateTime.now();
public static final String GENERAL_PATTERN = "yyyy-MM-dd HH:mm:ss";
public static final String GENERAL_DATE_PATTERN = "yyyy-MM-dd";
public static final long MAX_TIME_STAMP = 10000_000_000L;
public static final long MIN_TIME_STAMP = 1000_000_000L;
public static <L, M, R> Triple<Integer, Integer, Integer> getYearMonthDay() { return ImmutableTriple.of(today.getYear(), today.getMonthValue(), today.getDayOfMonth()); }
public static String getDateText() { return today.format(DateTimeFormatter.ofPattern(GENERAL_DATE_PATTERN)); }
public static String getDateTimeText() { return now.format(DateTimeFormatter.ofPattern(GENERAL_PATTERN)); }
public static String getDateTimeText(long time) { return now.format(DateTimeFormatter.ofPattern(GENERAL_PATTERN)); }
public static boolean isToday(String time) { return LocalDate.parse(time).isEqual(today); }
public static boolean isToday(long time) { LocalDate date = LocalDateTime.ofInstant(transToInstant(time), ZoneId.systemDefault()).toLocalDate(); return date.isEqual(today); }
public static Pair<Long, Long> getDaysAgoTime(Integer days) { LocalDate localDate = LocalDate.now().minusDays(days); long begin = localDate.atTime(0, 0, 0).toEpochSecond(ZoneOffset.ofHours(8)); long end = localDate.atTime(23, 59, 59).toEpochSecond(ZoneOffset.ofHours(8)); return ImmutablePair.of(begin, end); }
public static Pair<Long, Long> getDurationPair(String beginStr, String endStr) { long begin = LocalDate.parse(beginStr, DateTimeFormatter.ofPattern(GENERAL_DATE_PATTERN)).atTime(0, 0, 0).toEpochSecond(ZoneOffset.ofHours(8)); long end = LocalDate.parse(endStr, DateTimeFormatter.ofPattern(GENERAL_DATE_PATTERN)).atTime(23, 59, 59).toEpochSecond(ZoneOffset.ofHours(8)); return ImmutablePair.of(begin, end); }
public static long getBetweenDays(long start, long end) { return ChronoUnit .DAYS .between( LocalDateTime.ofInstant(transToInstant(start), ZoneId.systemDefault()), LocalDateTime.ofInstant(transToInstant(end), ZoneId.systemDefault()) ); }
public static long getBetweenDays(String start, String end) { return ChronoUnit.DAYS.between(LocalDate.parse(start), LocalDate.parse(end)); }
public static List<String> getBetweenDates(String start, String end) { List<String> list = new ArrayList<>();
LocalDate startDate = LocalDate.parse(start); LocalDate endDate = LocalDate.parse(end);
long distance = ChronoUnit.DAYS.between(startDate, endDate);
if (distance < 1) { return list; }
if (distance == 0) { list.add(start); return list; }
Stream.iterate(startDate, d -> d.plusDays(1)).limit(distance + 1).forEach(f -> { list.add(f.toString()); }); return list; } }
|