Javascript 日期和時(shí)間

2023-02-17 10:49 更新

讓我們來(lái)學(xué)習(xí)一個(gè)新的內(nèi)建對(duì)象:日期(Date)。該對(duì)象存儲(chǔ)日期和時(shí)間,并提供了日期/時(shí)間的管理方法。

我們可以使用它來(lái)存儲(chǔ)創(chuàng)建/修改時(shí)間,測(cè)量時(shí)間,或者僅用來(lái)打印當(dāng)前時(shí)間。

創(chuàng)建

調(diào)用 new Date() 來(lái)創(chuàng)建一個(gè)新的 Date 對(duì)象。在調(diào)用時(shí)可以帶有一些參數(shù),如下所示:

?new Date()
?

不帶參數(shù) —— 創(chuàng)建一個(gè)表示當(dāng)前日期和時(shí)間的 Date 對(duì)象:

let now = new Date();
alert( now ); // 顯示當(dāng)前的日期/時(shí)間

?new Date(milliseconds)
?

創(chuàng)建一個(gè) Date 對(duì)象,其時(shí)間等于 1970 年 1 月 1 日 UTC+0 之后經(jīng)過(guò)的毫秒數(shù)(1/1000 秒)。

// 0 表示 01.01.1970 UTC+0
let Jan01_1970 = new Date(0);
alert( Jan01_1970 );

// 現(xiàn)在增加 24 小時(shí),得到 02.01.1970 UTC+0
let Jan02_1970 = new Date(24 * 3600 * 1000);
alert( Jan02_1970 );

傳入的整數(shù)參數(shù)代表的是自 1970-01-01 00:00:00 以來(lái)經(jīng)過(guò)的毫秒數(shù),該整數(shù)被稱為 時(shí)間戳。

這是一種日期的輕量級(jí)數(shù)字表示形式。我們通常使用 new Date(timestamp) 通過(guò)時(shí)間戳來(lái)創(chuàng)建日期,并可以使用 date.getTime() 將現(xiàn)有的 Date 對(duì)象轉(zhuǎn)化為時(shí)間戳(下文會(huì)講到)。

在 01.01.1970 之前的日期帶有負(fù)的時(shí)間戳,例如:

// 31 Dec 1969
let Dec31_1969 = new Date(-24 * 3600 * 1000);
alert( Dec31_1969 );

?new Date(datestring)
?

如果只有一個(gè)參數(shù),并且是字符串,那么它會(huì)被自動(dòng)解析。該算法與 Date.parse 所使用的算法相同,將在下文中進(jìn)行介紹。

let date = new Date("2017-01-26");
alert(date);
// 未指定具體時(shí)間,所以假定時(shí)間為格林尼治標(biāo)準(zhǔn)時(shí)間(GMT)的午夜零點(diǎn)
// 并根據(jù)運(yùn)行代碼時(shí)的用戶的時(shí)區(qū)進(jìn)行調(diào)整
// 因此,結(jié)果可能是
// Thu Jan 26 2017 11:00:00 GMT+1100 (Australian Eastern Daylight Time)
// 或
// Wed Jan 25 2017 16:00:00 GMT-0800 (Pacific Standard Time)

?new Date(year, month, date, hours, minutes, seconds, ms)
?

使用當(dāng)前時(shí)區(qū)中的給定組件創(chuàng)建日期。只有前兩個(gè)參數(shù)是必須的。

  • ?year? 應(yīng)該是四位數(shù)。為了兼容性,也接受 2 位數(shù),并將其視為 ?19xx?,例如 ?98? 與 ?1998? 相同,但強(qiáng)烈建議始終使用 4 位數(shù)。
  • ?month? 計(jì)數(shù)從 ?0?(一月)開(kāi)始,到 ?11?(十二月)結(jié)束。
  • ?date? 是當(dāng)月的具體某一天,如果缺失,則為默認(rèn)值 ?1?。
  • 如果 ?hours/minutes/seconds/ms? 缺失,則均為默認(rèn)值 ?0?。

例如:

new Date(2011, 0, 1, 0, 0, 0, 0); // 1 Jan 2011, 00:00:00
new Date(2011, 0, 1); // 同樣,時(shí)分秒等均為默認(rèn)值 0

