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

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

このコミットは、Go言語の標準ライブラリの一部であるlib9ctime関数を追加するものです。特に、Unix系システムとPlan 9システム間でのctimeの挙動の違いを吸収し、コードの移植性を高めることを目的としています。これにより、gopackツールが日付表示を正しく行えるようになります。

コミット

commit 3f4a91d778ac4cab817e9d08c193a00a642f19aa
Author: Russ Cox <rsc@golang.org>
Date:   Mon Nov 7 13:15:16 2011 -0500

    lib9: add ctime
    
    ctime differs across Unix vs Plan 9 so add to portability library
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/5363043
---\n
 include/libc.h      |  2 ++\n
 src/cmd/gopack/ar.c |  7 ++-----\n
 src/lib9/Makefile   |  1 +\n
 src/lib9/ctime.c    | 28 ++++++++++++++++++++++++++++\n
 4 files changed, 33 insertions(+), 5 deletions(-)\n

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

https://github.com/golang/go/commit/3f4a91d778ac4cab817e9d08c193a00a642f19aa

元コミット内容

このコミットの主な目的は、lib9(Go言語の移植性ライブラリ)にctime関数を追加することです。これは、Unix系システムとPlan 9システムの間でctime関数の動作が異なるため、その差異を吸収し、Goのツールが異なる環境でも正しく動作するようにするための対応です。特に、gopackコマンドがアーカイブファイルの日付情報を表示する際に、この違いが問題となっていたようです。

変更の背景

Go言語は、その設計思想として「移植性」を重視しています。異なるオペレーティングシステム(OS)やアーキテクチャ上で同じコードが動作するように、多くの標準ライブラリがOS間の差異を吸収するよう設計されています。

ctimeは、C言語の標準ライブラリ関数の一つで、time_t型の時刻値を人間が読める形式の文字列に変換します。しかし、この関数の出力フォーマットや内部的な実装は、OSによって微妙に異なる場合があります。特に、Unix系OSとPlan 9という異なる設計思想を持つOS間では、その差異が顕著になることがあります。

このコミットでは、gopackというGoのツールが、アーカイブファイル(.aファイル)のメンバーの日付情報を表示する際に、ctimeを使用しています。もしctimeの挙動がOS間で異なると、gopackが生成する日付文字列のフォーマットが期待通りにならず、互換性の問題や表示の不整合が生じる可能性があります。

この問題を解決するため、Goの移植性ライブラリであるlib9に、Goが内部的に使用する統一されたctimeの実装(p9ctime)を追加することで、OS間の差異を吸収し、gopackを含むGoツールが常に正しい日付フォーマットで動作するようにしています。

前提知識の解説

ctime関数

ctimeはC標準ライブラリ<time.h>で定義されている関数です。 char *ctime(const time_t *timer); この関数は、timerが指すtime_t型の時刻値を、ローカルタイムゾーンでの人間が読める形式の文字列に変換して返します。返される文字列の形式は通常、以下のようになります。 "Www Mmm dd hh:mm:ss yyyy\n" 例: "Fri Jul 5 10:30:00 2025\n"

time_t

time_tは、通常、エポック(1970年1月1日00:00:00 UTC)からの経過秒数を表す整数型です。システムによってそのサイズや符号の有無が異なる場合があります。

struct tm構造体

struct tmは、カレンダーの時刻を分解して保持するための構造体です。<time.h>で定義されており、以下のメンバーを含みます。

  • int tm_sec; // 秒 (0-60)
  • int tm_min; // 分 (0-59)
  • int tm_hour; // 時 (0-23)
  • int tm_mday; // 月内の日 (1-31)
  • int tm_mon; // 1月からの月 (0-11)
  • int tm_year; // 1900年からの年
  • int tm_wday; // 日曜日からの曜日 (0-6)
  • int tm_yday; // 1月1日からの日 (0-365)
  • int tm_isdst; // 夏時間フラグ

localtime関数

