最近在做账单结算业务,需要根据客户选择的结算方式来推算下一次结算日期以及该次结算日期段。

推算日期这样的业务直男君以前就写过,只不过使用的是熟悉的 java.util.date 和 java.util.Calendar。

现在公司使用的 JDK8,所以本次就决定新的日期 API 啦,顺便结合业务实现对比回顾下。

 
直男君水平有限,无法从原理上洋洒,只能从业务开发角度分为这么几块讲:
  • Java8 前喜闻乐见的日期操作
  • 为什么推荐用新的日期 API
  • 新 API 的典型使用
  • 两种业务场景实现

以前喜闻乐见的日期操作

熟悉的日期操作三基友:java.util 包下的 Date 和 Calendar 加上 java.text.SimpleDateFormat。

1)得到当前日期对象

  1. //获取日期对象(包含时间)
  2. Date date = new Date();
  3. Date date1 = new Date(System.currentTimeMillis());

2)日历操作

  1. //获取日历对象
  2. Calendar cald = Calendar.getInstance();
  3. cald.setTime(date); //目标日期对象对应的日历
  4. cald.add(Calendar.MONTH, 1); //日历选取(下个月本号)
  5. Date date2 = cald.getTime(); //目标日历对应的日期对象

3)日期格式化

  1. //日期格式化
  2. DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  3. String nowDateStr = df.format(now); //日期 >> 格式串
  4. System.out.println(nowDateStr); //2019-05-28 20:11:11
  5. String dateStr = "2019-05-28 20:33:33";
  6. Date aimDate = df.parse(dateStr); //格式串 >> 日期
  7. System.out.println(aimDate);
  8. System.out.println(aimDate.after(now)); //日期比较
  9. System.out.println(aimDate.compareTo(now));

为什么推荐 Java8 日期 API (为啥用的爽)

1)更清晰合理的语义和架构

不再使用 Date(util 和 sql 包都有) 表示日期、时间、日期时间,而是 LocalDate、LocalTime、LocalDateTime。

以前日期格式化的类是在 java.text 包下,现在全部在 java.time 下,且语义分工明确。

  • java.time 最常用的基础类 LocalDate、LocalTime、LocalDateTime、Instant、Period、Duration 等。
  • java.time.format 日期格式化的类在这里,当然基础类已经提供了相关方法。
  • java.time.zone 时区支持
  • java.time.xxx 等

2)线程安全的设计

SimpleDataFormat 类一直为人诟病的线程安全问题:

  1. //SimpleDataFormat 源码部分↓
  2. private StringBuffer format(Date date, StringBuffer toAppendTo,
  3. FieldDelegate delegate) {
  4. // Convert input date to time field list
  5. calendar.setTime(date);
  6. boolean useDateFormatSymbols = useDateFormatSymbols();
  7. ......

PS:直男君倒觉得没什么,避免单例或者线程共享的场景就行了。

新的 API 类都是不可变类,避免了线程安全隐患。

  1. public final class LocalDateTime
  2. public final class Instant
  3. ......

PS:什么叫不可变类?

String 类就是不可变类,所以有这样的说法:每次使用 + 连接字符串都会生成新的 String 对象,对于重复拼接的场景应该使用 StringBuilder/StringBuffer。

参考 String 类的设计,可总结不可变类的关键特点:类名 final,保证类不会被拓展;所有成员变量 final 且不提供任何可以修改实例状态的方法。

3)更方便的日期操作

基础类比如 LocalDateTime 就已经合理的提供了足够多的场景方法,日期调整、格式化、比较等等。

4)时区、日历系统支持(体会还不深)

5)其他

新 API 典型使用

1)获取日期对象

提供了各种静态方法构造日期时间对象,以 LocalDate 为例:

  1. LocalDate date = LocalDate.now();
  2. System.out.println(date);
  3. date = LocalDate.of(2019, 5, 30);//LocalDate.of(2019, Month.MAY, 30)
  4. System.out.println(date);
  5. date = LocalDate.ofYearDay(2019, 300);
  6. System.out.println(date);
  7. //其他

