高校物理160203 views
高校国語788336 views
高校化学2924392 views
雑学1473612 views
高校日本史190571 views
小学社会310485 views
英語613639 views
小学算数1200427 views
りんご209490 views
高校生物551766 views
Help
Tools
NewsSpreadsheetCalendarSlidesTier ListPen ToolIllustrationCrayonPixel ArtASCII ArtPerspectiveEndless StairsGraphMind MapER DiagramFamily TreeMemeCurved TextImage EditorMosaicRetro FilterPencil SketchOCR/HighlighterMakeup EditorFaviconVideo TrimmerScrolling VideoVideo TitleColor PickerColor ExtractorBonfireFireworksWater RippleWater SplashBreaking GlassWood GrainMarble TextureCSS ButtonIcon MakerBar ChartGrouped Bar ChartStacked Bar ChartPie ChartLine ChartArea ChartStacked Area ChartScatter Plot3D Bar Chart3D Pie ChartBar Chart RaceBubble ChartPopulation PyramidPictogramEarningsCandlestick ChartInvestment RiskMortgage SimulatorCalculatorMatrix CalculatorFunction GraphPolynomial ExpansionVenn DiagramField VisualizerRubik's Cube Group TheoryTraveling SalesmanVoronoi and DelaunayFractalColumn ArithmeticDraw Math FiguresArithmetic AnimationArithmetic Word ProblemsCounting with Tree DiagramsCube NetsCross SectionsMotion PathMechanicsWavesLight and LensesThermodynamicsHow Semiconductors WorkMolecular StructuresAtomic OrbitalsElectrochemical CellsChemical EquilibriumCrystal LatticesBuffer pHComplex IonsDNA Double HelixCell DivisionMembrane ChannelsNerve ImpulseRock ClassificationWeatherConstellationsSolar and Lunar Eclipses3D ModelingFloor PlanSeismic StructuresIntersection TurnMaglevCooking AnimationOrigamiLive Viewer CountGeoJSON MapRailway MapPopulation MapCrime MapLand Price MapSchool MapShrine and Castle MapHouse of Representatives MapWord MapSolitaireReversiHakoiri MusumeChessHamburgerRippleSlide Puzzle MakerNeon PinballNovel MakerJapanese Typing PracticePiano Score EditorMusic Theory

English

Python で閏秒やうるう年を正しく扱う

日付・時刻を扱うプログラムでは、閏秒やうるう年といった例外的なケースを正しく処理する必要があります。Python の datetime がこれらをどう扱うか、そして実務でどう対処すべきかを解説します。

うるう年の判定

うるう年のルールは「4 で割り切れる年はうるう年、ただし 100 で割り切れる年は平年、ただし 400 で割り切れる年はうるう年」です。Python の calendar モジュールにはこの判定関数があります。

import calendar

# うるう年の判定
print(calendar.isleap(2024))  # True(4で割り切れる)
print(calendar.isleap(2100))  # False(100で割り切れるが400で割り切れない)
print(calendar.isleap(2000))  # True(400で割り切れる)

datetime でも 2 月 29 日を指定すれば、うるう年かどうかを間接的に確認できます。

from datetime import date

try:
    d = date(2024, 2, 29)
    print(f"{d.year}年はうるう年")
except ValueError:
    print(f"うるう年ではない")

うるう年をまたぐ計算

うるう年で問題になるのは「1 年後」の計算です。2024 年 2 月 29 日の 1 年後は何日でしょうか。

from datetime import date, timedelta

# 2月29日に365日を足す
feb29 = date(2024, 2, 29)
one_year_later = feb29 + timedelta(days=365)
print(one_year_later)
# 2025-02-28(365日後)

timedelta(days=365) は単純に 365 日を足すので、2 月 28 日になります。では timedelta(days=366) なら?

one_year_later = feb29 + timedelta(days=366)
print(one_year_later)
# 2025-03-01

「1 年後」の定義によって結果が変わります。業務ロジックで「1 年後」が必要な場合、dateutil ライブラリの relativedelta を使うと直感的です。

from datetime import date
from dateutil.relativedelta import relativedelta

feb29 = date(2024, 2, 29)
one_year_later = feb29 + relativedelta(years=1)
print(one_year_later)
# 2025-02-28(存在する最も近い日に調整)
timedelta

日数単位の加減算。「365 日後」と「1 年後」は別の意味になる。

relativedelta

年・月単位の加減算。存在しない日付は自動調整される。

月末日の計算とうるう年

「月末日」を扱う処理では、うるう年が影響します。

import calendar
from datetime import date

def last_day_of_month(year: int, month: int) -> date:
    """月の最終日を取得"""
    last_day = calendar.monthrange(year, month)[1]
    return date(year, month, last_day)