時(shí)間度量最大精確到 1 毫秒(1/1000 秒):

let date = new Date(2011, 0, 1, 2, 3, 4, 567);
alert( date ); // 1.01.2011, 02:03:04.567

訪問(wèn)日期組件

從 Date 對(duì)象中訪問(wèn)年、月等信息有多種方式:

getFullYear()

獲取年份(4 位數(shù))

getMonth()

獲取月份,從 0 到 11

getDate()

獲取當(dāng)月的具體日期,從 1 到 31,這個(gè)方法名稱可能看起來(lái)有些令人疑惑。

getHours(),getMinutes(), getSeconds(),getMilliseconds()

獲取相應(yīng)的時(shí)間組件。

不是 ?getYear()?,而是 ?getFullYear()?

很多 JavaScript 引擎都實(shí)現(xiàn)了一個(gè)非標(biāo)準(zhǔn)化的方法 getYear()。不推薦使用這個(gè)方法。它有時(shí)候可能會(huì)返回 2 位的年份信息。永遠(yuǎn)不要使用它。要獲取年份就使用 getFullYear()。

另外,我們還可以獲取一周中的第幾天:

getDay()

獲取一周中的第幾天,從 0(星期日)到 6(星期六)。第一天始終是星期日,在某些國(guó)家可能不是這樣的習(xí)慣,但是這不能被改變。

以上的所有方法返回的組件都是基于當(dāng)?shù)貢r(shí)區(qū)的。

當(dāng)然,也有與當(dāng)?shù)貢r(shí)區(qū)的 UTC 對(duì)應(yīng)項(xiàng),它們會(huì)返回基于 UTC+0 時(shí)區(qū)的日、月、年等:getUTCFullYear(),getUTCMonth(),getUTCDay()。只需要在 "get" 之后插入 "UTC" 即可。

如果你當(dāng)?shù)貢r(shí)區(qū)相對(duì)于 UTC 有偏移,那么下面代碼會(huì)顯示不同的小時(shí)數(shù):

//  當(dāng)前日期
let date = new Date();

// 當(dāng)?shù)貢r(shí)區(qū)的小時(shí)數(shù)
alert( date.getHours() );

// 在 UTC+0 時(shí)區(qū)的小時(shí)數(shù)(非夏令時(shí)的倫敦時(shí)間)
alert( date.getUTCHours() );

除了上述給定的方法,還有兩個(gè)沒(méi)有 UTC 變體的特殊方法:

getTime()

返回日期的時(shí)間戳 —— 從 1970-1-1 00:00:00 UTC+0 開(kāi)始到現(xiàn)在所經(jīng)過(guò)的毫秒數(shù)。

getTimezoneOffset()

返回 UTC 與本地時(shí)區(qū)之間的時(shí)差,以分鐘為單位:

// 如果你在時(shí)區(qū) UTC-1,輸出 60
// 如果你在時(shí)區(qū) UTC+3,輸出 -180
alert( new Date().getTimezoneOffset() );

設(shè)置日期組件

下列方法可以設(shè)置日期/時(shí)間組件:

以上方法除了 setTime() 都有 UTC 變體,例如:setUTCHours()。

我們可以看到,有些方法可以一次性設(shè)置多個(gè)組件,比如 ?setHours?。未提及的組件不會(huì)被修改。

舉個(gè)例子:

let today = new Date();

today.setHours(0);
alert(today); // 日期依然是今天,但是小時(shí)數(shù)被改為了 0

today.setHours(0, 0, 0, 0);
alert(today); // 日期依然是今天,時(shí)間為 00:00:00。

自動(dòng)校準(zhǔn)(Autocorrection)

自動(dòng)校準(zhǔn) 是 Date 對(duì)象的一個(gè)非常方便的特性。我們可以設(shè)置超范圍的數(shù)值,它會(huì)自動(dòng)校準(zhǔn)。

舉個(gè)例子:

let date = new Date(2013, 0, 32); // 32 Jan 2013 ?!?
alert(date); // ……是 1st Feb 2013!

超出范圍的日期組件將會(huì)被自動(dòng)分配。

