工作中我们会遇到些场景需要我们对时间或者日历进行处理,比如要统计账单、报表等,需要获取当前时间、获取当前时间前一天、获取本月第一天和最后一天、获取特定天特定的时刻(精确到秒)等,这时就需要借助一些第三方包的api来完成这些操作。Java 8的对时间/日期处理的类/第三方包主要有以下6种:

  • java.util包下的Date;
  • Instant;
  • SimpleDateFormat;
  • DateTimeFormatter;
  • Calendar;
  • LocalDateTime。

开发过程中建议使用后三种。

1、Date类

Date类是最基础也是最早发布的一个时间类,Date类重名的有4个相关的类,用于处理日期的类是位于java.util包下的Date类。Date类由于发布的比较早,很多方法已经不推荐使用了,可以用Calendar类代替。

1.1 Date类构造方法

Date类有6个构造方法,其中的4个已经被@Deprecated注解标注表明不推荐使用了,这里介绍还在使用的2中构造方法。

  1. // 无参构造方法,将会创建一个当前系统时间的Date对象,时间精确到毫秒
  2. public Date();
  3. // 接收一个参数,该参数是从1970年1月1日起的毫秒数
  4. public Date(long date);

举例:打印当前时间

  1. package Time.Date;
  2. import java.util.Date;
  3. public class DateMain {
  4. public static void main(String[] args) {
  5. Date date = new Date();
  6. System.out.println(date);
  7. }
  8. }

结果:

  1. Mon Jan 18 10:59:42 CST 2021

可以看到Date获取的日期格式是格林威治时间,并不那么直观,我们更希望的是年-月-日-时-分-秒这种格式,所以会用到第3节的SimpleDateFormat类对Date产生的日期进行格式化,转换成我们希望的格式。

1.2 Date类常用方法

Date类里面的方法也大部分都是废弃的,不建议使用的,这里介绍几个常用的方法。

1.2.1 boolean before(Date date)

