日期时间API

参考:https://lw900925.github.io/java/java8-newtime-api.html

旧日期时间API

System

java.lang.System类提供一个long currentTimeMillis() 静态方法:

获取当前时间与1970年1月1日0时0分0秒之间的UTC时间差 (ms),也叫做时间戳。

常用的时间标准主要有:

  • UTC (Coordinated Universal Time):世界标准时间,各国都基于UTC进行协调同步

  • GMT (Greenwich Mean Time):格林威治时间 (伦敦时间),等于UTC+0

  • CST (China Standard Time):中国标准时间 (北京时间),等于UTC+8

@Test
public void test01() throws InterruptedException {long start = System.currentTimeMillis();Thread.sleep(5000);long end = System.currentTimeMillis();System.out.println("程序执行时间 (ms): " + (end - start)); //5000
}

java.util.Date

表示特定的时刻,精确到毫秒,不包含时区信息。

构造器:

  • Date() :创建一个当前时间的Date对象

  • Date(long date) :创建一个指定时间戳的Date对象

常用方法:

  • long getTime() : 返回GMT时间戳 (ms),等同于System.currentTimeMillis()

  • String toString() : 转换成字符串形式,格式:dow mon dd hh:mm:ss zzz yyyy

    dow指星期几 (Sun, Mon, Tue, Wed, Thu, Fri, Sat)。
    

    mon指月份 (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec)。
    

    dd指本月的第几天,两位十进制数。

    hh指小时,两位十进制数。

    mm指分钟,两位十进制数。

    ss指秒,两位十进制数。

    zzz指时区,如CST、GMT等,如果时区不可用,则值为空。

    yyyy指年份,四位十进制数。

  • 其他很多方法都过时了
@Test
public void test01() {//创建一个当前时间的Date对象Date date1 = new Date();//创建一个指定时间戳的Date对象Date date2 = new Date(System.currentTimeMillis());//获得时间戳, 等同于System.currentTimeMillis()System.out.println(date1.getTime()); //1623740033611//打印Date, 格式: EEE MMM dd HH:mm:ss zzz yyyySystem.out.println(date1); //Tue Jun 15 14:50:33 CST 2021
}

java.sql.Date

还有一个java.sql.Date类,它是java.util.Date的子类,两者的区别:java.util.Date** 包含日期和时间,而 ** java.sql.Date** 只包含日期,正好对应于MySQL的** date类型。

java.sql.Date只能通过时间戳创建:

@Test
public void test02() {//根据时间戳创建java.sql.Date对象java.sql.Date date1 = new java.sql.Date(System.currentTimeMillis());System.out.println(date1); //2021-06-15
}

java.sql.Date和java.util.Date的转换

需要借助getTime() 方法:

@Test
public void test03() {//java.sql.Date转java.util.Datejava.sql.Date date1 = new java.sql.Date(System.currentTimeMillis());java.util.Date date2 = new java.util.Date(date1.getTime());System.out.println(date2);//java.util.Date转java.sql.Datejava.util.Date date3 = new java.util.Date();java.sql.Date date4 = new java.sql.Date(date3.getTime());System.out.println(date4);
}

String转java.sql.Date

需要借助SimpleDateFormat对象的parse() 方法先根据String构造java.util.Date对象,再调用 date.getTime() 获得时间戳,然后根据时间戳构造java.sql.Date对象。

/** 字符串转java.util.Date */
public static Date stringToSqlDate(String date) {try {java.util.Date utilDate = new SimpleDateFormat("yyyyMMdd").parse(date);return new Date(utilDate.getTime());} catch (ParseException e) {throw new RuntimeException("日期格式错误, 期望格式yyyyMMdd, 而实际是[" + date + "]");}
}

SimpleDateFormat

Date类的API不易于国际化,大部分被废弃了,java.text.SimpleDateFormat类用一种与语言环境无关的方式来格式化具体的Date对象,和解析具体的日期时间字符串。SimpleDateFormat不是线程安全的,在使用时,只能在方法内部创建新的局部变量。

创建SimpleDateFormat对象:

  • SimpleDateFormat() :使用默认的日期格式和语言环境来创建

  • SimpleDateFormat(String pattern) :根据给定的日期格式pattern,默认的语言环境来创建

  • SimpleDateFormat(String pattern, Locale locale) :根据给定的日期格式pattern和语言环境locale来创建

格式化:Date → String

  • String format(Date date) :将给定的date对象格式化为字符串

解析:String → Date

  • Date parse(String source) :将给定的source字符串解析成Date对象
