Dates
模塊提供了兩種關(guān)于時(shí)間的數(shù)據(jù)類型: Date
和 DateTime
, 精度分別為天和毫秒, 都是抽象數(shù)據(jù)類型 TimeType
的子類型. 使用兩種數(shù)據(jù)類型的原因很簡(jiǎn)單: 某些操作本身很簡(jiǎn)單, 無(wú)論是從代碼上看還是邏輯上, 使用高精度的數(shù)據(jù)類型是完全沒(méi)有必要的. 例如, Date
只精確到天 (也就是說(shuō), 沒(méi)有小時(shí), 分鐘或者秒), 所以使用時(shí)就不需要考慮時(shí)區(qū), 夏令時(shí)和閏秒.
Date
和 DateTime
都不過(guò)是 Int64
的簡(jiǎn)單封裝, 僅有的一個(gè)成員變量 instant
實(shí)際上的類型是 UTInstant{P}
, 代表的是基于世界時(shí)的機(jī)器時(shí)間 [1]. Datetime
類型是 不考慮時(shí)區(qū) 的 (根據(jù) Python 的講法), 或者說(shuō)是 Java 8 里面的 本地時(shí)間. 額外的時(shí)間日期操作可以通過(guò) Timezones.jl 擴(kuò)展包來(lái)獲取, 其中的數(shù)據(jù)來(lái)自 Olsen Time Zone Database . Date
和 DateTime
遵循 ISO 8601 標(biāo)準(zhǔn). 值得注意的一點(diǎn)是, ISO 8601 關(guān)于公元前日期的處理比較特殊. 簡(jiǎn)單來(lái)說(shuō), 公元前的最后一天是公元前 1-12-31, 接下來(lái)第二天是公元 1-1-1, 所以是沒(méi)有公元 0 年存在的. 而 ISO 標(biāo)準(zhǔn)認(rèn)定, 公元前 1 年是 0 年, 所以 0000-12-21
是 0001-01-01
的前一天, -0001
是公元前 2 年, -0003
是公元前 3 年, 等等.
[1] 一般來(lái)說(shuō)有兩種常用的時(shí)間表示法, 一種是基于地球的自轉(zhuǎn)狀態(tài) (地球轉(zhuǎn)一整圈 = 1 天), 另一種基于 SI 秒 (固定的常量). 這兩種表示方法是不一樣的. 試想一下, 因?yàn)榈厍蜃赞D(zhuǎn), 基于世界時(shí)的的秒可能是不等長(zhǎng)的. 但總得來(lái)說(shuō), 基于世界時(shí)的 Date
和 DateTime
是一種簡(jiǎn)化的方案, 例如閏秒的情況不需要考慮. 這種表示時(shí)間的方案的正式名稱為世界時(shí) . 這意味著, 每一分鐘有 60 秒, 每一天有 60 小時(shí), 這樣使得關(guān)于時(shí)間的計(jì)算更自然, 簡(jiǎn)單.
Date
和 DateType
可以通過(guò)整數(shù)或者 Period
構(gòu)造, 通過(guò)直接傳入, 或者作為與特定時(shí)間的差值:
julia> DateTime(2013)
2013-01-01T00:00:00
julia> DateTime(2013,7)
2013-07-01T00:00:00
julia> DateTime(2013,7,1)
2013-07-01T00:00:00
julia> DateTime(2013,7,1,12)
2013-07-01T12:00:00
julia> DateTime(2013,7,1,12,30)
2013-07-01T12:30:00
julia> DateTime(2013,7,1,12,30,59)
2013-07-01T12:30:59
julia> DateTime(2013,7,1,12,30,59,1)
2013-07-01T12:30:59.001
julia> Date(2013)
2013-01-01
julia> Date(2013,7)
2013-07-01
julia> Date(2013,7,1)
2013-07-01
julia> Date(Dates.Year(2013),Dates.Month(7),Dates.Day(1))
2013-07-01
julia> Date(Dates.Month(7),Dates.Year(2013))
2013-07-01
Date
和 DateTime
解析是通過(guò)格式化的字符串實(shí)現(xiàn)的. 格式化的字符串是指 分隔 的或者 固定寬度 的 "字符段" 來(lái)表示一段時(shí)間, 然后傳遞給 Date
或者 DateTime
的構(gòu)造函數(shù).
使用分隔的字符段方法, 需要顯示指明分隔符, 所以 "y-m-d"
告訴解析器第一個(gè)和第二個(gè)字符段中間有一個(gè) -
, 例如 "2014-07-16"
, y
, m
和 d
字符告訴解析器每個(gè)字符段的含義.
固定寬度字符段是使用固定寬度的字符串來(lái)表示時(shí)間. 所以 "yyyymmdd"
相對(duì)應(yīng)的時(shí)間字符串為 "20140716"
.
同時(shí)字符表示的月份也可以被解析, 通過(guò)使用 u
和 U
, 分別是月份的簡(jiǎn)稱和全稱. 默認(rèn)支持英文的月份名稱, 所以 u
對(duì)應(yīng)于 Jan
, Feb
, Mar
等等, U
對(duì)應(yīng)于 January
, February
, March
等等. 然而, 同 dayname
和 monthname
一樣, 本地化的輸出也可以實(shí)現(xiàn), 通過(guò)向 Dates.MONTHTOVALUEABBR
和 Dates.MONTHTOVALUE
字典添加 locale=>Dict{UTF8String, Int}
類型的映射.
更多的解析和格式化的例子可以參考 tests/dates/io.jl .
計(jì)算兩個(gè) Date
或者 DateTime
之間的間隔是很直觀的, 考慮到他們不過(guò)是 UTInstant{Day}
和 UTInstant{Millisecond}
的簡(jiǎn)單封裝. 不同點(diǎn)是, 計(jì)算兩個(gè) Date
的時(shí)間間隔, 返回的是 Day
, 而計(jì)算 DateTime
時(shí)間間隔返回的是 Millisecond
. 同樣的, 比較兩個(gè) TimeType
本質(zhì)上是比較兩個(gè) Int64
julia> dt = Date(2012,2,29)
2012-02-29
julia> dt2 = Date(2000,2,1)
2000-02-01
julia> dump(dt)
Date
instant: UTInstant{Day}
periods: Day
value: Int64 734562
julia> dump(dt2)
Date
instant: UTInstant{Day}
periods: Day
value: Int64 730151
julia> dt > dt2
true
julia> dt != dt2
true
julia> dt + dt2
Operation not defined for TimeTypes
julia> dt * dt2
Operation not defined for TimeTypes
julia> dt / dt2
Operation not defined for TimeTypes
julia> dt - dt2
4411 days
julia> dt2 - dt
-4411 days
julia> dt = DateTime(2012,2,29)
2012-02-29T00:00:00
julia> dt2 = DateTime(2000,2,1)
2000-02-01T00:00:00
julia> dt - dt2
381110402000 milliseconds
因?yàn)?Date
和 DateTime
類型是使用 Int64
的封裝, 具體的某一部分可以通過(guò)訪問(wèn)函數(shù)來(lái)獲得. 小寫字母的獲取函數(shù)返回值為整數(shù):
julia> t = Date(2014,1,31)
2014-01-31
julia> Dates.year(t)
2014
julia> Dates.month(t)
1
julia> Dates.week(t)
5
julia> Dates.day(t)
31
大寫字母的獲取函數(shù)返回值為 Period
:
julia> Dates.Year(t)
2014 years
julia> Dates.Day(t)
31 days
如果需要一次性獲取多個(gè)字段, 可以使用符合函數(shù):
julia> Dates.yearmonth(t)
(2014,1)
julia> Dates.monthday(t)
(1,31)
julia> Dates.yearmonthday(t)
(2014,1,31)
也可以直接獲取底層的 UTInstant
或 整數(shù)數(shù)值 :
julia> dump(t)
Date
instant: UTInstant{Day}
periods: Day
value: Int64 735264
julia> t.instant
UTInstant{Day}(735264 days)
julia> Dates.value(t)
735264
查詢函數(shù)可以用來(lái)獲得關(guān)于 TimeType
的額外信息, 例如某個(gè)日期是星期幾:
julia> t = Date(2014,1,31)
2014-01-31
julia> Dates.dayofweek(t)
5
julia> Dates.dayname(t)
"Friday"
julia> Dates.dayofweekofmonth(t)
5 # 5th Friday of January
月份信息 :
julia> Dates.monthname(t)
"January"
julia> Dates.daysinmonth(t)
31
年份信息和季節(jié)信息 :
julia> Dates.isleapyear(t)
false
julia> Dates.dayofyear(t)
31
julia> Dates.quarterofyear(t)
1
julia> Dates.dayofquarter(t)
31
dayname
和 monthname
可以傳入可選參數(shù) locale
來(lái)顯示
julia> const french_daysofweek =
[1=>"Lundi",2=>"Mardi",3=>"Mercredi",4=>"Jeudi",5=>"Vendredi",6=>"Samedi",7=>"Dimanche"];
# Load the mapping into the Dates module under locale name "french"
julia> Dates.VALUETODAYOFWEEK["french"] = french_daysofweek;
julia> Dates.dayname(t;locale="french")
"Vendredi"
monthname
與之類似的, 這時(shí), Dates.VALUETOMONTH
需要加載 locale=>Dict{Int, UTF8String}
.
在使用任何一門編程語(yǔ)言/時(shí)間日期框架前, 最好了解下時(shí)間間隔是怎么處理的, 因?yàn)橛行┑胤叫枰?a rel="nofollow" rel="external nofollow" target="_blank" target="_blank">特殊的技巧.
Dates
模塊的工作方式是這樣的, 在做 period
算術(shù)運(yùn)算時(shí), 每次都做盡量小的改動(dòng). 這種方式被稱之為 日歷 算術(shù), 或者就是平時(shí)日常交流中慣用的方式. 這些到底是什么? 舉個(gè)經(jīng)典的例子: 2014 年 1 月 31 號(hào)加一月. 答案是什么? JavaScript 會(huì)得出 3月3號(hào) (假設(shè)31天). PHP 會(huì)得到 3月2號(hào) <http://stackoverflow.com/questions/5760262/php-adding-months-to-a-date-while-not-exceeding-the-last-day-of-the-month>
_ (假設(shè)30天). 事實(shí)上, 這個(gè)問(wèn)題沒(méi)有正確答案. Dates
模塊會(huì)給出 2月28號(hào)的答案. 它是怎么得出的? 試想下賭場(chǎng)的 7-7-7 賭博游戲.
設(shè)想下, 賭博機(jī)的槽不是 7-7-7, 而是年-月-日, 或者在我們的例子中, 2014-01-31. 當(dāng)你想要在這個(gè)日期上增加一個(gè)月時(shí), 對(duì)應(yīng)于月份的那個(gè)槽會(huì)增加1, 所以現(xiàn)在是 2014-02-31, 然后檢查年-月-日中的日是否超過(guò)了這個(gè)月最大的合法的數(shù)字 (28). 這種方法有什么后果呢? 我們繼續(xù)加上一個(gè)月, 2014-02-28 + Month(1) == 2014-03-28
. 什么? 你是不是期望結(jié)果是3月的最后一天? 抱歉, 不是的, 想一下 7-7-7. 因?yàn)橐淖儽M量少的槽, 所以我們?cè)谠路萆霞?, 2014-03-28, 然后就沒(méi)有然后了, 因?yàn)檫@是個(gè)合法的日期. 然而, 如果我們?cè)谠瓉?lái)的日期(2014-01-31)上加上2個(gè)月, 我們會(huì)得到預(yù)想中的 2014-03-31. 這種方式帶來(lái)的另一個(gè)問(wèn)題是損失了可交換性, 如果強(qiáng)制加法的順序的話 (也就是說(shuō),用不用的順序相加會(huì)得到不同的結(jié)果). 例如 ::
julia> (Date(2014,1,29)+Dates.Day(1)) + Dates.Month(1)
2014-02-28
julia> (Date(2014,1,29)+Dates.Month(1)) + Dates.Day(1)
2014-03-01
這是怎么回事? 第一個(gè)例子中, 我們往1月29號(hào)加上一天, 得到 2014-01-30; 然后加上一月, 得到 2014-02-30, 然后被調(diào)整到 2014-02-28. 在第二個(gè)例子中, 我們 先 加一個(gè)月, 得到 2014-02-29, 然后被調(diào)整到 2014-02-28, 然后 加一天, 得到 2014-03-01. 在處理這種問(wèn)題時(shí)的一個(gè)設(shè)計(jì)原則是, 如果有多個(gè)時(shí)間間隔, 操作的順序是按照間隔的 類型 排列的, 而不是按照他們的值大小或者出現(xiàn)順序; 這就是說(shuō), 第一個(gè)加的是 Year
, 然后是 Month
, 然后是 Week
, 等等. 所以下面的例子 是 符合可交換性的 ::
julia> Date(2014,1,29) + Dates.Day(1) + Dates.Month(1)
2014-03-01
julia> Date(2014,1,29) + Dates.Month(1) + Dates.Day(1)
2014-03-01
很麻煩? 也許吧. 一個(gè) Dates
的初級(jí)用戶該怎么辦呢? 最基本的是要清楚, 當(dāng)操作月份時(shí), 如果強(qiáng)制指明操作的順序, 可能會(huì)產(chǎn)生意想不到的結(jié)果, 其他的就沒(méi)什么了. 幸運(yùn)的是, 這基本就是所有的特殊情況了 (UT 時(shí)間已經(jīng)免除了夏令時(shí), 閏秒之類的麻煩).
時(shí)間間隔的算術(shù)運(yùn)算是很方便, 但同時(shí), 有些時(shí)間的操作是基于 日歷 或者 時(shí)間 本身的, 而不是一個(gè)固定的時(shí)間間隔. 例如假期的計(jì)算, 諸如 "紀(jì)念日 = 五月的最后一個(gè)周一", 或者 "感恩節(jié) = 十一月的第四個(gè)周四". 這些時(shí)間的計(jì)算牽涉到基于日歷的規(guī)則, 例如某個(gè)月的第一天或者最后一天, 下一個(gè)周四, 或者第一個(gè)和第三個(gè)周三, 等等.
Dates
模塊提供幾個(gè)了 調(diào)整 函數(shù), 這樣可以簡(jiǎn)單簡(jiǎn)潔的描述時(shí)間規(guī)則. 第一組是關(guān)于周, 月, 季度, 年的第一和最后一個(gè)元素. 函數(shù)參數(shù)為 TimeType
, 然后按照規(guī)則返回或者 調(diào)整 到正確的日期。
# 調(diào)整時(shí)間到相應(yīng)的周一
julia> Dates.firstdayofweek(Date(2014,7,16))
2014-07-14
# 調(diào)整時(shí)間到這個(gè)月的最后一天
julia> Dates.lastdayofmonth(Date(2014,7,16))
2014-07-31
# 調(diào)整時(shí)間到這個(gè)季度的最后一天
julia> Dates.lastdayofquarter(Date(2014,7,16))
2014-09-30
接下來(lái)一組高階函數(shù), tofirst
, tolast
, tonext
, and toprev
, 第一個(gè)參數(shù)為 DateFunction
, 第二個(gè)參數(shù) TimeType
作為起點(diǎn)日期. 一個(gè) DateFunction
類型的變量是一個(gè)函數(shù), 通常是匿名函數(shù), 這個(gè)函數(shù)接受 TimeType
作為輸入, 返回 Bool
, true
來(lái)表示是否滿足特定的條件. 例如 ::
julia> istuesday = x->Dates.dayofweek(x) == Dates.Tuesday # 如果是周二, 返回 true
(anonymous function)
julia> Dates.tonext(istuesday, Date(2014,7,13)) # 2014-07-13 is a 是周日
2014-07-15
# 同時(shí)也額外提供了一些函數(shù), 使得對(duì)星期幾之類的操作更加方便
julia> Dates.tonext(Date(2014,7,13), Dates.Tuesday)
2014-07-15
如果是復(fù)雜的時(shí)間表達(dá)式, 使用 do-block
會(huì)很方便:
julia> Dates.tonext(Date(2014,7,13)) do x
# 如果是十一月的第四個(gè)星期四, 返回 true (感恩節(jié))
Dates.dayofweek(x) == Dates.Thursday &&
Dates.dayofweekofmonth(x) == 4 &&
Dates.month(x) == Dates.November
end
2014-11-27
類似的, tofirst
和 tolast
第一個(gè)參數(shù)為 DateFunction
, 但是默認(rèn)的調(diào)整范圍位當(dāng)月, 或者可以用關(guān)鍵字參數(shù)指明調(diào)整范圍為當(dāng)年 :
julia> Dates.tofirst(istuesday, Date(2014,7,13)) # 默認(rèn)位當(dāng)月
2014-07-01
julia> Dates.tofirst(istuesday, Date(2014,7,13); of=Dates.Year)
2014-01-07
julia> Dates.tolast(istuesday, Date(2014,7,13))
2014-07-29
julia> Dates.tolast(istuesday, Date(2014,7,13); of=Dates.Year)
2014-12-30
最后一個(gè)函數(shù)為 recur
. recur
函數(shù)是向量化的調(diào)整過(guò)程, 輸入為起始和結(jié)束日期 (或者指明 StepRange
), 加上一個(gè) DateFunction
來(lái)判斷某個(gè)日期是否應(yīng)該返回. 這種情況下, DateFunction
又被經(jīng)常稱為 "包括" 函數(shù), 因?yàn)樗该髁?(通過(guò)返回 true) 某個(gè)日期是否應(yīng)該出現(xiàn)在返回的日期數(shù)組中。
# 匹茲堡大街清理日期; 從四月份到十一月份每月的第二個(gè)星期二
# 時(shí)間范圍從2014年1月1號(hào)到2015年1月1號(hào)
julia> dr = Dates.Date(2014):Dates.Date(2015);
julia> recur(dr) do x
Dates.dayofweek(x) == Dates.Tue &&
Dates.April <= Dates.month(x) <= Dates.Nov &&
Dates.dayofweekofmonth(x) == 2
end
8-element Array{Date,1}:
2014-04-08
2014-05-13
2014-06-10
2014-07-08
2014-08-12
2014-09-09
2014-10-14
2014-11-11
更多的例子和測(cè)試可以參考 test/dates/adjusters.jl .
時(shí)間間隔是從人的角度考慮的一段時(shí)間, 有時(shí)是不規(guī)則的. 想下一個(gè)月; 如果從天數(shù)上講, 不同情況下, 它可能代表 28, 29, 30, 或者 31. 或者一年可以代表 365 或者 366 天. Period
類型是 Int64
類型的簡(jiǎn)單封裝, 可以通過(guò)任何可以轉(zhuǎn)換成 Int64
類型的數(shù)據(jù)構(gòu)造出來(lái), 比如 Year(1)
或者 Month(3.0)
. 相同類型的時(shí)間間隔的行為類似于整數(shù) :
julia> y1 = Dates.Year(1)
1 year
julia> y2 = Dates.Year(2)
2 years
julia> y3 = Dates.Year(10)
10 years
julia> y1 + y2
3 years
julia> div(y3,y2)
5 years
julia> y3 - y2
8 years
julia> y3 * y2
20 years
julia> y3 % y2
0 years
julia> y1 + 20
21 years
julia> div(y3,3) # 類似于整數(shù)除法
3 years
另加詳細(xì)的信息可以參考 :mod:Dates
模塊的 API 索引.
更多建議: