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