「日付と時刻は、小さい子どもにも教えられるものです。いったい何が難しいのでしょう?」
多くのプログラマーがこの過ちを犯してきました。そして、その後に味わう経験は、健康や幸福に悪い影響を与えがちです。
日付と時刻を扱う、ちょっとした以上のプログラミングをするなら、少なくとも起こりうる問題を理解し、和らげる用意はしておくべきです。
この文書では、Juliaが日付と時刻をどのように扱うかの概要を説明します。ただし、詳しい内容についてはマニュアルを参照する必要があります。 Wikibookもあり、説明がよりわかりやすいと感じる読者もいるでしょう。型階層を表した便利な図も含まれています。
Datesモジュール基本的な機能のほとんどはDatesにあります。Datesはデフォルトのインストールに含まれていますが、標準ライブラリの一部ではありません。
このモジュールを使うには、2つの方法があります。
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モジュールは大きくて複雑ですが、主に4種類の機能を提供します。
Dates.Date:日・月・年を含みますが、時刻は含みません。Dates.Time:時刻を含みますが、日付は含みません。Dates.DateTime:日付と時刻を組み合わせたものです。Dates.Period:時間や日付の間隔を扱います。DateTimeには通常、タイムゾーンの情報が含まれ_ません_(Pythonではこれを「naive」なdatetimeと呼びます)。
例外はnow()で、システムクロックからローカル時刻を取得し、必要であればUTCに変換できます。
おそらく最もよくあるニーズは次のとおりです。
Datesの型を構築します。Datesの型から、必要な数値形式または文字列形式を取得します。Date、Time、DateTimeの値にオフセットを適用します。today())や時刻(now())を取得します。視覚的な邪魔を減らすため、この文書の残りのコード例のほとんどはusing Datesを前提とし、関数呼び出しにモジュール名Dates.を付けません。
日付や時刻の書き方には多くの流儀があり、文化によって異なることが少なくありません。 「7/6/23」のような数字だけの日付は曖昧で紛らわしく、多国籍企業で数多くの高くつく間違いを生んできました(これは著者が苦い実体験から述べています)。
国際規格はISO 8601で定められており、主に2つの利点があります。
前の例で、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を構築することもできます。
日付・時刻の各要素には、YearからNanosecondまで、それぞれPeriod型があり、時間の間隔を表します。
これらは特に計算に役立ちます。
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"
上の例はごく単純で、ヨーロッパでよく使われる日/月/年の形式の日付にすぎません。 2種類の構文を紹介しましたが、結果は同じです。
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日にオープンしたニューヨークの美容サロン向けの、予約スケジューラーに取り組みます。
タスクは5つあり、どれも予約の日付を扱います。 時刻は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