替换老旧的java.util.Date
Java中的日期核心概念
- 所有java.time对象都是不可变的
- 一个Instant是时间线上的一个点,功能上类似老的java.util.Date
- 在java的时间里,每天都是86400秒,没有闰秒
- 一个Duration就是两个时刻的间隔
- LocalDateTime没有时区信息
- TemporalAdjuster能处理常用的日历计算,例如查找某月的第一个星期二
- ZonedDateTime是指定时区的一个时间点,类似于GregorianCalendar
- 当处理带时区时间时,使用Period而不是Duration,以便更好的考虑夏时令变化
- 使用DateTimeFormatter来格式化并解析日期和时间
从计算机角度看待日期(时间线)
Java中,一个Instant表示时间线上一个点。原点,就是格林尼治时间1970年1月1号0点。UNIX/POSIX时间也使用同样约定。从原点开始按照每天86400秒精确的计算,向前向后都以纳秒计算。 Instant.MIN追溯到10亿年前,Instant.MAX是1000000000年的12月31日。
Instant.now()会返回当前时间点。
Duration.between可以计算时间点的时间差。
Instant start = Instant.now();
runAlgorithm();
Instant end = Instant.now();
Duration timeElapsed = Duration.between(start, end);
long millis = timeElapsed.toMillis();
一个Duration就是两个瞬时时间点的时间量,时间量可以通过纳秒、毫秒、秒、分、小时、天等时间单位来计量。
下表是Instant和Duration的常用方法。
方法 | 描述 |
---|---|
plus, minus | 对当前的Instant 或者 Duration增加或者减少一段时间. |
plusNanos plusMillis plusSeconds plusMinutes plusHours plusDays | 根据指定时间单位增加Instant或者Duration |
minusNanos minusMillis minusSeconds minusMinutes minusHours minusDays | 根据指定时间单位减少Instant或者Duration |
multipliedBy dividedBy negated | 用给定的long值(或者-1)对Duration进行相乘或相除,从而对Duration进行缩放。 注意Instant是不能缩放的 |
isZero isNegative | 检查Duration是否为0或者负数 |
如果想检查一个算法的速度是否是另一个算法的10倍以上,可以这样:
Duration timeElapsed2 = Duration.between(start2, end2);
boolean overTenTimesFaster
= timeElapsed.multipliedBy(10).minus(timeElapsed2).isNegative();
// Or timeElapsed.toNanos() * 10 < timeElapsed2.toNanos()
Java8提供的常用的日期类
本地日期(LocalDate)
方法 | 描述 |
---|---|
now of ofInstant | 从当前时间、指定的年月日、Instant或者ZonedId构造LocalDate对象 |
plusDays, plusWeeks, plusMonths, plusYears | 根据指定时间单位增加LocalDate |
minusDays, minusWeeks, minusMonths, minusYears | 根据指定时间单位减少LocalDate |
datesUntil | 产生一个Stream, 包含当前当前日期和终止日期及两者之间的所有日期 |
plus, minus | 增减一个 Duration(针对Instant) 或者 Period(针对LocalDate). |
withDayOfMonth withDayOfYear withMonth withYear | 返回一个新的LocalDate对象,把年月日设定为指定的值 |
getDayOfMonth | 获取一个月中哪一天 ( 1 到 31). |
getDayOfYear | 获取一年中哪一天 ( 1 and 366). |
getDayOfWeek | 获取一周中哪一天, 返回 DayOfWeek 枚举值. |
getMonth getMonthValue | 获取一年中哪个月,返回 Month 枚举, 或者 1 and 12. |
getYear | 取得年份, between –999,999,999 and 999,999,999. |
until | 获取一个 Period 对象, 或者指定单位(ChronoUnits)的日期差. |
toEpochSecond | 给定一个LocalTime 和 ZoneOffset对象, 产生原点到指定时间点的秒数. |
isBefore isAfter | 比较LocalDate |
isLeapYear | 返回是否为闰年 |
程序员日是一年的第256天,我们很容易计算具体是哪一天:
LocalDate programmersDay = LocalDate.of(2014, 1, 1).plusDays(255);
// 九月13号, 闰年时是9月12号
时间线中两个时间点(Instant)的区间用Duration表示,对应的LocalDate的时间差异用Period来表示。
until方法会返回一个Period。
independenceDay.until(christmas); //返回独立日到圣诞节之间日期区间
java
independenceDay.until(christmas, ChronoUnit.DAYS) // 返回174 天
本地时间(LocalTime)
LocalTime表示本地时间,例如15:30:00。可以使用now或者of方法创建一个实例。
LocalTime now = LocalTime.now();
LocalTime bedTime = LocalTime.of(22, 30); //或者 LocalTime.of(22, 30, 0)
下表是LocalTime的常用方法。
方法 | 描述 |
---|---|
now, of, ofInstant | 这些静态方法用于构造LocalTime对象 |
plusHours, plusMinutes, plusSeconds, plusNanos | 向当前的LocalTime对象增加小时、分、秒、纳秒 |
minusHours, minusMinutes, minusSeconds, minusNanos | 从当前的LocalTime对象减小小时、分、秒、纳秒 |
plus, minus | 增减Duration |
withHour, withMinute, withSecond, withNano | 将LocalTime对象的小时、分钟、秒、纳秒设置为指定值,并返回一个新的LocalTime对象 |
getHour, getMinute, getSecond, getNano | 获得LocalTime的小时、分钟、秒、纳秒 |
toSecondOfDay, toNanoOfDay | 返回午夜零点到当前LocalTime对象之间相隔的秒数或者纳秒数 |
toEpochSecond | 把LocalTime对象转换成从原点(1970-01-01)开始的秒数 |
isBefore, isAfter | 比较LocalTime |
日期调整器(TemporalAdjusters)
TemporalAdjusters类提供了一些常用的静态方法用于调整日期。
方法 | 描述 |
---|---|
next(weekday), previous(weekday) | 指定weekday 的前一天或后一天 |
nextOrSame(weekday), previousOrSame(weekday) | 从指定日期开始,指定weekday 的前一天或后一天 |
dayOfWeekInMonth(n, weekday) | 指定月份的第几个weekday |
lastInMonth(weekday) | 指定月份的最后一个weekday |
firstDayOfMonth(), firstDayOfNextMonth(), firstDayOfNextYear(), lastDayOfMonth(), lastDayOfPreviousMonth(), lastDayOfYear() | 不解释 |
格式化和解析(DateTimeFormatter)
DateTimeFormatter)提供了三种格式化日期/时间的方式:
- 预定义的标准格式化(这个看源码就行了,基本没有符合我们中国人习惯的预定格式)
- 本地化的日期和时间格式化
- 自定义模板的格式化(这是我们天朝程序员的最爱)
仅凭DateTimeFormatter.ofPattern就可以仗剑走天涯了。
常见的日期问题
如何获取一天中的开始和结束时间
利用 LocalDate 对象:
// atStartOfDay()
LocalDateTime startOfDay = localDate.atStartOfDay();
ZonedDateTime startOfDay = localDate.atStartOfDay(ZoneId.of("Europe/Paris"));
// of()
LocalDateTime startOfDay = LocalDateTime.of(localDate, LocalTime.MIDNIGHT);
// atTime()
LocalDateTime startOfDay = localDate.atTime(LocalTime.MAX);
// atDate()
LocalDateTime endOfDate = LocalTime.MAX.atDate(localDate);
利用 LocalDateTime 对象
// 常规做法
LocalDateTime localDateTime = LocalDateTime.parse("2018-06-23T05:55:55");
LocalDateTime endOfDate = localDateTime.toLocalDate().atTime(LocalTime.MAX);
// with()
LocalDateTime endOfDate = localDateTime.with(ChronoField.NANO_OF_DAY, LocalTime.MAX.toNanoOfDay());
利用 ZonedDateTime 对象
ZonedDateTime startofDay = zonedDateTime.with(ChronoField.HOUR_OF_DAY, 0);
获取日期序列
java7 的方式
public static List<Date> getDatesBetweenUsingJava7(
Date startDate, Date endDate) {
List<Date> datesInRange = new ArrayList<>();
Calendar calendar = new GregorianCalendar();
calendar.setTime(startDate);
Calendar endCalendar = new GregorianCalendar();
endCalendar.setTime(endDate);
while (calendar.before(endCalendar)) {
Date result = calendar.getTime();
datesInRange.add(result);
calendar.add(Calendar.DATE, 1);
}
return datesInRange;
}
java8 的方式
public static List<LocalDate> getDatesBetweenUsingJava8(
LocalDate startDate, LocalDate endDate) {
long numOfDaysBetween = ChronoUnit.DAYS.between(startDate, endDate);
return IntStream.iterate(0, i -> i + 1)
.limit(numOfDaysBetween)
.mapToObj(i -> startDate.plusDays(i))
.collect(Collectors.toList());
}
java9 的方式
public static List<LocalDate> getDatesBetweenUsingJava9(
LocalDate startDate, LocalDate endDate) {
return startDate.datesUntil(endDate)
.collect(Collectors.toList());
}
两个日期之间相隔多少天(时/分/秒)
java.time.temporal.ChronoUnit
@Test
public void givenTwoDateTimesInJava8_whenDifferentiatingInSeconds_thenWeGetTen() {
LocalDateTime now = LocalDateTime.now();
LocalDateTime tenSecondsLater = now.plusSeconds(10);
long diff = ChronoUnit.SECONDS.between(now, tenSecondsLater);
assertEquals(10, diff);
}
java.time.temporal.Temporal#until()
@Test
public void givenTwoDateTimesInJava8_whenDifferentiatingInSecondsUsingUntil_thenWeGetTen() {
LocalDateTime now = LocalDateTime.now();
LocalDateTime tenSecondsLater = now.plusSeconds(10);
long diff = now.until(tenSecondsLater, ChronoUnit.SECONDS);
assertEquals(10, diff);
}
java.time.Duration and java.time.Period
@Test
public void givenTwoDateTimesInJava8_whenDifferentiating_thenWeGetSix() {
LocalDateTime now = LocalDateTime.now();
LocalDateTime sixMinutesBehind = now.minusMinutes(6);
Duration duration = Duration.between(now, sixMinutesBehind);
long diff = Math.abs(duration.toMinutes());
assertEquals(6, diff);
}
@Test
public void givenTwoDatesInJava8_whenUsingPeriodGetDays_thenWorks() {
LocalDate aDate = LocalDate.of(2020, 9, 11);
LocalDate sixDaysBehind = aDate.minusDays(6);
Period period = Period.between(aDate, sixDaysBehind);
int diff = Math.abs(period.getDays());
assertEquals(6, diff);
}
注意:这个方法是有问题的,Period具有独立的年月日三个字段。
@Test
public void givenTwoDatesInJava8_whenUsingPeriodGetDays_thenDoesNotWork() {
LocalDate aDate = LocalDate.of(2020, 9, 11);
LocalDate sixtyDaysBehind = aDate.minusDays(60);
Period period = Period.between(aDate, sixtyDaysBehind);
int diff = Math.abs(period.getDays());
assertEquals(60, diff); //实际值是29!!!!!!
}
修正的方法:
@Test
public void givenTwoDatesInJava8_whenUsingPeriod_thenWeGet0Year1Month29Days() {
LocalDate aDate = LocalDate.of(2020, 9, 11);
LocalDate sixtyDaysBehind = aDate.minusDays(60);
Period period = Period.between(aDate, sixtyDaysBehind);
int years = Math.abs(period.getYears());
int months = Math.abs(period.getMonths());
int days = Math.abs(period.getDays());
assertArrayEquals(new int[] { 0, 1, 29 }, new int[] { years, months, days });
}
ZonedDateTime 和 OffsetDateTime 的区别
ZonedDateTime
- 存储了日期时间的所有字段,精度为纳秒,并且带有时区信息
- 里面的时区信息控制时区偏差值, 不能随意设置
- 支持夏令时(DST: Daylight Saving Time)
OffsetDateTime
- 存储了日期时间的所有字段,精度为纳秒,并且带有相对于格林尼治时间的时区偏差值信息
- 适合存储在数据库中和进行网络传输
与遗留代码共存
Java中, Instant是原先java.util.Date的替代类。Java8的Date类中增加了两个方法。
toIntant方法将Date对象转换为Instant对象,还有一个静态方法from正好相反。
类 | 转换为遗留类 | 从遗留类转换为新类 |
---|---|---|
Instant ↔ java.util.Date | Date.from(instant) | data.toInstant() |
ZonedDateTime ↔ java.util.GregorianCalendar | GregorianCalendar .from(zonedDateTime) | cal.toZonedDateTime() |
Instant ↔ java.sql.Timestamp | TimeStamp.from(instant | timestamp.toInstant() |
LocalDateTime ↔ java.sql.Timestamp | Timestamp.valueOf(localDateTime) | timestamp.toLocalDateTime() |
LocalDate ↔ java.sql.Date | Date.valueOf(localDate) | date.toLocalDate() |
LocalTime ↔ java.sql.Time | Time.valueOf(localTime) | time.toLocalTime() |
DateTimeFormatter → java.text.DateFormat | formatter.toFormat() | 无 |
java.util.TimeZone → ZoneId | Timezone.getTimeZone(id) | timeZone.toZoneId() |
java.nio.file.attribute.FileTime → Instant | FileTime.from(instant) | fileTime.toInstant() |
新旧日期互转的一些例子
Java Date to LocalDate
常用的两种方式:
1、利用 Date.toInstant() 方法
// 注意要有时区转换
public static LocalDate convertDateToLocalDateUsingInstant(Date date) {
return date.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDate();
}
// Intant.ofEpochMilli(date.getTime())能获取Instant对象
public static LocalDate convertDateToLocalDateUsingOfEpochMilli(Date date) {
return Instant.ofEpochMilli(date.getTime())
.atZone(ZoneId.systemDefault())
.toLocalDate();
}
2、利用 java.sql.Date
// java.sql.Date提供了一个方便的转换方法
public static LocalDate convertDateToLocalDateUsingSQLDate(Date date) {
return new java.sql.Date(date.getTime()).toLocalDate();
}
Java LocalDate to Date
与上面类似, 还是两种方式: 利用Instant 和 java.sql.Date
1、利用Instant的例子
public static Date convertToDateUsingInstant(LocalDate date) {
return java.util.Date.from(date.atStartOfDay()
.atZone(ZoneId.systemDefault())
.toInstant());
}
2、利用java.sql.Date
public static Date convertToDateUsingDate(LocalDate date) {
return java.sql.Date.valueOf(date);
}
Java LocalDateTime to Date
利用 Instant 对象 或者 Timestamp
package com.sptan;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
public class LocalDateTimeToDateMain {
public static void main(String[] args) {
LocalDateTime ldt = LocalDateTime.now();
Date dt1=convertLocalDateTimeToDateUsingInstant(ldt);
System.out.println(dt1);
System.out.println("=====================");
Date dt2=convertLocalDateTimeToDateUsingTimestamp(ldt);
System.out.println(dt2);
}
public static Date convertLocalDateTimeToDateUsingInstant(LocalDateTime localDateTime) {
return Date
.from(localDateTime.atZone(ZoneId.systemDefault())
.toInstant());
}
public static Date convertLocalDateTimeToDateUsingTimestamp(LocalDateTime localDateTime) {
return Timestamp.valueOf(localDateTime);
}
}
Convert LocalDateTime to Timestamp in Java
package com.sptan;
import java.sql.Timestamp;
import java.time.LocalDateTime;
public class ConvertLocalDataTimeToTimestamp {
public static void main(String[] args) {
LocalDateTime current_date_time = LocalDateTime.now();
//returns time and date object of today's date.
//printing the time and date
System.out.println("Local Date Time : " + current_date_time);
//Timestamp object
Timestamp timestamp_object = Timestamp.valueOf(current_date_time);
System.out.println("Time stamp : " + timestamp_object);
}
}
输出结果类似于:
Local Date Time : 2021-09-01T23:49:33.175092900
Time stamp : 2021-09-01 23:49:33.1750929
一些常见问题新旧日期的不同处理方法示例
取得当前时间
// Old
Date now = new Date();
// New
ZonedDateTime now = ZonedDateTime.now();
表示指定时间
// Old
Date birthDay = new GregorianCalendar(1990, Calendar.DECEMBER, 15).getTime();
// New
LocalDate birthDay = LocalDate.of(1990, Month.DECEMBER, 15);
提取特定字段
// Old
int month = new GregorianCalendar().get(Calendar.MONTH);
// New
Month month = LocalDateTime.now().getMonth();
对时间进行增减运算
// Old
GregorianCalendar calendar = new GregorianCalendar();
calendar.add(Calendar.HOUR_OF_DAY, -5);
Date fiveHoursBefore = calendar.getTime();
// New
LocalDateTime fiveHoursBefore = LocalDateTime.now().minusHours(5);
设置指定的字段
// Old
GregorianCalendar calendar = new GregorianCalendar();
calendar.set(Calendar.MONTH, Calendar.JUNE);
Date inJune = calendar.getTime();
// New
LocalDateTime inJune = LocalDateTime.now().withMonth(Month.JUNE.getValue());
截取
截取是指将指定的时间字段重置。
比如下面的例子分钟重置到0。
// Old
Calendar now = Calendar.getInstance();
now.set(Calendar.MINUTE, 0);
now.set(Calendar.SECOND, 0);
now.set(Calendar.MILLISECOND, 0);
Date truncated = now.getTime();
// New
LocalTime truncated = LocalTime.now().truncatedTo(ChronoUnit.HOURS);
时区转换
// CET为欧洲中部时区
// Old
GregorianCalendar calendar = new GregorianCalendar();
calendar.setTimeZone(TimeZone.getTimeZone("CET"));
Date centralEastern = calendar.getTime();
// New
ZonedDateTime centralEastern = LocalDateTime.now().atZone(ZoneId.of("CET"));
取得时间跨度
// Old
GregorianCalendar calendar = new GregorianCalendar();
Date now = new Date();
calendar.add(Calendar.HOUR, 1);
Date hourLater = calendar.getTime();
long elapsed = hourLater.getTime() - now.getTime();
// New
LocalDateTime now = LocalDateTime.now();
LocalDateTime hourLater = LocalDateTime.now().plusHours(1);
Duration span = Duration.between(now, hourLater);
时间的格式化与解析
// Old
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
Date now = new Date();
String formattedDate = dateFormat.format(now);
Date parsedDate = dateFormat.parse(formattedDate);
// New
LocalDate now = LocalDate.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String formattedDate = now.format(formatter);
LocalDate parsedDate = LocalDate.parse(formattedDate, formatter);
一月之中的天数计数
// Old
Calendar calendar = new GregorianCalendar(1990, Calendar.FEBRUARY, 20);
int daysInMonth = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
// New
int daysInMonth = YearMonth.of(1990, 2).lengthOfMonth();