理解 Java8 的时间API(二)时间

理解 Java8 的时间API:java.time

上一篇介绍了 Java8 里新的时区API。这一篇介绍新的时间API:LocalDateTimeLocalDateLocalTime类。

三、Java8中的时间

最常用的应该是java.time.LocalDateTimejava.time.LocalDatejava.time.LocalTimejava.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.Periodjava.time.Duration

简而言之:Period最细粒度是“天”,即可以表示几年、几个月、一个月内的几天的时间范围。Duration的最大粒度是“天”,即可以表示几天、几小时、几分钟、几秒等范围。

https://zhuanlan.zhihu.com/p/144372010

四、时间格式的相互转化

最常用的时间格式大概是这两种: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://www.baeldung.com/java-localdatetime-zoneddatetime

另外:

https://dev.mysql.com/doc/refman/8.0/en/datetime.html
MySQL里时间相关的类型:

  • DATETIMEDATETIME:也是不包含时区信息的,和LocalDateTime,LocalDate,LocalTime可以一一对应;
  • TIMESTAMP:和 Instant 可以对应。

但是mysql关于时间的存储也有一些坑:
https://ifeve.com/mysql驱动中关于时间的坑/

posted @ 2023-05-02 15:16  Recycer  阅读(92)  评论(0编辑  收藏  举报