-
Notifications
You must be signed in to change notification settings - Fork 45
java8 time problems
landon edited this page Sep 14, 2018
·
1 revision
- 2018.3.8 发现200服务器时间出错 显示的时间比正常时间小8个小时
- 发现后 直接利用date命令就时间修改为了正常时间
- 修改完毕后发现客户端时间显示不正确
- 2018.3.20 重启了200服务器 发现客户端时间显示正确 但是服务器时间却还是比正常时间小8个小时 不过这次仔细看了一下发现是UTC时间
- 另外出现玩家登录即重置次数的bug
- 2018.3.21 运维将UTC时区调整为CST时区 一些正常
- UTC是世界协调时间 大家可以理解为是0时区
- 而我们的正常时间 即北京时间 其实是CST,即UTC + 8
- 即表示北京时间比UTC时间早8个小时
- 如北京时间 CST是 20:00,而UTC时间则是12:00
- 时区不同只表示两个地方的时间显示不同 但绝对时间戳是一样的 即System.currentTimeMillis在两个时区的值输出都是一样的
- 200服务器不清楚什么原因被修改了UTC时间 即3.8号发现的
- 注意此时的UTC时间是正确的 只不过是UTC时区 如果此时切换为CST时区的话 时间就是正确的北京时间
- 但当时我发现的时候 没有仔细看 即没有看到UTC 只看到了时间比正常时间小于8个小时 所以我直接用date命令修改了正确的时间
- 但是这里注意 还是UTC时间
- 如之前是UTC 07:00 此时北京时间应该是15:00 这个是正确的
- 而我修改为了UTC 15:00 但此时对应的北京时间却应该是23:00
- 所以我这里错误的相当于修改了服务器时间 相当于往后调了8个小时
- 所以出现了背景3提到的
- 客户端显示不正确 因为客户端显示的时间是比正常时间 + 8小时
- 而服务器时间显示''正确'' 这个只是看着正确 时区确实UTC
- 所以复盘1总结
- 是我没有仔细看清楚到底是哪个时区 就随便调整了服务器时间
- 2018.3.20号 200服务器重启了
- 因为date命令只是改了如终端时间 重启后时间会被重置
- 所以200服务器上输入date显示的时间是UTC时间
- 注意这个UTC时间是正确的 转为CST(+8 -> 北京时间后)就是正确的时间了
- 所以给客户端推送的系统时间是正确的
- 所以出现客户端时间正确 但是服务器却显示的是"不正确的"
-
为什么在UTC时区的情况下 玩家登录都会被重置次数?
-
简单说一下重置次数原理
- 玩家下线的时候会记一个resetTime
- 这个resetTime是下一天的0点
- 玩家上线的时候 会比较当前是否这个resetTime 如果大于就重置
-
200服务器当前是正确的UTC时间
-
玩家下线resetTime是用了
LocalDateTime#toInstant(ZoneOffset.of("+8")).toEpochMilli()
- 这个方法是比较ZoneOffset(比较当前时区和参数时区) 因为是当前UTC和参数ZoneOffset.of("+8")差8个小时
- 所以要比正常的返回时间戳要小于8小时
- 比如玩家2018.3.20 20点下线 那么这个方法返回的resetTime是2018.3.20 16点(24 - 8)
- 玩家再次登录发现loginTime > resetTime则执行了重置
-
所以根本原因是获取resetTime的方法问题 手动指定了ZoneOffset为北京时区 应该用系统默认时区
-
那么为什么CST时区下(北京时区) 上面这点代码就工作正常呢
- 因为当前时区就是北京时区 所以和参数ZoneOffset.of("+8")对比 发现不差
- 所以返回的时间戳是正确的时间戳
-
所以那段代码在北京时区的服务器下是没有问题的 如果是其他时区的服务器下是一定出问题的
-
LocalDateTime如何返回正确的时间戳,我这边测试了两种方式,原理就是参数ZoneOffset一定要和本地时区一致
long time5 = ldt.toInstant(OffsetDateTime.now().getOffset()).toEpochMilli();
ZoneId zoneId = ZoneId.systemDefault(); ZoneOffset offset = zoneId.getRules().getOffset(ldt); long time4 = ldt.toInstant(offset).toEpochMilli();
-
时间相关代码一定不要使用和具体时区相关的代码 一定会出问题的
- 我们的代码是一定要兼容不同时区服务器的
-
看服务器时间的时候一定要注意确认到底是UTC还是CST
- 如果CST 那么就是北京时间很好确认
- 如果是UTC 我们只需要确认比北京时间小8小时 就正确了
public class Java8TimeTest {
@Test
public void testNow() {
// 绝对时间 和时区无关系
long now1 = System.currentTimeMillis();
long now2 = new Date().getTime();
// 这个数值输出的long跟时区无关 但是LocalDateTime#toEpochMilli是和时区偏移有关系
long now3 = Instant.now().toEpochMilli();
System.out.println(now1);
System.out.println(now2);
System.out.println(now3);
// 1521645418308
// Wed Mar 21 15:16:58 UTC 2018
// 1521645498317 Wed Mar 21 23:18:18 CST 2018
// date显示和时区有关系
System.out.println(new Date(now3));
// 2018.3.22 10:49 cst 1521686999245 1521686999246 1521686999247
// 2018.3.22 02:53 utc 1521687230061 1521687230062 1521687230062
// 2018.3.22 cst 15:09:52 1521702592007 1521702592007 1521702592008
// 总结A
// 1. System.currentTimeMillis() | Instant.now().toEpochMilli()
// 返回的都是当前系统时间的时间戳 是一个绝对时间
// 2. 这个绝对时间和时区没有关系 即使切换时区如从cst(utc+8)切换到utc 这个绝对时间的输出也不会变
// 3. 因为切换时区不同 所以世界现实的时间会不同
// 总结B
// issue中问题 最后一次是2018.3.20 200上显示的时间是utc 2:30 此时客户端时间10:30 这个解释因为utc
// 2:30就是cst 10:30 所以从网络层给
// 客户端发的绝对时间戳就是10:30 所以显示时间是正确的
// 总结C
// 第一次出时间的问题复盘 200上显示的时间就是utc时间是正确的如10点 而对应的cst时间是18点
// 但是当我看到的时候没有看utc 直接看10点不对 所以直接通过date命令修改了时间手动加了8个小时 变为了utc 18点
// 那么此时实际的时间cst是18+8 为第二天的2点 而客户端不显示天 只显示02
// 而date修改过的时间再服务器重启以后又被重置为了utc正确的时间 即回到了总结B
// 总结D
// 为什么服务器变为utc时间之后 登录之后就会被重置次数
// 1.服务器现在是正确的utc时间
// 2.玩家下线resetTime是用了LocalDateTime#toInstant(ZoneOffset.of("+8")).toEpochMilli()
// 3.这个方法是比较ZoneOffset 因为是utc和参数ZoneOffset.of("+8")差8个小时
// 所以要比正常的返回时间戳要小于8小时
// 4.比如玩家2018.3.20 20点下线 那么这个方法返回的resetTime是2018.3.20 16点(24 - 8)
// 5.玩家再次登录发现loginTime > resetTime则执行了重置
// 6.所以根本原因是获取resetTime的方法问题 指定了北京时区 应该用系统默认时区 即下面单元测试用到的两种方式
}
/*
* 1521655200000
*
* Thu Mar 22 02:00:00 CST 2018
*
* 1521626400033
*
* Wed Mar 21 18:00:00 CST 2018
*
* 1521626400000
*
* Wed Mar 21 18:00:00 CST 2018
*/
@Test
public void testLocalDateTime() {
// 默认现在系统时区是cst
// UTC
// 我当前时区是cst(utc+8) 传入(ZoneOffset.UTC) 表示我当时时区比参数时区的offset 明显是大8个小时
// 所以输出是+8个小时
LocalDateTime ldt = LocalDateTime.of(2018, 3, 21, 18, 0);
long time1 = ldt.toInstant(ZoneOffset.UTC).toEpochMilli();
System.out.println(time1);
Date date1 = new Date(time1);
System.out.println(date1);
// 默认cst
Calendar calendar = Calendar.getInstance();
calendar.set(2018, 2, 21, 18, 0, 0);
long time2 = calendar.getTimeInMillis();
System.out.println(time2);
Date date2 = new Date(time2);
System.out.println(date2);
// +8 cst
// 为什么默认没有cst没有问题 因为当前就是utc+8 没有offset 所以正确
long time3 = ldt.toInstant(ZoneOffset.of("+8")).toEpochMilli();
System.out.println(time3);
Date date3 = new Date(time3);
System.out.println(date3);
// 正确方式 测试无问题 测试发现ldt.toInstant传入的offset必须要本地时区一致才可以
ZoneId zoneId = ZoneId.systemDefault();
ZoneOffset offset = zoneId.getRules().getOffset(ldt);
long time4 = ldt.toInstant(offset).toEpochMilli();
System.out.println(time4);
Date date4 = new Date(time4);
System.out.println(date4);
// 尝试另外一种方式
long time5 = ldt.toInstant(OffsetDateTime.now().getOffset()).toEpochMilli();
System.out.println(time5);
Date date5 = new Date(time5);
System.out.println(date5);
}
// 修改zone为utc(win7)
/*
* 1521655200000
*
* Wed Mar 21 18:00:00 UTC 2018
*
* 1521655200401
*
* Wed Mar 21 18:00:00 UTC 2018
*
* 1521626400000
*
* Wed Mar 21 10:00:00 UTC 2018
*/
@Test
public void testChangeZone() {
// win7直接修改时区为UTC(协调世界时) 此时显示的时间比北京时间晚8个小时
LocalDateTime ldt = LocalDateTime.of(2018, 3, 21, 18, 0);
long time1 = ldt.toInstant(ZoneOffset.UTC).toEpochMilli();
System.out.println(time1);
Date date1 = new Date(time1);
System.out.println(date1);
Calendar calendar = Calendar.getInstance();
calendar.set(2018, 2, 21, 18, 0, 0);
long time2 = calendar.getTimeInMillis();
System.out.println(time2);
Date date2 = new Date(time2);
System.out.println(date2);
// 我当前时区是utc 传入(ZoneOffset.of("+8")) 表示我当时时区比参数时区的offset 明显是小于8个小时
// 所以输出是-8个小时
long time3 = ldt.toInstant(ZoneOffset.of("+8")).toEpochMilli();
System.out.println(time3);
Date date3 = new Date(time3);
System.out.println(date3);
// 正确方式 测试无问题 测试发现ldt.toInstant传入的offset必须要本地时区一致才可以
ZoneId zoneId = ZoneId.systemDefault();
ZoneOffset offset = zoneId.getRules().getOffset(ldt);
long time4 = ldt.toInstant(offset).toEpochMilli();
System.out.println(time4);
Date date4 = new Date(time4);
System.out.println(date4);
// 尝试另外一种方式
long time5 = ldt.toInstant(OffsetDateTime.now().getOffset()).toEpochMilli();
System.out.println(time5);
Date date5 = new Date(time5);
System.out.println(date5);
}
}