理解 Java8 的时间API(二)时间
理解 Java8 的时间API:java.time
上一篇介绍了 Java8 里新的时区API。这一篇介绍新的时间API:LocalDateTime
,LocalDate
,LocalTime
类。
三、Java8中的时间
最常用的应该是java.time.LocalDateTime
,java.time.LocalDate
,java.time.LocalTime
,java.time.Instant
这几个类。
3.1 LocalDateTime
其实这个类很简单,这里我贴一下它的工厂构造方法 LocalDateTime.of()
的一个重载:
public static LocalDateTime of(int year, int month, int dayOfMonth, int hour, int minute, int second) {
LocalDate date = LocalDate.of(year, month, dayOfMonth);
LocalTime time = LocalTime.of(hour, minute, second);
return new LocalDateTime(date, time);
}
显然,一个LocalDateTime实例由“年+月+日+时+分+秒”这几个部分组成(实际上还包含纳秒,这里先忽略)。也就是形如“2023-05-02 11:59:00”这样的信息。并且它不包含时区信息,这也是名称里“Local”的含义。
LocalDateTime还有两个最为常用的工厂方法,第一个是使用系统默认时区构造一个实例,第二个是获取指定时区的当前日期时间。因此同一个“当前时间”,在不同时区,用LocalDateTime.now();
方法构造出来的日期时间是不同的。
public static LocalDateTime now();
public static LocalDateTime now(ZoneId zone);
3.2 LocalDate和LocalTime
很好理解:LocalDateTime
= LocalDate
+ LocalTime
。
LocalDate
其实就是LocalDateTime
中的日期部分,也就是“2023-05-02”这部分信息。
public static LocalDate of(int year, int month, int dayOfMonth);
public static LocalDate now(); // 获取系统时区的当前日期
public static LocalDate now(ZoneId zone); // 获取指定时区的当前日期
LocalTime
其实就是LocalDateTime
中的时间部分,也就是“11:59:00”这部分信息。
public static LocalTime of(int hour, int minute, int second);
public static LocalTime now(); // 获取系统时区的当前时间
public static LocalTime now(ZoneId zone); // 获取指定时区的当前时间
3.3 Instant
这个也可以字面理解:时刻、瞬间等含义。可以类比时间戳
public static Instant ofEpochSecond(long epochSecond); // 这里的epochSecond就是秒级时间戳
public static Instant now();
public long toEpochMilli(); // 实例方法,转为毫秒级时间戳
注:
从字面上看,替代java.util.Date
类的似乎是java.time.LocalDateTime
。其实不然, Date类内部的关键字段是时间戳,从这一点上看,替代Date类的其实是java.time.Instant
。
3.4 Period和Duration
除了上面这几个表示一个具体时间的类以外,还有几个表示时间段的类:java.time.Period
和java.time.Duration
。
简而言之:Period最细粒度是“天”,即可以表示几年、几个月、一个月内的几天的时间范围。Duration的最大粒度是“天”,即可以表示几天、几小时、几分钟、几秒等范围。
四、时间格式的相互转化
最常用的时间格式大概是这两种:1. 时间戳;2. 日期时间字符串(不包含时区)。这两种格式之间的转换莫过于下图里的逻辑:
值得注意,新的时间API推出了线程安全的java.time.format.DateTimeFormatter
替代以前的线程不安全的java.text.SimpleDateFormat
。上图中在Java中的转换代码示例如下:
public static void main(String[] args) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
ZoneId zoneId = ZoneId.of("America/Los_Angeles");
// 1. LocalDateTime 转成其他格式
LocalDateTime dateTime = LocalDateTime.of(2023, 5, 2, 11, 59, 0);
// 1.1 LocalDateTime => 日期时间字符串
String dateTimeStr1 = formatter.format(dateTime); // 2023-05-02 11:59:00
String dateTimeStr2 = dateTime.format(formatter); // 2023-05-02 11:59:00
// 这里是由于夏令时的存在,相同的zoneId,不同时间的offset可能会不一样
ZoneOffset offset = zoneId.getRules().getOffset(dateTime);
// 1.2 LocalDateTime => 时间戳
long epochSecond = dateTime.toEpochSecond(offset);
// 1.3 LocalDateTime => Instant
Instant instant = dateTime.toInstant(offset);
// 1.4 LocalDateTime => ZonedDateTime => Instant
ZonedDateTime zonedDateTime = dateTime.atZone(zoneId);
Instant instant1 = zonedDateTime.toInstant();
// 2. Instant 转成其他格式
Instant now = Instant.now();
// 2.1 Instant => LocalDateTime
LocalDateTime dateTime1 = LocalDateTime.ofInstant(instant, zoneId);
// 2.2 Instant => 时间戳
long epochSecond1 = now.getEpochSecond();
long epochMilli = now.toEpochMilli();
// 2.3 Instant => UTC时区的日期时间
String s = now.toString(); // 2023-05-02T05:43:09.605Z
// 3. 时间戳 转成其他格式
long timestamp = System.currentTimeMillis();
// 3.1 时间戳 => Instant
Instant instant2 = Instant.ofEpochMilli(timestamp);
Instant instant3 = Instant.ofEpochSecond(timestamp / 1000);
// 3.2 时间戳 => Instant => LocalDateTime
LocalDateTime dateTime2 = LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), zoneId);
// 4. 日期时间字符串 转成其他格式
String dateTimeStr = "2023-05-02 11:59:00";
// 4.1 日期时间字符串 => LocalDateTime
LocalDateTime parse = LocalDateTime.parse(dateTimeStr, formatter);
}
上面的转换代码组合一下,一个时间工具类就出来了:
import lombok.experimental.UtilityClass;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
@UtilityClass
public class DateTimeUtils {
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final ZoneId zoneId = ZoneId.of("America/Los_Angeles");
/**
* 如果是北京时间,下面要用到zoneOffset的地方直接用这个就行,不需要用zoneId来转换
*/
private static final ZoneOffset beijingOffset = ZoneOffset.of("+8");
/**
* @param dateTimeStr 日期时间字符串
* @return 毫秒时间戳
*/
public static long dateTimeStr2Timestamp(String dateTimeStr) {
LocalDateTime parse = LocalDateTime.parse(dateTimeStr, formatter);
return parse.toEpochSecond(getOffset(parse));
}
/**
* @param timestamp 毫秒时间戳
* @return 日期时间字符串
*/
public static String timestamp2DateTimeStr(long timestamp) {
Instant instant = Instant.ofEpochMilli(timestamp);
LocalDateTime dateTime = LocalDateTime.ofInstant(instant, zoneId);
return dateTime.format(formatter);
}
/**
* 毫秒时间戳 => Instant
*/
public static Instant timestamp2Instant(long timestamp) {
return Instant.ofEpochMilli(timestamp);
}
/**
* Instant => 毫秒时间戳
*/
public static long instant2Timestamp(Instant instant) {
return instant.toEpochMilli();
}
/**
* LocalDateTime => 秒级时间戳
*/
public static long localDateTime2Second(LocalDateTime localDateTime) {
ZoneOffset zoneOffset = getOffset(localDateTime);
return localDateTime.toEpochSecond(zoneOffset);
}
/**
* 日期时间字符串 => LocalDateTime
*/
public static LocalDateTime dateTimeStr2LocalDateTime(String dateTimeStr) {
return LocalDateTime.parse(dateTimeStr, formatter);
}
/**
* LocalDateTime => 日期时间字符串
*/
public static String localDateTime2DateTimeStr(LocalDateTime localDateTime) {
return localDateTime.format(formatter);
}
/**
* Instant => LocalDateTime
*/
private static LocalDateTime instant2LocalDateTime(Instant instant) {
return LocalDateTime.ofInstant(instant, zoneId);
}
/**
* LocalDateTime => Instant
*/
private static Instant localDateTime2Instant(LocalDateTime localDateTime) {
ZoneOffset zoneOffset = getOffset(localDateTime);
return localDateTime.toInstant(zoneOffset);
}
private static ZoneOffset getOffset(LocalDateTime localDateTime) {
return zoneId.getRules().getOffset(localDateTime);
}
private static ZoneOffset getOffset(Instant instant) {
return zoneId.getRules().getOffset(instant);
}
}
另外:
https://dev.mysql.com/doc/refman/8.0/en/datetime.html
MySQL里时间相关的类型:
DATETIME
、DATE
、TIME
:也是不包含时区信息的,和LocalDateTime,LocalDate,LocalTime可以一一对应;TIMESTAMP
:和 Instant 可以对应。但是mysql关于时间的存储也有一些坑:
https://ifeve.com/mysql驱动中关于时间的坑/