假設(shè)我們要在日期 “28 Feb 2016” 上加 2 天。結(jié)果可能是 “2 Mar” 或 “1 Mar”,因?yàn)榇嬖陂c年。但是我們不需要考慮這些,只需要直接加 2 天,剩下的 Date 對(duì)象會(huì)幫我們處理:

let date = new Date(2016, 1, 28);
date.setDate(date.getDate() + 2);

alert( date ); // 1 Mar 2016

這個(gè)特性經(jīng)常被用來(lái)獲取給定時(shí)間段后的日期。例如,我們想獲取“現(xiàn)在 70 秒后”的日期:

let date = new Date();
date.setSeconds(date.getSeconds() + 70);

alert( date ); // 顯示正確的日期信息

我們還可以設(shè)置 0 甚至可以設(shè)置負(fù)值。例如:

let date = new Date(2016, 0, 2); // 2016 年 1 月 2 日

date.setDate(1); // 設(shè)置為當(dāng)月的第一天
alert( date );

date.setDate(0); // 天數(shù)最小可以設(shè)置為 1,所以這里設(shè)置的是上一月的最后一天
alert( date ); // 31 Dec 2015

日期轉(zhuǎn)化為數(shù)字,日期差值

當(dāng) Date 對(duì)象被轉(zhuǎn)化為數(shù)字時(shí),得到的是對(duì)應(yīng)的時(shí)間戳,與使用 date.getTime() 的結(jié)果相同:

let date = new Date();
alert(+date); // 以毫秒為單位的數(shù)值,與使用 date.getTime() 的結(jié)果相同

有一個(gè)重要的副作用:日期可以相減,相減的結(jié)果是以毫秒為單位時(shí)間差。

這個(gè)作用可以用于時(shí)間測(cè)量:

let start = new Date(); // 開(kāi)始測(cè)量時(shí)間

// do the job
for (let i = 0; i < 100000; i++) {
  let doSomething = i * i * i;
}

let end = new Date(); // 結(jié)束測(cè)量時(shí)間

alert( `The loop took ${end - start} ms` );

Date.now()

如果我們僅僅想要測(cè)量時(shí)間間隔,我們不需要 Date 對(duì)象。

有一個(gè)特殊的方法 Date.now(),它會(huì)返回當(dāng)前的時(shí)間戳。

它相當(dāng)于 new Date().getTime(),但它不會(huì)創(chuàng)建中間的 Date 對(duì)象。因此它更快,而且不會(huì)對(duì)垃圾回收造成額外的壓力。

這種方法很多時(shí)候因?yàn)榉奖?,又或是因性能方面的考慮而被采用,例如使用 JavaScript 編寫(xiě)游戲或其他的特殊應(yīng)用場(chǎng)景。

因此這樣做可能會(huì)更好:

let start = Date.now(); // 從 1 Jan 1970 至今的時(shí)間戳

// do the job
for (let i = 0; i < 100000; i++) {
  let doSomething = i * i * i;
}

let end = Date.now(); // 完成

alert( `The loop took ${end - start} ms` ); // 相減的是時(shí)間戳,而不是日期

基準(zhǔn)測(cè)試(Benchmarking)

在對(duì)一個(gè)很耗 CPU 性能的函數(shù)進(jìn)行可靠的基準(zhǔn)測(cè)試(Benchmarking)時(shí),我們需要謹(jǐn)慎一點(diǎn)。

例如,我們想判斷以下兩個(gè)計(jì)算日期差值的函數(shù):哪個(gè)更快?

這種性能測(cè)量通常稱為“基準(zhǔn)測(cè)試(benchmark)”。

// 我們有 date1 和 date2,哪個(gè)函數(shù)會(huì)更快地返回兩者的時(shí)間差?
function diffSubtract(date1, date2) {
  return date2 - date1;
}

// or
function diffGetTime(date1, date2) {
  return date2.getTime() - date1.getTime();
}

這兩個(gè)函數(shù)做的事情完全相同,但是其中一個(gè)函數(shù)使用顯式的 date.getTime() 來(lái)獲取毫秒形式的日期,另一個(gè)則依賴于“日期 — 數(shù)字”的轉(zhuǎn)換。它們的結(jié)果是一樣的。

