學習軌道
/
Julia
Julia
/
練習
/
為美預約
為美預約

為美預約

學習練習

簡介

「日期和時間是我們教小孩子的东西,能有多難?」

許多程式設計師都犯過這個錯誤,而隨之而來的經歷往往對他們的身心健康造成很不好的影響。

任何要用日期和時間做不算簡單的程式設計的人,至少都該準備好理解並減輕潛在的問題。

本文件會試著概述 Julia 如何處理日期和時間,但若想了解更進一步的細節,還是得查閱手冊。 另外還有一本 Wikibook,有些讀者可能會覺得它的說明更清楚,裡頭還有一張很有幫助的圖,說明型別階層。

Dates模組

大部分基本功能都在Dates裡,這個模組隨預設安裝一起提供,但不屬於標準函式庫。

要使用這個模組,有兩種做法:

  1. 加上import Dates,然後在所有函式呼叫前面加上Dates.
  2. 加上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())。
Note

為了減少視覺上的干擾,本文件後續大部分的範例程式碼都假設已經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)。

1. 解析全數字的預約日期

實作 schedule_appointment() 函式,把預約日期的文字表示法解析成對應的 DateTime 格式。

請注意,輸入採用美式的月/日/年順序。

julia> schedule_appointment("07/25/2023 13:45:00")
2023-07-25T13:45:00

2. 檢查預約是否已經過了

實作 has_passed() 函式,它會接受一個預約日期,並檢查該預約是否已經過去了:

julia> has_passed(DateTime(1999, 12, 31, 9, 0, 0))
true

3. 檢查預約是否在下午

實作 is_afternoon_appointment() 函式,它會接受一個預約日期,並檢查該預約是否在下午(>= 12:00 且 < 18:00):

julia> is_afternoon_appointment(DateTime(2023, 3, 29, 15, 0, 0))
true

4. 描述預約的時間與日期

實作 description() 函式,它會接受一個預約日期,並回傳該日期與時間的描述:

julia> describe(DateTime(2023, 3, 29, 15, 0, 0))
"You have an appointment on Wednesday, March 29, 2023 at 15:00"

5. 回傳週年紀念日

實作 anniversary_date() 函式,它會回傳今年的週年紀念日,也就是 9 月 15 日:

julia> anniversary_date()  # run during 2025
2025-09-15
透過 GitHub 編輯 連結會在新視窗或分頁中開啟
Julia Exercism

準備好開始 為美預約 了嗎?

註冊 Exercism,透過 35 個概念128 個練習 和真人引導來學習並精通 Julia,全部免費。