Java日期/时间表示

旧版java.util和新版java.time包的日期/时间API

Posted by BlackDn on July 15, 2022

“尖酸刻薄,不擅温言宽慰;惰于交流,独自沉默执着。”

Java 日期/时间表示

前言

之前写过一个Java 日期表示,但是没写全,只是比较简单地讲了下Data类Calendar类
所以重新来整理一下,搞个相对全面的 Java 日期和时间的文章出来
最近经历了好多事情,毕业照、毕业视频、生日、漫协嘉年华,不过都一一落幕了
大学生活啊,已经结束了

日期和时间的一些概念

首先我们来看一看日期和时间相关的一些概念,有些是生活中所用到的,有些是计算机领域采用的标准,又有些是格式规定啥的=。=

  1. 本地时间:这个比较好理解,当我们明确地点+时间的时候,这时候指的就是本地时间,比如“北京时间2022-05-23 21:20”。还有一些缩写,如CST可以表示China Standard Time(中国标准时间)或Central Standard Time USA(美国中部时间)。此外还可以用洲/城市的方式表达本地时间,如上海时间表示为Asia/Shanghai。注意不是任何城市都能这样表示,而是由 ISO 规定的。
  2. 时区:如果用本地时间还是会出现问题,万一有人不知道北京这个地方呢?所以就有了时区的概念。每隔 15° 经度划分共 24 个时区,以英国伦敦(格林尼治天文台)所在的时区称为标准时区/中时区(零时区),向东向西分为东 1~东 12 区,西 1~西 12 区。东八区指的就是北京时间。
  3. GMT:格林尼治标准时间(Greenwich Mean Time),也称世界时(UT,Universal Time)。东八区可以表示为GMT+08:00,西八区可以表示为GMT-08:00,比如“2022-05-23 21:20 UTC+08:00”。当然这是基于时区来表示的。
  4. UTC:协调世界时(Universal Time Coordinated),UTC 和 GMT 类似,也是通过时区来表示时间,东八区为UTC+08:00,西八区可以表示为UTC-08:00。不过,由于 UTC 采用原子钟计时,即每隔几秒会有一个闰秒(就像地球自转回归年周期为 365.2422 天,所以每四年一个闰年),因此更加精确。
  5. 夏令时:DST,Daylight Saving Time,也称夏时制。说白了就是在夏天日长夜短的时候把时间拨快一点,让大家早点醒;冬天就反过来。不过由于这样做省不了多少点,还容易打乱生物钟,所以很多地方都取消了。比如本来中国在 1986 年开始采用夏令时,但是 1992 后就停止实行了。又比如美国的夏令时由各个州自行安排。
  6. ISO_8601:这是由国际标准化组织(ISO)设定的日期时间表示方法,要求年月日分别用 4 位,2 位,2 位数表示,且日期和时间中间用T分隔。主要标准格式有:日期表示为yyyy-MM-dd,时间表示为HH:mm:ss,日期和时间表示为:yyyy-MM-ddTHH:mm:ss等。
  7. Locale:因为不同地区采用不同的表示方法,计算机中用 Locale 特指当地的日期、时间等格式,并用语言_国家表示(当然要是世界范围内统一度量衡就没有这玩意了)。比如中国的日期表示形式为:zh_CN:2016-11-30,美国的表示形式为:en_US:11/30/2016
  8. Epoch Time:新纪元时间,又称时间戳,程序员们会对这个时间比较熟悉,即1970年1月1日零点(格林威治时区/GMT+00:00)到现在所经历的时间,其单位在不同语言中存在差异,Java 中常以long表示的毫秒的形式出现,最后三位表示毫秒数。比如1574208900123L表示北京时间2019-11-20T8:15:00.123。Java 中常用System.currentTimeMillis()语句获取时间戳。

API: java.util 包

这个包中是 Java 比较老旧的 API,主要包括DateCalendarTimeZone等类

Date 类

这个类表示一个日期和时间对象,要和数据库中表示时间的java.sql.Date区分。通过这个对象,我们可以轻易获取一个时间的年月日等信息。
当我们实例化这个类的时候,其无参构造方法调用System.currentTimeMillis()获取当前时间戳,然后哦我们可以通过getYear()getMonth()getDate()等方法获取信息

    //构造方法
	public Date() {
        this(System.currentTimeMillis());
    }

    //测试用例
    Date date = new Date();
    System.out.println(date.toString());        // 转换为String: Tue Jul 12 00:11:05 CST 2022
    System.out.println(date.toGMTString());        // 转换为GMT时区: 11 Jul 2022 16:11:05 GMT
    System.out.println(date.toLocaleString());        // 转换为本地时区:2022-7-12 0:11:05

    System.out.println(date.getYear() + 1900);	//输出年:2022
    System.out.println(date.getMonth() + 1);	//输出月:7
    System.out.println(date.getDate());			//输出日:11