# うるう年の2月
print(last_day_of_month(2024, 2))  # 2024-02-29
print(last_day_of_month(2025, 2))  # 2025-02-28

月末起算で「翌月の同日」を計算する場合、3 月 31 日の翌月は 4 月 30 日になるべきか、エラーにすべきか、業務要件次第です。

from datetime import date
from dateutil.relativedelta import relativedelta

# 3月31日の1ヶ月後
mar31 = date(2024, 3, 31)
next_month = mar31 + relativedelta(months=1)
print(next_month)
# 2024-04-30(4月31日は存在しないので調整)

閏秒とは

閏秒は地球の自転速度のゆらぎを補正するため、UTC に 1 秒を挿入(または削除)する仕組みです。最後に閏秒が挿入されたのは 2016 年 12 月 31 日で、23:59:60 という通常存在しない時刻が発生しました。

閏秒の歴史

1972 年から 2016 年までに 27 回挿入された。2035 年までに廃止される予定(国際度量衡総会の決議)。

UTC と TAI

TAI(国際原子時)は閏秒を含まない。UTC = TAI - 37 秒(2024 年現在)。

Python と閏秒

結論から言うと、Python の datetime は閏秒を扱えません。

from datetime import datetime

# 閏秒の時刻を作ろうとする
try:
    dt = datetime(2016, 12, 31, 23, 59, 60)
except ValueError as e:
    print(e)
# second must be in 0..59

これは datetime が POSIX 時間に基づいているためです。POSIX 時間では 1 日は常に 86400 秒と定義されており、閏秒は存在しません。

from datetime import datetime, timezone

# Unix タイムスタンプから datetime
ts = 1483228800  # 2017-01-01 00:00:00 UTC
dt = datetime.fromtimestamp(ts, tz=timezone.utc)
print(dt)
# 2017-01-01 00:00:00+00:00

Unix タイムスタンプ 1483228800 は、閏秒を含めれば 2016-12-31 23:59:60 を指すはずですが、POSIX の定義では 2017-01-01 00:00:00 になります。この 1 秒のズレは POSIX 時間の仕様上避けられません。

閏秒が問題になるケース

多くのアプリケーションでは閏秒を無視しても問題ありません。しかし、以下のケースでは注意が必要です。

高精度な時刻同期が必要なシステム

長時間の経過秒数を正確に計算する処理

異なる時刻基準のシステム間連携

実務での対処法

閏秒を正確に扱う必要がある場合、以下のアプローチがあります。

# TAI(国際原子時)を扱うライブラリの例
# pip install astropy

from astropy.time import Time

# UTC 時刻を TAI に変換
t = Time('2016-12-31 23:59:59', scale='utc')
print(t.tai)
# 2017-01-01 00:00:35.000

# TAI は閏秒を含まないので一貫した時刻計算が可能

astropy は天文学用のライブラリですが、高精度な時刻計算が必要な場合に使えます。

# 経過時間の計測には time.perf_counter()
import time

start = time.perf_counter()
# ... 処理 ...
elapsed = time.perf_counter() - start
print(f"経過時間: {elapsed}秒")

time.perf_counter() はシステムの高精度タイマーを使い、システム時刻の調整(NTP 同期や閏秒)の影響を受けません。経過時間の計測にはこちらを使うべきです。

2038 年問題

Unix タイムスタンプは多くのシステムで 32 ビット符号付き整数として格納されています。この場合、2038 年 1 月 19 日 3:14:07 UTC を超えるとオーバーフローします。

from datetime import datetime, timezone

# 32ビット符号付き整数の最大値
max_32bit = 2**31 - 1
dt = datetime.fromtimestamp(max_32bit, tz=timezone.utc)
print(dt)
# 2038-01-19 03:14:07+00:00

# その1秒後は32ビットでは表現できない
# (オーバーフローして1901年になる環境もある)

Python 自体は任意精度整数を使うので問題ありませんが、OS やデータベースとやり取りする際に注意が必要です。

from datetime import datetime, timezone

# Python は問題なく2038年以降を扱える
future = datetime(2050, 1, 1, tzinfo=timezone.utc)
print(future.timestamp())
# 2524608000.0

# 64ビットシステムでは問題ない
print(future)
# 2050-01-01 00:00:00+00:00
datetime モジュール

うるう年は正しく扱えるが、閏秒は扱えない。多くのアプリケーションではこれで十分。

高精度が必要な場合

astropy や専用ライブラリを検討。経過時間は time.perf_counter() を使う。

実務では、閏秒の厳密な扱いが本当に必要か要件を確認することが大切です。ほとんどの場合、datetime の「閏秒を無視する」動作で問題ありません。逆に、金融取引の時刻記録や科学計測など、1 秒未満の精度が求められる場面では、専用のライブラリや TAI 時刻の使用を検討してください。