TOC
KINA

KINA-0

Start having fun with KINA right now!

java.time日期/时间API包简介

Java 8日期/时间API的实现目标是克服旧的日期时间实现中所有的缺陷,新增的java.time包中定义的类表示了日期-时间概念的规则,这些都基于ISO日历系统,并遵循Gregorian规则。最重要的一点是值不可变,且线程安全。

1 JSR310介绍

JSR 310实际上有两个日期概念:

  1. Instant,大致对应于 java.util.Date 类,因为它代表一个确定的时间点,即相对于标准Java纪元(1970 年 1 月 1 日)的偏移量;但与java.util.Date类不同,其精确到了纳秒级别。
  2. LocalDate和LocalTime等,对应于人类自身的观念,代表一般的时区概念,要么是日期(不含时间),要么是时间(不含日期),类似于java.sql的表示方式。此外还有一个MonthDay,可以存储某人的生日(不含年份)。每个类都在内部存储正确的数据而不是像java.util.Date那样利用午夜12点来区分日期,利用1970-01-01来表示时间。

Java 8实现了JSR310的全部内容,新增的java.time包中定义的类表示了日期-时间概念的规则,包括Instant、Duration、Date、Time、TimeZone和Period。这些都基于ISO日历系统,并遵循Gregorian规则。最重要的一点是值不可变,且线程安全。java.time包下一些主要类的值的格式如下图所示

格式
LocalDate 2010-12-03
LocalTime 11:05:30
LocalDateTime 2010-12-03T11:05:30
OffsetTime 11:05:30+01:00
OffsetDateTime 2010-12-03T11:05:30+01:00
ZonedDateTime 2010-12-03T11:05:30+01:00 Europe/Paris
Year 2010
YearMonth 2010-12
MonthDay -12-03
Instant 2576458258.266 seconds after 1970-01-01

2 Java 8日期/时间特性

Java 8日期/时间API的实现目标是克服旧的日期时间实现中所有的缺陷,新的日期/时间API的一些设计原则如下:

  • 不变性:新的日期/时间 API 中,所有的类都是不可变的,这对多线程环境有好处。
  • 关注点分离:新的 API 将人可读的日期时间和机器时间(Unix Timestamp)明确分离,它为日期(Date)、时间 (Time)、日期时间(DateTime)、时间戳(unix timestamp)以及时区定义了不同的类。
  • 清晰:在所有的类中,方法都被明确定义用以完成相同的行为。例如,要拿到当前实例可以使用now()方 法,在所有的类中都定义了format()parse()方法,而不是像以前那样专门有一个独立的类。为了更好的处理问题,所有的类都使用了工厂模式和策略模式,一旦你使用了其中某个类的方法,与其他类协同工作并不困难。
  • 实用操作:所有新的日期/时间 API 类都实现了一系列方法用以完成通用的任务,如:加、减、格式化、解析、从日期/时间中提取单独部分等。
  • 可扩展性:新的日期/时间API默认工作在ISO-8601日历系统上,但也可以将其应用在非ISO的日历上。

Java 8常用的日期/时间API包如下所示:

  1. java.time包:新的Java日期/时间API的基础包,所有的主要基础类都是这个包的一部分,如:LocalDate、LocalTime、LocalDateTime、Instant、Period、Duration等。所有这些类都是不可变的和线程安全的,在绝大多数情况下,这些类能够有效地处理一些公共的需求。
  2. java.time.chrono包:为非ISO的日历系统定义了一些泛化的API,可以扩展AbstractChronology类来创建自己的日历系统。
  3. java.time.format包:这个包包含能够格式化和解析日期时间对象的类,在绝大多数情况下,我们不应该直接使 用它们,因为 java.time 包中相应的类已经提供了格式化和解析的方法。
  4. java.time.temporal包:这个包包含一些时态对象,可以用其找出关于日期/时间对象的某个特定日期或时间, 例如,找到某月的第一天或最后一天。这些方法都具有“withXXX”的格 式,可以非常容易地认出。
  5. java.time.zone包:这个包包含支持不同时区以及相关规则的类。

