前言

曾经以为时区这种东西一辈子都不会用到,又不出国,也不会接触到国外的项目。怎奈公司项目涉及海外版本,并且架构本身并没有考虑到时区问题,数据库中存储的相关时间部分不是时间戳,而是timestamp类型,当他来的时候总是那么的措不及防,以下是血泪史的记录,谨以此文告慰一周死去的脑细胞。

时区基础知识

  • 世界时区总共分为24个,从-12到+12,分别被称作是东一到东十二,西一到西十二。
    以北京时间为例: 属于东八区 +8 ,美国 西六区 -6

  • UTC :Coordinated Universal Time 协调世界时。因为地球自转越来越慢,每年都会比前一年多出零点几秒,每隔几年协调世界时组织都会给世界时+1秒,让基于原子钟的世界时和基于天文学(人类感知)的格林尼治标准时间相差不至于太大。并将得到的时间称为UTC,这是现在使用的世界标准时间。不与任何地区位置相关,也不代表此刻某地的时间

  • GMT: 根据地球的自转和公转来计算时间 ,由于现在世界上最精确的原子钟50亿年才会误差1秒,格林尼治刚好在0时区上 因此也近似等于 UTC+0

  • CAT: 同时可以代表如下 4 个不同的时区,常见于数据库中的时区
    Central Standard Time (USA) UT-6:00
    Central Standard Time (Australia) UT+9:30
    China Standard Time UT+8:00
    Cuba Standard Time UT-4:00

  • 时间戳:从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数,不考虑闰秒。是一个差值,和时区无关。同一个时间,不管哪个时区获取到时间戳都是一样的,但是代表的日期却因为时区不同而不同。

时区函数选择

因为项目使用的是php进行开发的,查阅相关文档,并没有发现php并不能够直接设置 UTC+(-)时区,而GMT可以,因此选择GMT来设置时区。
格式如下:
/Etc/GMT+8

php的坑

参考其他语言中设置时区,东八区就设置+8,但是php就是这么个性,需要取反,设置为-8,如果要设置为东八区需要:

date_default_timezone_set("Etc/GMT-8");

MySQL中的时区

查看当前时区,因为我设置过了所以这里查询出来是-6

show variables like '%time_zone%';

我们可以看到有一个系统的时区,同时也有一个当前会话对应的时区time_zone。

数据库中timestamp类型:
存储的时候,按照当前数据库的时区设置时间,比如再写入数据库的时候是+8区的时间,

我们设置为+7区,在查看时间,就晚了一个小时。

填坑

框架底层

项目使用yii2,重写数据库刚刚连接的方法,立即重置时区,保证一次会话中php和mysql时区都是一致的。

缓存

因为项目中在最底层重写了数据查询结果集,全部存储在redis缓存中,每当更新某个表的时候,刷新相关缓存,key的取值又是这种获取的类名和函数名,对于这种,在刷新相关缓存的key的时候,需要把时区的概念带进去。

对外api接口

因为游戏中心是所有数据的"心脏",对外提供不同的访问者,至少有5-6方,各个系统要求的格式也不一样,如果说所有的都由接口提供方转换,那么对于全列表的大数据量的json来说,那是致命的伤害。
因为在接口中不同的版本对应不同的时区,循环查询的时候频繁的设置数据库时区,并且写入缓存中,效率可想而知。

那就让需求方自己传一个时区过来,还是在连接数据库完成的时候,直接设置时区或者传和0区的秒数差,全部按照指定时区返回,由使用方自行调整。

配置文件中日期

有些数据是以字段的形式存储在数据库某个json字段中,对于这种纯字符串,没有timestamp的概念,只有存储的时候,以目标时区为准,在取出来的时候设置指定时区,后面通过php转为指定时区。

附录时区转换封装类