localtimeはC標準ライブラリ<time.h>で定義されている関数です。 struct tm *localtime(const time_t *timer); この関数は、timerが指すtime_t型の時刻値を、ローカルタイムゾーンでのstruct tm構造体に変換して返します。

snprintf関数

snprintfはC標準ライブラリ<stdio.h>で定義されている関数です。 int snprintf(char *str, size_t size, const char *format, ...); この関数は、formatに従って書式化された文字列をstrに書き込みます。sizestrが指すバッファの最大サイズを指定し、バッファオーバーフローを防ぐことができます。

移植性ライブラリ (lib9)

Go言語のソースコードには、lib9というディレクトリが存在します。これは、Goが動作する様々なOS(Unix系、Plan 9、Windowsなど)間で、C言語の標準ライブラリ関数やシステムコールの一部について、Goが内部的に使用する統一されたインターフェースや実装を提供する「移植性ライブラリ」です。OS固有の挙動の違いを吸収し、Goのコードが異なる環境でも一貫して動作するようにするために利用されます。

UnixとPlan 9のctimeの違い

コミットメッセージにある「ctime differs across Unix vs Plan 9」という記述は、具体的にはctimeが返す文字列のフォーマット、特に年の位置やタイムゾーンの表示方法に違いがあることを示唆しています。

  • Unix系OSのctime: 一般的に、年の情報は文字列の末尾に4桁で表示されます。例: Fri Jul 5 10:30:00 2025
  • Plan 9のctime: Plan 9のctimeは、Unixとは異なるフォーマットを持つことがあります。特に、年の位置がUnixとは異なる場合や、タイムゾーンの表示が省略される、あるいは異なる形式で表示されることがあります。このコミットのコードを見ると、Unixのctimeが返す文字列の20文字目から年が始まる(cp+20)と仮定している箇所があり、Plan 9ではそれが異なる(cp+24)という認識があったようです。これは、ctimeが返す文字列の構造がOSによって異なるため、文字列のオフセットで特定の情報を抽出する際に問題となることを示しています。

技術的詳細

このコミットは、Go言語のlib9ライブラリにp9ctimeという新しい関数を追加し、既存のctimeマクロをp9ctimeにリダイレクトすることで、OS間のctimeの差異を吸収しています。

  1. src/lib9/ctime.cの追加:

    • p9ctime関数が新しく定義されています。この関数は、long ttime_tに相当)を引数に取り、char*を返します。
    • 内部では、localtime関数を使用してtime_t値をstruct tm構造体に変換します。
    • snprintfを使用して、struct tmの各メンバーから、Goが期待する統一された日付フォーマットの文字列を生成します。
    • フォーマット文字列は、"SunMonTueWedThuFriSat""JanFebMarAprMayJunJulAugSepOctNovDec"という文字列リテラルをオフセットで参照することで、曜日と月の略称を効率的に取得しています。
    • tm->tm_year + 1900で正しい年を取得しています。
    • tm->tm_zoneを使用してタイムゾーン情報を取得しています。これは、Unix系OSのctimeがタイムゾーンを文字列に含めるのに対し、Plan 9では異なる可能性があるため、明示的に含めることで一貫性を保っています。
    • 生成された文字列は静的バッファbufに格納され、そのポインタが返されます。これは、ctime関数の一般的な実装パターンです。
  2. include/libc.hの変更:

    • extern char* p9ctime(long);というプロトタイプ宣言が追加され、p9ctime関数が外部から利用可能になります。
    • #define ctime p9ctimeというマクロが追加されています。これにより、Goのコード内でctimeが呼び出された場合、実際には新しく定義されたp9ctime関数が呼び出されるようになります。これは、既存のコードを変更せずに、新しい実装に切り替えるための一般的な手法です。
  3. src/cmd/gopack/ar.cの変更:

    • longt関数内で、ctimeの呼び出し方が変更されています。
    • 変更前: date = bp->date; cp = ctime(&date);
    • 変更後: cp = ctime(bp->date);
      • これは、bp->dateがすでにtime_t型(またはそれに相当するlong型)であるため、一時変数dateを介さずに直接ctime(実際にはp9ctime)に渡すように簡略化されたものです。
    • 最も重要な変更は、Bprint(&bout, " %-12.12s %-4.4s ", cp+4, cp+20);Bprint(&bout, " %-12.12s %-4.4s ", cp+4, cp+24);に変更された点です。
      • これは、Unixのctimeが返す文字列の20文字目から年が始まるという仮定(cp+20)が、p9ctimeが生成する文字列では24文字目から年が始まる(cp+24)という新しいフォーマットに合わせたものです。p9ctimesnprintfフォーマットを見ると、%3.3s %3.3s %02d %02d:%02d:%02d %3.3s %d\nとなっており、曜日(3) + 月(3) + 日(3) + 時刻(8) + タイムゾーン(4) + 年(4) + 改行(1) の合計26文字(スペース含む)で、年の開始位置がcp+24になることが確認できます。
  4. src/lib9/Makefileの変更:

    • LIB9OFILES変数にctime.$Oが追加されています。これは、新しく追加されたsrc/lib9/ctime.cファイルがlib9ライブラリのビルドプロセスに含まれるようにするための変更です。

