java日期系统

基本概念

日期

  指某一天,形如 2021-11-20 。

时间

  指 时分秒,形如:12:12:12 或者 2021-11-20 12:12:12。加上日期才能精确到某一时刻。

时间戳

  时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总毫秒数。

https://baike.baidu.com/item/%E6%97%B6%E9%97%B4%E6%88%B3/6439235?fr=aladdin

本地时间

  此地此时此刻的时间,比如说现在是北京时间2021-11-20 12:12:12。

时区

https://baike.baidu.com/item/%E6%97%B6%E5%8C%BA/491122?fr=aladdin

img

  • GMT

  全名是格林威治标准时间或格林威治平时 (Greenwich Mean Time),这个时间系统的概念在 1884 年确立,由英国伦敦的格林威治皇家天文台计算并维护,并在往后的几十年往欧陆其他国家扩散。在 1924 年开始,格林威治天文台每小时就会向全世界播报时间。在刚开始的几十年,GMT 的测量方法非常简单:观测者随时监控太阳在天空的位置,并且把每天太阳爬升到仰角最高的时候记录下来,这个时间点称呼为“过中天”。一般人对于一天 24 小时的理解,大致上就相等于两次太阳过中天的时间间隔。不过由于地球是以椭圆轨道绕着太阳,在轨道上的行进速率不一,导致一年之中会有“比较长的一天”与“比较短的一天”,所以格林威治的观测者必须要至少连续观测一年,然后求取 365 个长度不一的“天”,再把他们全部平均后,得到固定的一天长度,之后再细分成时、分、秒等单位。这个就是 GMT。GMT 12:00 就是指的是英国伦敦郊区的皇家格林尼治天文台当地的中午12:00,而GMT+8 12:00,则是指的东八区的北京当地时间的12:00。

  • UTC

  自从 1967 年国际度量衡大会把秒的定义改成铯原子进行固定震荡次数的时间后,时间的测量就可以与星球的自转脱节了。只利用原子钟计算时间与日期的系统,称作国际原子时 (International Atomic Time),这是一种只有“天”的系统,时分秒都以“天”的小数点零头来表示。以国际原子时为计算基准,把时间格式与 UT1 对齐,让一般人都方便使用的时间系统,就叫做协调世界时 (Universal Time Coordinated),也就是 UTC。这也就是 UTC 为什么与 GMT 几乎一样的关係。由于 UTC 直接与国际度量衡标准相联繫,所以目前所有的国际通讯系统,像是卫星、航空、GPS 等等,全部都协议採用 UTC 时间。协调世界时,又称世界统一时间、世界标准时间、国际协调时间。协调世界时,即以我为基准,向我看齐的意思。(英语:Coordinated Universal Time,法语:Temps Universel Coordonné,简称UTC)是最主要的世界时间标准,由于英文(CUT)和法文(TUC)的缩写不同,作为妥协,简称UTC。

  • CST

​ 表示China Standard Time,也就是中国标准时间。但是CST也可以表示美国中部时间Central Standard Time USA,因此,缩写容易产生混淆,我们尽量不要使用缩写。

夏令时

  所谓夏令时,就是夏天开始的时候,把时间往后拨1小时,夏天结束的时候,再把时间往前拨1小时。我们国家实行过一段时间夏令时,1992年就废除了,但是美国人还在使用,所以时间换算更加复杂。因为涉及到夏令时,相同的时区,如果表示的方式不同,转换出的时间是不同的。我们举个栗子:

  对于2019-11-20和2019-6-20两个日期来说,假设北京人在纽约:

  • 如果以GMT或者UTC作为时区,无论日期是多少,时间都是19:00
  • 如果以国家/城市表示,例如America/NewYork,虽然纽约也在西五区,但是,因为夏令时的存在,在不同的日期,GMT时间和纽约时间可能是不一样的:
时区2019-11-202019-6-20
GMT-05:0019:0019:00
UTC-05:0019:0019:00
America/New_York19:0020:00

实行夏令时的不同地区,进入和退出夏令时的时间很可能是不同的。同一个地区,根据历史上是否实行过夏令时,标准时间在不同年份换算成当地时间也是不同的。因此,计算夏令时,没有统一的公式,必须按照一组给定的规则来算,并且,该规则要定期更新。

本地化

  在计算机中,通常使用Locale表示一个国家或地区的日期、时间、数字、货币等格式。Locale语言_国家的字母缩写构成,例如,zh_CN表示中文+中国,en_US表示英文+美国。语言使用小写,国家使用大写。

对于日期来说,不同的Locale,例如,中国和美国的表示方式如下:

  • zh_CN:2016-11-30
  • en_US:11/30/2016

计算机用Locale在日期、时间、货币和字符串之间进行转换。一个电商网站会根据用户所在的Locale对用户显示如下:

中国用户美国用户
购买价格12000.0012,000.00
购买日期2016-11-3011/30/2016

参考

星期

周一周二周三周四周五周六周日
MonTueWedThuFriSatSun

月份

一月二月三月四月五月六月七月八月九月十月十一月十二月
JanFebMarAprMayJunJulAugSepOctNovDec

常用工具

Date

自jdk1.0开始,位于java.util包,可用来表示时间特定的时间。

创建指定时间(已过时)

创建一个 2020-11-22 12:20:30 的时间

  • Date(int year, int month, int date, int hrs, int min, int sec)

    注意

    year – the year minus 1900.
    month – the month between 0-11.
    date – the day of the month between 1-31.
    hrs – the hours between 0-23.
    min – the minutes between 0-59.
    sec – the seconds between 0-59.


Date date = new Date(121, 10, 30, 12, 20, 20);
System.out.println(date);

  • Date(int year, int month, int date, int hrs, int min)
  • Date(int year, int month, int date)
  • Date(String s)

  参数同上,很不好用。

Date date = new Date("Sun Nov 22 12:20:30 GMT 2020");
System.out.println(date);

  可以看到输出的时间不一致,这是因为我们使用的是GMT格林尼治时间,输出的是GMT北京时间,相差八个小时。但是这样写这样不符合我们的习惯,我们也可以传入yyyy/MM/dd HH:mm:ss 格式字符串:

Date date = new Date("2020/11/22 12:20:30");
System.out.println(date);

创建当前时间

  在平时的项目开发过程中,我们使用的最多的并不是创建一个指定时间,而是获取当前一个时间。

  • Date()
Date date = new Date();
System.out.println(date);

Fri:星期

Nov:月份

19:天

10:26:22 :时间

CST :时区 ,这里表示北京时间

2021:年份

  • Date(long date)

我们也可以传入当前时间戳(可以为负数)去获取当前时间Date对象:

Date date = new Date(System.currentTimeMillis());
System.out.println(date);

System.currentTimeMillis():获取当前时间的时间戳,我们也可以通过Date的getTime()方法返回时间戳。

	   long l = System.currentTimeMillis();
        Date date = new Date(l);
        System.out.println(date);
        long time = date.getTime();
        System.out.println(l);
        System.out.println(time);

获取年、月、日、时、分、秒(已过时)

        Date date = new Date(System.currentTimeMillis());
        int year = date.getYear()+1900;
	    //月 0-11 表示1-12月
        int month = date.getMonth()+1;
        //日
        int day = date.getDate();
        int hours = date.getHours();
        int minutes = date.getMinutes();
        int seconds = date.getSeconds();
        //周几 0-6   0表示周日  1表示周一
        int week = date.getDay();
        System.out.println(String.format("时间是:%s年%s月%s日%s点%s分%s秒 周%s",year,month,day,hours,minutes,seconds,week));

  同时,年月日时分秒也提供了相应的set方法进行修改,但也已经过时不提倡使用。

格式化输出时间

  • toLocaleString():String: 已弃用
  • DateFormat类:
Date date = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(simpleDateFormat.format(date));
LetterDate or Time ComponentPresentationExamples
GEra designatorTextAD
yYearYear1996; 96
YWeek yearYear2009; 09
MMonth in year (context sensitive)MonthJuly; Jul; 07
LMonth in year (standalone form)MonthJuly; Jul; 07
wWeek in yearNumber27
WWeek in monthNumber2
DDay in yearNumber189
dDay in monthNumber10
FDay of week in monthNumber2
EDay name in weekTextTuesday; Tue
uDay number of week (1 = Monday, …, 7 = Sunday)Number1
aAm/pm markerTextPM
HHour in day (0-23)Number0
kHour in day (1-24)Number24
KHour in am/pm (0-11)Number0
hHour in am/pm (1-12)Number12
mMinute in hourNumber30
sSecond in minuteNumber55
SMillisecondNumber978
zTime zoneGeneral time zonePacific Standard Time; PST; GMT-08:00
ZTime zoneRFC 822 time zone-0800
XTime zoneISO 8601 time zone-08; -0800; -08:00

我们也可以通过parse()方法去解析一个时间字符串:

        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println(simpleDateFormat.parse("2021-11-19 16:11:53"));

其他方法

  这里介绍常用的一些方法,其他的过时方法不建议使用,这里就不过多介绍。

  • after(Date when):boolean :测试此日期是否在指定日期之后。

    Date date1 = new Date("2020/11/22 12:20:31");
    Date date2 = new Date("2020/11/22 12:20:30");
    //date1是否在date2之后
    boolean after = date1.after(date2);
    System.out.println(after);
    
  • before(Date when):boolean :测试此日期是否在指定日期之前。

    Date date1 = new Date("2020/11/22 12:20:31");
    Date date2 = new Date("2020/11/22 12:20:30");
     //date1是否在date2之前
    boolean before = date1.before(date2);
    System.out.println(before);
    
  • compareTo(Date anotherDate):int :比较两个日期,如果参数Date等于此Date,则值为0 ; 如果此日期在Date参数之前,该值小于0(-1) ; 如果此日期在Date参数0则值大于0(1) 。

    Date date1 = new Date("2020/11/22 12:20:30");
    Date date2 = new Date("2020/11/22 12:20:31");
    int compare = date1.compareTo(date2);
    System.out.println(compare);
    
  • equals(Object obj):boolean:重写了Object的equals,比较两个时间点是否相等,如果相等则返回true,否则false。

Calendar

  可以看到Date类中的很多方法不符合人们使用的习惯,并且已经过时,因此从jdk1.1开始,出现了java.util.Calendar类替代了Date的很多功能。Calendar直译为日历,他是一个抽象类,因此我们通过getInstance() 获取他的子类GregorianCalendar进行操作。

  在Calendar中已经为我们定义好了月和周对应的值:

  • 星期:1-7 表示 周日-周一
/**
 * Value of the {@link #DAY_OF_WEEK} field indicating
 * Sunday.
 */
public final static int SUNDAY = 1;

/**
 * Value of the {@link #DAY_OF_WEEK} field indicating
 * Monday.
 */
public final static int MONDAY = 2;

/**
 * Value of the {@link #DAY_OF_WEEK} field indicating
 * Tuesday.
 */
public final static int TUESDAY = 3;

/**
 * Value of the {@link #DAY_OF_WEEK} field indicating
 * Wednesday.
 */
public final static int WEDNESDAY = 4;

