成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

關(guān)于“時(shí)間”的一次探索

fuyi501 / 2340人閱讀

摘要:示例指定了也就是零時(shí)區(qū),顯示的時(shí)間會(huì)加上本地時(shí)區(qū)的偏移小時(shí)。其實(shí)就是上面顯示時(shí)間時(shí)使用的形式除了能表示基本信息,還可以表示星期,但是一點(diǎn)也不容易讀,不建議使用。

原文對(duì) ISO 8601 時(shí)間格式中 T 和 Z 的表述有一些錯(cuò)誤,我已經(jīng)對(duì)原文進(jìn)行了一些修訂,抱歉給大家造成誤解。

最近使用 sequelize 過(guò)程中發(fā)現(xiàn)一個(gè)“奇怪”的問(wèn)題,將某個(gè)時(shí)間插入到表中后,通過(guò) sequelize 查詢出來(lái)的時(shí)間和通過(guò) mysql 命令行工具查詢出來(lái)的時(shí)間不一樣。非常困惑,于是研究了下,下面是學(xué)習(xí)成果。

基本概念

我們先來(lái)介紹一些可能當(dāng)年在地理課上學(xué)習(xí)過(guò)的基本概念。

說(shuō)起來(lái),時(shí)間真是一個(gè)神奇的東西。以前人們通過(guò)觀察太陽(yáng)的位置來(lái)決定時(shí)間(比如:使用日晷),這就使得不同經(jīng)緯度的地區(qū)時(shí)間是不一樣的。后來(lái)人們進(jìn)一步規(guī)定以子午線為中心,向東西兩側(cè)延伸,每 15 度劃分一個(gè)時(shí)區(qū),剛好是 24 個(gè)時(shí)區(qū)。然后因?yàn)橐惶煊?24 小時(shí),地球自轉(zhuǎn)一圈是 360 度,360 度 / 24 小時(shí) = 15 度/小時(shí),所以每差一個(gè)時(shí)區(qū),時(shí)間就差一個(gè)小時(shí)。

最開(kāi)始的標(biāo)準(zhǔn)時(shí)間(子午線中心處的時(shí)間)是英國(guó)倫敦的皇家格林威治天文臺(tái)的標(biāo)準(zhǔn)時(shí)間(因?yàn)樗鼊偤迷诒境踝游缇€經(jīng)過(guò)的地方),這就是我們常說(shuō)的 GMT(Greenwich Mean Time)。然后其他各個(gè)時(shí)區(qū)根據(jù)標(biāo)準(zhǔn)時(shí)間確定自己的時(shí)間,往東的時(shí)區(qū)時(shí)間晚(表示為 GMT+hh:mm)、往西的時(shí)區(qū)時(shí)間早(表示為 GMT-hh:mm)。比如,中國(guó)標(biāo)準(zhǔn)時(shí)間是東八區(qū),我們的時(shí)間就總是比 GMT 時(shí)間晚 8 小時(shí),他們?cè)诹璩?1 點(diǎn),我們已經(jīng)是早晨 9 點(diǎn)了。

但是 GMT 其實(shí)是根據(jù)地球自轉(zhuǎn)、公轉(zhuǎn)計(jì)算的(太陽(yáng)每天經(jīng)過(guò)英國(guó)倫敦皇家格林威治天文臺(tái)的時(shí)間為中午 12 點(diǎn)),不是非常準(zhǔn)確,于是后面提出了根據(jù)原子鐘計(jì)算的標(biāo)準(zhǔn)時(shí)間 UTC(Coordinated Universal Time)。

一般情況下,GMTUTC 可以互換,但是實(shí)際上,GMT 是一個(gè)時(shí)區(qū),而 UTC 是一個(gè)時(shí)間標(biāo)準(zhǔn)。

可以在這里看到所有的時(shí)區(qū):http://www.timeanddate.com/ti...

所以,當(dāng)我們“展示”某個(gè)時(shí)間時(shí),明確時(shí)區(qū)就變得非常重要了。不然你只說(shuō)現(xiàn)在是 2016-01-11 19:30:00,然后不告訴我時(shí)區(qū),我其實(shí)是沒(méi)法準(zhǔn)確知道時(shí)間的(當(dāng)然,我可以認(rèn)為這個(gè)時(shí)間是我所在時(shí)區(qū)的當(dāng)?shù)貢r(shí)間)。如果你說(shuō)現(xiàn)在是 2016-01-11 19:30:00 GMT+0800,那我就知道這個(gè)時(shí)間是東八區(qū)的時(shí)間了。如果我在東八區(qū),那時(shí)間就是 19:30,如果我在 GMT 時(shí)區(qū),那時(shí)間就是 11:30(減掉 8 小時(shí))。

