Julia 日期和時間

2018-08-12 21:26 更新

日期和時間

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

DateDateTime 都不過是 Int64 的簡單封裝, 僅有的一個成員變量 instant 實際上的類型是 UTInstant{P}, 代表的是基于世界時的機器時間 [1]. Datetime 類型是 不考慮時區(qū) 的 (根據(jù) Python 的講法), 或者說是 Java 8 里面的 本地時間. 額外的時間日期操作可以通過 Timezones.jl 擴展包來獲取, 其中的數(shù)據(jù)來自 Olsen Time Zone Database . DateDateTime 遵循 ISO 8601 標準. 值得注意的一點是, ISO 8601 關于公元前日期的處理比較特殊. 簡單來說, 公元前的最后一天是公元前 1-12-31, 接下來第二天是公元 1-1-1, 所以是沒有公元 0 年存在的. 而 ISO 標準認定, 公元前 1 年是 0 年, 所以 0000-12-210001-01-01 的前一天, -0001 是公元前 2 年, -0003 是公元前 3 年, 等等.

[1] 一般來說有兩種常用的時間表示法, 一種是基于地球的自轉狀態(tài) (地球轉一整圈 = 1 天), 另一種基于 SI 秒 (固定的常量). 這兩種表示方法是不一樣的. 試想一下, 因為地球自轉, 基于世界時的的秒可能是不等長的. 但總得來說, 基于世界時的 DateDateTime 是一種簡化的方案, 例如閏秒的情況不需要考慮. 這種表示時間的方案的正式名稱為世界時 . 這意味著, 每一分鐘有 60 秒, 每一天有 60 小時, 這樣使得關于時間的計算更自然, 簡單.

構造函數(shù)

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

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

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

固定寬度字符段是使用固定寬度的字符串來表示時間. 所以 "yyyymmdd" 相對應的時間字符串為 "20140716".

同時字符表示的月份也可以被解析, 通過使用 uU, 分別是月份的簡稱和全稱. 默認支持英文的月份名稱, 所以 u 對應于 Jan, Feb, Mar 等等, U 對應于 January, February, March 等等. 然而, 同 daynamemonthname 一樣, 本地化的輸出也可以實現(xiàn), 通過向 Dates.MONTHTOVALUEABBRDates.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

訪問函數(shù)

因為 DateDateTime 類型是使用 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ù)

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

daynamemonthname 可以傳入可選參數(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)免除了夏令時, 閏秒之類的麻煩).

調(diào)整函數(shù)

時間間隔的算術運算是很方便, 但同時, 有些時間的操作是基于 日歷 或者 時間 本身的, 而不是一個固定的時間間隔. 例如假期的計算, 諸如 "紀念日 = 五月的最后一個周一", 或者 "感恩節(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

類似的, tofirsttolast 第一個參數(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 索引.

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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號