国际化通用程序或标准协议通常都涉及到时区问题,比如最近项目用到的OIDC(OpenID Connect)。

OIDC基于OAuth2协议,其id_token中包含了exp来表达该Token的过期时间,值为Unix Epoch(Timestamp,时间戳),通常各语言的日期实现会将该时间戳转换为本地日期,然后进行日期的比较。

0 时区与Unix Epoch

0.1 时区

为了统一地球上各地区的时间,建立了世界时,格林威治标准时间即作为第一个标准时间。地球以格林威治子午线为标准即0时区,按经度划分为24时区,东半球早于标准时间为正时区,西半球晚于标准时间为负时区。中国采用北京时间即正8区。格林威治标准时间缩写为GMT,东8区用GMT+08:00表示,即格林威治子午线的时间加上8小时为北京当地时间。

随着计时精度要求的提高,出现了原子计时器,形成了新的世界时,又称世界协调时简写UTC,就日常生活需求GMT与UTC等同。同时时区的划分也是一致的,北京时区用UTC+08:00表示。

0.2 Unix Epoch

在Unix系统上,为了用一个整数表示具体的时间,以1970年1月1日0时0分0秒0为基准, 将经过的秒数记为一个数值,然后用该数值表示某个时间。比如时间戳3600即表示1970年1月1日1时0分0秒,也就是在基准时间上经过了3600秒。由于时区问题,对于UTC+00:00时区的1970年1月1日0时0分0秒0时间点,实际为北京当地时间的1970年1月1日8时0分0秒0,这样对于时间戳3600即为北京当地时间的1970年1月1日9时0分0秒0 表示为 1970-01-01 09:00:00+08:00

0.3 时间表示

时间表示一般分为带时区信息的、不带时区的、Unix Epoch。不带时区的日期串称为Naive Date,如1970-01-01 09:00:00。在日常生活中特指当地时间,在程序语言中有的没有特指,就是时区缺失,可以加上时区信息,有的语言默认为操作系统默认时区。

下面以Pythongolang为例, 进行API操作描述

1 Python 时区操作

1.0 使用模块

  1. import time
  2. from datetime import datetime, timezone, timedelta
  3. # 下面dateutil 可用,可不用
  4. from dateutil import tz # 该package需要安装, pip install dateutil

1.1 当前时间

  1. # Python 的datetime模块提供了两个函数来返回时间
  2. naive_now = datetime.now() # 返回一个naive,本地时区的时间
  3. # 如在世界时2017-04-25 10:00:03+00:00在北京时区的机器上执行该代码
  4. # 返回 datetime(2017,4,25,18,0,3) # 还有微妙部分,假设为0,省略
  5. naive_utcnow = datetime.utcnow() # 返回一个naive,UTC+0:00 <简写UTC>的时间
  6. # 即返回 datetime(2017,4,25,10,0,3) # 微妙同上

1.2 有时区的时间

  1. # 方法一:通过解析获取
  2. china_date = datetime.strptime('2017-04-25 18:00:03+0800', '%Y-%m-%d %H:%M:%S%z')
  3. # 返回 datetime(2017, 4, 25, 18, 0, 3, tzinfo=datetime.timezone(datetime.timedelta(0, 28800)))
  4. # 可以看到返回的日期上tzinfo不为None了
  5. # 当解析的日期不带时区时,返回的日期对象tzinfo为None即naive日期对象
  6. naive_date = datetime.strptime('2017-04-25 18:00:03', '%Y-%m-%d %H:%M:%S')
  7. naive_utcdate = datetime.strptime('2017-04-25 10:00:03', '%Y-%m-%d %H:%M:%S')
  8. # 当naive日期与带时区的日期对象比较时,即使年月日时分秒以及微妙一致也是不相等的(因为时区不同)
  9. # 即naive_date != china_date
  10. # 两个naive的对象可以正常比较,两个非naive的对象也可以正常比较
  11. # 方法二:强制设置naive日期对象的时区
  12. tz_utcdate = naive_utcdate.replace(tzinfo=timezone.utc)
  13. # 返回 datetime(2017, 4, 25, 10, 0, 3, tzinfo=datetime.timezone.utc)
  14. # 该方法强制替换某个时间(日期)对象的时区属性即使是非naive的,并生成新的一个日期对象

