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的一些设计原则是:
now()
方法。在所有这些类中定义了format()和parse()方法,而不是为它们设置单独的类。 所有类都使用工厂模式和策略模式来更好地处理。一旦你在其中一个类中使用了这些方法,那么使用其他类并不难。
Java 8 Date Time API包含以下包。
LocalDate
,LocalTime
,LocalDateTime
,Instant
,Period
,Duration
等所有这些类是不可变的和线程安全的。大多数情况下,这些类足以满足常见要求。AbstractChronology
类来创建我们自己的日历系统。我们已经研究了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类用于处理机器可读的时间格式,它将日期时间存储在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
Period 和 Duration。两个类看表示时间量或两个日期之间的差,两者之间的差异为:Period基于日期值,而Duration基于时间值。
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 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
有的时候,你需要进行一些更加复杂的操作,比如,将日期调整到下个周日、下个工作日,或者是本月的最后一天。这时,你可以使用重载版本的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天的时期