3 日期/时间常用API

java.time包下的方法概览

方法名 说明
of 静态工厂方法
parse 静态工厂方法,关注于解析
get 获取某事物的值
is 检查某事物是否为true
with 不可变的setter等价物
plus 加一些量到某个对象
minus 从某个对象减去一些量
to 转换到另一个类型
at 把这个对象与另一个对象组合起来

如果提供无效的参数去创建日期/时间, 系统会抛出异常java.time.DateTimeException。

支持通过传入ZoneId得到指定时区的日期/时间数据,可以从其Javadoc中得到支持的Zoneid列表。

3.1 java.time.LocalDate

LocalDate是一个不可变的类,它表示默认格式yyyy-MM-dd的日期,可以使用now()方法得到当前时间,也可以提供输入年份、月份和日期的输入参数来创建一个LocalDate实例。该类为now()方法提供了重载方法,可以传入ZoneId来获得指定时区的日期。该类提供与java.sql.Date相同的功能。

// Current date
LocalDate today = LocalDate.now();
System.out.println("Current date: " + today);

// Creating LocalDate by providing input arguments
LocalDate firstDay_2024 = LocalDate.of(2024, Month.JANUARY, 1);
System.out.println("Specific date: " + firstDay_2024);

// Current date in "Asia/Shanghai", you can get it from ZoneId javadoc
LocalDate todayShanghai = LocalDate.now(ZoneId.of("Asia/Shanghai"));
System.out.println("Current date in CTT: " + todayShanghai);

// Getting date from the base date i.e. 01/01/1970
LocalDate dateFromBase = LocalDate.ofEpochDay(365);
System.out.println("365th day from the base date: " + dateFromBase);

LocalDate hundredDay_2024 = LocalDate.ofYearDay(2014, 100);
System.out.println("100th day of 2024: " + hundredDay_2024);

3.2 java.time.LocalTime

LocalTime是一个不可变的类,它的实例代表一个符合人类可读格式的时间, 默认格式为hh:mm:ss.zzz。 像LocalDate一样,该类也提供了时区支持,同时也可以传入小时、分钟和秒等输入参数创建实例。

// Current time
LocalTime time = LocalTime.now();
System.out.println("Current Time: " + time);

// Creating LocalTime by providing input arguments
LocalTime specificTime = LocalTime.of(12, 20, 25, 40);
System.out.println("Specific time of day: " + specificTime);

// Current date in "Asia/Shanghai", you can get it from ZoneId javadoc
LocalTime timeShanghai = LocalTime.now(ZoneId.of("Asia/Shanghai"));
System.out.println("Current time in CTT: " + timeShanghai);

// Getting date from the base date i.e. 01/01/1970
LocalTime specificSecondTime = LocalTime.ofSecondOfDay(10000);
System.out.println("10000th second time: " + specificSecondTime);

3.3 java.time.LocalDateTime

LocalDateTime是一个不可变的日期-时间对象,它表示一组日期-时间,默认格式为yyyy-MM-dd-HH-mmss.zzz。它提供了一个工厂方法,接收LocalDate和LocalTime输入参数,创建LocalDateTime实例。

// Current date
LocalDateTime today = LocalDateTime.now();
System.out.println("Current DateTime: " + today);

// Creating LocalDateTime by using LocalDate and LocalTime
LocalDateTime localDateTime = LocalDateTime.of(LocalDate.now(), LocalTime.now());
System.out.println("Current DateTime: " + localDateTime);

// Creating LocalDateTime by providing input arguments
LocalDateTime specificDate = LocalDateTime.of(2024, Month.APRIL, 23, 10, 10, 30);
System.out.println("Specific date: " + specificDate);

// Current date in "Asia/Shanghai", you can get it from ZoneId javadoc
LocalDateTime todayShanghai = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
System.out.println("Current date in CTT: " + todayShanghai);

// 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);

