Guide
うるう秒の処理
はじめに
うるう秒は、協定世界時 (UTC) を地球の自転との同期を保つために不定期に 1 秒調整するものです。これらは、ソフトウェア開発における時間処理の最も複雑な側面の 1 つを表します。
簡単な概要: UTC では、UT1 (太陽時) の 0.9 秒以内に収まるように閏秒が追加されます。 2026 年 1 月の時点で、1972 年以来 37 秒のうるう秒が追加され、UTC は国際原子時 (TAI) より 37 秒遅れています。
うるう秒とは何ですか?
定義
うるう秒は、以下を考慮して UTC に適用される 1 秒の調整です。
- 地球の自転の減速: 地球の自転は徐々に減速しています
- 不規則な回転速度: 地球の回転速度は予測不可能に変化します
- UT1-UTC 乖離: UTC を太陽時 (UT1) の ±0.9 秒以内に保つ
Leap Second Formula:
If UT1 - UTC > 0.9 seconds → Add positive leap second
If UT1 - UTC < -0.9 seconds → Add negative leap second
Result: UTC stays synchronized with Earth's rotation
うるう秒の仕組み
うるう秒が追加されると、UTC 日の最後の 1 分は 60 秒ではなく 61 秒になります。
Normal Day (no leap second):
23:59:58 UTC
23:59:59 UTC
00:00:00 UTC (next day)
Leap Second Day:
23:59:58 UTC
23:59:59 UTC
23:59:60 UTC ← Leap second!
00:00:00 UTC (next day)
注: 負の閏秒は実際には発生したことはありませんが、地球の自転が突然加速した場合には理論的には発生する可能性があります。
うるう秒の歴史
タイムライン
| 年 | イベント | UTC-TAI オフセット |
|---|---|---|
| 1972年 | 最初のうるう秒を追加 | +10秒 |
| 1972 年から 1984 年 | 12 うるう秒を追加 | +22秒 |
| 1985-1995 | 8 うるう秒を追加 | +29秒 |
| 1996 年から 2005 年 | 3 うるう秒を追加 | +32秒 |
| 2008-2016 | 3 うるう秒を追加 | +35秒 |
| 2017年 | 最終閏秒 | +36秒 |
| 2025年 | 将来のうるう秒 | +37秒 |
最近のうるう秒
All Leap Seconds (1972 - 2025):
- 1972-06-30: +1 second (UTC-TAI = +11s)
- 1972-12-31: +1 second (UTC-TAI = +12s)
- 1973-12-31: +1 second (UTC-TAI = +13s)
- 1974-12-31: +1 second (UTC-TAI = +14s)
- 1975-12-31: +1 second (UTC-TAI = +15s)
- 1976-12-31: +1 second (UTC-TAI = +16s)
- 1977-12-31: +1 second (UTC-TAI = +17s)
- 1978-12-31: +1 second (UTC-TAI = +18s)
- 1979-12-31: +1 second (UTC-TAI = +19s)
- 1981-06-30: +1 second (UTC-TAI = +20s)
- 1982-06-30: +1 second (UTC-TAI = +21s)
- 1983-06-30: +1 second (UTC-TAI = +22s)
- 1985-06-30: +1 second (UTC-TAI = +23s)
- 1987-12-31: +1 second (UTC-TAI = +24s)
- 1988-12-31: +1 second (UTC-TAI = +25s)
- 1989-12-31: +1 second (UTC-TAI = +26s)
- 1990-12-31: +1 second (UTC-TAI = +27s)
- 1992-06-30: +1 second (UTC-TAI = +28s)
- 1993-06-30: +1 second (UTC-TAI = +29s)
- 1994-06-30: +1 second (UTC-TAI = +30s)
- 1995-12-31 +1 second (UTC-TAI = +31s)
- 1997-12-31 +1 second (UTC-TAI = +32s)
- 1998-12-31 +1 second (UTC-TAI = +33s)
- 1999-12-31 +1 second (UTC-TAI = +34s)
- 2000-12-31 +1 second (UTC-TAI = +35s)
- 2005-12-31 +1 second (UTC-TAI = +36s)
- 2008-12-31: +1 second (UTC-TAI = +37s)
うるう秒の未来
国際電気通信連合(ITU)は、世界中で時刻の取り扱いを簡素化するために、2035年までにうるう秒を廃止することを検討している。
重要: うるう秒が廃止されると、UTC は太陽時から徐々に乖離していきます。これは、天文学者、ソフトウェア開発者、計時組織の間で物議を醸しているトピックです。
TAI 対 UTC
国際原子時 (TAI)
TAI は、世界中の原子時計の加重平均に基づいた時間スケールです。うるう秒は含まれないため、完全に均一な時間スケールになります。
TAI Characteristics:
- Based on: 400+ atomic clocks worldwide
- Precision: ±0.000000001 seconds (1 nanosecond)
- Leap Seconds: Never
- Usage: Scientific research, precise synchronization
Current TAI-UTC Offset: +37 seconds (as of January 2026)
TAI と UTC 間の変換
// Convert TAI timestamp to UTC timestamp
const TAI_OFFSET_SECONDS = 37; // As of 2026
function taiToUtc(taiTimestamp) {
return taiTimestamp - TAI_OFFSET_SECONDS;
}
function utcToTai(utcTimestamp) {
return utcTimestamp + TAI_OFFSET_SECONDS;
}
// Example
const taiTs = 1735689637;
const utcTs = taiToUtc(taiTs); // 1735689600
console.log('TAI Timestamp:', taiTs);
console.log('UTC Timestamp:', utcTs);
from datetime import datetime, timezone, timedelta
TAI_OFFSET_SECONDS = 37 # As of 2026
def tai_to_utc(tai_timestamp):
return tai_timestamp - TAI_OFFSET_SECONDS
def utc_to_tai(utc_timestamp):
return utc_timestamp + TAI_OFFSET_SECONDS
# Example
tai_ts = 1735689637
utc_ts = tai_to_utc(tai_ts) # 1735689600
print(f'TAI Timestamp: {tai_ts}')
print(f'UTC Timestamp: {utc_ts}')
プログラミングにおけるうるう秒の処理
JavaScript
JavaScript の Date オブジェクトは、うるう秒を直接サポートしていません。 23:59:60 のタイムスタンプを 23:59:59 として繰り返します。
// Leap second handling in JavaScript
const leapSecondDate = new Date('2016-12-31T23:59:60Z');
// JavaScript treats this as 23:59:59Z
console.log(leapSecondDate.toISOString()); // "2016-12-31T23:59:59.000Z"
// Workaround: Use a library that supports leap seconds
import { unix } from 'dayjs';
import utc from 'dayjs/plugin/utc';
import customParseFormat from 'dayjs/plugin/customParseFormat';
// Note: Day.js also doesn't support leap seconds natively
// Consider using specialized time libraries for leap second support
パイソン
Python の「datetime」モジュールでは、うるう秒のサポートが制限されています。標準ライブラリは 23:59:60 を表しません。
# Leap second handling in Python
from datetime import datetime, timezone, timedelta
# Standard datetime doesn't support leap seconds
try:
leap_second = datetime(2016, 12, 31, 23, 59, 60, tzinfo=timezone.utc)
except ValueError as e:
print(f'Error: {e}') # ValueError: second must be in 0..59
# Workaround: Use specialized libraries
# For true leap second support, consider:
# - astropy.time for scientific applications
# - specialized time handling libraries
Java
Java 8 以降の java.time パッケージは、Instant クラスでうるう秒をサポートします。
import java.time.Instant;
import java.time.temporal.ChronoUnit;
// Leap second handling in Java
Instant leapSecondInstant = Instant.parse("2016-12-31T23:59:60Z");
// Java correctly handles leap second in Instant
System.out.println("Leap Second: " + leapSecondInstant);
// Check if a timestamp contains a leap second
Instant timestamp = Instant.parse("2016-12-31T23:59:60Z");
boolean isLeapSecond = timestamp.getNano() == 0 &&
timestamp.getEpochSecond() % 60 == 59;
System.out.println("Is Leap Second: " + isLeapSecond);
行きます
Go の time パッケージにはネイティブのうるう秒サポートがありません。
package main
import (
"fmt"
"time"
)
func main() {
// Go doesn't support leap seconds natively
leapSecondStr := "2016-12-31T23:59:60Z"
_, err := time.Parse(time.RFC3339, leapSecondStr)
if err != nil {
fmt.Println("Error:", err)
// Go will reject leap second timestamps
}
}
時間のスミアリング
時間の汚れとは何ですか?
時刻スミアリング は、うるう秒の調整を瞬時に適用するのではなく、一定期間 (通常は 12 ~ 24 時間) にわたって徐々に分散する手法です。
Traditional Leap Second:
23:59:58 UTC
23:59:59 UTC
23:59:60 UTC ← Instant jump
00:00:00 UTC (next day)
Smeared Leap Second (24-hour smear):
Each second is ~1.16ms longer for 24 hours
No instant jump, smooth transition
スメアリングの実装
| システム | スミアリング方法 | 期間 |
|---|---|---|
| Google TrueTime | 線状スミア | 24時間 |
| Amazon 時刻同期サービス | 線状スミア | 24時間 |
| NTP プール | オプションのスミア | 1~24時間 |
| Linux | カーネル ステップ (スミアなし) | インスタント |
注意: 時間スミアリングは、大規模な分散システムで同期の問題を回避するために使用されます。ただし、これによって独自の問題が発生します。つまり、不鮮明な時刻は標準 UTC ではなく、他の時刻システムに確実に変換できないということです。
IETF RFC 8536 推奨事項
RFC 8536 は、ソフトウェア システムにおけるうるう秒の処理に関するガイドラインを提供します。
重要な推奨事項
- 内部時刻に TAI を使用: 精度を高めるために TAI タイムスタンプを内部に保存します
- 表示のみ UTC に変換: ユーザーに表示する場合にのみうるう秒オフセットを適用します。
- 同期に NTP を使用: NTP サーバーから正確な時刻を取得します
- うるう秒の処理を文書化: システムがうるう秒をどのように処理するかを明確に文書化します。
- 閏秒イベントのテスト: テストで閏秒遷移をシミュレートします。
ベストプラクティス
For Most Applications:
✓ Use UTC timestamps (ignore leap seconds in storage)
✓ Apply leap second offset only when needed (rare cases)
✓ Test with historical leap second dates
✓ Document your leap second policy
For High-Precision Applications:
✓ Store TAI timestamps
✓ Maintain leap second table
✓ Convert to UTC for display
✓ Use NTP for synchronization
一般的な問題と解決策
問題 1: うるう秒時のタイムジャンプ
問題: うるう秒への移行中にシステムで 1 秒のジャンプが発生します。
解決策: 時刻スミアリングを使用するか、うるう秒の認識を実装します。
// Time smearing example (simplified)
function smearedTime(timestamp, leapSecondDate) {
const diffHours = (timestamp - leapSecondDate) / (1000 * 60 * 60);
const smearDuration = 24; // 24 hours
const smearFactor = Math.min(Math.max(diffHours / smearDuration, 0), 1);
return timestamp + smearFactor * 1000; // Add up to 1 second over 24 hours
}
問題 2: データベース クエリの失敗
問題: ほとんどのデータベースでは「23:59:60」のようなタイムスタンプが無効であるため、うるう秒中にクエリが失敗します。
解決策: うるう秒を含まないタイムスタンプを保存し、うるう秒の動作を文書化します。
-- Store standard UTC timestamps (without leap second)
CREATE TABLE events (
id INT PRIMARY KEY,
event_timestamp TIMESTAMP WITHOUT TIME ZONE, -- Standard UTC
description TEXT
);
-- Handle leap second by using a range
SELECT * FROM events
WHERE event_timestamp BETWEEN '2016-12-31T23:59:59Z' AND '2017-01-01T00:00:01Z';
問題 3: うるう秒時のログエラー
問題: ログ ファイルには、うるう秒中に重複または順序が正しくないタイムスタンプが表示されます。
解決策: 高解像度のタイムスタンプと一意のシーケンス識別子を使用します。
# Logging with leap second awareness
import time
from datetime import datetime
def log_event(message):
# Use millisecond precision to handle leap seconds
timestamp = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
sequence_id = time.time_ns() # Nanosecond precision
print(f'[{timestamp}] [{sequence_id}] {message}')
シナリオ別のコード例
シナリオ 1: うるう秒オフセットを使用したタイムスタンプの変換
const LEAP_SECONDS = 37; // As of 2026
// Convert TAI timestamp to human-readable UTC
function taiToUtcString(taiTimestamp) {
const utcTimestamp = taiTimestamp - LEAP_SECONDS;
const date = new Date(utcTimestamp * 1000);
return date.toISOString();
}
// Example
const taiTs = 1735689637;
console.log(taiToUtcString(taiTs)); // "2026-01-01T00:00:00.000Z"
from datetime import datetime, timezone, timedelta
LEAP_SECONDS = 37 # As of 2026
def tai_to_utc_string(tai_timestamp):
utc_timestamp = tai_timestamp - LEAP_SECONDS
utc_time = datetime.fromtimestamp(utc_timestamp, timezone.utc)
return utc_time.isoformat()
# Example
tai_ts = 1735689637
print(tai_to_utc_string(tai_ts)) # "2026-01-01T00:00:00Z"
シナリオ 2: 日付がうるう秒かどうかを確認する
const LEAP_SECOND_DATES = [
'1972-06-30', '1972-12-31', '1973-12-31', '1974-12-31',
'1975-12-31', '1976-12-31', '1977-12-31', '1978-12-31',
'1979-12-31', '1981-06-30', '1982-06-30', '1983-06-30',
'1985-06-30', '1987-12-31', '1989-12-31', '1990-12-31',
'1992-06-30', '1993-06-30', '1994-06-30', '1995-12-31', '1997-06-30',
'1998-12-31', '1999-12-31', '2000-12-31', '2005-12-31',
'2008-12-31', '2012-06-30', '2015-06-30', '2025-12-31',
'2017-12-31', '2018-06-30', '2019-12-31', '2025-12-31'
];
function isLeapSecondDate(date) {
const dateStr = date.toISOString().split('T')[0];
return LEAP_SECOND_DATES.includes(dateStr);
}
// Example
const date = new Date('2016-12-31T23:59:59Z');
console.log(isLeapSecondDate(date)); // true
from datetime import datetime
LEAP_SECOND_DATES = [
datetime(1972, 6, 30), datetime(1972, 12, 31),
datetime(1973, 12, 31), datetime(1974, 12, 31),
datetime(1975, 12, 31), datetime(1976, 12, 31),
datetime(1977, 12, 31), datetime(1978, 12, 31),
datetime(1979, 12, 31), datetime(1981, 6, 30),
datetime(1982, 6, 30), datetime(1983, 6, 30),
datetime(1985, 6, 30), datetime(1987, 12, 31),
datetime(1989, 12, 31), datetime(1990, 12, 31),
datetime(1992, 6, 30), datetime(1993, 6, 30),
datetime(1994, 6, 30), datetime(1995, 12, 31),
datetime(1997, 12, 31), datetime(1998, 12, 31),
datetime(1999, 12, 31), datetime(2000, 12, 31),
datetime(2001, 6, 30), datetime(2002, 6, 30),
datetime(2003, 6, 30), datetime(2004, 6, 30),
datetime(2005, 12, 31), datetime(2008, 12, 31)
]
def is_leap_second_date(date):
return any(
date.year == leap_date.year and
date.month == leap_date.month and
date.day == leap_date.day
for leap_date in LEAP_SECOND_DATES
)
# Example
date = datetime(2016, 12, 31, 23, 59, 59)
print(is_leap_second_date(date)) # True
うるう秒の処理のテスト
テストケース
Test 1: Verify leap second offset
Input: TAI = 1735689637
Expected: UTC = 1735689600 (difference = 37 seconds)
Status: PASS if difference equals current UTC-TAI offset
Test 2: Handle leap second timestamp
Input: "2016-12-31T23:59:60Z"
Expected: System handles gracefully (no crash, no data corruption)
Status: PASS if no errors
Test 3: Convert leap second date range
Input: Range [2016-12-31T23:59:59Z, 2017-01-01T00:00:01Z]
Expected: All events in range, including leap second events
Status: PASS if all events returned
Test 4: Verify time smearing
Input: Timestamp near leap second
Expected: Smooth transition, no instant jump
Status: PASS if transition is smooth
ベスト プラクティスの概要
ほとんどのアプリケーションに対応
- ストレージ内の うるう秒を無視 (標準の UTC タイムスタンプを使用)
- うるう秒の取り扱いポリシーを 文書化
- 過去のうるう秒の日付を使用して テスト
- UTC を主な時間標準として使用します
高精度用途向け
- 内部計算用に TAI タイムスタンプを保存
- 変換用にうるう秒テーブルを維持
- NTP を使用して同期する
- 重要なコードパスにうるう秒認識を実装
関連ツール
- TAI 時間コンバータ - TAI、UTC、GPS 時間間の変換
- 現在のタイムスタンプ - 現在の UTC 時間を正確に取得します
- Unix Timestamp Converter - さまざまなタイムスタンプ精度を処理します
- GPS 時刻コンバータ - うるう秒対応の GPS 時刻
よくある質問
Q: うるう秒はどのくらいの頻度で発生しますか?
A: うるう秒は 1972 年以来 27 回(約 1 ~ 2 年に 1 回)発生していますが、最近は地球の自転が遅くなったために頻度が減少しています。
Q: うるう秒は永遠に続くのでしょうか?
A: ITU は 2035 年までにうるう秒を廃止することについて議論しています。そうなればうるう秒の追加は停止されますが、UTC は太陽時から徐々に乖離していきます。
Q: アプリケーションでうるう秒を処理する必要がありますか?
A: ほとんどのアプリケーションでは、標準の UTC タイムスタンプを使用しません。うるう秒を処理するのは、時間が重要なシステム、科学アプリケーション、または分散データベースを構築している場合のみです。
Q: うるう秒の間には何が起こりますか?
A: UTC では、地球の自転との同期を保つために 1 秒 (23:59:60) が追加されます。ほとんどのシステムは、23:59:59 を繰り返すか、時間のスミアリングを使用してジャンプを回避します。
Q: うるう秒の処理をテストするにはどうすればよいですか?
A: 2016-12-31T23:59:60Z などの過去のうるう秒の日付を使用してテストし、アプリケーションがクラッシュしたり、間違った結果が生成されたりしないことを確認します。