听说你还在用Date类表示时间?

LocalDateTime类

1、Date类与LocalDateTime类

之前我们《日期处理类Date类》这篇文章已经聊过了Date类,但是Date类却遭“嫌弃”了,这是为啥呢?我们先来看几个例子。

范例1:创建一个表示“此刻”的日期,打印出来:

package edu.blog.test07;

import java.util.Date;

public class DateTestDemo01 {
    public static void main(String[] args) {
        Date rightNow = new Date();
        System.out.println("当前时刻:" + rightNow);
        System.out.println("当年年份" + rightNow.getYear());
        System.out.println("当年月份:" + rightNow.getMonth());
    }
}

/*
结果:
当前时刻:Thu Apr 01 23:58:11 CST 2021
当年年份121
当年月份:3
*/

当前时间:2021年4月1号

第一行:这打印结果可读性太差了

第二行:月份?你给个121?

第三行:当前月份是4月,结果为3月?

再者,仔细看源码,其实会发现Date里很多方法现在都已经被弃用了,如图 1-1所示。

图 1-1

是吧?确实弃用了很多方法。


2、LocalDateTime类

2.1、LocalDateTime类概述

自从JDK 1.8开始,JDK中其实就增加了一系列表示日期和时间的新类,最典型的就是LocalDateTime类。直言不讳,这哥们的出现就是为了干掉JDK版本的Date老哥。

此外java.time包提供了新的日期和时间API,主要涉及的类型:

  • 本地日期和时间:
    • LocalDateTime(年、月、日、时、分、秒,等同于LocalDate+LocalTime)
    • LocalDate(年、月、日)
    • LocalTime(时、分、秒)
  • 带时区的日期和时间:ZoneDateTime
  • 时刻:Instant(获取秒数)
  • 时区:ZoneId,ZoneOffset
  • 时间间隔:Duration

以及一套新的用于取代SimpleDateFormat的格式化类型DateTimeFormatter

和旧的API相比,新API严格区分了时刻、本地日期、本地时间和带时区的日期时间,并且,对日期和时间进行运算更加方便。此外,新API修正了旧API不合理的常量设计:

  • Month的范围用1~12表示1月到12月;
  • Week的范围用1~7表示周一到周日。

2.2、LocalDateTime类的方法

2.2.1、创建LocalDateTime类
方法说明
static LocalDateTime now()获取默认时区的当前日期时间
static LocalDateTime now(Clock clock)从指定时钟获取当前日期时间
static LocalDateTime now(ZoneId zone)获取指定时区的当前日期时间
static LocalDateTime of(LocalDate date, LocalTime time)根据日期和时间对象获取LocalDateTime 实例
static LocalDateTime of(int year, Month month, int dayOfMonth, int hour, int minute, int second)根据指定的年、月、日、时、分、秒获取LocalDateTime 实例
2.2.2、获取年、月、日、时、分、秒等
方法说明
int getYear()获取年份
Month getMonth()使用月份枚举类获取月份
int getDayOfMonth()获取日期在该月是第几天
DayOfWeek getDayOfWeek()获取日期是星期几
int getDayOfYear()获取日期在该年是第几天
int getHour()获取小时
int getMinute()获取分钟
int getSecond()获取秒
int getNano()获取纳秒

下面我们来举一个小例子

范例2:小试牛刀

package edu.blog.test07;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;

public class DateTestDemo02 {
    public static void main(String[] args) {
        LocalDate localDate = LocalDate.now();
        System.out.println("当前时刻:" + localDate);
        System.out.println("当时年份:" + localDate.getYear());
        System.out.println("当前月份:" + localDate.getMonth());
        System.out.println("当前日:" + localDate.getDayOfMonth());
        System.out.println("========================================");
        LocalTime localTime = LocalTime.now();
        System.out.println("当前时刻:" + localTime);
        System.out.println("当前时:" + localTime.getHour());
        System.out.println("当前分:" + localTime.getMinute());
        System.out.println("当前秒:" + localTime.getSecond());
        System.out.println("========================================");
        LocalDateTime localDateTime= LocalDateTime.now();
        System.out.println("当前时刻:" + localDateTime);
        System.out.println("当时年份:" + localDateTime.getYear());
        System.out.println("当前月份:" + localDateTime.getMonth());
        System.out.println("当前日:" + localDateTime.getDayOfMonth());
        System.out.println("当前时:" + localDateTime.getHour());
        System.out.println("当前分:" + localDateTime.getMinute());
        System.out.println("当前秒:" + localDateTime.getSecond());
    }
}