3.4 java.time.Instant

Instant类是用在机器可读的时间格式上的,它以Unix时间戳的形式存储日期时间。

// Current timestamp
Instant timestamp = Instant.now();
System.out.println("Current timestamp: " + timestamp);

// Instant from timestamp
Instant specificTime = Instant.ofEpochMilli(timestamp.toEpochMilli());
System.out.println("Specific time: " + specificTime);

// Duration example
Duration thirtyDay = Duration.ofDays(30);
System.out.println(thirtyDay);

3.5 日期API工具

大多数日期/时间API类都实现了一系列工具方法,如:加/减天数、周数、月份数等。还有其他的工具方法能够使用TemporalAdjuster调整日期,并计算两个日期间的周期。

LocalDate today = LocalDate.now();

// 判断所在年是否是闰年
System.out.println(today.isLeapYear());

// 比较是否在指定日期之前/之后
System.out.println("before 15/8/2024? " + today.isBefore(LocalDate.of(2024, 8, 15)));

// 由LocalDate创建LocalDateTime
System.out.println("Current Time: " + today.atTime(LocalTime.now()));

// 加减运算
System.out.println("10 days after today: " + today.plusDays(10));
System.out.println("3 weeks after today: " + today.plusWeeks(3));
System.out.println("20 months after today: " + today.plusMonths(20));
System.out.println("10 days before today: " + today.minusDays(10));
System.out.println("3 weeks before today: " + today.minusWeeks(3));
System.out.println("20 months before today: " + today.minusMonths(20));

// TemporalAdjusters
System.out.println("First day of this month: " + today.with(TemporalAdjusters.firstDayOfMonth()));
System.out.println("Last day pf this year: " + today.with(TemporalAdjusters.lastDayOfYear()));

// Period
Period period = today.until(today.with(TemporalAdjusters.lastDayOfYear()));
System.out.println("Period Format: " + period);
System.out.println("Months remaining in the year: " + period.getMonths());

3.6 解析和格式化

可用java.time.format.DateTimeFormatter来格式化时间日期。将一个日期格式转换为不同的格式,之后再解析一个字符串,得到日期时间对象。

LocalDate date = LocalDate.now();
// Default format
System.out.println("Default format of LocalDate: " + date);
// Specific format
System.out.println(date.format(DateTimeFormatter.ofPattern("d::MMM::uuuu")));
System.out.println(date.format(DateTimeFormatter.BASIC_ISO_DATE));

LocalDateTime dateTime = LocalDateTime.now();
// Default format
System.out.println("Default format of LocalDateTime: " + dateTime);
// Specific format
System.out.println(dateTime.format(DateTimeFormatter.ofPattern("d::MMM::uuuu HH::mm::ss")));
System.out.println(dateTime.format(DateTimeFormatter.BASIC_ISO_DATE));

Instant timestamp = Instant.now();
// Default format
System.out.println("Default format of Instant: " + timestamp);

//Parse example
DateTimeFormatter newFormatter = DateTimeFormatter.ofPattern("yyyy---MM---dd");
System.out.println(date.format(newFormatter));

3.7 旧版日期/时间类的支持

旧的日期/时间类已经在几乎所有的应用程序中使用,因此做到向下兼容是必须的。

// Date to Instant
Instant timestamp = new Date().toInstant();
// Instant to LocalDateTime or other similar classes
LocalDateTime date = LocalDateTime.ofInstant(timestamp, ZoneId.of(ZoneId.SHORT_IDS.get("CTT")));
System.out.println("Date: " + date);

// Calendar to Instant
Instant time = Calendar.getInstance().toInstant();
System.out.println(time);
// TimeZone to ZoneId
ZoneId defaultZone = TimeZone.getDefault().toZoneId();
System.out.println(defaultZone);

// ZonedDateTime from specific Calendar
ZonedDateTime gregorianCalendarDateTime = new GregorianCalendar().toZonedDateTime();
System.out.println(gregorianCalendarDateTime);

