[インデックス 10546] ファイルの概要
このコミットは、Go言語の標準ライブラリおよび関連ツール全体で、時間(time)を扱うAPIの利用方法を刷新するものです。具体的には、time.Nanoseconds()
や time.Seconds()
のような整数ベースのタイムスタンプ表現から、より型安全で表現力の高い time.Time
および time.Duration
型への移行を広範囲にわたって実施しています。これにより、コードの可読性、保守性、および堅牢性が向上しています。
コミット
commit 03823b881cdfd4432ac1ea576677b6279bc6bb74
Author: Russ Cox <rsc@golang.org>
Date: Wed Nov 30 12:01:46 2011 -0500
use new time API
R=bradfitz, gri, r, dsymonds
CC=golang-dev
https://golang.org/cl/5390042
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/03823b881cdfd4432ac1ea576677b6279bc6bb74
元コミット内容
このコミットの目的は、Go言語の標準ライブラリ内で「新しい時間APIを使用する」ことです。これは、従来の time
パッケージが提供していた、Unixエポックからのナノ秒や秒といった整数値で時間を表現する方法から、よりオブジェクト指向で型安全な time.Time
型(特定の時点を表す)と time.Duration
型(時間の長さを表す)への移行を意味します。この変更は、Go言語の進化における重要なステップであり、時間計算における一般的なバグ(単位の誤用など)を防ぎ、コードの意図をより明確にすることを目的としています。
変更の背景
Go言語の初期の time
パッケージは、Unixタイムスタンプ(エポックからの秒数やナノ秒数)を int64
で直接扱うことが一般的でした。しかし、このアプローチにはいくつかの問題がありました。
- 型安全性の欠如:
int64
は単なる数値であり、それが時間なのか、期間なのか、あるいは全く別の意味を持つ数値なのかをコンパイラが区別できませんでした。これにより、秒とナノ秒を誤って計算したり、時間と期間を混同したりするバグが発生しやすかったのです。 - 可読性の低下: コードを見ただけでは、その
int64
がどの時間単位を表しているのかが不明瞭であり、コメントや変数名に頼る必要がありました。 - 表現力の限界: 特定のタイムゾーン情報や、より複雑な時間操作(日付の加算、減算など)を行うには、追加の関数呼び出しや手動での変換が必要でした。
これらの問題を解決するため、Go言語は time.Time
と time.Duration
という専用の型を導入し、時間に関する操作をより安全かつ直感的に行えるようにしました。このコミットは、その新しいAPIを既存のコードベース全体に適用する大規模なリファクタリングの一環です。
前提知識の解説
このコミットを理解するためには、Go言語の time
パッケージにおける以下の概念を理解しておく必要があります。
- Unixエポックタイム: 1970年1月1日00:00:00 UTCからの経過秒数またはナノ秒数で時間を表現する方法。従来のGoの
time
パッケージでは、time.Nanoseconds()
やtime.Seconds()
のように直接int64
でこれらの値を取得していました。 time.Time
型: Go言語で特定の時点を表す構造体。内部的にはUnixエポックからのナノ秒数を保持していますが、ユーザーからは抽象化されており、年、月、日、時、分、秒、タイムゾーンなどの情報にアクセスするためのメソッドが提供されます。time.Now()
で現在の時刻を取得したり、time.Unix(sec, nsec)
でUnixタイムスタンプからtime.Time
オブジェクトを生成したりします。time.Duration
型: Go言語で時間の長さを表す型。内部的にはナノ秒単位のint64
で表現されますが、time.Second
,time.Minute
,time.Hour
などの定数を使って直感的に期間を表現できます。time.Time
オブジェクト間の差を計算するとtime.Duration
が返され、time.Time
にtime.Duration
を加算・減算することも可能です。os.FileInfo
: ファイルのメタデータ(名前、サイズ、更新時刻など)を提供するインターフェース。このコミットでは、ファイルの更新時刻 (ModTime
) の型がint64
からtime.Time
に変更されています。archive/tar
およびarchive/zip
ヘッダー: TARやZIPアーカイブ内のファイルエントリのメタデータ(更新時刻など)を定義する構造体。これらの構造体内の時間関連フィールドも、int64
からtime.Time
に変更されています。
技術的詳細
このコミットの主要な技術的変更点は、以下のパターンに集約されます。
-
time.Nanoseconds()
からtime.Now()
への移行:- 従来のコードでは、現在の時刻をナノ秒単位の
int64
で取得するためにtime.Nanoseconds()
を使用していました。 - 新しいAPIでは、現在の時刻を
time.Time
型で取得するためにtime.Now()
を使用します。これにより、時刻の比較や操作がより直感的になります。 - 例:
t := time.Nanoseconds()
がt := time.Now()
に変更。
- 従来のコードでは、現在の時刻をナノ秒単位の
-
int64
タイムスタンプからtime.Time
型への移行:- ファイルの更新時刻 (
os.FileInfo.Mtime_ns
->os.FileInfo.ModTime
) や、アーカイブヘッダー内の時刻 (tar.Header.Mtime
,zip.FileHeader.Mtime_ns
->tar.Header.ModTime
,zip.FileHeader.ModTime
) など、特定の時点を表すフィールドの型がint64
からtime.Time
に変更されました。 - これにより、これらの時刻情報を直接
time.Time
のメソッド(例:t.Local().String()
,t.Before(otherTime)
) を使って操作できるようになります。 - Unixタイムスタンプ(秒)から
time.Time
への変換にはtime.Unix(sec, 0)
が使用されます。
- ファイルの更新時刻 (
-
期間の表現における
time.Duration
の活用:- 従来のコードでは、期間をナノ秒単位の
int64
で直接表現していました(例:30e9
は30秒)。 - 新しいAPIでは、期間を
time.Duration
型で表現します。これにより、30 * time.Second
のように、より明確で読みやすいコードになります。 time.Duration
はtime.Time
との加算・減算が可能です(例:time.Now().Add(pkgBuildInterval)
)。time.Duration
型の値をfloat64
に変換して秒数を取得するにはdt.Seconds()
が使用されます。
- 従来のコードでは、期間をナノ秒単位の
-
時刻比較の変更:
int64
タイムスタンプの直接比較 (t1 < t2
) から、time.Time
型のメソッド (t1.Before(t2)
,t1.After(t2)
,t1.Equal(t2)
) を使用した比較に変わりました。これにより、比較の意図が明確になります。
-
time.Time
のゼロ値の扱い:time.Time
のゼロ値 (time.Time{}
) は、Unixエポックの開始時刻(0001年1月1日)を表します。これは、従来のint64
での0
とは異なる意味を持つため、ゼロ値のチェックにはt.IsZero()
メソッドが推奨されます。
これらの変更は、Go言語の time
パッケージがより現代的で堅牢な時間処理機能を提供するための基盤を築きました。
コアとなるコードの変更箇所
このコミットは非常に広範囲にわたる変更を含んでいますが、特に重要な変更箇所をいくつか抜粋して解説します。
-
misc/dashboard/builder/main.go
:waitInterval
やpkgBuildInterval
といった期間を表す定数が、30e9
(ナノ秒) から30 * time.Second
や24 * time.Hour
のようにtime.Duration
型で定義されるようになりました。time.Nanoseconds()
を使っていた箇所がtime.Now()
に変更され、時間の差分計算もtime.Now().Sub(t)
のようにtime.Duration
を返すメソッドに置き換えられました。nextBuild
の型がint64
からtime.Time
に変更され、ビルド時刻の比較がtime.Now().Before(nextBuild)
のように行われるようになりました。
--- a/misc/dashboard/builder/main.go +++ b/misc/dashboard/builder/main.go @@ -24,9 +24,9 @@ const ( codeProject = "go" codePyScript = "misc/dashboard/googlecode_upload.py" hgUrl = "https://go.googlecode.com/hg/" - waitInterval = 30e9 // time to wait before checking for new revs mkdirPerm = 0750 - pkgBuildInterval = 1e9 * 60 * 60 * 24 // rebuild packages every 24 hours + waitInterval = 30 * time.Second // time to wait before checking for new revs + pkgBuildInterval = 24 * time.Hour // rebuild packages every 24 hours ) // These variables are copied from the gobuilder's environment @@ -131,7 +131,7 @@ func main() { // check for new commits and build them for { built := false - t := time.Nanoseconds() + t := time.Now() if *parallel { done := make(chan bool) for _, b := range builders { @@ -152,9 +152,9 @@ func main() { // sleep if we're looping too fast. - t1 := time.Nanoseconds() - t - if t1 < waitInterval { - time.Sleep(waitInterval - t1) + dt := time.Now().Sub(t) + if dt < waitInterval { + time.Sleep(waitInterval - dt) } } } @@ -194,7 +194,7 @@ func NewBuilder(builder string) (*Builder, error) { // a new release tag is found. func (b *Builder) buildExternal() { var prevTag string - var nextBuild int64 + var nextBuild time.Time for { time.Sleep(waitInterval) err := run(nil, goroot, "hg", "pull", "-u") @@ -213,7 +213,7 @@ func (b *Builder) buildExternal() { // don't rebuild if there's no new release // and it's been less than pkgBuildInterval // nanoseconds since the last build. - if tag == prevTag && time.Nanoseconds() < nextBuild { + if tag == prevTag && time.Now().Before(nextBuild) { continue } // build will also build the packages @@ -222,7 +222,7 @@ func (b *Builder) buildExternal() { continue } prevTag = tag - nextBuild = time.Nanoseconds() + pkgBuildInterval + nextBuild = time.Now().Add(pkgBuildInterval) } }
-
src/cmd/godoc/filesystem.go
:FileInfo
インターフェースのMtime_ns()
メソッドがModTime()
に変更され、戻り値の型がint64
からtime.Time
になりました。osFI
構造体も同様にMtime_ns
フィールドがModTime
に変更されました。
--- a/src/cmd/godoc/filesystem.go +++ b/src/cmd/godoc/filesystem.go @@ -13,13 +13,14 @@ import ( "io" "io/ioutil" "os" + "time" ) // The FileInfo interface provides access to file information. type FileInfo interface { Name() string Size() int64 - Mtime_ns() int64 + ModTime() time.Time IsRegular() bool IsDirectory() bool } @@ -64,8 +65,8 @@ func (fi osFI) Size() int64 { return fi.FileInfo.Size } -func (fi osFI) Mtime_ns() int64 { - return fi.FileInfo.Mtime_ns +func (fi osFI) ModTime() time.Time { + return fi.FileInfo.ModTime } // osFS is the OS-specific implementation of FileSystem
-
src/pkg/archive/tar/common.go
:Header
構造体のMtime
,Atime
,Ctime
フィールドがint64
からtime.Time
に変更されました。
--- a/src/pkg/archive/tar/common.go +++ b/src/pkg/archive/tar/common.go @@ -11,41 +11,42 @@ // http://www.gnu.org/software/tar/manual/html_node/Standard.html package tar +import "time" + const ( blockSize = 512 // Types - TypeReg = '0' // regular file. - TypeRegA = '\x00' // regular file. - TypeLink = '1' // hard link. - TypeSymlink = '2' // symbolic link. - TypeChar = '3' // character device node. - TypeBlock = '4' // block device node. - TypeDir = '5' // directory. - TypeFifo = '6' // fifo node. - TypeCont = '7' // reserved. - TypeXHeader = 'x' // extended header. - TypeXGlobalHeader = 'g' // global extended header. + TypeReg = '0' // regular file + TypeRegA = '\x00' // regular file + TypeLink = '1' // hard link + TypeSymlink = '2' // symbolic link + TypeChar = '3' // character device node + TypeBlock = '4' // block device node + TypeDir = '5' // directory + TypeFifo = '6' // fifo node + TypeCont = '7' // reserved + TypeXHeader = 'x' // extended header + TypeXGlobalHeader = 'g' // global extended header ) // A Header represents a single header in a tar archive. // Some fields may not be populated. type Header struct { - Name string // name of header file entry. - Mode int64 // permission and mode bits. - Uid int // user id of owner. - Gid int // group id of owner. - Size int64 // length in bytes. - Mtime int64 // modified time; seconds since epoch. - Typeflag byte // type of header entry. - Linkname string // target name of link. - Uname string // user name of owner. - Gname string // group name of owner. - Devmajor int64 // major number of character or block device. - Devminor int64 // minor number of character or block device. - Atime int64 // access time; seconds since epoch. - Ctime int64 // status change time; seconds since epoch. - + Name string // name of header file entry + Mode int64 // permission and mode bits + Uid int // user id of owner + Gid int // group id of owner + Size int64 // length in bytes + ModTime time.Time // modified time + Typeflag byte // type of header entry + Linkname string // target name of link + Uname string // user name of owner + Gname string // group name of owner + Devmajor int64 // major number of character or block device + Devminor int64 // minor number of character or block device + AccessTime time.Time // access time + ChangeTime time.Time // status change time } var zeroBlock = make([]byte, blockSize)
コアとなるコードの解説
これらの変更は、Go言語のコードベース全体で時間に関する処理を統一し、より現代的なアプローチを採用するためのものです。
-
time.Duration
の導入:waitInterval
やpkgBuildInterval
のような期間を表す値をtime.Duration
型で明示的に定義することで、その数値が「秒」や「ナノ秒」といった特定の単位を持つことをコンパイラと開発者の両方に明確に伝えます。これにより、単位の誤用によるバグを防ぎ、コードの意図がより明確になります。例えば、30e9
と書くよりも30 * time.Second
と書く方が、それが30秒であることを直感的に理解できます。 -
time.Time
の全面的な採用:time.Nanoseconds()
のような整数ベースのタイムスタンプからtime.Now()
のようなtime.Time
型への移行は、時間に関する操作をよりオブジェクト指向的に行えるようにします。time.Time
オブジェクトは、その時点に関する豊富な情報(年、月、日、時、分、秒、タイムゾーンなど)をカプセル化しており、Sub()
,Add()
,Before()
,After()
などのメソッドを使って安全かつ柔軟に時間計算や比較を行うことができます。これにより、コードの可読性と堅牢性が大幅に向上します。 -
os.FileInfo
やアーカイブヘッダーの変更: ファイルの更新時刻やアーカイブ内のエントリの時刻情報がint64
からtime.Time
に変更されたことは、これらのメタデータがより正確でリッチな情報を持つことを意味します。これにより、ファイルシステムやアーカイブを扱うアプリケーションが、より高度な時間ベースのロジックを実装しやすくなります。例えば、ファイルの更新時刻を直接比較したり、特定のタイムゾーンで表示したりすることが容易になります。
これらの変更は、Go言語が提供する時間処理のプリミティブをより強力で使いやすいものにし、開発者が時間に関連するバグを減らし、より高品質なソフトウェアを構築できるようにするための重要なステップでした。
関連リンク
- Go言語
time
パッケージ公式ドキュメント: https://pkg.go.dev/time - Go言語の
time
パッケージに関するブログ記事やチュートリアル (一般的な情報源):- A Tour of Go - Time: https://go.dev/tour/moretypes/12
- Go by Example: Timers and Tickers: https://gobyexample.com/timers
参考にした情報源リンク
- GitHub上のコミットページ: https://github.com/golang/go/commit/03823b881cdfd4432ac1ea576677b6279bc6bb74
- Go言語の公式ドキュメント (
pkg.go.dev/time
) - Go言語の
time
パッケージの歴史に関する一般的な知識