那么,哪個(gè)更快呢?

首先想到的方法可能是連續(xù)運(yùn)行兩者很多次,并計(jì)算所消耗的時(shí)間之差。就這個(gè)例子而言,函數(shù)過(guò)于簡(jiǎn)單,所以我們必須執(zhí)行至少 100000 次。

讓我們開(kāi)始測(cè)量:

function diffSubtract(date1, date2) {
  return date2 - date1;
}

function diffGetTime(date1, date2) {
  return date2.getTime() - date1.getTime();
}

function bench(f) {
  let date1 = new Date(0);
  let date2 = new Date();

  let start = Date.now();
  for (let i = 0; i < 100000; i++) f(date1, date2);
  return Date.now() - start;
}

alert( 'Time of diffSubtract: ' + bench(diffSubtract) + 'ms' );
alert( 'Time of diffGetTime: ' + bench(diffGetTime) + 'ms' );

看起來(lái)使用 getTime() 這種方式快得多,這是因?yàn)樗鼪](méi)有進(jìn)行類型轉(zhuǎn)換,對(duì)引擎優(yōu)化來(lái)說(shuō)更加簡(jiǎn)單。

我們得到了結(jié)論,但是這并不是一個(gè)很好的度量的例子。

想象一下當(dāng)運(yùn)行 bench(diffSubtract) 的同時(shí),CPU 還在并行處理其他事務(wù),并且這也會(huì)占用資源。然而,運(yùn)行 bench(diffGetTime) 的時(shí)候,并行處理的事務(wù)完成了。

對(duì)于現(xiàn)代多進(jìn)程操作系統(tǒng)來(lái)說(shuō),這是一個(gè)非常常見(jiàn)的場(chǎng)景。

比起第二個(gè)函數(shù),第一個(gè)函數(shù)所能使用的 CPU 資源更少。這可能導(dǎo)致錯(cuò)誤的結(jié)論。

為了得到更加可靠的度量,整個(gè)度量測(cè)試包應(yīng)該重新運(yùn)行多次。

例如,像下面的代碼這樣:

function diffSubtract(date1, date2) {
  return date2 - date1;
}

function diffGetTime(date1, date2) {
  return date2.getTime() - date1.getTime();
}

function bench(f) {
  let date1 = new Date(0);
  let date2 = new Date();

  let start = Date.now();
  for (let i = 0; i < 100000; i++) f(date1, date2);
  return Date.now() - start;
}

let time1 = 0;
let time2 = 0;

// 交替運(yùn)行 bench(diffSubtract) 和 bench(diffGetTime) 各 10 次
for (let i = 0; i < 10; i++) {
  time1 += bench(diffSubtract);
  time2 += bench(diffGetTime);
}

alert( 'Total time for diffSubtract: ' + time1 );
alert( 'Total time for diffGetTime: ' + time2 );

現(xiàn)代的 JavaScript 引擎的先進(jìn)優(yōu)化策略只對(duì)執(zhí)行很多次的 “hot code” 有效(對(duì)于執(zhí)行很少次數(shù)的代碼沒(méi)有必要優(yōu)化)。因此,在上面的例子中,第一次執(zhí)行的優(yōu)化程度不高。我們可能需要增加一個(gè)預(yù)熱步驟:

// 在主循環(huán)中增加預(yù)熱環(huán)節(jié)
bench(diffSubtract);
bench(diffGetTime);

// 開(kāi)始度量
for (let i = 0; i < 10; i++) {
  time1 += bench(diffSubtract);
  time2 += bench(diffGetTime);
}

進(jìn)行微型基準(zhǔn)測(cè)試時(shí)要小心

現(xiàn)代的 JavaScript 引擎執(zhí)行了很多優(yōu)化。與正常編寫(xiě)的代碼相比,它們可能會(huì)改變“人為編寫(xiě)的專用于測(cè)試的代碼”的執(zhí)行流程,特別是在我們對(duì)很小的代碼片段進(jìn)行基準(zhǔn)測(cè)試時(shí),例如某個(gè)運(yùn)算符或內(nèi)建函數(shù)的工作方式。因此,為了深入理解性能問(wèn)題,請(qǐng)學(xué)習(xí) JavaScript 引擎的工作原理。在那之后,你或許再也不需要進(jìn)行微型基準(zhǔn)測(cè)試了。

