1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【python】毒島、閏秒はdatetime.datetimeで変換できないってよ!

Posted at

背景

先日、「閏秒(うるう秒:以下、すべて漢字表記します)」のレコードを持つデータを扱う機会があり、その際の対処について調べた結果をまとめておきます。

タイトルは、映画『桐島、部活やめるってよ』のパロディです  Pythonなので大蛇⇒ニシキヘビ⇒毒⇒毒島...すみません...

 
先人たちの知恵をお借りするなどして解決できたことを、この場をお借りして感謝するとともに、大変恐縮ですが自分のメモとしても、こちらへまとめておきます。

環境

  • Windows 10 Pro
  • Python 3.9.0, 3.8.5
  • Django 3.1.4
  • PostgreSQL 13.1
  • Nginx 1.95.1
  • Gunicorn
  • Putty 0.74

0.閏秒とは

Wikipedia - 閏秒 より引用:

閏秒は、現行の協定世界時 において、世界時のUT1との差を調整するために追加もしくは削除される秒である 。この現行方式のUTCは1972年に始まった。2019年までに実施された計27回の閏秒(★注1)は、いずれも1秒追加による調整であった。 直近の閏秒の挿入は、2017年1月1日午前9時直前に行われた。

(★注1) 末尾に実施日の一覧も掲載しています。

1.現象

    nofrag, frag = t.split('.')
    nofrag_dt = datetime.datetime.strptime(nofrag, "%Y-%m-%dT%H:%M:%S")
    dt = nofrag_dt.replace(microsecond=int(frag))
ValueError: second must be in 0..59

『値エラー:秒は0~59であるべき』が発生。

当然じゃん……ん!?で、フリーズ……恐る恐るデータを見てみると…
『なんということでしょう~ データを開けるとそこには、2016-12-31 23:59:60.202915 というレコードが隠されていました~』(大改造!!劇的ビフォーアフター風に)
という結果です。

その文字列を解析してdatetimeオブジェクトに変換するために、上記のコードを使用したところで値エラーに...

2.原因

Python公式ドキュメント - time --- 時刻データへのアクセスと変換 -- time.strftime では、うるう秒をサポートしていると記載あり
⇒ ”値60は閏秒を表すタイムスタンプで有効で、値61は歴史的な理由からサポートされています” との注釈入り

上述の通り、Python のドキュメントでは、%S は [0, 61] を受け入れるので、これは問題ではないと思いますね。
しかし、上記のタイムスタンプでこのエラーが発生します。

なんと、秒数が59を超える場合、ここでの問題は .strftime での扱いではなく、datetime.datetime へ変換できないことだったようです。

%S のドキュメントには、次のように記載あり:

time モジュールとは異なり、datetime モジュールは閏秒をサポートしていません。

上述の時間文字列 "2016-12-31T23:59:60.202915" は、時刻が UTC(協定世界時)における(現時点では最後の(直近の)閏秒)であることを意味します。

3.対処

以下の変換論理を用いて、datetime.datetime で扱えるようにします。参考元コードは某所よりお借りしてきました。

【注意】:現地時間での変換では失敗する可能性があります。(例:DST(夏時間)への遷移中)

import time
from calendar import timegm
from datetime import datetime, timedelta

time_string = '2016-12-31T23:59:60.202915'
time_string, dot, us = time_string.partition('.')
utc_time_tuple = time.strptime(time_string, "%Y-%m-%dT%H:%M:%S")
dt = datetime(1970, 1, 1) + timedelta(seconds=timegm(utc_time_tuple))
if dot:
    dt = dt.replace(microsecond=datetime.strptime(us, '%f').microsecond)
print(dt)

4.結果

2017-01-01 00:00:00.202915

閏秒の表記が、2017-01-01 00:00:00.202915 へ変換されました。

⇒ 上記例 2016-12-31 23:59:60.202915 は、JST(日本標準時)における 2017-01-01 08:59:60.202915 に相当します。※JST=UTC+9時間


参考

 

閏秒の実施:

(編集後記)

データ分析・解析の前処理では、例外レコードを扱う機会も多いかと思いますが、こういった歴史的にも特殊な事柄に関連する事象を、つい忘れがちです。
(00~59に当てはまらないため)例外だからエラーデータとして弾いてしまおう…とすると、正しい分析・解析に至らない可能性もあります。
何が原因なのか、このレコードが発生した背景・理由や、レコードの必要性や妥当性を、今一度考えるきっかけにしたいと思います。

1
2
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
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?