亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > JS > 正文

javascript關于“時間”的一次探索

2024-05-06 16:53:34
字體:
來源:轉載
供稿:網友

原文對 ISO 8601 時間格式中 T 和 Z 的表述有一些錯誤,我已經對原文進行了一些修訂,抱歉給大家造成誤解。

最近使用 sequelize 過程中發現一個“奇怪”的問題,將某個時間插入到表中后,通過 sequelize 查詢出來的時間和通過 mysql 命令行工具查詢出來的時間不一樣。非常困惑,于是研究了下,下面是學習成果。

基本概念

我們先來介紹一些可能當年在地理課上學習過的基本概念。

說起來,時間真是一個神奇的東西。以前人們通過觀察太陽的位置來決定時間(比如:使用日晷),這就使得不同經緯度的地區時間是不一樣的。后來人們進一步規定以子午線為中心,向東西兩側延伸,每 15 度劃分一個時區,剛好是 24 個時區。然后因為一天有 24 小時,地球自轉一圈是 360 度,360 度 / 24 小時 = 15 度/小時,所以每差一個時區,時間就差一個小時。

最開始的標準時間(子午線中心處的時間)是英國倫敦的皇家格林威治天文臺的標準時間(因為它剛好在本初子午線經過的地方),這就是我們常說的 GMT(Greenwich Mean Time)。然后其他各個時區根據標準時間確定自己的時間,往東的時區時間晚(表示為 GMT+hh:mm)、往西的時區時間早(表示為 GMT-hh:mm)。比如,中國標準時間是東八區,我們的時間就總是比 GMT 時間晚 8 小時,他們在凌晨 1 點,我們已經是早晨 9 點了。

但是 GMT 其實是根據地球自轉、公轉計算的(太陽每天經過英國倫敦皇家格林威治天文臺的時間為中午 12 點),不是非常準確,于是后面提出了根據原子鐘計算的標準時間 UTC(Coordinated Universal Time)。

一般情況下,GMT 和 UTC 可以互換,但是實際上,GMT 是一個時區,而 UTC 是一個時間標準。

可以在這里看到所有的時區:https://www.timeanddate.com/time/map/

所以,當我們“展示”某個時間時,明確時區就變得非常重要了。不然你只說現在是 2016-01-11 19:30:00,然后不告訴我時區,我其實是沒法準確知道時間的(當然,我可以認為這個時間是我所在時區的當地時間)。如果你說現在是 2016-01-11 19:30:00 GMT+0800,那我就知道這個時間是東八區的時間了。如果我在東八區,那時間就是 19:30,如果我在 GMT 時區,那時間就是 11:30(減掉 8 小時)。

JavaScript 中的“時間”

我們現在來介紹下 JavaScript 中的“時間”,包括:Date、Date.parse、Date.UTC、Date.now。

注:下面的代碼示例可以在 node shell 里面運行,如果你運行的時候結果和下面的不一致,那可能咱們不在一個時區:)

Date 構造器

構造時間的方法有下面幾種:

new Date();      // 當前時間new Date(value);   // 自 1970-01-01 00:00:00 UTC 經過的毫秒數new Date(dateString); // 時間字符串new Date(year, month[, day[, hour[, minutes[, seconds[, milliseconds]]]]]);

需要注意的是:構造出的日期用來顯示時,會被轉換為本地時間(調用 toString 方法):

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

打印出我寫這篇文章時的本地時間。后面的 GMT+0800 表示是“東八區”,CST 表示是“中國標準時間(China Standard Time)”。

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

> typeof Date()'string'> typeof new Date()'object'

時間字符串

我們先說最復雜的時間字符串形式。它實際上支持兩種格式:一種是 RFC-2822 的標準;另一種是 ISO 8601 的標準。我們主要介紹后一種。

ISO 8601

ISO 8601的標準格式是:YYYY-MM-DDTHH:mm:ss.sssZ,分別表示:

  • YYYY:年份,0000 ~ 9999
  • MM:月份,01 ~ 12
  • DD:日,01 ~ 31
  • T:分隔日期和時間
  • HH:小時,00 ~ 24
  • mm:分鐘,00 ~ 59
  • ss:秒,00 ~ 59
  • .sss:毫秒
  • Z:時區,可以是:Z(UFC)、+HH:mm、-HH:mm