/*
结果:
当前时刻:2021-04-02
当时年份:2021
当前月份:APRIL
当前日:2
========================================
当前时刻:13:47:25.568
当前时:13
当前分:47
当前秒:25
========================================
当前时刻:2021-04-02T13:47:25.568
当时年份:2021
当前月份:APRIL
当前日:2
当前时:13
当前分:47
当前秒:25
*/

我们再来举个小例子,通过of()方法创建时间(这里我以“2001年7月27日22时22分22秒”为例)

范例2:构造一个指定年、月、日的时间

package edu.blog.test07;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;

public class DateTestDemo03 {
    public static void main(String[] args) {
        LocalDate localDate = LocalDate.of(2001, 7, 27);
        System.out.println("年、月、日:" + localDate);
        System.out.println("=================================");
        LocalTime localTime = LocalTime.of(22, 22, 22);
        System.out.println("时、分、秒:" + localTime);
        System.out.println("=================================");
        LocalDateTime localDateTime01 = LocalDateTime.of(2001, Month.JULY, 27, 22, 22, 22);
        System.out.println("时间:" + localDateTime01);
        System.out.println("=================================");
        LocalDateTime localDateTime02 = LocalDateTime.parse("2001-07-27T22:22:22");
        System.out.println("时间:" + localDateTime02);
    }
}

/*
结果:
年、月、日:2001-07-27
=================================
时、分、秒:22:22:22
=================================
时间:2001-07-27T22:22:22
=================================
时间:2001-07-27T22:22:22
*/

由上两个程序可知,本地日期和时间通过now()获取得到的总是以当前默认时区返回的,和旧API不同,LocalDateTime、LocalDate、LocalTime都是默认严格按照ISO 8601规定的日期和时间格式进行打印的。

因为严格按照ISO 8601的格式,所以,将字符串转换为LocalDateTime应该传入标准格式的日期和时间字符串。

注意:ISO 8601的日期和时间分隔符是**“T”**,格式如下:

  • 日期:yyyy-MM-dd
  • 时间:HH:mm:ss
  • 时间(带毫秒):HH:mm:ss:SSS
  • 日期和时间:yyyy-MM-dd’T’HH:mm:ss
  • 日期和时间(带毫秒):yyyy-MM-dd’T’HH:mm:ss:SSS

2.3、调整日期和时间的with()方法

LocalDateTime类同时也提供了对日期和时间进行调整的with()方法,通用的with()方法允许我们做复杂的运算。

范例4:通用的with()方法

package edu.blog.test07;


import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.TemporalAdjusters;

public class DateTestDemo04 {
    public static void main(String[] args) {
        //本月第一天00:00时刻
        LocalDateTime firstDay = LocalDate.now().withDayOfMonth(1).atStartOfDay();
        System.out.println("本月第一天00:00时刻:"+firstDay);

        //本月最后1天
        LocalDate lastDay = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth());
        System.out.println("本月最后1天:"+lastDay);

        //下个月第一天
        LocalDate nextMonthFirstDay = LocalDate.now().with(TemporalAdjusters.firstDayOfNextMonth());
        System.out.println("下个月第一天:"+nextMonthFirstDay);

        //本月第1个周一
        LocalDate firstWeekday = LocalDate.now().with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY));
        System.out.println("本月第1个周一:"+firstWeekday);
    }
}

/*
结果:
本月第一天00:00时刻:2021-04-01T00:00
本月最后1天:2021-04-30
下个月第一天:2021-05-01
本月第1个周一:2021-04-05
*/

有些人可能对这个with()方法比较懵逼,无妨,LocalDateTime类同时也提供了对日期和时间进行加减的非常简单的链式调用。对日期和时间可以使用以下方法:

  • 调整年:withYear()
  • 调整月:withMonth()
  • 调整日:withDayOfMonth()
  • 调整时:withHour()
  • 调整分:withMinute()
  • 调整秒:withSecond()

注意:进行月份加减会自动调整日期(例如从2020-10-31减去1个月得到的结果是2020-9-30,原因很简单,那就是9月没有31日)。

范例5:调整日期和时间

package edu.blog.test07;

import java.time.LocalDateTime;
import java.util.Date;