由于 Date 对象是处理时间戳来获取年月日等信息的,因此其并不能表示此刻准时的事件,需要我们进行进一步操作。
比如getYear()得到年份是以 1900 年为元年的年份,需要我们加上 1900 得到此刻的年份。(简单查了一下没找到为啥,可能写这段代码的程序员喜欢)
getMonth()得到的月份表示为0-11,所以要+1 得到准确月份。getDate()得到的日期倒是没啥问题。 还有其他的一些方法,比如getHours()getMinutes()getSeconds()分别获取小时,分钟,秒等方法。

不过,Date 类存在许多弊端,比如它不能设置时区,只能以当前计算机的时区进行输出(除了toGMTString()可以输出GMT)。此外,Date 也难以比较两个日期,计算其相差时间;难以计算某日期是某个月第几个星期几等。

SimpleDateFormat

如果我们想要自己进一步格式化日期的格式,比如想让日期表示为yyyy-MM-dd的格式,就可以用SimpleDateFormat类来帮助实现。
其常见预定义的字符如下,详见JDK 的官方文档

yyyy:年
MM:月
dd: 日
HH: 小时
mm: 分钟
ss: 秒

比如我们想让时间表示为"yyyy-MM-dd HH:mm:ss"的格式

Date date = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(dateFormat.format(date));	//输出:2022-07-11 11:21:59

此外,JDK 文档中提到,字母越长,输出越长。以 7 月为例,M:输出7MM:输出07MMM:输出JulMMMM:输出July
不过可能因为是中国时区的关系,我试了一下,MMMMMMM输出的都是七月,即"E MMM dd, yyyy"输出的是:星期二 七月 12, 2022、(E表示星期)

Calendar

可以用于获取并设置年、月、日、时、分、秒,和Date相比,Calendar可以进行简单的运算,计算日期间隔等。
Calendar只能通过getInstance()方法获取对象,它似乎考虑到了线程安全,这相比于Date是一个进步。但是注意Calendar并非单例模式,它内部还是通过new来创建对象,只不过多了很多判断操作。以2022-07-11 11:21:59为例:

Calendar calendar = Calendar.getInstance();
int year = calendar.get(Calendar.YEAR);			//输出年:2022
int month = calendar.get(Calendar.MONTH) + 1;		//输出月:7(0-11表示)
int day = calendar.get(Calendar.DAY_OF_MONTH);		//输出日:11
int week = calendar.get(Calendar.DAY_OF_WEEK);		//输出星期:2(表示周一。1-7表示,1=周日,2=周一...7=周六)
int hour = calendar.get(Calendar.HOUR_OF_DAY);		//输出时:11
int minute = calendar.get(Calendar.MINUTE);			//输出分:21
int second = calendar.get(Calendar.SECOND);			//输出秒:59
int milli_second = calendar.get(Calendar.MILLISECOND);	//输出毫秒:881

如果想要重新为 Calendar 对象设置时间,需要用清空其内容,然后用 set 进行设置。(可以看到它很努力想变成单例模式)

        Calendar calendar = Calendar.getInstance();
        calendar.clear();		//清空calendar
        calendar.set(Calendar.YEAR, 2019);	//设置年份为2019年
		······	//设置其他时间

虽然Calendar解决了Date的一些缺点,但它仍存在自己的不足。比如月份还是用 0-11 表示(好在年份不是 1900 开始了);Calender没有自定义的格式化操作,因此还需要和 Date 进行转换从而使用DateFormat进行格式化。
getTime()方法将 Calender 对象转变为 Date 对象

        Date date = calendar.getTime();

顺便再看看 Calendar 进行时间的加减
根据其 add 方法和标志就可以快速进行加减操作。如果小时不够减了会自动跳到前一天,很智能的不用担心。