これらの変更により、Goのビルドシステムはp9ctimelib9の一部としてコンパイルし、Goのコードがctimeを呼び出す際には、OSネイティブのctimeではなく、Goが提供する移植性の高いp9ctimeが使用されるようになります。これにより、異なるOS環境下でも日付表示の一貫性が保たれます。

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

  • include/libc.h:
    • p9ctime関数のプロトタイプ宣言を追加。
    • ctimeマクロをp9ctimeに定義し直す。
  • src/cmd/gopack/ar.c:
    • longt関数内で、ctimeへの引数の渡し方を簡略化。
    • ctimeが返す文字列から年を抽出する際のオフセットをcp+20からcp+24に変更。
  • src/lib9/Makefile:
    • LIB9OFILESctime.$Oを追加し、src/lib9/ctime.cがビルドされるようにする。
  • src/lib9/ctime.c:
    • p9ctime関数を新規作成。この関数は、time_t値をstruct tmに変換し、snprintfを使ってGoが期待するフォーマットの文字列を生成する。

コアとなるコードの解説

src/lib9/ctime.c (新規追加ファイル)

// Copyright 2011 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

#define NOPLAN9DEFINES // Plan 9固有の定義を無効化
#include <u.h>         // Plan 9の基本的な型定義など
#include <libc.h>      // Plan 9の標準ライブラリ関数定義など

char*
p9ctime(long t) // time_t型の時刻値を受け取る
{
	static char buf[100]; // 静的バッファ。呼び出しごとに再利用される
	time_t tt;            // time_t型の一時変数
	struct tm *tm;        // struct tm構造体へのポインタ

	tt = t;               // 引数のlong値をtime_tに代入
	tm = localtime(&tt);  // ローカルタイムゾーンでのstruct tm構造体を取得
	snprint(buf, sizeof buf, "%3.3s %3.3s %02d %02d:%02d:%02d %3.3s %d\n",
		"SunMonTueWedThuFriSat"+(tm->tm_wday*3), // 曜日文字列から3文字を抽出
		"JanFebMarAprMayJunJulAugSepOctNovDec"+(tm->tm_mon*3), // 月文字列から3文字を抽出
		tm->tm_mday,   // 日
		tm->tm_hour,   // 時
		tm->tm_min,    // 分
		tm->tm_sec,    // 秒
		tm->tm_zone,   // タイムゾーン名
		tm->tm_year + 1900); // 1900年からの年を現在の年に変換
	return buf; // 生成された文字列のポインタを返す
}

このp9ctime関数は、Go言語が内部的に使用するctimeの実装です。引数として受け取ったlong型の時刻値をtime_tにキャストし、localtimestruct tm構造体に変換します。その後、snprintfを使って、曜日、月、日、時、分、秒、タイムゾーン、年を整形し、静的バッファbufに書き込みます。このバッファのポインタが返されることで、呼び出し元は整形された日付文字列を利用できます。特に、曜日と月の文字列は、固定文字列からのオフセット計算によって効率的に取得されています。