@Test
public void test01() throws ParseException {Date date = new Date();//使用默认的日期格式和语言环境来创建SimpleDateFormat对象SimpleDateFormat dateFormat1 = new SimpleDateFormat();//格式化成字符串String dateStr1 = dateFormat1.format(date);System.out.println(dateStr1);//解析成Date对象Date toDate1 = dateFormat1.parse("20-1-1 上午00:00"); //21-6-15 下午5:32System.out.println(toDate1); //Wed Jan 01 00:00:00 CST 2020System.out.println("------------------------------------------------");//根据给定的日期格式pattern,默认的语言环境来创建SimpleDateFormat对象SimpleDateFormat dateFormat2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//格式化成字符串String dateStr2 = dateFormat2.format(date);System.out.println(dateStr2); //2021-06-15 17:32:09//解析成Date对象Date toDate2 = dateFormat2.parse("2020-01-01 00:00:00");System.out.println(toDate2); //Wed Jan 01 00:00:00 CST 2020
}

常用的日期格式:

  • “yyyy-MM-dd”:只显示日期

  • “yyyy-MM-dd HH:mm:ss”:日期+时间,24小时制

  • “yyyy-MM-dd hh:mm:ss”:日期+时间,12小时制

Calendar

Calendar (日历) 是一个抽象的基类,包含时区信息。
主要用于获取和设置各个日期字段的值,以及基本的加法计算。

创建Calendar实例:

  • 调用Calendar.getInstance() 静态方法,创建一个当前时间的Calendar实例

  • 调用子类GregorianCalendar 的构造器,创建一个指定时间的Calendar实例

常用方法:

  • int get(int field) :获取指定日期字段的值,field的取值来自于Calendar内部定义的一些常量,包括YEARMONTHDAY_OF_WEEKDAY_OF_MONTHHOUR_OF_DAYMINUTESECOND等。

  • void set(int field, int value) :设置指定日期字段的值

  • void add(int field, int amount) :对指定的日期字段进行加法运算

  • void setTime(Date date) :根据Date对象来设置各个字段的值

  • Date getTime() :转换成Date对象

在获取月份时,一月是0,二月是1, 三月是2 … 十二月是11;
在获取星期时,周日是1,周一是2,周二是3 … 周六是7。

@Test
public void test04() {//创建当前时间的Calendar对象Calendar calendar = Calendar.getInstance();//get(): 取指定字段的值int year = calendar.get(Calendar.YEAR);int month = calendar.get(Calendar.MONTH);int day = calendar.get(Calendar.DAY_OF_MONTH);int hour = calendar.get(Calendar.HOUR_OF_DAY);int minute = calendar.get(Calendar.MINUTE);int second = calendar.get(Calendar.SECOND);//set(): 设置指定字段的值calendar.set(Calendar.YEAR, 2021);calendar.set(Calendar.MONTH, 0); //设置为一月份, 注意0代表一月份, 1代表二月份...calendar.set(Calendar.WEEK_OF_MONTH, 2); //设置为星期一, 注意1代表周日, 2代表周一...calendar.set(Calendar.DAY_OF_MONTH, 1);//add(): 对指定字段的值进行加法运算calendar.add(Calendar.MONTH, 3);calendar.add(Calendar.MONTH, 3);calendar.add(Calendar.DAY_OF_MONTH, 1);//getTime(): Calendar → DateDate date1 = calendar.getTime();//setTime(): Date → CalenderDate date2 = new Date();calendar.setTime(date2);
}

JDK 8新日期时间API

JDK 1.0中的java.util.Date类的大多数方法已经在JDK 1.1引入Calendar类之后被弃用了,但Calendar并不比Date好用多少。它们面临的问题是:

  • 可变性:像日期和时间这样的类应该是不可变的。

  • 偏移性:Date中的年份是从1900开始的,而月份都从0开始,容易出错。

  • 格式化:SimpleDateFormat只能格式化Date,不能格式化Calendar。

  • 此外,这些旧API都是线程不安全的,也不能适应闰秒等问题。

Java 8 推出了新的新日期时间API,在java.time包中包含了所有关于 本地日期(LocalDate)、本地时间(LocalTime)、本地日期时间(LocalDateTime)、时区(ZonedDateTime)和持续时间(Duration)等类。另外还为历史悠久的 Date 类新增了 toInstant() 方法,用来把 Date 转换成新的Instant类型。

这些新API有一个共同点:它们都是 不可变的对象 ,每次修改都会返回一个新的实例。

LocalDateTime

LocalDate、LocalTime、LocalDateTime 类都是不可变的,分别表示日期、时间、日期+时间。它们仅仅存储年月日、时分秒这样的数字,不包含时区信息。这意味着在不同的时区下,这样的数字其实代表着不同的时间。也支持获取和设置指定字段的值,以及进行加减运算。

  • LocalDate:表示yyyy-MM-dd格式的日期。

  • LocalTime:表示HH:mm:ss.SSS格式的时间。

  • LocalDateTime:表示yyyy-MM-ddTHH:mm:ss.SSS格式的日期和时间,这是最常用的类。