http://mrale.ph 提供了很多 V8 引擎相關(guān)的文章。

對(duì)字符串調(diào)用 Date.parse

Date.parse(str) 方法可以從一個(gè)字符串中讀取日期。

字符串的格式應(yīng)該為:YYYY-MM-DDTHH:mm:ss.sssZ,其中:

  • ?YYYY-MM-DD? —— 日期:年-月-日。
  • 字符 ?"T"? 是一個(gè)分隔符。
  • ?HH:mm:ss.sss? —— 時(shí)間:小時(shí),分鐘,秒,毫秒。
  • 可選字符 ?'Z'? 為 ?+-hh:mm? 格式的時(shí)區(qū)。單個(gè)字符 ?Z? 代表 UTC+0 時(shí)區(qū)。

簡(jiǎn)短形式也是可以的,比如 YYYY-MM-DD 或 YYYY-MM,甚至可以是 YYYY

Date.parse(str) 調(diào)用會(huì)解析給定格式的字符串,并返回時(shí)間戳(自 1970-01-01 00:00:00 起所經(jīng)過(guò)的毫秒數(shù))。如果給定字符串的格式不正確,則返回 NaN。

舉個(gè)例子:

let ms = Date.parse('2012-01-26T13:51:50.417-07:00');

alert(ms); // 1327611110417  (時(shí)間戳)

我們可以通過(guò)時(shí)間戳來(lái)立即創(chuàng)建一個(gè) new Date 對(duì)象:

let date = new Date( Date.parse('2012-01-26T13:51:50.417-07:00') );

alert(date);

總結(jié)

  • 在 JavaScript 中,日期和時(shí)間使用 Date 對(duì)象來(lái)表示。我們不能單獨(dú)創(chuàng)建日期或時(shí)間,?Date? 對(duì)象總是同時(shí)創(chuàng)建兩者。
  • 月份從 0 開(kāi)始計(jì)數(shù)(對(duì),一月是 0)。
  • 一周中的某一天 ?getDay()? 同樣從 0 開(kāi)始計(jì)算(0 代表星期日)。
  • 當(dāng)設(shè)置了超出范圍的組件時(shí),?Date? 會(huì)進(jìn)行自動(dòng)校準(zhǔn)。這一點(diǎn)對(duì)于日/月/小時(shí)的加減很有用。
  • 日期可以相減,得到的是以毫秒表示的兩者的差值。因?yàn)楫?dāng) ?Date? 被轉(zhuǎn)換為數(shù)字時(shí),?Date? 對(duì)象會(huì)被轉(zhuǎn)換為時(shí)間戳。
  • 使用 ?Date.now()? 可以更快地獲取當(dāng)前時(shí)間的時(shí)間戳。

和其他系統(tǒng)不同,JavaScript 中時(shí)間戳以毫秒為單位,而不是秒。

有時(shí)我們需要更加精準(zhǔn)的時(shí)間度量。JavaScript 自身并沒(méi)有測(cè)量微秒的方法(百萬(wàn)分之一秒),但大多數(shù)運(yùn)行環(huán)境會(huì)提供。例如:瀏覽器有 performance.now() 方法來(lái)給出從頁(yè)面加載開(kāi)始的以毫秒為單位的微秒數(shù)(精確到毫秒的小數(shù)點(diǎn)后三位):

alert(`Loading started ${performance.now()}ms ago`);
// 類似于 "Loading started 34731.26000000001ms ago"
// .26 表示的是微秒(260 微秒)
// 小數(shù)點(diǎn)后超過(guò) 3 位的數(shù)字是精度錯(cuò)誤,只有前三位數(shù)字是正確的

Node.js 可以通過(guò) microtime 模塊或使用其他方法。從技術(shù)上講,幾乎所有的設(shè)備和環(huán)境都允許獲取更高精度的時(shí)間數(shù)值,只不過(guò)不是使用 Date 對(duì)象。

任務(wù)


創(chuàng)建日期

重要程度: 5