调用此方法的Date对象在指定日期之前返回true,否则返回false。
举例:

  1. public class DateMain {
  2. public static void main(String[] args) {
  3. Date dateBefore = new Date();
  4. try {
  5. Thread.sleep(3000);
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. Date dateNow = new Date();
  10. System.out.println(dateBefore.before(dateNow));
  11. }
  12. }

1.2.2 boolean after(Date date)

调用此方法的Date对象在指定日期之后返回true,否则返回false。
举例:

  1. public class DateMain {
  2. public static void main(String[] args) {
  3. Date dateBefore = new Date();
  4. try {
  5. Thread.sleep(3000);
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. Date dateNow = new Date();
  10. System.out.println(dateNow.after(dateBefore));
  11. }
  12. }

1.2.3 int compareTo(Date anotherDate)

比较两个日期的顺序:

  • 如果调用方Date对象的时间早于anotherDate对象的时间返回-1;
  • 如果调用方Date对象的时间晚于anotherDate对象的时间返回1;
  • 如果调用方Date对象的时间等于anotherDate对象的时间返回0。

举例:

  1. public class DateMain {
  2. public static void main(String[] args) {
  3. Date dateBefore = new Date();
  4. try {
  5. Thread.sleep(3000);
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. Date dateNow = new Date();
  10. // 返回-1
  11. System.out.println(dateBefore.compareTo(dateNow));
  12. // 返回1
  13. System.out.println(dateNow.compareTo(dateBefore));
  14. }
  15. }

2、SimpleDateFormat

2.1 SimpleDateFormat使用

上面介绍了Date类的得到的日期是格林威治时间,并不直观,需要将格林威治时间转换为我们习惯的时间格式,SimpleDateFormat就是格林威治时间和直观的日期字符串之间转换的工具。

2.1.1 SimpleDateFormat构造方法

SimpleDateFormat有以下4种构造方法:

  1. public SimpleDateFormat(){}
  2. public SimpleDateFormat(String pattern){}
  3. public SimpleDateFormat(String pattern, Locale locale){}
  4. public SimpleDateFormat(String pattern, DateFormatSymbols formatSymbols){}

最经常使用的还是第二种构造方法,该方法传入一个日期格式字符串,通过该字符串定义的日期格式返回具体的时间。一般会写一个DateUtils将这些时间类处理封装。

2.1.2 String format(Date date)

按照指定的模式返回格式化后的日期字符串。

2.1.3 Date parse(String dateStr)

利用给定的日期字符串转成Date对象。
举例:
DateUtils:

  1. package Time.SimpleDateFormat;
  2. import java.text.ParseException;
  3. import java.text.SimpleDateFormat;
  4. import java.util.Date;
  5. public class DateUtils {
  6. // 注意:这些写是线程不安全的
  7. private static final String YM_FORMAT = "yyyy-MM";
  8. private static final String YMD_FORMAT = "yyyy-MM-dd";
  9. private static final String YMDH_FORMAT = "yyyy-MM-dd HH:mm:ss";
  10. private static final SimpleDateFormat ymFormat = new SimpleDateFormat(YM_FORMAT);
  11. private static final SimpleDateFormat ymdFormat = new SimpleDateFormat(YMD_FORMAT);
  12. private static final SimpleDateFormat ymdhFormat = new SimpleDateFormat(YMDH_FORMAT);
  13. public static Date stringToDate(String dateStr)
  14. {
  15. Date date = null;
  16. try {
  17. date = ymdhFormat.parse(dateStr);
  18. } catch (ParseException e) {
  19. e.printStackTrace();
  20. }
  21. return date;
  22. }
  23. public static String dateToString(Date date)
  24. {
  25. return ymdhFormat.format(date);
  26. }
  27. }

SDFMain:

  1. package Time.SimpleDateFormat;
  2. import java.util.Date;
  3. public class SDFMain {
  4. public static void main(String[] args) {
  5. Date nowDate = new Date();
  6. String dateStr = DateUtils.dateToString(nowDate);
  7. // 结果:2021-01-18 14:17:02
  8. System.out.println(dateStr);
  9. // 结果:Mon Jan 18 14:17:02 CST 2021
  10. System.out.println(DateUtils.stringToDate(dateStr));
  11. }
  12. }

2.2 SimpleDateFormat线程不安全

2.2.1 为什么SimpleDateFormat是线程不安全的?

单线程或者并发量比较少的情况下SimpleDateFormat不会出现这个问题,但当并发量上去后会出现这个问题。一般为了节省内存开销,会把SimpleDateFormat对象声明成static类型,SimpleDateFormat的两个核心方法:format和parse里会使用一个Calendar类型的成员变量来保存时间,那么这个被static修饰的SimpleDateFormat对象就是一个共享变量,SimpleDateFormat中的Calendar类型的成员变量也就可以被多个线程访问到,这就是线程不安全的原因。
举个例子:假设一个线程A刚执行完calendar.setTime把时间设置成2020-05-07,这个线程还没执行完,线程B又执行了calendar.setTime把时间改成了2020-06-07。这时候线程A继续往下执行,拿到的calendar.getTime得到的时间就是线程B改过之后的。

2.2.2 解决方案是什么?

  1. 每次使用时new一个SimpleDateFormat 的实例,这样可以保证每个实例使用自己的Calendar实例,但是每次使用都需要new一个对象,并且使用后由于没有其他引用,又需要回收,开销会很大;
  2. 可以使用synchronized 对SimpleDtaFormat实例进行同步;
  3. 使用ThreadLocal,这样每个线程只需要使用一个SimpleDateFormate实例,这相比第一种方式节省了对象的创建销毁开销,并且不需要使多个线程同步;
  4. 使用JDK8中的 DateTimeFormatter。

这里推荐使用第4种方法,因为ThreadLocal有内存泄漏的风险。

3、DateTimeFormatter

鉴于第2节中介绍的SimpleDateFormat是线程不安全的,jdk1.8中新增了 LocalDate 与 LocalDateTime等类来解决日期处理方法,同时引入了一个新的类DateTimeFormatter来解决日期格式化问题。LocalDateTime,DateTimeFormatter都是线程安全的,且二者是搭配使用的。
与创建一个SimpleDateFormat实例是用new的方法不同,创建一个DateTimeFormat实例是用一个静态方法ofPattern(String formatStr)。可以创建static修饰的DateTimeFormatter实例,且是线程安全的。
举例:

  1. public class DateUtils {
  2. private static final String YM_FORMAT = "yyyy-MM";
  3. private static final String YMD_FORMAT = "yyyy-MM-dd";
  4. private static final String YMDH_FORMAT = "yyyy-MM-dd HH:mm:ss";
  5. private static DateTimeFormatter ymFormatter = DateTimeFormatter.ofPattern(YM_FORMAT);
  6. private static DateTimeFormatter ymdFormatter = DateTimeFormatter.ofPattern(YMD_FORMAT);
  7. private static DateTimeFormatter ymdhFormatter = DateTimeFormatter.ofPattern(YMDH_FORMAT);
  8. }

DateTimeFormatter确保线程安全的机制???

4、Calendar

Calendar类是一个抽象类,GregorianCalendar是Calendar类的一个具体实现,Calendar 的getInstance()方法返回一个默认用当前的语言环境和时区初始化的GregorianCalendar对象,下面介绍方法时也是GregorianCalendar类的实例方法。
Calendar类中用以下这些常量表示不同的意义,即年、月、日、时、分、秒,如下:

常量 描述
Calendar.YEAR 年份
Calendar.MONTH 月份
Calendar.DATE 日期
Calendar.DAY_OF_MONTH 日,一个月的第几天
Calendar.HOUR 12小时制的小时
Calendar.HOUR_OF_DAY 24小时制的小时
Calendar.MINUTE 分钟
Calendar.SECOND
Calendar.DAY_OF_WEEK 星期几

4.1 getInstance

是Calendar类提供的获取GregorianCalendar实例的静态方法,因此创建Calendar实例不用new,用getInstance。
注意Calendar类的getInstance方法获取的是当前日期。

  1. Calendar calendar = Calendar.getInstance();

4.2 getTime

Date getTime(),获取当前时间(格林威治时间)。
举例:

  1. public class CalendarMain {
  2. public static void main(String[] args) {
  3. Calendar calendar = Calendar.getInstance();
  4. // 打印结果:Mon Jan 18 14:40:00 CST 2021
  5. System.out.println(calendar.getTime());
  6. }
  7. }

4.3 setTime

void setTime(Date date),用给定的日期设置Calendar的当前时间,参数类型为Date实例。
举例:

  1. public class CalendarMain {
  2. public static void main(String[] args) {
  3. Calendar calendar = Calendar.getInstance();
  4. Date date = new Date();
  5. calendar.setTime(date);
  6. System.out.println(calendar.getTime());
  7. }
  8. }

4.4 get

int get(int field),获取指定字段的时间值,返回int类型,入参field,指定字段,即介绍Calendar开始时的常量表格。
注意:

  • 获取当前时间的月份,calendar.get(Calendar.MONTH),结果要+1才是当前时间的月份;
  • 获取周几,返回的int类型的结果,1代表周日,2代表周一,3代表周二…以此类推,7代表周六。

举例:

  1. public class CalendarMain {
  2. public static void main(String[] args) {
  3. Calendar calendar = Calendar.getInstance();
  4. // 获取年,结果是:2021
  5. System.out.println(calendar.get(Calendar.YEAR));
  6. // 获取月,结果是:0
  7. System.out.println(calendar.get(Calendar.MONTH));
  8. // 获取日,结果是:18
  9. System.out.println(calendar.get(Calendar.DAY_OF_MONTH));
  10. // 获取24小时制的时,结果是:15
  11. System.out.println(calendar.get(Calendar.HOUR_OF_DAY));
  12. // 获取周几,结果是:2
  13. System.out.println(calendar.get(Calendar.DAY_OF_WEEK));
  14. }
  15. }

4.5 set

void set(int field, int value),设置指定字段的时间值,这里的指定字段field即介绍Calendar开始时的常量表格。
注意:

  • 下方的例子,如果某些字段没有set,则保留不变(下方例子里即当天的分钟、秒数);
  • 设置月份时,设置的value为目标月份-1,比如设置为5月,则 calendar.set(Calendar.MONTH, 4);
  • 设置周几时,1代表周天,2代表周一,以此类推…

举例:

  1. public class CalendarMain {
  2. public static void main(String[] args) {
  3. Calendar calendar = Calendar.getInstance();
  4. calendar.set(Calendar.YEAR, 1993);
  5. calendar.set(Calendar.MONTH, 4);
  6. calendar.set(Calendar.DAY_OF_MONTH, 16);
  7. calendar.set(Calendar.HOUR_OF_DAY, 5);
  8. calendar.set(Calendar.DAY_OF_WEEK, 1);
  9. // 打印结果:Sun May 16 05:19:05 CST 1993 我的生日
  10. System.out.println(calendar.getTime());
  11. }
  12. }

4.6 add

void add(int field, int amount),日历的偏移量,可以指定一个日历中的字段field,进行整数amount的偏移,这里的指定字段field即介绍Calendar开始时的常量表格。
·举例:

  1. public class CalendarMain {
  2. public static void main(String[] args) {
  3. Calendar calendar = Calendar.getInstance();
  4. calendar.setTime(new Date());
  5. // 获取此刻上一个月份的时刻
  6. calendar.add(Calendar.MONTH, -1);
  7. // 打印结果:Fri Dec 18 15:34:06 CST 2020
  8. System.out.println(calendar.getTime());
  9. calendar.setTime(new Date());
  10. // 获取此刻两天后的时刻
  11. calendar.add(Calendar.DAY_OF_MONTH, 2);
  12. // 打印结果:Wed Jan 20 15:34:06 CST 2021
  13. System.out.println(calendar.getTime());
  14. }
  15. }

4.7 int getActualMaximum(int field)/int getActualMinimum(int field)

返回当前日期,给定字段的最大值/最小值。
应用场景:比如2月有28天,有的月31天,有的月30天,如果想获得当前月的最后一天,可以用calendar.getActualMaximum(Calendar.DAY_OF_MONTH)
举例:

  1. public class CalendarMain {
  2. public static void main(String[] args) {
  3. Calendar calendar = Calendar.getInstance();
  4. // 获取当前月的最大天,结果:31
  5. System.out.println(calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
  6. // 获取当前年的最小月,结果:1
  7. System.out.println(calendar.getActualMinimum(Calendar.YEAR));
  8. }
  9. }

4.8 boolean isLeapYear(int year)

确定给定的年份是否为闰年。

4.9 before/after/compareTo

上面三个方法都是Calender实例的比较方法:

  • boolean before(Objecr when):实际应用时入参多为Calender实例,如果调用方时间早于入参,返回true,否则返回false;
  • boolean after(Objecr when):实际应用时入参多为Calender实例,如果调用方时间晚于入参,返回true,否则返回false;
  • int compareTo(Calender calender):如果调用方时间早于入参,返回-1;如果调用方时间晚于入参,返回1,如果相等返回0。

    5、LocalDateTime

    Java8之后日期类推荐使用LocalDateTime类,这个类的使用跟其他几个类密切相关,涉及到的类:

  • LocalDate:获取日期,即年、月、日的信息;

  • LocalTime:获取时间,即时、分、秒的信息;
  • LocalDateTime:获取年、月、日、时、分、秒的信息;
  • Instant:时间戳相关的类;
  • DateTimeFormatter:日期格式化类,对应之前的SimpleDateFormat。

疑问:LocalDateTime、LocalDate和LocalTime三个类只保留第一个不就行了,后两个还有存在的意义么?

5.1 LocalDate

LocalDate是专门用来获取日期中的年、月和日的信息的。

5.1.1 初始化LocalDate

LocalDate对象的获取方式主要有以下三种:

  1. LocalDate.now();
  2. LocalDate.of(year, month, dayOfMonth);
  3. LocalDate.ofYearDay(year, 当年的第几天);

举例:

  1. public class LDMain {
  2. public static void main(String[] args) {
  3. LocalDate localDate = LocalDate.now();
  4. // 打印结果:2021-01-19
  5. System.out.println(localDate);
  6. LocalDate localDate1 = LocalDate.of(1993, 5, 16);
  7. // 打印结果:1993-05-16
  8. System.out.println(localDate1);
  9. LocalDate localDate2 = LocalDate.ofYearDay(1993, 256);
  10. // 打印结果:1993-09-13
  11. System.out.println(localDate2);
  12. }
  13. }

5.1.2 使用LocalDate读取date

  1. // 获取年份
  2. int getYear();
  3. // 获取月份的英文名称,比如May、June等
  4. Month getMonth();
  5. // 获取月份的阿拉伯数字(1-12)
  6. int getMonthValue();
  7. // 获取一个月中的第几天
  8. int getDayOfMonth();
  9. // 获取当前日期是一年中的第几天
  10. int getDayOfYear();
  11. // 获取当前日期是周几,英文名称,比如Monday
  12. DayOfWeek getDayOfWeek();
  13. // 获取当前日期是周几,阿拉伯数字,1-7
  14. int get(ChronoField.DAY_OF_WEEK);
  15. // 获取当月有多少天,比如30、31、28...
  16. int lengthOfMonth();
  17. // 获取当年有多少天,比如365、366
  18. int lengthOfYear();
  19. // 判断当年是否是闰年,是闰年返回true,否则返回false
  20. boolean isLeapYear()

举例:

  1. package Time.LocalDateTime;
  2. import java.time.LocalDate;
  3. import java.time.temporal.ChronoField;
  4. public class LDMain {
  5. public static void main(String[] args) {
  6. LocalDate localDate = LocalDate.now();
  7. // 打印结果:2021
  8. System.out.println(localDate.getYear());
  9. // 打印结果:JANUARY
  10. System.out.println(localDate.getMonth());
  11. // 打印结果:1
  12. System.out.println(localDate.getMonthValue());
  13. // 打印结果:19
  14. System.out.println(localDate.getDayOfMonth());
  15. // 打印结果:19
  16. System.out.println(localDate.getDayOfYear());
  17. // 打印结果:TUESDAY
  18. System.out.println(localDate.getDayOfWeek());
  19. // 打印结果:2
  20. System.out.println(localDate.get(ChronoField.DAY_OF_WEEK));
  21. // 打印结果:31
  22. System.out.println(localDate.lengthOfMonth());
  23. // 打印结果:365
  24. System.out.println(localDate.lengthOfYear());
  25. // 打印结果:false
  26. System.out.println(localDate.isLeapYear());
  27. }
  28. }

5.1.3 TemporalField 读取 LocalDate 的值

ChronoField 是个枚举类,其实现了TemporalField接口,除了5.1.2的方式读取date之外,我们还可以使用ChronoField方式读取date,这里列举几个常用的ChronoField枚举类。

  1. // 获取当前日期的年份
  2. int get(ChronoField.YEAR);
  3. // 获取当前日期是第几月
  4. int get(ChronoField.MONTH_OF_YEAR);
  5. // 获取当前日期是当年的第几月
  6. int get(ChronoField.DAY_OF_MONTH);
  7. // 获取当前日期是当年的第几天
  8. int get(ChronoField.DAY_OF_YEAR);
  9. // 获取当前日期是本周的第几天
  10. int get(ChronoField.DAY_OF_WEEK);

举例:

  1. package Time.LocalDateTime;
  2. import java.time.LocalDate;
  3. import java.time.temporal.ChronoField;
  4. public class LDMain {
  5. public static void main(String[] args) {
  6. LocalDate localDate = LocalDate.now();
  7. // 打印结果:2021
  8. System.out.println(localDate.get(ChronoField.YEAR));
  9. // 打印结果:1
  10. System.out.println(localDate.get(ChronoField.MONTH_OF_YEAR));
  11. // 打印结果:19
  12. System.out.println(localDate.get(ChronoField.DAY_OF_MONTH));
  13. // 打印结果:19
  14. System.out.println(localDate.get(ChronoField.DAY_OF_YEAR));
  15. // 打印结果:2
  16. System.out.println(localDate.get(ChronoField.DAY_OF_WEEK));
  17. }
  18. }

5.1.4 修改LocalDate的date

通过withAttribute修改不会改变原来的localDate,会在原来localDate的基础上形成新的localDate副本。

  1. // 修改localDate的年份为year
  2. LocalDate withYear(int year);
  3. // 修改localDate的月份为month
  4. LocalDate withMonth(int month);
  5. // 修改localDate的日期为dayOfMonth,为当月的第几天
  6. LocalDate withDayOfMonth(int dayOfMonth);
  7. // 修改localDate的日期为dayOfYear,为当年的第几天
  8. LocalDate withDayOfYear(int dayOfYear);

举例:

  1. package Time.LocalDateTime;
  2. import java.time.LocalDate;
  3. public class LDMain {
  4. public static void main(String[] args) {
  5. // 2021-01-19
  6. LocalDate localDate = LocalDate.now();
  7. LocalDate localDate1 = localDate.withYear(1993);
  8. // 打印结果:1993-01-19
  9. System.out.println(localDate1);
  10. LocalDate localDate2 = localDate.withMonth(5);
  11. // 打印结果:2021-05-19
  12. System.out.println(localDate2);
  13. LocalDate localDate3 = localDate.withDayOfYear(192);
  14. // 打印结果:2021-07-11
  15. System.out.println(localDate3);
  16. LocalDate localDate4 = localDate.withDayOfMonth(16);
  17. // 打印结果:2021-01-16
  18. System.out.println(localDate4);
  19. }
  20. }

5.1.5 使用 TemporalAdjuster 修改日期

TemporalAdjuster 时间矫正器修改时间也不会改变原来的localDate,会新生成LocalDate 副本,相比于withAttribute,其API更加丰富,提供大量的静态工厂方法,能满足我们日常开发需求。

  1. // 获取localDate对应的本月第一天的日期
  2. localDate.with(TemporalAdjusters.firstDayOfMonth());
  3. // 获取localDate对应的本年第一天的日期
  4. localDate.with(TemporalAdjusters.firstDayOfYear());
  5. // 获取localDate对应的本月最后一天的日期
  6. localDate.with(TemporalAdjusters.lastDayOfMonth());
  7. // 获取localDate对应的本年的最后一天的日期
  8. localDate.with(TemporalAdjusters.lastDayOfYear());
  9. // 获取localDate对应的下个月的第一天的日期
  10. localDate.with(TemporalAdjusters.firstDayOfNextMonth());
  11. // 获取localDate对应的下一年的第一天的日期
  12. localDate.with(TemporalAdjusters.firstDayOfNextYear());
  13. // 获取localDate的下一个dayOfWeek(比如DayOfWeek.Monday)对应的日期,如果当天就是dayOfWeek,则往后找下一个
  14. // 比如当天是周二,localDate.with(TemporalAdjusters.next(DayOfWeek.Tuesday))返回的是下一个周二的日期
  15. localDate.with(TemporalAdjusters.next(DayOfWeek dayOfWeek));
  16. // 获取localDate的下一个dayOfWeek(比如DayOfWeek.Monday)对应的日期,如果当天就是dayOfWeek,则返回当天的日期
  17. // 比如当天是周二,localDate.with(TemporalAdjusters.nextOrSame(DayOfWeek.Tuesday))返回的就是当天的日期
  18. localDate.with(TemporalAdjusters.nextOrSame(DayOfWeek dayOfWeek));
  19. // 获取localDate的上一个dayOfWeek(比如DayOfWeek.Monday)对应的日期,如果当天就是dayOfWeek,则往前找上一个
  20. // 比如当天是周二,localDate.with(TemporalAdjusters.previous(DayOfWeek.Tuesday))返回的是上一个周二的日期
  21. localDate.with(TemporalAdjusters.previous(DayOfWeek dayOfWeek));
  22. // 获取localDate的上一个dayOfWeek(比如DayOfWeek.Monday)对应的日期,如果当天就是dayOfWeek,则返回当天的日期
  23. // 比如当天是周二,localDate.with(TemporalAdjusters.previous(DayOfWeek.Tuesday))返回的就是当天的日期
  24. localDate.with(TemporalAdjusters.previousOrSame(DayOfWeek dayOfWeek));
  25. // 获取localDate对应月份的第一个dayOfWeek(星期)
  26. localDate.with(TemporalAdjusters.firstInMonth(DayOfWeek dayOfWeek));
  27. // 获取localDate对应月份的最后一个dayOfWeek(星期)
  28. localDate.with(TemporalAdjusters.lastInMonth(DayOfWeek dayOfWeek));

举例:

  1. package Time.LocalDateTime;
  2. import java.time.DayOfWeek;
  3. import java.time.LocalDate;
  4. import java.time.temporal.TemporalAdjusters;
  5. public class LDMain {
  6. public static void main(String[] args) {
  7. // 2021-01-19
  8. LocalDate localDate = LocalDate.now();
  9. // 打印结果:2021-01-01
  10. System.out.println(localDate.with(TemporalAdjusters.firstDayOfMonth()));
  11. // 打印结果:2021-01-01
  12. System.out.println(localDate.with(TemporalAdjusters.firstDayOfYear()));
  13. // 打印结果:2021-01-31
  14. System.out.println(localDate.with(TemporalAdjusters.lastDayOfMonth()));
  15. // 打印结果:2021-12-31
  16. System.out.println(localDate.with(TemporalAdjusters.lastDayOfYear()));
  17. // 打印结果:2021-02-01
  18. System.out.println(localDate.with(TemporalAdjusters.firstDayOfNextMonth()));
  19. // 打印结果:2022-01-01
  20. System.out.println(localDate.with(TemporalAdjusters.firstDayOfNextYear()));
  21. // 打印结果:2021-01-25
  22. System.out.println(localDate.with(TemporalAdjusters.next(DayOfWeek.TUESDAY)));
  23. // 打印结果:2021-01-19
  24. System.out.println(localDate.with(TemporalAdjusters.nextOrSame(DayOfWeek.TUESDAY)));
  25. // 打印结果:2021-01-12
  26. System.out.println(localDate.with(TemporalAdjusters.previous(DayOfWeek.TUESDAY)));
  27. // 打印结果:2021-01-19
  28. System.out.println(localDate.with(TemporalAdjusters.previousOrSame(DayOfWeek.TUESDAY)));
  29. // 打印结果:2021-01-05
  30. System.out.println(localDate.with(TemporalAdjusters.firstInMonth(DayOfWeek.TUESDAY)));
  31. // 打印结果:2021-01-26
  32. System.out.println(localDate.with(TemporalAdjusters.lastInMonth(DayOfWeek.TUESDAY)));
  33. }
  34. }

5.1.6 解析LocalDate

就是String和LocalDate对象之间的相互转换。

  1. // 字符串转LocalDate对象
  2. DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("*******");
  3. LocalDate date = LocalDate.parse(dateStr, dateTimeFormatter);
  4. // LocalDate对象转字符串
  5. String dateStr = localDate.toString();

举例:将指定格式的字符串转成LocalDate对象

  1. public class LDMain {
  2. public static void main(String[] args) {
  3. DateTimeFormatter ymdFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
  4. String dateStr = "1993/05/16";
  5. LocalDate localDate = LocalDate.parse(dateStr, ymdFormatter);
  6. System.out.println(localDate);
  7. }
  8. }

5.1.7 使用Period操纵LocalDate

不好用,有大坑。

  1. public class LDMain {
  2. public static void main(String[] args) {
  3. LocalDate date1 = LocalDate.of(1993, 5, 16);
  4. LocalDate date2 = LocalDate.of(2021, 1, 19);
  5. Period between = Period.between(date1, date2);
  6. // 打印结果:27
  7. System.out.println(between.getYears());
  8. // 打印结果:8
  9. System.out.println(between.getMonths());
  10. // 打印结果:3
  11. System.out.println(between.getDays());
  12. }
  13. }

5.2 LocalTime

LocalTime的api跟LocalDate的很像。

5.2.1 初始化LocalTime

获取LocalTime对象主要有以下两种方式:

  1. // 获取当前时刻的LocalTime对象
  2. LocalTime.now();
  3. // 获取指定时、分、秒、纳秒的LocalTime对象
  4. LocalTime localTime1 = LocalTime.of(时, 分, 秒, 纳秒);

举例:

  1. public class LTMain {
  2. public static void main(String[] args) {
  3. LocalTime localTime = LocalTime.now();
  4. // 打印结果:17:38:55.586
  5. System.out.println(localTime);
  6. LocalTime localTime1 = LocalTime.of(11, 22, 33, 44);
  7. // 打印结果:11:22:33.000000044
  8. System.out.println(localTime1);
  9. }
  10. }

5.2.2 使用LocalTime读取时间

  1. // 获取localTime的时
  2. int getHour();
  3. // 获取localTime的分
  4. int getMinute();
  5. // 获取localTime的秒
  6. int getSecond();
  7. // 获取localTime的纳秒
  8. int getNano();

举例:

  1. public class LTMain {
  2. public static void main(String[] args) {
  3. // 17:43:18.102
  4. LocalTime localTime = LocalTime.now();
  5. // 打印结果:17
  6. System.out.println(localTime.getHour());
  7. // 打印结果:43
  8. System.out.println(localTime.getMinute());
  9. // 打印结果:18
  10. System.out.println(localTime.getSecond());
  11. // 打印结果:10200000
  12. System.out.println(localTime.getNano());
  13. }
  14. }

5.2.3 修改LocalTime的值

跟LocalDate一样,withPropertity方法也是创建副本并返回。

  1. // 设置localTime对象的时
  2. localTime.withHour(hour);
  3. // 设置localTime对象的分
  4. localTime.withMinute(minute);
  5. // 设置localTime对象的秒
  6. localTime.withSecond(second);
  7. // 设置localTime对象的纳秒
  8. localTime.withNano(nanosecond);

举例:

  1. public class LTMain {
  2. public static void main(String[] args) {
  3. LocalTime localTime = LocalTime.now();
  4. // 打印结果:17
  5. System.out.println(localTime.withHour(15));
  6. // 打印结果:43
  7. System.out.println(localTime.withMinute(16));
  8. // 打印结果:18
  9. System.out.println(localTime.withSecond(17));
  10. // 打印结果:10200000
  11. System.out.println(localTime.withNano(18));
  12. }
  13. }

5.2.4 解析LocalTime

举例:

  1. public void localTimeParse(){
  2. // 默认支持格式解析
  3. LocalTime parse = LocalTime.parse("22:50:00");
  4. System.out.println(parse);// 22:50
  5. // 指定格式解析
  6. LocalTime time = LocalTime.parse("22:50:00", DateTimeFormatter.ISO_TIME);
  7. System.out.println(time);// 22:50
  8. }

5.2.5 使用Duration获得时间差值

举例:

  1. public static void main(String[] args) {
  2. LocalTime time1 = LocalTime.of(22, 50, 20, 20);
  3. LocalTime time2 = LocalTime.of(23, 10);
  4. // 差值
  5. Duration duration = Duration.between(time1, time2);
  6. long seconds = duration.getSeconds();
  7. int nano = duration.getNano();
  8. System.out.println(seconds);//1179
  9. System.out.println(nano);//999999980
  10. }

5.3 LocalDateTime

5.3.1 LocalDateTime与LocalDate和LocalTime的转换

  1. // LocalDate和LocalTime合并为LocalDateTime
  2. LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime);
  3. // LocalDateTime转为LocalDate
  4. LocalDate localDate = localDateTime.toLocalDate();
  5. // LocalDateTime转为LocalTime
  6. LocalTime localTime = localDateTime.toLocalTime();

举例:

  1. public static void main(String[] args) {
  2. LocalDate localDate = LocalDate.of(1993, 5, 16);
  3. LocalTime localTime = LocalTime.of(5, 6, 6);
  4. // LocalDate和LocalTime合并为LocalDateTime
  5. LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime);
  6. // 打印结果:1993-05-16T05:06:06
  7. System.out.println(localDateTime);
  8. // LocalDateTime转为LocalDate
  9. LocalDate localDate1 = localDateTime.toLocalDate();
  10. // 打印结果:1993-05-16
  11. System.out.println(localDate1);
  12. // LocalDateTime转为LocalTime
  13. LocalTime localTime1 = localDateTime.toLocalTime();
  14. // 打印结果:05:06:06
  15. System.out.println(localTime1);
  16. }

5.3.2 LocalDateTime对象初始化

  1. // 获取当前时间
  2. LocalDateTime localDateTime = LocalDateTime.now();
  3. // 指定时间段的值拼装一个LocalDateTime
  4. LocalDateTime localDateTime = LocalDateTime.of(年,月,日,时,分,秒);

举例:

  1. public static void main(String[] args) {
  2. LocalDateTime localDateTime = LocalDateTime.now();
  3. // 打印结果:2021-01-19T19:37:48.761
  4. System.out.println(localDateTime);
  5. LocalDateTime localDateTime1 = LocalDateTime.of(1993, 5, 16, 6, 6, 6);
  6. // 打印结果:1993-05-16T06:06:06
  7. System.out.println(localDateTime1);
  8. }

5.3.3 LocalDateTime解析和格式化

注意:String转LocalDateTime时,中间会加一个字符“T”,ISO8601的规定,据说是为了避免歧义,SpirngBoot有个解决方案,如下:

  1. /**
  2. * 关于Java8中localDateTime去掉中间的T
  3. */
  4. @Configuration
  5. public class LocalDateTimeSerializerConfig {
  6. @org.springframework.beans.factory.annotation.Value("${spring.jackson.date-format:yyyy-MM-dd HH:mm:ss}")
  7. private String pattern;
  8. @Bean
  9. public LocalDateTimeSerializer localDateTimeDeserializer() {
  10. return new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(pattern));
  11. }
  12. @Bean
  13. public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
  14. return builder -> builder.serializerByType(LocalDateTime.class, localDateTimeDeserializer());
  15. }
  16. }

