背景
在做国际化相关的时间场景时,经常会遇到时区转换带来的难题。比如说,时区时间的对齐,这种场景会遇到韩国用户要求一个定时任务在韩国时间早上执行,美国客户要求一个定时任务在UTC时间执行,处理稍微不善,就会出现和目标时间点相差8个小时的情况。再比如说,时间格式上的要求,这种情况多出现于接口对接,例如有些接口参数会以时间戳的形式,有些则会以字符串,"2022/11/13 14:47:14",这种情况一般会指定这个时间所对应的时区,毕竟每个时区都会存在一个14:47:14。处理时间
将上面的问题归纳总结,其实可以归为两类问题,一类是时间怎么转换,另外一类是时间怎么做处理。 对于Java内的时间,我常用的类有两个 Instant类 和 Calendar类,Calendar类是java.util包下的类,而Instant类则在java.time包下,java.time是在Java1.8版本内新加入,专门处理时间的类。时间转换
首先是将字符串转换为时间戳的方法,字符串可能会有不同的标准,你可以选择 DateTimeFormatter类 中自带的标准,可以直接用 DateTimeFormatter.ofPattern 方法进行自定义。使用.atZone()方法可以指定时区,不同的时区会转换成不同的时间戳,代表不同的实际时间。
public static void main(String[] argv) {
String strDate = "2022/11/13 14:47:14";
long time = LocalDateTime.parse(strDate,
DateTimeFormatter.ofPattern("yyyy/MM/d HH:mm:ss"))
.atZone(ZoneId.of("UTC")).toInstant().toEpochMilli();
System.out.println(time);
// 1668350834000
// 北京时间(东八区) 2022-11-13 22:47:14
// 可以简单理解为 太阳从东方升起 所以在时间上 东七区 会比 东八区晚一个小时
}
LocalDateTime类,听上去是是根据服务器的Local时区做的一个转换,但一旦你点到这个类里面去,他的第一行注释就说明了他是一个和时区无关的类。
A date-time without a time-zone in the ISO-8601 calendar system, such as 2007-12-03T10:15:30.
如果将时间戳转为字符串,则可以使用Instant.ofEpochMilli()方法,后再用.atZone()指定时区。
public static void main(String[] argv) {
String strDate = Instant.ofEpochMilli(1668350834000L)
.atZone(ZoneId.of("UTC"))
.format(DateTimeFormatter.ofPattern("yyyy/MM/d HH:mm:ss"));
System.out.println(strDate);
// UTC 时间的 2022/11/13 14:47:14
}
时间处理
主要涉及到的时间处理会有年月日的加减法,比如说,一个月付会员这个月的18号付了钱,那么怎么计算才能计算到下一个月。如果硬要用秒级别的加减法去加上43200000毫秒也不是不行。但涉及到逻辑分支就太多了,比如说这个月是大月(加31天的毫秒)还是小月(加30天的毫秒),今年是平年(二月份加28天的毫秒)还是闰年(加29天的毫秒)。 public static void main(String[] argv) {
ZonedDateTime zone = Instant.ofEpochMilli(1643446640000L).atZone(ZoneId.of("UTC"));
//2022-1-29 16:57:20
zone = zone.plusMonths(1);
System.out.println(zone.toLocalDateTime().atZone(ZoneId.of("UTC")));
//2022-02-28T08:57:20Z[UTC]
}
这边是一个极端的例子,2022年的1月29号往后一个月到底应该是2月28号,还是应该是3月1号。在我们的场景是一个令人费解的问题。一个月到底是30天还是31天,甚至是29天,28天,都不是一个可以简单区分的。但如果你用得是ZoneDateTime类的计算方式,答案则是月底到月底。