当前位置: 代码迷 >> 综合 >> 【java快速入门-日期篇】- LocalDate Time and ZonedDateTime and DateTimeFormatter and instant
  详细解决方案

【java快速入门-日期篇】- LocalDate Time and ZonedDateTime and DateTimeFormatter and instant

热度:57   发布时间:2023-12-26 03:48:14.0

LocalDateTime

表示本地时间

public class Main {public static void main(String[] args) {LocalDate d = LocalDate.now(); // 当前日期LocalTime t = LocalTime.now(); // 当前时间LocalDateTime dt = LocalDateTime.now(); // 当前日期和时间System.out.println(d); // 严格按照ISO 8601格式打印System.out.println(t); // 严格按照ISO 8601格式打印System.out.println(dt); // 严格按照ISO 8601格式打印}
}output:
2021-03-27
16:52:53.998297
2021-03-27T16:52:53.998297

注意:获取三个类型的时候,由于代码的执行时间有所不同,3个类型的日期和时间很可能对不上(时间的毫秒数基本上不同)。为了保证获取到同一时刻的日期和时间,可以改写如下:

LocalDateTime dt = LocalDateTime.now(); // 先获取当前日期和时间
LocalDate d = dt.toLocalDate(); // 转换到当前日期
LocalTime t = dt.toLocalTime(); // 转换到当前时间

反过来,通过指定的日期和时间创建LocalDateTime可以通过of()方法

// 指定日期和时间:
LocalDate d2 = LocalDate.of(2019, 11, 30); // 2019-11-30, 注意11=11月
LocalTime t2 = LocalTime.of(15, 16, 17); // 15:16:17
LocalDateTime dt2 = LocalDateTime.of(2019, 11, 30, 15, 16, 17);
LocalDateTime dt3 = LocalDateTime.of(d2, t2);

因为严格按照ISO 8601的格式,因此,将字符串转换为LocalDateTime就可以传入标准格式:

LocalDateTime dt = LocalDateTime.parse("2019-11-19T15:16:17");
LocalDate d = LocalDate.parse("2019-11-19");
LocalTime t = LocalTime.parse("15:16:17")注意ISO 8601规定的日期和时间分隔符是T。标准格式如下:日期:yyyy-MM-dd
时间:HH:mm:ss
带毫秒的时间:HH:mm:ss.SSS
日期和时间:yyyy-MM-dd'T'HH:mm:ss
带毫秒的日期和时间:yyyy-MM-dd'T'HH:mm:ss.SSS

DateTimeFormatter

如果要自定义输出的格式,或者要把一个非ISO 8601格式的字符串解析成LocalDateTime,可以使用新的DateTimeFormatter(后面详细介绍)

public class Main {public static void main(String[] args) {// 自定义格式化:DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");System.out.println(dtf.format(LocalDateTime.now()));// 用自定义格式解析:LocalDateTime dt2 = LocalDateTime.parse("2021/03/27 15:16:17", dtf);//按照格式必须输入两位月值System.out.println(dt2);}
}

LocalDatatime提供了对日期和时间进行加减的非常简单的链式调用:

public class Main {public static void main(String[] args) {LocalDateTime localDateTime = LocalDateTime.now();System.out.println(localDateTime);LocalDateTime localDateTime1 = localDateTime.plusDays(5).minusHours(2);System.out.println(localDateTime1);LocalDateTime localDateTime2 = LocalDateTime.of(2022,12,25,21,52,32);System.out.println(localDateTime2);LocalDateTime localDateTime3 = localDateTime2.plusDays(1).plusMonths(1);System.out.println(localDateTime3);}
}outout:
2021-03-27T17:30:39.205395900
2021-04-01T15:30:39.205395900
2022-12-25T21:52:32
2023-01-26T21:52:32//自动调整年份

对日期和时间进行调整则使用withXxx()方法,例如:withHour(15)会把10:11:12变为15:11:12

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

LocalDateTime还有一个通用的with()方法允许我们做更复杂的运算,即with(TemporalAdjuster adjuster)、with(TemporalField field, long newValue)

其中LocalDateTime类的with(TemporalAdjuster Adjuster)方法用于使用TemporalAdjuster调整此日期时间,并且在调整后返回调整后的日期时间的副本。使用指定的调整器策略对象进行调整。此LocalDateTime实例是不可变的,不受此方法调用的影响。一个简单的调节器用于设置一个字段,例如Year字段,而更复杂的调节器可能将时间设置为一年的最后一天。

LocalDateTime类的with(TemporalField field,long newValue)方法用于将LocalDateTime的指定字段设置为新值并返回新时间的副本。此方法可用于更改任何受支持的字段,例如年,日,月,小时,分钟或秒。如果由于不支持该字段或其他原因而无法设置新值,则会引发异常。此LocalDateTime实例是不可变的,不受此方法调用的影响。

例如:

public class Main {public static void main(String[] args) {// 本月第一天0:00时刻:LocalDateTime firstDay = LocalDate.now().withDayOfMonth(1).atStartOfDay();System.out.println(firstDay);// 本月最后1天:LocalDate lastDay = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth());System.out.println(lastDay);// 下月第1天:firstInMonth实际取得的是当月的第一个周几的功能,并不能实现获取工作日的功能。LocalDate nextMonthFirstDay = LocalDate.now().with(TemporalAdjusters.firstDayOfNextMonth());System.out.println(nextMonthFirstDay);// 本月第1个周一:LocalDate firstWeekday = LocalDate.now().with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY));System.out.println(firstWeekday);LocalDateTime localDateTime = LocalDateTime.now();LocalDateTime localDateTime1 = localDateTime.with(ChronoField.YEAR,2022);System.out.println(localDateTime1); }
}

要判断两个LocalDateTime的先后,可以使用isBefore()isAfter()方法,对于LocalDateLocalTime类似:

public class Main {public static void main(String[] args) {LocalDateTime now = LocalDateTime.now();LocalDateTime target = LocalDateTime.of(2019, 11, 19, 8, 15, 0);System.out.println(now.isBefore(target));System.out.println(LocalDate.now().isBefore(LocalDate.of(2019, 11, 19)));System.out.println(LocalTime.now().isAfter(LocalTime.parse("08:15:00")));}
}

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

public class Main {public static void main(String[] args) {LocalDateTime start = LocalDateTime.now();LocalDateTime end = LocalDateTime.of(2033,5,22,6,52,32);Duration duration = Duration.between(start,end);System.out.println(duration);LocalDate startDate = LocalDate.of(2033,10,5);LocalDate endDate = LocalDate.now();Period period = startDate.until(endDate);System.out.println(period);period = endDate.until(startDate);System.out.println(period);}
}
output:
PT106521H48M40.1040551S//106521小时48分钟40.1040551秒
P-12Y-6M-9D//负值表示
P12Y6M8D//12年6月8天

DurationPeriod的表示方法也符合ISO 8601的格式,它以P...T...的形式表示,P...T之间表示日期间隔,T后面表示时间间隔。如果是PT...的格式表示仅有时间间隔。利用ofXxx()或者parse()方法也可以直接创建Duration

Duration d1 = Duration.ofHours(10); // 10 hours
Duration d2 = Duration.parse("P1DT2H3M"); // 1 day, 2 hours, 3 minutes

ZonedDateTime 

为了表示一个带时区的日期和时间,需要ZoneDateTime,可以简单地把ZonedDateTime理解成LocalDateTimeZoneIdZoneIdjava.time引入的新的时区类,注意和旧的java.util.TimeZone区别。

常用API

public class Main {public static void main(String[] args) {ZonedDateTime zbj = ZonedDateTime.now(); // 默认时区ZonedDateTime zny = ZonedDateTime.now(ZoneId.of("America/New_York")); // 用指定时区获取当前时间System.out.println(zbj);System.out.println(zny);}
}
output:
2021-03-27T22:13:17.766948100+08:00[Asia/Shanghai]
2021-03-27T10:13:17.767943900-04:00[America/New_York]

另一种方式是通过给一个LocalDateTime附加一个ZoneId,就可以变成ZonedDateTime

public class Main {public static void main(String[] args) {LocalDateTime ldt = LocalDateTime.of(2022, 1, 15, 15, 16, 17);ZonedDateTime zbj = ldt.atZone(ZoneId.systemDefault());ZonedDateTime zny = ldt.atZone(ZoneId.of("America/New_York"));System.out.println(zbj);System.out.println(zny);}
}
output:
2022-01-15T15:16:17+08:00[Asia/Shanghai]
2022-01-15T15:16:17-05:00[America/New_York]

时区转换

要转换时区,首先我们需要有一个ZonedDateTime对象,然后,通过withZoneSameInstant()将关联时区转换到另一个时区,转换后日期和时间都会相应调整。

public class Main {public static void main(String[] args) {// 以中国时区获取当前时间:ZonedDateTime zbj = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));// 转换为纽约时间:ZonedDateTime zny = zbj.withZoneSameInstant(ZoneId.of("America/New_York"));System.out.println(zbj);System.out.println(zny);}
}
output:
2021-03-27T22:25:35.052688200+08:00[Asia/Shanghai]
2021-03-27T10:25:35.052688200-04:00[America/New_York]
//注意:要特别注意,时区转换的时候,由于夏令时的存在,不同的日期转换的结果很可能是不同的。

有了ZonedDateTime,将其转换为本地时间就非常简单:

ZonedDateTime zdt = ...
LocalDateTime ldt = zdt.toLocalDateTime();

然后通过这样进行时差计算,会非常精确,并且避免夏时令的问题。

案例:某航线从北京飞到纽约需要13小时20分钟,请根据北京起飞日期和时间计算到达纽约的当地日期和时间。

public class Main {public static void main(String[] args) {LocalDateTime departureAtBeijing = LocalDateTime.of(2019, 9, 15, 13, 0, 0);int hours = 13;int minutes = 20;LocalDateTime arrivalAtNewYork = calculateArrivalAtNY(departureAtBeijing, hours, minutes);System.out.println(departureAtBeijing + " -> " + arrivalAtNewYork);// test:if (!LocalDateTime.of(2019, 10, 15, 14, 20, 0).equals(calculateArrivalAtNY(LocalDateTime.of(2019, 10, 15, 13, 0, 0), 13, 20))) {System.err.println("测试失败!");} else if (!LocalDateTime.of(2019, 11, 15, 13, 20, 0).equals(calculateArrivalAtNY(LocalDateTime.of(2019, 11, 15, 13, 0, 0), 13, 20))) {System.err.println("测试失败!");}}static LocalDateTime calculateArrivalAtNY(LocalDateTime bj, int h, int m) {LocalDateTime bjTemp = bj.plusHours(h).plusMinutes(m);ZonedDateTime localTime = bjTemp.atZone(ZoneId.of("Asia/Shanghai"));//首先确保为本地时间ZonedDateTime newyorkTime = localTime.withZoneSameInstant(ZoneId.of("America/New_York"));//然后转换为纽约时间LocalDateTime newyorkLocalTime = newyorkTime.toLocalDateTime();//再转换为纽约本地时间System.out.println("bjTemp = " + bjTemp);return newyorkLocalTime;}
}

DateTimeFormatter

使用旧的Date对象时,我们用SimpleDateFormat进行格式化显示。使用新的LocalDateTimeZonedLocalDateTime时,我们要进行格式化显示,就要使用DateTimeFormatter

创建DateTimeFormatter时,我们仍然通过传入格式化字符串实现:

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");

另一种创建DateTimeFormatter的方法是,传入格式化字符串时,同时指定Locale

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("E, yyyy-MMMM-dd HH:mm", Locale.US);

例如:

public class Main {public static void main(String[] args) {ZonedDateTime zdt = ZonedDateTime.now();var formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm ZZZZ");System.out.println(formatter.format(zdt));var zhFormatter = DateTimeFormatter.ofPattern("yyyy MMM dd EE HH:mm", Locale.CHINA);System.out.println(zhFormatter.format(zdt));var zhFormatter_us = DateTimeFormatter.ofPattern("yyyy MMM dd EE HH:mm", Locale.US);System.out.println(zhFormatter_us.format(zdt));var usFormatter = DateTimeFormatter.ofPattern("E, MMMM/dd/yyyy HH:mm", Locale.US);System.out.println(usFormatter.format(zdt));}
}

当我们直接调用System.out.println()对一个ZonedDateTime或者LocalDateTime实例进行打印的时候,实际上,调用的是它们的toString()方法,默认的toString()方法显示的字符串就是按照ISO 8601格式显示的,我们可以通过DateTimeFormatter预定义的几个静态变量来引用

var ldt = LocalDateTime.now();
System.out.println(DateTimeFormatter.ISO_DATE.format(ldt));
System.out.println(DateTimeFormatter.ISO_DATE_TIME.format(ldt));
output:
2021-03-28
2021-03-28T20:08:54.4184593
2021-03-28