举例:

  1. public static void main(String[] args) {
  2. // LocalDateTime转String
  3. String ymdhmsPattern = "yyyy-MM-dd HH:mm:ss";
  4. DateTimeFormatter ymdhmsFormat = DateTimeFormatter.ofPattern(ymdhmsPattern);
  5. LocalDateTime localDateTime = LocalDateTime.now();
  6. String dateStr = localDateTime.format(ymdhmsFormat);
  7. // 打印结果:2021-01-19 19:46:34
  8. System.out.println(dateStr);
  9. // String转LocalDateTime
  10. LocalDateTime localDateTime1 = LocalDateTime.parse(dateStr, ymdhmsFormat);
  11. // 打印结果:2021-01-19T19:46:34
  12. System.out.println(localDateTime1);
  13. }

5.3.4 get各种时间值

api就是LocalDate和LocalTime的get相关的api。

5.3.5 set各种时间值

api就是LocalDate和LocalTime的set相关的api。

5.3.6 自定义日期 / 日期加减

加减后得到的LocalDateTime对象是一个新的LocalDateTime对象,而不是在原来的LocalDateTime对象上做加减,是在副本上进行的。
举例:

  1. package Time.LocalDateTime;
  2. import java.time.LocalDateTime;
  3. public class LDTMain {
  4. public static void main(String[] args) {
  5. // 2020-01-29T14:35:51
  6. LocalDateTime dateTime = LocalDateTime.of(2020, 1, 29, 14, 35, 51);
  7. // 2020-01-29T14:35:50
  8. System.out.println(dateTime.minusSeconds(1));
  9. // 2020-01-29T14:34:51
  10. System.out.println(dateTime.minusMinutes(1));
  11. // 2020-01-29T13:35:51
  12. System.out.println(dateTime.minusHours(1));
  13. // 2020-01-29T13:35:51
  14. System.out.println(dateTime.minusDays(1));
  15. // 2020-01-28T14:35:51
  16. System.out.println(dateTime.minusWeeks(1));
  17. // 2020-01-22T14:35:51
  18. System.out.println(dateTime.minusMonths(1));
  19. // 2019-12-29T14:35:51
  20. System.out.println(dateTime.minusYears(1));
  21. }
  22. }