創(chuàng)建一個(gè) ?Date? 對(duì)象,日期是:Feb 20, 2012, 3:12am。時(shí)區(qū)是當(dāng)?shù)貢r(shí)區(qū)。

使用 ?alert? 顯示結(jié)果。


解決方案

new Date 構(gòu)造函數(shù)默認(rèn)使用本地時(shí)區(qū)。所以唯一需要牢記的就是月份從 0 開(kāi)始計(jì)數(shù)。

所以二月對(duì)應(yīng)的數(shù)值是 1。

這是一個(gè)以數(shù)字作為日期參數(shù)的示例:

// new Date(year, month, date, hour, minute, second, millisecond)
let d1 = new Date(2012, 1, 20, 3, 12);
alert( d1 );

我們還可以從字符串創(chuàng)建日期,像這樣:

// new Date(datestring)
let d2 = new Date("2012-02-20T03:12");
alert( d2 );

顯示星期數(shù)

重要程度: 5

編寫(xiě)一個(gè)函數(shù) ?getWeekDay(date)? 以短格式來(lái)顯示一個(gè)日期的星期數(shù):‘MO’,‘TU’,‘WE’,‘TH’,‘FR’,‘SA’,‘SU’。

例如:

let date = new Date(2012, 0, 3);  // 3 Jan 2012
alert( getWeekDay(date) );        // 應(yīng)該輸出 "TU"

解決方案

date.getDay() 方法返回從星期日開(kāi)始的星期數(shù)。

我們創(chuàng)建一個(gè)關(guān)于星期的數(shù)組,這樣我們就可以通過(guò)編號(hào)獲取正確的日期名稱:

function getWeekDay(date) {
  let days = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'];

  return days[date.getDay()];
}

let date = new Date(2014, 0, 3); // 3 Jan 2014
alert( getWeekDay(date) ); // FR

歐洲的星期表示方法

重要程度: 5

歐洲國(guó)家的星期計(jì)算是從星期一(數(shù)字 1)開(kāi)始的,然后是星期二(數(shù)字 2),直到星期日(數(shù)字 7)。編寫(xiě)一個(gè)函數(shù) getLocalDay(date),并返回日期的歐洲式星期數(shù)。

let date = new Date(2012, 0, 3);  // 3 Jan 2012
alert( getLocalDay(date) );       // 星期二,應(yīng)該顯示 2

解決方案

function getLocalDay(date) {

  let day = date.getDay();

  if (day == 0) { // weekday 0 (sunday) is 7 in european
    day = 7;
  }

  return day;
}

許多天之前是哪個(gè)月幾號(hào)?

重要程度: 4

寫(xiě)一個(gè)函數(shù) getDateAgo(date, days),返回特定日期 date 往前 days 天是哪個(gè)月的哪一天。

例如,假設(shè)今天是 20 號(hào),那么 getDateAgo(new Date(), 1) 的結(jié)果應(yīng)該是 19 號(hào),getDateAgo(new Date(), 2) 的結(jié)果應(yīng)該是 18 號(hào)。

跨月、年也應(yīng)該是正確輸出:

let date = new Date(2015, 0, 2);

alert( getDateAgo(date, 1) ); // 1, (1 Jan 2015)
alert( getDateAgo(date, 2) ); // 31, (31 Dec 2014)
alert( getDateAgo(date, 365) ); // 2, (2 Jan 2014)

P.S. 函數(shù)不應(yīng)該修改給定的 date 值。


解決方案

思路很簡(jiǎn)單:從 date 中減去給定的天數(shù):

function getDateAgo(date, days) {
  date.setDate(date.getDate() - days);
  return date.getDate();
}

……但是函數(shù)不能修改 date。這一點(diǎn)很重要,因?yàn)槲覀兲峁┤掌诘耐獠看a不希望它被修改。

要實(shí)現(xiàn)這一點(diǎn),我們可以復(fù)制這個(gè)日期,就像這樣:

function getDateAgo(date, days) {
  let dateCopy = new Date(date);

  dateCopy.setDate(date.getDate() - days);
  return dateCopy.getDate();
}

let date = new Date(2015, 0, 2);

alert( getDateAgo(date, 1) ); // 1, (1 Jan 2015)
alert( getDateAgo(date, 2) ); // 31, (31 Dec 2014)
alert( getDateAgo(date, 365) ); // 2, (2 Jan 2014)