1.3 获取时区

  1. #通过安装dateutil这个Package
  2. # 获取 当前系统时区,第一个参数为name,可以任意设置
  3. TZCUR = tz.tzoffset('current', -time.timezone) # 注意 time.timezone 与时区正负号相反, 用秒表示
  4. # 或者
  5. TZCUR = timezone(timedelta(minutes=-time.timezone/60)) # 作为timezone的参数,timedelta的参数最好用hours或minutes
  6. # 获取 UTC 时区
  7. TZUTC = timezone.utc
  8. # +8:00 时区/ China
  9. TZCHINA = tz.tzoffset('UTC+8:00', 8*3600)
  10. # 或
  11. TZCHINA = timezone(timedelta(hours=8), 'UTC+08:00') # 第二个参数为name可以为空或不传
  12. # +9:00 时区/ Japan
  13. TZJAPAN = tz.tzoffset('UTC+9:00', 9*3600)
  14. # 或
  15. TZJAPAN = timezone(timedelta(hours=9), 'UTC+09:00')

1.4 转换时区

将某个时区的日期转换为另一个时区的日期,如北京时间的晚18点,转换为东京时间,即为晚19点。

  1. # 使用astimezone函数,转换带时区的日期对象的时区
  2. japan_date = china_date.astimezone(TZJAPAN)
  3. # 返回 datetime(2017, 4, 25, 19, 0, 3, 0, tzinfo=tzoffset('UTC+9:00', 32400)
  4. # 对于naive的日期对象,即tzinfo属性为None时,该方法无法进行日期转换,抛出异常
  5. # 也就是 datetime.now()或datetime.utcnow()的结果都无法执行astimezone方法

1.5 Epoch 的 生成

  1. # 在Python3中datetime对象可以直接执行timestamp方法,如
  2. epoch = naive_date.timestamp()
  3. # 返回 1493114403.0 # 假设naive_date的微妙为0
  4. # 即该epoch表示从基准时间开始,经过1493114403秒
  5. # 对于没有时区信息的naive_date,默认使用系统时区
  6. # 即datetime(2017,4,25,18,0,3)当作datetime(2017,4,25,18,0,3,tzinfo=tzoffset('UTC+8:00', 28800))处理,对应世界时为2017-04-25 10:00:03+00:00,距离基准1970-01-01 00:00:00+00:00为1493114403秒。
  7. # 因此尽管naive_date 与 china_date对象不等,但执行timestamp方法后的结果一样
  8. # 而且 china_date通过astimezone方法转换为其他时区的对象后执行timestamp方法得到的结果也一样
  9. # 所以 datetime.now().timestamp() 与 datetime.utcnow().timestamp() 在非+00:00时区执行时不相等,因为两者实际是不同的时区,但由于缺失时区信息,强制按系统默认时区处理,两者将差time.timezone

1.6 按Epoch计算日期

  1. # 同now函数一样,Python提供了两个函数生成本地和世界时日期
  2. # 同样也是生成naive的日期,对象的时区属性为None
  3. naive_date_from_epoch = datetime.fromtimestamp(1493114403)
  4. naive_utcdate_from_epoch = datetime.utcfromtimestamp(1493114403)
  5. # 尽管两个epoch数值一样,得到naive日期对象将差8小时(若系统默认时区为+08:00)
  6. # 由于返回结果为naive日期,因此两个结果比较是不相等的,执行timestamp得到的数值也不相同
  7. # 将naive对象分别设置正确的时区后,两者将一直
  8. local_date_from_epoch = naive_date_from_epoch.replace(tzinfo=TZCUR) # 系统时区
  9. utc_date_from_epoch = naive_utcdate_from_epoch.replace(tzinfo=TZUTC) # UTC+0时区
  10. # 此时 local_date_from_epoch == utc_date_from_epoch
  11. # 两者生成的Epoch也都是1493114403
  12. # Epoch 为1时,可以更好的看到该情况
  13. datetime.utcfromtimestamp(1) # 返回 datetime(1970, 1, 1, 0, 0, 1)
  14. datetime.fromtimestamp(1) # 返回 datetime(1970, 1, 1, 8, 0, 1)
  15. 由于naive日期 datetime(1970, 1, 1, 0, 0, 1) 执行timestamp方法,将系统时区处理即按1970-01-01 00:00:01+0800处理时(对应1969-12-31 16:00:01+0000)在Epoch的基准线之前,Python3.5的版本会抛出OverflowError的异常

