Guide

うるう秒の処理

はじめに

うるう秒は、協定世界時 (UTC) を地球の自転との同期を保つために不定期に 1 秒調整するものです。これらは、ソフトウェア開発における時間処理の最も複雑な側面の 1 つを表します。

簡単な概要: UTC では、UT1 (太陽時) の 0.9 秒以内に収まるように閏秒が追加されます。 2026 年 1 月の時点で、1972 年以来 37 秒のうるう秒が追加され、UTC は国際原子時 (TAI) より 37 秒遅れています。

うるう秒とは何ですか?

定義

うるう秒は、以下を考慮して UTC に適用される 1 秒の調整です。

  1. 地球の自転の減速: 地球の自転は徐々に減速しています
  2. 不規則な回転速度: 地球の回転速度は予測不可能に変化します
  3. 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-19958 うるう秒を追加+29秒
1996 年から 2005 年3 うるう秒を追加+32秒
2008-20163 うるう秒を追加+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 は、ソフトウェア システムにおけるうるう秒の処理に関するガイドラインを提供します。

重要な推奨事項

  1. 内部時刻に TAI を使用: 精度を高めるために TAI タイムスタンプを内部に保存します
  2. 表示のみ UTC に変換: ユーザーに表示する場合にのみうるう秒オフセットを適用します。
  3. 同期に NTP を使用: NTP サーバーから正確な時刻を取得します
  4. うるう秒の処理を文書化: システムがうるう秒をどのように処理するかを明確に文書化します。
  5. 閏秒イベントのテスト: テストで閏秒遷移をシミュレートします。

ベストプラクティス

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

ベスト プラクティスの概要

ほとんどのアプリケーションに対応

  1. ストレージ内の うるう秒を無視 (標準の UTC タイムスタンプを使用)
  2. うるう秒の取り扱いポリシーを 文書化
  3. 過去のうるう秒の日付を使用して テスト
  4. UTC を主な時間標準として使用します

高精度用途向け

  1. 内部計算用に TAI タイムスタンプを保存
  2. 変換用にうるう秒テーブルを維持
  3. NTP を使用して同期する
  4. 重要なコードパスにうるう秒認識を実装

関連ツール

よくある質問

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 などの過去のうるう秒の日付を使用してテストし、アプリケーションがクラッシュしたり、間違った結果が生成されたりしないことを確認します。