JavaScript 中的“時(shí)間”

我們現(xiàn)在來(lái)介紹下 JavaScript 中的“時(shí)間”,包括:DateDate.parse、Date.UTC、Date.now。

注:下面的代碼示例可以在 node shell 里面運(yùn)行,如果你運(yùn)行的時(shí)候結(jié)果和下面的不一致,那可能咱們不在一個(gè)時(shí)區(qū):)

Date 構(gòu)造器

構(gòu)造時(shí)間的方法有下面幾種:

new Date();           // 當(dāng)前時(shí)間
new Date(value);      // 自 1970-01-01 00:00:00 UTC 經(jīng)過(guò)的毫秒數(shù)
new Date(dateString); // 時(shí)間字符串
new Date(year, month[, day[, hour[, minutes[, seconds[, milliseconds]]]]]);

需要注意的是:構(gòu)造出的日期用來(lái)顯示時(shí),會(huì)被轉(zhuǎn)換為本地時(shí)間(調(diào)用 toString 方法):

> new Date()
Mon Jan 11 2016 20:15:18 GMT+0800 (CST)

打印出我寫(xiě)這篇文章時(shí)的本地時(shí)間。后面的 GMT+0800 表示是“東八區(qū)”,CST 表示是“中國(guó)標(biāo)準(zhǔn)時(shí)間(China Standard Time)”。

有一個(gè)很“詭異”的地方是如果我們直接使用 Date,而不是 new Date,得到的將會(huì)是字符串,而不是 Date 類型的對(duì)象:

> typeof Date()
"string"
> typeof new Date()
"object"
時(shí)間字符串

我們先說(shuō)最復(fù)雜的時(shí)間字符串形式。它實(shí)際上支持兩種格式:一種是 RFC-2822 的標(biāo)準(zhǔn);另一種是 ISO 8601 的標(biāo)準(zhǔn)。我們主要介紹后一種。

ISO 8601

ISO 8601的標(biāo)準(zhǔn)格式是:YYYY-MM-DDTHH:mm:ss.sssZ,分別表示:

YYYY:年份,0000 ~ 9999

MM:月份,01 ~ 12

DD:日,01 ~ 31

T:分隔日期和時(shí)間

HH:小時(shí),00 ~ 24

mm:分鐘,00 ~ 59

ss:秒,00 ~ 59

.sss:毫秒

Z:時(shí)區(qū),可以是:Z(UFC)、+HH:mm、-HH:mm

這里我們主要來(lái)說(shuō)下 T、以及 Z。

T

T 也可以用空格表示,但是這兩種表示有點(diǎn)不一樣,T 其實(shí)表示 UTC,而空格會(huì)被認(rèn)為是本地時(shí)區(qū)(前提是不通過(guò) Z 指定時(shí)區(qū))這里的表述是錯(cuò)誤的,T 僅僅是分隔日期和時(shí)間的符號(hào),沒(méi)有其他含義。所以下面的例子其實(shí)結(jié)果是一樣的。

> new Date("1970-01-01 00:00:00")
Thu Jan 01 1970 00:00:00 GMT+0800 (CST)

> new Date("1970-01-01T00:00:00")
Thu Jan 01 1970 00:00:00 GMT+0800 (CST)

這里補(bǔ)充一點(diǎn)需要注意的,時(shí)間字符串這種形式有一個(gè)特殊的邏輯:如果你不提供“時(shí)間”(也就是 T 分隔后的內(nèi)容),得到的其實(shí)是 UTC 時(shí)間。比如:

> new Date("1970-01-01")
Thu Jan 01 1970 08:00:00 GMT+0800 (CST)

> new Date("1970-01-01T00:00")
Thu Jan 01 1970 00:00:00 GMT+0800 (CST)
Z

Z 用來(lái)表示傳入時(shí)間的時(shí)區(qū)(zone),不指定并且沒(méi)有使用 T 分隔而是使用空格分隔時(shí),就按本地時(shí)區(qū)處理。這個(gè)說(shuō)法也不嚴(yán)謹(jǐn),指定 Z 時(shí)表示 UTC 時(shí)間,不指定時(shí)表示的是本地時(shí)間。

> new Date("1970-01-01T00:00:00")
Thu Jan 01 1970 00:00:00 GMT+0800 (CST)

> new Date("1970-01-01T00:00:00Z")
Thu Jan 01 1970 08:00:00 GMT+0800 (CST)

