实战 - 封装日期工具类
当然更加建议读者自己多动手实验,最好的办法就是多给几个需求给自己,强制自己用JDK8的方法去实现,你会发现你掌握这些API会特别快。
注意事项:
所有的工具代码都使用了同一个本地格式化器构建方法:generateDefualtPattern()
:
/** * 生成默认的格式器 * * @param timeFormat 指定格式 * @return 默认时间格式器 */ private static DateTimeFormatter generateDefualtPattern(String timeFormat) { return new DateTimeFormatterBuilder().appendPattern(timeFormat) .parseDefaulting(ChronoField.HOUR_OF_DAY, 0) .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0) .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0) .toFormatter(Locale.CHINA); } 复制代码
获取指定时间的上一个工作日和下一个工作日
注意这个版本是不会判断节假日这些内容的,当然这里是手动实现的版本。
/** * 获取指定时间的上一个工作日 * * @param time 指定时间 * @param formattPattern 格式化参数 * @return */ public static String getPreWorkDay(String time, String formattPattern) { DateTimeFormatter dateTimeFormatter = generateDefualtPattern(formattPattern); LocalDateTime compareTime1 = LocalDateTime.parse(time, dateTimeFormatter); compareTime1 = compareTime1.with(temporal -> { // 当前日期 DayOfWeek dayOfWeek = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK)); // 正常情况下,每次减去一天 int dayToMinu = 1; // 如果是周日,减去2天 if (dayOfWeek == DayOfWeek.SUNDAY) { dayToMinu = 2; } // 如果是周六,减去一天 if (dayOfWeek == DayOfWeek.SATURDAY) { dayToMinu = 1; } return temporal.minus(dayToMinu, ChronoUnit.DAYS); }); return compareTime1.format(dateTimeFormatter); } /** * 获取指定时间的下一个工作日 * * @param time 指定时间 * @param formattPattern 格式参数 * @return */ public static String getNextWorkDay(String time, String formattPattern) { DateTimeFormatter dateTimeFormatter = generateDefualtPattern(formattPattern); LocalDateTime compareTime1 = LocalDateTime.parse(time, dateTimeFormatter); compareTime1 = compareTime1.with(temporal -> { // 当前日期 DayOfWeek dayOfWeek = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK)); // 正常情况下,每次增加一天 int dayToAdd = 1; // 如果是星期五,增加三天 if (dayOfWeek == DayOfWeek.FRIDAY) { dayToAdd = 3; } // 如果是星期六,增加两天 if (dayOfWeek == DayOfWeek.SATURDAY) { dayToAdd = 2; } return temporal.plus(dayToAdd, ChronoUnit.DAYS); }); return compareTime1.format(dateTimeFormatter); } 复制代码
判断当前时间是否小于目标时间
判断当前时间是否小于目标时间,这里结合了之前我们学到的一些方法,注意这里的时区使用的是当前系统的时区,如果你切换别的时区,可以看到不同的效果。另外这里使用的是LocalDateTime
不要混淆了。
/** * 使用jdk 1.8 的日期类进行比较时间 * 判断当前时间是否小于目标时间 * * @param time 时间字符串 * @param format 指定格式 * @return 判断当前时间是否小于目标时间 */ public static boolean isBefore(String time, String format) { DateTimeFormatter dateTimeFormatter = generateDefualtPattern(format); LocalDateTime compareTime = LocalDateTime.parse(time, dateTimeFormatter); // getNowByNew 封装了 now()方法 LocalDateTime current = LocalDateTime.parse(getNowByNew(format), dateTimeFormatter); long compare = Instant.from(compareTime.atZone(ZoneId.systemDefault())).toEpochMilli(); long currentTimeMillis = Instant.from(current.atZone(ZoneId.systemDefault())).toEpochMilli(); return currentTimeMillis < compare; } 复制代码
获取指定时间属于星期几
属于对JDK8自身的方法进行二次封装。
/** * 获取指定时间属于星期几 * 返回枚举对象 * * @param date 日期 * @param formattPattern 格式 * @return */ public static DayOfWeek getDayOfWeek(String date, String formattPattern) { DateTimeFormatter dateTimeFormatter = generateDefualtPattern(formattPattern); return LocalDate.parse(date, dateTimeFormatter).getDayOfWeek(); } 复制代码
获取开始日期和结束日期之间的日期
这里需要注意不是十分的严谨,最好是在执行之前日期的判断
public static final String yyyyMMdd = "yyyy-MM-dd"; /** * 获取开始日期和结束日期之间的日期(返回List<String>) * * @param startTime 开始日期 * @param endTime 结束日期 * @return 开始与结束之间的所以日期,包括起止 */ public static List<String> getMiddleDateToString(String startTime, String endTime) { LocalDate begin = LocalDate.parse(startTime, DateTimeFormatter.ofPattern(yyyyMMdd)); LocalDate end = LocalDate.parse(endTime, DateTimeFormatter.ofPattern(yyyyMMdd)); List<LocalDate> localDateList = new ArrayList<>(); long length = end.toEpochDay() - begin.toEpochDay(); // 收集相差的天数 for (long i = length; i >= 0; i--) { localDateList.add(end.minusDays(i)); } List<String> resultList = new ArrayList<>(); for (LocalDate temp : localDateList) { resultList.add(temp.toString()); } return resultList; } 复制代码
日期API常见的坑:
LocalDateTime
的格式化yyyy-MM-dd
报错:
第一次使用,最容易出现问题的diamante如下的形式所示,比如我们
LocalDateTime parse2 = LocalDateTime.parse("2021-11-11", DateTimeFormatter.ofPattern("yyyy-MM-dd")); 复制代码
在运行的时候,会抛出如下的异常:
java.time.format.DateTimeParseException: Text '2021-11-11' could not be parsed: Unable to obtain LocalDateTime from TemporalAccessor: {},ISO resolved to 2021-11-11 of type java.time.format.Parsed 复制代码
下面来说一下解决办法:
第一种解决办法比较蛋疼,但是确实是一种非常稳妥的解决方法。
try { LocalDate localDate = LocalDate.parse("2019-05-27", DateTimeFormatter.ofPattern("yyyy-MM-dd")); LocalDateTime localDateTime = localDate.atStartOfDay(); System.out.println(localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); } catch (Exception ex) { ex.printStackTrace(); } 复制代码
另外,还有一种方法是使用下面的方法,构建一个"中国化"的日期格式器:
/** * 生成默认的格式器 * * @param timeFormat 指定格式 * @return 默认时间格式器 */ private static DateTimeFormatter generateDefualtPattern(String timeFormat) { return new DateTimeFormatterBuilder().appendPattern(timeFormat) .parseDefaulting(ChronoField.HOUR_OF_DAY, 0) .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0) .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0) .toFormatter(Locale.CHINA); } 复制代码
调用format出现xx not be parsed, unparsed text found at index 10
问题原因:使用错误的格式去格式字符串,比如yyyy-MM-dd
格式化 2020-05-12 12:15:33
这种格式就会出现溢出,解决办法:使用正确的格式即可
对于上面几个问题的根本解决办法 原因:因为localdatetime 在进行格式化的时候如何case没有找到对应的格式,那么就会出现类似unsupport
方法
/** * 生成默认的格式器 * * @param timeFormat 指定格式 * @return */ private static DateTimeFormatter generateDefualtPattern(String timeFormat) { return new DateTimeFormatterBuilder().appendPattern(timeFormat) .parseDefaulting(ChronoField.HOUR_OF_DAY, 1) .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 1) .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0) .toFormatter(Locale.CHINA); } 复制代码
下面是其他的问题回答:
StackFlow地址:DateTimeParseException: Text could not be parsed: Unable to obtain LocalDateTime from TemporalAccessor
StackFlow地址:StackFlow无法解析文本:无法从TemporalAccessor获取LocalDateTime
StackFlow地址:解析LocalDateTime(Java 8)时,无法从TemporalAccessor获取LocalDateTime
DateTimeParseException一些小坑
参考了下面的异常日志,根本的原因是DateTimeFormatter
格式化没有HH
选项,这也是比较坑的地方
java.time.format.DateTimeParseException: Text '2017-02-02 08:59:12' could not be parsed: Unable to obtain LocalDateTime from TemporalAccessor: {MinuteOfHour=59, NanoOfSecond=0, SecondOfMinute=12, MicroOfSecond=0, MilliOfSecond=0, HourOfAmPm=8},ISO resolved to 2017-02-02 of type java.time.format.Parsed 复制代码
总结:
在个人编写工具类的过程中,发现确实比之前的Date
和Calendar
这两个类用起来好很多,同时JDK8的日期类都是线程安全的。当然JDK8对于国内使用不是十分友好,这也没有办法毕竟是老外的东西,不过解决办法也有不少,习惯了将解决套路之后也可以接受。最后,有条件最好使用谷歌的搜索引擎,不仅可以帮你把坑跨过去,老外很多大神还会给你讲讲原理,十分受用。
写在最后
写稿不易,求赞,求收藏。
最后推荐一下个人的微信公众号:“懒时小窝”。有什么问题可以通过公众号私信和我交流,当然评论的问题看到的也会第一时间解答。
其他问题
- 关于LocalDate的一个坑
直接上源代码,LocalDate
仅代表一个日期,而不代表DateTime。因此在格式化时“ HH:mm:ss”是毫无意义的,如果我们的格式化参数不符合下面的规则,此方法会抛出异常并且说明不支持对应的格式化操作。
private int get0(TemporalField field) { switch ((ChronoField) field) { case DAY_OF_WEEK: return getDayOfWeek().getValue(); case ALIGNED_DAY_OF_WEEK_IN_MONTH: return ((day - 1) % 7) + 1; case ALIGNED_DAY_OF_WEEK_IN_YEAR: return ((getDayOfYear() - 1) % 7) + 1; case DAY_OF_MONTH: return day; case DAY_OF_YEAR: return getDayOfYear(); case EPOCH_DAY: throw new UnsupportedTemporalTypeException("Invalid field 'EpochDay' for get() method, use getLong() instead"); case ALIGNED_WEEK_OF_MONTH: return ((day - 1) / 7) + 1; case ALIGNED_WEEK_OF_YEAR: return ((getDayOfYear() - 1) / 7) + 1; case MONTH_OF_YEAR: return month; case PROLEPTIC_MONTH: throw new UnsupportedTemporalTypeException("Invalid field 'ProlepticMonth' for get() method, use getLong() instead"); case YEAR_OF_ERA: return (year >= 1 ? year : 1 - year); case YEAR: return year; case ERA: return (year >= 1 ? 1 : 0); } throw new UnsupportedTemporalTypeException("Unsupported field: " + field); } 复制代码
- 格式化问题:
调用DateFomatter 有可能的报错,基本是由于使用错误到格式或者使用错误的时间类
Error java.time.format.DateTimeParseException: could not be parsed, unparsed text found at index 10