1.7 跨编程语言

  1. // golang 内置time模块进行日期相关处理
  2. // A 获取当前时间
  3. now = time.Now() // 与Python不同,返回的是一个带时区的'日期'对象
  4. // golang使用time.Date传入年月日,时分秒,微妙去构造一个日期对象, 还必须传入时区信息,如
  5. time.Date(2017, time.April, 25, 18, 0, 3, 0, time.Local)
  6. // B 时区转换
  7. now.UTC() // 转换为 UTC时间,即UTC+0时区时间
  8. // C 按日期生成Epoch
  9. now.Unix() // 生成到秒,返回整数
  10. // 其中 now.Unix() == now.UTC().Unix() ,与Python一致
  11. // D 按Epoch计算日期
  12. time.Unix(seconds, microseconds) // 返回一个带时区的'日期'对象
  13. // 以上两个日期对象都带本地时区
  14. // 也就是 golang的 time.Unix等同于python的datetime.fromtimestamp(seconds).replace(tzinfo=TZCUR)
  15. // 采用系统时区,基本是各语言的默认行为

2 总结

在进行跨时区处理时,只要正确区分naive日期对象和带时区的日期对象,基本就保证了时间处理的正确性,而Epoch值表示相对于基准时间的差值,有效的回避了该问题(不同时区基准naive不一样)。避免了传递不带日期的时间字符串的时区问题。

以Token过期为例,北京时间2017-04-25 18:00:03生成一个Token,一小时后过期,即北京时间2017-04-25 19:00:03过期。该Token传给东京的服务器后,按东京当地时间应该在东京2017-04-25 20:00:03时过期。

交互时传递参数为2017-04-25 19:00:03+0800时,由于带有时区信息可以准确表达,若缺失时区如2017-04-25 19:00:03时,双方都没有处理时会出错。

若用Epoch表示,Token生成时间为北京时间2017-04-25 18:00:03对应Epoch值为1493114403(即距离基准北京时间1970-01-01 08:00:00 相隔1493114403秒),Token过期时间为北京时间2017-04-25 19:00:03对应Epoch为1493118003(即生成时间之后3600秒)。 传递该Epoch值至东京服务器,收到后,解释为东京基准时间起之后1493118003秒的那个时间点该Token过期,自然就对应东京时间2017-04-25 20:00:03了。此过程,无需时区处理。

也就是说Epoch 1对应

  • 1970-01-01 00:00:01+0000(UTC)
  • 1970-01-01 08:00:01+0800(北京当地时间)
  • 1970-01-01 09:00:01+0900(东京当地时间)

而Epoch 1493118003对应

  • 2017-04-25 11:00:03+0000(UTC)
  • 2017-04-25 19:00:03+0800(北京)
  • 2017-04-25 20:00:03+0900(东京)

这样使用Epoch进行跨时区、跨语言交互时,处理与平常(无时区交互时)一致,无需任何特殊处理。若进行特殊处理,又处理不对应时,反倒会画蛇添足。

当采用的框架提供修改时区的功能是,可能会导致与语言默认行为不一致,此时要特别注意。

跨语言时区处理与Epoch的更多相关文章

  1. Atitit java c# php c++ js跨语言调用matlab实现边缘检测等功能attilax总结

    Atitit java c# php c++ js跨语言调用matlab实现边缘检测等功能attilax总结 1.1. 边缘检测的基本方法Canny最常用了1 1.2. 编写matlab边缘检测代码, ...

  2. 跨语言和跨编译器的那些坑(CPython vs IronPython)

    代码是宝贵的,世界上最郁闷的事情,便是写好的代码,还要在另外的平台上重写一次,或是同时维护功能相同的两套代码.所以才需要跨平台. 不仅如此,比如有人会吐槽Python的原生解释器CPython跑得太慢 ...

  3. Golang通过Thrift框架完美实现跨语言调用

    每种语言都有自己最擅长的领域,Golang 最适合的领域就是服务器端程序. 做为服务器端程序,需要考虑性能同时也要考虑与各种语言之间方便的通讯.采用http协议简单,但性能不高.采用TCP通讯,则需要 ...

  4. Apache Thrift 跨语言服务开发框架

    Apache Thrift 是一种支持多种编程语言的远程服务调用框架,由 Facebook 于 2007 年开发,并于 2008 年进入 Apache 开源项目管理.Apache Thrift 通过 ...

  5. Atitti 跨语言异常的转换抛出 java js

    Atitti 跨语言异常的转换抛出 java js 异常的转换,直接反序列化为json对象e对象即可.. Js.没有完整的e机制,可以参考java的实现一个stack层次机制的e对象即可.. 抛出Ru ...

  6. Golang、Php、Python、Java基于Thrift0.9.1实现跨语言调用

    目录: 一.什么是Thrift? 1) Thrift内部框架一瞥 2) 支持的数据传输格式.数据传输方式和服务模型 3) Thrift IDL 二.Thrift的官方网站在哪里? 三.在哪里下载?需要 ...

  7. 生成跨语言的类型声明和接口绑定的工具(Djinni )

    Djinni 是一个用来生成跨语言的类型声明和接口绑定的工具,主要用于 C++ 和 Java 以及 Objective-C 间的互通. 示例接口定义文件: # Multi-line comments ...

  8. Apache Thrift - 可伸缩的跨语言服务开发框架

    To put it simply, Apache Thrift is a binary communication protocol 原文地址:http://www.ibm.com/developer ...

  9. sqlserver2008安装出现跨语言

    我在安装sqlserver2008的时候出现了一个问题,安装到一半的时候出现 跨语言安装失败 ,我细细的查了下问题,我装的安装语言绝对没有错的吧,然后我后退几步又是同样的错误,最后我把镜像重新加载到虚 ...

随机推荐

  1. JavaScript前端最全API集锦

    一.节点1.1 节点属性Node.nodeName   //返回节点名称,只读Node.nodeType   //返回节点类型的常数值,只读Node.nodeValue  //返回Text或Comme ...

  2. 超级素数幂 java

    链接:https://www.nowcoder.com/questionTerminal/fb511c3f1ac447309368d7e5432c6c79来源:牛客网如果一个数字能表示为p^q(^表示 ...

  3. 根据模板导出Excel报表并生成多个Sheet页

    因为最近用报表导出比较多,所有就提成了一个工具类,本工具类使用的场景为  根据提供的模板来导出Excel报表 并且可根据提供的模板Sheet页进行复制 从而实现多个Sheet页的需求, 使用本工具类时 ...

  4. python IP地址转16进制

    python IP地址转16进制 第一种方法: 通过socket.inet_aton实现 import socket from binascii import hexlify ary='192.168 ...

  5. Servlet+jsp的分页案例

    查询的分页,在web中经常用到.一般,分页要维护的信息很多,我们把这些相关的信息,分装到一个类中,PageBean.具体如下: package cn.itcast.utils; import java ...

  6. 关于Trie KMP AC自动机

    个人认为trie,KMP,AC自动机是思想非常明确的,AC自动机的性质是与KMP算法的思想类似的(失配后跳转) 而KMP是线性的,AC自动机是在tire树上跑KMP,为方便那些不会用指针的小朋友(我也 ...

  7. spring-AOP-基于@AspectJ切面的小例子

    条件: 1.jdk的版本在5.0或者以上,否则无法使用注解技术 2.jar包: aspectjweaver-1.7.4.jar aspectjrt-1.7.4.jar spring-framework ...

  8. 极客君教你破解隔壁妹子的wifi密码,成功率高达90%

    首先,给大家推荐一个我自己维护的网站: 开发者网址导航:http://www.dev666.com/ 破解wifi密码听起来很复杂,实际上也不是非常的复杂,极客君(微信公众帐号:极客峰)今天教大家如何 ...

  9. (转)Linux core 文件介绍与处理

    1. core文件的简单介绍 在一个程序崩溃时,它一般会在指定目录下生成一个core文件.core文件仅仅是一个内存映象(同时加上调试信息),主要是用来调试的. 2. 开启或关闭core文件的生成用以 ...

  10. shell是什么,各种shell的初步认识,适用于初学者

    shell是什么?有什么用处?怎么用?我相信,这是大部分人刚接触到shell都有过的疑问.下面小编为大家讲解一下自己的讲解,希望能对大家有所帮助. 什么是shell? shell就是系统内核的一层壳, ...