/**
 * Value of the {@link #DAY_OF_WEEK} field indicating
 * Thursday.
 */
public final static int THURSDAY = 5;

/**
 * Value of the {@link #DAY_OF_WEEK} field indicating
 * Friday.
 */
public final static int FRIDAY = 6;

/**
 * Value of the {@link #DAY_OF_WEEK} field indicating
 * Saturday.
 */
public final static int SATURDAY = 7;
  • 月份:0-11表示1-12月
/**
 * Value of the {@link #MONTH} field indicating the
 * first month of the year in the Gregorian and Julian calendars.
 */
public final static int JANUARY = 0;

/**
 * Value of the {@link #MONTH} field indicating the
 * second month of the year in the Gregorian and Julian calendars.
 */
public final static int FEBRUARY = 1;

/**
 * Value of the {@link #MONTH} field indicating the
 * third month of the year in the Gregorian and Julian calendars.
 */
public final static int MARCH = 2;

/**
 * Value of the {@link #MONTH} field indicating the
 * fourth month of the year in the Gregorian and Julian calendars.
 */
public final static int APRIL = 3;

/**
 * Value of the {@link #MONTH} field indicating the
 * fifth month of the year in the Gregorian and Julian calendars.
 */
public final static int MAY = 4;

/**
 * Value of the {@link #MONTH} field indicating the
 * sixth month of the year in the Gregorian and Julian calendars.
 */
public final static int JUNE = 5;

/**
 * Value of the {@link #MONTH} field indicating the
 * seventh month of the year in the Gregorian and Julian calendars.
 */
public final static int JULY = 6;

/**
 * Value of the {@link #MONTH} field indicating the
 * eighth month of the year in the Gregorian and Julian calendars.
 */
public final static int AUGUST = 7;

/**
 * Value of the {@link #MONTH} field indicating the
 * ninth month of the year in the Gregorian and Julian calendars.
 */
public final static int SEPTEMBER = 8;

/**
 * Value of the {@link #MONTH} field indicating the
 * tenth month of the year in the Gregorian and Julian calendars.
 */
public final static int OCTOBER = 9;

/**
 * Value of the {@link #MONTH} field indicating the
 * eleventh month of the year in the Gregorian and Julian calendars.
 */
public final static int NOVEMBER = 10;

/**
 * Value of the {@link #MONTH} field indicating the
 * twelfth month of the year in the Gregorian and Julian calendars.
 */
public final static int DECEMBER = 11;

/**
 * Value of the {@link #MONTH} field indicating the
 * thirteenth month of the year. Although <code>GregorianCalendar</code>
 * does not use this value, lunar calendars do.
 */
public final static int UNDECIMBER = 12;

获取时间

  • getInstance():Calendar:获取当前时间对象 。

  • get(int field):int:返回给定日历字段的值。

    		//创建当前时间对象
            Calendar instance = Calendar.getInstance();
            //年
            System.out.println(instance.get(Calendar.YEAR));
            //月 0-11 表示 1-12月
            System.out.println(instance.get(Calendar.MONTH));
            //日
            System.out.println(instance.get(Calendar.DATE));
            System.out.println(instance.get(Calendar.DAY_OF_MONTH));
            //时 12小时制
            System.out.println(instance.get(Calendar.HOUR));
            //时 24小时制
            System.out.println(instance.get(Calendar.HOUR_OF_DAY));
            //1-7 表示 周日 到 周一
            System.out.println(instance.get(Calendar.DAY_OF_WEEK));
    

    Calendar中常量意义:

    属性含义
    ERA0时代,例如在儒略历中的AD或BC
    YEAR1
    MONTH2月,0-11 表示 1-12月
    WEEK_OF_YEAR3本年度的周数
    WEEK_OF_MONTH4当月的周数
    DATE5
    DAY_OF_MONTH5
    DAY_OF_YEAR6一年中的第几天
    DAY_OF_WEEK7一周中的第几天(周日为第一天)
    AM_PM9中午前还是中午后
    HOUR10时(12小时制)
    HOUR_OF_DAY11时(24小时制)
    MINUTE12分(0-59)
    SECOND13秒(0-59)
    MILLISECOND14毫秒
    ZONE_OFFSET15指示与GMT的原始偏移量(以毫秒为单位)
    DST_OFFSET16用于表示夏令时偏移量(以毫秒为单位)

设置时间

  • set(int year, int month, int date, int hourOfDay, int minute, int second) :设置日历字段中的值 YEARMONTHDAY_OF_MONTHHOUR_OF_DAYMINUTE

注意

​ 如果不set时分秒,则时分秒为当前值。

 //创建当前时间对象
  Calendar instance = Calendar.getInstance();
//设置时间为:2020/11/22 12:20:30
  instance.set(2020, Calendar.NOVEMBER,22,12,20,30);
  System.out.printf("%s年%s月%s日 %s时%s分%s秒%n"
                          ,instance.get(Calendar.YEAR)
                          ,instance.get(Calendar.MONTH)+1
                          ,instance.get(Calendar.DAY_OF_MONTH)
                          ,instance.get(Calendar.HOUR_OF_DAY)
                          ,instance.get(Calendar.MINUTE)
                          ,instance.get(Calendar.SECOND));
  • set(int field, int value) :将给定的日历字段设置为给定的值
 //创建当前时间对象
  Calendar instance = Calendar.getInstance();
//设置时间为:2020/11/22 
  instance.set(Calendar.YEAR,2020);
  instance.set(Calendar.MONTH,Calendar.NOVEMBER);
  instance.set(Calendar.DAY_OF_MONTH,22);
  System.out.printf("%s年%s月%s日 %s时%s分%s秒%n"
                          ,instance.get(Calendar.YEAR)
                          ,instance.get(Calendar.MONTH)+1
                          ,instance.get(Calendar.DAY_OF_MONTH)
                          ,instance.get(Calendar.HOUR_OF_DAY)
                          ,instance.get(Calendar.MINUTE)
                          ,instance.get(Calendar.SECOND));

计算时间

  • add(int field, int amount) :根据日历的规则,将指定的时间量添加或减去给定的日历字段。
		Calendar instance = Calendar.getInstance();
        System.out.printf("%s年%s月%s日 %s时%s分%s秒%n"
                ,instance.get(Calendar.YEAR)
                ,instance.get(Calendar.MONTH)+1
                ,instance.get(Calendar.DAY_OF_MONTH)
                ,instance.get(Calendar.HOUR_OF_DAY)
                ,instance.get(Calendar.MINUTE)
                ,instance.get(Calendar.SECOND));
        instance.add(Calendar.YEAR,1);
        instance.add(Calendar.MONTH,1);
        instance.add(Calendar.DAY_OF_MONTH,1);
        System.out.printf("%s年%s月%s日 %s时%s分%s秒%n"
                ,instance.get(Calendar.YEAR)
                ,instance.get(Calendar.MONTH)+1
                ,instance.get(Calendar.DAY_OF_MONTH)
                ,instance.get(Calendar.HOUR_OF_DAY)
                ,instance.get(Calendar.MINUTE)
                ,instance.get(Calendar.SECOND));
        instance.add(Calendar.YEAR,-2);
        instance.add(Calendar.MONTH,-2);
        instance.add(Calendar.DAY_OF_MONTH,-2);
        System.out.printf("%s年%s月%s日 %s时%s分%s秒%n"
                ,instance.get(Calendar.YEAR)
                ,instance.get(Calendar.MONTH)+1
                ,instance.get(Calendar.DAY_OF_MONTH)
                ,instance.get(Calendar.HOUR_OF_DAY)
                ,instance.get(Calendar.MINUTE)
                ,instance.get(Calendar.SECOND));
  • roll(int field, boolean up) :在给定时间字段上添加或减少单个时间单位,而不改变较大的字段。

    ​ 与add类似,不同的是改变的时间单位不会影响到上一级,比如天数增加并不会使月份增加,月份增加并不会使年份增加。如下:月份的增加超过一年,但是年份并没有跟着增加

     Calendar instance = Calendar.getInstance();
            System.out.printf("%s年%s月%s日 %s时%s分%s秒%n"
                    ,instance.get(Calendar.YEAR)
                    ,instance.get(Calendar.MONTH)+1
                    ,instance.get(Calendar.DAY_OF_MONTH)
                    ,instance.get(Calendar.HOUR_OF_DAY)
                    ,instance.get(Calendar.MINUTE)
                    ,instance.get(Calendar.SECOND));
            instance.roll(Calendar.MONTH,13);
            System.out.printf("%s年%s月%s日 %s时%s分%s秒%n"
                    ,instance.get(Calendar.YEAR)
                    ,instance.get(Calendar.MONTH)+1
                    ,instance.get(Calendar.DAY_OF_MONTH)
                    ,instance.get(Calendar.HOUR_OF_DAY)
                    ,instance.get(Calendar.MINUTE)
                    ,instance.get(Calendar.SECOND));
    

Date与Calendar的转换

  • getTime():Date:将其转换成Date对象 。
  • setTime(Date date) :使用给定的 Date设置此日历的时间。

上面格式化时间太过麻烦,我们可以把Calendar转成Date,然后使用SimpleDateFormat进行格式化输出:

		Calendar instance = Calendar.getInstance();
        Date time = instance.getTime();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println(simpleDateFormat.format(time));

JDK1.8新特性

  java8中新加的时系统都在java.time.* 包下,为什么1.8要出新的时间系统?

旧时间系统缺陷:

  • 设计不合理,易用性差

  • 非线程安全,无法适应并发需求

java8新日期时间系统:

  • 线程安全

  • 贴近实战,功能强大,易用性好

LocalDate

  LocalDate是一个不可变的,线程安全的类,它用来表示日期,包含年月日信息,同时也可以获取到星期几的信息,它不存储或者表示时间与时区。其常见使用方法如下:

创建日期

  • now() :从指定的时钟获取当前日期。
LocalDate localDate = LocalDate.now();
System.out.println(localDate);
  • of(int year, Month month, int dayOfMonth) :从一年,一个月和一天获得一个 LocalDate的实例。
LocalDate date = LocalDate.of(2021, Month.OCTOBER,31);
System.out.println(date);

获取日期分量

  • get(TemporalField field) :从此日期获取指定字段的 int
  • getYear() :获取年份字段。
  • getMonth() :使用 Month枚举获取月份字段。
  • getMonthValue() :将月份字段从1到12。
  • getDayOfMonth() :获取多少号。
  • getDayOfWeek() :获取星期几字段,这是一个枚举 DayOfWeek
  • getDayOfYear() :这一年的第几天。
  • lengthOfMonth():表示的月份的长度。
  • lengthOfYear() :表示的年份的长度。
  • isLeapYear() :根据ISO培训日历系统规则,检查年份是否是闰年。
  • getEra() :CE 表示公元元年或之后 BCE 表示公元前。
  • toEpochDay() :将此日期转换为大纪元日。 即计算与1970.01.01 相差的天数。
  • getLong(TemporalField field) :从此日期获取指定字段的值为 long 。 与get 一样,不过ChronoField.EPOCH_DAYChronoField.PROLEPTIC_MONTH(公元前1年 到现在的月数)只能用long返回。
  • range(TemporalField field) :获取指定字段的有效值的范围。 如月份为 1-12.
