当前位置: 首页 > news >正文

如何正确地处理时间

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

输入图片说明

这篇博客转载自廖雪峰的官方网站:

http://www.liaoxuefeng.com/article/0014132675721847f569c3514034f099477472c73b5dee2000

一直在想这个问题的怎么解决比较好,无意间看到作者写的和自己想的相似,在此记录下来,方便以后查阅

———————————————————————————————————————————————

日期和时间在程序中应用广泛,每种程序开发语言都自带处理日期和时间的相关函数,很多开发者把日期和时间存入数据库中,但是,一旦涉及到跨时区的日期和时间的处理时,大多数开发者根本就不明白如何正确地处理日期和时间。

首先,我们来看大部分的程序都是这么创建当前时间并存入数据库的:

Date date = new Date();
store2db(date);

这么做的问题在于,数据库的DateTime类型没有时区(time zone)信息,因此,存入的是本地时间,并且丢掉了时区信息。如果你把数据库服务器的时区改了,或者把应用服务器的时区改了,读出来的日期和时间就是错误的。如果以Timestamp类型存储,各数据库的实现也不相同,有的进行了内部时区自动转换,而且,存储的时间不超过2037年。

如果应用服务器的时区和数据库服务器的时区不一致,你无法确定数据库驱动程序会不会自动帮你转换。

大多数开发者遇到这个问题不是去探索正确的解决方法,而是自作聪明地在存入数据库之前先来个“调整”,比如把当前时间减掉8小时,在显示的时候遇到不正确的时间时,又来个“调整”,以“负负得正”的方式来掩盖错误。在遇到夏令时的时区时,还需要写更复杂的代码来调整小时。

正确的做法是先理解时间和时区的概念。


时区的概念

之所以有时区的概念是因为住在地球上不同地方的人看到太阳升起的时间是不一样的。我们假设北京人民在早上8:00看到了太阳刚刚升起,而此刻欧洲人民还在夜里,他们还需要再过7个小时才能看到太阳升起,所以,此刻欧洲人民的手表上显示的是凌晨1:00。如果你强迫他们用北京时间那他们每天看到日出的时间就是下午3点。 也就是说,东8区的北京人民的手表显示的8:00和东1区欧洲人民手表显示的1:00是相同的时刻:

"2014-10-14 08:00 +8:00" = "2014-10-14 01:00 +1:00"

但是,在计算机中,如果用本地时间来存储日期和时间,在遇到时区转换的问题上,即便你非常清楚地知道如何转换,也非常麻烦,尤其是矫情的美国人还在采用夏令时。

所以我们需要引入“绝对时间”的概念。绝对时间不需要年月日,而是以秒来计时。当前时间是指从一个基准时间(1970-1-1 00:00:00 +0:00),到现在的秒数,用一个整数表示。

当我们用绝对时间表示日期和时间时,无论服务器在哪个时区,任意时刻,他们生成的时间值都是相等的。所有编程语言都提供了方法来生成这个时间戳,Java和JavaScript输出以毫秒计算的Long型整数,Python等输出标准的Unix时间戳,以秒计算的Float型浮点数,这两者转换只存在1000倍的关系。

实际上,操作系统内部的计时器也是这个标准的时间戳,只有在显示给用户的时候,才转换为字符串格式的本地时间。


正确的存储方式

基于“数据的存储和显示相分离”的设计原则,我们只要把表示绝对时间的时间戳(无论是Long型还是Float)存入数据库,在显示的时候根据用户设置的时区格式化为正确的字符串。

数据的存储和显示相分离是非常基本的设计原则,却常常被大多数开发人员忽略。举个例子,在Excel中编写一个表格,表格的数据可视为数据的存储格式,你可以把表格的数据以柱状图或饼图表示出来,这些不同的图表是数据的不同显示格式,存储数据的时候,我们应该存储表格数据,绝不应该存储柱状图等图片信息。

HTML和CSS也是数据的存储和显示相分离的设计思想。

所以,数据库存储时间和日期时,只需要把Long或者Float表示的时间戳存到BIGINT或REAL类型的列中,完全不用管数据库自己提供的DATETIME或TIMESTAMP,也不用担心应用服务器和数据库服务器的时区设置问题,遇到Oracle数据库你不必去理会with timezone和with local timezone到底有啥区别。

读取时间时,读到的是一个Long或Float,只需要按照用户的时区格式化为字符串就能正确地显示出来:

// Java:
long t = System.currentTimeMillis();
System.out.println("long = " + t);

// current time zone:
SimpleDateFormat sdf_default = new SimpleDateFormat("yyyy-MM-dd HH:mm");
System.out.println(sdf_default.format(t));