示例 1 是東八區(qū)時(shí)間,顯示的時(shí)間和傳入的時(shí)間一致(因?yàn)槲冶镜貢r(shí)區(qū)是東八區(qū))。

示例 2 指定了 Z(也就是 UTC 零時(shí)區(qū)),顯示的時(shí)間會(huì)加上本地時(shí)區(qū)的偏移(8 小時(shí))。

RFC-2822

RFC-2822 的標(biāo)準(zhǔn)格式大概是這樣:Wed Mar 25 2015 09:56:24 GMT+0100。其實(shí)就是上面顯示時(shí)間時(shí)使用的形式:

> new Date("Thu Jan 01 1970 00:00:00 GMT+0800 (CST)")
Thu Jan 01 1970 00:00:00 GMT+0800 (CST)

除了能表示基本信息,還可以表示星期,但是一點(diǎn)也不容易讀,不建議使用。完整的規(guī)范可以在這里查看:http://tools.ietf.org/html/rf...

時(shí)間戳

Date 構(gòu)造器還可以接受整數(shù),表示想要構(gòu)造的時(shí)間自 UTC 時(shí)間 1970-01-01 00:00:00 經(jīng)過(guò)的毫秒數(shù)。比如下面的代碼:

> new Date(1000 * 1)
Thu Jan 01 1970 08:00:01 GMT+0800 (CST)

傳人 1 秒,等價(jià)于:1970-01-01 00:00:01Z,顯示的時(shí)間加上了本地時(shí)區(qū)的偏移(8 小時(shí))。

多參數(shù)

最后,Date 構(gòu)造器還支持傳遞多個(gè)參數(shù),這種方法就沒(méi)辦法指定時(shí)區(qū)了,都當(dāng)做本地時(shí)間處理。比如下面的代碼:

> new Date(1970, 0, 1, 0, 0, 0)
Thu Jan 01 1970 00:00:00 GMT+0800 (CST)

顯示時(shí)間和傳入時(shí)間一致,均是本地時(shí)間。注意:月份是從 0 開(kāi)始的。

Date.parse

Date.parse 接受一個(gè)時(shí)間字符串,如果字符串能正確解析就返回自 UTC 時(shí)間 1970-01-01 00:00:00 經(jīng)過(guò)的毫秒數(shù),否則返回 NaN

> Date.parse("1970-01-01 00:00:00")
-28800000

> new Date(Date.parse("1970-01-01 00:00:00"))
Thu Jan 01 1970 00:00:00 GMT+0800 (CST)

> Date.parse("1970-01-01T00:00:00")
0

> new Date(Date.parse("1970-01-01T00:00:00"))
Thu Jan 01 1970 08:00:00 GMT+0800 (CST)

示例 1,-28800000 換算后剛好是 8 小時(shí)表示的毫秒數(shù),28800000 / (1000 * 60 * 60),我們傳入的是本地時(shí)區(qū)時(shí)間,等于 UTC 時(shí)間的 1969-12-31 16:00:00,和 UTC 時(shí)間 1970-01-01 00:00:00 相差剛好 -8 小時(shí)。

示例 2,將 parse 后的毫秒數(shù)傳遞給構(gòu)造器,最后顯示的時(shí)間加上了本地時(shí)區(qū)的偏移(8 小時(shí)),所以結(jié)果剛好是 1970-01-01 00:00:00。

示例 3,傳入的是 UTC 時(shí)區(qū)時(shí)間,所以結(jié)果為 0。

示例 4,將 parse 后的毫秒數(shù)傳遞給構(gòu)造器,最后顯示的時(shí)間加上了本地時(shí)區(qū)的偏移(8 小時(shí)),所以結(jié)果剛好是 1970-01-01 08:00:00。

Date.UTC

Date.UTC 接受的參數(shù)和 Date 構(gòu)造器多參數(shù)形式一樣,然后返回時(shí)間自 UTC 時(shí)間 1970-01-01 00:00:00 經(jīng)過(guò)的毫秒數(shù):

> Date.UTC(1970,0,1,0,0,0)
0

> Date.parse("1970-01-01T00:00:00")
0

> Date.parse("1970-01-01 00:00:00Z")
0

可以看出,Date.UTC 進(jìn)行的是一種“絕對(duì)運(yùn)算”,傳入的時(shí)間就是 UTC 時(shí)間,不會(huì)轉(zhuǎn)換為當(dāng)?shù)貢r(shí)間。

Date.now

Date.now 返回當(dāng)前時(shí)間距 UTC 時(shí)間 1970-01-01 00:00:00 經(jīng)過(guò)的毫秒數(shù):

> Date.now()
1452520484343

