bug分享:跨国项目中时间戳引发的问题

问题

一个面向多国家的电商网站,商家在后台网站设置时间,被后台校验拦截,其拦截的代码如下:

代码
payStartTime().compareTo(startTime) >= 0 && payStartTime().compareTo(endTime) <= 0

这里的时间 payStartTime、startTime、endTime 都是Date的时间,如果用户填写的支付时间payStartTime是在[startTime,endTime]区间内,则校验不通过,这里startTime = 10:00:00 ,endTime = 12:00:00,后面发现商家看到的页面时间跟国内商家客服看到的时间不一致,由此引发了对时间的思考。

时间戳

这个时间也是大家比较熟悉的,例如System.currentTimeMillis() 就是经典的获取时间戳的方法,这个时间是指从一个基准时间(1970-1-1 00: 00:00 +0:00)到现在的秒数,用一个整数表示,为什么是这个基准时间,这就是关于unix系统的另一个故事了,这里不再讲解。无论服务器在哪个时区,任意时刻,对于地球上任何位置来说生成的时间戳也是一样的,但是为什么我们跟美国的时间显示是不一样的呢?因为我们没有用这个时间戳来表示当地的一个时间,手机、手表都没有用时间戳来显示,一方面确实不够浅显易懂,另一个方面是我们是早上8点,西方国家是黑夜,统一用时间戳似乎跟太阳不挂钩,所以用时间戳统一表示不切实际。

时区GMT(Greenwich Mean Time):格林威治标准时

为了科学表示每个国家的白天、黑夜,有人提出了时区的概念,地球就是一个球体,一个球体360度,分为24个时区,一个时区15个纬度,规定英国(格林尼治天文台旧址)为零时区(GMT+00),东1-12区,西1-12区,往西一个时区,则减去一小时;往东一个时区,则加上一小时。中国在东经120度上,(东经120°-东经0°)所得度数再除以15,即得8。中国北京处于东8区(GMT+08)。若英国时间为6点整,则北京时间为14点整。这样英国6点天刚刚亮,而中国也正是日当午,很好的把每个国家的早晨都放在了6点,每个国家天黑也都是在18点,但这样跟时间戳有什么关系?

本地时间:时间戳和时区

时间戳在地球的任何地方都是一样的,依靠时区,可以翻译为我们熟悉的具体的时间格式:2020-11-10 00:00:00 ,这样我们就可以正确的在每个国家的早晨看到 6点的时刻,所以这个也叫做本地时间,但计算机不会存储本地时间,如果存储本地时间,那么不同国家发生事件的同一个时刻不是逻辑上的时间一致,就好像飞机从中国飞往英国,穿越那么多的国家,但每个国家的时间都不是一致的,那怎么知道飞机随着时间线经过的经纬度,经度、纬度才可以确定一个飞机在地球的位置,但利用时间戳可以解决这个问题,因为时间戳只随着时间流逝一直增长,利用时间戳可以看到一个事物随时间的流逝,所以计算机存储时间戳。

回到本文问题

电商网页中前端的时间控件选的是本地时间对应的时间戳,假设韩国客户看到的是12:00:00,是时间戳A,这个时间戳A是根据韩国时区来转换的,但在中国此时是11:00:00,这样时间戳是一样的,带上时区后的时间是不一样的,但客户却以为这个时间也是在中国的12:00:00,因为时区不一样,时间戳传到后台服务器,后台服务器得到的时间是中国时区,这样时间就提前了,而上述代码的时间都是以中国时区转换过去的时间戳,韩国的12点对应的时间戳正好卡在了中国的[10:00:00,12:00:00]区间,那么本文上述代码就不会通过。注意整个过程中时间戳都没问题,问题是中国的12:00:00对应的时间戳比韩国客户看到的12:00:00对应的时间戳要大,时间戳不一致。

Date

Date对象里存的是自格林威治时间( GMT)1970年1月1日0点至Date对象所表示时刻所经过的毫秒数。所以,如果某一时刻遍布于世界各地的程序员同时执行new Date语句,这些Date对象所存的毫秒数是完全一样的。
也就是说,Date里存放的毫秒数是与时区无关的。但如果要显示出来:

System.out.println(date);

// 北京的程序员将会打印出 : 2017年8月24日11:17:10
//   东京的程序员会打印出 : 2017年8月24日12:17:10
//   伦敦的程序员会打印出 : 2017年8月24日4:17:10

这是因为Sysytem.out.println函数在打印时间时,会取操作系统当前所设置的时区,然后根据这个时区将同毫秒数解释成该时区的时间。我们来看下指定时区来打印时间:

Date date = new Date(1503544630000L);  // 对应的北京时间是2017-08-24 11:17:10
 
SimpleDateFormat bjSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");     // 北京
bjSdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));  // 设置北京时区
 
SimpleDateFormat tokyoSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  // 东京
tokyoSdf.setTimeZone(TimeZone.getTimeZone("Asia/Tokyo"));  // 设置东京时区
 
SimpleDateFormat londonSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // 伦敦
londonSdf.setTimeZone(TimeZone.getTimeZone("Europe/London"));  // 设置伦敦时区
 
System.out.println("毫秒数:" + date.getTime() + ", 北京时间:" + bjSdf.format(date));
System.out.println("毫秒数:" + date.getTime() + ", 东京时间:" + tokyoSdf.format(date));
System.out.println("毫秒数:" + date.getTime() + ", 伦敦时间:" + londonSdf.format(date));

毫秒数:1503544630000, 北京时间:2017-08-24 11:17:10
毫秒数:1503544630000, 东京时间:2017-08-24 12:17:10
毫秒数:1503544630000, 伦敦时间:2017-08-24 04:17:10

字符串表示的时间

如果用字符串表示时间,后期一定要用带时区才能表示为准备的时间戳,不然时间就错乱了。

参考博客

三句话理解时区与时间戳
Java 中的时区理解和处理

©️2020 CSDN 皮肤主题: 终极编程指南 设计师:CSDN官方博客 返回首页