Calendar calendar = Calendar.getInstance();
calendar.clear();
//设置Calendar
TimeZone myTimeZone = TimeZone.getTimeZone("Asia/Shanghai");
calendar.setTimeZone(myTimeZone);
calendar.set(2022, 6, 11, 11, 30, 0);   //设置为2022年7月11日 11时30分0秒
//加5天,减2小时
calendar.add(Calendar.DAY_OF_MONTH, 5);
calendar.add(Calendar.HOUR_OF_DAY, -2);
Date date = calendar.getTime();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(simpleDateFormat.format(date));  //输出:2022-07-16 09:30:00

TimeZone

之前提到,Date不能进行时区的转换,于是这个任务就交给了TimeZone
我们可以通过TimeZoneID来设置或者获取时区

TimeZone timeZoneDefault = TimeZone.getDefault();
System.out.println(timeZoneDefault.getID());
//获取本地时区,输出:Asia/Shanghai
TimeZone timeZoneGMT9 = TimeZone.getTimeZone("GMT+9:00");
System.out.println(timeZoneGMT9.getID());
//获取GMT+9的时区,输出:GMT+09:00
TimeZone timeZoneNewYork = TimeZone.getTimeZone("America/New_York");
System.out.println(timeZoneNewYork.getID());
//获取美国纽约的时区,输出:America/New_York

此外, 可以通过TimeZone.getAvailableIDs()语句来获取全部可用的时区 ID

由于CalendarSimpleDateFormat都存在TimeZone属性,我们可以以此实现时区的转换(Date 没有TimeZone属性)
比如我们将2022年7月11日 11时30分0秒的时间改为美国纽约时区:

        Calendar calendar = Calendar.getInstance();
        calendar.clear();
        //设置Calendar
        TimeZone myTimeZone = TimeZone.getTimeZone("Asia/Shanghai");
        calendar.setTimeZone(myTimeZone);
        calendar.set(2022, 6, 11, 11, 30, 0);   //设置为2022年7月11日 11时30分0秒
        //SimpleDateFormat设置时区,并将Calendar转换为Date,通过SimpleDateFormat转换时区
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        TimeZone newTimeZone = TimeZone.getTimeZone("America/New_York");
        simpleDateFormat.setTimeZone(newTimeZone);
        String newDate = simpleDateFormat.format(calendar.getTime());	//内容为:2022-07-10 23:30:00

我们先用 Calendar 设置当前时区,然后更改 SimpleDateFormat 的时区。
最后将 Calendar 转换为 Date,并通过 SimpleDateFormat 格式化 Date,从而得到新时区的时间。
可以看到2022年7月11日 11时30分0秒在美国纽约时区来看是2022年7月10日 23时30分0秒(东八区和西五区,相差 13 小时,不过此时美国执行夏令时,调快了一个小时,所以相差 12 小时)

API:java.time 包

Java 8 引入的 java.time 包提供了新的时间日期 API,遵守 ISO 8601 标准,功能更准确,功能更丰富,线程安全
主要类型有:

  • 本地日期/时间:LocalDateTimeLocalDateLocalTime
  • 时区日期/时间:ZonedDateTime
  • 时区:ZoneIdZoneOffset
  • 时间间隔:Duration
  • 格式化类型:DateTimeFormatter
  • 时刻(时间戳):Instant

这些新的 API 严格区分时刻、本地日期或时间、时区等,并且用 1-12 表示1月~12月,用 1-7 表示周一~周日

java.time 包下的这些类和Joda Time的开源工具类很像,这也是因为 Joda Time 的设计很好,因此 JDK 团队将其作者 Stephen Colebourne 挖去设计了 java.time API

LocalDateTime,LocalDate,LocalTime

这个类比较常用,LocalDateLocalTime两个类分别表示日期和时间,不过通常多用LocalDateTime来同时获取这两个信息
这三个类比较类似,都可以用now()方法获取当前时间。如果直接打印的话,会按照ISO 8601格式进行输出

        LocalDate localDate = LocalDate.now();
        System.out.println(localDate);  //输出:2022-07-12

        LocalTime localTime = LocalTime.now();
        System.out.println(localTime);  //输出:23:31:31.116

        LocalDateTime localDateTime = LocalDateTime.now();
        System.out.println(localDateTime);  //输出:2022-07-12T23:31:31.116