2)日期时间比较

  1. LocalDateTime now = LocalDateTime.now();
  2. logger.info("now: {}", now);
  3. LocalDate date = LocalDate.of(2017, 7, 27);
  4. LocalTime time = LocalTime.of(17, 11);
  5. LocalDateTime aim = LocalDateTime.of(date, time);
  6. logger.info("aim: {}", aim);
  7. System.out.println(now.compareTo(aim));
  8. System.out.println(now.isBefore(aim));
  9. System.out.println(now.isEqual(aim));
  10. System.out.println(now.isAfter(aim));

3)格式化

  1. //DateTimeFormatter 提供了很多内置格式,但好像都不是我们想要的
  2. DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME;
  3. LocalDateTime now = LocalDateTime.now();
  4. String str = now.format(formatter);
  5. System.out.println(str);
  6. //我们想要的格式
  7. formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
  8. System.out.println(now.format(formatter));
  9. //日期字符串转日期对象
  10. String dateStr = "2019-05-30";
  11. LocalDate aim = LocalDate.parse(dateStr);
  12. System.out.println(aim);
  13. //日期时间串一起转对象
  14. String dateTimeStr = "2019-05-30 10:30:00";
  15. LocalDateTime aim2 = LocalDateTime.parse(dateTimeStr, formatter);
  16. System.out.println(aim2);

4)日历操作(日期调整)

这个是业务实现上最有用的。

不再像以前使用 Calendar,而是基础类搭配 TemporalAdjusters 很好用!

  1. //今天
  2. LocalDateTime now = LocalDateTime.now();
  3. System.out.println(now);
  4. //分别获取今天日历值 也可以使用 ChronoField 的常量指定获取
  5. logger.info("今天是,本月{}号, 周{}, 今年的第{}天, 今年的第{}月",
  6. now.getDayOfMonth(), now.getDayOfWeek().getValue(), now.getDayOfYear(), now.getMonthValue());
  7.  
  8. //明年今天
  9. LocalDateTime aim = now.plusYears(1);
  10. System.out.println(aim);
  11. //下月今天
  12. aim = now.plusMonths(1);
  13. System.out.println(aim);
  14. //下周今天
  15. aim = now.plusWeeks(1);
  16. System.out.println(aim);
  17.  
  18. //本月一号
  19. aim = now.with(TemporalAdjusters.firstDayOfMonth());
  20. System.out.println(aim);
  21. //本月最后一天
  22. aim = now.with(TemporalAdjusters.lastDayOfMonth());
  23. System.out.println(aim);
  24. //...

5)时长对象使用

  • java.time.Period 日期时长
  • java.time.Duration 时间时长
  1. //现在
  2. LocalDateTime now = LocalDateTime.now();
  3. System.out.println(now);
  4. //3个月时长
  5. Period months3 = Period.ofMonths(3);
  6. //3个月后
  7. LocalDateTime aim = now.plus(months3);
  8. System.out.println(aim);
  9. //2个小时时长
  10. Duration hours2 = Duration.ofHours(2);
  11. //2个小时后
  12. aim = now.plus(hours2);
  13. System.out.println(aim);

6)兼容旧 API

当系统升级 JDK8 ,很容易将遗留的 Date 过渡为新 API 类使用,使用 java.time.Instant。

  1. //Date >> LocalDate(Time)
  2. Date date = new Date();
  3. LocalDateTime localDateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
  4. System.out.println(localDateTime);
  5. //Calendar >> LocalDate
  6. Calendar calen = Calendar.getInstance();
  7. localDateTime = LocalDateTime.ofInstant(calen.toInstant(), ZoneId.systemDefault());
  8. System.out.println(localDateTime);

两种业务场景实现

1)定投业务