常用方法:

创建对象 描述
now()、now(ZoneId zone) 静态方法,根据当前时间创建对象(可以指定时区)
of(…) 静态方法,根据给定的日期时间创建对象
parse(CharSequence text, DateTimeFormatter formatter) 静态方法,根据给定的字符串和格式化器创建对象
修改值,返回新对象 描述
withDayOfMonth()、withDayOfYear()、
withMonth()、withYear()
设置 月第几天、年第几天、月份、年份
plusDays()、plusWeeks()、
plusMonths()、plusYears()、plusHours()
加上 几天、几周、几个月、几年、几小时
minusMonths()、minusWeeks()、
minusDays()、minusYears()、minusHours()
减去 几月、几周、几天、几年、几小时
获取值 描述
getDayOfMonth()、getDayOfYear() 获取月第几天 (1-31)、获取年第几天 (1-366)
getDayOfWeek() 获取星期几,返回一个DayOfWeek枚举值
getMonth() 获得月份,返回一个 Month 枚举值
getHour()、getMinute()、getSecond() 获得 时、分、秒
转换为其他类型 描述
LocalTime toInstant(ZoneOffset offset) 转为Instant对象
LocalTime toLocalTime() 转为LocalTime对象
LocalDate toLocalDate() 转为LocalDate对象
long toEpochSecond(ZoneOffset offset) 转为秒级时间戳
OffsetDateTime atOffset(ZoneOffset offset) 结合指定的时区偏移量,转为OffsetDateTime对象

在用法上,LocalDateTime类似于Calender,不过它不包含时区信息,且是不可变的。

@Test
public void test15() {LocalDateTime dateTime = LocalDateTime.now();// 转为Instant对象Instant instant = dateTime.toInstant(ZoneOffset.ofHours(8));// 转为LocalTime对象LocalTime localTime = dateTime.toLocalTime();// 转为LocalDate对象LocalDate localDate = dateTime.toLocalDate();// 转为秒级时间戳long seconds = dateTime.toEpochSecond(ZoneOffset.ofHours(8));// 结合指定的时区偏移量返回一个OffsetDateTime对象OffsetDateTime offsetDateTime = dateTime.atOffset(ZoneOffset.ofHours(-8));
}
@Test
public void localDateTimeTest() {// now(): 获取当前的日期、时间、日期+时间LocalDate date1 = LocalDate.now();LocalTime time1 = LocalTime.now();LocalDateTime dateTime1 = LocalDateTime.now();System.out.println(date1); //2021-06-16System.out.println(time1); //18:39:58.198System.out.println(dateTime1); //2021-06-16T18:39:58.198// of(): 获取指定的日期、时间、日期+时间LocalDate date2 = LocalDate.of(2020, 10, 1);LocalTime time2 = LocalTime.of(12, 0, 0);LocalDateTime dateTime2 = LocalDateTime.of(2020, 10, 1, 12, 0, 0);System.out.println(date2); // 2020-10-01System.out.println(time2); // 12:00System.out.println(dateTime2); // 2020-10-01T12:00// parse(): 根据给定的字符串和格式化器创建对象LocalDate date3 = LocalDate.parse("2020-10-01", DateTimeFormatter.ofPattern("yyyy-MM-dd"));LocalTime time3 = LocalTime.parse("12:00:00", DateTimeFormatter.ofPattern("HH:mm:ss"));LocalDateTime dateTime = LocalDateTime.parse("2020-10-01 12:00:00", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));// getXxx(): 获取指定字段的值LocalDateTime dateTime3 = LocalDateTime.now();System.out.println(dateTime3.getYear());System.out.println(dateTime3.getMonth()); //Month枚举类System.out.println(dateTime3.getDayOfMonth());System.out.println(dateTime3.getHour());System.out.println(dateTime3.getMinute());System.out.println(dateTime3.getSecond());// WithXxx(): 设置指定字段的值, 注意LocalDateTime是不可变的LocalDateTime dateTime4 = LocalDateTime.now();System.out.println(dateTime4.withYear(2020)); //2020-06-16T18:48:15.795System.out.println(dateTime4.withMonth(10)); //2021-10-16T18:48:15.795System.out.println(dateTime4.withDayOfMonth(1)); //2021-06-01T18:48:15.795System.out.println(dateTime4); //不变, 仍是2021-06-16T18:48:15.795// plusXxx(), minusXxx(): 加法和减法运算, 注意LocalDateTime是不可变的LocalDateTime dateTime5 = LocalDateTime.now();System.out.println(dateTime5.plusMonths(4)); //2021-10-16T18:48:15.795System.out.println(dateTime5.plusDays(3)); //2021-06-19T18:48:15.795System.out.println(dateTime5.minusMonths(4)); //2021-02-16T18:48:15.795System.out.println(dateTime5.minusDays(3)); //2021-06-13T18:48:15.795
}
@Test
public void test15() {LocalDateTime dateTime = LocalDateTime.now();// 转为Instant对象Instant instant = dateTime.toInstant(ZoneOffset.ofHours(8));// 转为LocalTime对象LocalTime localTime = dateTime.toLocalTime();// 转为LocalDate对象LocalDate localDate = dateTime.toLocalDate();// 转为秒级时间戳long seconds = dateTime.toEpochSecond(ZoneOffset.ofHours(8));// 结合指定的时区偏移量返回一个OffsetDateTime对象OffsetDateTime offsetDateTime = dateTime.atOffset(ZoneOffset.ofHours(-8));
}