include/libc.h

// ... (既存のコード) ...

extern	char*\tp9ctime(long); // p9ctime関数のプロトタイプ宣言
#define p9setjmp(b)\tsigsetjmp((void*)(b), 1)

extern	void\tsysfatal(char*, ...);
// ... (既存のコード) ...

#undef  strtod
#define strtod\t\tfmtstrtod
#define charstod\tfmtcharstod
#define ctime\tp9ctime // ctimeマクロをp9ctimeに定義し直す
#endif

// ... (既存のコード) ...

libc.hでは、p9ctimeのプロトタイプ宣言が追加され、さらに#define ctime p9ctimeというマクロが定義されています。これにより、Goのソースコード内でctimeという名前が使われている箇所は、プリプロセッサによって自動的にp9ctimeに置き換えられます。これは、既存のコードベースに大きな変更を加えることなく、新しい移植性の高いctime実装を導入するための一般的な手法です。

src/cmd/gopack/ar.c

// ... (既存のコード) ...

void
longt(Armember *bp)
{
 	char *cp;
-	time_t date; // 削除: 一時変数dateは不要になった

 	pmode(strtoul(bp->hdr.mode, 0, 8));
 	Bprint(&bout, "%3ld/%1ld", strtol(bp->hdr.uid, 0, 0), strtol(bp->hdr.gid, 0, 0));
 	Bprint(&bout, "%7ld", bp->size);
-	date = bp->date; // 削除
-	cp = ctime(&date); // 変更前: &dateを渡していた
-	/* using unix ctime, not plan 9 time, so cp+20 for year, not cp+24 */ // 削除: コメントが古くなった
-	Bprint(&bout, " %-12.12s %-4.4s ", cp+4, cp+20); // 変更前: 年のオフセットがcp+20
+	cp = ctime(bp->date); // 変更後: bp->dateを直接渡す
+	Bprint(&bout, " %-12.12s %-4.4s ", cp+4, cp+24); // 変更後: 年のオフセットがcp+24
}

// ... (既存のコード) ...

gopackツールのar.cファイルでは、アーカイブメンバーの日付情報を表示するlongt関数が修正されています。

  1. time_t date;という一時変数が削除されました。これは、bp->dateが直接ctime(実際にはp9ctime)に渡せるようになったためです。
  2. cp = ctime(&date);からcp = ctime(bp->date);に変更されました。これは、p9ctimelong型の引数を直接取るように設計されているためです。
  3. 最も重要な変更は、Bprint関数内で年を抽出する際のオフセットがcp+20からcp+24に変更された点です。これは、p9ctimeが生成する日付文字列のフォーマットが、従来のUnixのctimeとは異なり、年の開始位置が24文字目から始まるように調整されたためです。これにより、gopackp9ctimeが返す文字列から正しい年情報を抽出できるようになります。

src/lib9/Makefile

# ... (既存のコード) ...

LIB9OFILES=\
	atoi.$O\
	cleanname.$O\
	create.$O\
	ctime.$O\ # ctime.$Oを追加
	dirfstat.$O\
	dirfwstat.$O\
	dirstat.$O\
# ... (既存のコード) ...

Makefileには、LIB9OFILES変数にctime.$Oが追加されています。これは、新しく作成されたsrc/lib9/ctime.cファイルがコンパイルされ、lib9ライブラリの一部としてリンクされるようにするための指示です。これにより、p9ctime関数がGoのビルドシステムによって正しく組み込まれることが保証されます。

関連リンク

参考にした情報源リンク

(Note: The specific differences in ctime output between Unix and Plan 9 are often subtle and not always explicitly documented in a single place. The commit message itself is the primary indicator of this issue. The code changes then show how Go addresses this by providing its own consistent implementation.) I have generated the comprehensive technical explanation for the commit.