「日期和時間是我們教小孩子的东西,能有多難?」
許多程式設計師都犯過這個錯誤,而隨之而來的經歷往往對他們的身心健康造成很不好的影響。
任何要用日期和時間做不算簡單的程式設計的人,至少都該準備好理解並減輕潛在的問題。
本文件會試著概述 Julia 如何處理日期和時間,但若想了解更進一步的細節,還是得查閱手冊。 另外還有一本 Wikibook,有些讀者可能會覺得它的說明更清楚,裡頭還有一張很有幫助的圖,說明型別階層。
Dates模組大部分基本功能都在Dates裡,這個模組隨預設安裝一起提供,但不屬於標準函式庫。
要使用這個模組,有兩種做法:
import Dates,然後在所有函式呼叫前面加上Dates.
using Dates,把整個模組帶進目前的命名空間(除了少數有問題的名稱)。# option 1
julia> import Dates
julia> Dates.now()
2025-06-21T16:02:39.080
julia> now() # wrong: need to include `Dates.` after an import
ERROR: UndefVarError: `now` not defined in `Main`
# option 2
julia> using Dates
julia> now() # functions are now in the namespace
2025-06-21T16:03:04.161
Dates模組既龐大又複雜,但它提供四大類功能:
Dates.Date,包含日、月、年,但不含時刻。Dates.Time,包含時刻,但不含日期。Dates.DateTime,結合日期與時間。Dates.Period,處理時間或日期的間隔。請注意,DateTime一般_不_包含時區資訊(Python 會稱之為「naive」日期時間)。
有個例外是now(),它會從你的系統時鐘取得本地時間,必要時還能轉換成 UTC。
最常見的需求大概有:
Dates型別。Dates型別取得所需的數字或字串格式。Date、Time或DateTime值套用偏移。today())和/或時間(用now())。為了減少視覺上的干擾,本文件後續大部分的範例程式碼都假設已經using Dates,並避免在函式呼叫中加上模組名稱Dates.。
日期和時間有許多種寫法,而且往往因文化而異。 「7/6/23」這種全數字日期既含糊又令人困惑,還曾在跨國組織裡造成許多代價高昂的錯誤(作者說這句話,是因為有過慘痛的親身經歷)。
國際標準定義在ISO 8601中,主要有兩大好處:
前面的範例裡,我們看到Date.now()回傳了2025-06-21T16:03:04.161。
這個格式由幾個部分組成:
YYYY-MM-DD。hh:mm:ss。其他字串格式會在後面的章節討論。
Dates.Date是相對單純、只含日期的型別,不處理時間或時區。
# constructor with year, month, day arguments
julia> date1 = Date(2025, 06, 23)
2025-06-23
# constructor with ISO 8601 string
julia> date2 = Date("2025-06-23")
2025-06-23
julia> date1 == date2
true
julia> typeof(date1)
Date
有一些存取函式可以取得日期的各個部分,這些函式成對出現,以大小寫區分。
# lowercase returns an integer
julia> month(date1)
6
julia> typeof(month(date1))
Int64
# Propercase returns a Period type
julia> Month(date1)
6 months
julia> typeof(Month(date1))
Month
有各式各樣的函式對日曆有些概念。 下一個範例只展示了其中幾個。
julia> date1
2025-06-23
julia> dayofweek(date1) # 1 = Monday, 7 = Sunday
1
julia> dayname(date1)
"Monday"
julia> daysinmonth(date1)
30
julia> isleapyear(date1)
false
julia> lastdayofmonth(date1)
2025-06-30
Dates.Time是基本的、只含時間的型別。
它不處理日期:時間到了午夜會自動繞回Time(0, 0, 0)。
完整的建構子格式是Time(hour, min, sec, millisec, microsec),或對應的 ISO 8601 寫法。
所有參數都是選用的,預設為0。
julia> Time(14, 57, 25, 142)
14:57:25.142
julia> Time("14:57:25.142")
14:57:25.142
julia> Time(14, 57, 25, 142, 32)
14:57:25.142032
和Date一樣,各個部分都可以用存取函式取得:
julia> minute(t) # => integer
57
julia> Minute(t) # => Period
57 minutes
Dates.DateTime結合了Date和Time型別的大部分特性。
julia> dt1 = DateTime(2025, 6, 23, 14, 57, 25, 142)
2025-06-23T14:57:25.142
# date1 and t were defined in previous examples
julia> dt2 = DateTime(date1, t)
2025-06-23T14:57:25.142
年、月、日參數預設為1,時間參數則全部預設為0。
要記清楚這些參數並不容易,所以輸入時用 ISO 格式可能會比較方便。
或者,也可以像上面的範例那樣,用一個Date和一個Time建構出DateTime。
日期時間的每個組成部分都各有自己的Period型別,從Year一路到Nanosecond,用來表示一段時間間隔。
這些型別在做運算時特別好用。
julia> dt1
2025-06-23T14:57:25.142
julia> Year(21)
21 years
julia> Second(61)
61 seconds
julia> dt1 + Year(21) - Second(61)
2046-06-23T14:56:24.142
julia> Date(2025, 07, 03) - Date(2025, 04, 25)
69 days
處理Period型別時,Julia 會試著聰明地應付月長這類日曆上的不規則,必要時進行捨入。
# February has fewer days than January
julia> Date(2025, 1, 31) + Month(1)
2025-02-28
我們前面提過 ISO 8601 標準,它是 Julia 在格式化或解析字串時預設的日期時間表示法。
當然,還有許多其他格式經常被使用,而且往往因文化而異。
為了處理這些格式,Julia 提供了DateFormat型別,可以用任意複雜度的格式字串建構出來。
# dateformat"" string syntax
julia> dtfmt = dateformat"d/m/y"
dateformat"d/m/y"
julia> typeof(dtfmt)
DateFormat{Symbol("d/m/y"), Tuple{Dates.DatePart{'d'}, Dates.Delim{Char, 1}, Dates.DatePart{'m'}, Dates.Delim{Char, 1}, Dates.DatePart{'y'}}}
# DateFormat constructor
julia> DateFormat("d/m/y")
dateformat"d/m/y"
上面的範例很簡單,只是一個歐洲常見的日/月/年格式日期。 這裡展示了兩種語法變化,但結果完全相同。
接著就能用DateFormat來解析任何合適的字串:
julia> DateTime("14/7/2023", dtfmt)
2023-07-14T00:00:00
建立DateFormat有不小的額外負擔,所以只做一次這個步驟(就像這裡的dtfmt),在迴圈處理大量日期時間字串時對效能很有幫助。
格式可以比這複雜得多,詳情請參閱手冊。
同一個DateFormat也可以用來把日期時間輸出成任何想要的字串格式,只要使用Dates.format()函式。
可惜format似乎有命名空間衝突(至少在 Julia 1.11.5 中是如此),所以即使已經using Dates,最好還是保留Dates.這個前綴。
julia> dt1
2025-06-23T14:57:25.142
julia> Dates.format(dt1, dateformat"e, u d")
"Mon, Jun 23"
如同文件所述,"e, u d"會給出星期幾和月份的縮寫文字表示法。
文字表示法預設為英文。 也可以加入其他語言,不過目前這方面的支援還不太理想。
在這個練習中,你要為紐約一間於 2012 年 9 月 15 日開幕的美容沙龍製作預約排程系統。
這五個任務都會牽涉到預約日期。 時間採用 24 小時制(沒有 AM/PM)。
實作 schedule_appointment() 函式,把預約日期的文字表示法解析成對應的 DateTime 格式。
請注意,輸入採用美式的月/日/年順序。
julia> schedule_appointment("07/25/2023 13:45:00")
2023-07-25T13:45:00
實作 has_passed() 函式,它會接受一個預約日期,並檢查該預約是否已經過去了:
julia> has_passed(DateTime(1999, 12, 31, 9, 0, 0))
true
實作 is_afternoon_appointment() 函式,它會接受一個預約日期,並檢查該預約是否在下午(>= 12:00 且 < 18:00):
julia> is_afternoon_appointment(DateTime(2023, 3, 29, 15, 0, 0))
true
實作 description() 函式,它會接受一個預約日期,並回傳該日期與時間的描述:
julia> describe(DateTime(2023, 3, 29, 15, 0, 0))
"You have an appointment on Wednesday, March 29, 2023 at 15:00"
實作 anniversary_date() 函式,它會回傳今年的週年紀念日,也就是 9 月 15 日:
julia> anniversary_date() # run during 2025
2025-09-15