最近在作帐单结算业务,须要根据客户选择的结算方式来推算下一次结算日期以及该次结算日期段。java
推算日期这样的业务直男君之前就写过,只不过使用的是熟悉的 java.util.date 和 java.util.Calendar。如今公司使用的 JDK8,因此本次就决定新的日期 API 啦,顺便结合业务实现对比回顾下。sql
但愿能帮到各位道友,啰嗦结束,开始!安全
熟悉的日期操做三基友:java.util 包下的 Date 和 Calendar 加上 java.text.SimpleDateFormat。架构
1)获得当前日期对象app
//获取日期对象(包含时间) Date date = new Date(); Date date1 = new Date(System.currentTimeMillis());
2)日历操做ui
//获取日历对象 Calendar cald = Calendar.getInstance(); cald.setTime(date); //目标日期对象对应的日历 cald.add(Calendar.MONTH, 1); //日历选取(下个月本号) Date date2 = cald.getTime(); //目标日历对应的日期对象
3)日期格式化线程
//日期格式化 DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String nowDateStr = df.format(now); //日期 >> 格式串 System.out.println(nowDateStr); //2019-05-28 20:11:11 String dateStr = "2019-05-28 20:33:33"; Date aimDate = df.parse(dateStr); //格式串 >> 日期 System.out.println(aimDate); System.out.println(aimDate.after(now)); //日期比较 System.out.println(aimDate.compareTo(now));
1)更清晰合理的语义和架构设计
再也不使用 Date(util 和 sql 包都有) 表示日期、时间、日期时间,而是 LocalDate、LocalTime、LocalDateTime。orm
之前日期格式化的类是在 java.text 包下,如今所有在 java.time 下,且语义分工明确。对象
2)线程安全的设计
SimpleDataFormat 类一直为人诟病的线程安全问题:
//SimpleDataFormat 源码部分↓ private StringBuffer format(Date date, StringBuffer toAppendTo, FieldDelegate delegate) { // Convert input date to time field list calendar.setTime(date); boolean useDateFormatSymbols = useDateFormatSymbols(); ......
PS:直男君倒以为没什么,避免单例或者线程共享的场景就好了。
新的 API 类都是不可变类,避免了线程安全隐患。
public final class LocalDateTime public final class Instant ......
PS:什么叫不可变类?
String 类就是不可变类,因此有这样的说法:每次使用 + 链接字符串都会生成新的 String 对象,对于重复拼接的场景应该使用 StringBuilder/StringBuffer。
参考 String 类的设计,可总结不可变类的关键特色:类名 final,保证类不会被拓展;全部成员变量 final 且不提供任何能够修改实例状态的方法。
3)更方便的日期操做
基础类好比 LocalDateTime 就已经合理的提供了足够多的场景方法,日期调整、格式化、比较等等。
4)时区、日历系统支持(体会还不深)
5)其余
1)获取日期对象
提供了各类静态方法构造日期时间对象,以 LocalDate 为例:
LocalDate date = LocalDate.now(); System.out.println(date); date = LocalDate.of(2019, 5, 30);//LocalDate.of(2019, Month.MAY, 30) System.out.println(date); date = LocalDate.ofYearDay(2019, 300); System.out.println(date); //其余
2)日期时间比较
LocalDateTime now = LocalDateTime.now(); logger.info("now: {}", now); LocalDate date = LocalDate.of(2017, 7, 27); LocalTime time = LocalTime.of(17, 11); LocalDateTime aim = LocalDateTime.of(date, time); logger.info("aim: {}", aim); System.out.println(now.compareTo(aim)); System.out.println(now.isBefore(aim)); System.out.println(now.isEqual(aim)); System.out.println(now.isAfter(aim));
3)格式化
//DateTimeFormatter 提供了不少内置格式,但好像都不是咱们想要的 DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME; LocalDateTime now = LocalDateTime.now(); String str = now.format(formatter); System.out.println(str); //咱们想要的格式 formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); System.out.println(now.format(formatter)); //日期字符串转日期对象 String dateStr = "2019-05-30"; LocalDate aim = LocalDate.parse(dateStr); System.out.println(aim); //日期时间串一块儿转对象 String dateTimeStr = "2019-05-30 10:30:00"; LocalDateTime aim2 = LocalDateTime.parse(dateTimeStr, formatter); System.out.println(aim2);
4)日历操做(日期调整)
这个是业务实现上最有用的。
再也不像之前使用 Calendar,而是基础类搭配 TemporalAdjusters 很好用!
//今天 LocalDateTime now = LocalDateTime.now(); System.out.println(now); //分别获取今天日历值 也可使用 ChronoField 的常量指定获取 logger.info("今天是,本月{}号, 周{}, 今年的第{}天, 今年的第{}月", now.getDayOfMonth(), now.getDayOfWeek().getValue(), now.getDayOfYear(), now.getMonthValue()); //明年今天 LocalDateTime aim = now.plusYears(1); System.out.println(aim); //下月今天 aim = now.plusMonths(1); System.out.println(aim); //下周今天 aim = now.plusWeeks(1); System.out.println(aim); //本月一号 aim = now.with(TemporalAdjusters.firstDayOfMonth()); System.out.println(aim); //本月最后一天 aim = now.with(TemporalAdjusters.lastDayOfMonth()); System.out.println(aim); //...
5)时长对象使用
//如今 LocalDateTime now = LocalDateTime.now(); System.out.println(now); //3个月时长 Period months3 = Period.ofMonths(3); //3个月后 LocalDateTime aim = now.plus(months3); System.out.println(aim); //2个小时时长 Duration hours2 = Duration.ofHours(2); //2个小时后 aim = now.plus(hours2); System.out.println(aim);
6)兼容旧 API
当系统升级 JDK8 ,很容易将遗留的 Date 过渡为新 API 类使用,使用 java.time.Instant。
//Date >> LocalDate(Time) Date date = new Date(); LocalDateTime localDateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()); System.out.println(localDateTime); //Calendar >> LocalDate Calendar calen = Calendar.getInstance(); localDateTime = LocalDateTime.ofInstant(calen.toInstant(), ZoneId.systemDefault()); System.out.println(localDateTime);
1)定投业务
这个支付宝和各大银行都有的业务,即获得客户容许,按照客户选择的方式(按月或按周)投入本金至余额宝或其余理财产品,通常投入动做工做日进行,节假日顺延。
/** * 模拟客户输入 */ //假设客户选择 按月,每个月5号定投 Integer type = 1, day = 5; //前置校验 日期号检查 略 ActionType aimType = ActionType.ordinalContain(type); if(aimType==null) { throw new RuntimeException("定投类型输入不合法!"); } /** * 业务处理 */ LocalDate now = LocalDate.now(); //当天日期 LocalDate aimDate = LocalDate.now(); //结果日期 switch (aimType) { case T_WEEK: //按周,假设每周5 aimDate = now.with(ChronoField.DAY_OF_WEEK, day); //本周5 if(!now.isBefore(aimDate)) { //若是当天>=本周5,取下周5 aimDate = aimDate.plusWeeks(1); } break; case T_MONTH: //按月,假设每个月5号 aimDate = now.withDayOfMonth(day);//本月5号 if(!now.isBefore(aimDate)) { //若是当天>=本月5号,取下月5号 aimDate = aimDate.plusMonths(1); } break; default: break; } /** * 输出结果 */ DateTimeFormatter formatter = DateTimeFormatter.BASIC_ISO_DATE; String aimDateStr = aimDate.format(formatter); //节假日顺延须要有工做日历蓝本,此处略 logger.info("下次投入日期:{}", aimDateStr);
2)帐单业务
这个也很常见,好比每个月几号结算上个月/季度的帐单(分成,缴费啥的)。关键一点和定投场景不一样的是,老是下个月开始动做,而定投会根据当前日期比对。
/** * 模拟客户输入 */ //假设客户选择 按月,每个月5号结算上个月的帐单 Integer type = 1, day = 5; //前置校验 日期号检查 略 ActionType aimType = ActionType.ordinalContain(type); if(aimType==null) { throw new RuntimeException("结算类型输入不合法!"); } /** * 业务处理 */ LocalDate now = LocalDate.now(); //当天日期 6.5 //结算日期 下个月5号(7.5) or 三个月后的的5号(9.5) LocalDate aimDate = now.withDayOfMonth(day).plusMonths(aimType.monthPeriod); //帐单起始日 6.1 //帐单结束日 6.30 or 8.31 LocalDate startDate = now.with(TemporalAdjusters.firstDayOfMonth()); LocalDate endDate = aimDate.minusMonths(1).with(TemporalAdjusters.lastDayOfMonth()); /** * 输出结果 */ DateTimeFormatter formatter = DateTimeFormatter.BASIC_ISO_DATE; logger.info("下次结算日期:{},帐单周期段:[{}, {}]", aimDate.format(formatter), startDate.format(formatter), endDate.format(formatter));
最近忙于业务开发,陆陆续续总算总结完了,写点题外话。
小伙伴们应该发现了,直男君的文章都是按需发的,一来记录下本身碰到的有所真正实践的点,但愿职友碰到这些场景能够有所共鸣;二来培养梳理表达能力并但愿获得沟通成长。
因此,文章都是浅显直白的,没有高深的东西(职场实践向,直男君尚未大宗师那样的火候...)。
打完手工,欢迎踩点吐槽,收藏转发交流~