这个支付宝和各大银行都有的业务,即得到客户允许,按照客户选择的方式(按月或按周)投入本金至余额宝或其他理财产品,一般投入动作工作日进行,节假日顺延。

  1. /**
  2. * 模拟客户输入
  3. */
  4. //假设客户选择 按月,每月5号定投
  5. Integer type = 1, day = 5;
  6. //前置校验 日期号检查 略
  7. ActionType aimType = ActionType.ordinalContain(type);
  8. if(aimType==null) {
  9. throw new RuntimeException("定投类型输入不合法!");
  10. }
  11. /**
  12. * 业务处理
  13. */
  14. LocalDate now = LocalDate.now(); //当天日期
  15. LocalDate aimDate = LocalDate.now(); //结果日期
  16. switch (aimType) {
  17. case T_WEEK: //按周,假设每周5
  18. aimDate = now.with(ChronoField.DAY_OF_WEEK, day); //本周5
  19. if(!now.isBefore(aimDate)) { //如果当天>=本周5,取下周5
  20. aimDate = aimDate.plusWeeks(1);
  21. }
  22. break;
  23. case T_MONTH: //按月,假设每月5号
  24. aimDate = now.withDayOfMonth(day);//本月5号
  25. if(!now.isBefore(aimDate)) { //如果当天>=本月5号,取下月5号
  26. aimDate = aimDate.plusMonths(1);
  27. }
  28. break;
  29. default:
  30. break;
  31. }
  32. /**
  33. * 输出结果
  34. */
  35. DateTimeFormatter formatter = DateTimeFormatter.BASIC_ISO_DATE;
  36. String aimDateStr = aimDate.format(formatter);
  37. //节假日顺延需要有工作日历蓝本,此处略
  38. logger.info("下次投入日期:{}", aimDateStr);

2)账单业务

这个也很常见,比如每月几号结算上个月/季度的账单(分红,缴费啥的)。关键一点和定投场景不同的是,总是下个月开始动作,而定投会根据当前日期比对。

  1. /**
  2. * 模拟客户输入
  3. */
  4. //假设客户选择 按月,每月5号结算上个月的账单
  5. Integer type = 1, day = 5;
  6. //前置校验 日期号检查 略
  7. ActionType aimType = ActionType.ordinalContain(type);
  8. if(aimType==null) {
  9. throw new RuntimeException("结算类型输入不合法!");
  10. }
  11. /**
  12. * 业务处理
  13. */
  14. LocalDate now = LocalDate.now(); //当天日期 6.5
  15. //结算日期 下个月5号(7.5) or 三个月后的的5号(9.5)
  16. LocalDate aimDate = now.withDayOfMonth(day).plusMonths(aimType.monthPeriod);
  17. //账单起始日 6.1
  18. //账单结束日 6.30 or 8.31
  19. LocalDate startDate = now.with(TemporalAdjusters.firstDayOfMonth());
  20. LocalDate endDate = aimDate.minusMonths(1).with(TemporalAdjusters.lastDayOfMonth());
  21. /**
  22. * 输出结果
  23. */
  24. DateTimeFormatter formatter = DateTimeFormatter.BASIC_ISO_DATE;
  25. logger.info("下次结算日期:{},账单周期段:[{}, {}]", aimDate.format(formatter),
  26. startDate.format(formatter), endDate.format(formatter));