//年
System.out.println(localDate.getYear());
System.out.println(localDate.get(ChronoField.YEAR));
//月 1-12 表示1-12月
System.out.println(localDate.getMonth());
System.out.println(localDate.getMonthValue());
System.out.println(localDate.get(ChronoField.MONTH_OF_YEAR));
//日
System.out.println(localDate.getDayOfMonth());
System.out.println(localDate.get(ChronoField.DAY_OF_MONTH));
//周 1-7 表示周一-周日
System.out.println(localDate.getDayOfWeek());
System.out.println(localDate.getDayOfWeek().getValue());
System.out.println(localDate.get(ChronoField.DAY_OF_WEEK));

//把这个月的第一天按周一算  此时应是星期几
System.out.println(localDate.get(ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH));

//这一年年的第几天
System.out.println(localDate.getDayOfYear());
//这个月多少天
System.out.println(localDate.lengthOfMonth());
//这一年多少天
System.out.println(localDate.lengthOfYear());
//是否是闰年
System.out.println(localDate.isLeapYear());

//CE 表示公元元年或之后  BCE 表示公元前
System.out.println(localDate.getEra());

//计算与1970.01.01 相差的天数
System.out.println(localDate.toEpochDay());

//纪年法  ISO  表示公历纪年法 现在的国际标准
System.out.println(localDate.getChronology());

//getLong 与get 一样,不过返回的是long,下面两个只能用long返回
System.out.println(localDate.getLong(ChronoField.EPOCH_DAY));

//公元前1年 到现在的月数
System.out.println(localDate.getLong(ChronoField.PROLEPTIC_MONTH));

//获取LocalDate所能表示年份的范围
System.out.println(date.range(ChronoField.YEAR));
System.out.println(date.range(ChronoField.MONTH_OF_YEAR));
System.out.println(date.range(ChronoField.DAY_OF_MONTH));

  有些字段属性LocalDateget()方法是不支持的,如ChronoField.MILLI_OF_DAY表示这一天的多少毫秒,然而LocalDate的时间只精确到天数,因此如下获取会报错:

//LocalDate 不支持,因为LocalDate指支持到天,MILLI_OF_DAY是获取这一天的多少毫秒
System.out.println(date.get(ChronoField.MILLI_OF_DAY));
Exception in thread "main" java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: MilliOfDay
	at java.time.LocalDate.get0(LocalDate.java:680)
	at java.time.LocalDate.get(LocalDate.java:622)
	at com.transsion.date.Main.main(Main.java:19)

  我们可以通过isSupported()方法去判断是否支持这个字段属性:

  • isSupported(TemporalField field) :检查指定的字段是否受支持。
//可以通过isSupported测试是否支持这个枚举分量
System.out.println(date.isSupported(ChronoField.MILLI_OF_DAY));

修改日期分量

  • withMonth(int month) :返回这个日期的副本,并更改年月日。

  • withYear(int year) :返回此日期的副本,并更改年份。

  • withDayOfMonth(int dayOfMonth) :修改为多少号。

  • withDayOfYear(int dayOfYear) :修改为这一点的第多少天。

  • with(TemporalField field, long newValue) :返回此日期的副本,并将指定的字段设置为新值。

LocalDate now = LocalDate.now();
//修改月
LocalDate withMonth = now.withMonth(10);
//原来对象未修改,应使用新生成的对象
System.out.println(now);
System.out.println(withMonth);

//修改日为月的第几天
LocalDate withDayOfMonth = now.withDayOfMonth(10);
System.out.println(withDayOfMonth);

//修改日为年的第几天
LocalDate withDayOfYear = now.withDayOfYear(10);
System.out.println(withDayOfYear);

//通过with修改
LocalDate with = now.with(ChronoField.MONTH_OF_YEAR, 1);
System.out.println(with);
  • with(TemporalAdjuster adjuster) :返回此日期的调整副本。
//with转换其他历法为公历
//伊斯兰历法转换为公历。如果是公历转公历 则直接返回
HijrahDate hijrahDate = HijrahDate.of(1400,5,5);
LocalDate withHijrahDate = now.with(hijrahDate);
System.out.println(withHijrahDate);

计算日期

  • plusYears(long yearsToAdd) :加几年,如果为负数则减几年。
//增加年   负数则为减几年
LocalDate plusYears = now.plusYears(1);
System.out.println(plusYears);

同理可以对日、周、月进行计算。

  • plus(long amountToAdd, TemporalUnit unit) :加/减指定单位的数量。
//加一年
System.out.println(now.plus(1, ChronoUnit.YEARS));
  • plus(TemporalAmount amountToAdd) :返回此日期的副本,并添加指定的金额。
//表示一段时间
Period period = Period.of(1,1,1);
//增加一年一月一天
System.out.println(now.plus(period));

计算日期差

  • until(Temporal endExclusive, TemporalUnit unit):根据指定的单位计算直到另一个日期的时间量。

  • until(ChronoLocalDate endDateExclusive) :将此日期和其他日期之间的期间计算为 Period

    //--------计算日期差
    System.out.println(now.until(date, ChronoUnit.MONTHS));
    //返回Period  表示一段时间
    System.out.println(now.until(date));
    

日期比较

  • equals(Object obj) :检查这个日期是否等于另一个日期。
  • isEqual(ChronoLocalDate other) :检查此日期是否等于指定的日期。
  • isAfter(ChronoLocalDate other) :检查此日期是否在指定日期之后。
  • isBefore(ChronoLocalDate other) :检查此日期是否在指定日期之前。
  • compareTo(ChronoLocalDate other) :将此日期与另一个日期进行比较, 负数表示早于 0 表示相等 正数表示晚于。
System.out.println(now.equals(date));
System.out.println(now.isEqual(date));
System.out.println(now.isBefore(date));
System.out.println(now.isAfter(date));
System.out.println(now.compareTo(date));

其他日期表示

  • MonthDay表示月日,比如一些节日日期可以用它来表示,比如10月1日。
  • YearMonth表示年月,比如2021年11月。

//11月20日
MonthDay birthday = MonthDay.of(11,20);
//今天
MonthDay today = MonthDay.from(now);
//比较
System.out.println(birthday.compareTo(today));
//指定年份
LocalDate ld = birthday.atYear(2021);
System.out.println(ld);
//--------------年月
YearMonth yearMonth = YearMonth.of(2021,11);
YearMonth thisMonth = YearMonth.from(now);
System.out.println(yearMonth);
System.out.println(thisMonth);

LocalTime

  LocalTime是一个不可变的日期时间对象,代表一个时间,通常被看作是时:分:秒。时间表示为纳秒精度。例如,值“13:45:30.123456789”可以存储在LocalTime ,其常用的方法月LocalDate类似,这里不多说,值介绍一些特有的常用方法。

创建时间

  • ofSecondOfDay(long secondOfDay) :这一天的第1000秒。
        //当前时间
        LocalTime localTime = LocalTime.now();
        System.out.println(localTime);
        //时分秒 纳秒 一秒的十亿分之一
        System.out.println(LocalTime.of(19, 20, 20,1));
         //这一天的第1000秒
        System.out.println(LocalTime.ofSecondOfDay(1000));
        //中午的时间
        System.out.println(LocalTime.NOON);
        //最晚的时间
        System.out.println(LocalTime.MAX);
        //最早的时间
        System.out.println(LocalTime.MIN);
        //午夜
        System.out.println(LocalTime.MIDNIGHT);

获取时间分量

//----------获取时分秒 纳秒
System.out.println(localTime.getHour());
System.out.println(localTime.getMinute());
System.out.println(localTime.getSecond());
System.out.println(localTime.getNano());
//在这一天的第多少秒
System.out.println(localTime.toSecondOfDay());
//在这一天的第多少纳秒
System.out.println(localTime.toNanoOfDay());
//通过get方法
System.out.println(localTime.get(ChronoField.MILLI_OF_DAY));

时间比较与计算

  方法与LocalDate都类似。

  • truncatedTo(TemporalUnit unit) :舍去零头
        //舍去零头
        System.out.println(localTime.truncatedTo(ChronoUnit.MINUTES));

LocalDateTime

  LocalDateTime是一个不可变的日期时间对象,代表日期时间,通常被视为年 - 月 - 日 - 时 - 分 - 秒。 也可以访问其他日期和时间字段,例如日期,星期几和星期。 时间表示为纳秒精度。 例如,值“2007年10月2日在13:45:30.123456789”可以存储在LocalDateTime

  其常用方法与上述类似,其实是由LocalDateLocalTime组成,因此可以获取到LocalDateLocalTime对象。

LocalDateTime now = LocalDateTime.now();
System.out.println(now.toLocalDate());
System.out.println(now.toLocalTime());

  需要注意的是:toEpochDaytoEpochDaytoSecondOfDay在LocalDateTime等方法在LocalDateTime中不存在,是无法使用的。truncatedTo最多清楚零头到天。

Period

  Period表示以年月日为计时单位的时间段,无法精确到时分秒,它可以通过构造和计算两种方式获得。年月日在Period中分别以三个私有计数器进行表示,且年月日三个计数器的数值之间不能够自动互相转换,因此一个Period对象是无法指出包含了多少天的。

创建时间段

  • of(int years, int months, int days) :获得 Period多年,几个月和几天的Period。
  • ofDays(int days) :获得 Period代表天数.
  • ofMonths(int months) :月数。
  • ofWeeks(int weeks) :几周。
  • ofYears(int years) :几年。
  • parse(CharSequence text) :从一个文本字符串获取一个 Period ,如 PnYnMnD
  • between(LocalDate startDateInclusive, LocalDate endDateExclusive) :获得一个 Period ,由两个日期之间的年数,月份和日期组成。

获取分量

  • get(TemporalUnit unit) :获取所请求单元的值。
  • getUnits() :取此期间支持的单位集。
  • getDays() :获得此期间的天数。
  • getMonths() :获取此期间的月数。
  • getYears() :获得这段时间的年数。

修改时间段

  • withDays(int days) :以指定的天数返回此期间的副本。
  • withMonths(int months) :以指定的月数返回此期间的副本。
  • withYears(int years) :以指定的年数返回此期间的副本。

计算时间段

  • plus(TemporalAmount amountToAdd) :返回指定期间添加的此期间的副本。
  • plusDays(long daysToAdd): 返回添加指定日期的此期间的副本。
  • plusMonths(long monthsToAdd) :返回添加指定月份的此期间的副本。
  • plusYears(long yearsToAdd) :返回添加指定年份的此期间的副本。
  • minus(TemporalAmount amountToSubtract) :返回此期间的副本,并减去指定的时间段。
  • minusDays(long daysToSubtract) :返回此期间的副本,并减去指定的天数。
  • minusMonths(long monthsToSubtract) :返回此期间的副本,减去指定的月份。
  • minusYears(long yearsToSubtract) :返回此期间的副本,并减去指定的年数。
  • multipliedBy(int scalar) :返回一个新的实例,该时间段中的每个元素乘以指定的标量。 年月日乘scalar倍。
  • normalized() :规范化时间段格式。月进年,天数不变。
  • negated() :反转各计数器的值。正变负,负变正。
  • addTo(Temporal temporal) :将此时段添加到指定的时间对象。
  • subtractFrom(Temporal temporal) :从指定的时间对象中减去这个时间段。

