英文 | https://blog.logrocket.com/javascript-date-libraries/從理論上講,向您的應(yīng)用程序添加工作日期似乎總是一件容易的事,除非您嘗試這樣做??偸菚?huì)遇到麻煩,無(wú)論是在嘗試使您的網(wǎng)站國(guó)際化時(shí)的時(shí)區(qū)問(wèn)題,還是以所需格式顯示日期的問(wèn)題,甚至試圖對(duì)它們進(jìn)行一些基本的算術(shù)運(yùn)算。不幸的是,JavaScript并沒(méi)有真正準(zhǔn)備好處理日期。這有點(diǎn)諷刺,因?yàn)樗_實(shí)有一個(gè)日期對(duì)象,我們可以用它來(lái)進(jìn)行一些基本的操作。我說(shuō)JavaScript還沒(méi)準(zhǔn)備好是什么意思呢?我的意思是,這個(gè)對(duì)象的API不夠豐富,不能滿(mǎn)足我們的日常需求,它沒(méi)有您所期望的來(lái)自這樣一個(gè)對(duì)象的高級(jí)支持來(lái)處理國(guó)際化、時(shí)區(qū)支持等問(wèn)題。這就是我將要回顧的庫(kù)發(fā)揮作用的地方。這七個(gè)日期庫(kù)在JavaScript的日期對(duì)象之上添加了抽象層,并使其實(shí)際有用。Moment.js是Node.js和Vanilla JavaScript(使其與瀏覽器兼容)的最古老,最知名的日期相關(guān)庫(kù)之一。因此,他們有時(shí)間使其與許多最常見(jiàn)的Web開(kāi)發(fā)實(shí)用程序兼容,例如:Bower
requireJS
Nuget
Browsify
TypeScript
meteor
它提供了一個(gè)很棒的API,而不需要修改Date對(duì)象的原始原型,相反,他們?cè)贒ate對(duì)象周?chē)鷦?chuàng)建了一個(gè)包裝器對(duì)象,以確保不破壞原始名稱(chēng)空間。作為一個(gè)簡(jiǎn)單的示例,下面是如何設(shè)置當(dāng)前日期。加上7天,然后減去一個(gè)月,最后設(shè)置年份和時(shí)間值。所有這些都在一行易于閱讀的代碼中,就像這樣:moment().add(7, 'days').subtract(1, 'months').year(2009).hours(0).minutes(0).seconds(0);
它們支持20多個(gè)不同的地區(qū),因此如果您想解決國(guó)際化問(wèn)題,最好使用moment.js。一個(gè)特定庫(kù)的成功和整體實(shí)用性的一個(gè)很好的衡量標(biāo)準(zhǔn)是檢查存在于該庫(kù)周?chē)牟寮?擴(kuò)展生態(tài)系統(tǒng)。就moment.js而言,有22個(gè)官方支持的版本。乍一看,這可能不是一個(gè)很大的數(shù)目,但是如果您考慮所有這些庫(kù)都是圍繞一個(gè)對(duì)象(Date對(duì)象)設(shè)計(jì)的,那么擁有超過(guò)20個(gè)插件無(wú)疑是一個(gè)很好的跡象。你可以在他們的文檔頁(yè)面上找到完整的插件列表,但其中一些最有趣的是:它允許你像Twitter一樣格式化你的日期和時(shí)間(注意他們是如何用一個(gè)字母縮寫(xiě)他們的時(shí)間日期的,比如1h表示“1小時(shí)前”,2d表示“2天前”)。有了這個(gè)插件,你可以做一些簡(jiǎn)單的事情:moment().subtract(6, 'days').twitterShort();
當(dāng)你試圖以一種特定的方式顯示你的日期時(shí),你寫(xiě)了多少次日期格式?類(lèi)似于YYYY-MM-dd或類(lèi)似的變體。但是我們總是手動(dòng)編寫(xiě)它,然后庫(kù)相應(yīng)地格式化日期。這個(gè)插件不是格式化日期,而是解析一個(gè)日期字符串并返回實(shí)際的格式供您重用。var format = moment.parseFormat('Thursday, February 6th, 2014 9:20pm');
這個(gè)特別的插件非常適合將動(dòng)態(tài)行為添加到您的格式化邏輯中,例如,使格式化成為動(dòng)態(tài)的,并允許您的用戶(hù)輸入一個(gè)日期示例,而不是讓他們學(xué)習(xí)如何配置自己的格式。這實(shí)際上是對(duì)JavaScript的setTimeInterval和setTimeout函數(shù)的完全重寫(xiě),允許您混合moment的語(yǔ)法并創(chuàng)建一個(gè)更強(qiáng)大的計(jì)時(shí)器。var timer = moment.duration(5, "seconds").timer({loop: true},function() {
與以下內(nèi)容相比,以下內(nèi)容更容易閱讀和理解:setInterval(function() { }, 5000)
date-fns被宣傳為大量的數(shù)據(jù)庫(kù),它試圖提供比Moment.js更好的體驗(yàn)。它的API非常廣泛,有140多個(gè)不同的與時(shí)間相關(guān)的函數(shù),它們的創(chuàng)建者和貢獻(xiàn)者希望讓您從使用Moment切換到自己的時(shí)間管理解決方案。所有函數(shù)都是按文件分組的,允許您導(dǎo)入您需要的東西,而不必為您真正使用的兩個(gè)方法而使項(xiàng)目變得臃腫。這對(duì)于需要優(yōu)化每js行字節(jié)數(shù)的前端開(kāi)發(fā)人員特別有用,因?yàn)槊總€(gè)字節(jié)都很重要。對(duì)于Node.js開(kāi)發(fā)人員來(lái)說(shuō),這對(duì)于保持導(dǎo)入仍然很有用,并且需要更有條理。
與其他庫(kù)(查看您的Moment.js)不同,date-fns返回的日期對(duì)象是不可變的,這有助于避免不必要的修改和無(wú)數(shù)個(gè)小時(shí)的調(diào)試。
FP子模塊提供了一組與FP相關(guān)的功能,可以幫助您輕松地通過(guò)幾行代碼來(lái)組成復(fù)雜的行為。
它們總共支持57個(gè)不同的地區(qū),所以如果您的目標(biāo)是國(guó)際化,這里是另一個(gè)不錯(cuò)的選擇!
他們有TypeScript和流支持。
最后但并非最不重要的是,他們的文檔非常詳細(xì),這是我從一個(gè)庫(kù)中一直欣賞的東西,特別是那些具有如此廣泛的API的文檔。
讓我們快速運(yùn)行一些代碼示例,讓您了解這個(gè)庫(kù)的獨(dú)特之處。const { addYears, formatWithOptions } = require('date-fns/fp')const { es } = require('date-fns/locale')
const addFiveYears = addYears(5)
const dateToString = formatWithOptions({ locale: es }, 'd MMMM yyyy')
const dates = [ new Date(2017, 0, 1), new Date(2017, 1, 11), new Date(2017, 6, 2)]
const toUpper = arg => String(arg).toUpperCase()
const formattedDates = dates.map(addFiveYears).map(dateToString).map(toUpper)
這個(gè)例子展示了我上面提到的兩點(diǎn):每個(gè)文件的功能機(jī)制,使您只需要實(shí)際需要的位(兩個(gè)導(dǎo)入都利用了示例中的位)和功能編程輔助函數(shù)。請(qǐng)注意,如何使用這兩個(gè)導(dǎo)入的函數(shù)(addYears和formatWithOptions)在最后一行(這兩個(gè)函數(shù)以及toUpper匿名函數(shù))中組成整個(gè)過(guò)程。關(guān)于代碼的一個(gè)簡(jiǎn)短說(shuō)明:盡管它與庫(kù)主頁(yè)上顯示的示例相同,但我必須使它與Node.js兼容。luxon是一個(gè)非常有趣的項(xiàng)目,因?yàn)槿绻榭此膗rl,它就位于moment.js項(xiàng)目下,那么它為什么會(huì)存在呢?你可以從作者自己那里讀到整個(gè)故事,但主要的要點(diǎn)是,它試圖成為一個(gè)更好的版本,然而:與它的前輩(如果我們可以調(diào)用Moment.js)相比,Luxon的主要區(qū)別之一是,所有對(duì)象都是不可變的,你可能會(huì)說(shuō)Moment.js做出了一個(gè)糟糕的決定,讓他們的對(duì)象發(fā)生了變化,而社區(qū)里的每個(gè)人都想盡辦法去解決這個(gè)問(wèn)題。var m1 = moment();var m2 = m1.add(1, 'hours');m1.valueOf() === m2.valueOf();
var d1 = DateTime.local();var d2 = d1.plus({ hours: 1 });d1.valueOf() === d2.valueOf();
在上面的例子中,您可以看到這種不同的作用,而對(duì)于moment.js(第一個(gè)例子),您會(huì)遇到那種“問(wèn)題”(這里有引號(hào),因?yàn)閱?wèn)題只在您不注意的情況下才會(huì)發(fā)生),因?yàn)閍dd方法會(huì)變異m1,而不是返回m2上的新值,luxon的api會(huì)阻止您遇到這個(gè)問(wèn)題,因?yàn)閜lus在d2上返回一個(gè)新對(duì)象,而不是修改d1。與moments .js的另一個(gè)重大區(qū)別是,國(guó)際化是基于來(lái)自瀏覽器的Intl API。本質(zhì)上,這意味著:如果您是Moment.js用戶(hù),您可能會(huì)對(duì)其他更改感興趣,所以一定要查看他們的文檔。DayJS試圖成為Moment.js的縮小版(人們?cè)谶@里看到這些模式了嗎?)。對(duì)于聲稱(chēng)擁有Moment.js相同API并將其文件大小減少97%的庫(kù),可以說(shuō)些什么。沒(méi)錯(cuò),Moment.js完整壓縮文件的總重量為67,9Kb,而DayJS壓縮文件的大小僅為2Kb。太瘋狂了,但是他們支持國(guó)際化,插件和其他所有功能。當(dāng)每個(gè)人都在使用下劃線時(shí),你可以把dayjs看作lodash。lodash出現(xiàn)在圖片中,提出了一個(gè)類(lèi)似的聲明,他們有一個(gè)非常相似的api,占用空間更小,他們總是試圖節(jié)省盡可能多的字節(jié),以減少加載時(shí)間。就像Moment.js一樣,該庫(kù)有大約20個(gè)正式支持的插件,您可以在其文檔中查看。最后,雖然這個(gè)庫(kù)似乎是它所聲稱(chēng)的一切,開(kāi)發(fā)人員開(kāi)始采用它,如下面的下載趨勢(shì)圖所示:
MomentJS的月下載量仍然很高,因?yàn)樗呀?jīng)有8年多的歷史了(與DayJS 1年多的歷史相比)。這將需要一些時(shí)間,但如果MomentJS不適應(yīng)(也許Luxon可能是一個(gè)選擇?)它將最終被這個(gè)新來(lái)的家伙所取代。由于API實(shí)際上與MomentJS是一樣的,所以展示代碼示例是沒(méi)有意義的,如果您需要特殊的東西,只需檢查他們的官方文檔,如果您擔(dān)心加載時(shí)間和數(shù)據(jù)使用(對(duì)于移動(dòng)web開(kāi)發(fā)人員來(lái)說(shuō)是一個(gè)大問(wèn)題),則切換到DayJS。為了稍微改變一下,下一個(gè)庫(kù)并不是作為MomentJS的替代品創(chuàng)建的(我知道,令人震驚!),相反,它只有一個(gè)任務(wù),而且做得非常好。MS的目標(biāo)是將任何類(lèi)型的日期格式轉(zhuǎn)換為毫秒并返回。這是一個(gè)非常狹窄的用例,我知道,但你知道,日期轉(zhuǎn)化為毫秒都有其優(yōu)點(diǎn),尤其是如果你想做比較,和某些形式的算術(shù)運(yùn)算(這很容易添加1000毫秒數(shù),比說(shuō)你需要添加1秒約會(huì)對(duì)象)。換句話說(shuō),有了這個(gè)庫(kù),你可以這樣做:ms('2 days') ms('1d') ms('10h') ms('2.5 hrs') ms('2h') ms('1m') ms('5s')
ms(60000) ms(2 * 60000) ms(-3 * 60000)
考慮到這個(gè)庫(kù)每周有超過(guò)3000萬(wàn)次的下載,我認(rèn)為它涵蓋了一個(gè)非常具體但又很常見(jiàn)的用例。所以,如果這是你的時(shí)間相關(guān)邏輯所需要的全部,考慮檢查一下。另一個(gè)通用時(shí)間管理庫(kù),旨在取代MomentJS和其他從上述名單。它試圖通過(guò)避免對(duì)Date對(duì)象技術(shù)使用相同的包裝來(lái)區(qū)別于MomentJS等其他技術(shù),相反,它從頭實(shí)現(xiàn)了整個(gè)邏輯。這樣更好嗎?這個(gè)問(wèn)題還沒(méi)有定論,它可能會(huì)給維護(hù)人員更多的空間來(lái)處理基本概念,并以日期對(duì)象無(wú)法處理的方式扭曲它們。也就是說(shuō),這個(gè)庫(kù)比momentjs(大約40kb)要小一些,而momentjs離dayjs的2kb很小。但是,它確實(shí)提供了不變性(沖擊)和特定于域的類(lèi),從而幫助開(kāi)發(fā)人員編寫(xiě)更清晰的OOP代碼。js-joda的一個(gè)非常有趣的特性是它提供的可擴(kuò)展性。由于不可變對(duì)象實(shí)際上沒(méi)有setter,這個(gè)庫(kù)提供了with方法,它返回一個(gè)帶有新值集的新對(duì)象。如果不設(shè)置一個(gè)值,而是設(shè)置一個(gè)特殊對(duì)象,那么可以擴(kuò)展獲取值的方式。var nextOrSameEvenDay = { adjustInto: function(t) { return t.dayOfMonth() % 2 === 0 ? t : t.plusDays(1); }};
LocalDateTime.parse("2012-12-23T12:00").with(nextOrSameEvenDay); LocalDate.parse("2012-12-24").with(nextOrSameEvenDay);
基本上,我們正在解析一個(gè)日期字符串(沒(méi)什么特別的),然后使用自定義對(duì)象(請(qǐng)注意特殊的into方法),我們正在添加一個(gè)非常隨機(jī)的行為,但仍然有效。如果您正在尋找這種靈活性,我個(gè)人會(huì)推薦該庫(kù),否則,上面已經(jīng)介紹了一些更好的方法。我不能漏掉一個(gè)叫做時(shí)空的時(shí)間庫(kù),這一個(gè)有一個(gè)非常有趣的重點(diǎn):時(shí)區(qū)。是的,它提供了一個(gè)類(lèi)似于momentjs的API,還提供了不可變的對(duì)象(哎呀!),但是這個(gè)庫(kù)的主要目的是幫助您輕松地處理時(shí)區(qū)。請(qǐng)記住,在處理任何類(lèi)型的國(guó)際化開(kāi)發(fā)時(shí),時(shí)區(qū)往往是主要問(wèn)題之一,所以這看起來(lái)很有前途。const spacetime = require('spacetime')
let d = spacetime('March 1 2012', 'America/New_York')d = d.time('4:20pm')console.log(d.time())
d = d.goto('America/Los_Angeles')console.log(d.time())
那非常容易,不是嗎?不過(guò),有關(guān)時(shí)空的說(shuō)明是,它不像Luxon那樣依賴(lài)Intl API,因此該庫(kù)本身并不是真正的輕量級(jí)設(shè)備,大約40Kb。話雖如此,時(shí)區(qū)仍然遵循IANA命名約定,這對(duì)于標(biāo)準(zhǔn)化代碼非常有用。關(guān)于我們最后一個(gè)圖書(shū)館的另一件很酷的事情是,它可以在適用的情況下遵守夏令時(shí),所以會(huì)發(fā)生以下情況:d = d.goto('Eastern Time') // "America/New_York"d = d.goto('PST') // automatically becomes 'PDT' in the summer
還有其他的方法,如賽季返回當(dāng)前季節(jié)為特定日期(夏季/冬季/返回春季/秋季)以及hasDST inDST它返回一個(gè)時(shí)區(qū)是否使用日光節(jié)約時(shí)間和如果它活躍在特定日期/時(shí)間配置。最后,擴(kuò)展時(shí)空非常簡(jiǎn)單,這有助于添加額外的功能,如:spacetime.extend({ isHappyHour: function() { return this.hour() === 16 }})d = d.time('4:15pm')console.log(d.isHappyHour())
這是處理日期和時(shí)間的7種最常見(jiàn)的JavaScript庫(kù)。鑒于在線上已有的現(xiàn)有信息和使用情況數(shù)據(jù),如果您必須處理非常復(fù)雜的與日期時(shí)間相關(guān)的功能。那么我的建議是使用MomentJS(因?yàn)樗墙?jīng)過(guò)時(shí)間驗(yàn)證的經(jīng)典庫(kù)),或者只是嘗試全新的和 更快的模型:DayJS,相同的API,更好的對(duì)象管理和更小的占地面積。另一方面,如果您有非常特定的需求,請(qǐng)考慮使用ms或Spacetime。