Java8 日期 API 业务使用的更多相关文章

  1. java8 异步api、循环、日期

    java8 异步api.循环.日期 转载请注明出处:https://www.cnblogs.com/funnyzpc/p/10801470.html 异步api 对于多任务耗时的业务场景,一般我们会用 ...

  2. JAVA8学习——新的时间日期API&Java8总结

    JAVA8-时间日期API java8之前用过的时间日期类. Date Calendar SimpleDateFormat 有很多致命的问题. 1.没有时区概念 2.计算麻烦,实现困难 3.类是可变的 ...

  3. (转载)Java8新的日期API LocalDate, LocalTime

    前言 由于Java Date的各种问题,Java8推出了新的日期API,很受一拨人的追捧. 为什么我们需要新的Java日期/时间API? 在开始研究Java 8日期/时间API之前,让我们先来看一下为 ...

  4. java8新特性——时间日期API

    传统的时间 API 存在线程安全的问题,在多线程开发中必须要上锁,所以 java8 现在为我们提供了一套全新的时间日期 API ,今天进来学习一下java8 的时间日期 API. 一.使用 Local ...

  5. [转] Java8 日期/时间(Date Time)API指南

    [From] http://www.importnew.com/14140.html Java 8日期/时间( Date/Time)API是开发人员最受追捧的变化之一,Java从一开始就没有对日期时间 ...

  6. Java8新特性(三)——Optional类、接口方法与新时间日期API

    一.Optional容器类 这是一个可以为null的容器对象.如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象. 查看结构图可以看到有如下常用方法: of(T)—— ...

  7. 为什么不建议使用Date,而是使用Java8新的时间和日期API?

    Java 8:新的时间和日期API 在Java 8之前,所有关于时间和日期的API都存在各种使用方面的缺陷,因此建议使用新的时间和日期API,分别从旧的时间和日期的API的缺点以及解决方法.Java ...

  8. [转]JavaSE 8—新的时间和日期API

    为什么我们需要一个新的时间日期API Java开发中一直存在一个问题,JDK提供的时间日期API一直对开发者没有提供良好的支持. 比如,已有的的类(如java.util.Date和SimpleDate ...

  9. Java8 日期/时间(Date Time)使用简介

    特别说明: LocalDateTime 为日期时间的计算提供了很大的方便, 在构造对象/运算/toString等方便都非常便利. 3个常用的类: java.time.LocalDateTime; ja ...

随机推荐

  1. ios下,微信小程序scrollview组件中的fixed元素抖得和帕金森病人一样

    问题现象 这个问题是最近在优化小程序代码时发现的. 在ios环境下,微信小程序的scrollview组件包裹着一个position:fixed的view. 当在scrollview组件上滑动时,这个v ...

  2. 关于报错:The Microsoft.ACE. Oledb.12.0 provider was not registered on the local computer

    错误描述:The Microsoft.ACE. Oledb.12.0 provider was not registered on the local computer 最近在Web项目中做一个自动生 ...

  3. 转载 vue-awesome-swiper - 基于vue实现h5滑动翻页效果

    说到h5的翻页,很定第一时间想到的是swiper.但是我当时想到的却是,vue里边怎么用swiper?! 中国有句古话叫:天塌下来有个高的顶着. 在前端圈里,总有前仆后继的仁人志士相继挥洒着热汗(这里 ...

  4. kylin Retrieving hive dependency...

    由于公司环境配置hive默认连接hiveserver2 ,不管hive cli 还是beeline cli都默认使用beeline cli,连接hive需要输入账号密码; 启动kylin 时会Retr ...

  5. [Abp vNext 源码分析] - 7. 权限与验证

    一.简要说明 在上篇文章里面,我们在 ApplicationService 当中看到了权限检测代码,通过注入 IAuthorizationService 就可以实现权限检测.不过跳转到源码才发现,这个 ...

  6. java常见面试题目(三)

    1.jsp的内置对象. JSP中一共预先定义了9个这样的对象,分别为:request.response.session.application.out.pagecontext.config.page. ...

  7. 用JavaScript带你体验V8引擎解析标识符过程

    上一篇讲了字符串的解析过程,这一篇来讲讲标识符(IDENTIFIER)的解析. 先上知识点,标识符的扫描分为快解析和慢解析,一旦出现Ascii值大于128的字符或者转义字符,会进入慢解析,略微影响性能 ...

  8. ext container的使用的场景

    container 是 panel 简化,他称之为容器,而panel则是面板. 如果不需要类似Ext.panel.Panel,Ext.window.Window和Ext.tab.Panel 等功能,则 ...

  9. 小X的逆袭

    [问题描述]毕业于普通本科的小x 一直自称是资深屌丝.谁又能想到,如此不起眼的小x 在历经重重面试环节后,竟然如愿以偿加入了心仪已久的腾讯公司!正所谓野百合也有春天,屌丝也有逆袭的那一天!一段时间以后 ...

  10. NodeJs小试牛刀--聊天室搭建

    最近研究聊天室功能,准备用nodejs实现.下面是自己的尝试!! nodejs的安装这里就不详细赘述了. 程序创建 引入required模块 var express = require('expres ...