Pythonのタイムゾーン変換(UTC->JST)ではまった


Python 2.5

Twitterの時刻文字列「Mon Aug 24 12:38:04 +0000 2009」をUTCからJSTに変換しようとしました.

2011/3/30追記:コメント欄で教えて頂いたdateutilモジュールを使ってもっと簡単に変換する方法を書きました

datetimeクラスのstrptimeメソッドで時刻文字列をdatetimeオブジェクトに変換し,更にastimezoneメソッドでタイムゾーンを変換すればうまくいくとの情報を得て次のように組んでみました.

Python2.5

import datetime
class JST(datetime.tzinfo):
def utcoffset(self,dt):
return datetime.timedelta(hours=9)
def dst(self,dt):
return datetime.timedelta(0)
def tzname(self,dt):
return "JST"
def _main():
dt = datetime.datetime.strptime("Mon Aug 24 12:38:04 +0000 2009", "%a %b %d %H:%M:%S +0000 %Y")
dt = dt.astimezone(JST())
if __name__ == '__main__':
_main()

すると,次のエラーメッセージが…….

ValueError: astimezone() cannot be applied to a naive datetime

naive(単純?)なdatetimeには適用できないらしいです.
しかし,naiveでないdatetimeが何かわかりませんでした.

エラーメッセージで検索してみると次のページが
http://groups.google.com/group/google-appengine/browse_thread/thread/17add9b3070aad7e/b995fe7e1f16cd4e

A datetime object is naive when it has no tzinfo object, so try
greeting.date.replace(tzinfo=pytz.utc).astimezone(taipei_tz).

tzinfo object(タイムゾーンオブジェクト)がセットされていないdatetime objectがnaiveなdatetimeオブジェクトらしいです.
なので,とりあえずUTCのタイムゾーンオブジェクトをセットした上で変換したらうまくいくよと.

これを踏まえてdatetimeクラスのastimezoneメソッドのリファレンスを読み直してみます.
http://www.python.jp/doc/nightly/lib/datetime-datetime.htm

astimezone(tz)

datetime オブジェクトを返します。返されるオブジェクトは 新たな tzinfo メンバ tz を持ちます。tz は日付および時刻を調整して、オブジェクトが self と同じ UTC 時刻を持つが、tz におけるローカルな時刻を表すようにします。

tz は tzinfo のサブクラスのインスタンスでなければ ならず、インスタンスの utcoffset() および dst() メソッドは None を返してはなりません。self は aware でなくてはなりません (self.tzinfo が None であってはならず、かつ self.utcoffset() は None を返してはなりません)。

わかりにくい表記なので一文ずつ見てみました.

datetime オブジェクトを返します。返されるオブジェクトは 新たな tzinfo メンバ tz を持ちます。

まず,「tz」は「astimezone」メソッドの引数.
返されるオブジェクトがtzを持つというのは,つまり渡したtzをdatetimeオブジェクトにセットしますよと.

tz は日付および時刻を調整して、オブジェクトが self と同じ UTC 時刻を持つが、tz におけるローカルな時刻を表すようにします。

ここでselfという言葉が出てきていますが,Pythonの暗黙のルールでは「self」は「this」のことなのでこれは対象のdatetimeオブジェクトのことですね.

tz は tzinfo のサブクラスのインスタンスでなければ ならず、インスタンスの utcoffset() および dst() メソッドは None を返してはなりません。

これはtzinfoサブクラスはちゃんとutcoffsetメソッドととdstメソッドを実装しておいてねって感じかな.

self は aware でなくてはなりません (self.tzinfo が None であってはならず、かつ self.utcoffset() は None を返してはなりません)。

……このselfはdatetimeオブジェクトのことだから,対象のdatetimeオブジェクトはメンバ変数tzinfoがNoneではなくutcoffsetメソッドが有効じゃないとダメですよと.
utcoffsetメソッドは恐らくtz.utcoffsetを呼び出していて,だからtzinfoがセットされていないと動かない.
ので,さっきのではうまくいかなかったと.
naiveでない=awareである,つまりnaiveでないというのはtzinfoがセットされているってことみたいですね.

ぱっと見でこれがわからなかったのは私の読解力の問題でしょう.
とりあえずやり方はわかったのでUTCのオブジェクトをセットして……と思ったら.

datetime モジュールでは具体的な tsinfo クラスを 提供していないので注意してください。必要な詳細仕様を備えた タイムゾーン機能を提供するのはアプリケーションの責任です。 世界各国における時刻の修正に関する法則は合理的というよりも政治的な ものであり、全てのアプリケーションに適した標準というものが 存在しないのです。

ええー!?今基準となっているUTCのクラスも無いのかなあ…….
というわけで,正解はこんな感じぽいです.

import datetime
class UTC(datetime.tzinfo):
def utcoffset(self, dt):
return datetime.timedelta(0)
def tzname(self, dt):
return "UTC"
def dst(self, dt):
return datetime.timedelta(0)
class JST(datetime.tzinfo):
def utcoffset(self,dt):
return datetime.timedelta(hours=9)
def dst(self,dt):
return datetime.timedelta(0)
def tzname(self,dt):
return "JST"
def _main():
dt = datetime.datetime.strptime("Mon Aug 24 12:38:04 +0000 2009", "%a %b %d %H:%M:%S +0000 %Y")
dt = dt.replace(tzinfo=UTC()).astimezone(JST())
if __name__ == '__main__':
_main()

うーん…….
ちょっと時刻を変換したかっただけなのにクラスを二つも作らないといけないのは,あんまり嬉しくない感じがします.
とりあえずこれで動きました.

  1. こんばんは。私もTwitterのAPIで得られる日付時間をPythonでJSTに変換する方法を探していてここにたどり着きました。簡単な方法はないんですね。参考に、というかまねさせていただこうと思います。

  2. 確かに面倒ですので、datetime を直接使うのではなく、dateutilモジュール http://niemeyer.net/python-dateutil を使うことが多いみたいです。tzinfo以外にも色々な機能があって便利ですし。

  3. ありがとうございます。python-dateutilに関して追記しました。

Leave a Comment


NOTE - You can use these HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>