标准库API
旧API:定义在java.util
包里,包括Date
、Calendar
、TimeZone
。
新API:Java8引入,定义在java.time
里,包括LocalDateTime
、ZonedDateTime
、ZoneId
。
旧API
Date
例子:
// 获取当前时间:
Date date = new Date();
System.out.println(date.getYear() + 1900); // 必须加上1900
System.out.println(date.getMonth() + 1); // 0~11,必须加上1
System.out.println(date.getDate()); // 1~31,不能加1
// 转换为String:
System.out.println(date.toString()); // Tue Jan 11 18:57:51 CST 2022
// 转换为GMT时区:
System.out.println(date.toGMTString()); //格林尼治时间(晚8小时) 11 Jan 2022 10:57:51 GMT
// 转换为本地时区:
System.out.println(date.toLocaleString()); //本地时区(zh_CN) 2022-1-11 18:57:51
// 自定义格式输出
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(date)); // 2022-01-11 18:57:51
缺点:
(1)不能转换时区,除了toGMTString()
可以按GMT+0:00
输出外,Date总是以当前计算机系统的默认时区为基础进行输出。
(2)很难对日期和时间进行加减,计算两个日期相差多少天,计算某个月第一个星期一的日期等。
Calendar
Calendar
可以用于获取并设置年、月、日、时、分、秒,它和Date
比,主要多了一个可以做简单的日期和时间运算的功能。
例子:
// 获取当前时间:
Calendar c = Calendar.getInstance();
int y = c.get(Calendar.YEAR);//2022
int m = 1 + c.get(Calendar.MONTH);//1
int d = c.get(Calendar.DAY_OF_MONTH);//11
int w = c.get(Calendar.DAY_OF_WEEK);//3(一周从周日开始)
int hh = c.get(Calendar.HOUR_OF_DAY);//19
int mm = c.get(Calendar.MINUTE);//24
int ss = c.get(Calendar.SECOND);//17
int ms = c.get(Calendar.MILLISECOND);//471
//2022-1-11 3 19:24:17.471
System.out.println(y + "-" + m + "-" + d + " " + w + " " + hh + ":" + mm + ":" + ss + "." + ms);
Calendar
只有一种方式获取,即Calendar.getInstance()
,而且一获取到就是当前时间。如果我们想给它设置成特定的一个日期和时间,就必须先清除所有字段:
// 获取当前时间:
Calendar c = Calendar.getInstance();
// 清除所有字段
c.clear();
// 设置year
c.set(Calendar.YEAR, 2021);
// year增加1年
c.add(Calendar.YEAR, 1);
// 打印时间:2022-01-01 00:00:00
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(c.getTime()));
TimeZone
Calendar
和Date
相比,它提供了时区转换的功能。时区用TimeZone
对象表示:
创建时区:
TimeZone tzDefault = TimeZone.getDefault(); // 当前时区
TimeZone tzGMT9 = TimeZone.getTimeZone("GMT+09:00"); // GMT+9:00时区
TimeZone tzNY = TimeZone.getTimeZone("America/New_York"); // 纽约时区
System.out.println(tzDefault.getID()); // Asia/Shanghai
System.out.println(tzGMT9.getID()); // GMT+09:00
System.out.println(tzNY.getID()); // America/New_York
将北京时间2020-01-01 20:15:00
按纽约时间打印:
// 当前时间:
Calendar c = Calendar.getInstance();
// 清除所有:
c.clear();
// 设置为北京时区:
c.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
// 设置年月日时分秒:
c.set(2020,0,1,20,30,0);
// 显示时间:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 2020-01-01 20:30:00
System.out.println(sdf.format(c.getTime()));
// 修改 sdf 的时区信息
sdf.setTimeZone(TimeZone.getTimeZone("America/New_York"));
// 2020-01-01 07:30:00
System.out.println(sdf.format(c.getTime()));
新API
(1)从Java 8开始,java.time
包提供了新的日期和时间API,主要涉及的类型有:
- 本地日期和时间:
LocalDateTime
,LocalDate
,LocalTime
; - 带时区的日期和时间:
ZonedDateTime
; - 时刻:
Instant
; - 时区:
ZoneId
,ZoneOffset
; - 时间间隔:
Duration
。
(2)以及一套新的用于取代SimpleDateFormat
的格式化类型DateTimeFormatter
。
(3)和旧的API相比,新API严格区分了时刻、本地日期、本地时间和带时区的日期时间,并且,对日期和时间进行运算更加方便。
(4)此外,新API修正了旧API不合理的常量设计:
- Month的范围用1~12表示1月到12月;
- Week的范围用1~7表示周一到周日。
(5)最后,新API的类型几乎全部是不变类型(和String类似),可以放心使用不必担心被修改。
LocalDateTime、LocalDate、LocalTime
(1)本地日期和时间通过now()获取到的总是以当前默认时区返回的,和旧API不同,LocalDateTime
、LocalDate
和LocalTime
默认严格按照ISO 8601规定的日期和时间格式进行打印。
LocalDate d = LocalDate.now();// 当前日期
LocalTime t = LocalTime.now();// 当前时间
LocalDateTime dt = LocalDateTime.now();// 当前日期和时间
//严格按照ISO 8601格式打印
System.out.println(d);//2022-01-12
System.out.println(t);//08:33:00.768
System.out.println(dt);//2022-01-12T08:33:00.768
(2)LocalDateTime 转 LocalDate, LocalTime:
LocalDateTime dt = LocalDateTime.now(); // 当前日期和时间
LocalDate d = dt.toLocalDate(); // 转换到当前日期
LocalTime t = dt.toLocalTime(); // 转换到当前时间
(3)指定日期创建 LocalDateTime:
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);
(4)按照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");
(5)对日期和时间进行加减:
注意到月份加减会自动调整日期,例如从2019-10-31
减去1个月得到的结果是2019-09-30
,因为9月没有31日。
LocalDateTime dt = LocalDateTime.of(2019, 10, 26, 20, 30, 59);
System.out.println(dt);// 2019-10-26T20:30:59
// 加5天减3小时:
LocalDateTime dt2 = dt.plusDays(5).minusHours(3);
System.out.println(dt2); // 2019-10-31T17:30:59
// 减1月:
LocalDateTime dt3 = dt2.minusMonths(1);
System.out.println(dt3); // 2019-09-30T17:30:59
(6)设置日期和时间:
对日期和时间进行调整则使用withXxx()
方法,例如:withHour(15)
会把10:11:12
变为15:11:12
:
- 调整年:withYear()
- 调整月:withMonth()
- 调整日:withDayOfMonth()
- 调整时:withHour()
- 调整分:withMinute()
- 调整秒:withSecond()
(7)通用的with()
方法做更复杂的运算:
// 本月第一天0:00时刻:
LocalDateTime firstDay = LocalDate.now().withDayOfMonth(1).atStartOfDay();
// 本月最后1天:
LocalDate lastDay = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth());
// 下月第1天:
LocalDate nextMonthFirstDay = LocalDate.now().with(TemporalAdjusters.firstDayOfNextMonth());
// 本月第1个周一:
LocalDate firstWeekday = LocalDate.now().with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY));
(8)要判断两个LocalDateTime
的先后:
LocalDate d_2020_7_1 = LocalDate.of(2020, 7, 1);
LocalDate d_2020_8_1 = LocalDate.of(2020, 8, 1);
boolean b1 = d_2020_7_1.isBefore(d_2020_8_1);//true
boolean b2 = d_2020_7_1.isAfter(d_2020_8_1);//false
boolean b3 = d_2020_7_1.isEqual(d_2020_8_1);//false
DateTimeFormatter
(1)用于自定义输出格式,或者把一个非ISO 8601格式的字符串解析成LocalDateTime
。
// 自定义输出格式:
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
String s1 = dtf.format(LocalDateTime.now());//2022/01/12 08:48:10
// 用自定义格式解析:
LocalDateTime dt = LocalDateTime.parse("2020/01/02 15:16:17", dtf);
System.out.println(dt);//2020-01-02T15:16:17
(2)创建DateTimeFormatter时,指定地区习惯Locale:
ZonedDateTime zdt = ZonedDateTime.now();
//默认地区
DateTimeFormatter dtf1 = DateTimeFormatter.ofPattern("yyyy MMM dd EE HH:mm");
System.out.println(dtf1.format(zdt));//2022 一月 12 星期三 15:23
//中国地区
DateTimeFormatter dtf2 = DateTimeFormatter.ofPattern("yyyy MMM dd EE HH:mm", Locale.CHINA);
System.out.println(dtf2.format(zdt));//2022 一月 12 星期三 15:23
//美国地区
DateTimeFormatter dtf3 = DateTimeFormatter.ofPattern("yyyy MMM dd EE HH:mm", Locale.US);
System.out.println(dtf3.format(zdt));//2022 Jan 12 Wed 15:23
ZonedDateTime
(1)
LocalDateTime
无法与时间戳进行转换,因为LocalDateTime
没有时区,无法确定某一时刻。
ZonedDateTime
相当于LocalDateTime
加时区的组合,它具有时区,可以与long
表示的时间戳进行转换。
可以简单地把ZonedDateTime
理解成LocalDateTime
加ZoneId
。ZoneId
是java.time
引入的新的时区类,注意和旧的java.util.TimeZone
区别。
(2)指定时间:
//创建时间
LocalDateTime dt = LocalDateTime.of(2020, 1, 2, 15, 16, 17);
//创建时区
ZoneId zone_shanghai = ZoneId.of("Asia/Shanghai");
ZoneId zone_0800 = ZoneId.of("+08:00");
ZoneId zone_utc = ZoneOffset.UTC;
ZoneId zone_0000 = ZoneId.of("+00:00");
System.out.println(zone_shanghai);//Asia/Shanghai
System.out.println(zone_0800);//+08:00
System.out.println(zone_utc);//Z
System.out.println(zone_0800.equals(zone_shanghai));//false
System.out.println(zone_0000.equals(zone_utc));//true
//给时间指定时区(shanghai)
ZonedDateTime zdt_shanghai = ZonedDateTime.of(dt, zone_shanghai);
//上海时区 的时间
System.out.println(zdt_shanghai);//2020-01-02T15:16:17+08:00[Asia/Shanghai]
//上海时区 对应的时刻(即0时区现在的时间)
System.out.println(zdt_shanghai.toInstant());//2020-01-02T07:16:17Z
//给时间指定时区(+08:00)
ZonedDateTime zdt_0800 = ZonedDateTime.of(dt, zone_0800);
//东8区 的时间
System.out.println(zdt_0800);//2020-01-02T15:16:17+08:00
//东8区 对应的时刻(即0时区现在的时间)
System.out.println(zdt_0800.toInstant());//2020-01-02T07:16:17Z
//东8区 与 上海时区,虽然时刻相等,但本身不相等
System.out.println(zdt_0800.equals(zdt_shanghai));//false
//给时间指定时区(UTC)
ZonedDateTime zdt_utc = ZonedDateTime.of(dt, zone_utc);
//UTC(0时区)的时间
System.out.println(zdt_utc);//2020-01-02T15:16:17Z
//UTC 对应的时刻(即0时区现在的时间)
System.out.println(zdt_utc.toInstant());//2020-01-02T15:16:17Z
(3)当前时间:
ZonedDateTime zbj = ZonedDateTime.now(); // 默认时区
ZonedDateTime zny = ZonedDateTime.now(ZoneId.of("America/New_York")); // 用指定时区获取当前时间
(4)时区转换(将北京时间转换为纽约时间):
// 以中国时区获取当前时间:
ZonedDateTime zbj = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
// 转换为纽约时间:
ZonedDateTime zny = zbj.withZoneSameInstant(ZoneId.of("America/New_York"));
输出:
2022-01-12T14:45:43.906+08:00[Asia/Shanghai]
2022-01-12T01:45:43.906-05:00[America/New_York]
Duration和Period
(1)Duration:时刻的间隔。Period:日期的间隔。
(2)Duration 和 Period 的表示方法符合ISO 8601的格式,它以P...T...
的形式表示,P...T
之间表示日期间隔,T
后面表示时间间隔。如果是PT...
的格式表示仅有时间间隔。
Duration
格式:PT...
,Period
格式:P...
。
(3)
Duration:
/*--- 创建Duration ---*/
Duration d1 = Duration.ofDays(1);
System.out.println(d1);//PT24H
Duration d2 = Duration.of(1, ChronoUnit.HOURS);
System.out.println(d2);//PT1H
Duration d3 = Duration.ofHours(2);
System.out.println(d3);//PT2H
Duration d4 = Duration.parse("PT1H2M3.123456789S");
System.out.println(d4);//PT1H2M3.456789123S
Duration d5 = Duration.parse("-PT1H");
System.out.println(d5);//PT-1H
Duration d6 = Duration.between(LocalDateTime.of(2020, 10, 1, 15, 15, 15), LocalDateTime.of(2020, 10, 1, 16, 16, 16));
System.out.println(d6);//PT1H1M1S
Period:
/*--- 创建Period ---*/
Period p1 = Period.ofDays(1);
System.out.println(p1);//P1D
Period p2 = Period.of(1, 2, 3);
System.out.println(p2);//P1Y2M3D
Period p3 = Period.parse("P1D");
System.out.println(p3);//P1D
Period p4 = Period.between(LocalDate.of(2020, 10, 1), LocalDate.of(2020, 10, 2));//只能传入LocalDate
System.out.println(p4);//P1D
Instant
Instant 代表时刻。
我们用Instant.now()
获取当前时间戳,效果和System.currentTimeMillis()
类似:
Instant now = Instant.now();
System.out.println(now); // 2022-01-12T07:41:22.862Z
System.out.println(now.getEpochSecond()); // 转换为秒
System.out.println(now.toEpochMilli()); // 转换为毫秒
最佳实践
旧API转新API
如果要把旧式的Date
或Calendar
转换为新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());
新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()));
calendar.setTimeInMillis(zdt.toEpochSecond() * 1000);