Instant

Instant代表时间线上的一个瞬时点,也就是时间戳,不包含时区信息。

从概念上讲,Instant只是简单地表示自1970年1月1日0时0分0秒(UTC)开始的秒数。因为java.time包是基于纳秒计算的,所以Instant的精度可以达到纳秒级。

1s = 1000ms =10^6us = 10^9ns

常用方法:

创建对象 描述
now() 静态方法,创建当前时间的UTC时区的Instant对象
ofEpochMilli(long epochMilli) 静态方法,根据指定的毫秒时间戳创建Instant对象
获取值 描述
long toEpochMilli() 获取毫秒时间戳
atOffset(ZoneOffset offset) 结合指定的时区偏移量返回一个OffsetDateTime对象

在用法上,Instant类似于Date,两者都不包含时区信息,但 Instant 是不可变的。

@Test
public void test01() {//now(): 创建当前时间的UTC时区的Instant对象Instant instant1 = Instant.now();//of(): 根据指定的时间戳创建Instant对象Instant instant2 = Instant.ofEpochMilli(1623843136891L);//直接打印的话, 因为是以UTC+0为基准, 所以结果比北京时间少8小时System.out.println(instant1); //2021-06-16T11:28:25.397Z//atOffset(): 根据给定ZoneOffset时区偏移量, 返回一个OffsetDateTime对象, 但仍不包含时区信息//转换为UTC+8时区的OffsetDateTime对象OffsetDateTime dateTime = instant1.atOffset(ZoneOffset.ofHours(8));System.out.println(dateTime); //2021-06-16T19:28:25.397+08:00//toEpochMill(): 获得从1970-1-1开始的毫秒数, 即时间戳long milli = instant1.toEpochMilli();System.out.println(milli); //1623843136891
}

DateTimeFormatter

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

与SimpleDateFormat不同的是,DateTimeFormatter不但是不可变的对象,还是线程安全的。可以只创建一个DateTimeFormatter实例,然后到处引用。

创建DateTimeFormatter实例:

  • 预定义的标准格式。如:.ISO_LOCAL_DATE_TIME.ISO_LOCAL_DATE.ISO_LOCAL_TIME

  • 本地化相关的格式。如:.ofLocalizedDateTime(FormatStyle.LONG),可选值:FormatStyle.SHORTFormatStyle.MEDIUMFormatStyle.LONGFormatStyle.FULL

  • 自定义的格式,最常用。如:.ofPattern("yyyy-MM-dd HH:mm:ss")

常用方法:

  • String format(TemporalAccessor t) :将TemporalAccessor格式化成字符串。这个TemporalAccessor可以是LocalDate、LocalTime、LocalDateTime等。

  • TemporalAccessor parse(CharSequence text) :将字符串解析成一个TemporalAccessor的实现类对象java.time.format.Parsed,并不好用。更推荐使用LocalDateTime.parse() 方法,根据给定的字符串和DateTimeFormatter来解析出LocalDateTime对象。