Duration

  Duration表示一个时间段以时分秒纳秒计,他只有秒和纳秒两个计数器,没有时和分。纳秒计数器不会出现负数,如果创建对象的时候纳秒传参为负数,则会像秒计数器借1秒,将纳秒修正为正数。

创建时间段

  • ofSeconds(long seconds) :创建一个时间段,以秒计。
  • ofSeconds(long seconds, long nanoAdjustment) :创建一个时间段,以秒和纳秒计。
  • ofNanos(long nanos) :创建一个时间段,以纳秒计。
  • ofMinutes(long minutes) :创建一个时间段,以秒分钟。
  • ofHours(long hours) :创建一个时间段,以小时计。
  • ofDays(long days) :创建一个时间段,以天计。
  • of(long amount, TemporalUnit unit) :获得定单位的时间段 。
  • between(Temporal startInclusive, Temporal endExclusive) :获取一个 Duration表示两个时间对象之间的持续时间。 可以以LocalDateTimeLocalTime为对象进行计算,不能以LocalDate为对象进行计算。
  • parse(CharSequence text) :从一个文本字符串获得一个 Duration ,如 PnDTnHnMn.nS

获取分量

  • getNano() :获取纳秒计数器的值。
  • getSeconds() :获取秒计数器的值。
  • get(TemporalUnit unit) :获取指定计数器的值。
  • getUnits() :支持的单位。
  • toDays() :计算时间段共多少天。
  • toHours() :计算时间段共多少小时。

修改时间段

  with方法都直接修改计数器的值,不会发生进位/退位。

  • withNanos(int nanoOfSecond) :修改纳秒计数器的值。0-999999999,如果设置为负数报错。
  • withSeconds(long seconds) :修改秒计算器的值。

计算时间段

  plus方法可以发生进位/退位。

  • plus(Duration duration) :返回此持续时间的副本,并添加指定的持续时间。
  • plus(long amountToAdd, TemporalUnit unit) :返回此持续时间的副本,并添加指定的持续时间。
  • plusDays(long daysToAdd) :给时间段增加指定的天数。
  • plusHours(long hoursToAdd) :给时间段增加指定的小时数。
  • plusMillis(long millisToAdd) :给时间段增加指定的毫秒数。
  • plusMinutes(long minutesToAdd) :给时间段增加指定的分钟数。
  • plusNanos(long nanosToAdd) :给时间段增加指定的纳秒数。
  • plusSeconds(long secondsToAdd) :给时间段增加指定的秒数。
  • subtractFrom(Temporal temporal):从指定的时间对象中减去此持续时间。
  • minus(Duration duration) :返回指定持续时间的副本减去。
  • minus(long amountToSubtract, TemporalUnit unit) :返回指定持续时间的副本减去。
  • minusDays(long daysToSubtract) :给时间段减少指定的天数。
  • minusHours(long hoursToSubtract) :给时间段减少指定的小时数。
  • minusMillis(long millisToSubtract) :给时间段减少指定的毫秒数。
  • minusMinutes(long minutesToSubtract) :给时间段减少指定的分钟数。
  • minusNanos(long nanosToSubtract) :给时间段减少指定的纳秒数。
  • minusSeconds(long secondsToSubtract) :给时间段减少指定的秒数。
  • multipliedBy(long multiplicand) :返回此持续时间的副本乘以标量。
  • dividedBy(long divisor) :返回此持续时间的副本除以指定的值。
  • abs() :求时间段的绝对值。
  • negated() :求时间段的相反数。

其他方法

  • isNegative() :检查此持续时间是否为负,不包括零。
  • isZero() :检查此持续时间是否为零长度。
  • equals(Object otherDuration) :检查此持续时间是否等于指定的 Duration
  • compareTo(Duration otherDuration) :返回值:-1表示短, 0 表示相等 ,1表示长。
  • addTo(Temporal temporal):为LocalTime或者LocalDateTime增加一段时间,不能与LocalDate进行运算。

ZoneId

  ZoneId是一个抽象类,它表示地区与时差,他有两个子类分别是ZoneRegionZoneOffset

  • ZoneRegion表示城市或地区,并以城市或地区反映时区。
  • ZoneOffset表示时差,特指与格林尼治时间的偏差。

ZoneRegion

  它不是一个公有的类,只能通过ZoneId去创建。

  • systemDefault() :获取系统默认时区。
ZoneId zoneId = ZoneId.systemDefault();
System.out.println(zoneId.getClass());
System.out.println(zoneId);
class java.time.ZoneRegion
Asia/Shanghai

Process finished with exit code 0

  可以看到,通过systemDefault()方法创建的时区对象是他的子类ZoneRegionAsia/Shanghai表示 亚洲/上海,由于JDK8自己定义了一套时区标志性城市,所以和系统显示的不一样,我们只需要知道,不管是系统显示的还是JDK8中定义的都是表示中国的时区,实际含义是一样的就行。

  • normalized() :标准化输出时区,与直接输出一样。
  • getRules():获取时区信息。
ZoneId zoneId = ZoneId.systemDefault();
System.out.println(zoneId.getRules());
ZoneRules[currentStandardOffset=+08:00]

Process finished with exit code 0

  currentStandardOffset=+08:00:当前标准偏移 加8小时。

  • of(String zoneId) :从ID获取一个 ZoneId的实例,确保该ID有效并且可供使用。
        ZoneId zoneId = ZoneId.of("Asia/Shanghai");
        System.out.println(zoneId);

  传入的参数必须是JDK8中规定的那些城市格式,具体系统中规定的城市可以用如下方法获取:

  • getAvailableZoneIds():获取一组可用的区域ID。
for (String availableZoneId : ZoneId.getAvailableZoneIds()) {
    System.out.println(availableZoneId);
}

  输出结果很多,这里不做展示,其实有很多的地域时区是不经常使用的,因此JDK8中也已经收集了一些有代表性的大城市,放入了SHORT_IDS属性中,方便我们查找。

ZoneId.SHORT_IDS.forEach((key,value)->{
    System.out.println(key+"-"+value);
});
CTT-Asia/Shanghai
ART-Africa/Cairo
CNT-America/St_Johns
PRT-America/Puerto_Rico
PNT-America/Phoenix
PLT-Asia/Karachi
AST-America/Anchorage
BST-Asia/Dhaka
CST-America/Chicago
EST--05:00
HST--10:00
JST-Asia/Tokyo
IST-Asia/Kolkata
AGT-America/Argentina/Buenos_Aires
NST-Pacific/Auckland
MST--07:00
AET-Australia/Sydney
BET-America/Sao_Paulo
PST-America/Los_Angeles
ACT-Australia/Darwin
SST-Pacific/Guadalcanal
VST-Asia/Ho_Chi_Minh
CAT-Africa/Harare
ECT-Europe/Paris
EAT-Africa/Addis_Ababa
IET-America/Indiana/Indianapolis
MIT-Pacific/Apia
NET-Asia/Yerevan

Process finished with exit code 0

  可以看到SHORT_IDS是一个Map对象,可以通过他的缩写去查找他的标准格式。

  • of(String zoneId, Map<String,String> aliasMap):获取 ZoneId的实例,使用其ID使用别名映射来补充标准区域ID。
ZoneId ctt = ZoneId.of("CTT", ZoneId.SHORT_IDS);
System.out.println(ctt);

ZoneOffset

  同样使用ZoneId.of(String zoneId)方法,不过传入的是时间偏差的字符串。注意:

  • 当字符串中只有小时分钟的时候,不可以省略小时前的0,必须用+08:00这样的格式。
  • 当字符串中有时分秒的时候,可以省略冒号,如+080000这样的格式。
  • 如果字符串中只有小时的时候,可以省略小时前面的0,如+8这样的格式。
  • 字符串偏差最大只能是±18小时。
  • 最前面也可以加上UTC或者GMT表示时间标准,这两个在前面有讲到,实际上在JDK8中,UTCGMT并无数值上的差别。
        ZoneId zoneId = ZoneId.of("+08:00");
        ZoneId zoneId1 = ZoneId.of("+080000");
        ZoneId zoneId2 = ZoneId.of("+8");
        ZoneId zoneId3 = ZoneId.of("GMT+08:00");
        System.out.println(zoneId.getClass());
        System.out.println(zoneId1.getClass());
        System.out.println(zoneId2.getClass());
        System.out.println(zoneId3.getClass());
        System.out.println(zoneId);
        System.out.println(zoneId1);
        System.out.println(zoneId2);
        System.out.println(zoneId3);
  • of(String offsetId) :使用ID获取 ZoneOffset的实例。使用同ZoneId.of(String zoneId),不同的是不能加上UTCGMT
  • ofHoursMinutesSeconds(int hours, int minutes, int seconds) :获取 ZoneOffset的实例,使用小时,分钟和秒的偏移量。
ZoneOffset zoneOffset = ZoneOffset.ofHoursMinutesSeconds(8, 30, 40);
System.out.println(zoneOffset);

​ 注意:时分秒不能同时出现正数和负数,只能全为正数或者全为负数,否则会报错。

  • ofHoursMinutes(int hours, int minutes) :获取 ZoneOffset的实例,使用小时和分钟的偏移量。
  • ofHours(int hours) :获得 ZoneOffset的实例,使用小时数的偏移量。
  • ofTotalSeconds(int totalSeconds) :获取 ZoneOffset的实例, ZoneOffset总偏移量(以秒为单位) 。
  • getTotalSeconds() :获取总区域偏移量(以秒为单位)。
  • get(TemporalField field) :从该偏移量获取指定字段的值作为 int ,参数只能为秒,即ChronoField.OFFSET_SECONDS
  • compareTo(ZoneOffset other) :将此偏移量与其他偏移量按降序进行比较。 偏差大的比偏差小的则 返回负数,反之则返回正数,这一点与之前遇到的compareTo方法不同。

ZonedDateTime

  ZonedDateTime表示有时区信息的时间,我们可以看看他的属性,其中包含了LocalDateTimeZoneOffsetZoneId,所以它包含了时间、时间偏差与地区信息。

/**
 * The local date-time.
 */
private final LocalDateTime dateTime;
/**
 * The offset from UTC/Greenwich.
 */
private final ZoneOffset offset;
/**
 * The time-zone.
 */
private final ZoneId zone;

  

创建时间

  • now() :从指定的时钟获取当前的日期时间。
ZonedDateTime now = ZonedDateTime.now();
System.out.println(now);
2021-11-25T21:20:23.538+08:00[Asia/Shanghai]

Process finished with exit code 0
  • of(LocalDateTime localDateTime, ZoneId zone) :从本地日期时间获取 ZonedDateTime的实例。