当然,我们可以将LocalDateTime的内容传给LocalDateLocalTime,也可以反过来,根据LocalDateLocalTime构建一个LocalDateTime
不仅如此,还可以用of方法传入年月日时分秒的数值、或parse()方法传入ISO 8601标准的字符串来构建LocalDateTime

        //LocalDateTime转为LocalDate和LocalTime
        LocalDateTime localDateTime = LocalDateTime.now();
        LocalDate localDate = localDateTime.toLocalDate();
        LocalTime localTime = localDateTime.toLocalTime();
        //LocalDate和LocalTime转为LocalDateTime
        LocalDate localDateNew = LocalDate.of(2022, 7, 12);
        LocalTime localTimeNew = LocalTime.of(22, 25, 30);
        LocalDateTime localDateTimeNew = LocalDateTime.of(localDate, localTime);
        //字符串传入LocalDateTime
        LocalDateTime localDateTimeFromNum = LocalDateTime.of(2022, 7, 12, 15, 16, 17);
        LocalDateTime localDateTimeFromString = LocalDateTime.parse("2022-07-12T15:16:17");

当然,LocalDateLocalTime也有ofparse方法,用法也类似。
注意字符串一定要满足ISO 8601标准,因此月份或日期一定要两位数,所以要补 0,7 月用07表示。

日期的加减和修改

LocalDateTime可以通过链式操作进行日期和时间的加减,比如plusDays(3)就是加三天,minusHours(5)就是减少 5 个小时。
当然还可以直接修改内容,比如withHour(15)可以直接将小时改为 15
LocalDateTime会自动调整操作后的新时间,比如23时30分加两个小时会变成后一天的01时30分,比如10月31日减一个月会变成9月30日,因为 9 月没有 31 日。

//2022-07-12 15:16:17
LocalDateTime localDateTime = LocalDateTime.of(2022, 7, 12, 15, 16, 17);
LocalDateTime localDateTimeNew = localDateTime.plusDays(3).minusHours(5);        //2022-07-15 10:16:17
LocalDateTime localDateTimeWith = localDateTime.withYear(2002);         //2002-07-15 10:16:17

此外,LocalDateTime有一个通用的with()方法可以实现更复杂的计算
因此,对于类似计算某个月第一个周日是几号这样的问题,就可以很方便的实现。

判断日期先后

判断两个LocalDateTime的先后,可以使用isBefore()isAfter()方法。LocalDateLocalTime也有这个方法。

LocalDateTime localDateTime = LocalDateTime.now();     //2022-07-12
LocalDateTime localDateTimeOld = LocalDateTime.of(2022, 6, 6, 12, 13, 14);  //2022-06-06
System.out.println(localDateTime.isAfter(localDateTimeOld));        //输出:true
System.out.println(localDateTime.isBefore(localDateTimeOld));       //输出:false

DateTimeFormatter

DateTimeFormatter的初衷是用以取代SimpleDateFormat,可以自定义输出格式,或者将时间字符串解析成ISO 8601格式。

        //自定义格式
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
        LocalDateTime localDateTime = LocalDateTime.now();
        System.out.println(formatter.format(localDateTime));    //输出:2022/07/12 22:14:15
        //解析成ISO 8601
        LocalDateTime localDateTimeNew = LocalDateTime.parse("2022/07/12 13:14:15", formatter);
        System.out.println(localDateTimeNew);   //输出:2022-07-12T13:14:15

Duration 和 Period

Duration表示两个时刻之间的时间间隔,而Period表示两个日期之间的天数。

LocalDateTime localDateTime = LocalDateTime.of(2022, 7, 12, 15, 16, 17);  //2022-07-12 15:16:17
LocalDateTime localDateTimeOld = LocalDateTime.of(2022, 6, 6, 12, 13, 14);  //2022-06-06 12:13:14
Duration duration = Duration.between(localDateTimeOld, localDateTime);
System.out.println(duration);   //输出:PT867H3M3S

LocalDate localDate = localDateTime.toLocalDate();
LocalDate localDateOld = localDateTimeOld.toLocalDate();
Period period = localDateOld.until(localDate);
System.out.println(period);     //输出:P1M6D

Duration输出的PT867H3M3S表示两者相差了867小时3分3秒Period输出的P1M6D表示两者相差了1个月6天
这也是ISO 8601规定的时间格式,P...T...用于表示时间间隔,P的后面是日期间隔,T的后面是时间间隔。如果只有时间间隔,那就要用PT...表示

对于Period,如果想直接获取相隔的天数/月数/年数,可以直接调用其getDays()getgetMonths()getYears()等方法

ChronoUnit

