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

[インデックス 10740] ファイルの概要

このコミットは、Go言語のバージョン1(Go 1)リリースにおけるtimeパッケージの大幅な再設計に関する公式ドキュメントの更新を目的としています。具体的には、doc/go1.htmlおよびdoc/go1.tmplに新しいtimeパッケージのAPIとセマンティクスに関する詳細な説明を追加し、doc/progs/go1.goにその使用例を盛り込んでいます。これにより、Go 1で導入された時間管理の新しいアプローチが開発者に明確に伝えられます。

コミット

commit 5fa18e10618d609ce2c272026e460c47ec864250
Author: Rob Pike <r@golang.org>
Date:   Mon Dec 12 21:08:03 2011 -0800

    doc/go1: time
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/5477077

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/5fa18e10618d609ce2c272026e460c47ec864250

元コミット内容

doc/go1: time

R=rsc
CC=golang-dev
https://golang.org/cl/5477077

変更の背景

Go言語は、2012年3月に最初の安定版であるGo 1をリリースしました。Go 1の目標は、言語と標準ライブラリの安定性を提供し、将来のバージョンとの互換性を保証することでした。この安定化プロセスの一環として、既存のAPIのレビューと改善が行われました。

timeパッケージは、Go 1以前に存在していましたが、そのAPIは使いにくく、直感的ではない部分がありました。特に、時間の「瞬間」と「期間」を区別する明確な型がなく、ナノ秒単位のint64とポインタ型の*time.Timeが混在していました。これにより、コードの可読性や安全性が損なわれる可能性がありました。

このコミットは、Go 1のリリースに向けてtimeパッケージが大幅に再設計されたことを受けて、その変更点を公式ドキュメントに反映させるために作成されました。開発者が新しいAPIを理解し、既存のコードをスムーズに移行できるよう、詳細な説明と具体的なコード例が追加されています。

前提知識の解説

  • Go 1リリース: Go言語の最初のメジャー安定版リリース。このリリース以降、Go言語の互換性保証が始まり、既存のコードが将来のGoバージョンで動作し続けることが約束されました。
  • Go言語の型システム(値型とポインタ型): Go言語では、変数は値型(例: int, string, struct)またはポインタ型(例: *int, *MyStruct)として扱われます。値型はデータのコピーを渡し、ポインタ型はデータのメモリアドレスを渡します。time.Timeがポインタ型から値型に変更されたことは、その使用方法に大きな影響を与えます。
  • Unixエポック: 1970年1月1日00:00:00 UTCを基準点とする時刻表現。多くのシステムで時刻の内部表現として使用されます。Goの旧timeパッケージでは、time.Now()がUnixエポックからのナノ秒数を返していました。
  • gofixツール: Go言語のツールチェーンに含まれるコマンドラインツール。Go言語のAPIが変更された際に、古いAPIを使用しているコードを新しいAPIに自動的に書き換える機能を提供します。これにより、大規模なコードベースの移行作業が容易になります。ただし、すべてのケースを完全に自動で処理できるわけではなく、手動での修正が必要な場合もあります。
  • Go言語のパッケージ: Go言語のコードはパッケージに分割され、関連する機能がまとめられています。timeパッケージは、日付と時刻の操作に関連する機能を提供します。

技術的詳細

Go 1におけるtimeパッケージの再設計は、時間管理の概念をより明確にし、APIの使いやすさと堅牢性を向上させることを目的としていました。

旧APIの問題点

Go 1以前のtimeパッケージでは、時間の瞬間をint64型のナノ秒数で表現したり、*time.Timeというポインタ型で表現したりしていました。これにより、以下のような問題がありました。

  • 概念の混同: 時間の「瞬間」と「期間」が明確に区別されておらず、int64が両方の意味で使われることがありました。
  • ポインタ型の煩雑さ: *time.Timeは常にポインタとして扱われるため、値のコピーではなく参照渡しが基本となり、意図しない副作用やnilポインタの扱いに関する注意が必要でした。
  • Unixエポックへの依存: time.Now()が常にUnixエポックからのナノ秒数を返すため、特定の時間帯やカレンダーシステムに依存しない汎用的な時間操作が困難でした。

