Julia 日期和時(shí)間

2018-08-12 21:26 更新

日期和時(shí)間

Dates 模塊提供了兩種關(guān)于時(shí)間的數(shù)據(jù)類型: DateDateTime, 精度分別為天和毫秒, 都是抽象數(shù)據(jù)類型 TimeType 的子類型. 使用兩種數(shù)據(jù)類型的原因很簡(jiǎn)單: 某些操作本身很簡(jiǎn)單, 無(wú)論是從代碼上看還是邏輯上, 使用高精度的數(shù)據(jù)類型是完全沒(méi)有必要的. 例如, Date 只精確到天 (也就是說(shuō), 沒(méi)有小時(shí), 分鐘或者秒), 所以使用時(shí)就不需要考慮時(shí)區(qū), 夏令時(shí)和閏秒.

DateDateTime 都不過(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 . DateDateTime 遵循 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-210001-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í)的 DateDateTime 是一種簡(jiǎn)化的方案, 例如閏秒的情況不需要考慮. 這種表示時(shí)間的方案的正式名稱為世界時(shí) . 這意味著, 每一分鐘有 60 秒, 每一天有 60 小時(shí), 這樣使得關(guān)于時(shí)間的計(jì)算更自然, 簡(jiǎn)單.

構(gòu)造函數(shù)

DateDateType 可以通過(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

DateDateTime 解析是通過(guò)格式化的字符串實(shí)現(xiàn)的. 格式化的字符串是指 分隔 的或者 固定寬度 的 "字符段" 來(lái)表示一段時(shí)間, 然后傳遞給 Date 或者 DateTime 的構(gòu)造函數(shù).

使用分隔的字符段方法, 需要顯示指明分隔符, 所以 "y-m-d" 告訴解析器第一個(gè)和第二個(gè)字符段中間有一個(gè) -, 例如 "2014-07-16", y, md 字符告訴解析器每個(gè)字符段的含義.

固定寬度字符段是使用固定寬度的字符串來(lái)表示時(shí)間. 所以 "yyyymmdd" 相對(duì)應(yīng)的時(shí)間字符串為 "20140716".

同時(shí)字符表示的月份也可以被解析, 通過(guò)使用 uU, 分別是月份的簡(jiǎn)稱和全稱. 默認(rèn)支持英文的月份名稱, 所以 u 對(duì)應(yīng)于 Jan, Feb, Mar 等等, U 對(duì)應(yīng)于 January, February, March 等等. 然而, 同 daynamemonthname 一樣, 本地化的輸出也可以實(shí)現(xiàn), 通過(guò)向 Dates.MONTHTOVALUEABBRDates.MONTHTOVALUE 字典添加 locale=>Dict{UTF8String, Int} 類型的映射.

更多的解析和格式化的例子可以參考 tests/dates/io.jl .

時(shí)間間隔/比較

計(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

訪問(wèn)函數(shù)

因?yàn)?DateDateTime 類型是使用 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ù)

查詢函數(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

daynamemonthname 可以傳入可選參數(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}.

時(shí)間間隔算術(shù)運(yùn)算

在使用任何一門編程語(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í), 閏秒之類的麻煩).

調(diào)整函數(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

類似的, tofirsttolast 第一個(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í)間, 有時(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 索引.

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

掃描二維碼

下載編程獅App

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

編程獅公眾號(hào)