LocalDateTime localDateTime = LocalDateTime.now();
ZoneId zon = ZoneId.of("Asia/Tokyo"); //日本东京时间  比中国早一个小时
ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, zon);
System.out.println(zonedDateTime);

   这个方法只会把时间和时区进行组合,不会进行时间的转换,所以输出的结果是东京的这个时间。其他重写的of方法也是如此。

  • of(LocalDate date, LocalTime time, ZoneId zone) :从当地的日期和时间获取一个 ZonedDateTime的实例。
  • of(int year, int month, int dayOfMonth, int hour, int minute, int second, int nanoOfSecond, ZoneId zone) :从年,月,日,时,分,秒,纳秒和时区获取 ZonedDateTime的实例。
  • ofInstant(Instant instant, ZoneId zone) :从 Instant获取一个 ZonedDateTime的实例。
ZoneId zoneId = ZoneId.of("Asia/Tokyo");
Instant instant = Instant.now();
ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(instant, zoneId);
System.out.println(instant);
System.out.println(zonedDateTime);
2021-11-29T13:57:18.498Z
2021-11-29T22:57:18.498+09:00[Asia/Tokyo]

Process finished with exit code 0

  Instant输出中的Z表示零时区或者中时区,也可以表示UTC标准时间,或者格林尼治时间。

  结果可以看到Instant的时间就是表示格林尼治时间的2021-11-29T13:57:18.498,而通过此Instant生成的ZonedDateTime的时间是2021-11-29T22:57:18.498,相差9个小时即指定的东京时间,也就是说,在使用ofInstant()方法生成ZonedDateTime的时候,会将格林尼治时间转换为指定地区的时间,这与of()的几个重写的方法不同。

  • ofInstant(LocalDateTime localDateTime, ZoneOffset offset, ZoneId zone) :从通过组合本地日期时间和偏移量形成的瞬间获取 ZonedDateTime的实例。

  localDateTime:指定一个日期时间。 offset:用来提供一个时差,即指定localDateTime这个参数对应格林尼治时间的偏差。

  zone:表示最后要转换的目标时区。

ZoneId zoneId = ZoneId.of("Asia/Tokyo");
LocalDateTime localDateTime = LocalDateTime.now();
ZoneOffset zoneOffset = ZoneOffset.of("+06:00");
ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(localDateTime, zoneOffset,zoneId);
System.out.println(localDateTime);
System.out.println(zonedDateTime);
2021-11-29T22:18:02.560
2021-11-30T01:18:02.560+09:00[Asia/Tokyo]

Process finished with exit code 0

  以上计算过程为:把2021-11-29 22:18:02.560时间减去指定的6个小时的偏差,获取格林尼治时间,然后指定的Asia/Tokyo为东京时间即+09:00,所以在格林尼治时间的基础上加9个小时,最终得到的东京时间为2021-11-30 01:18:02.560

  • ofStrict(LocalDateTime localDateTime, ZoneOffset offset, ZoneId zone) :获取 ZonedDateTime的实例,严格验证本地日期时间,偏移量和区域ID的组合。 即:offset表示的时间偏差和zone代表的地区必须要吻合,否则要报错。这个方法也是只做时间和时区的组合,不进行时区的转换。
ZoneId zoneId = ZoneId.of("Asia/Tokyo");
LocalDateTime localDateTime = LocalDateTime.now();
ZoneOffset zoneOffset = ZoneOffset.of("+09:00");
ZonedDateTime zonedDateTime = ZonedDateTime.ofStrict(localDateTime, zoneOffset,zoneId);
System.out.println(localDateTime);
System.out.println(zonedDateTime);

获取时间分量

  • getOffset() :获取区域偏移量,例如“+01:00”。
  • getSecond() :获得秒。

  年月日时分秒都类似,自己照着API试即可

  • getLong(TemporalField field) :当前时间与时间原点时间相差多少。
  • toEpochSecond() :当前时间与时间原点时间相差多少秒
  • withEarlierOffsetAtOverlap() :获取标准时和夏令时较早的那个时间。
  • withLaterOffsetAtOverlap() :获取标准时和夏令时较晚的那个时间。
  • query(TemporalQuery<R> query) :使用指定的查询查询此日期时间。

  可以看到TemporalQuery并没有找到相应的实现类,进去源码可以看到TemporalQueries中封装了七个对应的匿名内部类,可以作为参数。

ZonedDateTime zonedDateTime = ZonedDateTime.now();
LocalDate localDate = zonedDateTime.query(TemporalQueries.localDate());
System.out.println(localDate);
LocalTime localTime = zonedDateTime.query(TemporalQueries.localTime());
System.out.println(localTime);
Chronology chronology = zonedDateTime.query(TemporalQueries.chronology()); //计年方式
System.out.println(chronology);
ZoneOffset offset = zonedDateTime.query(TemporalQueries.offset());
System.out.println(offset);
TemporalUnit precision = zonedDateTime.query(TemporalQueries.precision()); //最高精度计时单位
System.out.println(precision);
ZoneId zone = zonedDateTime.query(TemporalQueries.zone());
System.out.println(zone);
ZoneId zoneId = zonedDateTime.query(TemporalQueries.zoneId());
System.out.println(zoneId);
2021-12-01
23:03:56.305
ISO
+08:00
Nanos
Asia/Shanghai
Asia/Shanghai

Process finished with exit code 0

修改时间分量

  修改时间分量的大部分方法与LocalDateTime中的类似,这里只列出时差相关的计算方法。

  • withZoneSameLocal(ZoneId zone) :使用不同的时区返回此日期时间的副本,如果可能,保留本地日期时间。
  • withZoneSameInstant(ZoneId zone) :使用不同的时区返回此日期时间的副本,保留即时。
ZonedDateTime zonedDateTime = ZonedDateTime.now();
ZoneId zoneId = ZoneId.of("Asia/Tokyo");
System.out.println(zonedDateTime);
System.out.println(zonedDateTime.withZoneSameLocal(zoneId));
System.out.println(zonedDateTime.withZoneSameInstant(zoneId));
2021-12-01T23:27:04.782+08:00[Asia/Shanghai]
2021-12-01T23:27:04.782+09:00[Asia/Tokyo]
2021-12-02T00:27:04.782+09:00[Asia/Tokyo]

Process finished with exit code 0

  可以看到withZoneSameLocal方法只改变了时区,并没有改变日期时间,而withZoneSameInstant方法则是会以本地时间为基准进行换算,从而获取到本地时间所对应的地区的时间。

  • withFixedOffsetZone() :返回此日期时间的副本,ZoneId设置为偏移量,即去掉城市信息。

比较时间

  • equals(Object obj) :检查这个日期时间是否等于另一个日期时间。
LocalDateTime localDateTime = LocalDateTime.now();
ZoneId zoneId1 = ZoneId.of("Asia/Shanghai");
ZoneId zoneId2 = ZoneId.of("+08:00");
ZonedDateTime zonedDateTime1 = ZonedDateTime.of(localDateTime, zoneId1);
ZonedDateTime zonedDateTime2 = ZonedDateTime.of(localDateTime, zoneId2);
System.out.println(zonedDateTime1.equals(zonedDateTime2));

  +08:00Asia/Shanghai明明可以表示同一个时区,只是一个使用区域表示,一个用时间差表示,但是结果确为false,这与我们要的结果不符,具体看一下方法源码发现,equals()方法是对自身所有的属性对象进行比较,都相同才认为相等。

@Override
    public boolean equals(Object obj) {
        if (this == obj) {   //地址是否相同,即是否是自身
            return true;
        }
        if (obj instanceof ZonedDateTime) {  //被比较对象是否是ZonedDateTime对象
            ZonedDateTime other = (ZonedDateTime) obj;
            return dateTime.equals(other.dateTime) &&  // 本地时间
                offset.equals(other.offset) &&   //时间偏差
                zone.equals(other.zone);  //地区
        }
        return false;
    }
  • isEqual(ChronoZonedDateTime<?> other):这是它所实现的接口ChronoZonedDateTime<D extends ChronoLocalDate>中定义的默认方法,同样用来检查此日期时间的时间是否等于指定的日期时间。

  把刚刚那段代码比较方法换成这个:

LocalDateTime localDateTime = LocalDateTime.now();
ZoneId zoneId1 = ZoneId.of("Asia/Shanghai");
ZoneId zoneId2 = ZoneId.of("+08:00");
ZonedDateTime zonedDateTime1 = ZonedDateTime.of(localDateTime, zoneId1);
ZonedDateTime zonedDateTime2 = ZonedDateTime.of(localDateTime, zoneId2);
System.out.println(zonedDateTime1.isEqual(zonedDateTime2));

  通过源码发现,这个方法先比较的是时间原点到这个时间点的秒数,然后比较纳秒计数器上的值,因此比较的结果为true

default boolean isEqual(ChronoZonedDateTime<?> other) {
    return toEpochSecond() == other.toEpochSecond() &&  //与时间原点相差的秒数
            toLocalTime().getNano() == other.toLocalTime().getNano(); //比较纳秒数
}
  • isBefore(ChronoZonedDateTime<?> other) :检查此日期时间是否在指定的日期时间之前。
  • isAfter(ChronoZonedDateTime<?> other) :检查此日期时间是否在指定的日期时间之后。
  • compareTo(ChronoZonedDateTime<?> other) :将此日期时间与其他日期时间进行比较,包括年表。 注意:这个方法不靠谱,不建议使用。
LocalDateTime localDateTime1 = LocalDateTime.of(2021,12,6,12,0,0,0);
LocalDateTime localDateTime2 = LocalDateTime.of(2021,12,6,13,0,0,0);
ZoneId zoneId1 = ZoneId.of("Asia/Shanghai");
ZoneId zoneId2 = ZoneId.of("+09:00");
ZonedDateTime zonedDateTime1 = ZonedDateTime.of(localDateTime1, zoneId1);
ZonedDateTime zonedDateTime2 = ZonedDateTime.of(localDateTime1, zoneId2);
System.out.println(zonedDateTime1.compareTo(zonedDateTime2));
1

Process finished with exit code 0

  结果与预想的不一样,看下源码:

@Override
default int compareTo(ChronoZonedDateTime<?> other) {
    int cmp = Long.compare(toEpochSecond(), other.toEpochSecond());  //比较到时间原点描述
    if (cmp == 0) {
        cmp = toLocalTime().getNano() - other.toLocalTime().getNano();  //比较毫秒数
        if (cmp == 0) {
            cmp = toLocalDateTime().compareTo(other.toLocalDateTime());  //比较时间对象,这里面会比较到纪年法,直接比较字符串的,不靠谱
            if (cmp == 0) {
                cmp = getZone().getId().compareTo(other.getZone().getId()); //比较ZoneId的id,也是字符串比较
                if (cmp == 0) {
                    cmp = getChronology().compareTo(other.getChronology()); //再次比较纪年法,字符串比较
                }
            }
        }
    }
    return cmp;
}

  感觉这个比较代码是乱写的一样,乱比,当然我们可以通过OffsetDateTimetimeLineOrder()方法获取一个比较器,将ZonedDateTime转换成OffsetDateTime,然后通过这个比较器进行比较。