新APIの導入

Go 1では、これらの問題を解決するために、以下の2つの基本的な型が導入されました。

  1. time.Time:
    • 時間の「瞬間」を表す値型です(ポインタの*がなくなりました)。
    • ナノ秒の精度を持ちます。
    • 古代から遠い未来までのあらゆる時間を表現できます。
    • これにより、time.Timeの値を関数に渡す際に、値がコピーされるため、元の値が変更される心配がなくなりました。
  2. time.Duration:
    • 時間の「期間」または「間隔」を表す型です。
    • ナノ秒の精度を持ちます。
    • 約±290年間の期間を表現できます。
    • time.Secondのような便利な事前定義された定数が提供されます。

新しいメソッドとセマンティクス

新しいtime.Timetime.Duration型には、直感的な時間操作を可能にする多くのメソッドが追加されました。

  • Time.Add(d Duration) Time: TimeDurationを加算し、新しいTimeを返します。
  • Time.Sub(t Time) Duration: 2つのTimeの差を計算し、Durationを返します。
  • Time.After(u Time) bool: Timeが別のTimeより後であるかを判定します。
  • Time.Before(u Time) bool: Timeが別のTimeより前であるかを判定します。

最も重要なセマンティックな変更は、Unixエポックの関連性が限定されたことです。

  • time.Unix()Time.Unix()Time.UnixNano()といったUnixという名前が明示的に含まれる関数やメソッドのみがUnixエポックを基準とします。
  • time.Now()は、もはやUnixエポックからの整数ナノ秒数を返しません。 代わりに、現在の瞬間を表すtime.Time値を返します。これにより、時間操作がより抽象的で汎用的になりました。

標準パッケージへの波及

新しい型、メソッド、定数は、osパッケージにおけるファイルタイムスタンプの表現など、時間を使用するすべての標準パッケージに伝播されました。これにより、Goエコシステム全体で一貫した時間管理が可能になりました。

gofixによる移行支援

Go 1への移行を容易にするため、gofixツールが更新され、古いtimeパッケージの多くの使用箇所を新しい型とメソッドに自動的に更新できるようになりました。しかし、いくつかの注意点があります。

  • 1e9のようなナノ秒を表すリテラル値は自動的に置き換えられません。これらは手動でtime.Secondのような定数に置き換える必要があります。
  • 型変更によって生じる一部の式は、gofixによる書き換え後も手動での修正が必要になる場合があります。gofixは正しい関数やメソッドを提案しますが、型が合わない場合や、さらなる分析が必要な場合があります。

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

このコミットでは、主に以下の3つのファイルが変更されています。

  1. doc/go1.html:
    • Go 1の変更点をまとめたHTMLドキュメント。
    • timeパッケージのセクションが大幅に拡張され、新しいtime.Timetime.Durationの概念、主要なメソッド、Unixエポックの扱いの変更、gofixによる移行に関する説明が追加されました。
    • structEqualityのコード例がコメントアウトから解除され、Go 1で構造体がマップのキーとして使用できるようになったことを示すコードが有効化されました。
  2. doc/go1.tmpl:
    • doc/go1.htmlを生成するためのテンプレートファイル。
    • doc/go1.htmlに追加されたtimeパッケージに関する説明のテンプレートコードが追加されました。
  3. doc/progs/go1.go:
    • doc/go1.htmlで使用されるコード例を含むGoプログラム。
    • timeパッケージをインポートする行が追加されました。
    • main関数にtimePackage()の呼び出しが追加されました。
    • sleepUntil関数が追加されました。これは、time.Timetime.Durationtime.Now()Time.Sub()time.Sleep()の使用方法を示す具体的な例です。
    • timePackage関数が追加され、sleepUntilを呼び出すことでtimeパッケージの新しいAPIの利用例を示しています。
    • structEquality関数内のコードがコメントアウトから解除され、構造体をマップのキーとして使用する例が有効化されました。