public class DateTestDemo05 {
    public static void main(String[] args) {
        //设置时间
        LocalDateTime localDateTime01 = LocalDateTime.of(2001, 7, 27, 22, 22, 22);
        System.out.println(localDateTime01);

        //日期变为30日
        LocalDateTime localDateTime02 = localDateTime01.withDayOfMonth(30);
        System.out.println(localDateTime02);

        //月份变为10月
        LocalDateTime localDateTime03 = localDateTime02.withMonth(10);
        System.out.println(localDateTime03);

        //年份变为2000年
        LocalDateTime localDateTime04 = localDateTime03.withYear(2000);
        System.out.println(localDateTime04);
    }
}

/*
结果:
2001-07-27T22:22:22
2001-07-30T22:22:22
2001-10-30T22:22:22
2000-10-30T22:22:22
*/

2.4、日期和时间的加减方法

LocalDateTime类提供了以下方法,可以对日期和时间进行加减。

方法说明
LocalDateTime plusYears(long years)增加年
LocalDateTime plusMonths(long months)增加月
LocalDateTime plusWeeks(long weeks)增加周
LocalDateTime plusDays(long days)增加天
LocalDateTime plusHours(long hours)增加小时
LocalDateTime plusMinutes(long minutes)增加分
LocalDateTime plusSeconds(long seconds)增加秒
LocalDateTime plusNanos(long nanos)增加纳秒
LocalDateTime minusYears(long years)减少年
LocalDateTime minusMonths(long months)减少月
LocalDateTimeminusWeeks(long weeks)减少周
LocalDateTime minusDays(long days)减少天
LocalDateTime minusHours(long hours)减少小时
LocalDateTime minusMinutes(long minutes)减少分
LocalDateTime minusSeconds(long seconds)减少秒
LocalDateTime minusNanos(long nanos)减少纳秒

范例6:日期和时间的加减

package edu.blog.test07;


import java.time.LocalDateTime;

public class DateTestDemo06 {
    public static void main(String[] args) {
        LocalDateTime localDateTime = LocalDateTime.now();
        System.out.println("当前时间: " + localDateTime);

        System.out.println("========================");
        System.out.println("增加天数 : " + localDateTime.plusDays(1));
        System.out.println("增加周数 : " + localDateTime.plusWeeks(1));
        System.out.println("增加月数 : " + localDateTime.plusMonths(1));
        System.out.println("增加年数 : " + localDateTime.plusYears(1));

        System.out.println("========================");
        System.out.println("减少天数 : " + localDateTime.minusDays(1));
        System.out.println("减少月数 : " + localDateTime.minusMonths(1));
        System.out.println("减少周数 : " + localDateTime.minusWeeks(1));
        System.out.println("减少年数 : " + localDateTime.minusYears(1));

        System.out.println("========================");
        System.out.println("增加1小时: " + localDateTime.plusHours(1));
        System.out.println("增加30分钟: " + localDateTime.plusMinutes(30));
        System.out.println("增加30秒: " + localDateTime.plusSeconds(30));
        System.out.println("增加10000纳秒:" + localDateTime.plusNanos(10000));

        System.out.println("========================");
        System.out.println("减少1小时:" + localDateTime.minusHours(1));
        System.out.println("减少30分钟:" + localDateTime.minusMinutes(30));
        System.out.println("减少30秒: " + localDateTime.minusSeconds(30));
        System.out.println("减少10000纳秒:" + localDateTime.minusNanos(10000));
    }
}

/*
结果:
当前时间: 2021-04-30T21:54:48.640
========================
增加天数 : 2021-05-01T21:54:48.640
增加周数 : 2021-05-07T21:54:48.640
增加月数 : 2021-05-30T21:54:48.640
增加年数 : 2022-04-30T21:54:48.640
========================
减少天数 : 2021-04-29T21:54:48.640
减少月数 : 2021-03-30T21:54:48.640
减少周数 : 2021-04-23T21:54:48.640
减少年数 : 2020-04-30T21:54:48.640
========================
增加1小时: 2021-04-30T22:54:48.640
增加30分钟: 2021-04-30T22:24:48.640
增加30秒: 2021-04-30T21:55:18.640
增加10000纳秒:2021-04-30T21:54:48.640010
========================
减少1小时:2021-04-30T20:54:48.640
减少30分钟:2021-04-30T21:24:48.640
减少30秒: 2021-04-30T21:54:18.640
减少10000纳秒:2021-04-30T21:54:48.639990
 */

