6
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

狩野英孝さんのポストは、なぜ1992年へ飛んだのか?整数オーバーフローが起こす時刻バグ

6
Posted at

はじめに

こんばんは。

狩野英孝さんのポスト日時が1992年と表示されたニュース画像

2026年3月、お笑い芸人の狩野英孝さんがXへ投稿したはずのポストに、なぜか1992年9月2日と表示されて話題になりました。彼は笑いの神様に愛されるあまり、時空まで操れるようになったのでしょうか。

実は、X以外でも似た現象は起きています。未遂もありますが、カンナムスタイル、Y2K問題、2038年問題とか、ですね。聞き覚えある方も多いのではないでしょうか。では、狩野さんを1992年へ送った犯人を、いくつかの事件から追跡して、断崖絶壁で「犯人はお前だ!」をやりましょう。

目次

事件1 狩野英孝さん、10歳で未来の仕事をポストする
事件2 機器が1970年へ戻る
事件3 カンナムスタイルが21億の壁を越える
有力な犯人候補は秒とミリ秒の取り違え
Y2K問題、年を2桁に省いた代償
2038年問題、次は時刻そのものがあふれる

事件1 狩野英孝さん、10歳で未来の仕事をポストする

話題になったのは、狩野さんが「大阪へやってきました」と報告し、ジャルジャルさんとの仕事を楽しみにしているポストです。Xの画面では、投稿日時が「1992年9月2日 午後11時07分」と表示されています。

Twitterで最初の投稿が行われたのは2006年3月21日です。狩野さんは1982年2月22日生まれなので、2026年7月現在は44歳、本当にこの日時に行ったのなら投稿時は10歳でした。

さらに、狩野さんがデビューしたのは2003年、ジャルジャルさんの結成も2003年4月です。つまり10歳の狩野さんは、Twitterが生まれる約14年前、本人と仕事相手が芸人として世に出る10年以上前に、未来の仕事を報告したことになります。卵が先か鶏が先か、いや狩野が先と言わんばかりの時空操作で、永遠の論争を終わらせました。

もちろん、これはX上の表示だけが実際の投稿日時とは異なる値へ変わった事象です。その仕組みを理解するには、コンピュータが時刻をどのように保存しているかを知る必要があります。またあとで深追いしましょう。

事件2 機器が1970年へ戻る

私は似たような現象に、実体験ベースで心当たりがありました。電池が切れたり初期化したりした機器で、写真の日付や時計が1970年1月1日付近になっていたことがあります。撮影したのは今日なのに、記録上はなぜか昭和45年になっていました。当時戦後の歴史を専攻していたのと信長協奏曲を読んでいたので、ついにタイムリープしたのかとウキウキしました。

実はPOSIX(OSがアプリケーションへ提供する機能や呼び出し方を共通化し、プログラムを移植しやすくするための標準仕様)に準拠するシステムでは、時刻を1970年1月1日0時0分0秒(UTC)から経過した秒数で表します。一般にUNIX時間、Unixタイムスタンプなどと呼ばれる形式です。

from datetime import datetime, timezone

# UNIX時間の0秒と、記事執筆日の0時(UTC)を確認する
epoch = datetime.fromtimestamp(0, tz=timezone.utc)
written_at = datetime(2026, 7, 23, tzinfo=timezone.utc)

print(epoch.isoformat())
print(int(written_at.timestamp()))

ためしにこのPythonコードを実行すると、こうなります。

1970-01-01T00:00:00+00:00
1784764800

1行目はUNIX時間の0に対応する日時で、末尾の+00:00はUTCを示します。2行目の1784764800は、1970年1月1日から2026年7月23日までに経過した秒数です。コンピュータは、このような整数を人が読める日時へ変換して表示しています。

もし現在時刻を失った機器やソフトウェアが初期値の0を時刻として扱えば、画面には1970年1月1日が現れます。時間を数え始めた地点へ戻った、という感じなんです。

時刻の起点は実装によって異なるため、すべての機器が1970年へ戻るとは限りません。それでも1970年付近を何度も見かけるのは、UNIX時間の0がそこにあるためです。

2016年には、一部のiPhoneなどで日付を1970年5月以前へ手動変更して再起動すると、端末が起動しなくなる問題も発生しました。AppleはiOS 9.3で修正したことを公式に記録しています。狩野さんの表示異常とは別の不具合ですが、1970年付近の値を想定どおり扱えなかった点は共通しています。

事件3 カンナムスタイルが21億の壁を越える

時間に限らず、コンピュータで扱う整数には、データ型ごとの上限があります。その上限を越えて値が表現できなくなることを、整数オーバーフローと呼びます。

2014年12月、PSYさんのカンナムスタイルは2,147,483,647回を越えました。符号付き32ビット整数の最大値、2³¹ - 1です。Pythonで計算すると、約21億の出どころがわかります。

bits = 32