這里我們主要來說下 T、以及 Z。

T

T 也可以用空格表示,但是這兩種表示有點不一樣,T 其實表示 UTC,而空格會被認為是本地時區(前提是不通過 Z 指定時區)。這里的表述是錯誤的,T 僅僅是分隔日期和時間的符號,沒有其他含義。所以下面的例子其實結果是一樣的。

> 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)

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

> 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 用來表示傳入時間的時區(zone),不指定并且沒有使用 T 分隔而是使用空格分隔時,就按本地時區處理。這個說法也不嚴謹,指定 Z 時表示 UTC 時間,不指定時表示的是本地時間。

> 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 是東八區時間,顯示的時間和傳入的時間一致(因為我本地時區是東八區)。

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

RFC-2822

RFC-2822 的標準格式大概是這樣:Wed Mar 25 2015 09:56:24 GMT+0100。其實就是上面顯示時間時使用的形式:

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

除了能表示基本信息,還可以表示星期,但是一點也不容易讀,不建議使用。完整的規范可以在這里查看:https://tools.ietf.org/html/rfc2822#page-14

時間戳

Date 構造器還可以接受整數,表示想要構造的時間自 UTC 時間 1970-01-01 00:00:00 經過的毫秒數。比如下面的代碼:

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

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

多參數

最后,Date 構造器還支持傳遞多個參數,這種方法就沒辦法指定時區了,都當做本地時間處理。比如下面的代碼:

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

顯示時間和傳入時間一致,均是本地時間。注意:月份是從 0 開始的。

Date.parse

Date.parse 接受一個時間字符串,如果字符串能正確解析就返回自 UTC 時間 1970-01-01 00:00:00 經過的毫秒數,否則返回 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 小時表示的毫秒數,28800000 / (1000 * 60 * 60),我們傳入的是本地時區時間,等于 UTC 時間的 1969-12-31 16:00:00,和 UTC 時間 1970-01-01 00:00:00 相差剛好 -8 小時。

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

示例 3,傳入的是 UTC 時區時間,所以結果為 0。

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

Date.UTC

Date.UTC 接受的參數和 Date 構造器多參數形式一樣,然后返回時間自 UTC 時間 1970-01-01 00:00:00 經過的毫秒數:

> 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 進行的是一種“絕對運算”,傳入的時間就是 UTC 時間,不會轉換為當地時間。

Date.now
Date.now 返回當前時間距 UTC 時間 1970-01-01 00:00:00 經過的毫秒數:

> 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 中的“時間”

MySQL 中和時間相關的數據類型主要包括:YEAR、TIME、DATE、DATETIME、TIMESTAMP。

DATE、YEAR、TIME 比較簡單,大概總結如下:

 

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

 

注:TIME 的小時范圍可以這么大(超過 24 小時),是因為它還可以用來表示兩個時間點之差。

DATEIME vs TIMESTAMP

我們主要來說明下 DATETIME 和 TIMESTAMP,可以做下面的總結:

 

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

 

第一個區別是占用字節不同,導致能表示的時間范圍也不一樣。

第二個區別是 DATETIME 是“常量”,保存時就是保存時的值,檢索時是一樣的值,不會改變;而 TIMESTAMP 則是“變量”,保存時數據庫服務器將其從time_zone 時區轉換為 UTC 時間后保存,檢索時將其轉換從 UTC 時間轉換為 time_zone 時區時間后返回。

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

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

連接到數據庫服務器后,可以執行 SHOW VARIABLES LIKE '%time_zone%' 查看當前時區設置。類似下面這樣的結果:

 

Variable_name Value
system_time_zone CST
time_zone SYSTEM

 

說明我目前時區是 CST(China Standard Time),也就是東八區。

我們嘗試插入下面的數據:

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

會發現有一個報錯:Error Code: 1292. Incorrect datetime value: '1970-01-01 00:00:00' for column 'timestamp'。給 timestamp 這一列提供的值不對,因為我們嘗試插入 1970-01-01 00:00:00 時,數據庫服務器會根據 time_zone 的設置將其轉換為 UTC 時間,也就是 1969-12-31 16:00:00,而這個值明顯超過了 TIMESTAMP 類型的范圍。

我們換個大一點的值:

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

這次就成功插入了。

再次檢索時結果也是正確的(數據庫服務器將值從 UTC 時間轉換為 time_zone 設置的時區時間):