> new Date(Date.now())
Mon Jan 11 2016 21:54:55 GMT+0800 (CST)

> new Date()
Mon Jan 11 2016 21:55:00 GMT+0800 (CST)
MySQL 中的“時(shí)間”

MySQL 中和時(shí)間相關(guān)的數(shù)據(jù)類型主要包括:YEAR、TIMEDATE、DATETIMETIMESTAMP。

DATE、YEARTIME 比較簡(jiǎn)單,大概總結(jié)如下:

名稱 占用字節(jié) 取值
DATE 3 字節(jié) 1000-01-01 ~ 9999-12-31
YEAR 1 字節(jié) 1901 ~ 2155
TIME 3 字節(jié) -838:59:59 ~ 838:59:59

注:TIME 的小時(shí)范圍可以這么大(超過(guò) 24 小時(shí)),是因?yàn)樗€可以用來(lái)表示兩個(gè)時(shí)間點(diǎn)之差。

DATEIME vs TIMESTAMP

我們主要來(lái)說(shuō)明下 DATETIMETIMESTAMP,可以做下面的總結(jié):

名稱 占用字節(jié) 取值 受 time_zone 設(shè)置影響
DATETIME 8 字節(jié) 1000-01-01 00:00:00 ~ 9999-12-31 23:59:59
TIMESTAMP 4 字節(jié) 1970-01-01 00:00:00 ~ 2038-01-19 03:14:07

第一個(gè)區(qū)別是占用字節(jié)不同,導(dǎo)致能表示的時(shí)間范圍也不一樣。

第二個(gè)區(qū)別是 DATETIME 是“常量”,保存時(shí)就是保存時(shí)的值,檢索時(shí)是一樣的值,不會(huì)改變;而 TIMESTAMP 則是“變量”,保存時(shí)數(shù)據(jù)庫(kù)服務(wù)器將其從time_zone 時(shí)區(qū)轉(zhuǎn)換為 UTC 時(shí)間后保存,檢索時(shí)將其轉(zhuǎn)換從 UTC 時(shí)間轉(zhuǎn)換為 time_zone 時(shí)區(qū)時(shí)間后返回。

比如,我們有下面這樣一張表:

CREATE TABLE `tests` (
    `id` INTEGER NOT NULL auto_increment , 
    `datetime` DATETIME, 
    `timestamp` TIMESTAMP, 
    PRIMARY KEY (`id`)
) ENGINE=InnoDB;

連接到數(shù)據(jù)庫(kù)服務(wù)器后,可以執(zhí)行 SHOW VARIABLES LIKE "%time_zone%" 查看當(dāng)前時(shí)區(qū)設(shè)置。類似下面這樣的結(jié)果:

Variable_name Value
system_time_zone CST
time_zone SYSTEM

說(shuō)明我目前時(shí)區(qū)是 CST(China Standard Time),也就是東八區(qū)。

我們嘗試插入下面的數(shù)據(jù):

INSERT INTO `tests` (`id`, `datetime`, `timestamp`) VALUES (DEFAULT, "1970-01-01 00:00:00", "1970-01-01 00:00:00");

會(huì)發(fā)現(xiàn)有一個(gè)報(bào)錯(cuò):Error Code: 1292. Incorrect datetime value: "1970-01-01 00:00:00" for column "timestamp"。給 timestamp 這一列提供的值不對(duì),因?yàn)槲覀儑L試插入 1970-01-01 00:00:00 時(shí),數(shù)據(jù)庫(kù)服務(wù)器會(huì)根據(jù) time_zone 的設(shè)置將其轉(zhuǎn)換為 UTC 時(shí)間,也就是 1969-12-31 16:00:00,而這個(gè)值明顯超過(guò)了 TIMESTAMP 類型的范圍。

我們換個(gè)大一點(diǎn)的值:

INSERT INTO `tests` (`id`, `datetime`, `timestamp`) VALUES (DEFAULT, "2000-01-01 00:00:00", "2000-01-01 00:00:00");

這次就成功插入了。

再次檢索時(shí)結(jié)果也是正確的(數(shù)據(jù)庫(kù)服務(wù)器將值從 UTC 時(shí)間轉(zhuǎn)換為 time_zone 設(shè)置的時(shí)區(qū)時(shí)間):

SELECT * FROM sample.tests;

返回:

id datetime timestamp
1 2000-01-01 00:00:00 2000-01-01 00:00:00

如果我們先將 time_zone 設(shè)置為一個(gè)不同的值后再進(jìn)行檢索就會(huì)發(fā)現(xiàn)不同的結(jié)果:

SET time_zone = "+00:00";
SELECT * FROM sample.tests;

