Gitが表現できる最古の日付は何時か？

# tl;dr

実用的には `1970-01-01 00:00:00 +0000` 。

実際のリポジトリ: https://github.com/okuoku/the-epoc-repo

追記: **DO NOT TRY THIS AT HOME** . これGitHubのプロフィールに何故か1969年の項目ができるようだ。(後述)

# (没になった前置き)

今年2019年はUNIX生誕50周年で、ちょうど1969年の夏にPDP-7上でUNIX(の原型)が成立したあたりからカウントされている。UNIX日付を採用したGitで最古のコミットをpushすると51年目に...

![SnapCrab_NoName_2019-6-30_3-26-45_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67446/b5831dc5-b00f-c64d-8267-369954424a12.png)

ならねぇじゃん (終)

# 最古のコミットの作り方

`git commit` は `--date` オプションでauthor dateを直接指定できる。

```
git commit --date="1970-01-01 00:00:00 +0000" -m temp
```

この方法だとcommitter dateは `git commit` した瞬間のものが採用されるため、committer dateを表示する箇所でも昔の日付を表示したければ `GIT_COMMITTER_DATE` 環境変数を設定した上で実行することになる。

## plumbing コマンドを直接使う

(see comments。初出ではこちらを先に書いていた)

gitは細かい操作が全てコマンドに分かれており、コマンドライン上でかなり低レベルな操作を直接行える。重要なのは `write-tree` コマンドと `commit-tree` コマンドで、特に `commit-tree` コマンドに生の日付が直接与えられるのがミソということになる。

手順は、

1. `git init` でリポジトリを用意する
2. `git add` で `index` に適当にファイルを追加する
3. `git write-tree` で `index` をtreeオブジェクトに変換する
4. `git commit-tree` でtreeオブジェクトを指すcommitオブジェクトを用意する (日付は環境変数 `GIT_AUTHOR_DATE` 等で与える)
5. `git reset` なりなんなりでHEADを更新する

のようになる。具体的には、

```
git init
git add README.txt
git write-tree > tree.txt
GIT_AUTHOR_DATE="1970-01-01 00:00:00 +0000" GIT_COMMITTER_DATE="1970-01-01 00:00:00 +0000" git commit-tree -F msg1.txt `cat tree.txt`
```

のような感じになる。

ここで与えている、 `1970-01-01 00:00:00 +0000` の日付が、Gitで普通に表現できる最古の日付ということになる。これは **UNIX Epoch** と呼ばれる時刻で、Gitを含めUNIX上のプログラムの多くが採用している時刻系の **始端** にあたる。

このコミットを表示させると、時刻部分には `Thu Jan 1 00:00:00 1970 +0000` とか `0 +0000` と表示される。後者が実際のGitオブジェクトで使われる形式で、UNIX時刻的にはゼロということになる。

```:人間にやさしい表示
$ git show 
commit e111c81454967d83488272a47c9422c446d6fa65
Author: okuoku <mjt@cltn.org>
Date:   Thu Jan 1 00:00:00 1970 +0000
```

```:人間に厳しい表示
$ git cat-file commit HEAD
tree 0f148df97147b603999860a1fed76de1f5ee3b02
author okuoku <mjt@cltn.org> 0 +0000
committer okuoku <mjt@cltn.org> 0 +0000

This is +0000 timezone offset commit
```