2.5、比较方法—isBefore()、isAfter()、isEqual()

如果想判断两个LocalDateTime的之间的前后关系(LocalDate、LocalTime类似),可以使用isBefore()方法、isAfter()、isEqual()方法。

方法说明
boolean isAfter(ChronoLocalDateTime<?> other)检查是否在指定日期时间之后
boolean isBefore(ChronoLocalDateTime<?> other)检查是否在指定日期时间之前
boolean isEqual(ChronoLocalDateTime<?> other)判断日期时间是否相等

范例7:判断两个LocalDateTime先后关系

package edu.blog.test07;

import java.time.LocalDateTime;

public class DateTestDemo07 {
    public static void main(String[] args) {
        LocalDateTime localDateTime01 = LocalDateTime.now();
        LocalDateTime localDateTime02 = LocalDateTime.of(2001, 7, 27, 22, 22, 22);
        System.out.println(localDateTime01.isBefore(localDateTime02));
        System.out.println(LocalDateTime.now().isBefore(LocalDateTime.of(2000, 10, 30, 22, 22, 22)));
    }
}
/*
结果:
false
false
*/

注意:LocalDateTime无法与时间戳进行转换,因为LocalDateTime没有时区,无法确定某一时刻。

而ZoneDateTime(这里不展开讲述,《ZoneDateTime类》这篇文章讲得很详细)等同于LocalDateTime加时区的组合,它具有时区,可以与long表示的时间戳进行转换。


2.6、转换方法

LocalDateTime类提供了一些转换方法,可以转换为LocalDate或者是LocalTime。同时也提供了方法支持通过文本字符串来获取LocalDateTime类对象。

方法说明
LocalDate toLocalDate()获取日期部分
LocalTime toLocalTime()获取时间部分
static LocalDateTime parse(CharSequence text)从文本字符串获取LocalDateTime 实例
static LocalDateTime parse(CharSequence text, DateTimeFormatter formatter)检使用特定格式化形式从文本字符串获取LocalDateTime 实例

范例8:转换方法

package edu.blog.test07;


import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class DateTestDemo08 {
    public static void main(String[] args) {
        LocalDateTime localDateTime01 = LocalDateTime.now();
        System.out.println("LocalDateTime:"+localDateTime01);
        System.out.println("LocalDate:"+localDateTime01.toLocalDate());
        System.out.println("LocalTime:"+localDateTime01.toLocalTime());
        System.out.println("============================");

        LocalDateTime localDateTime02 = LocalDateTime.parse("2001-07-27T00:00:00.303995200");
        System.out.println(localDateTime02);
        System.out.println("============================");

        DateTimeFormatter dateTimeFormatter1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        LocalDateTime localDateTime03 = LocalDateTime.parse("2001-07-27 21:20:06", dateTimeFormatter1);
        System.out.println(localDateTime03);
        System.out.println("============================");

        String localDateTime04 = dateTimeFormatter1.format(localDateTime02);
        System.out.println(localDateTime04);
    }
}

/*
结果:
LocalDateTime:2021-04-30T22:05:08.860
LocalDate:2021-04-30
LocalTime:22:05:08.860
============================
2001-07-27T00:00:00.303995200
============================
2001-07-27T21:20:06
============================
2001-07-27 00:00:00
 */

2.7、Duration和Period

Duration表示两个时刻之间的时间间隔。另一个类似的Period表示两个日期之间的天数。

范例9:“时间”间隔与“日期”间隔

package edu.blog.test07;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Period;
import java.util.Date;

public class DateTestDemo09 {
    public static void main(String[] args) {
        LocalDateTime localDateTime01 = LocalDateTime.of(2000, 10, 30, 22, 22, 22);
        LocalDateTime localDateTime02 = LocalDateTime.of(2001, 7, 27, 2, 2, 2);
        System.out.println("时间1:" + localDateTime01);
        System.out.println("时间2:" + localDateTime02);
        Duration duration = Duration.between(localDateTime01, localDateTime02);
        System.out.println("时间差:" + duration);

        System.out.println("===========================================");
        LocalDate localDate01 = LocalDate.of(2000, 10, 30);
        LocalDate localDate02 = LocalDate.of(2001, 7, 27);
        System.out.println("日期1:" + localDate01);
        System.out.println("日期2:" + localDate02);
        Period period = localDate01.until(localDate02);
        System.out.println("日期差:" + period);

    }
}