@Test
public void test03() {LocalDateTime now = LocalDateTime.now();//创建DateTimeFormatter对象:// 预定义的标准格式。如:ISO_LOCAL_DATE_TIME、ISO_LOCAL_DATE、ISO_LOCAL_TIMEDateTimeFormatter formatter1 = DateTimeFormatter.ISO_LOCAL_DATE_TIME;// 本地化相关的格式。如:ofLocalizedDateTime(FormatStyle.MEDIUM)//可选值:FormatStyle.SHORT、FormatStyle.MEDIUM、FormatStyle.LONG、FormatStyle.FULLDateTimeFormatter formatter2 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT);DateTimeFormatter formatter3 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM);DateTimeFormatter formatter4 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG);// 自定义的格式,最常用。如:ofPattern("yyyy-MM-dd HH:mm:ss E")DateTimeFormatter formatter5 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss E");//格式化: 日期 -> 字符串System.out.println(formatter1.format(now)); //2021-06-16T20:14:03.584System.out.println(formatter2.format(now)); //21-6-16 下午8:14System.out.println(formatter3.format(now)); //2021-6-16 20:14:03System.out.println(formatter4.format(now)); //2021年6月16日 下午08时14分03秒System.out.println(formatter5.format(now)); //2021-06-16 20:14:03 星期三//解析: 字符串 -> 日期TemporalAccessor temporalAccessor1 = formatter1.parse("2021-06-16T20:00:32.175");TemporalAccessor temporalAccessor2 = formatter5.parse("2021-06-16 20:12:28 星期三");System.out.println(temporalAccessor1); //{},ISO resolved to 2021-06-16T20:00:32.175System.out.println(temporalAccessor2); //{},ISO resolved to 2021-06-16T20:12:28
}

TemporalAdjuster

为了保证线程安全,Java 8 中的日期/时间类都是不可变的。任何修改都会返回一个新的实例:

LocalDate date = LocalDate.of(2017, 1, 5);          // 2017-01-05LocalDate date1 = date.withYear(2016);              // 修改为 2016-01-05
LocalDate date2 = date.withMonth(2);                // 修改为 2017-02-05
LocalDate date3 = date.withDayOfMonth(1);           // 修改为 2017-01-01LocalDate date4 = date.plusYears(1);                // 增加一年 2018-01-05
LocalDate date5 = date.minusMonths(2);              // 减少两个月 2016-11-05
LocalDate date6 = date.plus(5, ChronoUnit.DAYS);    // 增加5天 2017-01-10

上面例子中对于日期的操作比较简单,但是有些时候我们要面临更复杂的时间操作,比如将时间调到下一个工作日,或者是下个月的最后一天。这时候我们可以将一个TemporalAdjuster传给LocalDateTime实例的with()方法,来获取一个调整后的时间。

LocalDate date7 = date.with(nextOrSame(DayOfWeek.SUNDAY));      // 返回下一个距离当前时间最近的星期日
LocalDate date9 = date.with(lastInMonth(DayOfWeek.SATURDAY));   // 返回本月最后一个星期六

要使上面的代码正确编译,你需要使用静态导入TemporalAdjusters对象:

import static java.time.temporal.TemporalAdjusters.*;

TemporalAdjuster是一个时间调整器, 指代一种时间调整逻辑。**
**

TemporalAdjusters是一个工具类,提供了大量常用的TemporalAdjuster实现:

方法名 描述
dayOfWeekInMonth 返回同一个月中每周的第几天
firstDayOfMonth 返回当月的第一天
firstDayOfNextMonth 返回下月的第一天
firstDayOfNextYear 返回下一年的第一天
firstDayOfYear 返回本年的第一天
firstInMonth 返回同一个月中第一个星期几
lastDayOfMonth 返回当月的最后一天
lastDayOfNextMonth 返回下月的最后一天
lastDayOfNextYear 返回下一年的最后一天
lastDayOfYear 返回本年的最后一天
lastInMonth 返回同一个月中最后一个星期几
next / previous 返回下一个 / 前一个给定的星期几
nextOrSame / previousOrSame 返回下一个 / 前一个给定的星期几,
如果这个值满足条件,直接返回

如果上面表格中列出的方法不能满足你的需求,你还可以创建自定义的TemporalAdjuster接口的实现,TemporalAdjuster也是一个函数式接口,所以我们可以使用 Lambda 表达式:

@FunctionalInterface
public interface TemporalAdjuster {Temporal adjustInto(Temporal temporal);
}

比如给定一个日期,计算该日期的下一个工作日:

LocalDate date = LocalDate.of(2017, 1, 5);
date.with(temporal -> {// 当前日期DayOfWeek dayOfWeek = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK));// 正常情况下,每次增加一天int dayToAdd = 1;// 如果是星期五,增加三天if (dayOfWeek == DayOfWeek.FRIDAY) {dayToAdd = 3;}// 如果是星期六,增加两天if (dayOfWeek == DayOfWeek.SATURDAY) {dayToAdd = 2;}return temporal.plus(dayToAdd, ChronoUnit.DAYS);
});

ZoneId和ZoneOffset

ZoneId代表一个时区,内部格式是 “地区/城市”,如 “Asia/Shanghai”。

新的时区类java.time.ZoneId是原有的java.util.TimeZone类的替代品。