// Date API to Legacy classes
Date dt = Date.from(Instant.now());
System.out.println(dt);

TimeZone tz = TimeZone.getTimeZone(defaultZone);
System.out.println(tz);

GregorianCalendar gc = GregorianCalendar.from(gregorianCalendarDateTime);
System.out.println(gc);

4 与旧版API对比

新旧API的对比

Java.time ISO Calendar Java.util Calendar
Instant Date
LocalDate, LocalTime, LocalDateTime Calendar
ZonedDateTime Calendar
OffsetDateTime, OffsetTime Calendar
ZoneId, ZoneOffset, ZoneRules TimeZone
Week Starts on Monday(1 ... 7)
enum MONDAY, TUESDAY, ..., SUNDAY
Week Starts on Sunday(1 ... 7)
int values SUNDAY, MONDAY, ... , SATURDAY
12 Months (1 ... 12)
enum JANUARY, FEBRUARY, ... , DECEMBER
12 Months (0 ... 11)
int values JANUARY, FEBRUARY, ... , DECEMBER
流畅的API 不流畅的API
实例不可变 实例可变
线程安全 非线程安全

4.1 获取年月日、时分秒

Calendar cal = Calendar.getInstance();
System.out.println(cal.get(Calendar.YEAR));
System.out.println(cal.get(Calendar.MONTH));    // 0 ~ 11
System.out.println(cal.get(Calendar.DATE));
System.out.println(cal.get(Calendar.HOUR_OF_DAY));
System.out.println(cal.get(Calendar.MINUTE));
System.out.println(cal.get(Calendar.SECOND));

/* Java 8 */
LocalDateTime dt = LocalDateTime.now();
System.out.println(dt.getYear());
System.out.println(dt.getMonthValue());     // 1 ~ 12
System.out.println(dt.getDayOfMonth());
System.out.println(dt.getHour());
System.out.println(dt.getMinute());
System.out.println(dt.getSecond());

4.2 获取从1970年1月1日0:0:0到现在的毫秒数

// 法1
Calendar.getInstance().getTimeInMillis();
// 法2
System.currentTimeMillis();

/* Java 8 */
Clock.systemDefaultZone().millis();

4.3 获取本月第一天/最后一天

// 本月第一天
Calendar cal1 = Calendar.getInstance();
cal1.add(Calendar.MONTH, 0);
cal1.set(Calendar.DAY_OF_MONTH, 1);  // 设置为1号,即本月第一天
String first = cal1.getTime().toString();
// 本月最后一天
Calendar cal2 = Calendar.getInstance();
cal2.set(Calendar.DAY_OF_MONTH, cal2.getActualMaximum(Calendar.DAY_OF_MONTH));
String last = cal2.getTime().toString();

System.out.println("first: " + first);
System.out.println("last: " + last);

/* Java 8 */
LocalDate today = LocalDate.now();
// 本月第一天
LocalDate firstDay = LocalDate.of(today.getYear(), today.getMonth(), 1);
// 本月最后一天
LocalDate lastDay = today.with(TemporalAdjusters.lastDayOfMonth());

System.out.println("本月第一天:" + firstDay);
System.out.println("本月最后一天:" + lastDay);

4.4 打印昨天的当前时刻

Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE, -1);
System.out.println(cal.getTime());

/* Java 8 */
LocalDateTime today = LocalDateTime.now();
LocalDateTime yesterday = today.minusDays(1);
System.out.println(yesterday);

4.5 格式化日期

Java.text.DataFormat的子类(如 SimpleDateFormat 类)中的format(Date)方法可将日期格式化。

Java 8中用java.time.format.DateTimeFormatter来格式化时间日期。

SimpleDateFormat oldFormatter = new SimpleDateFormat("yyyy/MM/dd");
Date date1 = new Date();
System.out.println(oldFormatter.format(date1));

/* Java 8 */
DateTimeFormatter newFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
LocalDate date2 = LocalDate.now();
System.out.println(date2.format(newFormatter));

发表评论