/*
结果:
时间1:2000-10-30T22:22:22
时间2:2001-07-27T02:02:02
时间差:PT6459H39M40S
===========================================
日期1:2000-10-30
日期2:2001-07-27
日期差:P8M27D
*/

注意:两个LocalDateTime之间的差值使用Duration表示,类似于"PT1234H10M30S",表示1234小时10分钟30秒。而两个LocalDate之间的差值用Period表示,类似于"P1M21D",表示1个月21天。

Duration和Period的表示方法也符合ISO 8601的格式,它以"P…T…"的形式表示,"P…T"之间表示日期间隔,"T"后面表示时间间隔。如果是"PT…"的格式表示仅有时间间隔。


2.8、在数据库中存储日期和时间

除了旧式的java.util.Date,我们还可以找到另一个java.sql.Date,它继承自java.util.Date,但会自动忽略所有时间相关信息。这个奇葩的设计原因要追溯到数据库的日期与时间类型。

在数据库中,也存在几种日期和时间类型:

  • DATETIME:表示日期和时间;
  • DATE:仅表示日期;
  • TIME:仅表示时间;
  • TIMESTAMP和DATETIME类似,但是数据库会在创建或者更新记录的时候同时修改TIMESTAMP。

在使用Java程序操作数据库时,我们需要把数据库类型与Java类型映射起来。数据库类型与Java类的映射关系如下表所示。

数据库对应Java类(旧)对应Java类(新)
DATETIMEjava.util.Datejava.time.LocalDateTime
DATEjava.sql.Datejava.time.LocalDate
TIMEjava.sql.Timejava.time.LocalTime
TIMESTAMPjava.sql.Timestampjava.time.LocalDateTime

实际上,在数据库中,我们需要存储的最常用的是时刻(“Instant”),因为有了时刻信息,就可以根据用户自己选择的时区,显示出正确的本地时间。所以,最好的方法是直接用长整数"long"表示,在数据库中存储为BIGINT类型。

范例10:

通过存储一个"long"型时间戳,我们可以编写一个timestampToString()的方法,非常简单地为不同用户以不同的偏好来显示不同的本地时间:


3、DateTimeFormatter类

如果想要自定义输出格式,或者是想把一个非ISO 8601格式的字符串解析成LocalDateTime,可以使用新的DateTimeFormatter类。这里我们先简单举个例子,《请别再使用SimpleDateFormat了,用DateTimeFormatter吧!》这篇文章再详细展开。

范例11:自定义输出格式

package edu.blog.test07;


import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class DateTestDemo11 {
    public static void main(String[] args) {
        //自定义输出格式
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
        System.out.println(dtf.format(LocalDateTime.now()));
        System.out.println("===================================");
        //自定义格式解析
        LocalDateTime localDateTime = LocalDateTime.parse("2001/07/27 22:22:22", dtf);
        System.out.println(localDateTime);
    }
}

/*
结果:
2021/04/02 14:58:21
===================================
2001-07-27T22:22:22
*/

4、LoalDateTime的线程安全问题

其实上面讲来讲去只讲了Date和LocalDateTime两者在用法上的差别,这其实倒还好,并不致命,可是接下来要讨论的线程安全性问题才是致命的!

其实以前我们惯用的 Date类是可变类,这就意味着在多线程环境下对共享 Date变量进行操作时,必须由程序员自己来保证线程安全!否则极有可能翻车。

而自JDK 1.8开始推出的 LocalDateTime类却是线程安全的,开发人员不用再考虑并发问题,这点我们从 LocalDateTime类的部分官方源码,如图 4-1所示。

图 4-1

就冲这句话:“This class is immutable and thread-safe.”,就没有任何理由不用 LocalDateTime。

除了惯用 Date来表示时间之外,还有一个用于和Date连用的SimpleDateFormat时间格式化类大家也可能经常在使用,但是其最主要的致命问题也是在于它本身线程并不安全。那取而代之,我们现在该用什么呢?那就是上面提到的DateTimeFormatter类,他也是线程安全的。同样,我们在《请别再使用SimpleDateFormat了,用DateTimeFormatter吧!》这篇文章再详细展开。


注:此文章为个人学习笔记,如有错误,敬请指正。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

窝在角落里学习

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值