Java8特性-时间API参照手册(实用版)
文章目录
如果想查看如何记录算法的时间和比较算法的性能,直接跳转1.6节和1.7节。如果想知道如何DIY日期安排,直接跳转2.2节。如果想知道如何将日期格式化输出,直接跳转第5节。如果想知道LocalDateTime和Instant和ZonedDateTime之间的相互转化,直接跳转第6节。
0.前言
0.1章节介绍
本文讲解的思路首先是Instant和Duration。Instant有两个重要的属性即seconds和nanos,其用来表示时间线上的某个特定时刻,当然这个时刻可读性不强,毕竟没有人会很快地意识到60seconds, 0nanos代表的是1970年1月1日0时1分整;Duration也有两个重要的属性seconds和nanos,可以借助Duration来对两个时刻(时刻至少精确到seconds)求间隔,这样的一个很好的应用就是计算程序运行的时间。
其次,会讲解LocalTime和LocalDate和LocalDateTime,这些时间表示的方式更贴近人的思维;Period可以来求两个LocalDate的间隔,这样可以弥补Duration要求时间对象必须支持seconds的不足。
然后,会补充一下讲时区的时间ZonedDateTime,毕竟东八区的当地时间和东九区的当地时间是不一样的。
再然后,会谈到时间的格式化解析,让时间以你想要的方式进行输出。
最后,会讲及LocalDateTime和Instant和ZonedDateTime之间的相互转化。
0.2预备知识
下面的图展示了Instant和LocalDateTime在时间线的表示以及Instant、LocalDateTime,LocalDate,LocalTime的重要属性。
下面的图要有一个印象,其目的在于知道函数的参数到底是什么,就好比CharSequence的参数,你要明白可以传String一样。
TemporalAccessor代表对Temporal对象的只读权限,这可以理解为一个功能性接口,定义了读的权限。
Temporal泛指时间类对象,比如ZonedDateTime,LocalDateTime,这个接口定义了对Temporal对象的读和写权限。
TemporalAdjuster代表的是调整器,它是一个函数式接口,提供一个方法Temporal adjustInto(Temporal temporal);
,其表示如何根据一个Temporal对象转换为另一个Temporal对象。TemporalAdjuster有一个工具类TemporalAdjusters,该类预定义了一些方法。这个工具类在DIY定制日程安排上会用得到。
TemporalAmount泛指时间间隔,比如3小时,5天。它的常见的两个实现类就是Period和Duration,前者用于计算日期的间隔,比如3天,5年。后者用于计算更加精确的时间间隔,比如2秒,3纳秒。
TemporalField代表时间的字段,这个不好表达它的意思。我就用它的常见的枚举类ChronoField我举例说明,
@Test
public void testChronoField(){
LocalDateTime now = LocalDateTime.now();
System.out.println(now);
//拿到当月的有关日的字段
int result1 = now.get(ChronoField.DAY_OF_MONTH);
System.out.println(result1);
//拿到当周有关日的字段
int result2 = now.get(ChronoField.DAY_OF_WEEK);
System.out.println(result2);
int result3 = now.get(ChronoField.DAY_OF_YEAR);
System.out.println(result3);
}
测试结果:
2020-10-14T14:27:52.652
14
3
288
DAY_OF_MONTH,指的是当天是当月的第几天。
DAY_OF_WEEK,指的是当天是当周的第几天。
DAY_OF_YEAR,指的是当天是当年的第几天。
TemporalUnit代表的是时间单位,比如小时,分钟,天等等。它的常用的枚举类就是ChronoUnit。
1.Instant和Duration
1.1Instant实例创建
Instant代表的是时间线的一个时刻,它有两个重要的属性seconds和nanos来表示这个时刻。不过需要注意的是nanos的部分永远是正数,详情见下面代码。
//代表seconds的部分
private final long seconds;
//代表nanos的部分,这个部分的范围是0--999 999 999,其作用是用来修正seconds。
//比如 纪元即1970年1月1日00:00:00 的前1纳秒的表示不是 0 seonds -1 nanos 而是-1seconds 999 999 999 nanos
private final int nanos;
Instant类常见的创建对象的对象方式分为两类:
//创建当前时间的Instant对象
static Instant now();
//下面的三个方法都是传入相对于纪元时刻的秒数或是毫秒数来创建Instant对象
static Instant ofEpochMilli(long epochMilli) ;
static Instant ofEpochSecond(long epochSecond) ;
static Instant ofEpochSecond(long epochSecond, long nanoAdjustment) ;
测试代码:
@Test
public void testCreateInstant(){
//创建当前时刻
Instant now = Instant.now();
//创建纪元后1s的时刻
Instant instant1 = Instant.ofEpochMilli(1000);
Instant instant2 = Instant.ofEpochSecond(1);
Instant instant3 = Instant.ofEpochSecond(1, 0);
System.out.println(now);
System.out.println(instant1);
System.out.println(instant2);
System.out.println(instant3);
}
测试结果:
2020-10-13T09:00:38.937Z
1970-01-01T00:00:01Z
1970-01-01T00:00:01Z
1970-01-01T00:00:01Z
1.2Instant比较
Instant的常见一个问题就是比较。由于Instant,LocalTime,LocalDate等时间类都实现了Comparable接口,所以可以用compareTo方法进行比较,其次Instant类内置了isBefore和isAfter方法也可以用来比较,最后也可以通过Duration来进行比较。
API如下:
//Instant类的方法
int compareTo(Instant another);
//Instant类的方法
boolean isBefore(Instant otherInstant);
boolean isAfter(Instant otherInstant);
//Duration类的方法
//判断时间间隔是否为负
boolean isNegative() ;
测试代码:
/**
* 1.比较两个Instant的实例对象
* 1.1.Instant和LocalDate,LocalTime,LocalDateTime.
* 以及用于比较Instant,LocalTime,LocalDateTime的Duration
* 比较LocalDate的Period都实现了
* Comparable接口。
*
* 所以可以使用int compareTo(Instant another)方法进行两个Instant实例对象的比较
*
* 1.2.通过 boolean isAfter(Instant another) 或是 boolean isBefore(Instant another)比较
*
* 1.3 通过Duration 的静态方法
* public static Duration between(Temporal startInclusive, Temporal endExclusive)
* 获得两个时间点Instant的间隔Duration后。
* 调用Duration对象的boolean isNegative()方法来判断,如果为true,则前者更晚,如果为false,则后者更晚
*/
@Test
public void compareInstant(){
Instant min = Instant.MIN;
Instant max = Instant.MAX;
//1.通过int compareTo(T another)比较
if (max.compareTo(min)>0){
System.out.println("max的时间更晚");
}
//2.通过 boolean isAfter(Instant another) 或是 boolean isBefore(Instant another)比较
if (max.isAfter(min)){
System.out.println("max的时间更晚");
}
//3. 通过boolean isNegative()方法比较
Duration duration = Duration.between(min, max);
if (!duration.isNegative()){
System.out.println("max的时间更晚");
}
}
测试结果:
max的时间更晚
max的时间更晚
max的时间更晚
1.3Instant的类似get和set
见下面的API解释
//将Instant转换为1970-01-01T00:00:00Z的时期的毫秒数
long toEpochMilli() ;
//获得nanos部分
int getNano() ;
//获得seconds部分
long getEpochSecond() ;
//获得特定field部分,这个field要求必须是ChronoField的下面4个枚举对象
//NANO_OF_SECOND
//MICRO_OF_SECOND
//MILLI_OF_SECOND
//INSTANT_SECONDS:
int get(TemporalField field);
long get(TemporalField field);
//将特定字段转为新值,并返回一个新的Instant 对象
//field的要求如get方法
Instant with(TemporalField field, long newValue)
测试代码:
@Test
public void testInstantGetAndWith(){
/**
* 类似于Get
*/
Instant now = Instant.now();
long epochSecond = now.getEpochSecond();
int nano = now.getNano();
//这里与Instant的创建实例方法:static Instant ofEpochMilli(long epochMilli)对应
//将now转为毫秒数
long epochMilli = now.toEpochMilli();
//获得Instant的nano部分,并转为Micro
long aLong = now.getLong(ChronoField.MICRO_OF_SECOND);
System.out.println(now);
System.out.println(epochSecond);
System.out.println(nano);
System.out.println(epochMilli);
System.out.println(aLong);
/**
* 类似于set
* 但实际上Instant是一个不可变的类,这个方法会返回一个新的实例。
*/
Instant instant1 = now.with(ChronoField.INSTANT_SECONDS, 0);
Instant instant2 = now.with(ChronoField.NANO_OF_SECOND, 0);
System.out.println(instant1);
System.out.println(instant2);
}
测试结果:
2020-10-14T01:16:36.853Z
1602638196
853000000
1602638196853
853000
1970-01-01T00:00:00.853Z
2020-10-14T01:16:36Z
1.4Instant的plus和minus
这个太简单,比如在Instant实例对象的基础上添加1个ns等等。
@Test
public void testInstantPlus(){
Instant instant1 = Instant.now();
Instant instant2 = instant1.plusMillis(3);
Instant instant3 = instant1.minusNanos(3);
}
类似的,敲一敲就行了。
1.5Duration的实例创建
Duration的两个常见的实例域seconds和nanos,这里和Instant是类似的,seconds表示秒的部分,nanos部分用于修正,范围为0到999 999 999.
创建Duration的实例常见的方法是between方法和of方法,如下面所示:
//Temproal代表的实例对象必须要有seconds部分,也就是说LocalDate不能当做参数传入
//这个方法的含义就是取两个Temporal对象的差值
static Duration between(Temporal startInclusive, Temporal endExclusive) ;
//获取大小为amount,单位为unit的Duration
static Duration of(long amount, TemporalUnit unit)
//获取大小为days,单位为Day的Duration
static Duration ofDays(long days)
static Duration ofHours(long hours)
static Duration ofMillis(long millis)
//............. 还有一些ofXXX的方法就不展示了
测试代码:
@Test
public void testCreateDuration2(){
Instant now = Instant.now();
//加三个小时
Instant over3Hours = now.plus(3, ChronoUnit.HOURS);
//between方法,创建间隔为3小时的Duration
Duration duration1 = Duration.between(now, over3Hours);
//of创建间隔为3小时的Duration
Duration duration2 = Duration.ofHours(3);
Duration duration3 = Duration.of(3, ChronoUnit.HOURS);
if (duration1.equals(duration2) && duration1.equals(duration3)){
System.out.println("三者相等");
}
}
测试结果:
三者相等
1.6Duration来计算算法时间
这一小节用Duration来计算算法的时间,相关的解释已经放在了代码内部。
/**
* 2.计算算法的执行时间
* Duration可以计算两个时刻的差值,所以常用Duration来计算算法的执行时间
* Duration 有两个域存储时间差 seconds 和 nanos
* long seconds; 表示秒的部分
* int nanos; 表示纳秒的部分(范围0到 999 999 999),用于修正seconds
*
* int getNano() :获得nano的部分
* .
* long getSeconds() :获得seconds的部分
*
* 2.1如果精确度不高,那么可以直接调用getSeconds() 获得seconds的部分
*
* 2.2
* long toDays()
* long toHours()
* long toMinutes()
* 上面三个方法是将seconds 的部分进行转化,忽略nanos部分。
* 底层就是除法,所以会产生整数截断情况。
*
* 下面两个办法是将seconds和nanos进行转化。
* long toMillis()
* long toNanos()
*
* 一般情况下,转换不会出现溢出情况。
* 毕竟当Duration跨度300年左右时间,toNanos才会产生溢出
*
* 假如我就是想得到us呢?
* 你得到ns后除以1000就可以了呀
*/
@Test
public void calculateAlgorithm(){
Instant begin = Instant.now();
/** 模拟算法执行 **/
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
/** 模拟算法执行 **/
Instant end = Instant.now();
Duration duration = Duration.between(begin, end);
//1. long getSeconds()
long seconds = duration.getSeconds();
System.out.println("算法用时"+seconds+"s");
//2. long toHours()
long hours = duration.toHours();
System.out.println("算法用时"+hours+"h");
//3. long toMillis()
// long toNanos()
long millis = duration.toMillis();
System.out.println("算法用时"+millis+"ms");
long nanos = duration.toNanos();
System.out.println("算法用时"+nanos+"ns");
}
测试结果:
算法用时1s
算法用时0h
算法用时1001ms
算法用时1001000000ns
1.7Duration来比较两个算法快慢
这一小节讲解如何通过Duration来比较两个算法的快慢.
/**
* 3.比较算法的快慢
* 1.通过获取算法的准确时间然后比较(见2)
*
* 以比较一个算法是不是另一个算法的10倍为例
*/
@Test
public void compareAlgorithm(){
Instant begin1 = Instant.now();
/** 模拟算法1执行 **/
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
/** 模拟算法1执行 **/
Instant end1 = Instant.now();
Instant begin2 = Instant.now();
/** 模拟算法2执行 **/
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
/** 模拟算法2执行 **/
Instant end2 = Instant.now();
Duration duration1 = Duration.between(begin1, end1);
Duration duration2 = Duration.between(begin2, end2);
if(duration1.multipliedBy(10).minus(duration2).isNegative()){
System.out.println("算法1至少是算法2的10倍速");
}
if (duration1.toNanos()*10<duration2.toNanos()){
System.out.println("算法1至少是算法2的10倍速");
}
}
测试结果:
算法1至少是算法2的10倍速
算法1至少是算法2的10倍速
1.8关于Duration的补充
Duration代表的是一个时间间隔,这个时间间隔也可能是代表是负数,想象一下,假如有人往between方法传入的参数刚好弄反了呢?为了避免这个情况,可以采用一个方法abs(),它的含义就是取Duration的绝对值。
测试代码:
@Test
public void testABS(){
Instant min = Instant.MIN;
Instant max = Instant.MAX;
//我把小的时刻传到后面的参数
Duration duration = Duration.between(max, min);
Duration duration1 = duration.abs();
Duration duration2 = duration1.abs();
System.out.println(duration);
System.out.println(duration1);
System.out.println(duration2);
}
测试结果:
PT-17531640008784H0.000000001S
PT17531640008783H59M59.999999999S
PT17531640008783H59M59.999999999S
2.LocalDateTime
LocalDateTime代表的本地的时间,比如2020年9月12日12点20分37秒,与此相对的是时区时间ZonedDateTime,比如北京2020年9月12日12点20分37秒的时刻就对应东京2020年9月12日13点20分37秒,时区时间后面会讲到,这里就不赘述。与LocalDateTime类似的就是LocalDate和LocalTime,LocalDateTime就是由LocalDate和LocalTime构成的,懂了LocalDateTime,其他两个也就可以猜个七七八八,所以这两个也不再赘述。
2.1LocalDateTime实例创建
主要还是两种方法,一个是now,获取当前时间,另外一个是of,获取定制时间
//获得系统的当地时间
static LocalDateTime now() ;
//从指定时区的系统时钟获取当前的日期时间
static LocalDateTime now(ZoneID id);
//获得定制的本地时间
static LocalDateTime of(int year, int month, int dayOfMonth, int hour, int minute)
static LocalDateTime of(int year, int month, int dayOfMonth, int hour, int minute, int second)
//..............还有很多of的重载方法,参照API即可
测试代码:
@Test
public void testCreateLocalDateTime(){
//获得系统默认的本地时间
LocalDateTime now0= LocalDateTime.now();
//获得特定时区的当地时间
LocalDateTime now1 = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
System.out.println(now0);
System.out.println(now1);
LocalDateTime localDateTime = LocalDateTime.of(LocalDate.now(), LocalTime.now());
System.out.println(localDateTime);
}
测试结果:
2020-10-13T19:45:20.168
2020-10-13T19:45:20.169
2020-10-13T19:45:20.169
这里有一个问题就是为什么能获得系统的默认当地时间而不是GMT(格林威治时间)呢。这是因为每个时区都有一个特定的ID,当你执行创建LocalDateTime对象的时候,如果没有显式指定ZoneID,那么系统会获取你本地的默认ID,然后创建LocalDateTime对象。
参考代码:
打印所有时区ID:
/**
* 时区是动态获取的,并不是枚举量或是常量。
* 时区都对应一个ID,比如Asia/Aden
*
* 打印时区ID的数量,并打印所有id
*/
@Test
public void testGetAvailableZoneId(){
Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();
System.out.println(availableZoneIds.size());
availableZoneIds.forEach(System.out::println);
}
600
Asia/Aden
America/Cuiaba
Etc/GMT+9
Etc/GMT+8…
Europe/Athens
US/Pacific
Europe/Monaco
获得当地时区ID:
@Test
public void testLocalZonedId(){
ZoneId zoneId = ZoneId.systemDefault();
System.out.println(zoneId.getId());
}
测试结果:
Asia/Shanghai
2.2其他重要的方法实践(定制日程)
LocalDateTime和Instant其实差不多,只不过一个是机器看着方便,一个是人看着方便,比如plus和get方法都差不多。这里主要谈谈日程安排的一些应用就可以了。这里常用的就是with方法了。
//传入一个时间矫正器,并返回一个新的LocalDateTime
public LocalDateTime with(TemporalAdjuster adjuster)
测试代码如下:
@Test
public void testWith(){
LocalDateTime now = LocalDateTime.now();
System.out.println(now);
//本月的头天
LocalDateTime localDateTime = now.with(TemporalAdjusters.firstDayOfMonth());
System.out.println(localDateTime);
//下周周五
LocalDateTime localDateTime1 = now.with(TemporalAdjusters.next(DayOfWeek.FRIDAY));
System.out.println(localDateTime1);
//传入一个TemporalAdjustor函数式接口对象,定义下一个工作日
LocalDateTime nextWorkDay= now.with(ele->{
LocalDateTime temp = (LocalDateTime)ele;
int weekValue = temp.getDayOfWeek().getValue();
//如果是周五周六周日,那么下个工作日就是下一个周一
if (weekValue/5 ==1){
return temp.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
}
//如果是周一到周四,则返回当日的明天
return temp.plusDays(1);
});
System.out.println(nextWorkDay);
}
测试结果:
2020-10-14T10:26:08.724
2020-10-01T10:26:08.724
2020-10-16T10:26:08.724
2020-10-15T10:26:08.724
3.Period
与Duration相对的就是Period,这个Period对象用于表示两个LocalDate的间隔。
测试代码:
@Test
public void testPeriod(){
//中国建国日
LocalDate foundedDay = LocalDate.of(1949,10,1);
LocalDate now = LocalDate.now();
Period period = Period.between(foundedDay, now);
System.out.println(period);
}
测试结果:
P71Y13D
4.ZonedDateTime
这是一个与时区相关的日期表示形式,它的很多方法其实和LocalDateTime也差不多,下面就展示时区的切换:
测试代码:
@Test
public void testZonedTimeTransfer(){
ZonedDateTime now = ZonedDateTime.now();
System.out.println(now);
//获得美国纽约此时的时区时间
ZonedDateTime zonedDateTime = now.withZoneSameInstant(ZoneId.of("America/New_York"));
System.out.println(zonedDateTime);
//切换成当地时间相同的美国纽约时间
ZonedDateTime zonedDateTime1 = now.withZoneSameLocal(ZoneId.of("America/New_York"));
System.out.println(zonedDateTime1);
}
测试结果:
2020-10-14T10:45:22.357+08:00[Asia/Shanghai]
2020-10-13T22:45:22.357-04:00[America/New_York]
2020-10-14T10:45:22.357-04:00[America/New_York]
5.时间的格式化解析
如何让时间格式化打印输出呢?这里要涉及到一个类:DateTimeFormatter.这个类已经预定义了一些标准的格式,当然,也提供了方法让我们自己设计输出的格式。下面我摘录了Java核心技术卷2预定义的标准格式和自定义格式需要用到的参数(也可以查看官网):
关于格式化解析有两类重要的方法,一类是将String转为特定的日期类型,一类是将日期类型转为特定的日期格式并打印输出:
//创建定制格式的DateTimeFormatter
static DateTimeFormatter ofPattern(String pattern);
//与此相对的就是预定义的DateTimeFormatter,可以参照代码
//将特定的时间对象按照DateFormatter的格式打印输出
String format(TemporalAccessor temporal);
//通过调用特定时间的类型的静态parse方法,传入相应的DateTimeFormatter和String 文本
//以LocalDateTIme的静态方法 parse为例
static LocalDateTime parse(CharSequence text, DateTimeFormatter formatter);
测试代码:
@Test
public void test(){
//内置解析器
DateTimeFormatter isoLocalDateTimeFormatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
String result2 = isoLocalDateTimeFormatter.format(LocalDateTime.now());
System.out.println(result2);
//DIY定制解析器
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
String result3 = dateTimeFormatter.format(LocalDateTime.now());
String result4 = dateTimeFormatter.format(ZonedDateTime.now());
System.out.println(result3);
System.out.println(result4);
//静态方法:利用特定的日期格式和特定的格式器,转换为对应的日期。
LocalDateTime localDateTime = LocalDateTime.parse("2020年10月13日 14:14:05", dateTimeFormatter);
System.out.println(localDateTime);
}
测试结果:
2020-10-14T11:06:45.42
2020年10月14日 11:06:45
2020年10月14日 11:06:45
2020-10-13T14:14:05
6.LocalDateTime和Instant和ZonedDateTime的相互转化
当然,转化的方法不止一种,我就附上我用到的方法。
测试代码:
@Test
public void transfer(){
//创建三个不同类型对象
LocalDateTime localDateTime = LocalDateTime.now();
Instant instant = Instant.now();
ZonedDateTime zonedDateTime = ZonedDateTime.now();
System.out.println(localDateTime);
System.out.println(instant);
System.out.println(zonedDateTime);
System.out.println("..............................分割线.......................");
/**
* LocalDateTime转为ZonedTime
*/
//将当地时间转为特定时区的时间
ZonedDateTime zonedDateTime1 = localDateTime.atZone(ZoneId.of("Asia/Shanghai"));
//将当地时间转为系统默认时区的时间
ZonedDateTime zonedDateTime2 = localDateTime.atZone(ZoneId.systemDefault());
//效果同上面两个
ZonedDateTime zonedDateTime3 = ZonedDateTime.of(localDateTime, ZoneId.of("Asia/Shanghai"));
ZonedDateTime zonedDateTime4 = ZonedDateTime.of(localDateTime, ZoneId.systemDefault());
System.out.println(zonedDateTime1);
System.out.println(zonedDateTime2);
System.out.println(zonedDateTime3);
System.out.println(zonedDateTime4);
System.out.println("..............................分割线.......................");
/**
* LocalDateTime转为Instant
*/
//此方法需要指定LocalDateTime所在的时区。ZoneOffset是ZoneID的子类
Instant instant1 = localDateTime.toInstant(ZoneOffset.of("+8"));
System.out.println(instant1);
System.out.println("..............................分割线.......................");
/**
* Instant转为LocalDateTime
*/
LocalDateTime localDateTime1 = LocalDateTime.ofInstant(instant, ZoneId.of("Asia/Shanghai"));
System.out.println(localDateTime1);
System.out.println("..............................分割线.......................");
/**
* Instant转为ZonedDateTime
*/
ZonedDateTime zonedDateTime5 = instant.atZone(ZoneId.of("Asia/Shanghai"));
ZonedDateTime zonedDateTime6 = ZonedDateTime.ofInstant(instant, ZoneId.of("Asia/Shanghai"));
System.out.println(zonedDateTime5);
System.out.println(zonedDateTime6);
System.out.println("..............................分割线.......................");
/**
* ZonedDateTime转为Instant
*/
Instant instant2 = zonedDateTime.toInstant();
System.out.println(instant2);
System.out.println("..............................分割线.......................");
/**
* ZonedDateTime转为LocalDateTime
*/
LocalDateTime localDateTime2 = zonedDateTime.toLocalDateTime();
System.out.println(localDateTime2);
}
测试结果:
2020-10-14T12:29:16.628
2020-10-14T04:29:16.628Z
2020-10-14T12:29:16.629+08:00[Asia/Shanghai]
…分割线…
2020-10-14T12:29:16.628+08:00[Asia/Shanghai]
2020-10-14T12:29:16.628+08:00[Asia/Shanghai]
2020-10-14T12:29:16.628+08:00[Asia/Shanghai]
2020-10-14T12:29:16.628+08:00[Asia/Shanghai]
…分割线…
2020-10-14T04:29:16.628Z
…分割线…
2020-10-14T12:29:16.628
…分割线…
2020-10-14T12:29:16.628+08:00[Asia/Shanghai]
2020-10-14T12:29:16.628+08:00[Asia/Shanghai]
…分割线…
2020-10-14T04:29:16.629Z
…分割线…
2020-10-14T12:29:16.629
完结撒花!!!