signed_int32_max = (1 << (bits - 1)) - 1
unsigned_int32_max = (1 << bits) - 1

print(f"符号付き32ビット整数の最大値: {signed_int32_max:,}")
print(f"符号なし32ビット整数の最大値: {unsigned_int32_max:,}")
符号付き32ビット整数の最大値: 2,147,483,647
符号なし32ビット整数の最大値: 4,294,967,295

符号付きは負数も扱える型で、32ビットでは-2,147,483,648から2,147,483,647までです。符号なしは32ビットすべてを0以上の値へ使うため、0から4,294,967,295まで扱えます。同じ32ビットでも、符号なしの上限は符号付きのほぼ2倍です。

YouTube自身が当時、32ビット整数の上限を2,147,483,647回と説明したため、ここでは符号付き32ビット整数として考えます。問題点として、上限を越えた値を同じ32ビットへ収めて折り返す実装では、その次が負数の最小値-2,147,483,648になってうことがあります。

では、21億回に達した瞬間、再生数が-2,147,483,648へ反転したのでしょうか。実際には、カウンターは止まらず、負数にもなりませんでした。実はYouTubeは僅か数か月前に64ビット整数へ移行しており、12月3日の時点ですでに約21億5千万回を正しく表示していました。

代わりに起きたのが、YouTubeらしい遊びです。再生数にマウスカーソルを載せると数字が回転するイースターエッグ(隠し機能やメッセージなどの遊び)を仕込み、PSYさんに出会うまでは32ビット整数を越えるほど再生される動画があると思わなかった、と公式Google+で発表しました(公式投稿はGoogle+の終了に伴って消えています)。

つまり、カンナムスタイルがYouTubeを停止させる前に、開発者が気づいて器を大きくしていました。到達後にはイースターエッグとして祝った、というのが実際の経緯です。

この記事を書いている2026年7月時点で、動画ページの再生回数は60億回を越えています。約21億の壁を、今ではほぼ3周しているとんでもない動画です。

有力な犯人候補は秒とミリ秒の取り違え

それでは、狩野さんを1992年へ送った仕組みとして有力な説から、「犯人はお前だ!(仮)」をやりましょう。

XのポストURL末尾にある145669840114425857は、Snowflakeと呼ばれる64ビットのIDです。Xの仕様では、このIDには投稿時刻、IDを生成したサーバー、同じミリ秒内での順番が含まれています。表示が壊れても、URLに残ったIDから本来の投稿時刻を確かめることができます。

Twitterが公開したSnowflakeの仕様を使って時刻を復元すると、本来の投稿時間は2011年12月11日10時02分50秒(日本時間)になります。
そして、現在有力な説であるUNIX時間の秒とミリ秒を取り違え、32ビット整数として処理をしたことによってタイムトラベルしてしまった、流れを再現してみます。

from datetime import datetime, timedelta, timezone

# 問題のポストURLに含まれるSnowflake ID
post_id = 145669840114425857

# TwitterがSnowflakeで使った独自エポック(ミリ秒)
twitter_epoch_ms = 1288834974657
jst = timezone(timedelta(hours=9))

# 上位41ビットから本来の投稿時刻を復元する
posted_at_ms = (post_id >> 22) + twitter_epoch_ms
posted_at_sec = posted_at_ms // 1000

# 秒をミリ秒へ変換した値を、32ビット符号なし整数へ押し込む
mistaken_milliseconds = posted_at_sec * 1000
uint32_mask = (1 << 32) - 1
overflowed_value = mistaken_milliseconds & uint32_mask

# あふれた値を、今度は秒として日時へ戻す
actual_date = datetime.fromtimestamp(posted_at_ms / 1000, tz=jst)
displayed_date = datetime.fromtimestamp(overflowed_value, tz=jst)

print(f"本来の投稿日時: {actual_date.isoformat()}")
print(f"UNIX時間(秒): {posted_at_sec}")
print(f"1000倍した値: {mistaken_milliseconds}")
print(f"32ビットに残る値: {overflowed_value}")
print(f"秒として読み直した日時: {displayed_date.isoformat()}")
本来の投稿日時: 2011-12-11T10:02:50.955000+09:00
UNIX時間(秒): 1323565370
1000倍した値: 1323565370000
32ビットに残る値: 715442832
秒として読み直した日時: 1992-09-02T23:07:12+09:00

出ました。Xに表示された1992年9月2日23時07分と、秒まで一致します。

大きな数を32ビットへ押し込むと、上位の桁は入りきらず、下位32ビットにあたる715442832だけが残ります。その残り物を秒として日付へ戻すと、2011年のポストが1992年へ着地するわけです。

原因を検証された祥太さんは、同じ1秒間に投稿された日本語ポストを検索したところ、この表示異常が起きていたのは狩野さんのポストだけだったと報告しています。数あるポストの中で狩野さんだけが1992年へ飛ぶあたり、これはもう笑いの神様に愛されていることは疑いようがありません。

