LocalDateTime与时间戳

众所周知,如果想把 LocalDateTime 转为时间戳,需要先指定时区,然后才能转为时间戳,例如:

LocalDateTime localDateTime = LocalDateTime.now();  
ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.systemDefault());  
long second = zonedDateTime.toEpochSecond();

但是既然 LocalDateTime(本地时间)已经确定当前时间,为什么不能直接转为时间戳?

因为时间戳指的是自 1970 年 1 月 1 日(00:00:00 UTC/GMT)以来的秒数,所以无论在哪个时区,同一时间获取的都是相同时间戳,可以用于跨时区。但是我们现实生活用到的本地时间是跟时区挂钩的,中国所在的时区是东八区,会比 UTC 时间快 8 个小时。时间戳是从 UTC 时间得来的,所以时间戳与本地时间的相互转换,需要根据时区来转换。

通过查看 LocalDateTime.now() 方法源码,也能看出会先获取系统默认时区,然后再时间戳和时区获得本地时间。

public static LocalDateTime now() {  
    return now(Clock.systemDefaultZone());  //先获取系统默认时区
}

Clock.systemDefaultZone() 方法一直往下走,到达获取系统默认时区的重要方法。首先获取 JVM 参数中的时区信息,如果不存在则获取操作系统的时区信息,否则默认使用 GMT。

//java.util.TimeZone#setDefaultZone
private static synchronized TimeZone setDefaultZone() {  
    TimeZone tz;  
    // get the time zone ID from the system properties  
    Properties props = GetPropertyAction.privilegedGetProperties();  
    String zoneID = props.getProperty("user.timezone");  //通过JVM属性获取
  
    // if the time zone ID is not set (yet), perform the  
    // platform to Java time zone ID mapping.   
    if (zoneID == null || zoneID.isEmpty()) {  
        String javaHome = StaticProperty.javaHome();  
        try {  
            zoneID = getSystemTimeZoneID(javaHome);  //获取操作系统时区
            if (zoneID == null) {  
                zoneID = GMT_ID;  //默认使用GMT
            }  
        } catch (NullPointerException e) {  
            zoneID = GMT_ID;  
        }  
    }  
  
    // Get the time zone for zoneID. But not fall back to  
    // "GMT" here.   
    tz = getTimeZone(zoneID, false);   //从默认的时区配置和java根目录下的/lib/tzdb.dat中根据时区id获取时区信息。
  
    if (tz == null) {  
        // If the given zone ID is unknown in Java, try to  
        // get the GMT-offset-based time zone ID,        
        // a.k.a. custom time zone ID (e.g., "GMT-08:00").        
        String gmtOffsetID = getSystemGMTOffsetID();  
        if (gmtOffsetID != null) {  
            zoneID = gmtOffsetID;  
        }  
        tz = getTimeZone(zoneID, true);  
    }  
    assert tz != null;  
  
    final String id = zoneID;  
    props.setProperty("user.timezone", id);  
  
    defaultTimeZone = tz;  
    return tz;  
}

getSystemTimeZoneID() 是一个 native 方法,根据不同操作系统,获取的方法不同。对于 window 系统,获取的是注册表里的时区信息。

//java.util.TimeZone#getSystemTimeZoneID
private static native String getSystemTimeZoneID(String javaHome);

获取到系统默认时区后,通过获取 1970 年 1 月 1 日午夜至今的秒数和时区偏移量,计算出本地时间的秒数,借此创建 LocalDateTime 实例。

//java.time.LocalDateTime#now(java.time.Clock)
public static LocalDateTime now(Clock clock) {  
    Objects.requireNonNull(clock, "clock");  
    final Instant now = clock.instant();  // called once   //获取1970 年 1 月 1 日午夜至今的秒数
    ZoneOffset offset = clock.getZone().getRules().getOffset(now);  //获取时区偏移量
    return ofEpochSecond(now.getEpochSecond(), now.getNano(), offset);  
}

//java.time.LocalDateTime#ofEpochSecond
public static LocalDateTime ofEpochSecond(long epochSecond, int nanoOfSecond, ZoneOffset offset) {  
    Objects.requireNonNull(offset, "offset");  
    NANO_OF_SECOND.checkValidValue(nanoOfSecond);  
    long localSecond = epochSecond + offset.getTotalSeconds();  // overflow caught later  //将UTC时间下的秒数和时区偏移的秒数相加,获得本地时间的秒数
    long localEpochDay = Math.floorDiv(localSecond, SECONDS_PER_DAY);  //将本地时间的秒数除以每天的秒数,得到天数
    int secsOfDay = Math.floorMod(localSecond, SECONDS_PER_DAY);  //将本地时间的秒数对每天的秒数取余,得到一天内剩下的秒数
    LocalDate date = LocalDate.ofEpochDay(localEpochDay);  
    LocalTime time = LocalTime.ofNanoOfDay(secsOfDay * NANOS_PER_SECOND + nanoOfSecond);  
    return new LocalDateTime(date, time);  //通过LocalDate和LocalTime创建LocalDateTime实例
}

既然本地时间是根据 1970 年 1 月 1 日午夜至今的秒数加上时区偏移的秒数得到的,那么从 LocalDateTime 转换为时间戳也自然需要减去时区偏移的秒数。

//java.time.chrono.ChronoZonedDateTime#toEpochSecond
default long toEpochSecond() {  
    long epochDay = toLocalDate().toEpochDay();  
    long secs = epochDay * 86400 + toLocalTime().toSecondOfDay();  //获取本地时间的秒数
    secs -= getOffset().getTotalSeconds();  //减去时区偏移的秒数
    return secs;  
}

你可能感兴趣的:(源码分析,开发语言,java)