一. 简介
Java 8通过发布新的Date-Time API (JSR 310)来进一步加强对日期与时间的处理。
在旧版的 Java 中,日期时间 API 存在诸多问题,其中有:
- 非线程安全 − java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
- 设计很差 − Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。
- 时区处理麻烦 − 日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。
Java 8 在 java.time 包下提供了很多新的 API。以下为两个比较重要的 API:
- Local(本地) − 简化了日期时间的处理,没有时区的问题。
- Zoned(时区) − 通过制定的时区处理日期时间。
新的java.time包涵盖了所有处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操作。
二. 时间戳 Instant和Clock
1. 时刻 Instant
在新的时间API中,Instant表示一个精确的时间点,Duration和Period表示两个时间点之间的时间量(所以我们比较两个时间差,用新API更方便了,后面会有示例)。
Instant它是精确到纳秒的(而不是象旧版本的Date精确到毫秒,System.nanoTime倒是是精确到纳秒级别了),如果使用纳秒去表示一个时间则原来使用一位Long类型是不够的,需要占用更多一点的存储空间,所以它内部是用两个字段去存储的。第一个部分保存的是自格林威治时间(就是1970年1月1日开始)到现在的秒数(用long存储),第二部分保存的是纳秒数(用int存储,永远不会超过999,999,999)
示例1: 获取当前时间的毫秒级时间戳
可以调用Instant.now()方法获取当前时间的纳秒级时间戳,然后通过toEpochMilli()转换成毫秒级时间戳
Instant now = Instant.now();
System.out.println(now.toEpochMilli());
示例2: 获取指定时间的毫秒级时间戳
Instant.parse()可以通过传入指定的日期字符串获取对应的时间戳
Instant now = Instant.parse();
System.out.println(now.toEpochMilli());
示例3: 时间戳的加减
Instant定义了一系列的plusXXX()方法用来对时间戳进行加减,正加负减,同样,还可以传入TemporalUnit(时间单位)接口的实现类枚举ChronoUnit来决定修改的属性是天,小时或者秒等
PS: 这里使用了一个java5提供的时间工具类TimeUnit,可以很方便地对时间粒度进行转换
Instant now = Instant.now();
Instant instant_1 = now.plusSeconds(TimeUnit.HOURS.toSeconds(25));
Instant instant_2 = now.plusMillis(1);
Instant instant_3 = now.plusNanos(1);
Instant instant_4 = now.plus(1, ChronoUnit.DAYS);
示例5: 统计程序运行的时间
java8中的Duration类可以用来表示时间差,而Period则用来表示日期差,在后面会专门讲解
Instant start = Instant.now();
Thread.sleep(10);
Instant end = Instant.now();
//计算时间差 采用Duration来处理时间戳的差
Duration timeElapsed = Duration.between(start, end);
long millis = timeElapsed.toMillis();
System.out.println("millis = " + millis);
2. 时钟 Clock(使用频率不高)
公共抽象类Clock 提供使用时区的当前时刻,日期和时间的访问,可以使用存储的时区来解释当前时刻以查找当前日期和时间。因此,可以使用时钟代替System.currentTimeMillis()
和TimeZone.getDefault()
。
必须小心实现此抽象类,以确保其他类正确运行,可以实例化的所有实现必须是final的,不可变的并且是线程安全的。
此处提供的时钟实现基于System.currentTimeMillis()
。该方法几乎不能保证时钟的准确性。需要更精确时钟的应用程序必须使用其他外部时钟(例如NTP服务器)自己实现此抽象类。
示例1: 打印时区的日期和毫秒数
通过调用静态方法获取Clock对象,并且可以转换为时间戳或者毫秒数
final Clock clock = Clock.systemUTC();
System.out.println( clock.instant() );
System.out.println( clock.millis() );
示例2: 打印指定的日期和毫秒数
通过传入ZoneId可以指定Clock的时区,并且Java的其他时间类支持传入Clock进行构造
Clock shanghai = Clock.system(ZoneId.of("Asia/Shanghai")); // 上海时间
System.out.println(LocalDateTime.now(shanghai));
System.out.println(LocalDate.now(shanghai));
System.out.println(LocalTime.now(shanghai));
三. 日期初识 LocalData
LocalData有三个主要属性year、month、day,分别是int、short、short类型,用来表示日期
API的设计者推荐使用不带时区的时间,除非真的希望表示绝对的时间点。
可以使用静态方法now()和of()创建LocalDate。
示例1: 获取当天日期
Java 8中的 LocalDate 用于表示当天日期。和java.util.Date不同,它只有日期,不包含时间。调用静态方法LocalDate.now()获取当天日期
LocalDate date = LocalDate.now();
System.out.println("当前日期=" + date);//2019-12-10
示例2: 构造指定日期
调用工厂方法LocalDate.of()创建任意日期, 该方法需要传入年、月、日做参数,返回对应的LocalDate实例。这个方法的好处是没再犯老API的设计错误,比如年度起始于1900,月份是从0开 始等等
LocalDate date = LocalDate.of(2000, 1, 1);
System.out.println("千禧年=" + date);
示例3: 获取年月日闰年等信息
可以通过get方法获取对应的年月日信息,如getYear(),getMonthValue(),getDayOfMonth()
LocalDate date = LocalDate.now();
System.out.printf("年=%d, 月=%d, 日=%d", date.getYear(), date.getMonthValue(), date.getDayOfMonth());
获取信息的方法列举如下
- int getYear() :年份信息
- int getMonthValue() :月份信息
- Month getMonth() :Month对象,这是一个月份的枚举
- int getDayOfMonth() : 相对于月份的天
- int getDayOfYear() : 相对于年份的天
- DayOfWeek getDayOfWeek() : DayOfWeek对象,这是一个星期一到星期天的枚举
- boolean isLeapYear() : 是否是闰年
- int lengthOfMonth() :月份天数
- int lengthOfYear() :年份天数
示例4: 比较两个日期是否相等
LocalDate实现了equals方法,可以通过它判断日期是否相等
LocalDate date1 = LocalDate.now();
LocalDate date2 = LocalDate.now();
System.out.println("日期是否相等=" + date1.equals(date2));
四. 时间初识 LocalTime
Java 8中的 LocalTime 用于表示当天时间。和java.util.Date不同,它只有时间,不包含日期。
LocalTime有四个主要属性hour、minute、second、nano,分别是byte、byte、byte、int类型,用来表示使劲按
示例1: 获取当前时间
同样,我们通过LocalTime**.**now()方法获取当前时间
LocalTime time = LocalTime.now();
System.out.println("当前时间=" + time);
示例2: 获取指定时间
调用工厂方法LocalTime**.of()**创建任意时间, 该方法需要传入时、分、秒、纳秒做参数,可以传入部分或全部
LocalTime time_1 = LocalTime.of(1, 2);//hour minute
LocalTime time_2 = LocalTime.of(1, 2, 30);//hour minute second
LocalTime time_3 = LocalTime.of(1, 2, 30, 40);//hour minute second nonoOfSecond
实例3:获取时分秒等信息
同样,LocalTime也可以获取时、分、秒、纳秒的信息,并且通过equals判断是否相等
LocalTime time_1 = LocalTime.of(1, 2, 30, 40);
LocalTime time_2 = LocalTime.of(1, 2, 30, 40);
System.out.printf("时=%d, 分=%d, 秒=%d, 纳秒=%d", time_1.getHour(), time_1.getMinute(), time_1.getMinute(), time_1.getNano());
System.out.printf("\n 是否相等=" + time_1.equals(time_2));
实例4:获取时间常量
LocalTime中拥有一些方便的时间常量,省去我们定义的功夫,
- 每天的小时数:HOURS_PER_DAY = 24
- 每小时的分钟数:MINUTES_PER_HOUR = 60
- 每天的分钟数:MINUTES_PER_DAY = MINUTES_PER_HOUR * HOURS_PER_DAY
- 每分钟的秒数:SECONDS_PER_MINUTE = 60
- 每小时的秒数:SECONDS_PER_HOUR = SECONDS_PER_MINUTE * MINUTES_PER_HOUR
- 每天的分钟数:SECONDS_PER_DAY = SECONDS_PER_HOUR * HOURS_PER_DAY
- 每天的毫秒数:MILLIS_PER_DAY = SECONDS_PER_DAY * 1000L
- 每天的微秒数:MICROS_PER_DAY = SECONDS_PER_DAY * 1000_000L
- 每秒纳秒数:NANOS_PER_SECOND = 1000_000_000L
- 每分钟纳秒数:NANOS_PER_MINUTE = NANOS_PER_SECOND * SECONDS_PER_MINUTE
- 每小时纳秒数:NANOS_PER_HOUR = NANOS_PER_MINUTE * MINUTES_PER_HOUR
- 每天的那描述:NANOS_PER_DAY = NANOS_PER_HOUR * HOURS_PER_DAY;
五、比较与计算
Java8提供了新的plusXxx()方法用于计算日期时间增量值,替代了原来的add()方法。新的API将返回一个全新的日期时间示例,需要使用新的对象进行接收。
示例1: 时间的加减
调用对应属性的plus方法来对其进行修改,正加负减
LocalTime time = LocalTime.now();
LocalTime time_2 = time.plusHours(2);
LocalTime time_3 = time.plusMinutes(2);
LocalTime time_4 = time.plusSeconds(2);
LocalTime time_5 = time.plusNanos(2);
LocalTime time_6 = time.plusNanos(-2);
同样,还可以传入TemporalUnit(时间单位)接口的实现类枚举ChronoUnit来决定修改的属性
LocalTime time_6 = time.plus(1, ChronoUnit.HOURS);
LocalTime time_7 = time.plus(1, ChronoUnit.SECONDS);
示例2: 日期的加减
LocalDate日期的加减同理
LocalDate date = LocalDate.now();
LocalDate date_2 = date.plusYears(1);
LocalDate date_3 = date.plusMonths(1);
LocalDate date_4 = date.plusWeeks(1);
LocalDate date_5 = date.plusDays(1);
LocalDate date_6 = date.plus(1, ChronoUnit.DAYS);
LocalDate date_7 = date.plus(Period.of(2019, 12, 10));
示例3: 时间的比较
Java 8提供了isAfter()、isBefore()用于判断当前日期时间和指定日期时间的比较LocalDate、LocalTime和LocalDateTime都有这个方法
LocalDate now = LocalDate.now();
LocalDate date1 = LocalDate.of(2000, 1, 1);
if (now.isAfter(date1)) {
System.out.println("千禧年已经过去了");
}
LocalDate date2 = LocalDate.of(2020, 1, 1);
if (now.isBefore(date2)) {
System.out.println("2020年还未到来");
}
六、日期时间差 Period 和 Duration
java 8 中引入的两个与日期相关的新类:Period 和 Duration。两个类表示时间量或两个日期之间的差,两者之间的差异为:Period基于日期值,而Duration基于时间值。
1. Period(一段时期)
示例1: 获取两个日期的差
Period 类表示一段时间的年、月、日,使用between()方法获取两个日期之间的差作为Period 对象返回,通过get方法可以获取间隔的年、月、日;
LocalDate startDate = LocalDate.of(2015, 2, 20);
LocalDate endDate = LocalDate.of(2017, 1, 15);
Period period = Period.between(startDate, endDate);
System.out.printf("years:%d, months:%d, days:%d",period.getYears(), period.getMonths(), period.getDays());
示例2: 比较日期的前后关系
在这种情况下,任何一个属性为负值,则isNegative()方法返回true,因此可以用于判断startDate是否晚于endDate
LocalDate startDate = LocalDate.of(2017, 1, 15);
LocalDate endDate = LocalDate.of(2015, 2, 20);
Period period = Period.between(startDate, endDate);
if (period.isNegative()) {
System.out.println("开始时间比结束时间晚");
}
示例3: 创建指定的日期间隔
Period提供了一系列ofXXX()的静态方法用来构建自己
Period fromUnits = Period.of(3, 10, 10);
Period fromDays = Period.ofDays(50);
Period fromMonths = Period.ofMonths(5);
Period fromYears = Period.ofYears(10);
Period fromWeeks = Period.ofWeeks(40);
assertEquals(280, fromWeeks.getDays());
示例4: 文本参数转Period
我们也可以通过解析文本序列来创建Period,其格式为“PnYnMnD”,YMD分别表示为年月日
Period fromCharYears = Period.parse("P2Y");
assertEquals(2, fromCharYears.getYears());//2年
Period fromCharUnits = Period.parse("P2Y3M5D");
assertEquals(5, fromCharUnits.getDays());//2年3个月5天
示例5:Period的加减
Period的年月日可以通过plusX()、minusX()方法进行增加或减少,其中X表示日期单元:
assertEquals(56, period.plusDays(50).getDays());
assertEquals(9, period.minusMonths(2).getMonths());
2. Duration(持续时间)
Duration类表示秒或纳秒时间间隔,适合处理较短的时间,需要更高的精确性,拥有long **seconds和int **nanos两个属性。我们能使用between()方法比较两个瞬间的差,同时用get()方法获取秒和纳秒的值
示例1: 获取时间差
利用Duration.between()获取Instant的时间差
Instant start = Instant.parse("2017-10-03T10:15:30.00Z");
Instant end = Instant.parse("2017-10-03T10:16:30.00Z");
Duration duration = Duration.between(start, end);
System.out.printf("seconds:%d, nanos:%d", duration.getSeconds(), duration.getNano());
利用Duration.between()获取LocalDataTime或者LocalTime的时间差
LocalTime start = LocalTime.of(1, 20, 25, 1024);
LocalTime end = LocalTime.of(3, 22, 27, 1544);
Duration.between(start, end).getSeconds();
示例2: 比较时间的前后关系
在这种情况下,任何一个属性为负值,则isNegative()方法返回true,因此可以用于判断startTime是否晚于endTime
duration.isNegative()
示例3: 创建指定的日期间隔
Duration同样也提供了一系列ofXXX()的静态方法用来构建自己
Duration duration1 = Duration.ofDays(1);
Duration duration2 = Duration.ofHours(1);
Duration duration3 = Duration.ofMinutes(1);
Duration duration4 = Duration.ofSeconds(1);
Duration duration5 = Duration.ofMillis(1);
Duration duration6 = Duration.ofNanos(1);
示例4: 文本参数转Duration
我们也可以通过解析文本序列来创建Duration,其格式为“PnDTnHnMn.nS”,DHMS分别表示为日时分秒,P和T是固定格式,T在最后一位是要省略
Duration fromChar1 = Duration.parse("P1DT1H10M10.5S");
Duration fromChar2 = Duration.parse("PT10M");
Duration fromChar3 = Duration.parse("-P2D");
七. 时区 ZoneDateTime
ZoneDateTime用来表示带有时区的时间,同样有三个主要属性:
- LocalDateTime **dateTime**:聚合了LocalDate和LocalTime,用来表时完整的日期和时间
- LocalDate
- LocalTime
- ZoneOffset **offse**t:表示时区的偏移量
- totalSeconds
- id
- ZoneRegion **zone**:表示时区属性
- id
- ZoneRules rules
示例: 创建带有时区的日期时间
Java 8不仅分离了日期和时间,也把时区分离出来了。现在有一系列单独的类如ZoneId来处理特定时区,ZoneDateTime类来表示某时区下的时间。
// 上海时间
ZoneId shanghaiZoneId = ZoneId.of("Asia/Shanghai");
ZonedDateTime shanghaiZonedDateTime = ZonedDateTime.now(shanghaiZoneId);
// 东京时间
ZoneId tokyoZoneId = ZoneId.of("Asia/Tokyo");
ZonedDateTime tokyoZonedDateTime = ZonedDateTime.now(tokyoZoneId);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
System.out.println("上海时间: " + shanghaiZonedDateTime.format(formatter));
System.out.println("东京时间: " + tokyoZonedDateTime.format(formatter));
八. 格式化
java8 提供了DateTimeFormatter来格式化时间
示例1: 使用预定义格式解析与格式化日期
LocalDate和LocalDateTime都拥有parse()方法,可以依据DateTimeFormatter内预设的日期格式对传入的日期字符串进行解析
// 解析日期
String dateText = "20180924";
LocalDate date = LocalDate.parse(dateText, DateTimeFormatter.BASIC_ISO_DATE);
System.out.println("格式化之后的日期=" + date);
// 格式化日期
dateText = date.format(DateTimeFormatter.ISO_DATE);
System.out.println("dateText=" + dateText);
示例2: 日期和字符串的相互转换
可以通过DateTimeFormatter**.ofPattern()**方法,传入一个日期格式来初始化DateTimeFormatter,进行转换
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 日期时间转字符串
LocalDateTime now = LocalDateTime.now();
String nowText = now.format(formatter);
System.out.println("nowText=" + nowText);
// 字符串转日期时间
String datetimeText = "1999-12-31 23:59:59";
LocalDateTime datetime = LocalDateTime.parse(datetimeText, formatter);
System.out.println(datetime);
九、相关类说明
Instant 时间戳
Duration 持续时间、时间差
LocalDate 只包含日期,比如:2018-09-24
LocalTime 只包含时间,比如:10:32:10
LocalDateTime 包含日期和时间,比如:2018-09-24 10:32:10
Peroid 时间段
ZoneOffset 时区偏移量,比如:+8:00
ZonedDateTime 带时区的日期时间
Clock 时钟,可用于获取当前时间戳
java.time.format.DateTimeFormatter 时间格式化类