[インデックス 10275] ファイルの概要
このコミットは、Go言語の標準ライブラリの一部であるlib9
にctime
関数を追加するものです。特に、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
に書き込みます。size
はstr
が指すバッファの最大サイズを指定し、バッファオーバーフローを防ぐことができます。
移植性ライブラリ (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
の差異を吸収しています。
-
src/lib9/ctime.c
の追加:p9ctime
関数が新しく定義されています。この関数は、long t
(time_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
関数の一般的な実装パターンです。
-
include/libc.h
の変更:extern char* p9ctime(long);
というプロトタイプ宣言が追加され、p9ctime
関数が外部から利用可能になります。#define ctime p9ctime
というマクロが追加されています。これにより、Goのコード内でctime
が呼び出された場合、実際には新しく定義されたp9ctime
関数が呼び出されるようになります。これは、既存のコードを変更せずに、新しい実装に切り替えるための一般的な手法です。
-
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
)という新しいフォーマットに合わせたものです。p9ctime
のsnprintf
フォーマットを見ると、%3.3s %3.3s %02d %02d:%02d:%02d %3.3s %d\n
となっており、曜日(3) + 月(3) + 日(3) + 時刻(8) + タイムゾーン(4) + 年(4) + 改行(1) の合計26文字(スペース含む)で、年の開始位置がcp+24
になることが確認できます。
- これは、Unixの
-
src/lib9/Makefile
の変更:LIB9OFILES
変数にctime.$O
が追加されています。これは、新しく追加されたsrc/lib9/ctime.c
ファイルがlib9
ライブラリのビルドプロセスに含まれるようにするための変更です。
これらの変更により、Goのビルドシステムはp9ctime
をlib9
の一部としてコンパイルし、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
:LIB9OFILES
にctime.$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
にキャストし、localtime
でstruct 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
関数が修正されています。
time_t date;
という一時変数が削除されました。これは、bp->date
が直接ctime
(実際にはp9ctime
)に渡せるようになったためです。cp = ctime(&date);
からcp = ctime(bp->date);
に変更されました。これは、p9ctime
がlong
型の引数を直接取るように設計されているためです。- 最も重要な変更は、
Bprint
関数内で年を抽出する際のオフセットがcp+20
からcp+24
に変更された点です。これは、p9ctime
が生成する日付文字列のフォーマットが、従来のUnixのctime
とは異なり、年の開始位置が24文字目から始まるように調整されたためです。これにより、gopack
はp9ctime
が返す文字列から正しい年情報を抽出できるようになります。
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のビルドシステムによって正しく組み込まれることが保証されます。
関連リンク
- Go CL 5363043: https://golang.org/cl/5363043
参考にした情報源リンク
- C言語
ctime
関数: https://ja.cppreference.com/w/c/chrono/ctime - C言語
localtime
関数: https://ja.cppreference.com/w/c/chrono/localtime - C言語
snprintf
関数: https://ja.cppreference.com/w/c/io/fprintf (snprintfもこのページで解説されています) - Plan 9 from Bell Labs: https://9p.io/plan9/ (Plan 9に関する一般的な情報)
- Unix
ctime
format: https://pubs.opengroup.org/onlinepubs/9699919799/functions/ctime.html
(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.