@Test
public void test07() {// ZoneId: 代表一个时区,内部格式是 "地区/城市",如 "Asia/Shanghai"// ZoneId.systemDefault(): 获取当前系统默认时区的ZoneId对象ZoneId zoneId = ZoneId.systemDefault();System.out.println(zoneId); //Asia/Shanghai// ZoneId.of(zoneStr): 获取指定时区的ZoneId对象, 需传入一个 "地区/城市" 格式的字符串ZoneId shanghai = ZoneId.of("Asia/Shanghai");LocalDateTime dateTime = LocalDateTime.now(shanghai);// ZoneId.getAvailableZoneIds(): 获取所有时区的ZoneIdSet<String> zoneIds = ZoneId.getAvailableZoneIds();for (String zoneId : zoneIds) {System.out.println(zoneId);}
}

对于老的时区类TimeZone,Java 8 也提供了转化方法:

ZoneId oldToNewZoneId = TimeZone.getDefault().toZoneId();

借助于ZoneId,我们就可以将一个LocalDateTime对象转化为带时区的ZonedDateTime对象:

LocalDateTime localDateTime = LocalDateTime.now();
ZoneId shanghai = ZoneId.of("Asia/Shanghai");
ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, shanghai);
System.out.println(zonedDateTime);

打印结果:

2017-01-05T15:26:56.147+08:00[Asia/Shanghai]

ZonedDateTime对象由两部分构成:LocalDateTime+ZoneId,其中“2017-01-05T15:26:56.147”部分为LocalDateTime,“+08:00[Asia/Shanghai]”部分为ZoneId

另一种表示时区的方式是使用ZoneOffset,它表示当前时间和GMT时间的偏移量,例如:

ZoneOffset zoneOffset = ZoneOffset.of("+09:00");
LocalDateTime localDateTime = LocalDateTime.now();
//这个offsetDateTime代表的是系统默认时区的时间+9小时后的时间
OffsetDateTime offsetDateTime = OffsetDateTime.of(localDateTime, zoneOffset);

其他API

  • ZonedDateTime :带时区的日期时间,如 2021-06-19T13:28:34.906+08:00[Asia/Shanghai]。
@Test
public void test08() {// ZonedDateTime: 带时区的日期时间// ZonedDateTime.now(): 获得当前系统的默认时区的日期时间ZonedDateTime dateTime = ZonedDateTime.now();System.out.println(dateTime); //2021-06-19T13:28:34.906+08:00[Asia/Shanghai]// getZone(): 获取ZonedDateTime里的时区ZoneId zoneId = dateTime.getZone();System.out.println(zoneId); //Asia/Shanghai// ZonedDateTime.now(ZoneId zoneId): 获取指定时区的ZonedDateTime对象ZonedDateTime dateTime2 = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));System.out.println(dateTime2); //2021-06-19T13:28:34.906+08:00[Asia/Shanghai]
}
  • Clock :使用时区提供对当前即时、日期和时间的访问的时钟。

  • Duration :持续时间,表示两个时间的间隔时长,以【秒数+纳秒数】衡量。

@Test
public void test09() {// Duration: 表示两个时间的间隔时长, 以 秒数+纳秒数 衡量LocalTime time1 = LocalTime.of(0, 0, 0);LocalTime time2 = LocalTime.now();// Duration.between(time1, time2): 返回一个Duration对象, 表示两个时间的间隔时长Duration duration = Duration.between(time1, time2);System.out.println(duration); //PT14H58M9.822S// getSeconds(): 获得秒数部分System.out.println(duration.getSeconds()); //53889// getNano(): 获得纳秒部分System.out.println(duration.getNano()); //822000000// toMillis(): 转为对应的毫秒数System.out.println(duration.toMillis()); //53889822// toDays(): 换算成天数表示, 即 秒数/86400, 不足一天的直接被忽略LocalDateTime dateTime1 = LocalDateTime.of(2016, 1, 1, 15, 23, 32);LocalDateTime datetime2 = LocalDateTime.of(2017, 1, 1, 15, 23, 32);Duration duration2 = Duration.between(dateTime1, datetime2);System.out.println(duration2.toDays()); //366
}
  • Period :日期间隔,表示两个日期的间隔,以【年+月+日】衡量。
@Test
public void test10() {// Period: 表示两个日期的间隔, 以 年+月+日 衡量LocalDate date1 = LocalDate.now();LocalDate date2 = LocalDate.of(2038, 3, 18);// Period.between(date1, date2): 获取两个日期的间隔Period period1 = Period.between(date1, date2);System.out.println(period1); //P16Y8M27D// getYears()/getMonths()/getDays(): 分别获得 年份/月份/月内天数 部分System.out.println(period1.getYears()); //16System.out.println(period1.getMonths()); //8System.out.println(period1.getDays()); //27// withYears()/withMonths()/withDays(): 分别修改 年份/月份/月内天数, 返回一个新Period对象Period period2 = period1.withYears(2);System.out.println(period1.getYears()); //16System.out.println(period2.getYears()); //2
}

