来源:[https://www.cnblogs.com/Don/p/9262368.html](https://www.cnblogs.com/Don/p/9262368.html)
在PHP开发中,我们经常会在时间问题上被搞糊涂,比如我们希望显示一个北京时间,但是当我们使用date函数进行输出时,却发现少了8个小时。几乎所有的php猿类都必须对php中几个重要的时间转换等方法进行研究。本文就来梳理这些问题。
时间戳(timestamp)
GMT
在时间戳这个点上,它是一个概念,而不是具体的编程问题,是计算机世界通用的一种约定。时间戳是指格林尼治时间(GMT)1970年01月01日00时00分00秒到当前时间的总秒数。
GMT(也被称为世界时)是固定为本初子午线经过地区的时间,因此被作为时间参照物。
UTC
协调世界时(UTC)和GMT一样都是一种时间的参照物,但是他们的计算方法不同UTC是以原子时秒长为基础,在时刻上尽量接近于世界时的一种时间计量系统,从精度上讲,更加精确(自然也比GMT更精确),因此被称世界统一时间,世界标准时间,国际协调时间。
Unix时间戳
Unix时间戳是在计算机领域才有的,每一台电脑(服务器)在生产的时候,将GMT/UTC的1970年01月01日00时00分00秒作为起始值进行计算,得到的总秒数就是这个Unix时间戳。至于是GMT还是UTC意义并不大,因为GMT和UTC的1970年01月01日00时00分00秒是一致的,起点一致的情况下,运行的秒数也是一致的。
为什么要时间戳?因为从0开始运行的秒数永远相等,即使出现润秒,也并不影响时间戳。
在php中,可以通过time函数获取时间戳:
time();
但是你应该明白,time()获取的是,当前这台电脑(服务器)上的Unix时间戳。两台电脑可能这个时间戳并不相同,有的甚至相差几十秒。从理论上讲时间戳应该是一摸一样的,但是由于不同的电脑硬件出厂时的设定不同,也会导致GMT/UTC起始值稍有差异,甚至在计算每一秒时也有可能存在差异,这台机器上一秒的时间比另一台要长也是有可能的,时间久了,积累下来的时间差就会体现出来。但是,这种时间差一般不会超过几秒钟。
时区(Time Zone)
但是上面的time()的表述并不准确,因为我们在实践中经常遇到time()得到的值并不是我们想要的。对应的是,date()函数得到的值,也可能出乎我们意料。
什么是时区呢?也就是以GMT/UTC为参照物的时间偏移。
以GMT为参照物的时区
在传统的教材里,全球被划分为24个时区,首先基于经度,其次按照国家或地区,将每一个地区划分到某一个时区,这样可以避免时间上的混乱。在24时区划分之前,世界上的时间换算并没有准确的参照,比如中国人去英国,只能问当地人现在几点,然后拨自己的表来对。而当时区划分之后,中国人到了英国,只需要拨慢8小时即可。在时区划分之前,英国人跟中国人的时间可能并不是严格的8小时之差。
但为了照顾到同一个国家内时间的统一,大部分国家规定自己属于同一个时区,比如中国,统一规定为东八区,这样中国东部和西部可以采用同一个时间。毕竟没有必要大家一定要在早上6点看到日出,沿海城市5点看,乌鲁木齐9点看,并不影响大家的正常作息。
在php中,提供了大量的地区作为时区切换的标准,例如:
date_default_timezone_set('Asia/Shanghai');echo date('Y-m-d H:i:s'); // 获得的是上海所在时区的时间
注意:PRC是中国的地区时标志,并不在Asia中,而是在Others里面找。
以UTC为参照物计算时区
但随着UTC取代GMT成为世界标准时后,时区的计算开始使用UTC作为标准。UTC+8代表东八区,UTC-11代表西十一区。
不过随着精度需求的提升,按大时区计算已经不能满足需求,0.5个时区也被普遍使用,比如UTC+7.5。
在PHP中,我们可以采用这种方式来切换时区。比如:
date_default_timezone_set('UTC');echo date('Y-m-d H:i:s'); // 获取的是0时区时间
时区给PHP带来的影响
我们上面给出的代码并没有什么实际意义,因为你还不知道为什么要这样去做。实际上,php在使用date函数的时候,会依照所在时区去进行计算。
例如,你的服务器是放在英国的,而服务器的默认配置php.ini中没有规定时区,那么php就会以操作系统默认是时区作为时区进行输出,这就会导致这台服务器上的date()函数输出的时间是以UTC+0作为时区的,如果你的用户在中国,那么网站的访客看到的时间就会少8个小时。
而上面使用date()进行输出的时间,就是我们所说的本地时间。
本地时间,其实是指服务器上的程序输出时间,date函数输出的时间依托Unix时间戳和时区,因此它一定是一个不准确的时间,因为Unix时间戳基本上都是不准确的,但是这个不准确是可以忽略的,严重的是时区的偏差。
造成php输出时间混乱的原因总结起来:
使用默认的date函数的输出值
在保存时间的时候使用调整过时区的时间,而输出时又调整了时区
第一点比较容易理解,比如默认存进去的时候存入的是time(),输出的时候使用date(),time()是没有错的,但是date()在输出的时候,时区和当前访客的时区对不上,从而导致输出内容的错误。
如何在PHP中保证输出时间的准确性?
我们想的更多的是如何保证时间的准确性问题。这要从多方面去考虑,输入输出的一致性与非一致性是一个很大的挑战,你需要把握好全局关系。
1.php.ini配置文件中规定时区
从php5.1.0开始,php.ini配置文件中支持设置一个date.timezome的值来规定默认的时区,找到它,并改为:
date.timezone = PRC
当然,PRC也可以用php官方给出的列表中的其他时区代表值表示。
这种配置的好处是,可以在所有的php代码中生效,坏处是灵活性差,而且大部分主机并不直接支持php.ini配置。
2. ini_set('date.timezone')
在php代码开头,可以使用ini_set函数来临时修改一些php的默认配置,可以:
ini_set('date.timezone','Asia/Shanghai');
这种方法的好处是比较灵活,需要配置时区的代码里才使用,把这个配置放置在一个共享文件里,可以使所有引用这个文件的php脚本都获得这个配置。坏处是有的主机不支持ini_set。
3.date_default_timezone_set
和ini_set函数一样,date_default_timezone_set函数也可以临时修改php配置。
date_default_timezone_set('Asia/Shanghai');
4.自己计算
当你在输出日期的时候,可以考虑自己调整时区,然后进行计算,将计算的结果格式化为日期再输出。首先,我们要搞清楚哪些是可变哪些是不可变。
可变:date()不可变:time()、gmdate()
当你在输出一个日期的时候,如果使用date,就是可变的,但如果使用gmdate()就是不可变的,gmdate()永远把时区当做是UTC+0,即使你通过前面三种方法临时修改了时区,也不会影响gmdate的输出结果,而这个时候,其实你又知道你的访客所在的时区,所以,你可以自己计算一下:
// 方法1date('Y-m-d H:i:s',strtotime(gmdate('Y-m-d H:i:s').' +8 hours'));// 方法2(推荐)gmdate('Y-m-d H:i:s',time() + 8*3600);
使用上面的两个方法,无论你的服务器处于什么时区,无论你是否使用date_default_timezone_set设置了新的临时时区,都不会影响结果,因为gmdate永远以UTC+0作为参照,根本不会理会你新设置的时区。甚至,你把你的这段代码,从非洲的服务器搬到中国的服务器上,它的结果也还是一样(忽略timestamp的微小误差)。
有一个有趣的现象是,我们可以通过一个动态的数字来控制date()是时区,而无需去设置时区,比如:
date('Y-m-d H:i:s',time() + n*3600)
其中的n则是时区,东八区就是+8,西五区就是-5。而我们却可以找出这个动态的n值,它和时区时时相关:date('Z')
date('Z')是一个军事级别的应用,它用于计算以秒为单位的时区偏移量,比如东八区,它的值就是8*3600,我们可以在time()的基础上减去这个值,得到一个比当前时间戳少时区偏移量的值,这个值在实际中没有任何意义,它不代表任何时间戳(或者说是当前时间n小时之前的时间戳),但如果我们再对这个时间戳进行date运算时,date会把n时区的偏移量加回来,这样就得到了一个固定的UTC+0的日期时间:
$gmt_date = date('Y-m-d H:i:s',time() - date('Z'));
它的效果其实和gmdate('Y-m-d H:i:s')相同,但算法上更加复杂。
同样的道理,我们以UTC+0作为基准,增加这个偏移量,反而可以得到我们想要的时区所在的时间:
$local_time = gmdate('Y-m-d H:i:s',time() + date('Z'));
但这和$local_time = date('Y-m-d H:i:s');没有任何结果上的区别。
选择你合适的时间进行保存
在前面的分析里面,你看到有一种情况比较乱,就是使用了调整时区后的时间进行保存,但是显示的时候,又进行时区调整,这导致显示错误。
推荐的一种时间保存方案,是只保存timestamp,也就是time(),它的值是固定的,不随着时区的调整而改变,即使更换了服务器,它的误差也很小,所以有利于今后将程序分发部署到不同的服务器上面。
而在自己显示的时候,可以确定一个方法,比如上面推荐的方法2作为输出:
function st_date($format,$timestamp = false) {
$timestamp = is_numberic($timestamp) ? $timestamp : time();
return gmdate($format,$timestamp + 8*3600);
}
这样就可以保证这段代码无论在哪里,都可以输出东八区的时间。
- 常见功能
- 第三方授权登录
- 邮件发送
- 简易聊天室
- 获取各国汇率
- PHP获取服务器硬件指标
- 数据上报之
- web开发
- 开发规范
- 前端
- 踩坑
- 将footer固定在底部
- bootstrap
- Metronic
- 用到的jquery插件
- bootstrap-hover-dropdown
- jquery.slimscroll
- jquery.blockui
- bootstrap-switch
- js.cookie
- moment
- bootstrap-daterangepicker
- morris
- raphael
- jquery.waypoints
- jquery.counterup
- select2
- 取值和设置默认值
- vue
- axios
- 浏览器
- 谷歌浏览器
- 谷歌插件
- layui
- layui-表格
- layui-表单
- layui-弹窗
- layui-分页
- 后端
- 操作系统
- linux
- 用户管理
- 文件管理
- 目录管理
- 压缩和解压缩
- 进程查看
- 端口查看
- 开机自启动服务
- 定时任务
- shell脚本
- 杀掉运行超过指定时长指定服务的进程
- 获取服务器使用状态
- bash-shell连接socket
- 自定义快捷命令
- centos-踩坑
- 防火墙
- 软件
- yum
- vim
- screen
- window
- 语言
- PHP
- 配置优化
- 框架
- thinkphp5.1+
- think命令行
- laravel6.+
- 维护模式
- 根据环境读取不同配置
- laravel6.+采坑
- laravel坑位
- 数据库事务
- 任务调度
- 文件权限问题
- 增强框架
- larvel:elastic-search
- 图形验证码
- laravel获取ip
- 函数
- strtotime
- 正则匹配
- 类
- 接口类与抽象类
- 类相关的关键字 - abstract
- 类相关的关键字 - interface
- PHP有关类的调用方式"->"与"::"的区别
- 扩展
- 问题归纳
- json_encode和json_decode
- 字符串的运算
- curl
- 优化php效率
- 数组相加合并与array_merge
- 时区转换
- 不常用特性
- php反射
- 包管理器-composer
- GuzzleHttp
- Python
- Go
- 数据库
- Redis
- 安装
- 本地化-数据备份
- php-redis操作
- Mysql
- mysql-命令集合
- 设置终端可访问
- 数据库设计
- 用户基础信息表
- 踩坑集合
- mysql-2002
- mysql-2054
- 优化策略
- mysql-密码验证插件
- 一些牛逼的sql查询
- topN
- 无限级分类
- Memcache
- MongoDb
- 安装mongo-server
- 安装php-mongodb扩展
- 在laravel中使用mongoDB
- 客户端软件
- Hbase
- Elasticsearch
- elastic-search
- restfulApi操作es
- web服务器
- 1.nginx
- 配置语法规则
- 配置详解
- rewrite规则
- request_filename
- 2.apache
- 功能设计
- 加密解密
- Base64
- 对亚马逊SKU加密
- 兼职项目中的加解密
- 腾讯外包时的加密
- 接口设计
- 接口限流设计
- 分库分表
- 遍历展示文件目录结构
- 时区换算
- 文件切割
- 解析xml字符串
- 项目
- 博客后台管理
- 亚马逊广告API
- 官方指引文档
- 开发人员中心
- 应用商店
- 第三方库
- 申请API邮件记录
- 亚马逊MWS
- 付款报告
- 乱码
- 亚马逊管理库存报告
- 报告
- 商品
- 入库
- 履行
- 出库
- 财务
- 订单
- 异步任务处理
- 集群如何同步代码
- 基本开发流程
- 文档管理
- showdoc
- 运行环境
- 开发环境
- vagrant
- windows上配置安装
- vagrant安装插件缓慢
- 更换ssh默认端口映射
- 设置x-shell密码登录
- 使用市场的box-homestead
- homestead-7: Box 'lc/homestead'
- 常见问题
- 虚拟环境reboot
- 突然无法使用
- phpStudy
- wamp
- 压测性能
- VPN
- vultr
- 凌空图床
- 宝塔
- 自动化部署
- 版本管理软件钩子
- 线上环境-LNMP
- centos7
- nginx
- mysql
- mysql开机自启
- mysql-更换默认端口
- datetime字段类型默认值
- php
- php扩展安装
- redis
- swoole
- gd
- BCMath
- igbinary
- zstd
- 包管理器:composer
- 优化性能
- nodejs
- 更新gcc版本
- 版本控制
- git
- 常用命令
- gitlab
- 版本管理规范
- 使用阿里云创建远程仓库
- git自动化部署
- svn
- 忽略指定文件
- 拉取代码
- 自动化运维
- jekins
- 容器
- 集群
- 架构设计
- 设计原则
- 阅读参考
- 代码规划
- 架构实战
- 服务治理
- 权限控制设计
- 具体设计
- 计划
- 疑问知识点
- 读书笔记
- 高性能Mysql
- TCP-IP详解-卷一:协议
- 思考
- php如何实现并发执行
- 对接调用设计
- 如何在浏览器上实现插件
- 如何设计一个app结合业务告警
- mysql的where查询没有用到索引
- 为啥in查询比循环嵌套sql的查询还要慢
- 使用git来创建属于自己的composer包
- 翻页获取数据的时候又新增了数据
- 安全思路
- 月报
- PHP ?? 和 ?: 的区别
- PHP异步执行
- redis集群的目标是什么
- 大文件数据处理
- 性能瓶颈分析
- 命令行里输出带颜色的字体
- 面试问题合集
- 基础
- 安全
- 算法
- 冒泡排序
- 快速排序
- 二分法查询数组指定成员
- 字符查找匹配
- 令牌桶
- 漏桶
- 计数器
- 代理
- 协议
- http
- 状态码
- tcp
- udp
- Oauth2.0
- 设计模式
- 单例模式
- 适配器模式
- 工厂模式
- 观察者模式
- 流程化
- 地址栏输入网址到返回网页的流程
- 题目收集
- 工具
- rabbitMq
- rabbitMQ用户管理
- 生产者
- 消费者
- 支持TP5.*的think-queue
- 消息丢失
- 消费者报错
- rabbitMQ配置优化
- 磁盘满载导致服务挂掉
- PHP类库
- rabbitMQ踩坑
- navicat
- vscode
- phpstorm
- 激活码
- markdown
- PHP自定义类库
- 工具类
- 领导力
- 任务分配
- 代码组织
- 不要重复
- 避免污染
- 接口定义规范
- 小业务需求
- 获取充值面额组成
- 监控服务器CPU和内存
- shell脚本版本