返回:

id datetime timestamp
1 2000-01-01 00:00:00 1999-12-31 16:00:00

可以看到 datetime 列值沒(méi)有受 time_zone 設(shè)置的影響,而 timestamp 列值卻改變了。數(shù)據(jù)庫(kù)服務(wù)器將其從 UTC 時(shí)區(qū)轉(zhuǎn)換為 time_zone 時(shí)區(qū)的時(shí)間(首先 2000-01-01 00:00:00 在上面進(jìn)行插入時(shí)根據(jù) time_zone 被轉(zhuǎn)換為了 1999-12-31 16:00:00,此次檢索時(shí) time_zone 被設(shè)置為 +00:00,轉(zhuǎn)換回來(lái)剛好就是 1999-12-31 16:00:00)。

那這兩種類型怎么選擇呢?建議優(yōu)先使用 DATETIME,表示范圍大、不容易受服務(wù)器的設(shè)置影響。

在 JavaScript 和 MySQL 間轉(zhuǎn)換

分別說(shuō)明了 JavaScript 和 MySQL 中的“時(shí)間”后,我們來(lái)聊聊 ORM 框架一般都是怎么樣在兩者間進(jìn)行正確、合適的轉(zhuǎn)換來(lái)避免混亂的。下面的說(shuō)明將基于 sequelize 框架來(lái)解釋,主要是一種思路,其他的框架可以閱讀框架提供的文檔或是源碼。

sequelize 實(shí)際上有一個(gè) timezone 的配置,默認(rèn)是 +00:00(http://sequelize.readthedocs....)。這個(gè) timezone 有下面的用途:

建立數(shù)據(jù)庫(kù)連接時(shí),執(zhí)行 SET time_zone = opts.timezone

MySQL 的時(shí)間類型和 JavaScript 的時(shí)間類型的互相轉(zhuǎn)換

第一個(gè)用途很簡(jiǎn)單,體現(xiàn)在源碼里就是執(zhí)行一個(gè) SQL 語(yǔ)句:

connection.query("SET time_zone = "" + self.sequelize.options.timezone + """); /* jshint ignore: line */

第二個(gè)用途主要體現(xiàn)在兩個(gè)地方:1)在 JavaScript 中調(diào)用 ORM 方法進(jìn)行插入、更新時(shí),需要將 Date 類型轉(zhuǎn)為正確的 SQL 語(yǔ)句;2)從 MySQL 服務(wù)器查詢數(shù)據(jù)時(shí),需要將數(shù)據(jù)庫(kù)查詢到的值轉(zhuǎn)換為 JavaScript 中的 Date 類型。下面我們分別來(lái)看一看。

JavaScript -> MySQL

這個(gè)轉(zhuǎn)換的核心代碼如下:

SqlString.dateToString = function(date, timeZone, dialect) {
  if (moment.tz.zone(timeZone)) {
    date = moment(date).tz(timeZone);
  } else {
    date = moment(date).utcOffset(timeZone);
  }

  if (dialect === "mysql" || dialect === "mariadb") {
    return date.format("YYYY-MM-DD HH:mm:ss");
  } else {
    // ZZ here means current timezone, _not_ UTC
    return date.format("YYYY-MM-DD HH:mm:ss.SSS Z");
  }
};

代碼邏輯如下:

檢查 timeZone 是否存在,如果存在(存在指的是類似 America/New_York 這樣的表示法),調(diào)用 tz 設(shè)置 date 的時(shí)區(qū)。

如果不存在(類似 +00:00-07:00 這樣的表示法),調(diào)用 utcOffset 設(shè)置 date 的相對(duì) UTC 的時(shí)區(qū)偏移。

最后使用上面設(shè)置的時(shí)區(qū)偏移將其 format 成 MySQL 需要的 YYYY-MM-DD HH:mm:ss 格式。

舉兩個(gè)例子。

如果 timeZone 等于 +00:00,date 等于 new Date("2016-01-12 09:46:00"),到 UTC 的偏移等于 (timeZone - 本地時(shí)區(qū)) + timeZone:(00:00 - 08:00) + 00:00 = -08:00,即 2016-01-12 09:46:00-08:00,于是 format 后的結(jié)果是 2016-01-12 01:46:00。

如果 timeZone 等于 +08:00,date 等于 new Date("2016-01-12 09:46:00"),到 UTC 的偏移等于 (timeZone - 本地時(shí)區(qū)) + timeZone:(08:00 - 08:00) + 08:00 = 08:00,即 2016-01-12 09:46:00+08:00。于是 format 后的結(jié)果是 2016-01-12 09:46:00