SELECT * FROM sample.tests;

返回:

 

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

 

如果我們先將 time_zone 設置為一個不同的值后再進行檢索就會發現不同的結果:

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 列值沒有受 time_zone 設置的影響,而 timestamp 列值卻改變了。數據庫服務器將其從 UTC 時區轉換為 time_zone 時區的時間(首先 2000-01-01 00:00:00 在上面進行插入時根據 time_zone 被轉換為了 1999-12-31 16:00:00,此次檢索時 time_zone 被設置為 +00:00,轉換回來剛好就是 1999-12-31 16:00:00)。

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

在 JavaScript 和 MySQL 間轉換

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

sequelize 實際上有一個 timezone 的配置,默認是 +00:00(http://sequelize.readthedocs.org/en/latest/api/sequelize/.)。這個 timezone 有下面的用途:

  • 建立數據庫連接時,執行 SET time_zone = opts.timezone
  • MySQL 的時間類型和 JavaScript 的時間類型的互相轉換

第一個用途很簡單,體現在源碼里就是執行一個 SQL 語句:

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

第二個用途主要體現在兩個地方:1)在 JavaScript 中調用 ORM 方法進行插入、更新時,需要將 Date 類型轉為正確的 SQL 語句;2)從 MySQL 服務器查詢數據時,需要將數據庫查詢到的值轉換為 JavaScript 中的 Date 類型。下面我們分別來看一看。

JavaScript -> MySQL

這個轉換的核心代碼如下:

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'); }};

代碼邏輯如下:

  1. 檢查 timeZone 是否存在,如果存在(存在指的是類似 America/New_York 這樣的表示法),調用 tz 設置 date 的時區。
  2. 如果不存在(類似 +00:00、-07:00 這樣的表示法),調用 utcOffset 設置 date 的相對 UTC 的時區偏移。
  3. 最后使用上面設置的時區偏移將其 format 成 MySQL 需要的 YYYY-MM-DD HH:mm:ss 格式。

舉兩個例子。

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

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

如果 timeZone 等于 Asia/Shanghai,結果也會是 2016-01-12 09:46:00,和 +08:00 等價。

sequelize 的 timezone 默認是 +00:00,所以,我們在 JavaScript 中的時間最后應用到數據庫中都會被轉換成 UTC 的時間(比實際的時間早 8 小時)。

MySQL -> JavaScript

這個轉換過程實際上是更底層的 node-mysql 庫來實現的。核心代碼如下:

 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;  // 更多代碼...}

處理過程大概是這樣:

  1. 用 parser 將服務器返回的二進制數據解析為時間字符串
  2. 如果配置了強制返回字符串 dateStrings 而不是轉換回 Date 類型,直接返回 dateString
  3. 如果字段類型是 DATE,時間字符串的時間部分統一為 00:00:00
  4. 如果配置的 timeZone 不是 local(本地時區),時間字符串加上時區信息
  5. 將時間字符串傳給 Date 構造器,如果構造出的時間不合法,返回原始時間字符串,否則返回時間對象

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

一個例子

在使用 sequelize 定義模型時,其實是沒有 TIMESTAMP 類型的,sequelize 只提供了一個 Sequelize.DATE 類型,生成建表語句時被轉換為 DATETIME。

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

sequelize 默認將 time_zone 設置為 +00:00,當我們執行下面代碼時:

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

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

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

當我們執行 Test.findAll() 來查詢數據時,會進行上面提到的 MySQL 時間到 JavaScript 時間的轉換,其實就是返回這樣的結果(顯示時時間從 UTC 時間轉換回了本地時間):

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

和我們插入時的時間是一致的。

如果我們通過 MySQL 命令行來查詢數據時,發現其實是這樣的結果:

 

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

 

這很好理解,因為我們數據庫服務器的 time_zone 默認是東八區,TIMESTAMP 是受時區影響的,查詢時被數據庫服務器從 UTC 時間轉換回了 time_zone 時區時間;DATETIME 不受影響,還是 UTC 時間。

如果我們先執行 SET time_zone = '+00:00',再進行查詢,那結果就都會是 UTC 時間了。所以,不要以為數據出錯了哦。