コアとなるコードの解説

doc/progs/go1.goにおけるtimeパッケージの例

このコミットで追加されたsleepUntil関数は、新しいtimeパッケージのAPIを理解する上で非常に重要です。

// sleepUntil sleeps until the specified time. It returns immediately if it's too late.
func sleepUntil(wakeup time.Time) {
    now := time.Now() // A Time.
    if !wakeup.After(now) {
        return
    }
    delta := wakeup.Sub(now) // A Duration.
    log.Printf("Sleeping for %.3fs", delta.Seconds())
    time.Sleep(delta)
}

func timePackage() {
    sleepUntil(time.Now().Add(123 * time.Millisecond))
}
  • func sleepUntil(wakeup time.Time): この関数は、引数としてtime.Time型のwakeup(目覚める時刻)を受け取ります。これは、時間の「瞬間」を表す値型です。
  • now := time.Now(): time.Now()は、現在の瞬間を表すtime.Time値を返します。旧APIのようにint64のナノ秒数を返すわけではありません。
  • if !wakeup.After(now): Time.After()メソッドは、wakeupnowより後であるかを判定します。これにより、すでに指定時刻を過ぎている場合はすぐにリターンします。
  • delta := wakeup.Sub(now): Time.Sub()メソッドは、2つのtime.Time値の差を計算し、time.Duration型の値を返します。このdeltaは、スリープすべき「期間」を表します。
  • log.Printf("Sleeping for %.3fs", delta.Seconds()): Duration.Seconds()メソッドは、time.Durationを秒単位のfloat64として返します。これにより、期間を人間が読める形式で出力できます。
  • time.Sleep(delta): time.Sleep()関数は、引数としてtime.Durationを受け取り、その期間だけ現在のゴルーチンをスリープさせます。
  • func timePackage(): この関数は、sleepUntilの使用例を示しています。time.Now().Add(123 * time.Millisecond)は、現在の時刻に123ミリ秒の期間を加算した新しいtime.Time値を生成しています。time.Millisecondtime.Duration型の定数であり、期間の表現に役立ちます。

この例は、time.Timeが時間の瞬間を、time.Durationが時間の期間をそれぞれ明確に表現し、それらの間で直感的な操作(加算、減算、比較)が可能になったことを示しています。

structEqualityの変更

doc/progs/go1.gostructEquality関数内の変更は、timeパッケージとは直接関係ありませんが、Go 1で導入された別の重要な機能を示しています。

type Day struct {
    long  string
    short string
}
Christmas := Day{"Christmas", "XMas"}
Thanksgiving := Day{"Thanksgiving", "Turkey"}
holiday := map[Day]bool{
    Christmas:    true,
    Thanksgiving: true,
}
fmt.Printf("Christmas is a holiday: %t\n", holiday[Christmas])

このコードは、Go 1で構造体(struct)をマップのキーとして使用できるようになったことを示しています。Go 1以前は、マップのキーとして使用できるのは比較可能な型(プリミティブ型、ポインタ、インターフェース、配列、構造体、関数など)に限られており、構造体は比較可能ではありませんでした。Go 1では、すべてのフィールドが比較可能な構造体は比較可能となり、マップのキーとして使用できるようになりました。これにより、より複雑なデータ構造をマップのキーとして利用できるようになり、柔軟性が向上しました。

関連リンク

参考にした情報源リンク

  • Go 1 Release Notes - Time: https://go.dev/doc/go1#time
  • Go 1 Release Notes - Map keys: https://go.dev/doc/go1#map_keys
  • A Tour of Go - Time: https://go.dev/tour/moretypes/20 (これは現在のGoのツアーであり、Go 1当時のものではないが、timeパッケージの基本的な使い方を理解するのに役立つ)
  • Go言語のtimeパッケージに関するブログ記事やチュートリアル(具体的なURLは検索結果によるため省略)
  • Go言語のgofixに関する情報(具体的なURLは検索結果によるため省略)
  • Go言語の構造体の比較可能性に関する情報(具体的なURLは検索結果によるため省略)