如果 timeZone 等于 Asia/Shanghai,結(jié)果也會(huì)是 2016-01-12 09:46:00,和 +08:00 等價(jià)。

sequelize 的 timezone 默認(rèn)是 +00:00,所以,我們?cè)?JavaScript 中的時(shí)間最后應(yīng)用到數(shù)據(jù)庫(kù)中都會(huì)被轉(zhuǎn)換成 UTC 的時(shí)間(比實(shí)際的時(shí)間早 8 小時(shí))。

MySQL -> JavaScript

這個(gè)轉(zhuǎn)換過(guò)程實(shí)際上是更底層的 node-mysql 庫(kù)來(lái)實(shí)現(xiàn)的。核心代碼如下:

  switch (field.type) {
    case Types.TIMESTAMP:
    case Types.DATE:
    case Types.DATETIME:
    case Types.NEWDATE:
      var dateString = parser.parseLengthCodedString();
      if (dateStrings) {
        return dateString;
      }
      var dt;

      if (dateString === null) {
        return null;
      }

      var originalString = dateString;
      if (field.type === Types.DATE) {
        dateString += " 00:00:00";
      }

      if (timeZone !== "local") {
        dateString += " " + timeZone;
      }

      dt = new Date(dateString);
      if (isNaN(dt.getTime())) {
        return originalString;
      }

      return dt;
   // 更多代碼...
}

處理過(guò)程大概是這樣:

parser 將服務(wù)器返回的二進(jìn)制數(shù)據(jù)解析為時(shí)間字符串

如果配置了強(qiáng)制返回字符串 dateStrings 而不是轉(zhuǎn)換回 Date 類型,直接返回 dateString

如果字段類型是 DATE,時(shí)間字符串的時(shí)間部分統(tǒng)一為 00:00:00

如果配置的 timeZone 不是 local(本地時(shí)區(qū)),時(shí)間字符串加上時(shí)區(qū)信息

將時(shí)間字符串傳給 Date 構(gòu)造器,如果構(gòu)造出的時(shí)間不合法,返回原始時(shí)間字符串,否則返回時(shí)間對(duì)象

默認(rèn)情況下,sequelize 在進(jìn)行連接時(shí)傳遞給 node-mysql 的 timeZone+00:00,所以,第 4 步的時(shí)間字符串會(huì)是類似這樣的值 2016-01-12 01:46:00+00:00,而這個(gè)值傳遞給 Date 構(gòu)造器,在顯示時(shí)轉(zhuǎn)換回本地時(shí)區(qū)時(shí)間,就變成了 2016-01-12 09:46:00(比數(shù)據(jù)庫(kù)中的時(shí)間晚 8 小時(shí))。

一個(gè)例子

在使用 sequelize 定義模型時(shí),其實(shí)是沒(méi)有 TIMESTAMP 類型的,sequelize 只提供了一個(gè) Sequelize.DATE 類型,生成建表語(yǔ)句時(shí)被轉(zhuǎn)換為 DATETIME。

如果是在舊表上定義模型,而這張舊表剛好有 TIMESTAMP 類型的列,對(duì) TIMESTAMP 類型的列定義模型時(shí)還是可以使用 Sequelize.DATE,對(duì)操作沒(méi)有任何影響。但是 TIMESTAMP 是受 time_zone 設(shè)置影響的,這會(huì)引起一些困惑。下面我們來(lái)看一個(gè)例子。

sequelize 默認(rèn)將 time_zone 設(shè)置為 +00:00,當(dāng)我們執(zhí)行下面代碼時(shí):

Test.create({
    "datetime": new Date("2016-01-10 20:07:00"),
    "timestamp": new Date("2016-01-10 20:07:00")
  });

會(huì)進(jìn)行上面提到的 JavaScript 時(shí)間到 MySQL 時(shí)間字符串的轉(zhuǎn)換,生成的 SQL 其實(shí)是(時(shí)間被轉(zhuǎn)換為了 UTC 時(shí)間,比本地時(shí)間早了 8 小時(shí)):

INSERT INTO `tests` (`id`,`datetime`,`timestamp`) VALUES (DEFAULT,"2016-01-10 12:07:00","2016-01-10 12:07:00");

當(dāng)我們執(zhí)行 Test.findAll() 來(lái)查詢數(shù)據(jù)時(shí),會(huì)進(jìn)行上面提到的 MySQL 時(shí)間到 JavaScript 時(shí)間的轉(zhuǎn)換,其實(shí)就是返回這樣的結(jié)果(顯示時(shí)時(shí)間從 UTC 時(shí)間轉(zhuǎn)換回了本地時(shí)間):

