JS 原生对象 Date 和 HTML 标签 input[type="datetime"] 的坑

2,653 阅读5分钟
原文链接: coderge.com

今天接到了一个需求,在某个页面内用户选择一个DateTime后,服务端保存这个DateTime,当用户再次进入这个页面,直接显示之前选择的DateTime(如果服务端没有返回,则直接显示当前DateTime)。 这个需求听起来并没有什么难度,于是就风风火火开始coding……(此处省略800字,请自行脑补血腥的coding场面)。

DateTimePicker使用原生控件以节约性能,并且可以根据不同的系统有不同的本地化样式,反正就看浏览器是怎么处理的了。

<input name="datetime" id="J_datetime_picker_1" type="datetime" />
<input name="datetime" id="J_datetime_picker_2" type="datetime-local" />

根据w3school,DateTimePicker有datetimedatetime-local两种type,但根据我的试验,前者只能展现出一个text类型的输入框,Don't konw why 。。。

之后要做的就很简单了,当用户选择了一个DateTime之后,获取它并展示它并把它存起来。然后我就这么做了……

const dateTimePicker = document.querySelector('#J_datetime_picker');
const dateTime = dateTimePicker.value;
// dateTime <==> '2017-02-22T18:40'
const dateTimeString = dateTime.replace('-', '年')
                                .replace('-', '月')
                                .replace('T', '日 ');
// dateTimeString <==> '2017年02月22日 18:40'
// dateTime用来存,dateTimeString用来显示

看到这里就不免感觉有些奇怪了,平时我们看到的时间对象并不长这样啊?!!!

new Date();
// Wed Feb 22 2017 18:40:00 GMT+0800 (CST)

new Date('1487760000000');
// Invalid Date

Date.now();
// 1487760000000

无论如何也没能通过Date对象构造出一个带'T'的时间字符串…… 于是老老实实查阅资料~

RFC2822 标准日期字符串

YYYY/MM/DD HH:MM:SS ± timezon(时区用4位数字表示) // 如: 1992/02/12 12:23:22+0800

ISO 8601 标准日期字符串

YYYY-MM-DDThh:mm:ss ± timezone(时区用HH:MM表示)

1997-07-16T08:20:30Z // “Z”表示UTC标准时区,即"00:00",所以这里表示零时区的1997年7月16日08时20分30秒 //转换成位于东八区的北京时间则为1997年7月17日16时20分30秒

1997-07-16T19:20:30+01:00 // 表示东一区的1997年7月16日19时20秒30分,转换成UTC标准时间的话是1997-07-16T18:20:30Z

由于业务的需求,程序中存储的是时间Date对象,而不是和服务端交互用的字符串,然后就发现了一个新的问题。

// 之前存在服务端的DateTime字符串为'2017-02-22T18:40'

new Date('2017-02-22T18:40');
// Thu Feb 23 2017 02:40:00 GMT+0800 (CST)

// 改用RFC2822标准日期字符串

new Date('2017/02/22 18:40');
// Wed Feb 22 2017 18:40:00 GMT+0800 (CST)

可以看到,在未指定时区的前提下,RFC2822返回结果是以当前时区的零点为准,而ISO8601返回结果则会以UTC时间为标准进行解析。

于是,对于从DateTimePicker中取出的时间串,在转换成Date对象前需要对应当前时区进行转换。

// 从DateTimePicker中取出的时间字符串为'2017-02-22T18:40'
const inputValue = '2017-02-22T18:40';
// 转换成正确的Date对象
const dateTime = new Date(inputValue + '+08:00');

而DateTimePicker绑定值进行回显时也需要注意:

<!-- 这种写法是不被支持的,虽然value是一个标准的ISO8601日期字符串 -->
<input type="datetime-local" name="datetime" value="2017-02-22T18:40+08:00" />

<!-- 这种写法是正确的,会直接按照绑定的时间显示,不考虑时区 -->
<input type="datetime-local" name="datetime" value="2017-02-22T18:40" />

郁闷极了,同是表示日期时间的对象(或对象字符串),竟然有如此多种表达形式,并且在各种形式上也没有达成统一。

除此之外,Date对象的一些方法也是匪夷所思:

方法名 作用 2017年2月22日 18:40(星期三)
getFullYear 从 Date 对象以四位数字返回年份 2017
getMonth 从 Date 对象返回月份 (0 ~ 11) 1
getDate 从 Date 对象返回一个月中的某一天 (1 ~ 31) 22
getHours 返回 Date 对象的小时 (0 ~ 23) 18
getMinutes 返回 Date 对象的分钟 (0 ~ 59) 40
getSeconds 返回 Date 对象的秒数 (0 ~ 59) 0

可以看到,年月日时分秒中,只有月份是不正常的,我们在处理Date对象展示年月日信息时还需要对月份进行单独处理。

const dateObj = new Date('2017/02/22 18:40');
const year = dateObj.getFullYear();
const month = dateObj.getMonth() + 1;
const date = dateObj.getDate();
const hour = dateObj.getHours();
const minute = dateObj.getMinutes();
const tmp = {
  year: year.toString(),
  month: month < 10 ? '0' + month : month.toString(),
  date: date < 10 ? '0' + date : date.toString(),
  hour: hour < 10 ? '0' + hour : hour.toString(),
  minute: minute < 10 ? '0' + minute : minute.toString()
}
const {year, month, date, hour, minute} = tmp;
return `${year}年${month}月${date}日 ${hour}:${minute}`;
// 2017年02月22日 18:40

作为一个小前端,心有余而力不足,只能盼望着早日统一标准,填坑愉快吧~