某月的最后一天?

重要程度: 5

寫(xiě)一個(gè)函數(shù) getLastDayOfMonth(year, month) 返回 month 月的最后一天。有時(shí)候是 30,有時(shí)是 31,甚至在二月的時(shí)候會(huì)是 28/29。

參數(shù):

  • ?year? —— 四位數(shù)的年份,比如 2012。
  • ?month? —— 月份,從 0 到 11。

舉個(gè)例子,?getLastDayOfMonth(2012, 1) = 29?(閏年,二月)


解決方案

讓我們使用下個(gè)月創(chuàng)建日期,但將零作為天數(shù)(day)傳遞:

function getLastDayOfMonth(year, month) {
  let date = new Date(year, month + 1, 0);
  return date.getDate();
}

alert( getLastDayOfMonth(2012, 0) ); // 31
alert( getLastDayOfMonth(2012, 1) ); // 29
alert( getLastDayOfMonth(2013, 1) ); // 28

通常,日期從 1 開(kāi)始,但從技術(shù)上講,我們可以傳遞任何數(shù)字,日期會(huì)自動(dòng)進(jìn)行調(diào)整。因此,當(dāng)我們傳遞 0 時(shí),它的意思是“一個(gè)月的第一天的前一天”,換句話說(shuō):“上個(gè)月的最后一天”。


今天過(guò)去了多少秒?

重要程度: 5

寫(xiě)一個(gè)函數(shù) getSecondsToday(),返回今天已經(jīng)過(guò)去了多少秒?

例如:如果現(xiàn)在是 10:00 am,并且沒(méi)有夏令時(shí)轉(zhuǎn)換,那么:

getSecondsToday() == 36000 // (3600 * 10)

該函數(shù)應(yīng)該在任意一天都能正確運(yùn)行。那意味著,它不應(yīng)具有“今天”的硬編碼值。


解決方案

為獲取秒數(shù),我們可以使用今天的日期和 00:00:00 這個(gè)時(shí)間創(chuàng)建一個(gè)日期,然后使用當(dāng)前時(shí)間減去該時(shí)間。

不同之處在于,從今天之初開(kāi)始算起的時(shí)間是以毫秒計(jì)算的,我們應(yīng)該將其除以 1000,進(jìn)而得到秒數(shù):

function getSecondsToday() {
  let now = new Date();

  // 使用當(dāng)前的 day/month/year 創(chuàng)建一個(gè)對(duì)象
  let today = new Date(now.getFullYear(), now.getMonth(), now.getDate());

  let diff = now - today; // ms difference
  return Math.round(diff / 1000); // make seconds
}

alert( getSecondsToday() );

另一種解決方法是獲取 hours/minutes/seconds,然后把它們轉(zhuǎn)換為秒數(shù):

function getSecondsToday() {
  let d = new Date();
  return d.getHours() * 3600 + d.getMinutes() * 60 + d.getSeconds();
};

alert( getSecondsToday() );

距離明天還有多少秒?

重要程度: 5

寫(xiě)一個(gè)函數(shù) getSecondsToTomorrow(),返回距離明天的秒數(shù)。

例如,現(xiàn)在是 23:00,那么:

getSecondsToTomorrow() == 3600

P.S. 該函數(shù)應(yīng)該在任意一天都能正確運(yùn)行。那意味著,它不應(yīng)具有“今天”的硬編碼值。


解決方案

為獲取距離明天的毫秒數(shù),我們可以用“明天 00:00:00”這個(gè)日期減去當(dāng)前的日期。

首先我們生成“明天”,然后對(duì)其進(jìn)行減法操作:

function getSecondsToTomorrow() {
  let now = new Date();

  // tomorrow date
  let tomorrow = new Date(now.getFullYear(), now.getMonth(), now.getDate()+1);

  let diff = tomorrow - now; // difference in ms
  return Math.round(diff / 1000); // convert to seconds
}

另一種解法:

function getSecondsToTomorrow() {
  let now = new Date();
  let hour = now.getHours();
  let minutes = now.getMinutes();
  let seconds = now.getSeconds();
  let totalSecondsToday = (hour * 60 + minutes) * 60 + seconds;
  let totalSecondsInADay = 86400;

  return totalSecondsInADay - totalSecondsToday;
}

