鍍金池/ 教程/ Java/ 日期和時間
類型轉(zhuǎn)換和類型提升
方法
嵌入式 Julia
交互
調(diào)用 C 和 Fortran 代碼
類型
代碼性能優(yōu)化
多維數(shù)組
元編程
函數(shù)
簡介
線性代數(shù)
與其它語言的區(qū)別
數(shù)學(xué)運算和基本函數(shù)
構(gòu)造函數(shù)
控制流
常見問題
并行計算
擴展包
開發(fā)擴展包
開始
字符串
運行外部程序
變量的作用域
模塊
網(wǎng)絡(luò)和流
代碼樣式
復(fù)數(shù)和分?jǐn)?shù)
可空類型
整數(shù)和浮點數(shù)
變量
日期和時間

日期和時間

Dates 模塊提供了兩種關(guān)于時間的數(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 標(biāo)準(zhǔn). 值得注意的一點是, ISO 8601 關(guān)于公元前日期的處理比較特殊. 簡單來說, 公元前的最后一天是公元前 1-12-31, 接下來第二天是公元 1-1-1, 所以是沒有公元 0 年存在的. 而 ISO 標(biāo)準(zhǔn)認(rèn)定, 公元前 1 年是 0 年, 所以 0000-12-210001-01-01 的前一天, -0001 是公元前 2 年, -0003 是公元前 3 年, 等等.

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

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

DateDateType 可以通過整數(shù)或者 Period 構(gòu)造, 通過直接傳入, 或者作為與特定時間的差值:

 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 的構(gòu)造函數(shù).

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

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

同時字符表示的月份也可以被解析, 通過使用 uU, 分別是月份的簡稱和全稱. 默認(rèn)支持英文的月份名稱, 所以 u 對應(yīng)于 Jan, Feb, Mar 等等, U 對應(yīng)于 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ù)可以用來獲得關(guān)于 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}.

時間間隔算術(shù)運算

在使用任何一門編程語言/時間日期框架前, 最好了解下時間間隔是怎么處理的, 因為有些地方需要特殊的技巧.

Dates 模塊的工作方式是這樣的, 在做 period 算術(shù)運算時, 每次都做盡量小的改動. 這種方式被稱之為 日歷 算術(shù), 或者就是平時日常交流中慣用的方式. 這些到底是什么? 舉個經(jīng)典的例子: 2014 年 1 月 31 號加一月. 答案是什么? JavaScript 會得出 3月3號 (假設(shè)31天). PHP 會得到 3月2號 <http://stackoverflow.com/questions/5760262/php-adding-months-to-a-date-while-not-exceeding-the-last-day-of-the-month>_ (假設(shè)30天). 事實上, 這個問題沒有正確答案. Dates 模塊會給出 2月28號的答案. 它是怎么得出的? 試想下賭場的 7-7-7 賭博游戲.

設(shè)想下, 賭博機的槽不是 7-7-7, 而是年-月-日, 或者在我們的例子中, 2014-01-31. 當(dāng)你想要在這個日期上增加一個月時, 對應(yīng)于月份的那個槽會增加1, 所以現(xiàn)在是 2014-02-31, 然后檢查年-月-日中的日是否超過了這個月最大的合法的數(shù)字 (28). 這種方法有什么后果呢? 我們繼續(xù)加上一個月, 2014-02-28 + Month(1) == 2014-03-28. 什么? 你是不是期望結(jié)果是3月的最后一天? 抱歉, 不是的, 想一下 7-7-7. 因為要改變盡量少的槽, 所以我們在月份上加1, 2014-03-28, 然后就沒有然后了, 因為這是個合法的日期. 然而, 如果我們在原來的日期(2014-01-31)上加上2個月, 我們會得到預(yù)想中的 2014-03-31. 這種方式帶來的另一個問題是損失了可交換性, 如果強制加法的順序的話 (也就是說,用不用的順序相加會得到不同的結(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

這是怎么回事? 第一個例子中, 我們往1月29號加上一天, 得到 2014-01-30; 然后加上一月, 得到 2014-02-30, 然后被調(diào)整到 2014-02-28. 在第二個例子中, 我們 加一個月, 得到 2014-02-29, 然后被調(diào)整到 2014-02-28, 然后 加一天, 得到 2014-03-01. 在處理這種問題時的一個設(shè)計原則是, 如果有多個時間間隔, 操作的順序是按照間隔的 類型 排列的, 而不是按照他們的值大小或者出現(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 的初級用戶該怎么辦呢? 最基本的是要清楚, 當(dāng)操作月份時, 如果強制指明操作的順序, 可能會產(chǎn)生意想不到的結(jié)果, 其他的就沒什么了. 幸運的是, 這基本就是所有的特殊情況了 (UT 時間已經(jīng)免除了夏令時, 閏秒之類的麻煩).

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

時間間隔的算術(shù)運算是很方便, 但同時, 有些時間的操作是基于 日歷 或者 時間 本身的, 而不是一個固定的時間間隔. 例如假期的計算, 諸如 "紀(jì)念日 = 五月的最后一個周一", 或者 "感恩節(jié) = 十一月的第四個周四". 這些時間的計算牽涉到基于日歷的規(guī)則, 例如某個月的第一天或者最后一天, 下一個周四, 或者第一個和第三個周三, 等等.

Dates 模塊提供幾個了 調(diào)整 函數(shù), 這樣可以簡單簡潔的描述時間規(guī)則. 第一組是關(guān)于周, 月, 季度, 年的第一和最后一個元素. 函數(shù)參數(shù)為 TimeType, 然后按照規(guī)則返回或者 調(diào)整 到正確的日期。

   # 調(diào)整時間到相應(yīng)的周一
   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

如果是復(fù)雜的時間表達(dá)式, 使用 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, 但是默認(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

最后一個函數(shù)為 recur. recur 函數(shù)是向量化的調(diào)整過程, 輸入為起始和結(jié)束日期 (或者指明 StepRange), 加上一個 DateFunction 來判斷某個日期是否應(yīng)該返回. 這種情況下, DateFunction 又被經(jīng)常稱為 "包括" 函數(shù), 因為它指明了 (通過返回 true) 某個日期是否應(yīng)該出現(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 類型的簡單封裝, 可以通過任何可以轉(zhuǎn)換成 Int64 類型的數(shù)據(jù)構(gòu)造出來, 比如 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

另加詳細(xì)的信息可以參考 :mod:Dates 模塊的 API 索引.

上一篇:擴展包下一篇:控制流