增加的为对应的plus函数,如dateTime.plusSeconds(1)。

5.3.7 比较LocalDateTime日期

举例:

  1. public static void main(String[] args) {
  2. // 2020-01-29T14:35:51.207
  3. LocalDateTime now = LocalDateTime.now();
  4. // 2020-01-28T14:35:51.207
  5. LocalDateTime yesterday = now.minusDays(1);
  6. // true
  7. boolean after = now.isAfter(yesterday);
  8. // false
  9. boolean before = now.isBefore(yesterday);
  10. // false
  11. boolean equal = now.isEqual(yesterday);
  12. }

5.3.8 Duration && Period

5.3.8.1 Duration

Duration是用来统计两个LocalDateTime对象之间的时间间隔的,粒度是时、分、秒、纳秒。
举例:

  1. public static void main(String[] args) {
  2. LocalTime localTime = LocalTime.of(18, 20, 1);
  3. LocalTime localTime2 = LocalTime.of(19, 19, 19);
  4. Duration duration = Duration.between(localTime, localTime2);
  5. System.out.println(duration);
  6. // Duration区间是否为0
  7. System.out.println(duration.isZero());
  8. // Duration区间是否为负
  9. System.out.println(duration.isNegative());
  10. // Duration区间值的秒数
  11. System.out.println(duration.getSeconds());
  12. // Duration区间值的纳秒数
  13. System.out.println(duration.getNano());
  14. // Duration的度量单位
  15. System.out.println(duration.getUnits());
  16. // Duration区间相差几天
  17. System.out.println(duration.toDays());
  18. // Duration区间差几小时
  19. System.out.println(duration.toHours());
  20. // Duration区间相差几分钟
  21. System.out.println(duration.toMinutes());
  22. // Duration区间相差几毫秒
  23. System.out.println(duration.toMillis());
  24. }