ちなみに、 `git commit-tree` のマニュアル( https://git-scm.com/docs/git-commit-tree )にあるように、 `GIT_AUTHOR_DATE` などはいくつかの日付フォーマットを受け入れ、UNIX時刻を直接書くこともできる。が、上で出てきた `0 +0000` は直接指定することはできない。 **UNIX時刻表記は8ケタ以上必要** で、それ以下では認識されないようになっている。これは、通常のYYYYMMDDフォーマットと紛らわしいため。

- https://stackoverflow.com/questions/5093533/git-ignores-git-author-date-is-this-a-bug/5093714#5093714
  - SO の回答
- https://github.com/git/git/blob/6e0cc6776106079ed4efa0cc9abace4107657abf/date.c#L637
  - 実際のコード

```c

	/*
	 * Seconds since 1970? We trigger on that for any numbers with
	 * more than 8 digits. This is because we don't want to rule out
	 * numbers like 20070606 as a YYYYMMDD date.
	 */
	if (num >= 100000000 && nodate(tm)) {
		time_t time = num;
		if (gmtime_r(&time, tm)) {
			*tm_gmt = 1;
			return end - date;
		}
	}
```

# もっと古い時刻を入れるとどうなるのか

当然の疑問として、もっと古い時刻を入れるとどうなるのかというものがある。実際、JSTにおける1970/1/1深夜である、 `1970-01-01 00:00:00 +0900` はコマンドとしては受け入れられ、実際にコミットを作ることもできる:

```:コミットを表示させたところ
$ git cat-file commit 321603c4a49a98b4bb0502640844d56ca145da71
tree c6babee1ec49d936cf7df8f20b309d5d8627ce20
author okuoku <mjt@cltn.org> 18446744073709519216 +0900
committer okuoku <mjt@cltn.org> 1561830314 +0900
```

ここでは、 `18446744073709519216` と相当べらんめぇな数値が表示されているが、これを16進表記すると `0xffffffffffff8170` となり、いわゆるinteger overflowを起こしていることがわかる。UNIX時刻はふつう符号付きで使われる(ので32bitsでは [2038年問題](https://ja.wikipedia.org/wiki/2038%E5%B9%B4%E5%95%8F%E9%A1%8C) を起こす)。

一見コミット的には正常に行われているように見えるが、このようなコミットは `git fsck` を通らず、GitHubにはpushできない。

```
Counting objects: 4, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (4/4), 979 bytes | 979.00 KiB/s, done.
Total 4 (delta 0), reused 0 (delta 0)
remote: error: object 32b0128451f66ca872431bc6c23fb2854b20e85d: badDateOverflow: invalid author/committer line - date causes integer overflow
remote: fatal: fsck error in packed object
error: remote unpack failed: index-pack abnormal exit
To github.com:okuoku/the-oldest-repo.git
 ! [remote rejected] master -> master (failed)
error: failed to push some refs to 'git@github.com:okuoku/the-oldest-repo.git'
```

このチェックは `date_overflows` 関数で行われていて、64bit符号無しで `TIME_MAX` に収まることと、システムの `time_t` に収まることの両方を要求している。UNIX Epochから見た負値、つまり1970/1/1 深夜 UTCよりも前の日付は後者の条件を満たさず(システムの`time_t` で解釈すると異符号)、Git的には不正ということになる。

- https://github.com/git/git/blob/8dca754b1e874719a732bc9ab7b0e14b21b1bc10/date.c#L1320

```c
int date_overflows(timestamp_t t)
{
	time_t sys;

	/* If we overflowed our timestamp data type, that's bad... */
	if ((uintmax_t)t >= TIME_MAX)
		return 1;

	/*
	 * ...but we also are going to feed the result to system
	 * functions that expect time_t, which is often "signed long".
	 * Make sure that we fit into time_t, as well.
	 */
	sys = t;
	return t != sys || (t < 1) != (sys < 1);
}
```

# 実際のリポジトリ

今回GitHubにリポジトリを用意した( https://github.com/okuoku/the-epoc-repo )が、これは当然fakeで、実際の50年物のコードとしては例えばUNIX history repo( https://github.com/dspinellis/unix-history-repo )がある。

<blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">今年でunix-history-repo <a href="https://t.co/19ZJ79xpAw">https://t.co/19ZJ79xpAw</a> は50 years agoに突入してるのか。。Git日付はUNIX時間なのでUNIX以前の歴史は記録できない。そのUNIXの歴史の最初が1970年6月30日、UNIX時間 15588000 (-0500) ということになっている。 <a href="https://t.co/q1eQqboYQY">pic.twitter.com/q1eQqboYQY</a></p>&mdash; okuoku (@okuoku) <a href="https://twitter.com/okuoku/status/1113241164088455168?ref_src=twsrc%5Etfw">April 3, 2019</a></blockquote>

つまり、 **Git的には1970年以前の人類はソフトウェアを書いていない** ことになる。 これに対して、 [Windowsのシステム時間は1601年1/1から始まる](https://docs.microsoft.com/en-us/windows/desktop/SysInfo/file-times) から、UNIXよりもWindowsの方がずっと歴史があるな！ (実際には、WindowsのEpochはWindowsの開発開始付近ではなく、 [単に開発開始時のグレゴリオ歴の400年周期が始まった年](https://devblogs.microsoft.com/oldnewthing/20090306-00/?p=18913) というだけで決まっている。)

# 追記: 1969 年の謎

... ↑では散々1970年が最古の日付と書いてきたが、何故かGitHubのプロフィール上ではこの日付は1969年として扱われるようだ。

- https://github.com/okuoku?tab=overview&from=1969-12-01&to=1969-12-31

![SnapCrab_NoName_2019-6-30_19-33-30_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67446/83de26a5-2071-6163-bdc1-3176a0426e0f.png)

Svnミラーや他の方法を駆使すればもっと昔の日付にチャレンジできるかもしれない。

(add: このズレもたぶんタイムゾーンの都合だと思うんだけど、ローカル時刻を変えるだけではどうやっても1970年にすることはできなかった。GitHubの設置場所に依る？)