在计算两个日期相差天数的时候,Period可以给出相差的年月日,但是无法给出统一单位后的结果。
就比如上面的例子中,相差的日子是P1M6D,即1个月6天 。这时候调用getgetMonths()getDays()方法。会分别返回16
但是我们无法确定总共相差了多少天,我们不能确定相差的月份有多少天。因此,这里就要用到ChronoUnit
通过指定我们想要的单位,我们就可以快速地获取两个日子的差值,还是取上面的例子:

        LocalDate localDate = LocalDate.of(2022, 7, 12);	//2022-7-12
        LocalDate localDateOld = LocalDate.of(2022, 6,6);	//2022-6-6
        long months = ChronoUnit.MONTHS.between(localDateOld, localDate);   //输出:1
        long days = ChronoUnit.DAYS.between(localDateOld, localDate);   //输出:36

如果老的日期在后面,新的日期在前面,输出的结果则为负数
事实上,根据关键字的不同,ChronoUnit还可以计算星期、时分秒、毫秒等的差值。但是要注意的是,LocalDate只有日期没有时间,所以不支持时分秒的计算;同理LocalTime只有时间没有日期,不支持年月日的计算。当然LocalDateTime是全都支持的。

ZonedDateTime

LocalDateTime用于表示本地的日期和时间,如果需要带上时区,就要用到ZonedDateTime。可以将其理解为:时区+LocalDateTime
now()方法可以得到系统时区的当前时间,当然也可以通过ID指定时区;
LocalDateTimeatZone()方法可以为其设置时区属性,并返回一个ZonedDateTime对象

        ZonedDateTime zonedDateTime = ZonedDateTime.now();  //系统默认时区
        System.out.println(zonedDateTime);  //输出:2022-07-14T16:58:40.941+08:00[Asia/Shanghai]

        ZonedDateTime zonedDateTimeWithId = ZonedDateTime.now(ZoneId.of("America/New_York"));   //指定美国纽约时区
        System.out.println(zonedDateTimeWithId);    //输出:2022-07-14T04:58:40.943-04:00[America/New_York]

        LocalDateTime localDateTime = LocalDateTime.now();	//为LocalDateTime设置时区并转为ZonedDateTime
        ZonedDateTime zonedDateTime1WithLDT = localDateTime.atZone(ZoneId.of("America/New_York"));
        System.out.println(zonedDateTime1WithLDT);	//输出:2022-07-14T16:59:58.183-04:00[America/New_York]

可以看到两个ZonedDateTime对象表示的是同一时刻不同时区的时间,并且其自带时区的属性。(毫秒差是执行语句的时间差)
要注意为LocalDateTime设置时区后,其时间是不会变化的,仅仅改变了时区。
同样,也可以轻易地将ZonedDateTime变为一个LocalDateTime对象

        ZonedDateTime zonedDateTime = ZonedDateTime.now();  //系统默认时区:2022-07-14T17:39:22.156+08:00[Asia/Shanghai]
        LocalDateTime localDateTime = zonedDateTime.toLocalDateTime();
        System.out.println(localDateTime);	//输出:2022-07-14T17:42:33.617

如果要对某一时间进行时区的转换,可以用withZoneSameInstant()方法,方法名中的SameInstant就表示同一时刻

        ZonedDateTime zonedDateTime = ZonedDateTime.now();  //系统默认时区
        System.out.println(zonedDateTime);  //输出:2022-07-14T17:39:22.156+08:00[Asia/Shanghai]

        ZonedDateTime zonedDateTimeNewYork = zonedDateTime.withZoneSameInstant(ZoneId.of("America/New_York"));
        System.out.println(zonedDateTimeNewYork);   //输出:2022-07-14T05:39:22.156-04:00[America/New_York]

DateTimeFormatter

由于SimpleDateFormat存在线程不安全等缺点,因此特意设计了DateTimeFormatter来进行时间的格式化显示
使用方法类似,还是使用字符串来规定我们想要的格式,比如"yyyy-MM-dd HH:mm"

        ZonedDateTime zonedDateTime = ZonedDateTime.now();   //2022-07-14T17:48:49.184+08:00[Asia/Shanghai]
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm ZZZZ"); //Z表示时区
        System.out.println(dateTimeFormatter.format(zonedDateTime));    //输出:2022-07-14T17:48 GMT+08:00