LocalDateTime localDateTime1 = LocalDateTime.of(2021,12,6,12,0,0,0);
LocalDateTime localDateTime2 = LocalDateTime.of(2021,12,6,13,0,0,0);
ZoneId zoneId1 = ZoneId.of("Asia/Shanghai");
ZoneId zoneId2 = ZoneId.of("+09:00");
ZonedDateTime zonedDateTime1 = ZonedDateTime.of(localDateTime1, zoneId1);
ZonedDateTime zonedDateTime2 = ZonedDateTime.of(localDateTime2, zoneId2);

OffsetDateTime offsetDateTime1 = zonedDateTime1.toOffsetDateTime();
OffsetDateTime offsetDateTime2 = zonedDateTime2.toOffsetDateTime();
System.out.println(OffsetDateTime.timeLineOrder().compare(offsetDateTime1, offsetDateTime2));

计算时间

  • until(Temporal endExclusive, TemporalUnit unit) :根据指定的单位计算到另一个日期时间的时间量。
LocalDateTime localDateTime1 = LocalDateTime.of(2021,12,6,12,0,0,0);
LocalDateTime localDateTime2 = LocalDateTime.of(2021,12,6,13,0,0,0);
ZoneId zoneId1 = ZoneId.of("Asia/Shanghai");
ZoneId zoneId2 = ZoneId.of("+09:00");
ZonedDateTime zonedDateTime1 = ZonedDateTime.of(localDateTime1, zoneId1);
ZonedDateTime zonedDateTime2 = ZonedDateTime.of(localDateTime2, zoneId2);
System.out.println(zonedDateTime1.until(zonedDateTime2, ChronoUnit.SECONDS));
  • minus(long amountToSubtract, TemporalUnit unit) :减去指定单位的时间。
  • minus(TemporalAmount amountToSubtract) :减去一个时间段。
  • plus …相关方法,增加时间。

  以上方法与之前学到的类似,自行试验即可。

OffsetDateTime

  OffsetDateTime表示一个带有时间偏差的不可变的时间对象,他有dateTimeoffset两个属性。

/**
 * The local date-time.
 */
private final LocalDateTime dateTime;
/**
 * The offset from UTC/Greenwich.
 */
private final ZoneOffset offset;

创建时间

  • now() :从默认时区的系统时钟获取当前的日期时间。
  • now(ZoneId zone) :从指定时区的系统时钟获取当前的日期时间。
ZoneId zoneId = ZoneId.of("Asia/Tokyo");
OffsetDateTime offsetDateTime = OffsetDateTime.now(zoneId);
System.out.println(offsetDateTime);

  注意:这个方法会获取当前系统的系统时间与时间偏差,然后计算出指定的zoneId的时间偏差此时的时间。比如:当前从系统获取到的是北京时间,为2021-11-29T23:25:30.663+08:00,由于北京时间时间偏差为+08:00,而东京的时间偏差为+09:00,那么最后得到的时间则为东京的2021-11-30T00:25:30.663+09:00

  • of(LocalDateTime dateTime, ZoneOffset offset) :从日期时间和偏移量获取 OffsetDateTime的实例。
  • of(LocalDate date, LocalTime time, ZoneOffset offset) :从日期,时间和偏移量获取 OffsetDateTime的实例。
  • of(int year, int month, int dayOfMonth, int hour, int minute, int second, int nanoOfSecond, ZoneOffset offset) :从一年,一个月,一天,一小时,一分钟,秒,纳秒和偏移中获取一个 OffsetDateTime的实例。
  • ofInstant(Instant instant, ZoneId zone) :从 Instant和区域ID获取一个 OffsetDateTime的实例。

  of方法与ZonedDateTime中的都类似,自己写代码试验即可。

获取时间分量

  与ZonedDateTime类似。

修改时间分量

  • withOffsetSameInstant(ZoneOffset offset) :返回此副本 OffsetDateTime具有指定偏移确保结果是在同一瞬间。同ZonedDateTimewithZoneSameInstant(ZoneId zone)
  • withOffsetSameLocal(ZoneOffset offset) :返回此副本 OffsetDateTime具有指定偏移确保结果具有相同的本地日期时间。 同ZonedDateTimewithZoneSameLocal(ZoneId zone)

时间比较

  • equals(Object obj) :检查这个日期时间是否等于另一个日期时间。
  • isEqual(ChronoZonedDateTime<?> other):这是它所实现的接口ChronoZonedDateTime<D extends ChronoLocalDate>中定义的默认方法,同样用来检查此日期时间的时间是否等于指定的日期时间。

  这两个方法与ZonedDateTime的类似,我们也可以通过把ZonedDateTime转换成OffsetDateTime对象,然后与OffsetDateTime对象进行时间比较:

LocalDateTime localDateTime1 = LocalDateTime.of(2021,12,6,12,0,0,0);
LocalDateTime localDateTime2 = LocalDateTime.of(2021,12,6,13,0,0,0);
ZoneId zoneId1 = ZoneId.of("Asia/Shanghai");
ZoneId zoneId2 = ZoneId.of("+09:00");
ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime1, zoneId1);
OffsetDateTime offsetDateTime = OffsetDateTime.of(localDateTime2, (ZoneOffset) zoneId2);
OffsetDateTime offset = zonedDateTime.toOffsetDateTime(); //把zonedDateTime1转换成OffsetDateTime对象
System.out.println(offset.isEqual(offsetDateTime));
  • isBefore(ChronoZonedDateTime<?> other) :检查此日期时间是否在指定的日期时间之前。
  • isAfter(ChronoZonedDateTime<?> other) :检查此日期时间是否·在指定的日期时间之后。
  • compareTo(ChronoZonedDateTime<?> other) :将此日期时间与其他日期时间进行比较,包括年表。 注意:这个方法不靠谱,不建议使用。
  • timeLineOrder() :获取一个比较器,只比较两个 OffsetDateTime实例。
private static int compareInstant(OffsetDateTime datetime1, OffsetDateTime datetime2) {
    if (datetime1.getOffset().equals(datetime2.getOffset())) {  //比较时差
        return datetime1.toLocalDateTime().compareTo(datetime2.toLocalDateTime());  //如果时差则直接比较日期时间
    }
    int cmp = Long.compare(datetime1.toEpochSecond(), datetime2.toEpochSecond()); //比较与原点时间相差秒
    if (cmp == 0) {
        cmp = datetime1.toLocalTime().getNano() - datetime2.toLocalTime().getNano(); //比较纳秒
        
    }
    return cmp;
}

格式化日期

  虽然直接打印这些日期时间对象,就可以直接获取到其格式化好的字符串对象,但是有很多时候并不符合我们的阅读习惯,因此很多时候需要我们自己定义需要的日期时间格式并输出。

DateTimeFormatter

ZonedDateTime zonedDateTime = ZonedDateTime.now();
 //x 表示时间差,x的个数不同,显示的格式不同  xxxx 精确到分钟 xxx 小时和分钟之间加:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss x");
DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy-MMMM-dd HH:mm:ss xxx");
System.out.println(zonedDateTime.format(formatter));
System.out.println(zonedDateTime.format(formatter2));

  有些不同的字母个数表示不同的输出格式,比如x表示时间差,一个x和三个x就输出不同格式的时间差,这些下去自己试验。其实在DateTimeFormatter中已经预定义了一些时间格式,只不过输出的风格都不是我们所习惯的,因此大部分时候还是需要我们自己定义。

ZonedDateTime zonedDateTime = ZonedDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME;
System.out.println(zonedDateTime.format(formatter));
2021-12-02T23:59:16.792+08:00[Asia/Shanghai]

Process finished with exit code 0

