概念:
UTC
协调世界时,又称世界统一时间、世界标准时间、国际协调时间。由于英文(CUT)和法文(TUC)的缩写不同,作为妥协,简称UTC。代表了时间差。
Epoch
时期; 纪元;世;新时代;指的是一个特定的时间:1970-01-01 00:00:00 UTC。代表了一个世界范围内绝对的时间点。是个timezone无关的点。
Z的理解
参考java.time.format.DateTimeFormatter
Offset X and x: This formats the offset based on the number of pattern letters. One letter outputs just the hour, such as ‘+01’, unless the minute is non-zero in which case the minute is also output, such as ‘+0130’. Two letters outputs the hour and minute, without a colon, such as ‘+0130’. Three letters outputs the hour and minute, with a colon, such as ‘+01:30’. Four letters outputs the hour and minute and optional second, without a colon, such as ‘+013015’. Five letters outputs the hour and minute and optional second, with a colon, such as ‘+01:30:15’. Six or more letters throws IllegalArgumentException. Pattern letter ‘X’ (upper case) will output ‘Z’ when the offset to be output would be zero, whereas pattern letter ‘x’ (lower case) will output ‘+00’, ‘+0000’, or ‘+00:00’.
偏移量: ‘Z’ ‘+00’, ‘+0000’, or ‘+00:00’ 本质都是等价的。
// java.time.format.DateTimeFormatterBuilder.OffsetIdPrinterParser
static final OffsetIdPrinterParser INSTANCE_ID_Z = new OffsetIdPrinterParser("+HH:MM:ss", "Z");
static final OffsetIdPrinterParser INSTANCE_ID_ZERO = new OffsetIdPrinterParser("+HH:MM:ss", "0");
OffsetIdPrinterParser(String pattern, String noOffsetText);
// 翻译得 Z 代表的是no offset 和 0是等价的。
java.util.Date
介绍
Although the Date class is intended to reflect coordinated universal time (UTC), it may not do so exactly, depending on the host environment of the Java Virtual Machine.
date是以UTC时间 为基准的。Date是timezone无关的。
Date zDate = new Date(0L);
System.out.println(zDate)
// Thu Jan 01 08:00:00 CST 1970
// 可以看到0L时间就是UTC的基准时间
在date类中有属性fastTime代表现有时间至UTC基准的毫秒值。从构造方法可再次确认。
private transient long fastTime;
public Date(long date) {
fastTime = date;
}
Date的toString方法BaseCalendar.Date date = normalize();
结果得到再次确认Thu Jan 01 08:00:00 CST 1970
返回的是当前时间并带有时区表示CST。
java.sql.Timestamp
介绍
A thin wrapper around java.util.Date
This type is a composite of a java.util.Date and a separate nanoseconds value. Only integral seconds are stored in the java.util.Date component. The fractional seconds - the nanos - are separate.
可以看出timestamp就是一个缝合怪。将用Date存储UTC 的秒值,在属性private int nanos;
存储秒的小数单位。timestamp也是timezone无关的。
// public static Timestamp valueOf(String s) ; 此方法流程
—> 校验
—> 数值转换(不含时区转化)
—> 调用super(Date)的方法并将年月日时分秒传入。
—> Date 方法中含有时区转换工具。将提供的 数字转化为UTC时区 cal.newCalendarDate(TimeZone.getDefaultRef());
其toString方法是调用父类 的Date方法。在Date的private final BaseCalendar.Date normalize()
方法中会执行时区转换。返回当前时区的 年月日时分秒 值。
方法:public LocalDateTime toLocalDateTime()
会调用父类Date的各种方法分别获取年月日时分秒信息。本质也是执行的Date的normalize()
进行时区转换。
java.time.LocalDateTime
介绍
A date-time without a time-zone in the ISO-8601 calendar system, such as 2007-12-03T10:15:30.
This class does not store or represent a time-zone. Instead, it is a description of the date, as used for birthdays, combined with the local time as seen on a wall clock. It cannot represent an instant on the time-line without additional information such as an offset or time-zone.
可以看出LocalDateTime是不带有时区的。其在不同的时区语义中代表不同的UTC时间。同一localDateTime在不同时区信息下,将转化出不同的 date,和timestamp时间。
其内部通过private final LocalDate date;
和 private final LocalTime time;
两部分存储日期和时间。
// 表显时间:2023-07-20 10:09:22
System.out.println(LocalDateTime.now(ZoneOffset.UTC) +" --- " +LocalDateTime.now());
// 2023-07-20T02:08:26.484 --- 2023-07-20T10:08:26.491
以上案例可以得出。将ZoneId传递给LocalDateTime的时候,会按照zoneId取解析时间(now 或者string类型)并转化为当前时区的时间。
方法:public static LocalDateTime ofInstant(Instant instant, ZoneId zone)
LocalDateTime instantLdt = LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.of("+08:00"));
System.out.println(instantLdt);
// 1970-01-01T08:00 提供的时区参数代表目标LocalDateTime所在时区。
方法:public static LocalDateTime ofEpochSecond(long epochSecond, int nanoOfSecond, ZoneOffset offset)
LocalDateTime epochSecondLdt = LocalDateTime.ofEpochSecond(0L, 0, ZoneOffset.of("+08:00"));
System.out.println(epochSecondLdt);
// 1970-01-01T08:00 提供的时区参数代表目标LocalDateTime所在时区。
方法:public static LocalDateTime parse(CharSequence text)
内部调用了DateTimeFormatter.ISO_LOCAL_DATE_TIME)
默认使用当前时区转化字符串,如: "2007-12-03T10:15:30"
。
LocalDateTime ldt0 = LocalDateTime.ofEpochSecond(0L, 0, ZoneOffset.of("+08:00"));
System.out.println(ldt0); // 1970-01-01T08:00
// Converts this date-time to the number of seconds from the epoch of 1970-01-01T00:00:00Z.
long seconds = ldt0.toEpochSecond(ZoneOffset.of("+08:00")); // to UTC 1970-01-01T00:00:00Z ,ZoneOffset是当前localDateTime所在的时区。
System.out.println(seconds); // 0
Instant instant = Instant.ofEpochSecond(seconds); // 调用了ZoneOffset.getTotalSeconds()进行转化
LocalDateTime ldt1 = LocalDateTime.ofInstant(instant, ZoneOffset.of("+08:00")); // ZoneOffset是当前localDateTime所在的时区。
System.out.println(ldt1); // 1970-01-01T08:00
LocalDateTime ldt2 = LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.of("+08:00")); // ZoneOffset是当前localDateTime所在的时区。
System.out.println(ldt2); // 1970-01-01T08//
int totalSecondsP8 = ZoneOffset.of("+08:00").getTotalSeconds();
int totalSecondsN8 = ZoneOffset.of("-08:00").getTotalSeconds();
System.out.println(totalSecondsP8 + " --- " + totalSecondsN8); // 28800 --- -28800
toString就是调用了LocalDate和LocalTime的toString方法。而这两个类就是将内部存储的年月日时分秒拼接成字符串。
java.time.Instant
介绍
An instantaneous point on the time-line.
The range of an instant requires the storage of a number larger than a long. To achieve this, the class stores a long representing epoch-seconds and an int representing nanosecond-of-second。
The epoch-seconds are measured from the standard Java epoch of 1970-01-01T00:00:00Z where instants after the epoch have positive values, and earlier instants have negative values.
本质是epoch的时间线上的一个点。所以是timezone无关的。
EPOCH 时间是全局唯一的,代表了。
public static final Instant EPOCH = new Instant(0, 0);
public static Instant ofEpochMilli(long epochMilli)
之前我们确认了 Z
代表 UTC的0偏移。所以此处的 standard Java epoch of 1970-01-01T00:00:00Z
就是一个世界的绝对时间点。是和timezone无关的。
org.apache.flink.table.data.TimestampData
介绍
An internal data structure representing data of TimestampType and LocalZonedTimestampType.
This data structure is immutable and consists of a milliseconds and nanos-of-millisecond since 1970-01-01 00:00:00. It might be stored in a compact representation (as a long value) if values are small enough.
TimestampData可以代表timestamp without timzone 和 timestamp with local time zone.
内部存储结构:
// this field holds the integral second and the milli-of-second
private final long millisecond;
// this field holds the nano-of-millisecond
private final int nanoOfMillisecond;
可以看出其实timezone相关的。仅仅是一种存储数的结构。其millisecond 并不一定是EPOCH为起点的。
方法:public static TimestampData fromInstant(Instant instant)
/**
* Creates an instance of {@link TimestampData} from an instance of {@link Instant}.
* @param instant an instance of {@link Instant}
*/
public static TimestampData fromInstant(Instant instant) {
long epochSecond = instant.getEpochSecond();
int nanoSecond = instant.getNano();
long millisecond = epochSecond * 1_000 + nanoSecond / 1_000_000;
int nanoOfMillisecond = nanoSecond % 1_000_000;
return new TimestampData(millisecond, nanoOfMillisecond);
}
方法:public Instant toInstant()
这里默认了TimestampData存储的时间是EPOCH基准的。所以,时间转换可能出现错误。比如
存储是fromDateTime 但是取出是toInstant,就将当前timezone的millisecond作为Epoch的millisecond处理了。
public Instant toInstant() {
long epochSecond = millisecond / 1000;
int milliOfSecond = (int) (millisecond % 1000);
if (milliOfSecond < 0) {
--epochSecond;
milliOfSecond += 1000;
}
long nanoAdjustment = milliOfSecond * 1_000_000 + nanoOfMillisecond;
return Instant.ofEpochSecond(epochSecond, nanoAdjustment);
}
方法:public static TimestampData fromLocalDateTime(LocalDateTime dateTime)
此处时间转换并将两个天和纳秒进行存储。
/**
* Creates an instance of {@link TimestampData} from an instance of {@link LocalDateTime}.
* @param dateTime an instance of {@link LocalDateTime}
*/
public static TimestampData fromLocalDateTime(LocalDateTime dateTime) {
long epochDay = dateTime.toLocalDate().toEpochDay();
long nanoOfDay = dateTime.toLocalTime().toNanoOfDay();
long millisecond = epochDay * MILLIS_PER_DAY + nanoOfDay / 1_000_000;
int nanoOfMillisecond = (int) (nanoOfDay % 1_000_000);
return new TimestampData(millisecond, nanoOfMillisecond);
}
方法:public LocalDateTime toLocalDateTime()
简单粗暴,直接计算得到,并没有时区转换。
/** Converts this {@link TimestampData} object to a {@link LocalDateTime}. */
public LocalDateTime toLocalDateTime() {
int date = (int) (millisecond / MILLIS_PER_DAY);
int time = (int) (millisecond % MILLIS_PER_DAY);
if (time < 0) {
--date;
time += MILLIS_PER_DAY;
}
long nanoOfDay = time * 1_000_000L + nanoOfMillisecond;
LocalDate localDate = LocalDate.ofEpochDay(date);
LocalTime localTime = LocalTime.ofNanoOfDay(nanoOfDay);
return LocalDateTime.of(localDate, localTime);
}
方法:
public static TimestampData fromTimestamp(Timestamp timestamp)`
/**
* Creates an instance of {@link TimestampData} from an instance of {@link Timestamp}.
* @param timestamp an instance of {@link Timestamp}
*/
public static TimestampData fromTimestamp(Timestamp timestamp) {
return fromLocalDateTime(timestamp.toLocalDateTime());
}
方法:public Timestamp toTimestamp()
/** Converts this {@link TimestampData} object to a {@link Timestamp}. */
public Timestamp toTimestamp() {
return Timestamp.valueOf(toLocalDateTime());
}
方法:public long getMillisecond()
注意:这里返回的并不一定是epoch 的millisecond。
(1)TimestampData.fromInstant —> getMillisecond()返回 EPOCH的毫秒值。
(2)TimestampData.fromLocalDateTime 或者 fromTimestamp —> getMillisecond()返回 当前时区下距离1970-01-01 00:00:00的毫秒值。
LocalDateTime ldt0 = LocalDateTime.of(1970, 1, 1, 8, 0, 0, 0);
System.out.println(ldt0); // 1970-01-01T08:00
Instant ins0 = ldt0.toInstant(ZoneOffset.of("+08:00"));
long milliIns0 = ins0.toEpochMilli();
System.out.println(milliIns0+" --- "+milliIns0/1000/3600); // 0 --- 0 确认了ldt0就是EPOCH 也就是(UTC) 1970-01-01 00:00:00Z
TimestampData tsd0 = TimestampData.fromLocalDateTime(ldt0);
System.out.println(tsd0); // 1970-01-01T08:00
long milli0 = tsd0.getMillisecond();
System.out.println(milli0+" --- "+milli0/1000/3600); // 28800000 --- 8
TimestampData tsd1 = TimestampData.fromInstant(Instant.EPOCH);
long milli1 = tsd1.getMillisecond();
System.out.println(milli1+" --- "+milli1/1000/3600); // 0 --- 0
TimestampData小结
- 作为 TimestampType 时候使用,基于EPOCH的
fromInstant --> toInstant 没有问题 - 作为 LocalZonedTimestampType 类型的载体
fromLocalDateTime —> toLocalDateTime 没有问题
fromTimestamp —> toTimestamp 没有问题
fromTimestamp —> toLocalDateTime 没有问题
fromLocalDateTime —> toTimestamp 没有问题
toString方法:
@Override
public String toString() {
return toLocalDateTime().toString();
}