Date对象及其使用要点

前阵子做了一个倒计时的促销,需求很简单,在活动开始前显示活动即将开始的文字,活动中显示倒计时的时钟,在活动结束后显示活动结束的文字。快刀斩乱麻地做了一个本地Demo,用var now = new Date()获取现在的时间,用Date的构造函数创建了开始时间和结束时间,然后减一下判断一下就OK……

调试通过之后准备用Ajax把当前时间换成服务器时间,就搞定了……本以为是这样简单的事…后来发现自己 too young too simple

用原生js本可以用这种方式获得服务器时间:

1
2
3
4
5
6
7
8
9
10
11
var xhr = new XMLHttpRequest();  
if( !xhr ){
xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
xhr.open("HEAD",location.href,true);
xhr.onreadystatechange=function(){
if( xhr.readyState == 4 && xhr.status == 200 ){
alert(xhr.getResponseHeader("Date"));
}
}
xhr.send(null);

但是我在我们服务器上测试的时候发现,无论如何都获得不到正确的信息,查了一下 request 发现服务器返回值居然是403,据说是某些使用 Apache 配置的服务器,不能够仅请求响应头信息…因为我没有权限修改服务器设置(而且也不会2333),只好把HEAD改成GET(

服务器时间拿来是一段字符串,转成Date对象再一减不就好惹!可是这一转我就傻了…因为字符串拿来是UTC时间(格林威治标准时间),生成Date对象之后再console出来变成了北京时间(UTC+8),但是我的目标用户是日本用户(也就是UTC+9),服务器在美国西海岸呢(UTC-5的样子)…………

即刻。卒。

后来跟小伙伴讨论到最后的结论只是基础知识掌握得太糟糕(从没用过Date对象鬼记得住那么多有没有的!),主要重点其实说下来很简单:

  • Date对象存储的数据是从1970年1月1日0点0分0秒(UTC)到某一时刻的毫秒数,不管你在哪个时区或者设置成什么时区,这个毫秒数是不会变的。
  • new Date(string)或者new Date(year, month[, day[, hour[, minutes[, seconds[, milliseconds]]]]])所创建的Date对象,未经指定都是以本地时间来创建的。

在这个需求里,上面两个要点就是,获得当前时间目标时间(开始和结束时间)的时间戳(就是上面说的毫秒数),因为时间戳都是相对UTC时间而言的,所以根本不需要考虑时区时差这种让人想死的问题。

目标时间本身是使用字符串记录的,转成数组并生成Date对象的时候,为了避免当地时间对生成结果的影响,于是使用了Date.UTC()创建,然后再补上时差。比如说:

  1. 我想设置时间 2015 年 8 月 15 日 21 点整,于是我先输入new Date(Date.UTC(2015,7,15,21))(月份的取值是从0开始的),得到的时间戳是1439672400000。
  2. 但是我实际需要的时间应该是9个小时之前的时间(日本时间晚上9点,UTC时间是中午12点,UTC时间晚上9点,日本时间就已经是第二天早上6点了),也就是32400000毫秒之前。
  3. 所以我需要用第一个时间,减去它们的时间差。
  4. 服务器时间本身就是UTC时间,所以直接转成Date对象就好,不需要额外操作。

可喜可贺。

接下来就是些细枝末节的事,比如服务器时间与真实时间的时间差,因为正常情况下应该只差几百毫秒,不比我用字符串新建Date对象的误差小多少,所以忽略掉。再比如性能处理,因为不需要时间校正,也就不需要反复请求,于是在活动开始之前,算出当前时间与开始时间的时间差,setTimeout()一下就好。

PS: var now = new Date(),now + 1 会把 now 转成字符串,而 now - 1 会把 now 转成数值,在setTimeout()之后要对原来的时间戳进行操作,如果没有显式转换过的话,要注意一下…

本部落格采用DISQUS评论系统,如果您无法见到留言框,可前往我的GitHub微博提Issue(留言)。
为您带来了不便我也很尴尬╮(╯_╰)╭