// +8:00 time zone:
SimpleDateFormat sdf_8 = new SimpleDateFormat("yyyy-MM-dd HH:mm");
sdf_8.setTimeZone(TimeZone.getTimeZone("GMT+8:00"));
System.out.println("GMT+8:00 = " + sdf_8.format(t));

// +7:00 time zone:
SimpleDateFormat sdf_7 = new SimpleDateFormat("yyyy-MM-dd HH:mm");
sdf_7.setTimeZone(TimeZone.getTimeZone("GMT+7:00"));
System.out.println("GMT+7:00 = " + sdf_7.format(t));

// -9:00 time zone:
SimpleDateFormat sdf_la = new SimpleDateFormat("yyyy-MM-dd HH:mm");
sdf_la.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));
System.out.println("America/Los_Angeles = " + sdf_la.format(t));

输出:

long = 1413230086802
2014-10-14 03:54
GMT+8:00 = 2014-10-14 03:54
GMT+7:00 = 2014-10-14 02:54
America/Los_Angeles = 2014-10-13 12:54

基于绝对时间戳的时间存储,从根本上就没有时区的问题。时区只是一个显示问题。额外获得的好处还包括:

  • 两个时间的比较就是数值的比较,根本不涉及时区问题,极其简单;
  • 时间的筛选也是两个数值之间筛选,写出SQL就是between(?, ?);
  • 显示时间时,把Long或Float传到页面,无论用服务端脚本还是用JavaScript都能简单而正确地显示时间。

你唯一需要编写的两个辅助函数就是String->Long和Long->String。String->Long的作用是把用户输入的时间字符串按照用户指定时区转换成Long存进数据库。

唯一的缺点是数据库查询你看到的不是时间字符串,而是类似1413266801750之类的数字。

转载于:https://my.oschina.net/lvhuizhenblog/blog/798421

相关文章:

  • 博为峰Java技术题 ——JavaSE Java JDBC数据库查询代码实例
  • 台湾富士康捐10亿助力清华
  • 缓存与缓冲
  • 一个JAVA程序员的成长经历
  • 记萌新赛的命题过程与踩气球过程
  • Mysql的连接状态
  • java集合类(二)
  • 对OpenCV中Haar特征CvHaarClassifierCascade等结构理解
  • 《代码整洁之道》读书笔记
  • ArcEngine标注和注记(-)
  • cucumber安装可能发生的错误
  • Java获取本地IP地址
  • vue的路由传值query方法
  • Linux 安装python3.7.0
  • TensorFlow教程03:针对机器学习初学者的MNIST实验——回归的实现、训练和模型评估...
  • ES6指北【2】—— 箭头函数
  • [笔记] php常见简单功能及函数
  • 【108天】Java——《Head First Java》笔记(第1-4章)
  • 【跃迁之路】【477天】刻意练习系列236(2018.05.28)
  • android 一些 utils
  • canvas绘制圆角头像
  • css系列之关于字体的事
  • es6
  • Git的一些常用操作
  • JS笔记四:作用域、变量(函数)提升
  • JS学习笔记——闭包
  • Material Design
  • nginx 配置多 域名 + 多 https
  • Python 使用 Tornado 框架实现 WebHook 自动部署 Git 项目
  • Python连接Oracle
  • Spring Cloud Alibaba迁移指南(一):一行代码从 Hystrix 迁移到 Sentinel
  • 聊聊spring cloud的LoadBalancerAutoConfiguration
  • 微服务框架lagom
  • 阿里云移动端播放器高级功能介绍
  • ​软考-高级-系统架构设计师教程(清华第2版)【第15章 面向服务架构设计理论与实践(P527~554)-思维导图】​
  • !!Dom4j 学习笔记
  • #我与Java虚拟机的故事#连载13:有这本书就够了
  • (¥1011)-(一千零一拾一元整)输出
  • (31)对象的克隆
  • (C语言)共用体union的用法举例
  • (ZT)薛涌:谈贫说富
  • (附源码)springboot宠物管理系统 毕业设计 121654
  • (附源码)springboot青少年公共卫生教育平台 毕业设计 643214
  • (附源码)ssm高校志愿者服务系统 毕业设计 011648
  • (转)【Hibernate总结系列】使用举例
  • (转)利用PHP的debug_backtrace函数,实现PHP文件权限管理、动态加载 【反射】...
  • (转)人的集合论——移山之道
  • (转)原始图像数据和PDF中的图像数据
  • ***通过什么方式***网吧
  • .Mobi域名介绍
  • .NET/C# 获取一个正在运行的进程的命令行参数
  • .NET构架之我见
  • .one4-V-XXXXXXXX勒索病毒数据怎么处理|数据解密恢复
  • ??myeclipse+tomcat
  • @WebService和@WebMethod注解的用法