本稿の執筆時点で、X公式からこの表示異常の詳しい原因説明は確認できなかったので、ここで示した処理は、投稿IDから復元した本来の日時と画面上の日時を一致させられる有力な再現例だとご理解ください。

つまり犯人(仮)は、秒とミリ秒を取り違え、ミリ秒相当の値を32ビット整数に収めた際に整数オーバーフローが起き、その結果残った値を秒として再解釈したことでした。

Y2K問題、年を2桁に省いた代償

スクリーンショット 2026-07-23 23.14.03.png

この話には有名な先駆者がいます。西暦2000年問題、いわゆるY2K問題です。YはYear、2Kは2000を表します。昨今Y2Kブームが来ているみたいですが、私はY2Kはこの問題の文脈の方が聞き馴染みがあります。

初期のコンピュータでは、限られた記憶容量を節約し、当時の帳票やデータ形式にも合わせるため、西暦を下2桁だけで保存する設計が広く使われました。1998年なら98、1999年なら99です。ところが2000年は00になるため、1900年との区別がつきません。

日付を表示するだけなら00年と出て困る程度に思えますが、実際に問題になるのは計算、比較、並べ替えです。1998年から2000年までの経過年数を、2桁のまま単純に引き算すると、00 - 98 = -98になります。

本当は2年後なのに、計算上は98年前として扱われてしまいます。ローンの満期、保険契約、年齢、在庫の有効期限、予約順など、日付に依存する処理へ同じ勘違いが広がれば、表示だけでは済みません。

世界中が身構えたのはこのためです。日本でも政府、日本銀行、金融、エネルギー、通信、運輸、医療など各業界が事前にシステムを調査し、プログラム修正や切り替え試験を進めました。日本政府の2000年1月5日時点のまとめでは、報告された47件のうち27件がY2Kに起因すると判定されましたが、社会インフラを揺るがす重大な問題は発生しませんでした。官房長官も、深刻な事態を避けられたのは官民双方の対策によるものだと総括しています。

大混乱が起きなかったため、騒ぎすぎだったようにも見えます。しかし、混乱を避けられたのは、期限が来る前に世界規模で修正した結果です。カンナムスタイルのカウンターが止まらなかったのと同じで、事件が小さく終わった裏には、先回りして対応してくださった称えるべき方々がいました。

2038年問題、次は時刻そのものがあふれる

スクリーンショット 2026-07-23 23.16.10.png

Y2Kは年を2桁へ省いたことによる問題でしたね。この2038年問題では、UNIX時間の秒数そのものが32ビット符号付き整数の上限へ届きます。

先ほどのカンナムスタイルで登場した2,147,483,647が、ここでも活躍します。この値をUNIX時間として日時へ変換すると、2038年1月19日3時14分7秒(UTC)です。

from datetime import datetime, timezone

int32_max = (1 << 31) - 1
uint32_mask = (1 << 32) - 1

# 符号付き32ビット整数へ1を加えた状態を再現する
next_32_bits = (int32_max + 1) & uint32_mask
wrapped_signed_value = next_32_bits - (1 << 32)

print(datetime.fromtimestamp(int32_max, tz=timezone.utc).isoformat())
print(wrapped_signed_value)
print(datetime.fromtimestamp(wrapped_signed_value, tz=timezone.utc).isoformat())
2038-01-19T03:14:07+00:00
-2147483648
1901-12-13T20:45:52+00:00

典型的な2の補数表現(正の値のビットをすべて反転し、1を加えて対応する負数を表す方式)で値が折り返す実装なら、次の1秒は-2,147,483,648となり、日付は1901年12月13日へ飛びます。実際の挙動は実装次第で、時刻取得がエラーになる、日付比較が壊れる、処理が停止するなど、1901年を表示しないケースもあります。

現在のPOSIX.1-2024では、時刻を表すtime_tに少なくとも64ビットを使うことが求められています。新しいOSやライブラリでは対策が進んでいますが、古い32ビットABIは残っています。ABIは、OSとアプリケーションの間で使うデータ型や呼び出し規約です。

32ビットABIで動くソフトウェア、組み込み機器、ファイルやデータベースに32ビットで保存された時刻は、別途確認する必要があります。OSだけを64ビットにしても、過去に保存したデータまでは自動で広がりません。

64ビット符号付き整数へ移行すれば、正の上限は9,223,372,036,854,775,807秒です。2038年は回避できますが、今回の事件からわかるのは、数字の上限を増やすだけでは解決しない点です。秒とミリ秒、符号の有無、保存先の型まできちんと揃って、ようやく同じ時刻を共有できます。そう、結構シビアなんです。

余談

未だに狩野さんのポストの年月が1992年なのは、運営側も面白がっているからなんでしょうかね、笑。

ではまた、お会いしましょう。

参考リンク

狩野英孝さんとX

UNIX時間と2038年問題

カンナムスタイルと32ビット整数

西暦2000年問題

6
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?