格式和解析模式参考JDK文档

  • 所有字母“A”至“Z”和“a”至“z”保留为图案字母。 定义了以下图案字母:

      Symbol  Meaning                     Presentation      Examples
      ------  -------                     ------------      -------
       G       era                         text              AD; Anno Domini; A
       u       year                        year              2004; 04
       y       year-of-era                 year              2004; 04
       D       day-of-year                 number            189
       M/L     month-of-year               number/text       7; 07; Jul; July; J
       d       day-of-month                number            10
    
       Q/q     quarter-of-year             number/text       3; 03; Q3; 3rd quarter
       Y       week-based-year             year              1996; 96
       w       week-of-week-based-year     number            27
       W       week-of-month               number            4
       E       day-of-week                 text              Tue; Tuesday; T
       e/c     localized day-of-week       number/text       2; 02; Tue; Tuesday; T
       F       week-of-month               number            3
    
       a       am-pm-of-day                text              PM
       h       clock-hour-of-am-pm (1-12)  number            12
       K       hour-of-am-pm (0-11)        number            0
       k       clock-hour-of-am-pm (1-24)  number            0
    
       H       hour-of-day (0-23)          number            0
       m       minute-of-hour              number            30
       s       second-of-minute            number            55
       S       fraction-of-second          fraction          978
       A       milli-of-day                number            1234
       n       nano-of-second              number            987654321
       N       nano-of-day                 number            1234000000
    
       V       time-zone ID                zone-id           America/Los_Angeles; Z; -08:30
       z       time-zone name              zone-name         Pacific Standard Time; PST
       O       localized zone-offset       offset-O          GMT+8; GMT+08:00; UTC-08:00;
       X       zone-offset 'Z' for zero    offset-X          Z; -08; -0830; -08:30; -083015; -08:30:15;
       x       zone-offset                 offset-x          +0000; -08; -0830; -08:30; -083015; -08:30:15;
       Z       zone-offset                 offset-Z          +0000; -0800; -08:00;
    
       p       pad next                    pad modifier      1
    
       '       escape for text             delimiter
       ''      single quote                literal           '
       [       optional section start
       ]       optional section end
       #       reserved for future use
       {       reserved for future use
       }       reserved for future use 
    

    模式字母的数量决定了格式。

    文本 :文字样式是根据所使用的图案字母数确定的。 少于4个图案字母将使用short form 。 完全4个图案字母将使用full form 。 完全5个图案字母将使用narrow form 。 图案字母’L’,'c’和’q’指定文本样式的独立形式。

    编号 :如果字母数为1,则使用最小位数输出该值,而不填充。 否则,使用数字计数作为输出字段的宽度,根据需要使用零填充值。 以下模式字母对字母数的约束。 只能指定’c’和’F’的一个字母。 可以指定多达两个’d’,‘H’,‘h’,‘K’,‘k’,‘m’和’s’的字母。 最多可以指定三个字母’D’。

    数字/文本 :如果模式字母的数量为3或更大,请使用上述文本规则。 否则使用上面的数字规则。

    分数 :输出二分之一纳秒的场。 纳秒值有九位数,因此模式字母的计数从1到9.如果小于9,那么纳秒值将被截断,只有最高有效位被输出。 在严格模式下解析时,解析数字的数量必须与模式字母的数量相匹配。 当在宽松模式下解析时,解析数字的数目必须至少为模式字母数,最多9位数。

    年份 :字母数确定使用最小字段宽度低于哪个填充。 如果字母数为2,则使用一个reduced两位数的形式。 对于打印,这将输出最右边的两位数字。 对于解析,这将使用基数值2000解析,导致一年在2000到2099之间的范围内。 如果字母数小于四(但不是两个),则符号只能按照SignStyle.NORMAL输出为负数。 否则,符号为输出如果超过垫宽度,按照SignStyle.EXCEEDS_PAD

    ZoneId :输出时区ID,如“Europe / Paris”。 如果字母数为2,则输出时区ID。 任何其他字母数字抛出IllegalArgumentException

    区域名称 :输出时区ID的显示名称。 如果字母数为1,2或3,则输出短名称。 如果字母数为4,则输出全名。 五个或更多的字母抛出IllegalArgumentException

    偏移X和x :这将根据模式字母的数量格式化偏移量。 一个字母只输出小时,例如“+01”,除非分钟不为零,在这种情况下也输出分钟,例如“+0130”。 两个字母输出小时和分钟,没有冒号,例如’+0130’。 三个字母输出小时和分钟,冒号如“+01:30”。 四个字母输出小时和分钟,可选第二个,没有冒号,例如’+013015’。 五个字母输出小时和分钟,可选第二个,冒号如“+01:30:15”。 六个或更多的字母抛出IllegalArgumentException 。 当要输出的偏移量为零时,模式字母“X”(大写)将输出“Z”,而模式字母“x”(小写)将输出“+00”,“+0000”或“+00 :00’。

    偏移量O :根据模式字母的数量格式化局部偏移量。 一个字母输出局部偏移的short形式,这是局部偏移文本,如“GMT”,小时无前导零,可选的2位数分钟和秒,如果非零,冒号,例如’GMT + 8 '。 四个字母输出full表格,这是一个本地化的偏移文本,例如“GMT”,具有2位小时和分钟字段,可选第二个字段(如果非零),冒号(例如’GMT + 08:00)。 任何其他字母数字抛出IllegalArgumentException

    偏移Z :根据模式字母的数量格式化偏移量。 一个,两个或三个字母输出小时和分钟,没有冒号,例如’+0130’。 当偏移为零时,输出将为“+0000”。 四个字母输出full形式的局部偏移量,相当于Offset-O的四个字母。 如果偏移为零,输出将为相应的局部偏移文本。 五个字母输出小时,分钟,可选第二个(如果非零),冒号。 如果偏移为零,则输出“Z”。 六个或更多的字母抛出IllegalArgumentException

    可选部分 :可选部分标记与调用DateTimeFormatterBuilder.optionalStart()DateTimeFormatterBuilder.optionalEnd()完全相同

    垫修饰符 :修改紧随其后的模式以填充空格。 垫宽度由图案字母的数量决定。 这与拨打DateTimeFormatterBuilder.padNext(int)相同。

    例如,'ppH’输出在左边填充空格的宽度为2的小时。

    任何无法识别的字母都是错误。 除’[‘,’]‘,’{‘,’}‘,’#'和单引号之外的任何非字母字符都将直接输出。 尽管如此,建议对要直接输出的所有字符使用单引号,以确保将来的更改不会破坏您的应用程序。

    解决

    解析实现为两阶段操作。首先,使用格式化程序定义的布局解析文本,产生一个Map的字段,一个ZoneId和一个Chronology 。第二,通过验证,组合和简化各种领域,使解析的数据得到解决

    这个类提供了五种解析方法。 其中四个执行解析和解析阶段。 第五种方法, parseUnresolved(CharSequence, ParsePosition) ,仅执行第一阶段,留下结果未解决。 因此,它本质上是一个低级别的操作。

    解析阶段由这个类设置的两个参数控制。

    ResolverStyle是一种枚举,提供三种不同的方法,严格,智能和宽松。 智能选项是默认值。 可以使用withResolverStyle(ResolverStyle)设置。

    withResolverFields(TemporalField...)参数允许在解析开始之前对要解析的字段进行过滤。 例如,如果格式化程序已经解析了一年,一个月,一个月的日子和一天,那么有两种方法可以解决一个日期:(年+月+月 - 日)和(年+一年)。 解析器字段允许选择两种方法之一。 如果没有设置解析器字段,则两种方法都必须产生相同的日期。

    解决单独的字段以形成完整的日期和时间是一个复杂的过程,其行为分布在多个类中。 它遵循这些步骤:

    1. 年表是确定的。 结果的年表是被解析的年表,或者如果没有按时间顺序被解析,那么这个时间顺序是在这个类上设置的,或者如果是空的,那么是IsoChronology
    2. ChronoField日期字段已解决。 这是使用Chronology.resolveDate(Map, ResolverStyle)实现的。 有关现场解析的文件位于执行Chronology
    3. ChronoField时间字段已解决。 这在ChronoField有所记载 ,对于所有的年表都是一样的。
    4. 任何不是ChronoField字段都被处理。 这是使用TemporalField.resolve(Map, TemporalAccessor, ResolverStyle)实现的。 关于现场解决的文件位于执行TemporalField
    5. ChronoField日期和时间字段被重新解析。 这允许第四步中的字段生成ChronoField值,并将它们处理为日期和时间。
    6. 如果有至少一个小时LocalTime则形成一个LocalTime。 这涉及提供分秒,秒和秒的分数的默认值。
    7. 任何剩余的未解决的字段与已解决的任何日期和/或时间进行交叉检查。 因此,较早的阶段将解决(年+月+月的日)到一个日期,这个阶段将检查星期几是有效的日期。
    8. 如果excess number of days被解析,那么如果有可用的日期,则将其添加到日期。

Clock

  他是一个抽象的类,用来表示当前时间,存储了当前时间距离时间原点的毫秒数,同时还记录了时区信息,因此Clock可以和各种日期时间类进行转换。其自身内部包含了他的实现类:OffsetClockSystemClockFixedClockTickClock

创建对象

  • systemDefaultZone() :使用系统默认时区创建一个对象。
  • system(ZoneId zone) :通过指定时区创建一个对象。
  • systemUTC() :获取中时区的Clock对象。
System.out.println(Clock.systemDefaultZone());
ZoneId zoneId = ZoneId.of("Asia/Tokyo");
Clock clock = Clock.system(zoneId);
System.out.println(clock);
System.out.println(Clock.systemUTC());
SystemClock[Asia/Shanghai]  //当地时区
SystemClock[Asia/Tokyo]  //指定的东京时区
SystemClock[Z]  //中时区

Process finished with exit code 0
  • offset(Clock baseClock, Duration offsetDuration) :获取一个时钟,从指定的时钟周期返回时钟,并添加指定的时间 。
Clock clock = Clock.systemDefaultZone();
Duration duration = Duration.ofHours(24);
Clock offset = Clock.offset(clock, duration);
System.out.println(clock);
System.out.println(offset);
SystemClock[Asia/Shanghai]
OffsetClock[SystemClock[Asia/Shanghai],PT24H]

Process finished with exit code 0

  • tickSeconds(ZoneId zone) :创建一个表示当前时刻的Clock对象,但是该对象只能精确到秒。
System.out.println(Clock.systemDefaultZone().millis());
ZoneId zoneId = ZoneId.of("Asia/Shanghai");
System.out.println(Clock.tickSeconds(zoneId).millis());
1638535593995
1638535593000  

Process finished with exit code 0
  • tickMinutes(ZoneId zone) :创建一个表示当前时刻的Clock对象,但是该对象只能精确到分钟。
  • tick(Clock baseClock, Duration tickDuration) :在baseClock的基础上,创建一个以tickDuration为一个计时单位的Clock对象。
Clock clock = Clock.systemDefaultZone();
System.out.println(clock.millis());
 //相当于精确到1小时,即每过一个整小时,毫秒数才会变化,如果指定为24,则表示精确到天
System.out.println(Clock.tick(clock,Duration.ofHours(1)).millis()); 

获取分量

  • millis() :获取时钟当前毫秒数。
  • getZone() :获取时区信息。

修改分量

  • withZone(ZoneId zone) :修改时区,Clock并没有提供修改毫秒数的方法。

Instant

  表示时间轴上的任意一个时间点,没有时区信息,最小记录时间单位是纳秒,使用一个秒计数器和一个纳秒计数器来记录到时间原点的距离。

  与Clock不同的是,Clock表示当前的时间点,包含了时区信息;而Instant表示任意一个时间点,并没有时区信息。

创建时间点

  • now() :从系统时钟获取当前瞬间。
Instant now = Instant.now();
System.out.println(now);
2021-12-03T15:53:55.624Z

Process finished with exit code 0

  通过结果可以看到,输出的时间与当前时间相比,少了8个小时,时区为Z,因此可以知道,Instant表示的是零时区的时间瞬间。

  • ofEpochMilli(long epochMilli) :获得的一个实例 Instant从1970-01-01T00划时代使用毫秒:00:00Z。
  • ofEpochSecond(long epochSecond) :使用从1970-01-01T00:00:00Z的时代开始的秒数获得一个 Instant的实例。
  • ofEpochSecond(long epochSecond, long nanoAdjustment):使用从1970-01-01T00:00:00Z的时期开始的秒数获得 Instant的实例, Instant获得秒的纳秒分数。

获取分量

  • getEpochSecond() :与时间原点相差秒数。
  • getNano() :获得纳秒分量。
  • getLong(TemporalField field) :获取指定单位零头。
  • toEpochMilli() :与时间原点相差毫秒数。

修改分量

  • plus(TemporalAmount amountToAdd) :在某个时间点上加一段时间产生一个新的事件点。注意:处理的最大时间单位是天,即参数最大只能是Period.ofDays(),超过(比如用ofMonths())则会报错。而使用Duration的所有单位都可以接收。

  那么我们可以总结一下,PeriodDuration都表示一段时间,表示的分量不同,因此不同的时间类能使用的时间段对象有所不同:

Period与Duration作用对象

  • LocalDate仅可接受Period对象。

  • LocalTime仅可接受Duration对象。

  • LocalDateTime/ZonedDateTime/OffsetDateTime即可接受Period对象,也可接受Druation对象。

  • Instant可以完全接受Duration对象,在Period的年月计数器值为0的情况下,也可以接受Period对象。

  • plus(long amountToAdd, TemporalUnit unit) :增加指定单位的时间数。

后续minus相关方法与plus相关方法则与之前讲的时间类类似,可自己试。

  • with(TemporalAdjuster adjuster) :以另外一个Instant对象为基数修改,相当于复制另一个Instant对象的所有分量。
  • with(TemporalField field, long newValue) :直接修改此时间分量的数值。

比较时间点

  比较时间点的相关方法也与之前类似。

  • equals(Object otherInstant) :检查这个瞬间是否等于指定的时刻。
  • isAfter(Instant otherInstant) :检查这个瞬间是否在指定的时刻之后。
  • isBefore(Instant otherInstant) :检查这个时刻是否在指定的时刻之前。
  • until(Temporal endExclusive, TemporalUnit unit) :根据指定单位计算直到另一瞬间的时间量。

now()方法

  • 几乎所有表示日期时间的类都定义了now()方法(除Clock)。
  • 几乎所有类的now()方法都有3个版本(Instant只有两个)。
  • now()方法创建出的日期时间对象实际取决于Clock参数。
  • 无参的now()方法,本质是调用了一个以Clock对象为参数的now()方法,该参数以操作系统默认的失去创建。
  • 以ZoneId为参数的now()方法,其参数的作用是为了创建一个Clock对象,因为Clock对象包含了时区信息。以该版本now()方法创建的日期时间对象表示的是参数所指定时区的当前时间。