结果:

  1. PT59M18S
  2. false
  3. false
  4. 3558
  5. 0
  6. [Seconds, Nanos]
  7. 0
  8. 0
  9. 59
  10. 3558000

说明:

  • duration.toMinutes()和duration.toMillis()方法得到的是时间间隔换算成分钟和秒,且取整数,比如相差5分钟10秒,则duration.toMinutes()返回的是5,duration.toMillis()方法返回的是(5 60 + 10) 1000 = 310000;
  • toProperty方法返回的值可以是正数,也可以是负数,如果是负数,说明Duration.between(localTime, localTime2)方法中,localTime比localTime2晚,即后面减去前面的。

    5.3.8.2 Period

    Period是LocalDateTime用来求两个LocalDateTime之间的时间间隔的,粒度为年、月、日。
    举例:

    1. public static void main(String[] args) {
    2. LocalDate localDate = LocalDate.of(2021, 11, 2);
    3. LocalDate localDate2 = LocalDate.of(2020, 12, 12);
    4. Period period = Period.between(localDate, localDate2);
    5. System.out.println(period);
    6. // 区间是否为0
    7. System.out.println(period.isZero());
    8. // 区间是否为为负
    9. System.out.println(period.isNegative());
    10. // 区间的相差几年
    11. System.out.println(period.getYears());
    12. // 区间的相差几月
    13. System.out.println(period.getMonths());
    14. // 区间的相差几日
    15. System.out.println(period.getDays());
    16. // 区间相差多少个月
    17. System.out.println(period.toTotalMonths());
    18. }

    结果:

    P-10M-21D
    false
    true
    0
    -10
    -21
    -10
    

    说明:

  • period.getDays()方法是个大坑,求的是当月相差的天数,并不是一共差多少天。

    5.3.9 ChronoUnit

    ChronoUnit是用来表示时间单位的,是一个枚举类,但同时提供了两个非常有用的方法:between和plus。

    5.3.9.1 between

    between方法用来计算两个LocalDateTime对象的差值,比如差几年、几个月、几天等,上面不是说period.getDays()方法是个大坑嘛,统计具体时间差值还是用ChronoUnit的between方法。

    ChronoUnit.时间枚举类.between(localDateTime1, localDateTime2);
    

    举例:

    public static void main(String[] args) {
          LocalDateTime localDateTime = LocalDateTime.of(2021, 11, 2, 12, 34, 54);
          LocalDateTime localDateTime2 = LocalDateTime.of(2020, 12, 12, 2, 43, 5);
    
          long betweenYears = ChronoUnit.YEARS.between(localDateTime, localDateTime2);
          // 0
          System.out.println(betweenYears);
    
          long betweenMonths = ChronoUnit.MONTHS.between(localDateTime, localDateTime2);
          // -10
          System.out.println(betweenMonths);
    
          long betweenDays = ChronoUnit.DAYS.between(localDateTime, localDateTime2);
          // -325
          System.out.println(betweenDays);
    
          long betweenWeeks = ChronoUnit.WEEKS.between(localDateTime, localDateTime2);
          // -46
          System.out.println(betweenWeeks);
    
          long betweenHours = ChronoUnit.HOURS.between(localDateTime, localDateTime2);
          // -7809
          System.out.println(betweenHours);
    
          long betweenMinutes = ChronoUnit.MINUTES.between(localDateTime, localDateTime2);
          // -468591
          System.out.println(betweenMinutes);
    
          long betweenSeconds = ChronoUnit.SECONDS.between(localDateTime, localDateTime2);
          // -28115509
          System.out.println(betweenSeconds);
      }
    

    5.3.9.2 plus

    plus是ChronoUnit提供的加减特定时间的方法,跟LocalDateTime的minusMinutes和plusDays方法是一个功能,返回的也是LocalDateTime对象,不同的是ChronoUnit将加和减统一用一个方法plus完成,只需传入正数或者负数即可。

    LocalDateTime localDateTime1 = localDateTime.plus(1, ChronoUnit.YEARS)
    

    举例: ```java package com.Jerry.Date.LocalDateTime;

import java.time.LocalDate; import java.time.LocalDateTime; import java.time.Period; import java.time.temporal.ChronoUnit;

public class LDTMain { public static void main(String[] args) { LocalDateTime localDateTime = LocalDateTime.of(2021, 11, 2, 12, 34, 54);

    LocalDateTime plusYear = localDateTime.plus(1, ChronoUnit.YEARS);
    // 2022-11-02T12:34:54
    System.out.println(plusYear);

    LocalDateTime plusMonth = localDateTime.plus(-1, ChronoUnit.MONTHS);
    // 2021-10-02T12:34:54
    System.out.println(plusMonth);

    LocalDateTime plusDay = localDateTime.plus(1, ChronoUnit.DAYS);
    // 2021-11-03T12:34:54
    System.out.println(plusDay);

    LocalDateTime plusWeek = localDateTime.plus(1, ChronoUnit.WEEKS);
    // 2021-11-09T12:34:54
    System.out.println(plusWeek);

    LocalDateTime plusHour = localDateTime.plus(1, ChronoUnit.HOURS);
    // 2021-11-02T13:34:54
    System.out.println(plusHour);

    LocalDateTime plusMinute = localDateTime.plus(1, ChronoUnit.MINUTES);
    // 2021-11-02T12:35:54
    System.out.println(plusMinute);

    LocalDateTime plusSecond = localDateTime.plus(1, ChronoUnit.SECONDS);
    // 2021-11-02T12:34:55
    System.out.println(plusSecond);
}

}

<a name="FOLdv"></a>
# 6、Instant
Instant类在Java.time包下,Instant对象表示的就是在时间轴上的一个点,与时间戳一一对应。该类一般用于处理时间戳,并不直接处理具体的日期时间,因此用的不多。下面给出一些Instant类的常用方法:
```java
public static void main(String[] args) {
        // 获取当前Instant对象
        Instant instant = Instant.now();
        // 以ISO-8601输出
        System.out.println(instant);

        //将java.util.Date转换为Instant
        Instant instant1 = Instant.ofEpochMilli(new Date().getTime());
        System.out.println(instant1);

        //从字符串类型中创建Instant类型的时间
        Instant instant2 = Instant.parse("1995-10-23T10:12:35Z");
        System.out.println(instant2);
    }

至于Instant对象的加减和求时间间隔,可以用LocalDateTime去处理,不用Instant的方法。

参考

Java工具类——日期相关的类
Java 日期时间
2020 年,你还在使用 Java 中的 SimpleDateFormat 吗?
Java基础-Calendar类常用方法介绍
LocalDateTime用法大全
java8新特性 时间操作类 Duration Period和 LocalDateTime LocalDate LocalTimejava8在日常开发中使用LocalDate和LocalTime
Duration和Period的区别—通俗易懂