> new Date("2016-01-10 12:07:00+00:00")
Sun Jan 10 2016 20:07:00 GMT+0800 (CST)

和我們插入時(shí)的時(shí)間是一致的。

如果我們通過(guò) MySQL 命令行來(lái)查詢數(shù)據(jù)時(shí),發(fā)現(xiàn)其實(shí)是這樣的結(jié)果:

id datetime timestamp
1 2016-01-10 12:07:00 2016-01-10 20:07:00

這很好理解,因?yàn)槲覀償?shù)據(jù)庫(kù)服務(wù)器的 time_zone 默認(rèn)是東八區(qū),TIMESTAMP 是受時(shí)區(qū)影響的,查詢時(shí)被數(shù)據(jù)庫(kù)服務(wù)器從 UTC 時(shí)間轉(zhuǎn)換回了 time_zone 時(shí)區(qū)時(shí)間;DATETIME 不受影響,還是 UTC 時(shí)間。

如果我們先執(zhí)行 SET time_zone = "+00:00",再進(jìn)行查詢,那結(jié)果就都會(huì)是 UTC 時(shí)間了。所以,不要以為數(shù)據(jù)出錯(cuò)了哦。

總結(jié)下就是,sequelize 會(huì)將本地時(shí)間轉(zhuǎn)換為 UTC 時(shí)間后入庫(kù),查詢時(shí)再將 UTC 時(shí)間轉(zhuǎn)換為本地時(shí)間。這能達(dá)到最好的兼容性,存儲(chǔ)總是使用 UTC 時(shí)間,展示時(shí)應(yīng)用端自己轉(zhuǎn)換為本地時(shí)區(qū)時(shí)間后顯示。當(dāng)然這個(gè)的前提是數(shù)據(jù)類型選用 DATETIME。

兼容老數(shù)據(jù)

這里要說(shuō)的最后一個(gè)問(wèn)題是基于舊表定義 sequelize 模型,并且表中時(shí)間值插入時(shí)沒(méi)有轉(zhuǎn)換為 UTC 時(shí)間(全部是東八區(qū)時(shí)間),而且 DATETIMETIMESTAMP 混用,該怎么辦?

在默認(rèn)配置下,情況如下:

查詢 DATETIME 類型數(shù)據(jù)時(shí),時(shí)間總是會(huì)晚 8 小時(shí)。比如,數(shù)據(jù)庫(kù)中某條老數(shù)據(jù)的時(shí)間是 2012-01-01 01:00:00(已經(jīng)是本地時(shí)間了,因?yàn)闆](méi)轉(zhuǎn)換),查詢時(shí)被 sequelize 轉(zhuǎn)換為 new Date("2012-01-01 01:00:00+00:00"),顯示時(shí)轉(zhuǎn)換為本地時(shí)間 2012-01-01 09:00:00,結(jié)果顯然不對(duì)。

查詢 TIMESTAMP 類型數(shù)據(jù)時(shí),時(shí)間是正確的。這是因?yàn)?TIMESTAMPtime_zone 影響,sequelize 默認(rèn)將其設(shè)置為 +00:00,查詢時(shí)數(shù)據(jù)庫(kù)服務(wù)器先將時(shí)間轉(zhuǎn)換到 time_zone 設(shè)置的時(shí)區(qū)時(shí)間,由于沒(méi)有時(shí)區(qū)偏移,剛好查出來(lái)的就是數(shù)據(jù)庫(kù)中的值。比如:2012-01-01 00:00:00(注意這個(gè)值是 UTC 時(shí)間),sequelize 將其轉(zhuǎn)換為 new Date("2012-01-01 00:00:00+00:00"),顯示時(shí)轉(zhuǎn)換為本地時(shí)間 2012-01-01 08:00:00,剛好“僥幸”正確。

新插入的數(shù)據(jù) sequelize 會(huì)進(jìn)行上一部分說(shuō)的雙向轉(zhuǎn)換來(lái)保證結(jié)果的正確。

維持默認(rèn)配置顯然導(dǎo)致查詢 DATETIME 不準(zhǔn)確,解決方法就是將 sequelize 的 timezone 配置為 +08:00。這樣一來(lái),情況變成下面這樣:

查詢 DATETIME 類型數(shù)據(jù)時(shí),時(shí)間 2012-01-01 01:00:00 被轉(zhuǎn)換為 new Date("2012-01-01 01:00:00+08:00"),顯示時(shí)轉(zhuǎn)換為本地時(shí)間 2012-01-01 01:00:00,結(jié)果正確。

