[インデックス 10809] ファイルの概要
このコミットは、Go言語の標準ライブラリである time
パッケージに AddDate
メソッドを新しく追加するものです。AddDate
メソッドは、指定された年、月、日のオフセットを既存の Time
オブジェクトに加算し、新しい Time
オブジェクトを返します。これにより、日付の加算・減算が容易になります。
コミット
commit cebf55dc9b2bf6b298f60cf3bffb4ad7a4583f05
Author: Roger Peppe <rogpeppe@gmail.com>
Date: Thu Dec 15 11:23:01 2011 -0500
time: new AddDate method
R=rsc
CC=golang-dev
https://golang.org/cl/5465044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/cebf55dc9b2bf6b298f60cf3bffb4ad7a4583f05
元コミット内容
time
パッケージに AddDate
という新しいメソッドを追加します。このメソッドは、指定された年数、月数、日数を既存の Time
オオブジェクトに加算する機能を提供します。
変更の背景
Go言語の time
パッケージは、日付と時刻を扱うための基本的な機能を提供しますが、特定の日付オフセット(年、月、日)を加算する直接的なメソッドは存在しませんでした。既存の Add
メソッドは Duration
型を受け取るため、秒単位での加算は可能でしたが、月や年といった可変長の時間単位での加算は複雑でした。
例えば、「今日から3ヶ月後」や「5年前の今日」といった計算を行う場合、開発者はうるう年や各月の異なる日数などを考慮して手動で計算する必要がありました。これはエラーの温床となりやすく、コードの可読性も損なう可能性がありました。
AddDate
メソッドの導入により、このような日付の加算・減算が直感的かつ正確に行えるようになり、開発者の負担が軽減され、time
パッケージの利便性が向上しました。特に、日付の「正規化」を内部で処理することで、例えば10月31日に1ヶ月を加算すると12月1日になる(11月31日という存在しない日付を正規化する)といった、日付計算における一般的な課題を解決しています。
前提知識の解説
Go言語の time
パッケージ
Go言語の time
パッケージは、日付と時刻を扱うための機能を提供します。主要な型として Time
と Duration
があります。
time.Time
構造体: 特定の時点を表します。内部的には、エポック(1970年1月1日UTC)からの経過秒数とナノ秒数、およびタイムゾーン情報を持っています。time.Duration
型: 期間を表します。ナノ秒単位で表現され、time.Second
,time.Minute
,time.Hour
などの定数を使って期間を表現できます。Time.Date()
メソッド:Time
オブジェクトから年、月、日を返します。Time.Clock()
メソッド:Time
オブジェクトから時、分、秒を返します。time.Date()
関数: 指定された年、月、日、時、分、秒、ナノ秒、ロケーション(タイムゾーン)から新しいTime
オブジェクトを作成します。この関数は、日付の「正規化」を行います。例えば、time.Date(2023, time.February, 30, ...)
のように存在しない日付を指定した場合、自動的に3月2日などに調整されます。Time.Location()
メソッド:Time
オブジェクトのタイムゾーン情報を返します。
日付の正規化 (Normalization)
日付の正規化とは、存在しない日付(例: 2月30日、11月31日)や、月の範囲を超える日付(例: 1月32日)が指定された場合に、それを有効な日付に自動的に調整するプロセスを指します。
例えば、time.Date
関数において:
time.Date(2023, time.February, 30, ...)
は、2023年2月28日の2日後として、2023年3月2日に正規化されます。time.Date(2023, time.October, 31, ...)
に1ヶ月を加算する場合、単純に月を11にするだけでは11月31日となり存在しません。この場合、AddDate
はtime.Date
の正規化ロジックを利用して、11月30日の翌日である12月1日に正規化します。
この正規化の挙動は、日付計算の正確性と堅牢性を保証するために非常に重要です。
技術的詳細
AddDate
メソッドは Time
型のレシーバメソッドとして定義されています。その実装は非常にシンプルでありながら、Go言語の time
パッケージが提供する既存の強力な機能を活用しています。
AddDate
の内部ロジックは以下のステップで構成されます。
-
現在の年月日と時刻の取得:
t.Date()
を呼び出して、現在のTime
オブジェクトt
から年 (year
)、月 (month
)、日 (day
) を取得します。t.Clock()
を呼び出して、現在のTime
オブジェクトt
から時 (hour
)、分 (min
)、秒 (sec
) を取得します。 ナノ秒 (t.nsec
) とロケーション (t.loc
) も直接利用します。 -
オフセットの加算: 取得した年、月、日に、引数として渡された
years
、months
、days
をそれぞれ加算します。year + years
month + Month(months)
(ここでmonths
はtime.Month
型にキャストされます)day + days
-
新しい
Time
オブジェクトの生成と正規化: 加算された年、月、日、そして元の時、分、秒、ナノ秒、ロケーションを引数としてtime.Date()
関数を呼び出します。time.Date()
関数は、内部的に日付の正規化ロジックを含んでいます。これにより、例えば「10月31日に1ヶ月を加える」といった場合に、自動的に「12月1日」として正しい日付が計算されます。これは、time.Date
が与えられた年、月、日を基に、その月の最終日を超えた分を翌月に繰り越す(または前月に繰り下げる)ためです。
このアプローチにより、AddDate
メソッドは複雑な日付計算ロジックを自身で持つことなく、time.Date
関数の堅牢な正規化機能を再利用しています。
コアとなるコードの変更箇所
src/pkg/time/time.go
// AddDate returns the time corresponding to adding the
// given number of years, months, and days to t.
// For example, AddDate(-1, 2, 3) applied to January 1, 2011
// returns March 4, 2010.
//
// AddDate normalizes its result in the same way that Date does,
// so, for example, adding one month to October 31 yields
// December 1, the normalized form for November 31.
func (t Time) AddDate(years int, months int, days int) Time {
year, month, day := t.Date()
hour, min, sec := t.Clock()
return Date(year+years, month+Month(months), day+days, hour, min, sec, int(t.nsec), t.loc)
}
src/pkg/time/time_test.go
// Several ways of getting from
// Fri Nov 18 7:56:35 PST 2011
// to
// Thu Mar 19 7:56:35 PST 2016
var addDateTests = []struct {
years, months, days int
}{
{4, 4, 1},
{3, 16, 1},
{3, 15, 30},
{5, -6, -18 - 30 - 12},
}
func TestAddDate(t *testing.T) {
t0 := Date(2011, 11, 18, 7, 56, 35, 0, UTC)
t1 := Date(2016, 3, 19, 7, 56, 35, 0, UTC)
for _, at := range addDateTests {
time := t0.AddDate(at.years, at.months, at.days)
if !time.Equal(t1) {
t.Errorf("AddDate(%d, %d, %d) = %v, want %v",
at.years, at.months, at.days,
time, t1)
}
}
}
コアとなるコードの解説
AddDate
メソッド (src/pkg/time/time.go
)
-
func (t Time) AddDate(years int, months int, days int) Time
:Time
型にAddDate
というメソッドを追加しています。このメソッドはyears
(年数)、months
(月数)、days
(日数) の3つの整数を引数として受け取り、新しいTime
オブジェクトを返します。レシーバt
は元のTime
オブジェクトです。 -
year, month, day := t.Date()
: レシーバt
のDate()
メソッドを呼び出し、現在の年、月、日を取得します。 -
hour, min, sec := t.Clock()
: レシーバt
のClock()
メソッドを呼び出し、現在の時、分、秒を取得します。 -
return Date(year+years, month+Month(months), day+days, hour, min, sec, int(t.nsec), t.loc)
:time.Date()
関数を呼び出して新しいTime
オブジェクトを構築し、それを返します。year+years
: 現在の年に指定された年数を加算します。month+Month(months)
: 現在の月に指定された月数を加算します。months
はint
型なので、time.Month
型にキャストしています。day+days
: 現在の日に指定された日数を加算します。hour, min, sec
: 元の時刻の時、分、秒をそのまま使用します。AddDate
は日付の加算のみを行い、時刻は変更しません。int(t.nsec)
: 元の時刻のナノ秒をそのまま使用します。t.loc
: 元の時刻のロケーション(タイムゾーン)をそのまま使用します。
この
time.Date()
関数への呼び出しが、日付の正規化を自動的に処理する重要な部分です。例えば、day+days
の結果がその月の最大日数を超えた場合、time.Date()
は自動的に月を繰り上げ、正しい日付を計算します。
TestAddDate
関数 (src/pkg/time/time_test.go
)
-
var addDateTests = []struct { ... }
:AddDate
メソッドのテストケースを定義するための匿名構造体のスライスです。各要素はyears
,months
,days
の組み合わせを表します。 -
func TestAddDate(t *testing.T)
: Goのテストフレームワークによって実行されるテスト関数です。 -
t0 := Date(2011, 11, 18, 7, 56, 35, 0, UTC)
: テストの基準となる開始日時t0
を作成します。これは「2011年11月18日 7時56分35秒 UTC」です。 -
t1 := Date(2016, 3, 19, 7, 56, 35, 0, UTC)
:t0
に様々なオフセットを加算した結果、最終的に到達することを期待する目標日時t1
を作成します。これは「2016年3月19日 7時56分35秒 UTC」です。 -
for _, at := range addDateTests { ... }
:addDateTests
スライスをループし、各テストケースを実行します。 -
time := t0.AddDate(at.years, at.months, at.days)
: 基準日時t0
に対して、現在のテストケース (at
) で指定された年、月、日を加算し、結果をtime
変数に格納します。 -
if !time.Equal(t1) { ... }
: 計算されたtime
が期待される目標日時t1
と等しいかどうかをEqual
メソッドで比較します。Equal
メソッドは、Time
オブジェクトが同じ時点を表しているかどうかを比較します。 -
t.Errorf(...)
: もし計算結果が期待値と異なる場合、t.Errorf
を呼び出してテストエラーを報告します。これにより、どの入力でどのような結果が得られ、何が期待されていたかが詳細に表示されます。
このテストは、異なる年、月、日の組み合わせ(正の値、負の値、大きな値)を与えても、AddDate
が正しく日付を計算し、特に日付の正規化が期待通りに行われることを検証しています。例えば、{3, 16, 1}
は3年と16ヶ月と1日を加算するケースですが、16ヶ月は1年4ヶ月に正規化され、最終的に正しい日付に到達することを確認しています。
関連リンク
- Go
time
パッケージのドキュメント: https://pkg.go.dev/time - Go
time.Time.AddDate
のドキュメント: https://pkg.go.dev/time#Time.AddDate - Go
time.Date
のドキュメント: https://pkg.go.dev/time#Date
参考にした情報源リンク
- Go言語の公式ドキュメント
- コミットメッセージ内の Go CL (Code Review) リンク: https://golang.org/cl/5465044
- Go言語の
time
パッケージに関する一般的な情報源 (例: Go by Example: Time)