總結下就是,sequelize 會將本地時間轉換為 UTC 時間后入庫,查詢時再將 UTC 時間轉換為本地時間。這能達到最好的兼容性,存儲總是使用 UTC 時間,展示時應用端自己轉換為本地時區時間后顯示。當然這個的前提是數據類型選用 DATETIME。

兼容老數據

這里要說的最后一個問題是基于舊表定義 sequelize 模型,并且表中時間值插入時沒有轉換為 UTC 時間(全部是東八區時間),而且 DATETIME 和 TIMESTAMP 混用,該怎么辦?

在默認配置下,情況如下:

查詢 DATETIME 類型數據時,時間總是會晚 8 小時。比如,數據庫中某條老數據的時間是 2012-01-01 01:00:00(已經是本地時間了,因為沒轉換),查詢時被 sequelize 轉換為 new Date('2012-01-01 01:00:00+00:00'),顯示時轉換為本地時間 2012-01-01 09:00:00,結果顯然不對。

查詢 TIMESTAMP 類型數據時,時間是正確的。這是因為 TIMESTAMP 受 time_zone 影響,sequelize 默認將其設置為 +00:00,查詢時數據庫服務器先將時間轉換到 time_zone 設置的時區時間,由于沒有時區偏移,剛好查出來的就是數據庫中的值。比如:2012-01-01 00:00:00(注意這個值是 UTC 時間),sequelize 將其轉換為 new Date('2012-01-01 00:00:00+00:00'),顯示時轉換為本地時間 2012-01-01 08:00:00,剛好“僥幸”正確。

新插入的數據 sequelize 會進行上一部分說的雙向轉換來保證結果的正確。

維持默認配置顯然導致查詢 DATETIME 不準確,解決方法就是將 sequelize 的 timezone 配置為 +08:00。這樣一來,情況變成下面這樣:

查詢 DATETIME 類型數據時,時間 2012-01-01 01:00:00 被轉換為 new Date('2012-01-01 01:00:00+08:00'),顯示時轉換為本地時間 2012-01-01 01:00:00,結果正確。

查詢 TIMESTAMP 類型數據時,由于 time_zone 被設置為了 +08:00,數據庫服務器先將庫中 UTC 時間 2011-01-01 00:00:00 轉換到 time_zone 時區時間(加上 8 小時偏移)為 2011-01-01 08:00:00,sequelize 將其轉換為 new Date('2011-01-01 08:00:00+08:00'),顯示時轉換為本地時間 2011-01-01 08:00:00,結果正確。

插入、更新數據時,所有 JavaScript 時間會轉換為東八區時間入庫。

這樣帶來的問題是,所有入庫時間都是東八區時間,如果有其他應用的時區不是東八區,那就需要自己基于東八區時間計算偏移并轉換時間后顯示了。

參考資料

一不小心寫的有點長了,下面列出參考資料供大家進一步學習:

http://www.timeanddate.com/time/gmt-utc-time.html
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Datehttps://developer.mozilla.org...
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/UTC
http://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.15
http://www.w3schools.com/js/js_date_formats.asp
https://momentjs.com/timezone/docs/
https://sequelize.readthedocs.io/en/latest/api/sequelize/
https://github.com/mysqljs/mysql