与旧API的类型转换

主要就是调用旧API的一些方法来进行类型转换。

类型转换 方法
Instant → Date Date.from(instant)
Date → Instant date.toInstant()
Instant → java.sql.Timestamp Timestamp.from(instant)
java.sql.Timestamp → Instant timestamp.toInstant()
ZonedDateTime → GregorianCalendar GregorianCalendar.from(zonedDateTime)
GregorianCalendar → ZonedDateTime calendar.toZonedDateTime()
LocalTime → java.sql.Time time.valueOf(lcoalTime)
java.sql.Time → LocalTime time.toLocalTime()
LocalDateTime → java.sql.Timestamp Timestamp.valueOf(localDateTime)
java.sql.Timestamp → LocalDateTime timestamp.toLocalDateTime()
ZoneId → TimeZone Timezone.getTimeZone(zoneId)
TimeZone → ZoneId timeZone.toZoneId()
DateTimeFormatter → SimpleDateFormat formatter.toFormat()
SimpleDateFormat → DateTimeFormatter

其他历法

Java 中使用的历法是 ISO 8601 日历系统,它是世界民用历法,也就是我们所说的公历。平年有 365 天,闰年是 366 天。闰年的定义是:非世纪年,能被 4 整除;世纪年能被 400 整除。为了计算的一致性,公元 1 年的前一年被当做公元 0 年,以此类推。

此外 Java 8 还提供了 4 套其他历法(很奇怪为什么没有汉族人使用的农历),每套历法都对应一个日期类,分别是:

  • ThaiBuddhistDate:泰国佛教历

  • MinguoDate:中华民国历

  • JapaneseDate:日本历

  • HijrahDate:伊斯兰历

每个日期类都继承ChronoLocalDate类,所以可以在不知道具体历法的情况下也可以操作。不过这些历法一般不常用,除非是有某些特殊需求情况下才会使用。

这些不同的历法也可以从公历转换而来:

LocalDate date = LocalDate.now();
JapaneseDate jpDate = JapaneseDate.from(date);

由于它们都继承ChronoLocalDate类,所以在不知道具体历法情况下,可以通过ChronoLocalDate类操作日期:

Chronology jpChronology = Chronology.ofLocale(Locale.JAPANESE);
ChronoLocalDate jpChronoLocalDate = jpChronology.dateNow();

我们在开发过程中应该尽量避免使用ChronoLocalDate,尽量可以用与历法无关的方式操作时间,但是不同的历法计算日期的方式不一样,比如开发者会在程序中做一些假设,假设一年中有 12 个月,如果是中国农历中包含了闰月,一年有可能是 13 个月,但开发者认为是 12 个月,多出来的一个月被当作是明年的。再比如假设开发者认为年份是累加的,过了一年就在原来的年份上加一,但日本天皇在换代之后需要重新纪年,所以过了一年的年份后可能会从 1 开始计算。

在实际开发过程中建议使用LocalDate,包括存储、操作、业务规则的解读;除非需要将程序的输入或者输出本地化,这时才使用ChronoLocalDate类。

日期时间练习题

常见需求

获取当前时间戳

@Test
public void test12() {// 法一long timeMillis1 = System.currentTimeMillis();// 法二long timeMillis2 = new Date().getTime();// 法三~~long timeMillis3 = Calendar.getInstance().getTimeInMillis();~~// 法四long timeMillis4 = Instant.now().toEpochMilli();
}

获取当前时间

@Test
public void test13() {// 不带时区信息:// 法一, 新APILocalDateTime localDateTime = LocalDateTime.now();// 法二, 旧APIDate date = new Date();// 带时区信息:// 法一, 新APIZonedDateTime zonedDateTime = ZonedDateTime.now();// 法二, 旧APICalendar calendar = Calendar.getInstance();
}

获取两个日期相差的毫秒数

@Test
public void test14() {LocalDateTime datetime1 = LocalDateTime.now();LocalDateTime datetime2 = LocalDateTime.of(2048, 1, 1, 0, 0, 0);// 法一, 基于DurationDuration duration = Duration.between(datetime1, datetime2);long millis1 = duration.toMillis();// 法二, 基于时间戳long millis2 = datetime2.toInstant(ZoneOffset.ofHours(8)).toEpochMilli()- datetime1.toInstant(ZoneOffset.ofHours(8)).toEpochMilli();System.out.println(millis1);System.out.println(millis2);
}