請(qǐng)注意,很多國(guó)家有夏令時(shí)(DST),因此他們的一天可能有 23 小時(shí)或者 25 小時(shí)。我們對(duì)這些天數(shù)要區(qū)別對(duì)待。


格式化相對(duì)日期

重要程度: 4

寫(xiě)一個(gè)函數(shù) formatDate(date),能夠?qū)?nbsp;date 進(jìn)行如下格式化:

  • 如果 ?date? 距離現(xiàn)在不到 1 秒,輸出 ?"right now"?。
  • 否則,如果 ?date? 距離現(xiàn)在不到 1 分鐘,輸出 ?"n sec. ago"?。
  • 否則,如果不到 1 小時(shí),輸出 ?"m min. ago"?。
  • 否則,以 ?"DD.MM.YY HH:mm"? 格式輸出完整日期。即:?"day.month.year hours:minutes"?,全部以兩位數(shù)格式表示,例如:?31.12.16 10:00?。

舉個(gè)例子:

alert( formatDate(new Date(new Date - 1)) ); // "right now"

alert( formatDate(new Date(new Date - 30 * 1000)) ); // "30 sec. ago"

alert( formatDate(new Date(new Date - 5 * 60 * 1000)) ); // "5 min. ago"

// 昨天的日期,例如 31.12.16 20:00
alert( formatDate(new Date(new Date - 86400 * 1000)) );

解決方案

為了獲取 date 距離當(dāng)前時(shí)間的間隔 —— 我們將兩個(gè)日期相減。

function formatDate(date) {
  let diff = new Date() - date; // 以毫秒表示的差值

  if (diff < 1000) { // 少于 1 秒
    return 'right now';
  }

  let sec = Math.floor(diff / 1000); // 將 diff 轉(zhuǎn)換為秒

  if (sec < 60) {
    return sec + ' sec. ago';
  }

  let min = Math.floor(diff / 60000); // 將 diff 轉(zhuǎn)換為分鐘
  if (min < 60) {
    return min + ' min. ago';
  }

  // 格式化 date
  // 將前置 0 加到一位數(shù) day/month/hours/minutes 前
  let d = date;
  d = [
    '0' + d.getDate(),
    '0' + (d.getMonth() + 1),
    '' + d.getFullYear(),
    '0' + d.getHours(),
    '0' + d.getMinutes()
  ].map(component => component.slice(-2)); // 得到每個(gè)組件的后兩位

  // 將時(shí)間信息和日期組合在一起
 return d.slice(0, 3).join('.') + ' ' + d.slice(3).join(':');
}

alert( formatDate(new Date(new Date - 1)) ); // "right now"

alert( formatDate(new Date(new Date - 30 * 1000)) ); // "30 sec. ago"

alert( formatDate(new Date(new Date - 5 * 60 * 1000)) ); // "5 min. ago"

// 昨天的日期如:31.12.2016 20:00
alert( formatDate(new Date(new Date - 86400 * 1000)) );

另一種解法:

function formatDate(date) {
  let dayOfMonth = date.getDate();
  let month = date.getMonth() + 1;
  let year = date.getFullYear();
  let hour = date.getHours();
  let minutes = date.getMinutes();
  let diffMs = new Date() - date;
  let diffSec = Math.round(diffMs / 1000);
  let diffMin = diffSec / 60;
  let diffHour = diffMin / 60;

  // 格式化
  year = year.toString().slice(-2);
  month = month < 10 ? '0' + month : month;
  dayOfMonth = dayOfMonth < 10 ? '0' + dayOfMonth : dayOfMonth;
  hour = hour < 10 ? '0' + hour : hour;
  minutes = minutes < 10 ? '0' + minutes : minutes;

  if (diffSec < 1) {
    return 'right now';
  } else if (diffMin < 1) {
    return `${diffSec} sec. ago`
  } else if (diffHour < 1) {
    return `${diffMin} min. ago`
  } else {
    return `${dayOfMonth}.${month}.${year} ${hour}:${minutes}`
  }
}


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)