查詢 TIMESTAMP 類型數(shù)據(jù)時(shí),由于 time_zone 被設(shè)置為了 +08:00,數(shù)據(jù)庫(kù)服務(wù)器先將庫(kù)中 UTC 時(shí)間 2011-01-01 00:00:00 轉(zhuǎn)換到 time_zone 時(shí)區(qū)時(shí)間(加上 8 小時(shí)偏移)為 2011-01-01 08:00:00,sequelize 將其轉(zhuǎn)換為 new Date("2011-01-01 08:00:00+08:00"),顯示時(shí)轉(zhuǎn)換為本地時(shí)間 2011-01-01 08:00:00,結(jié)果正確。

插入、更新數(shù)據(jù)時(shí),所有 JavaScript 時(shí)間會(huì)轉(zhuǎn)換為東八區(qū)時(shí)間入庫(kù)。

這樣帶來(lái)的問(wèn)題是,所有入庫(kù)時(shí)間都是東八區(qū)時(shí)間,如果有其他應(yīng)用的時(shí)區(qū)不是東八區(qū),那就需要自己基于東八區(qū)時(shí)間計(jì)算偏移并轉(zhuǎn)換時(shí)間后顯示了。

參考資料

一不小心寫(xiě)的有點(diǎn)長(zhǎng)了,下面列出參考資料供大家進(jìn)一步學(xué)習(xí):

http://www.timeanddate.com/ti...

https://developer.mozilla.org...

https://developer.mozilla.org...

https://developer.mozilla.org...

http://tools.ietf.org/html/rf...

http://www.ecma-international...

http://www.w3schools.com/js/j...

http://momentjs.com/timezone/...

http://sequelize.readthedocs....

https://github.com/felixge/no...

《MySQL 技術(shù)內(nèi)幕》

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/78471.html

相關(guān)文章

  • 2017--年度個(gè)人總結(jié)

    摘要:離職新路線年的總結(jié)在這里年總結(jié),其實(shí)在發(fā)布這個(gè)文章之前,就已經(jīng)跟阿里那邊再談新的,會(huì)以的級(jí)別入職阿里閑魚(yú)部門(mén)??偹苤?,我司在月份調(diào)整了一次架構(gòu),具體如下美團(tuán)調(diào)整了組織架構(gòu)。 17年的總結(jié)來(lái)的更晚一點(diǎn),其實(shí)是一直在猶豫要不要寫(xiě),主要感覺(jué)去年一年折騰的有點(diǎn)兇殘,連續(xù)換工作以及地點(diǎn),一路走來(lái)有糾結(jié),有痛苦,有快樂(lè),有興奮,有迷茫,有得有失,所以想了很久,還是來(lái)記錄下這一年的關(guān)鍵點(diǎn)。 離職 ...

    2450184176 評(píng)論0 收藏0
  • 2017--年度個(gè)人總結(jié)

    摘要:離職新路線年的總結(jié)在這里年總結(jié),其實(shí)在發(fā)布這個(gè)文章之前,就已經(jīng)跟阿里那邊再談新的,會(huì)以的級(jí)別入職阿里閑魚(yú)部門(mén)。總所周知,我司在月份調(diào)整了一次架構(gòu),具體如下美團(tuán)調(diào)整了組織架構(gòu)。 17年的總結(jié)來(lái)的更晚一點(diǎn),其實(shí)是一直在猶豫要不要寫(xiě),主要感覺(jué)去年一年折騰的有點(diǎn)兇殘,連續(xù)換工作以及地點(diǎn),一路走來(lái)有糾結(jié),有痛苦,有快樂(lè),有興奮,有迷茫,有得有失,所以想了很久,還是來(lái)記錄下這一年的關(guān)鍵點(diǎn)。 離職 ...

    TIGERB 評(píng)論0 收藏0
  • 【譯】JavaScript 框架的探索與變遷(下)

    摘要:對(duì)此沒(méi)有任何限制,它不關(guān)心這個(gè)。一種控制變化的辦法是不可改變的,持久化的數(shù)據(jù)結(jié)構(gòu)。總結(jié)檢測(cè)變化時(shí)開(kāi)發(fā)中的核心問(wèn)題,而框架們以各種方式解決這個(gè)問(wèn)題。因?yàn)榻M件內(nèi)的變化是不被允許的。 AngularJS:臟檢查 我不知道什么更新了,所以當(dāng)更新的時(shí)候,我只能檢查所有的東西。 AngularJS 類似于 Ember,當(dāng)狀態(tài)改變的時(shí)候,必須人工去處理。但不同的是,AngularJS 從不同的角度來(lái)...

    CollinPeng 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<