Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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 で直接扱うことが一般的でした。しかし、このアプローチにはいくつかの問題がありました。

  1. 型安全性の欠如: int64 は単なる数値であり、それが時間なのか、期間なのか、あるいは全く別の意味を持つ数値なのかをコンパイラが区別できませんでした。これにより、秒とナノ秒を誤って計算したり、時間と期間を混同したりするバグが発生しやすかったのです。
  2. 可読性の低下: コードを見ただけでは、その int64 がどの時間単位を表しているのかが不明瞭であり、コメントや変数名に頼る必要がありました。
  3. 表現力の限界: 特定のタイムゾーン情報や、より複雑な時間操作(日付の加算、減算など)を行うには、追加の関数呼び出しや手動での変換が必要でした。

これらの問題を解決するため、Go言語は time.Timetime.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.Timetime.Duration を加算・減算することも可能です。
  • os.FileInfo: ファイルのメタデータ(名前、サイズ、更新時刻など)を提供するインターフェース。このコミットでは、ファイルの更新時刻 (ModTime) の型が int64 から time.Time に変更されています。
  • archive/tar および archive/zip ヘッダー: TARやZIPアーカイブ内のファイルエントリのメタデータ(更新時刻など)を定義する構造体。これらの構造体内の時間関連フィールドも、int64 から time.Time に変更されています。

技術的詳細

このコミットの主要な技術的変更点は、以下のパターンに集約されます。

  1. time.Nanoseconds() から time.Now() への移行:

    • 従来のコードでは、現在の時刻をナノ秒単位の int64 で取得するために time.Nanoseconds() を使用していました。
    • 新しいAPIでは、現在の時刻を time.Time 型で取得するために time.Now() を使用します。これにより、時刻の比較や操作がより直感的になります。
    • 例: t := time.Nanoseconds()t := time.Now() に変更。
  2. 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) が使用されます。
  3. 期間の表現における time.Duration の活用:

    • 従来のコードでは、期間をナノ秒単位の int64 で直接表現していました(例: 30e9 は30秒)。
    • 新しいAPIでは、期間を time.Duration 型で表現します。これにより、30 * time.Second のように、より明確で読みやすいコードになります。
    • time.Durationtime.Time との加算・減算が可能です(例: time.Now().Add(pkgBuildInterval))。
    • time.Duration 型の値を float64 に変換して秒数を取得するには dt.Seconds() が使用されます。
  4. 時刻比較の変更:

    • int64 タイムスタンプの直接比較 (t1 < t2) から、time.Time 型のメソッド (t1.Before(t2), t1.After(t2), t1.Equal(t2)) を使用した比較に変わりました。これにより、比較の意図が明確になります。
  5. time.Time のゼロ値の扱い:

    • time.Time のゼロ値 (time.Time{}) は、Unixエポックの開始時刻(0001年1月1日)を表します。これは、従来の int64 での 0 とは異なる意味を持つため、ゼロ値のチェックには t.IsZero() メソッドが推奨されます。

これらの変更は、Go言語の time パッケージがより現代的で堅牢な時間処理機能を提供するための基盤を築きました。

コアとなるコードの変更箇所

このコミットは非常に広範囲にわたる変更を含んでいますが、特に重要な変更箇所をいくつか抜粋して解説します。

  1. misc/dashboard/builder/main.go:

    • waitIntervalpkgBuildInterval といった期間を表す定数が、30e9 (ナノ秒) から 30 * time.Second24 * 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)
     	}
     }
    
  2. 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
    
  3. 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 の導入: waitIntervalpkgBuildInterval のような期間を表す値を 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言語が提供する時間処理のプリミティブをより強力で使いやすいものにし、開発者が時間に関連するバグを減らし、より高品質なソフトウェアを構築できるようにするための重要なステップでした。

関連リンク

参考にした情報源リンク