Java日期时间API相关推荐

  1. java8 日期api_我们多么想要新的Java日期/时间API?

    java8 日期api 当前的Java.net 民意测验问题是:" 对于用Java 8实现的JSR-310(新的日期和时间API)有多重要? "在我撰写本文时,将近150位受访者投 ...

  2. 我们多么想要新的Java日期/时间API?

    当前的Java.net 民意测验问题是:" 对于用Java 8实现的JSR-310(新的日期和时间API)有多重要? "在我撰写本文时,将近150位受访者投了赞成票,绝大多数人回答 ...

  3. 一文告诉你Java日期时间API到底有多烂

    前言 你好,我是A哥(YourBatman). 好看的代码,千篇一律!难看的代码,卧槽卧槽~其实没有什么代码是"史上最烂"的,要有也只有"史上更烂". 日期是商 ...

  4. Java —— 日期时间 API

    一.java.util.Date 在 JDK 1.1 之前, Date 有两个附加功能. 它允许将日期解释为年,月,日,小时,分钟和第二个值. 它还允许格式化和解析日期字符串. 不幸的是,这些功能的 ...

  5. java date只保留年月日_Java日期时间API系列14-----Jdk8中日期API类,日期计算1,获取年月日时分秒等...

    通过Java日期时间API系列8-----Jdk8中java.time包中的新的日期时间API类的LocalDate源码分析 ,可以看出java8设计非常好,实现接口Temporal, Tempora ...

  6. 6 日期字符串转日期_Java日期时间API系列6-----Jdk8中java.time包中的新的日期时间API类...

    因为Jdk7及以前的日期时间类的不方便使用问题和线程安全问题等问题,2005年,Stephen Colebourne创建了Joda-Time库,作为替代的日期和时间API.Stephen向JCP提交了 ...

  7. java date加一天_Java日期时间API系列15-----Jdk8中API类,java日期计算2,年月日时分秒的加减等...

    通过Java日期时间API系列8-----Jdk8中java.time包中的新的日期时间API类的LocalDate源码分析 ,可以看出java8设计非常好,实现接口Temporal, Tempora ...

  8. java 包结构 枚举类_Java日期时间API系列6-----Jdk8中java.time包中的新的日期时间API类...

    因为Jdk7及以前的日期时间类的不方便使用问题和线程安全问题等问题,2005年,Stephen Colebourne创建了Joda-Time库,作为替代的日期和时间API.Stephen向JCP提交了 ...

  9. java 纳秒 格式化_Java日期时间API系列35-----Jdk8中java.time包中的新的日期时间API类应用,微秒和纳秒等更精确的时间格式化和解析。...

    通过Java日期时间API系列1-----Jdk7及以前的日期时间类中得知,Java8以前除了java.sql.Timestamp扩充纳秒,其他类最大只精确到毫秒:Java8 time包所有相关类都支 ...

最新文章

  1. ABAP性能实例七例
  2. DuiLib的Combo控件点击无响应的问题
  3. matlab 雷达图函数,R语言之可视化(20)ggradar雷达图
  4. Druid 配置_DruidDataSource参考配置
  5. 拒绝了对对象 'sp_sdidebug'(数据库 'master',所有者 'dbo')的 EXECUTE 权限
  6. enumset.allof_Java EnumSet allOf()方法与示例
  7. div固定大小文字溢出自动缩小_Figma 教程 | 文字工具
  8. mui 头部tab代码2
  9. 白话解释 Javascript 原型继承(prototype inheritance)
  10. 在 Linux 下使用 RAID(九):如何使用 ‘Mdadm’ 工具管理软件 RAID
  11. 01:golang开发环境
  12. Java Queue 使用总结
  13. 系统分析师-论文题目
  14. XSS跨站脚本攻击详解(包括攻击方式和防御方式)
  15. HR人事管理系统软件有哪些?如何选择HR人事管理软件?
  16. C语言求三角形斜边长
  17. windows放到Linux替换,windows过渡到linux之软件的替换
  18. 《惢客创业日记》2021.01.22(周五)英雄心,狗熊命?
  19. oracle学习入门系列之五内存结构、数据库结构、进程
  20. sphinx php搜索引擎,sphinx 全文搜索引擎

热门文章

  1. 一个显示器分屏显示两个画面_测了两个爆款游戏显示器,结果我发现他们都有坑...
  2. java 二维码生成和加密base64压码
  3. Java程序员的薪资取决于年限还是技术?
  4. 写给数据分析入门者:一种通用的数据分析思路
  5. 推荐几个海外优秀的新闻网站[中文]
  6. 操作系统经典问题之吸烟者问题
  7. 中山大学计算机在职研究生分数线,中山大学在职研究生复试分数线详情
  8. R语言和医学统计学(6):重复测量方差分析
  9. python百度云盘搜索引擎_2016百度云网盘搜索引擎源码,附带Python爬虫+PHP网站+Xunsearch搜索引擎...
  10. 华为云workerman超时ERR_CONNECTION_TIMED_OUT