在格式化的字符串中,Z表示时区,而单引号的内容会被保留。
此外,在规定格式的字符串中,可以指定Locale来改变时间的输出形式,让其变得更加本地化

        ZonedDateTime zonedDateTime = ZonedDateTime.now();   //2022-07-14T17:48:49.184+08:00[Asia/Shanghai]
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy MMM dd EE HH:mm", Locale.CHINA);
        System.out.println(dateTimeFormatter.format(zonedDateTime));    //输出:2022 七月 14 星期四 17:48

        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("E, MMMM/dd/yyyy HH:mm", Locale.US);
        System.out.println(dateTimeFormatter.format(zonedDateTime));    //输出:Thu, July/14/2022 17:48

Instant

Instant用于表达某一时刻,其now()方法返回一个时间戳,和System.currentTimeMillis()方法类似

        Instant instant = Instant.now();
        System.out.println(instant.getEpochSecond());   //精确到秒:1657879596
        System.out.println(instant.toEpochMilli());     //精确到毫秒:1657879596486

事实上,Instant拥有更高的精确度,因为它多了更高精度的纳秒(nanos)的属性
时间戳通过转换就可以表示一个时间,因此加上时区也可以将其转化为ZonedDateTime

        Instant instant = Instant.ofEpochSecond(1657879596);    //手动传入时间戳
        ZonedDateTime zonedDateTime = instant.atZone(ZoneId.systemDefault());   //系统默认时区,这里自然是Asia/Shanghai
        System.out.println(zonedDateTime);          //输出:2022-07-15T18:06:36+08:00[Asia/Shanghai]

如果我们想获取LocalDateTimeZonedDateTime的时间戳形式,可以调用toEpochSecond()方法(返回的是long类型),当然这个单位是秒,获取毫秒则需要手动乘 1000

        ZonedDateTime zonedDateTime = ZonedDateTime.now();  //2022-07-15T18:30:14.462+08:00[Asia/Shanghai]
        long timeStampSec = zonedDateTime.toEpochSecond();	//秒为单位
        long timeStampMilliSec = zonedDateTime.toEpochSecond() * 1000;	//毫秒为单位

        Instant instantFromSec = Instant.ofEpochSecond(timeStampSec);
        Instant instantFromMilliSec = Instant.ofEpochMilli(timeStampMilliSec);
		//两个Instant输出都是:2022-07-15T10:30:14Z

注意一点,根据时间戳(Epoch Time)的定义,其表示的是 0 时区的格林尼治时间,因此和上面的ZonedDateTime相差了 8 个小时(我们是东八区)
需要在后续操作中自己设定时区

两个 API 的转换

既然旧的java.util API和新的java.time API都可以表示日期和时间,难免会涉及两种 API 的转换

旧 API 转为新 API

如果要把旧的DateCalendar转换为新 API 对象,可以通过toInstant()方法转换为Instant对象,再继续转换为ZonedDateTime
当然Date需要手动添加ZoneId,而Calendar本身带有TimeZone时区属性,因此可以通过toZoneId()方法转换为ZoneId属性

//Date -> Instant
Date date = new Date();
Instant instantFromDate = date.toInstant();
//Calendar -> Instant -> ZonedDateTime
Calendar calendar = Calendar.getInstance();
Instant instantFromCalendar = calendar.toInstant();
ZonedDateTime zonedDateTime = instantFromDate.atZone(calendar.getTimeZone().toZoneId());

新 API 转为旧 API

新 API 并不能直接转为旧 API,毕竟提出新的 API 就是为了取代旧的 API,因此只能将时间转为时间戳,再转为旧 API
由于时间戳多为long类型,因此注释中用long来表示时间戳

//ZonedDateTime -> long
ZonedDateTime zonedDateTime = ZonedDateTime.now();
long timeStamp = zonedDateTime.toEpochSecond() * 1000;
//long -> Date
Date date = new Date(timeStamp);
//long -> Calendar
Calendar calendar = Calendar.getInstance();
calendar.clear();
String myZone = zonedDateTime.getZone().getId();    //得到String的时区标识:Asia/Shanghai
calendar.setTimeZone(TimeZone.getTimeZone(myZone)); //设置ZoneId
calendar.setTimeInMillis(timeStamp);    //设置时间

对于时区的表示,旧 API 采用TimeZone,而新 API 采用ZoneId,因此需要ZoneId.getId()方法获得字符串,再让TimeZone根据字符串设定时区。

参考

  1. Java 日期表示
  2. 廖雪峰:日期与时间
  3. java 日期时间
  4. JDK 官方文档:SimpleDateFormat
  5. Joda.org