# 日期與時間

Date在JavaScript中是一個內建的特殊物件，Date(日期)的資料型態應該是指Datetime(日期時間)而言，所以不只包含所謂的日期 - 年月日而已，它也包含時間中的時、分、秒與毫秒。

`Date`物件複雜的地方不只是它的進位並非十進位而已，年月日的進位還涉及閏年、星期幾的問題，以及全球各種不同語言、全球時區使用的本地化格式的問題等等各種問題。所以`Date`物件有時是個很難處理的東西，在複雜的應用情況時，以及需要較好的瀏覽器相容性時，我們會用其他的工具函式庫來協助，例如一套很常使用的函式庫 - [Moment.js](http://momentjs.com/)，不過它並不在本章的範圍之中，有需要可以再參考使用。

`Date`物件中包含了許多對日期時間處理方法，大致上可以分成以下幾類:

* getter: 獲取某種時間格式，例如getFullYear是回傳西元年
* setter: 設定某種時間格式，例如setFullYear設定西元年
* 格式化的getter: 通常是要獲取不同地區的日期(或時間)格式，以及轉換成各種資料格式

> 註: 毫秒(Millisecond, ms)是千分之一秒

## `Date`物件的建構式

`Date`物件一定只能使用`new`運算符來建立，這是JavaScript中的硬規則，有一些特殊的內建物件一定要使用建構式進行物件的實體化。

`Date`物件有四種可在建構式傳入參數的類型，如以下的語法說明，其中只有第三種是可以傳入字串資料類型，傳入參數`dateString`指的是遵守通用的日期時間字串、國際標準[RFC2822](https://tools.ietf.org/html/rfc2822#section-3.3)或[ISO 8601](https://zh.wikipedia.org/wiki/ISO_8601)這幾種的字串格式，後面會再說明其中的內容。

```js
const date = new Date()
const date = new Date(milliseconds)
const date = new Date(dateString) //只有這種方式是傳入字串類型，其他都不是。
const date = new Date(year, month, day, hours, minutes, seconds, milliseconds)
```

以下就每種建立`Date`物件的應用情況分別說明，從範例中來理解它的用法。

### 取得目前日期時間

`new Date()`不加任何參數，就會使用瀏覽器環境來取得的目前日期時間作為傳入參數，說白一點也就是獲得當下的日期時間，輸出時會使用國際日期標準的格式輸出，相等於呼叫`toString()`方法，注意它仍然是個物件類型，只是無法直接輸出裡面資料結構而已:

```js
const datetime = new Date()
console.log(datetime) //Tue Jul 12 2016 11:35:48 GMT+0800 (CST)
console.log(datetime.toString()) //Tue Jul 12 2016 11:35:48 GMT+0800 (CST)
console.log(datetime.toUTCString()) //Tue, 12 Jul 2016 03:35:48 GMT
console.log(datetime.toTimeString()) //11:35:48 GMT+0800 (CST)
```

另一種要用於計算時間用的格式，則是把`Date`物件轉成只用毫秒數值的格式，它是一個由`1 January 1970 00:00:00 UTC`開始計算，到目前日期時間的毫秒數字值。這個時間類似稱之為[UNIX時間](https://zh.wikipedia.org/wiki/UNIX%E6%97%B6%E9%97%B4)，原本是UNIX系統用來表示時間的方式，不過UNIX時間只計算到秒，而這個數字值是計算到毫秒，現在很常用於在程式中計算時間之用。有兩種方式可以獲得這個數字值:

```js
//很常用，正號(+)是強制轉換為數字的語法
const timeInMs1 = +new Date()

//因為是靜態方法，不常見
const timeInMs2 = Date.now()
```

> 註: 使用`Date`物件來評估或測量JavaScript應用程式(或語句)的執行時間並不夠可靠與精確，有這個需求的話，你應該使用[performance.now](https://developer.mozilla.org/en-US/docs/Web/API/Performance/now)或[benchmark.js](https://github.com/bestiejs/benchmark.js)函式庫。

### 取得日期時間其中某個值

在得到`Date`物件實體後，可以對`Date`物件取得包含在其中的某個值，例如年、月、日、星期、時分秒等等。

我們先來看年月日與星期怎麼取得的範例:

```js
const dateObject = new Date() //Fri Jul 15 2016 16:23:49 GMT+0800 (CST)

const date = dateObject.getDate() //15
const day = dateObject.getDay()  //5
const month = dateObject.getMonth()  //6
const year = dateObject.getFullYear()  //2016
```

`Date`物件的取值方法，說實在如果直接從名稱上理解會有些不清楚，先看比較沒問題的部份:

* getDate: 取得日期的值，範圍為1-31
* getFullYear: 取得年(西元年)，為4位數數字

那麼不清楚或一眼就看得出來的部份是下面兩個:

* getDay: 取得星期幾的值，範例為0-6
* getMonth: 取得月份的值，範例為0-11

這兩個方法的回傳值，實際上是陣列索引值，也就是說如果你要轉成真正的月份與星期的值，需要有一個對照的陣列，然後用這個回傳值當作陣列索引值去轉換出來。仔細想想，會這樣設計也是合理的，因為這兩個值在不同世界地區或語言中，都有不同的代表字詞(例如台灣的"星期"，又可以稱為"週"或"禮拜"，台語也可簡稱"拜")，所以乾脆保留給設計師自行處理。以下是月份的範例:

```js
const monthNamesEn = [
  'January', 'February', 'March', 'April', 'May', 'June',
  'July', 'August', 'September', 'October', 'November', 'December'
]

const monthNamesZh = [
  '一月', '二月', '三月', '四月', '五月', '六月',
  '七月', '八月', '九月', '十月', '十一月', '十二月'
]

console.log(monthNamesEn[month]) //July
console.log(monthNamesZh[month]) //七月
```

以下則是星期的範例，注意陣列的第一個(索引值為0)是指"星期日"，有些人真的不知道"星期日"才是一週的第一天:

```js
const dayNamesEn = [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
const dayNamesZh = [ '星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']

console.log(dayNamesEn[day]) //Friday
console.log(dayNamesZh[day]) //星期五
```

時、分、秒與毫秒的取得就很標準一致，以下為範例，後面註解說明它的回傳數字的範圍:

```js
const hour = dateObject.getHours() //0-24
const minute = dateObject.getMinutes() //0-59
const second = dateObject.getSeconds() //0-59
const ms = dateObject.getMilliseconds() //0-999
```

> 註: 仔細比對一下上面範例，取得年月日星期的方法為單數英文字詞，取得時分秒毫秒的是複數英文字詞。

另外，`Date`物件中還有數個名稱中帶有UTC的取得方法，這些都是在獲取UTC(標準時間)的值。至於取得時區的方法是`getTimezoneOffset`，它是用分鐘計算的值，而且是以目前程式執行的時區為基準，所以例如以下的範例會回傳`-480`，實際上是`UTC+8`的時區:

```js
const timezoneByMins = dateObject.getTimezoneOffset() //-480

//除以60後正負相轉才是時區
const timezone = -(dateObject.getTimezoneOffset()/60)
```

> 註: 時區處理也有可能很複雜，如果有需要可以用專門處理時區的工具函式庫來協助，例如[Moment Timezone](http://momentjs.com/timezone/)。

### 設定日期時間 與 日期時間字串

在`Date`物件實體化時，給定傳入參數值可以直接設定這個`Date`物件內的內容，除了不給定內容的建構式會自動抓取瀏覽器目前的日期時間。其它兩種建構式就不多作說明了，本節的重點在於日期時間字串，

JavaScript中的日期格式字串是個頭痛的問題，目前雖然已經是標準的一員。但這標準可不是一開始就有的，而是在ES5標準後才訂定的，所以有些舊版的瀏覽器中並不一定可以這樣用，也有可能在這個瀏覽器可以這樣定義，到別的瀏覽器就不能用了。這裡有一份參考的[時間日期字串格式相容表](http://dygraphs.com/date-formats.html)。

#### 通用的格式

> 建議使用

這並不算什麼標準，只是用這樣定義的話，幾乎在每種瀏覽器品牌或版本都可以相容使用。除非你有非不得已要用其他格式的理由，通常都建議只用這種定義格式就好了，而且最好是使用固定位數的數字，實作上會方便很多:

```js
2009/07/12
2009/7/12
2009/07/12 12:34
2009/07/12 12:34:56
```

把年放在最後面也是可以，不過這方式是英文語言使用者常用的方式，請不要搞混月份與日期的位置，第一個位置是"月份":

```js
07/02/2012
7/2/2012
7/2/2012 12:34
```

如果你想要直接在字串中加上毫秒的定義，會出現嚴重的不相容性，大概只有Chrome瀏覽器認得。所以解決方式就拆開所有的值，改用使用第四種的`Date`物件建構式分別傳入每個值，或是再額外用設定值的方法`setMilliseconds`直接設定才行。

> 註: 月、日的表示如果要用兩位數字就都用兩位(例如01/01與11/04)，如果要用1到2位的數字就統一這樣用(例如1/1或11/4)。如果你要混用的話，不見得一定可以用，瀏覽器的相容性可能會有問題。

> 註: 在時間上的字串值，時與分至少都要有，而且都用兩位數字，例如01:00或12:09。

#### ISO 8601格式

> 注意: 舊版瀏覽器不相容

EMCAScript標準中定義的格式，實際上它就是ISO 8601標準，格式如下:

```
YYYY-MM-DDTHH:mm:ss.sssZ
```

YMD代表年月日，Hms代表是時分秒，這兩部份比較沒什麼太大的問題。

那麼`T`代表什麼？`T`只是分隔年月日與時分秒而已。

那麼`Z`代表什麼？`Z`是時區的意思，單純用Z代表是UTC標準時間，如果是其他時區的時間可以用`+`或`-`，再加上`HH:mm`作為時區的表達式，所以像下面的幾個日期時間字串是合於這個規定的:

```js
2016-01-01 //年月日可以只有年、年月
2016-07-15T09:00  //時間的部份至少要有時與分
2016-07-15T09:26:23.216Z
2016-07-15T09:20:37.788+08:00
```

#### RFC 2822格式

另一種格式則是RFC2822格式，它並不是一種專門用於定義日期時間格式的標準，這個標準所定義的標題名稱是"Internet Message Format"(網際網路訊息格式)，主要是用於定義Email(或RSS)的相關格式的標準，而其中有一章是關於日期時間格式的章節。所以，這種日期時間格式，主要的使用群是英文使用者，原因在它使用了英文中的月份與星期縮寫在字串，以下為格式範例:

```
ddd, DD MMM YYYY HH:mm:ss ZZ
MMM DD, YYYY
DD MMM, YYYY
```

以下是幾個範例:

```js
Mon, 15 Aug 2005 15:52:01 +0000
Thu, 18 Feb 2016 15:33:10 +0200
Jan 1, 2015
1 Jan, 2015
```

由於月份與星期都已經字串化(不是數字)，所以你可能會看到有很多種位置不同的寫法，基本上瀏覽器處理這種字串並不會太困難。RFC2822標準並沒有明確指出格式的限制，程式語言都可以很容易實作與解析這種日期字串。例如在MSDN中的說明，把它歸類為其他格式之中，與其他的日期格式使用相同的規則來作解析處理。

> 注意: 這段的內容只是說明RFC2822與ISO 8601格式定義日期時間的用法，一般情況下你只需要使用第一種的定義方式即可。

> 註: 你可能不太明白為何在設定日期時間時，還要加上星期幾的意義何在。實際上這個格式通常是用來轉換到其他的格式之用。

> 注意: JavaScript有一個內建的Date.parse()方法，它是對照使用日期時間字串的`Date`物件實體化的靜態方法，但它的不相容性相當高，建議不要使用它。

#### 設定方法

設定方法對照取得某個值的方法，除了把get變為set外，也少了星期的部份。要注意的是，除了上面說的日期時間字串(dateString)，其他的傳入參數一律只能用數字類型。

所有的set方法，都可以使用正負整數或0作為傳入值，例如以`setMonth`為例，雖然它的設定範例是0-11，例超過這個範圍後，會開始進行相加或相減，有可能會影響到年份增加或減少。傳入參數值的規則如下:

* 0: 對`setDate`方法是回傳上個月的最後一日。`setMonth`方法則是回傳一月(索引值為0)。
* 範圍內的整數: 直接設定為該數字
* 其他正整數: 日期相加
* 其他負整數: 日期相減

先看年月日的部份，這部份除了月的部份也是要用陣列的索引值外，還有一個`setDate`要特別注意:

```js
const dateObject = new Date()

dateObject.setFullYear(2015) //年，4位數字
dateObject.setMonth(0) //月，陣列索引值 0-11
dateObject.setDate(24) //日，正負整數
```

時分秒與毫秒的設定方法部份，都有類似`setDate`的規則，只要超過可設定的範例，就會開始進行相加或相減的情況，以下為簡單的範例:

```js
dateObject.setHours(2)
dateObject.setMinutes(20)
dateObject.setSeconds(60)
dateObject.setMilliseconds(150)
```

最後是`setTime`方法，它是對照傳入值為毫秒數值的`Date`物件實體化的建構式，傳入值是上面所說的由`1 January 1970 00:00:00 UTC`開始計算，到目前日期時間的毫秒數字值。

此外，設定方法中也有多個帶有UTC字串的方法，這些都是專門用於設定UTC時間用的，用法與上述類似，就不再多說。

> 注意: 由於setYear與getYear兩個方法存在了Y2K(千禧蟲危機)的問題而被棄用了，目前已改用 setFullYear與getFullYear。

### 本地化與格式化

`Date`物件中已有幾個可以輸出本地化格式字串的方法，主要有三個分別為`toLocaleString`、`toLocaleDateString`、`toLocaleTimeString`，它們會依照JavaScript程式所執行的環境(瀏覽器)進行本地化，一個簡單的範例如下:

```js
const date = new Date()

console.log(date.toLocaleString())     //2016/9/14 下午4:33:41
console.log(date.toLocaleDateString())  //2016/9/14
console.log(date.toLocaleTimeString()) //下午4:33:41
```

格式化的需求通常會用於各種不同的日期或時間的顯示，配合上面所說的獲取日期或時間中某個值的說明，就可以進行各種日期或時間的格式化。`Date`物件並沒有提供可以進行格式化(format)的標準方法，所以這部份需要程式設計師自己撰寫或使用外部函式庫來輔助，一個簡單的格式化範例如下:

```js
const now = new Date()
const months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']
const formattedDate = now.getDate() + '-' + months[now.getMonth()] + '-' + now.getFullYear()

console.log(formattedDate)
```

> 註: `toLocaleFormat()`方法是一個非標準的方法，大部份的瀏覽器品牌都不能使用。

### 日期比較

`Date`物件可以直接比較大小，這種比較是比較日期時間的早晚順序，日期時間愈晚的`Date`物件會愈大，一個簡單的範例如下:

```js
const myDate = new Date()
const today = new Date()

myDate.setFullYear(2015,8,9)

if ( myDate > today ){
  console.log('今天比2015.8.9日早')
} else {
  console.log('今天比2015.8.9日晚')
}
```

`Date`物件也可以轉為自毫秒數值的格式，它是一個由`1 January 1970 00:00:00 UTC`開始計算，到目前日期時間的毫秒數字值。要取得這個數值是使用`getTime`方法，一個簡單的範例如下:

```js
const myDate = new Date()
const today = new Date()

myDate.setFullYear(2015,8,9)

console.log(myDate.getTime())
console.log(today.getTime())
```

## 實例

### 時鐘

一個簡單的電子時鐘範例如下:

```js
function startTime(){
  const today = new Date()
  const hour  = today.getHours()
  const min   = today.getMinutes()
  const sec   = today.getSeconds()

  // 轉成字串，如果低於10，前面加上'0'
  const hourString = ( hour < 10)? ('0'+ hour) : ('' + hour)
  const minString = ( min < 10)? ('0'+ min) : ('' + min)
  const secString = ( sec < 10)? ('0'+ sec) : ('' + sec)

  document.getElementById('content').innerHTML = hourString + ':' + minString + ':' + secString
}

//重覆每秒執行
setInterval(startTime ,1000)
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://eddy-chang.gitbook.io/javascript-start-from-es6/part3/datetime.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
