JAVA8实战 - 日期API(上)

简介: JAVA8实战 - 日期API(上)

前言



这一节我们来讲讲JAVA8的日期类,源代码的作者其实就是Joda-Time,所以可以看到很多代码的API和Joda类比较像。日期类一直是一个比较难用的东西,但是JAVA8给日期类提供了一套新的API让日期类更加好用。

本文代码较多,建议亲自运行代码理解。


思维导图:



地址:www.mubucm.com/doc/ck5ZCrg…


网络异常,图片无法展示
|


内容概述:



  1. 关于JDK8日期的三个核心类:LocalDate、LocalTime、LocalDateTime的相关介绍
  2. 机器时间和日期格式Instant等关于细粒度的时间操作介绍
  3. TemporalAdjusters 用于更加复杂的日期计算,比如计算下一个工作日的时候这个类提供了一些实现
  4. DateTimeFormatter 格式化器,非常的灵活多变,属于SimpleDateFormat的替代品。
  5. 日期API的一些个人工具封装举例,以及在使用JDK8的时候一些个人的踩坑

最后希望通过本文能帮你摆脱new Date()


什么是ISO-8601?



日期离不开ISO-8601,下面对ISO-8601简单描述一下,参考自百度百科:

  1. ISO-8601: 国际标准化组织制定的日期和时间的表示方法,全称为《数据存储和交换形式·信息交换·日期和时间的表示方法》,简称为ISO-8601。
  2. 日的表示:小时、分和秒都用2位数表示,对UTC时间最后加一个大写字母Z,其他时区用实际时间加时差表示。如UTC时间下午2点30分5秒表示为14:30:05Z或143005Z,当时的北京时间表示为22:30:05+08:00或223005+0800,也可以简化成223005+08。
  3. 日期和时间的组合表示:合并表示时,要在时间前面加一大写字母T,如要表示北京时间2004年5月3日下午5点30分8秒,可以写成2004-05-03T17:30:08+08:00或20040503T173008+08。

LocalDate、LocalTime、LocalDateTime


JDK8把时间拆分成了三个大部分,一个是时间,代表了年月日的信息,一个是日期,代表了时分秒的部分,最后是这两个对象总和具体的时间。


LocalDate


LocalDate:类表示一个具体的日期,但不包含具体时间,也不包含时区信息。可以通过LocalDate的静态方法of()创建一个实例,LocalDate也包含一些方法用来获取年份,月份,天,星期几等,下面是LocalDate的常见使用方式:


@Test
    public void localDateTest() throws Exception {
        // 创建一个LocalDate:
        LocalDate of = LocalDate.of(2021, 8, 9);
        // 获取当前时间
        LocalDate now = LocalDate.now();
        // 格式化
        LocalDate parse1 = LocalDate.parse("2021-05-11");
        // 指定日期格式化
        LocalDate parse2 = LocalDate.parse("2021-05-11", DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        // 下面的代码会出现格式化异常
        // java.time.format.DateTimeParseException: Text '2021-05-11 11:53:53' could not be parsed, unparsed text found at index 10
//        LocalDate parse3 = LocalDate.parse("2021-05-11 11:53:53", DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        // 正确的格式化方法
        LocalDate parse3 = LocalDate.parse("2021-05-11 11:53:53", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        // 当前时间
        System.out.println("now() => "+ now);
        // 获取月份
        int dayOfMonth = parse1.getDayOfMonth();
        System.out.println("dayOfMonth => " + dayOfMonth);
        // 获取年份
        int dayOfYear = parse1.getDayOfYear();
        System.out.println("getDayOfYear => " + dayOfYear);
        // 获取那一周,注意这里获取的是对象
        DayOfWeek dayOfWeek = parse1.getDayOfWeek();
        System.out.println("getDayOfWeek => " + dayOfWeek);
        // 获取月份数据
        int monthValue = parse3.getMonthValue();
        System.out.println("getMonthValue => " + monthValue);
        // 获取年份
        int year = parse3.getYear();
        System.out.println("getYear => " + year);
        // getChronology 获取的是当前时间的排序,这里输出结果是 ISO
        System.out.println("getChronology => " + parse3.getChronology());
        System.out.println("getEra => " + parse3.getEra());
        // 使用timeField获取值:TemporalField 是一个接口,定义了如何访问 TemporalField 的值,ChronnoField 实现了这个接口
        /*
        LocalDate 支持的格式如下:
         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);
        * */
        // Unsupported field: HourOfDay
//        System.out.println("ChronoField.HOUR_OF_DAY => " + parse1.get(ChronoField.HOUR_OF_DAY));
        // Unsupported field: MinuteOfHour
//        System.out.println("ChronoField.MINUTE_OF_HOUR => " + parse1.get(ChronoField.MINUTE_OF_HOUR));
        // Unsupported field: MinuteOfHour
//        System.out.println("ChronoField.SECOND_OF_MINUTE => " + parse1.get(ChronoField.SECOND_OF_MINUTE));
        System.out.println("ChronoField.YEAR => " + parse1.get(ChronoField.YEAR));
        // Unsupported field: MinuteOfHour
//        System.out.println("ChronoField.INSTANT_SECONDS => " + parse1.get(ChronoField.INSTANT_SECONDS));
    }/*运行结果:
    now() => 2021-08-08
    dayOfMonth => 11
    getDayOfYear => 131
    getDayOfWeek => TUESDAY
    getMonthValue => 5
    getYear => 2021
    getChronology => ISO
    getEra => CE
    ChronoField.YEAR => 2021
    */
复制代码


TemporalField 是一个接口,定义了如何访问 TemporalField 的值,ChronnoField 实现了这个接口


LocalTime


LocalTime:和LocalDate类似,区别在于包含具体时间,同时拥有更多操作具体时间时间的方法,下面是对应的方法以及测试:


@Test
    public void localTimeTest() throws Exception {
        LocalTime now = LocalTime.now();
        System.out.println("LocalTime.now() => "+  now);
        System.out.println("getHour => "+ now.getHour());
        System.out.println("getMinute => "+ now.getMinute());
        System.out.println("getNano => "+ now.getNano());
        System.out.println("getSecond => "+ now.getSecond());
        LocalTime systemDefault = LocalTime.now(Clock.systemDefaultZone());
        // ZoneName => java.time.format.ZoneName.zidMap 从这个map里面进行获取
        LocalTime japan = LocalTime.now(Clock.system(ZoneId.of("Japan")));
        // 或者直接更换时区
        LocalTime japan2 = LocalTime.now(ZoneId.of("Japan"));
        // 格式化时间
        LocalTime localTime = LocalTime.of(15, 22);
        // from 从另一个时间进行转化,只要他们接口兼容
        LocalTime from = LocalTime.from(LocalDateTime.now());
        // 范湖纳秒值
        LocalTime localTime1 = LocalTime.ofNanoOfDay(1);
        LocalTime localTime2 = LocalTime.ofSecondOfDay(1);
        // 越界异常 Invalid value for MinuteOfHour (valid values 0 - 59): 77
//        LocalTime.of(15, 77);
        // 获取本地的默认时间
        System.out.println("LocalTime.now(Clock.systemDefaultZone()) => "+ systemDefault);
        // 获取日本时区的时间
        System.out.println("LocalTime.now(Clock.system(ZoneId.of(\"Japan\"))) => "+ japan);
        System.out.println("LocalTime.now(ZoneId.of(\"Japan\")) => "+ japan2);
        System.out.println("LocalTime.of(15, 22) => "+ localTime);
        System.out.println("LocalTime.from(LocalDateTime.now()) => "+ from);
        System.out.println("LocalTime.ofNanoOfDay(1) => "+ localTime1);
        System.out.println("LocalTime.ofSecondOfDay(1) => "+ localTime2);
    }/*运行结果:
    LocalTime.now() => 12:58:13.553
    getHour => 12
    getMinute => 58
    getNano => 553000000
    getSecond => 13
    LocalTime.now(Clock.systemDefaultZone()) => 12:58:13.553
    LocalTime.now(Clock.system(ZoneId.of("Japan"))) => 13:58:13.553
    LocalTime.now(ZoneId.of("Japan")) => 13:58:13.553
    LocalTime.of(15, 22) => 15:22
    LocalTime.from(LocalDateTime.now()) => 12:58:13.553
    LocalTime.ofNanoOfDay(1) => 00:00:00.000000001
    LocalTime.ofSecondOfDay(1) => 00:00:01
    */
复制代码


LocalDateTime


LocalDateTimeLocalDateTime类是LocalDateLocalTime结合体,可以通过of()方法直接创建,也可以调用LocalDateatTime()方法或LocalTimeatDate()方法将LocalDateLocalTime合并成一个LocalDateTime,下面是一些简单的方法测试,由于篇幅有限,后续会结合这些内容编写一个工具类的代码。


@Test
    public void localDateTimeTest() throws Exception {
        //Text '2021-11-11 15:30:11' could not be parsed at index 10
//        LocalDateTime parse = LocalDateTime.parse("2021-11-11 15:30:11");
        // 默认使用的是ISO的时间格式
        LocalDateTime parse1 = LocalDateTime.parse("2011-12-03T10:15:30");
        // 如果要自己的格式,需要手动格式化
        LocalDateTime parse = LocalDateTime.parse("2021-11-11 15:30:11", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        System.out.println("LocalDateTime.parse(....) => "+ parse1);
        System.out.println("LocalDateTime.parse(....) => "+ parse);
        LocalDateTime of = LocalDateTime.of(LocalDate.now(), LocalTime.now());
        LocalDateTime japan = LocalDateTime.now(ZoneId.of("Japan"));
        System.out.println("LocalDateTime.of(LocalDate.now(), LocalTime.now()) => "+ of);
        System.out.println("LocalDateTime.now(ZoneId.of(\"Japan\")) => "+ japan);
    }/*运行结果:
    LocalDateTime.parse(....) => 2011-12-03T10:15:30
    LocalDateTime.parse(....) => 2021-11-11T15:30:11
    LocalDateTime.of(LocalDate.now(), LocalTime.now()) => 2021-08-08T13:22:59.697
    LocalDateTime.now(ZoneId.of("Japan")) => 2021-08-08T14:22:59.697
    */
复制代码


细粒度机器时间操作



JDK8还对机器的时间进行了分类,比如像下面这样


Instant


Instant用于表示一个时间戳,它与我们常使用的System.currentTimeMillis()有些类似,不过Instant可以精确到纳秒(Nano-Second)


注意: 内部使用了两个常量,seconds表示从1970-01-01 00:00:00开始到现在的秒数,nanos表示纳秒部分(nanos的值不会超过999,999,999


下面是一些具体的测试用例:


@Test
    public void instantTest() throws Exception {
        Instant now = Instant.now();
        // Unable to obtain Instant from TemporalAccessor: 2021-08-08T13:37:34.403 of type java.time.LocalDateTime
//        Instant from = Instant.from(LocalDateTime.now());
        Instant instant = Instant.ofEpochSecond(3, 0);
        Instant instant1 = Instant.ofEpochSecond(5, 1_000_000_000);
        System.out.println("Instant.now() => "+ now);
//        System.out.println("Instant.from(LocalDateTime.now()) => "+ from);
        System.out.println("Instant.ofEpochSecond => "+ instant);
        System.out.println("Instant.ofEpochSecond => "+ instant1);
        System.out.println("Instant.get(ChronoField.NANO_OF_SECOND) => "+ now.get(ChronoField.NANO_OF_SECOND));
    }/*运行结果:
    Instant.now() => 2021-08-08T05:42:42.465Z
    Instant.ofEpochSecond => 1970-01-01T00:00:03Z
    Instant.ofEpochSecond => 1970-01-01T00:00:06Z
    Instant.get(ChronoField.NANO_OF_SECOND) => 465000000
    */
复制代码


Duration


Duration的内部实现与Instant类似,也是包含两部分:seconds表示秒,nanos表示纳秒。两者的区别是Instant用于表示一个时间戳(或者说是一个时间点),而Duration表示一个时间段,比如想要获取两个时间的差值:


@Test
    public void durationTest() throws Exception {
        // Text '201-08-08T10:15:30' could not be parsed at index 0
        Duration between = Duration.between(LocalDateTime.parse("2011-12-03T10:15:30"), LocalDateTime.parse("2021-08-08T10:15:30"));
        System.out.println("Duration.between(LocalDateTime.parse(\"2011-12-03T10:15:30\"), LocalDateTime.parse(\"2021-08-08T10:15:30\")) => "+ between);
        Duration duration = Duration.ofDays(7);
        System.out.println("Duration.ofDays(7) => "+ duration);
    }
复制代码


Period


Period在概念上和Duration类似,区别在于Period是以年月日来衡量一个时间段(比如2年3个月6天),下面是对应单元测试以及相关的代码:


@Test
    public void periodTest() throws Exception {
        Period between = Period.between(LocalDate.parse("2011-12-03"), LocalDate.parse("2021-08-08"));
        Period period = Period.ofWeeks(53);
        Period period1 = Period.ofWeeks(22);
        System.out.println("Period.between(LocalDate.parse(\"2011-12-03\"), LocalDate.parse(\"2021-08-08\")) => "+ between);
        System.out.println("Period.ofWeeks(53) => "+ period);
        System.out.println("Period.ofWeeks(53) getDays => "+ period.getDays());
        // 注意,这里如果没有对应值,会出现 0
        System.out.println("Period.ofWeeks(53) getMonths => "+ period.getMonths());
        System.out.println("Period.ofWeeks(22) getMonths => "+ period1.getMonths());
        System.out.println("Period.ofWeeks(22) getYears => "+ period1.getYears());
    }/*运行结果:
    Period.between(LocalDate.parse("2011-12-03"), LocalDate.parse("2021-08-08")) => P9Y8M5D
    Period.ofWeeks(53) => P371D
    Period.ofWeeks(53) getDays => 371
    Period.ofWeeks(53) getMonths => 0
    Period.ofWeeks(22) getMonths => 0
    Period.ofWeeks(22) getYears => 0
    */
复制代码


TemporalAdjusters 复杂日期操作


这个类可以对于时间进行各种更加复杂的操作,比如下一个工作日,本月的最后一天,这时候我们可以借助with这个方法进行获取:


@Test
public void testTemporalAdjusters(){
    LocalDate of = LocalDate.of(2021, 8, 1);
    // 获取当前年份的第一天
    LocalDate with = of.with(TemporalAdjusters.firstDayOfYear());
    System.out.println(" TemporalAdjusters.firstDayOfYear => "+ with);
    // 获取指定日期的下一个周六
    LocalDate with1 = of.with(TemporalAdjusters.next(DayOfWeek.SATURDAY));
    System.out.println(" TemporalAdjusters.next(DayOfWeek.SATURDAY) => "+ with1);
    // 获取当月的最后一天
    LocalDate with2 = of.with(TemporalAdjusters.lastDayOfMonth());
    System.out.println("TemporalAdjusters.lastDayOfMonth() => "+ with2);
}
复制代码


下面从网络找到一份表,对应所有的方法作用


方法名 描述
dayOfWeekInMonth 返回同一个月中每周的第几天
firstDayOfMonth 返回当月的第一天
firstDayOfNextMonth 返回下月的第一天
firstDayOfNextYear 返回下一年的第一天
firstDayOfYear 返回本年的第一天
firstInMonth 返回同一个月中第一个星期几
lastDayOfMonth 返回当月的最后一天
lastDayOfNextMonth 返回下月的最后一天
lastDayOfNextYear 返回下一年的最后一天
lastDayOfYear 返回本年的最后一天
lastInMonth 返回同一个月中最后一个星期几
next / previous 返回后一个/前一个给定的星期几
nextOrSame / previousOrSame 返回后一个/前一个给定的星期几,如果这个值满足条件,直接返回


DateTimeFormatter 格式化器


这个类可以认为是用来替代SimpleDateFormat这个类,他拥有更加强大的定制化操作,同时他是线程安全的类,不用担心多线程访问会出现问题。

下面是根据DateTimeFormatter 构建一个本土化的格式化器,代码也十分的简单易懂:


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);
}
复制代码


时区信息


时区信息一般用的比较少,在做和国际化相关的操作时候有可能会用到,比如最近个人从苹果买了一个东西,虽然我下单是在6号,但是电话说订单时间却是5号下单的,这里个人认为苹果的确切下单时间是按照美国时间算的。


JDK8日期类关于时区的强相关类(注意是JDK8才出现的类,不要误认为是对之前类的兼容),在之前的单元测试其实已经用到了相关时区的方法,在JDK8中使用了 ZoneId这个类来表示,但是我们有时候不知道怎么获取地区,可以参考下面的内容:


网络异常,图片无法展示
|


// ZoneName => java.time.format.ZoneName.zidMap 从这个map里面进行获取
LocalTime japan = LocalTime.now(Clock.system(ZoneId.of("Japan")));
复制代码


相关文章
|
1月前
|
缓存 监控 前端开发
顺企网 API 开发实战:搜索 / 详情接口从 0 到 1 落地(附 Elasticsearch 优化 + 错误速查)
企业API开发常陷参数、缓存、错误处理三大坑?本指南拆解顺企网双接口全流程,涵盖搜索优化、签名验证、限流应对,附可复用代码与错误速查表,助你2小时高效搞定开发,提升响应速度与稳定性。
|
1月前
|
缓存 自然语言处理 API
阿里巴巴国际站关键字搜索 API 实战:3 步搞定多语言适配 + 限流破局,询盘量提升 40%
跨境电商API开发常陷合规、多语言、限流等坑。本文详解从国际合规(GDPR/CCPA)到参数优化、数据结构化及区域化搜索的全链路方案,附Python代码模板与缓存重试架构,助力提升调用成功率至99%+,精准询盘增长42%。
|
1月前
|
存储 人工智能 算法
从零掌握贪心算法Java版:LeetCode 10题实战解析(上)
在算法世界里,有一种思想如同生活中的"见好就收"——每次做出当前看来最优的选择,寄希望于通过局部最优达成全局最优。这种思想就是贪心算法,它以其简洁高效的特点,成为解决最优问题的利器。今天我们就来系统学习贪心算法的核心思想,并通过10道LeetCode经典题目实战演练,带你掌握这种"步步为营"的解题思维。
|
1月前
|
安全 Java 开发者
告别NullPointerException:Java Optional实战指南
告别NullPointerException:Java Optional实战指南
243 119
|
1月前
|
开发者 API 机器学习/深度学习
淘宝 / 1688 / 义乌购图搜 API 实战指南:接口调用与商业场景应用
本文详解淘宝、1688、义乌购三大平台图片搜索接口的核心特点、调用流程与实战代码。涵盖跨平台对比、参数配置、响应解析及避坑指南,支持URL/Base64上传,返回商品ID、价格、销量等关键信息,助力开发者快速实现商品识别与比价功能。
淘宝 / 1688 / 义乌购图搜 API 实战指南:接口调用与商业场景应用
|
1月前
|
Cloud Native 算法 API
Python API接口实战指南:从入门到精通
🌟蒋星熠Jaxonic,技术宇宙的星际旅人。深耕API开发,以Python为舟,探索RESTful、GraphQL等接口奥秘。擅长requests、aiohttp实战,专注性能优化与架构设计,用代码连接万物,谱写极客诗篇。
Python API接口实战指南:从入门到精通
|
2月前
|
人工智能 运维 监控
阿里云 API 聚合实战:破解接口碎片化难题,3 类场景方案让业务响应提速 60%
API聚合破解接口碎片化困局,助力开发者降本增效。通过统一中间层整合微服务、第三方接口与AI模型,实现调用次数减少60%、响应提速70%。阿里云实测:APISIX+函数计算+ARMS监控组合,支撑百万级并发,故障定位效率提升90%。
271 0
|
2月前
|
人工智能 Java API
Java AI智能体实战:使用LangChain4j构建能使用工具的AI助手
随着AI技术的发展,AI智能体(Agent)能够通过使用工具来执行复杂任务,从而大幅扩展其能力边界。本文介绍如何在Java中使用LangChain4j框架构建一个能够使用外部工具的AI智能体。我们将通过一个具体示例——一个能获取天气信息和执行数学计算的AI助手,详细讲解如何定义工具、创建智能体并处理执行流程。本文包含完整的代码示例和架构说明,帮助Java开发者快速上手AI智能体的开发。
1019 8
|
2月前
|
JSON API 调度
Midjourney 技术拆解与阿里云开发者实战指南:从扩散模型到 API 批量生成
Midjourney深度解析:基于优化Stable Diffusion,实现文本到图像高效生成。涵盖技术架构、扩散模型原理、API调用、批量生成系统及阿里云生态协同,助力开发者快速落地AIGC图像创作。
503 0
|
2月前
|
数据采集 缓存 API
小红书笔记详情 API 实战指南:从开发对接、场景落地到收益挖掘(附避坑技巧)
本文详解小红书笔记详情API的开发对接、实战场景与收益模式,涵盖注册避坑、签名生成、数据解析全流程,并分享品牌营销、内容创作、SAAS工具等落地应用,助力开发者高效掘金“种草经济”。
小红书笔记详情 API 实战指南:从开发对接、场景落地到收益挖掘(附避坑技巧)