注:相關教程知識閱讀請移步到JavaScript/Ajax教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
在线国产精品视频| 成人亚洲综合色就1024| 亚洲综合在线做性| 在线视频免费一区二区| 亚洲激情久久久| 亚洲精品久久7777777| 亚洲高清av在线| 久久免费少妇高潮久久精品99| 欧美成人激情图片网| 久久精品国产亚洲7777| 亚洲黄色av网站| 日韩中文字幕欧美| 国产精品成人av在线| 日韩精品在线视频美女| 国产欧美日韩免费| 亚洲精品成人av| 亚洲三级av在线| 亚洲成人激情图| 亚洲欧美中文日韩在线v日本| 国产精品无av码在线观看| 777午夜精品福利在线观看| 欧美疯狂做受xxxx高潮| 欧美日韩国产综合新一区| 国产91精品久久久| 亚洲欧美一区二区三区久久| 日韩欧美国产免费播放| 国产日韩在线视频| 欧美一区二区三区精品电影| 久久伊人免费视频| 91精品久久久久久久久久| 亚洲成人精品视频| 26uuu亚洲伊人春色| 亚洲精品中文字| 91在线视频精品| 欧美香蕉大胸在线视频观看| 亚洲国产中文字幕久久网| 国内精品久久久久久| 2020欧美日韩在线视频| 日韩欧美第一页| 福利微拍一区二区| 亚洲综合精品伊人久久| 97精品一区二区三区| 午夜精品一区二区三区在线视| 九九热这里只有精品6| 欧美色欧美亚洲高清在线视频| 综合av色偷偷网| 91久久久久久久| 97香蕉超级碰碰久久免费软件| 国产精品狼人色视频一区| 欧美日本啪啪无遮挡网站| 九九久久国产精品| 欧美亚洲视频在线看网址| 2019亚洲男人天堂| 亚洲影视九九影院在线观看| 欧美精品一区二区三区国产精品| 国产精品va在线| 国产精品视频最多的网站| 国产精品视频内| 日韩精品免费观看| 日韩精品中文字幕在线| 91精品视频在线免费观看| 色妞欧美日韩在线| 久久久免费av| 性色av一区二区三区在线观看| 国产精品高潮视频| 97视频在线看| 丝袜情趣国产精品| 国产视频亚洲视频| 91最新在线免费观看| 黑人极品videos精品欧美裸| 欧美亚洲第一区| 亚洲午夜色婷婷在线| 亚洲黄色免费三级| 国产精品扒开腿做爽爽爽男男| 国产精品久久久久一区二区| 一本一本久久a久久精品综合小说| 7777免费精品视频| 日韩欧美亚洲综合| 亚洲欧美日韩国产中文专区| 国产亚洲欧美日韩一区二区| 中文字幕精品久久久久| 亚洲人成人99网站| 亚洲xxx大片| 日韩色av导航| 亚洲欧美一区二区三区情侣bbw| 国产精品视频一区二区三区四| 欧美激情一区二区久久久| 欧美情侣性视频| 日本久久久久久久| 中文字幕精品一区二区精品| 欧美成人网在线| 成人高h视频在线| 久久综合免费视频| 亚洲欧美国产日韩天堂区| 理论片在线不卡免费观看| 国产精品免费久久久久影院| 欧美精品免费在线观看| 在线视频欧美日韩精品| 国产精品流白浆视频| 18一19gay欧美视频网站| 在线电影欧美日韩一区二区私密| 国产精品欧美一区二区三区奶水| 日韩中文字幕在线看| 亚洲精品国产成人| 久久精品国产69国产精品亚洲| 亚洲视频在线免费看| 国产在线久久久| 欧美高清在线观看| 午夜精品久久久久久久白皮肤| 国产成人综合精品| www.久久久久久.com| 成人性生交大片免费看视频直播| 国产精品视频自拍| 国产69精品久久久久9999| 在线不卡国产精品| 精品亚洲一区二区| 8x拔播拔播x8国产精品| 亚洲一区二区在线播放| 日日摸夜夜添一区| 亚洲国产精品视频在线观看| 久久免费国产视频| 日韩欧美在线视频| 国产视频丨精品|在线观看| 国产成人综合av| 欧美日韩成人免费| 97高清免费视频| 国产97色在线|日韩| 97超碰国产精品女人人人爽| 国产精品96久久久久久| 日韩在线免费观看视频| 91手机视频在线观看| 国产98色在线| 久久久久久久久久久av| 中文字幕日韩专区| 91精品国产自产在线老师啪| 亚洲精品国产综合久久| 亚洲精品v欧美精品v日韩精品| 国产亚洲欧美日韩美女| 国产精品极品在线| 97激碰免费视频| 精品一区二区亚洲| 成人写真视频福利网| 91亚洲va在线va天堂va国| 欧美久久精品午夜青青大伊人| 欧美大尺度在线观看| www.欧美精品一二三区| 综合国产在线观看| 亚洲综合视频1区| 亚洲一区二区久久久久久久| 91wwwcom在线观看| 日韩中文视频免费在线观看| 亚洲男人av电影| 国产精品成av人在线视午夜片| 91精品久久久久久久久青青| 最近中文字幕日韩精品| 欧美成人午夜剧场免费观看| 91精品国产乱码久久久久久蜜臀| 欧美精品激情在线| 人妖精品videosex性欧美| 国产精品美女主播| 久久香蕉精品香蕉| 黑丝美女久久久| 国产日韩中文字幕| 国产成人精品视频在线|