6.6.3 关于时间戳及相关问题

优质
小牛编辑
149浏览
2023-12-01

前言

时间戳是很多应用系统,特别是加密货币开发设计中非常重要的元素。各种语言都提供了相应的时间处理函数,以前直接拿来就用了,也没有发现什么问题。但是在时间处理上,开发语言核心模块提供的个别Api并没有完全延续人类习惯。在Javascript语言里,有一个Date类的函数就非常奇葩,网络上很多文档的举例都是错误的,因此需要简单总结一下。

这似乎不是什么大问题,但是从stackoverflow.com网站上关于时间处理的问题(见参考)及受关注程度来看,却是很多人遇到的普遍问题。另外,万一还有更多“非常规”的用法存在,那么这种看似很小的问题,可能就是致命的(逻辑上没问题,意识中没发现)。按照前面关于异常和错误的处理要求,这种错误应该是程序员自身的问题了,唯一的解决办法就是提高能力,让自己更加严谨、更加认真。所以,这篇文章就是查缺补漏的,提醒以后别在这个小问题上翻船。

问题再现

请猜想下面的语句应该输出哪一天:

new Date(Date.UTC(2005,7,8));

结果是: Mon Aug 08 2005 08:00:00 GMT+0800 (中国标准时间) , 即: 2005年8月8日

但是,看看这篇文档的解释 http://www.w3school.com.cn/jsref/jsref_utc.asp

其实,网上还有很多文档犯了这个错误。我本人也是在反复没有获得想要的结果情况下,才着手查询文档的。

时间戳的重要性

时间戳(timestamp),通常是一个字符序列,唯一地标识某一刻的时间,是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总毫秒数。中国有国家授时中心,因其守时监测功能而保障时间戳证书中的时间的准确性和不被篡改,具有法律效力。

2008年11月25日,深圳市龙岗区法院依据最高法院“知识产权司法保护活动月”的要求公开宣判知识产权纠纷案,其中“利龙湖”一案系国内首例时间戳技术司法应用案例,宣判后双方当事人均未提起上诉,该案判决书已经发生法律效力。

从技术上讲,时间戳可以理解为数据写入或修改时的时间点,使用毫秒表示,是一项数据存在性证明(Proof of existence)的唯一有效参数。加密货币把时间戳作为重要字段信息,每笔交易、每个区块都要记录时间戳。对于亿书(请参考白皮书)而言,更是版权信息一个不可或缺的元素。

不同产品对时间处理的需求

我在思考这样一个问题:作为加密货币,亿书对每笔交易都有时间戳,而且对版权保护,时间戳还是非常重要的字段。如果,亿书一个节点在中国,另一个在美国,那么这个时间处理该如何统一,相关的方法该如何选择?

从理论上分析,不同产品可能对时间处理的需求也不相同。

1.对于一般的应用系统,特别是没有国际化要求的产品,时间自然没有特殊要求,前端后台统一是很简单的事,只要避免上面的失误,正确使用时间相关的接口函数即可。

2.对于有国际化需求的产品,特别是加密货币这种去中心化的产品,全世界的用户都在使用,就要考虑两个方面的因素,一个是面向用户的时间本地化问题,另一个是面向产品后台的时间统一性问题。

时间处理基本原理

天下人的时间都是一样的。但是,相同时刻,不同时区的人们,所处的时空是不一样的。不同时区,人们对于时空的感受又是那么惊奇的相似,比如:除了南北极,大多数地方都是早上五六点钟太阳升起,中午12点左右艳阳高照,下午五六点钟日落西山,如果一个中国人去了美国(比中国晚13个小时),原本是中午艳阳高照,一看手表,竟然是午夜0点钟,还不得精神分裂啊,因此为了迎合人类习惯,自然就有了很多的本地化需求。

从编程的角度看,时间具备唯一、均匀和恒定的特点,只要使用统一确定的参考点,就不至于在系统中出现混乱。所以,对应上面的产品需求,对于不需要国际化的产品,只要使用本地化时间处理方法即可(参考点是本地),前后端统一,逻辑简单。而对于国际化的产品,那就使用世界时间(参考点是国际日期变更线<见参考>,UTC或GMT标准时间),然后在客户端面向用户进行本地化处理即可。

Javascript语言的Date对象