 Instant

前面我们知道,计算机存储的当前时间,本质上只是一个不断递增的整数。Java提供的System.currentTimeMillis()返回的就是以毫秒表示的当前时间戳。

这个当前时间戳在java.time中以Instant类型表示,我们用Instant.now()获取当前时间戳,效果和System.currentTimeMillis()类似:

public class Main {public static void main(String[] args) {Instant now = Instant.now();System.out.println(now.getEpochSecond()); // 秒System.out.println(now.toEpochMilli()); // 毫秒}
}

一个是以秒为单位的时间戳,一个是更精确的纳秒精度。它和System.currentTimeMillis()返回的long相比,只是多了更高精度的纳秒。

利用Instant创建一个带有时区的ZoneDateTime:

public class Main {public static void main(String[] args) {Instant instant = Instant.ofEpochSecond(22626261);
//对于某一个时间戳,给它关联上指定的ZoneId,就得到了ZonedDateTime,继而可以获得了对应时区的LocalDateTime。ZonedDateTime zonedDateTime = instant.atZone(ZoneId.systemDefault());System.out.println(zonedDateTime);}
}

故综合所有的新的API可以发现

旧API转新API

如果要把旧式的DateCalendar转换为新API对象,可以通过toInstant()方法转换为Instant对象,再继续转换为ZonedDateTime

// Date -> Instant:
Instant ins1 = new Date().toInstant();// Calendar -> Instant -> ZonedDateTime:
Calendar calendar = Calendar.getInstance();
Instant ins2 = calendar.toInstant();
ZonedDateTime zdt = ins2.atZone(calendar.getTimeZone().toZoneId());//旧的TimeZone提供了一个toZoneId(),可以把自己变成新的ZoneId。

新API转旧API

如果要把新的ZonedDateTime转换为旧的API对象,只能借助long型时间戳做一个“中转”:

// ZonedDateTime -> long:
ZonedDateTime zdt = ZonedDateTime.now();
long ts = zdt.toEpochSecond() * 1000;// long -> Date:
Date date = new Date(ts);// long -> Calendar:
Calendar calendar = Calendar.getInstance();
calendar.clear();
calendar.setTimeZone(TimeZone.getTimeZone(zdt.getZone().getId()));//记住二者的区别
calendar.setTimeInMillis(zdt.toEpochSecond() * 1000);

 

 

 

  相关解决方案