Java 8通过发布新的Date-Time API来进一步加强对日期与时间的处理。 旧版的 Java 中,日期时间 API 存在诸多问题 :
非线程安全 − java.util.Date 是非线程安全的,所有的日期类都是可变的,
设计很差 − Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。
时区处理麻烦 − 日期类并不提供国际化,没有时区支持
Java 8 在 java.time 包下提供了很多新的 API。以下为两个比较重要的 API:
Local(本地) : 简化了日期时间的处理,没有时区的问题。
Zoned(时区) − 通过制定的时区处理日期时间。
新的java.time包涵盖了所有处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操作。
Java 8 Date Time API是JSR-310实现。它旨在克服传统日期时间实现中的所有缺陷。新Date Time API的一些设计原则是:
- 不可变性:新Date Time API中的所有类都是不可变的,适用于多线程环境。
- 关注点分离:新API明确区分人类可读日期时间和机器时间(unix时间戳)。它为Date,Time,DateTime,Timestamp,Timezone等定义了单独的类。
- 清晰度:方法明确定义,并在所有类中执行相同的操作。例如,要获取当前实例,我们有
now()
方法。在所有这些类中定义了format()和parse()方法,而不是为它们设置单独的类。 - 实用程序操作:所有新的Date Time API类都带有执行常见任务的方法,例如加号,减号,格式,解析,在日期/时间中获取单独的部分等。
- 可扩展:新的Date Time API适用于ISO-8601日历系统,但我们也可以将其与其他非ISO日历一起使用。
Java 8 Date Time API包
Java 8 Date Time API包含以下包。
- java.time包:这是新的Java Date Time API的基础包。所有主要的基类是该计划的一部分,比如
LocalDate
,LocalTime
,LocalDateTime
,Instant
,Period
,Duration
等所有这些类是不可变的和线程安全的。大多数情况下,这些类足以满足常见要求。 - java.time.chrono包:此包定义非ISO日历系统的通用API。我们可以扩展
AbstractChronology
类来创建我们自己的日历系统。 - java.time.format包:此包包含用于格式化和解析日期时间对象的类。大多数情况下,我们不会直接使用它们,因为java.time包中的主要类提供了格式化和解析方法。
- java.time.temporal包:此包包含临时对象,我们可以使用它来查找与日期/时间对象相关的特定日期或时间。例如,我们可以使用这些来查找该月的第一天或最后一天。您可以轻松识别这些方法,因为它们始终具有“withXXX”格式。
- java.time.zone包:此包包含用于支持不同时区及其规则的类。
Java 8 Date Time API示例
我们已经研究了Java Date Time API的大部分重要部分。现在是时候通过示例查看最重要的Date Time API类。
LOCALDATE
LocalDate只提供日期不提供时间信息。它是不可变类且线程安全的。
LocalDate
是一个不可变类,表示默认格式为yyyy-MM-dd的Date。我们可以使用now()
方法来获取当前日期。我们还可以提供年,月和日的输入参数来创建LocalDate实例。此类为now()提供重载方法,我们可以通过ZoneId获取特定时区的日期。该类提供与以下相同的功能java.sql.Date
。让我们看一个简单的例子来说明它的用法。
public static void main(String[] args) {
//获取当前时间
LocalDate today = LocalDate.now();
System.out.println("Current Date="+today);
//根据传入的参数构建年月日
LocalDate firstDay_2019 = LocalDate.of(2019, Month.JANUARY, 1);
System.out.println("Specific Date="+firstDay_2019);
//或
LocalDate firstDayOfNove_2019 = LocalDate.of(2019, 11, 1);
System.out.println("Specific Date="+firstDayOfNove_2019);
//Try creating date by providing invalid inputs
//LocalDate feb29_2014 = LocalDate.of(2014, Month.FEBRUARY, 29);
//Exception in thread "main" java.time.DateTimeException:
//Invalid date 'February 29' as '2014' is not a leap year
//Current date in "Asia/Kolkata", you can get it from ZoneId javadoc
// now(ZoneId) : 从指定时区的系统时钟中获取当前日期
LocalDate todayKolkata = LocalDate.now(ZoneId.of("Asia/Kolkata"));
System.out.println("Current Date in IST="+todayKolkata);
//java.time.zone.ZoneRulesException: Unknown time-zone ID: IST
//LocalDate todayIST = LocalDate.now(ZoneId.of("IST"));
//Getting date from the base date i.e 01/01/1970,
// 基于1970-01-01时代转换的大纪元日。从1970年开始获取指定天数的LocalDate实例
LocalDate dateFromBase = LocalDate.ofEpochDay(730);
System.out.println("730th day from base date= "+dateFromBase);
//ofYearDay(int year,int dyaOfYear) : 指定某年中的指定天数获取LocalDate实例,比如2019年的第100天是几年几月几日
LocalDate hundredDay2019 = LocalDate.ofYearDay(2019, 100);
System.out.println("100th day of 2019="+hundredDay2019);
}
结果:
Current Date=2019-10-23
Specific Date=2019-01-01
Specific Date=2019-11-01
Current Date in IST=2019-10-23
730th day from base date= 1972-01-01
100th day of 2019=2019-04-10
LocalDateTime
LocalDateTime
是一个不可变的日期时间对象,表示日期时间,默认格式为yyyy-MM-dd-HH-mm-ss.zzz。它提供了一个工厂方法,用于获取LocalDate
和LocalTime
输入参数以创建LocalDateTime
实例。
public static void main(String[] args) {
//Current Date
LocalDateTime today = LocalDateTime.now();
System.out.println("Current DateTime="+today);
//Current Date using LocalDate and LocalTime
today = LocalDateTime.of(LocalDate.now(), LocalTime.now());
System.out.println("Current DateTime="+today);
//Creating LocalDateTime by providing input arguments
LocalDateTime specificDate = LocalDateTime.of(2019, Month.JANUARY, 1, 10, 10, 30);
System.out.println("Specific Date="+specificDate);
//Try creating date by providing invalid inputs
//LocalDateTime feb29_2014 = LocalDateTime.of(2014, Month.FEBRUARY, 28, 25,1,1);
//Exception in thread "main" java.time.DateTimeException:
//Invalid value for HourOfDay (valid values 0 - 23): 25
//Current date in "Asia/Kolkata", you can get it from ZoneId javadoc
LocalDateTime todayKolkata = LocalDateTime.now(ZoneId.of("Asia/Kolkata"));
System.out.println("Current Date in IST="+todayKolkata);
//java.time.zone.ZoneRulesException: Unknown time-zone ID: IST
//LocalDateTime todayIST = LocalDateTime.now(ZoneId.of("IST"));
//Getting date from the base date i.e 01/01/1970
LocalDateTime dateFromBase = LocalDateTime.ofEpochSecond(10000, 0, ZoneOffset.UTC);
System.out.println("10000th second time from 01/01/1970= "+dateFromBase);
}
在所有这三个例子中,我们已经看到如果我们为创建Date / Time提供了无效的参数,那么它抛出的java.time.DateTimeException
是RuntimeException,所以我们不需要显式地捕获它。
我们还看到我们可以通过传递获取日期/时间数据ZoneId
,您可以从它的javadoc获取支持的ZoneId值列表。当我们在上面运行时,我们得到以下输出。
Current DateTime=2019-10-23T19:30:19.334
Current DateTime=2019-10-23T19:30:19.335
Specific Date=2019-01-01T10:10:30
Current Date in IST=2019-10-23T17:00:19.336
10000th second time from 01/01/1970= 1970-01-01T02:46:40
Instant 时间戳
Instant类用于处理机器可读的时间格式,它将日期时间存储在unix时间戳中。
public static void main(String[] args) {
//Current timestamp
//Instant.now()使用等是UTC时间Clock.systemUTC().instant()
Instant timestamp = Instant.now();
System.out.println("Current Timestamp = "+timestamp);
//通过上述方式获取的时间戳与北京时间相差8个时区,需要修正为北京时间
Instant now = Instant.now().plusMillis(TimeUnit.HOURS.toMillis(8));
System.out.println("Beijing时间为:"+now);
System.out.println("秒数:"+now.getEpochSecond());
System.out.println("毫秒数:"+now.toEpochMilli());
//Instant from timestamp
Instant specificTime = Instant.ofEpochMilli(timestamp.toEpochMilli());
System.out.println("Specific Time = "+specificTime);
}
结果:
Current Timestamp = 2019-10-23T11:38:45.004Z
Beijing时间为:2019-10-23T19:38:45.149Z
秒数:1571859525
毫秒数:1571859525149
Specific Time = 2019-10-23T11:38:45.004Z
java 8 的 Period 和 Duration 类
Period 和 Duration。两个类看表示时间量或两个日期之间的差,两者之间的差异为:Period基于日期值,而Duration基于时间值。
Period.between方法坑及注意事项
public static void main(String[] args) {
LocalDate startDate = LocalDate.of(2018, 6, 25);
LocalDate endDate = LocalDate.of(2019, 8, 24);
//使用between()方法获取两个日期之间的差作为Period 对象返回
Period period = Period.between(startDate, endDate);
System.out.println("相差年数:" + period.getYears() );
//月份数是1.。。。。。。。。。。。。。。。。。。。。并不跨年,而且少于2个月就会等于为1个月
System.out.println("相差月数:" + period.getMonths());
//不跨年月
System.out.println("相差天数:"+period.getDays());
Period fromWeeks = Period.ofWeeks(40);
System.out.println(fromWeeks.getDays());
}
结果:可以看到有坑,相差天数明显不是我们想要的结果
相差年数:1
相差月数:1
相差天数:30
280
Duration类表示秒或纳秒时间间隔,适合处理较短的时间,需要更高的精确性。我们能使用between()方法比较两个瞬间的差:
Instant start = Instant.parse("2019-11-03T10:15:30.00Z");
Instant end = Instant.parse("2019-11-03T10:16:30.00Z");
Duration duration = Duration.between(start, end);
System.out.println(duration.getSeconds());
结果:
60
计算两个时间间隔多少天:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate startDate = LocalDate.parse("2019-01-01", formatter);
LocalDate endDate = LocalDate.parse("2020-01-01", formatter);
// 日期区间
long days = ChronoUnit.DAYS.between(startDate, endDate);
//月
long month = ChronoUnit.MONTHS.between(startDate, endDate);
//年
long year = ChronoUnit.YEARS.between(startDate, endDate);
LocalDate与String相互转化
LocalDate date = LocalDate.now();
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String dateStr = date.format(fmt);
System.out.println("LocalDate转String:"+dateStr);
String timeStr = "2019-11-05";
LocalDate localDate = LocalDate.parse(timeStr, fmt);
System.out.println("String转LocalDate:"+localDate);
结果:
LocalDate转String:2019-10-23
String转LocalDate:2019-11-05
TemporalAdjuster
有的时候,你需要进行一些更加复杂的操作,比如,将日期调整到下个周日、下个工作日,或者是本月的最后一天。这时,你可以使用重载版本的with方法,向其传递一个提供了更多定制化选择的TemporalAdjuster对象
@FunctionalInterface
public interface TemporalAdjuster {
Temporal adjustInto(Temporal temporal);
}
这里一些常用的操作在TemporalAdjusters类已经预定义了
TemporalAdjusters的一些预定义方法
dayOfWeekInMonth 创建一个新的日期,它的值为同一个月中每一周的第几天
firstDayOfMonth 创建一个新的日期,它的值为当月的第一天
firstDayOfNextMonth 创建一个新的日期,它的值为下月的第一天
firstDayOfNextYear 创建一个新的日期,它的值为明年的第一天
firstDayOfYear 创建一个新的日期,它的值为当年的第一天
firstInMonth 创建一个新的日期,它的值为同一个月中,第一个符合星期几要求的值
lastDayOfMonth 创建一个新的日期,它的值为当月的最后一天
lastDayOfNextMonth 创建一个新的日期,它的值为下月的最后一天
lastDayOfNextYear 创建一个新的日期,它的值为明年的最后一天
lastDayOfYear 创建一个新的日期,它的值为今年的最后一天
lastInMonth 创建一个新的日期,它的值为同一个月中,最后一个符合星期几要求的值
next/previous
创建一个新的日期,并将其值设定为日期调整后或者调整前,第一个符合指定星 期几要求的日期
nextOrSame/previousOrSame创建一个新的日期,并将其值设定为日期调整后或者调整前,第一个符合指定星
期几要求的日期,如果该日期已经符合要求,直接返回该对象
LocalDate today = LocalDate.now();
System.out.println("today"+today);
//判断是否为闰年
System.out.println("Year "+today.getYear()+" is Leap Year? "+today.isLeapYear());
//Compare two LocalDate for before and after
System.out.println("Today is before 01/01/2019? "+today.isBefore(LocalDate.of(2019,1,1)));
//Create LocalDateTime from LocalDate
System.out.println("Current Time="+today.atTime(LocalTime.now()));
//plus and minus operations
System.out.println("10 days after today will be "+today.plusDays(10));
System.out.println("3 weeks after today will be "+today.plusWeeks(3));
System.out.println("20 months after today will be "+today.plusMonths(20));
System.out.println("10 days before today will be "+today.minusDays(10));
System.out.println("3 weeks before today will be "+today.minusWeeks(3));
System.out.println("20 months before today will be "+today.minusMonths(20));
//Temporal adjusters for adjusting the dates
System.out.println("First date of this month= "+today.with(TemporalAdjusters.firstDayOfMonth()));
LocalDate lastDayOfYear = today.with(TemporalAdjusters.lastDayOfYear());
System.out.println("Last date of this year= "+lastDayOfYear);
LocalDate lastDayOfMonth = today.with(TemporalAdjusters.lastDayOfMonth());
System.out.println("Last date of this month= "+lastDayOfMonth);
//本月中的最后一个星期五
LocalDate lastInMonth = today.with(TemporalAdjusters.lastInMonth(DayOfWeek.FRIDAY));
System.out.println("Last friday of this month= "+lastInMonth);
//获取月份值
int month = today.getMonthValue();
System.out.println("Month of today= "+ month);
Month month1 = today.getMonth();
System.out.println("Month of today= "+month1);
Period period = today.until(lastDayOfYear);
System.out.println("Period Format= "+period);
System.out.println("Months remaining in the year= "+period.getMonths());
结果:
today2019-10-23
Year 2019 is Leap Year? false
Today is before 01/01/2019? false
Current Time=2019-10-23T20:34:02.658
10 days after today will be 2019-11-02
3 weeks after today will be 2019-11-13
20 months after today will be 2021-06-23
10 days before today will be 2019-10-13
3 weeks before today will be 2019-10-02
20 months before today will be 2018-02-23
First date of this month= 2019-10-01
Last date of this year= 2019-12-31
Last date of this month= 2019-10-31
Last friday of this month= 2019-10-25
Month of today= 10
Month of today= OCTOBER
Period Format= P2M8D
Months remaining in the year= 2
其中输出Period
或Duration
使用时toString()
,将根据ISO-8601标准使用特殊格式。一个期间使用的模式是PnYnMnD,其中n定义了期间内存在的年数,月数或天数。这意味着P1Y2M3D定义了1年,2个月和3天的时期
获取上月及当前月
YearMonth yearMonth = YearMonth.now();
System.out.println(yearMonth.toString());
YearMonth lastYearMonth = YearMonth.now().minusMonths(1);
System.out.println(lastYearMonth.toString());
结果:
2020-09
2020-08
LocalDate 获取上月对象
LocalDate date = LocalDate.now().minusMonths(1);
api:
getYear() int 获取当前日期的年份
getMonth() Month 获取当前日期的月份对象
getMonthValue() int 获取当前日期是第几月
getDayOfWeek() DayOfWeek 表示该对象表示的日期是星期几
getDayOfMonth() int 表示该对象表示的日期是这个月第几天
getDayOfYear() int 表示该对象表示的日期是今年第几天
withYear(int year) LocalDate 修改当前对象的年份
withMonth(int month) LocalDate 修改当前对象的月份
withDayOfMonth(int dayOfMonth) LocalDate 修改当前对象在当月的日期
isLeapYear() boolean 是否是闰年
lengthOfMonth() int 这个月有多少天
lengthOfYear() int 该对象表示的年份有多少天(365或者366)
plusYears(long yearsToAdd) LocalDate 当前对象增加指定的年份数
plusMonths(long monthsToAdd) LocalDate 当前对象增加指定的月份数
plusWeeks(long weeksToAdd) LocalDate 当前对象增加指定的周数
plusDays(long daysToAdd) LocalDate 当前对象增加指定的天数
minusYears(long yearsToSubtract) LocalDate 当前对象减去指定的年数
minusMonths(long monthsToSubtract) LocalDate 当前对象减去注定的月数
minusWeeks(long weeksToSubtract) LocalDate 当前对象减去指定的周数
minusDays(long daysToSubtract) LocalDate 当前对象减去指定的天数
compareTo(ChronoLocalDate other) int 比较当前对象和other对象在时间上的大小,返回值如果为正,则当前对象时间较晚,
isBefore(ChronoLocalDate other) boolean 比较当前对象日期是否在other对象日期之前
isAfter(ChronoLocalDate other) boolean 比较当前对象日期是否在other对象日期之后
isEqual(ChronoLocalDate other) boolean 比较两个日期对象是否相等