js提供了操作日期和时间的对象Date。Date对象是js语言的一中内部数据类型(类函数),要使用语法new Date()创建。创建了对象之后,就可以使用多种方法来操作它了。这里列出几个需要熟练掌握的细节供参考。

(1)构造函数

new Date();
new Date(milliseconds);
new Date(dateString);
new Date(year, month[, day[, hour[, minutes[, seconds[, milliseconds]]]]]);

简单一句话:新建对象可以使用0~7个参数,这些参数可以是字符串或数字。

(2)参数

需要注意的是:

  • Date时刻面向本地时间,参数代表的是本地时间,新建的对象代表的也是本地时间,输出的结果后面通常有(某国标准时间)等字样;
  • 0参数,代表当前日期和时间;
  • 1参数,若是数字milliseconds,应该是距离1970年1月1日午夜(UTC)的毫秒数,通常使用Date.UTC()函数来设定;若是字符串dateString,应该是时间格式,如:new Date('December 17, 1995 03:24:00');
  • 2~7参数,只能是数字。只有day从1开始,其他都是从0开始。因为周期通常采取取模运算,所以如果数值大于合理范围(如月份为13),数值会被自动调整。比如: new Date(2013, 13, 1)等于new Date(2014, 1, 1),它们都表示日期2014-02-01(注意月份是从0开始的)。
  • 如果需要世界时,要使用 new Date(Date.UTC(...)) 和相同参数。

(3)方法

关于方法要注意两点:

  • 静态方法:3个,都返回距离标准时间的毫秒数,分别对应构建函数三个形式,其中包括 Date.now()、Date.parse()(解析一个表示日期的字符串)、Date.UTC() (从2到7参数)。

  • 实例方法:4种,get类查询方法、set类设置方法、to类转换方法、of类,其中每种方法,都提供了本地和世界时两个参考系的方法,比如get[UTC]Date()方法。

特别注意的是:getTime()方法不分时区,返回Date对象的内部毫秒表示,这将是去伪存真的唯一途径,比如:如何判断不同时区是否同时呢,仅仅看这个方法的返回值就是了, 应该说在世界上不同时区,同时运行(new Date()).getTime()的返回值是一样的。

实践

编程里,需要处理的问题,无非是这样几个场景(下面的代码来自官方文档和亿书源码):

(1)记录时间

var today = new Date(); //本地当前日期和时间
var birthday = new Date("1995-12-17T03:24:00"); //1995年12月17日
var birthday = new Date(1995,11,17); //1995年12月17日

如果需要格式化,就可以使用上面提到的to为前缀的转换方法,例如:toLocaleDateString()、toLocaleString()等,如果不满意,就找找现成的扩展插件,比如moment.js。如果仍不满意,那就自己实现吧,也很简单。

(2)计算时间

主要原则是计算距离标准时间的毫秒数,然后再计算。因为这类方法本身就是基于世界时UTC的,并且在计算过程中可能使用减法处理时间段(差值),因此参考点抵消,可以不去考虑参考点的存在。

这方面被大量用到项目中,也有很多第三方包存在,比如:timeago.js之类的,下面简单举例:

a> 在浏览器上推荐使用 Date 对象:

var start = Date.now();

// 这里进行耗时的方法调用:
doSomethingForALongTime();
var end = Date.now();
var elapsed = end - start; // 运行时间的毫秒值

为了兼容IE8及以前的浏览器,可以做如下处理:

if (!Date.now) {
    Date.now = function() { return new Date().getTime(); }
}

b> 在Node.js等环境下,建议使用内建的getTime()方法:

// 设置初始时间
var begin = Date.UTC(2016, 6, 1, 0, 0, 0, 0); // 2016年7月1日,月份从0开始

// 获得当前时间
var now = new Date();
var elapsed = now.getTime() - begin.getTime(); // 毫秒值

c> 为了获得秒为单位的时间戳,建议使用:

Math.floor(Date.now() / 1000)

链接

本系列文章即时更新,若要掌握最新内容,请关注下面的链接

本源文地址: https://github.com/imfly/bitcoin-on-nodejs

电子书阅读: http://bitcoin-on-nodejs.ebookchain.org

参考

国际日期变更线

时间戳

JavaScript标准库 Date文档

How do you get a timestamp in JavaScript?