class DateHelper
{public static $cacheTime = 600;/*** 根据serverType获取时区* @param $serverType int|string* @param $gid int|string 用于缓存中使用* @return string 格式:+08:00* @throws \Exception*/public static function getTimeZoneByServerType($serverType,$gid){$cacheKey = __CLASS__ . __FUNCTION__ . $serverType;$value = \Yii::$app->ac->get($cacheKey, GameServerType::tableName().'::'.$gid, function () use ($serverType) {//根据serverType获取时区$server_type_info = GameServerType::findOne($serverType);if (empty($server_type_info) || is_null($server_type_info)) {\Yii::info("date_helper:获取{$serverType}信息失败");$time_zone = "+08:00";} else {$data = $server_type_info->data;AppCommon::decodeJsonToArray($data);//配置中配置的json转为数组//配置了时区if (!empty($data) && isset($data['time_zone'])) {if (self::isTimeZoneAvaiable($data['time_zone'])) {$time_zone = $data['time_zone'];} else {throw new \Exception("date_helper---serverType:{$serverType},time_zone:{$data['time_zone']}格式非法");}} else {$time_zone = "+08:00";}}return $time_zone;});return $value;}/*** 设置会话级php和MySQL的时区* @param int $serverType string serverType* @param int $gid string gid* @param bool $is_set_mysql 是否设置mysql数据库时区,有的地方只需要设置php的时区* @throws \Exception* @throws \yii\db\Exception*/public static function setSessionTimeZone($serverType = 0, $gid=0,$is_set_mysql = true){//如果是根据ServerType获取时区if ($serverType != 0) {$time_zone = self::getTimeZoneByServerType($serverType,$gid);//设置时区 mysql+phpif ($is_set_mysql) {\Yii::$app->db->createCommand("set time_zone = '{$time_zone}'")->execute();}$php_time_zone = explode(':', $time_zone)[0];} else {//$time_zone='Etc/GMT-8'  //如果没有传入time_zone直接获取php默认的时区//设置时区 mysql+phpif ($is_set_mysql) {\Yii::$app->db->createCommand("set time_zone = '+08:00'")->execute();}$php_time_zone = '+8';}if (strpos($php_time_zone, '-') !== false) {$diff = intval(trim($php_time_zone, '-'));date_default_timezone_set("Etc/GMT+{$diff}");} else {$diff = intval(trim($php_time_zone, '+'));date_default_timezone_set("Etc/GMT-{$diff}");}}/*** 判断设置的时区是否可用* @param $time_zone string 格式:+08:00* @param int $serverType string|int 兼容根据serverType!=0时根据serverType获取* @return bool*/public static function isTimeZoneAvaiable($time_zone, $serverType = 0){if (empty($time_zone)) {return false;}preg_match('/[+,-]{1}[0-2]{1}[0-9]{1}\:\w+/', $time_zone, $match);if (!empty($match)) {return true;} else {\Yii::info("serverType:{$serverType}配置时区格式错误,time_zone:{$time_zone}");return false;}}/*** 获取php默认时区,time_zone 格式:/Etc/GMT+8 simple_zone 格式:+08:00* @return array*/public static function getPHPDefaultZone(){$time_zone = 'Etc/GMT-8';//默认东八区,对应服务器上php的时区,如果服务器上改了,这里需要改,配置文件里面那个和时区相关的字段也需要修改。$simple_zone = self::getSimpleZone($time_zone);return ['time_zone' => $time_zone,'simple_zone' => $simple_zone,];}/*** 转换 /Etc/GMT+8 格式 ---> +08:00 格式* @param $time_zone* @return int|string*/public static function getSimpleZone($time_zone){$temp = substr($time_zone, 7);// +8 -10 这种$op = substr($temp, 0, 1);$num = intval(substr($temp, 1));$op = ($op == '+' ? '-' : '+');$num = $op . str_repeat('0', 2 - strlen($num)) . $num . ':00';return $num;}/*** 设置固定格式时区* @param $time_zone string 游戏中心配置的 +08:00这种* @throws \Exception* @throws \yii\db\Exception*/public static function setFixedTimeZone($time_zone){//判断传过来的时区格式 符合 +08:00这种 才设置if (self::isTimeZoneAvaiable($time_zone)) {\Yii::$app->db->createCommand("set time_zone = '{$time_zone}'")->execute();$php_time_zone = explode(':', $time_zone)[0];if (strpos($php_time_zone, '-') !== false) {$diff = intval(trim($php_time_zone, '-'));date_default_timezone_set("Etc/GMT+{$diff}");} else {$diff = intval(trim($php_time_zone, '+'));date_default_timezone_set("Etc/GMT-{$diff}");}} else {self::setSessionTimeZone();}}
}

php转换时区

/*** Created by PhpStorm.* User: 骆同超* Date: 2020/12/15 10:17* Mail:1446028125@qq.com*/
class DateTools
{//根据配置的 +08:00 配置返回 php 设置时区需要的参数public static function getPhpFormatTimeZone($simple_time_zone){$php_time_zone = explode(':', $simple_time_zone)[0];if (strpos($php_time_zone, '-') !== false) {$diff = intval(trim($php_time_zone, '-'));return "Etc/GMT+{$diff}";} else {$diff = intval(trim($php_time_zone, '+'));return "Etc/GMT-{$diff}";}}/*** 把日期从指定时区转到指定时区,* @param $src string 源时区* @param $target string 目标时区* @param $datetime array 需要转换的字段,同一张表可能多个字段都需要转时区,如['open_time'=>'具体时间','create_time'=>'具体时间']* @param string $format string 日期格式* @return array*/public static function convertTimeZone($src, $target, $datetime, $format = 'Y-m-d H:i:s'){//目标时区和源时区一样,转个格式直接返回if ($src == $target) {$news = [];foreach ($datetime as $k => $v) {if (!empty($v)) {$news[$k] = date($format, strtotime($v));} else {$news[$k] = $v;}}return ['data' => $news,'today_datetime' => date('Y-m-d H:i:s'),];}//当前php时区默认和源时区不一样才重置时区少一步操作if (date_default_timezone_get() != $src) {date_default_timezone_set($src);}//全部转为当前时区的时间戳,避免转为最小时间戳,导致转为日期是出现 1970-01-01这种$src_time_stamp = [];foreach ($datetime as $kk => $vv) {if(!empty($vv)){$src_time_stamp[$kk] = strtotime($vv);}else{$src_time_stamp[$kk] = $vv;}}//转为目标时区date_default_timezone_set($target);$target_time_arr = [];foreach ($src_time_stamp as $kkk => $vvv) {if(!empty($vvv)){$target_time_arr[$kkk] = date($format, $vvv);}else{$target_time_arr[$kkk] = $vvv;}}$today_datetime = date('Y-m-d H:i:s');//获取当前时区下date_default_timezone_set($src);//重置为源时区return ['data' => $target_time_arr,'today_datetime' => $today_datetime,];}/*** 处理时间问题,重置指定时区的若干字段。* @param $list array 查询出来的结果集* @param $gid int|string game_id* @param array $key array 需要转的具体的字段* @return mixed* @throws \Exception*/public static function dealTime($list, $gid, $key = ['datetime']){$org_time_zone = DateHelper::getPHPDefaultZone();$src_time_zone = $org_time_zone['time_zone'];//当前时区,/Etc/GMT-8 格式$src_simple_time_zone = $org_time_zone['simple_zone'];//当前时区+08:00foreach ($list as &$item) {//根据sid获取server_type$server_type = DateHelper:: getServerTypeBySid($gid, $item['sid']);//根据server_type获取时区$dst_simple_time_zone = DateHelper::getTimeZoneByServerType($server_type,$gid);//两个时区不同才转换if (trim($src_simple_time_zone) != trim($dst_simple_time_zone)) {//根据配置时区转换为php格式时区$dst_time_zone = self::getPhpFormatTimeZone($dst_simple_time_zone);//设置目标时区$data = self::convertTimeZone($src_time_zone, $dst_time_zone, self::getChangeCondition($key, $item));foreach ($key as $v) {$item[$v] = $data['data'][$v];}}$item['mergeSid'] = $item['sid'];}unset($item);return $list;}/*** 生成循环的条件* @param array $change_field_arr* @param $item* @return array*/public static function getChangeCondition($change_field_arr = ['datetime'], $item){$news = [];foreach ($change_field_arr as $k => $v) {$news[$v] = $item[$v];}return $news;}/*** 根据传过来的和0时区相差的秒数转换为时区格式* @param $second* @return string*/public static function getTimeZoneBySecond($second){$diff = floor($second / 3600);//计算相差的小时数$op = $diff > 0 ? '+' : '-';return $op . str_repeat(0, 2 - strlen(abs($diff))) . abs($diff) . ':00';}}

一次因时区引发的血案相关推荐

  1. 波涛汹涌的黄金甲,一碗中药引发的血案!

    严重声明:网路转载 主要情节: 父王(周润发)说母后(巩利)身体虚寒,需要每天定时服用亲自配置的中药,已服用了几十年.而父王早就知道了母后和太子元祥(刘烨)之间的苟且之事,远征回宫后在其中药中加入一味 ...

  2. mysql backlog_一次优化引发的血案

    前些天一个Nginx+PHP项目上线后遭遇了性能问题,于是打算练练手,因为代码并不是我亲自写的,所以决定从系统层面入手看看能否做一些粗线条的优化. 首先,我发现服务的Backlog设置过小,可以通过s ...

  3. 第三方账号登陆的过程及由此引发的血案

    72agency · 2014/03/19 10:40 0x00 前言 第三方账号登陆也就是当你没有A网站的注册账号时,你可以使用该与A网站合作的第三方账号登陆A,在大多数情况下你会立即拥有与你第三方 ...

  4. 一个普通ERROR 1135 (HY000)错误引发的血案:

    一个普通ERROR 1135 (HY000)错误引发的血案: 今天接到测试人员反应,测试环境前端应用程序无连接mysql数据库,登录mysql服务器,查看错误日志,发现有如下报错: 点击(此处)折叠或 ...

  5. 一次 Druid 连接池泄露引发的血案!

    最近某个应用程序老是卡,需要重启才能解决问题,导致被各种投诉,排查问题是 Druid 连接池泄露引发的血案.. 异常日志如下: ERROR - com.alibaba.druid.pool.GetCo ...

  6. 线上 CPU100% 异常案例:一个正则表达式引发的血案

    前几天线上一个项目监控信息突然报告异常,上到机器上后查看相关资源的使用情况,发现 CPU 利用率将近 100%.通过 Java 自带的线程 Dump 工具,我们导出了出问题的堆栈信息. 我们可以看到所 ...

  7. 一场由过滤器Filter引发的血案

    一场由过滤器Filter引发的血案 事件起因 本来应该是下图的登录界面 变成了这样 What's the fuck????? 抓狂 原因 解决方法: 在过滤器中给资源文件开个绿色通道

  8. 一个由正则表达式引发的血案

    阿里妹导读:周末快到了,今天为大家送上一篇很有意思的小文章,具有提神醒脑之功效.作者是来自阿里巴巴LAZADA产品技术部的申徒童鞋. 血案由来 近期我在为Lazada卖家中心做一个自助注册的项目,其中 ...

  9. silverlight Image Source URI : 一个反斜杠引发的血案

    silverlight Image Source URI : 一个反斜杠引发的血案 Silverlight2 现在支持的Image格式有jpg和png,部分png编码也不支持,同时有些png在xaml ...

最新文章

  1. elasticSearch6源码分析(9)ActionModule
  2. python解压文件_Python压缩和解压缩文件(zip/unzip)详解
  3. C#自定义控件,在项目工具箱中加入自定义控件,调用自定义控件
  4. WebApi的调用-3.Basic验证
  5. python创建数据库计算机积极拒绝、无法连接_Python3 请求网页源码 目标计算机积极拒绝,无法连接...
  6. 外星人颜色python练习_在知乎上学Python爬虫
  7. JQuery全选反选 随其他checkbox自动勾选全选反选
  8. 试驾小程序_Linux如何成为Linux:试驾1993-2003发行版
  9. 基础知识—条件判断语句-if条件类型的语句
  10. java 蓝桥杯 算法训练 区间k大数查询(题解)
  11. django数据模型中关于on_delete的使用
  12. 面向能源效率的云计算
  13. 7-7 选民投票 (20分)(不区分大小写投票)
  14. LINUX最小系统安装过程中的Partition Disks分配问题
  15. JRebel LS client not configured解决方案
  16. CDN在前端开发中的作用
  17. CreateProcess 的正确关闭
  18. (二)基于STM32f103的I2C通信接口的EPPROM模块(24C256)读写程序详解
  19. 【锐捷交换】接入交换机配置DHCP Snooping + IP Source guard + ARP-check
  20. 技术汇总:第七章:三种验证方式

热门文章

  1. ICRA2017三篇论文泛读
  2. 最伟大的操作系统sinox和它的开发者sjm100的故事
  3. 23种设计模式(中英文对照)
  4. 「聊天宝」体验记录,生朋友聊天
  5. 第六章 PL/SQL与Oracle间交互
  6. 计算机视觉与深度学习(1)
  7. python3.8缩进的格式要求_python首行缩进_python中缩进
  8. 解决 html5 中 video 标签无法播放 m3u8
  9. HEIC图片格式如何快速转换呢?
  10. Android studio屡次停止运行