时间搞对象

  在之前的方法中,我们都是通过指定日期时间的各种分量来进行时间对象的创建,但是在实际开发的过程中,我们从数据库获取到的是时间的一个字符串,因此我们更多的是把一个时间字符串解析成一个时间对象。

LocalDate ld = LocalDate.parse("2021-12-04");
System.out.println(ld);
LocalTime lt = LocalTime.parse("22:22:22");
System.out.println(lt);
LocalDateTime ldt = LocalDateTime.parse("2021-12-04T22:22:22");
System.out.println(ldt);
ZonedDateTime zdt = ZonedDateTime.parse("2021-12-04T22:22:22+01:00[Europe/Paris]");
System.out.println(zdt);
OffsetDateTime odt = OffsetDateTime.parse("2021-12-04T22:22:22+01:00");
System.out.println(odt);
OffsetTime ot = OffsetTime.parse("22:22:22+01:00");
System.out.println(ot);
MonthDay md = MonthDay.parse("--12-04");
System.out.println(md);
YearMonth ym = YearMonth.parse("2021-12");
System.out.println(ym);

  这个时间字符串的格式默认使用系统指定的,我们可以通过创建一个对象然后打印出来去查看系统默认的时间格式。当然我们也可以通过DateTimeFormatter自己定义符合我们自己习惯的时间格式.。

DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日");
LocalDate ld = LocalDate.parse("2021年12月04日",dateTimeFormatter);
System.out.println(ld);

  目前个人接触的项目中,有很多都还在用旧日期系统中的Date对象,然鹅旧日期系统有很多不好用的地方,我们就需要把他转换成新日期系统进行计算,因此也有很多场景需要进行新旧日期系统对象的转换。

  我们可以以Instant为媒介,去进行新旧时间的转换:

旧转新:

        //旧转新
        Date date = new Date();
        Instant instant = date.toInstant();
        Calendar calendar = Calendar.getInstance();
//        Instant instant = calendar.toInstant();
        ZonedDateTime zonedDateTime = instant.atZone(ZoneId.of("Z"));
//        OffsetDateTime offsetDateTime = instant.atOffset((ZoneOffset) ZoneId.of("+08:00"));
        OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.UTC);
        LocalDateTime localDateTime = zonedDateTime.toLocalDateTime();
        LocalDate localDate = zonedDateTime.toLocalDate();
        LocalTime localTime = zonedDateTime.toLocalTime();
        Clock clock = Clock.fixed(instant, TimeZone.getDefault().toZoneId());

新转旧:

ZonedDateTime zonedDateTime = ZonedDateTime.now();
Instant instant = zonedDateTime.toInstant();
Date date = new Date(instant.toEpochMilli());
Calendar calendar = Calendar.getInstance();
calendar.clear();
calendar.setTimeZone(TimeZone.getDefault());
calendar.setTimeInMillis(instant.toEpochMilli());
System.out.println(date);
System.out.println(calendar);

Java日期时间类的坑

SimpleDateFormat错误使用格式化字母

Date date = new Date(120, 11, 31, 12, 20, 20);
//Date date = new Date();
//2020年12月31
System.out.println(date);  
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("YYYY-MM-dd");
String format = simpleDateFormat.format(date);
System.out.println(format);
Thu Dec 31 12:20:20 CST 2020
2021-12-31

Process finished with exit code 0

  可以看到,我们使用当前时间的时候,日期打印正确,当把日期设置为2020年12月31的时候,进行时间转换后,打印的是2021-12-31,这是为什么呢?

  这里我们使用的时间转换格式是YYYY-MM-dd,年的部分使用的是大写的Y,通过前面的表格我们可以知道他与小写y的区别:

  • y:表示年
  • Y:表示星期年,指的是某个星期在的那个年,也就是说如果出现星期跨年的情况下,会以这个星期所在两个年份的后面那一年为准。

  因此,我们错误的使用了大写的Y,这样就会导致特殊情况下(星期跨年)出现格式转换错误。代码里面也会提示我们格式有误,我们只需要用小写的y去表示年即可避免

  类似的情况也出现在大写的D与小写的d身上,用的时候也需要注意:

  • d:表示这个时间在这个月的第几天。
  • D:表示这个时间在这一年的第几天。

Date/Calendar传递错误日期

Date date = new Date(120, 11, 32, 25, 20, 20);//2020-12-32 25:20:20
System.out.println(date);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String format = simpleDateFormat.format(date);
System.out.println(format);

Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.DATE,32);
System.out.println(calendar.get(Calendar.DATE));

  如上我们传入一个错误的时间,但是代码并没有去报错而是产生了一个不一样的时间,这就有可能导致代码产生不易排查的bug。

Period天月无法换算

Period period = Period.ofWeeks(20);
System.out.println(period.toTotalMonths());
System.out.println(period.getDays());

Period period1 = Period.of(3,34,125);
Period period2 = Period.of(3,-34,125);
System.out.println(period1.normalized());
System.out.println(period2.normalized());
0
140
P5Y10M125D
P2M125D    

Process finished with exit code 0

  可以看到,周与日之间有着明确的换算单位,一周等于七天,而天与月份是没有明确的换算单位的,因此天和月之间是无法相互转换的,所以第一行打印的月数为0,而后面两行打印只把年和月之间进行了换算,天数是无法换算的。

ChronoField与ChronoUnit

  • ChronoField表示一个时间单位,通常用在获取时间分量的方法中,如get等方法。
  • ChronoUnit表示一个时间段,一般用在时间计算的方法中,如plus等方法。

  通过ChronoUnit的枚举值可以看到,都包含了Duration对象:

NANOS("Nanos", Duration.ofNanos(1)),
/**
 * Unit that represents the concept of a microsecond.
 * For the ISO calendar system, it is equal to the 1,000,000th part of the second unit.
 */
MICROS("Micros", Duration.ofNanos(1000)),
/**
 * Unit that represents the concept of a millisecond.
 * For the ISO calendar system, it is equal to the 1000th part of the second unit.
 */
MILLIS("Millis", Duration.ofNanos(1000_000)),
/**
 * Unit that represents the concept of a second.
 * For the ISO calendar system, it is equal to the second in the SI system
 * of units, except around a leap-second.
 */
SECONDS("Seconds", Duration.ofSeconds(1)),
/**
 * Unit that represents the concept of a minute.
 * For the ISO calendar system, it is equal to 60 seconds.
 */
MINUTES("Minutes", Duration.ofSeconds(60)),
/**
 * Unit that represents the concept of an hour.
 * For the ISO calendar system, it is equal to 60 minutes.
 */
HOURS("Hours", Duration.ofSeconds(3600)),
/**
 * Unit that represents the concept of half a day, as used in AM/PM.
 * For the ISO calendar system, it is equal to 12 hours.
 */
HALF_DAYS("HalfDays", Duration.ofSeconds(43200)),
/**
 * Unit that represents the concept of a day.
 * For the ISO calendar system, it is the standard day from midnight to midnight.
 * The estimated duration of a day is {@code 24 Hours}.
 * <p>
 * When used with other calendar systems it must correspond to the day defined by
 * the rising and setting of the Sun on Earth. It is not required that days begin
 * at midnight - when converting between calendar systems, the date should be
 * equivalent at midday.
 */
DAYS("Days", Duration.ofSeconds(86400)),
/**
 * Unit that represents the concept of a week.
 * For the ISO calendar system, it is equal to 7 days.
 * <p>
 * When used with other calendar systems it must correspond to an integral number of days.
 */
WEEKS("Weeks", Duration.ofSeconds(7 * 86400L)),
/**
 * Unit that represents the concept of a month.
 * For the ISO calendar system, the length of the month varies by month-of-year.
 * The estimated duration of a month is one twelfth of {@code 365.2425 Days}.
 * <p>
 * When used with other calendar systems it must correspond to an integral number of days.
 */
MONTHS("Months", Duration.ofSeconds(31556952L / 12)),
/**
 * Unit that represents the concept of a year.
 * For the ISO calendar system, it is equal to 12 months.
 * The estimated duration of a year is {@code 365.2425 Days}.
 * <p>
 * When used with other calendar systems it must correspond to an integral number of days
 * or months roughly equal to a year defined by the passage of the Earth around the Sun.
 */
YEARS("Years", Duration.ofSeconds(31556952L)),
/**
 * Unit that represents the concept of a decade.
 * For the ISO calendar system, it is equal to 10 years.
 * <p>
 * When used with other calendar systems it must correspond to an integral number of days
 * and is normally an integral number of years.
 */
DECADES("Decades", Duration.ofSeconds(31556952L * 10L)),
/**
 * Unit that represents the concept of a century.
 * For the ISO calendar system, it is equal to 100 years.
 * <p>
 * When used with other calendar systems it must correspond to an integral number of days
 * and is normally an integral number of years.
 */
CENTURIES("Centuries", Duration.ofSeconds(31556952L * 100L)),
/**
 * Unit that represents the concept of a millennium.
 * For the ISO calendar system, it is equal to 1000 years.
 * <p>
 * When used with other calendar systems it must correspond to an integral number of days
 * and is normally an integral number of years.
 */
MILLENNIA("Millennia", Duration.ofSeconds(31556952L * 1000L)),
/**
 * Unit that represents the concept of an era.
 * The ISO calendar system doesn't have eras thus it is impossible to add
 * an era to a date or date-time.
 * The estimated duration of the era is artificially defined as {@code 1,000,000,000 Years}.
 * <p>
 * When used with other calendar systems there are no restrictions on the unit.
 */
ERAS("Eras", Duration.ofSeconds(31556952L * 1000_000_000L)),
/**
 * Artificial unit that represents the concept of forever.
 * This is primarily used with {@link TemporalField} to represent unbounded fields
 * such as the year or era.
 * The estimated duration of the era is artificially defined as the largest duration
 * supported by {@code Duration}.
 */
FOREVER("Forever", Duration.ofSeconds(Long.MAX_VALUE, 999_999_999));

  其中年的枚举可以看到,并不是我们所熟知的整数,这是科学家对实际地球围绕太阳公转计算出的一个秒数,现在我们通过一系列方法,将一个时间增加1000年:

LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime);
System.out.println(localDateTime.plusYears(1000));
System.out.println(localDateTime.plus(Period.of(1000, 0, 0)));
System.out.println(localDateTime.plus(1000, ChronoUnit.YEARS));
System.out.println(localDateTime.plus(1, ChronoUnit.MILLENNIA));
System.out.println(localDateTime.plus(ChronoUnit.MILLENNIA.getDuration()));

  结果打印可发现,最后一行多了12小时,这是因为通过最后那个方法计算的时候,获取到的是Duration对象,会精确到时分秒,而前面几个方法只会精确到年月日,刚刚我们说过,枚举里面定义的年的秒数并不是我们按照常理计算出的秒数,而是有一定的时间偏差,这个时间偏差由于计算精确度的问题而体现了出来。

  因此,当我们使用我们不是很熟悉的方法的时候,不能想当然的去使用,我们需要通过往上搜索或者自己看源码的方式,去了解清楚这个方法,从而才能避免很多的坑。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值