Pythonでのタイムゾーン

Python でタイムゾーンを考慮した時刻を扱うのはちょっと面倒。
datetime ライブラリの日付や時刻のオブジェクトには “naive” と “aware” の2種類があって、”naive” なオブジェクトはタイムゾーンに関する情報を持っていない。

>>> from datetime import datetime
>>> now = datetime.now()
>>> now
datetime.datetime(2015, 11, 23, 11, 9, 51, 831037)
>>> utc_now = datetime.utcnow()
>>> utc_now
datetime.datetime(2015, 11, 23, 2, 10, 6, 927019)

だからタイムゾーンを考慮した日付・時刻を扱おうとすると “aware” なオブジェクトにしないといけない。で、タイムゾーンを表すには datetime.tzinfo クラスを使うんだけど、これは抽象クラスであって実際に使うにはタイムゾーンそれぞれのサブクラスを作らなきゃいけない。
リファレンスマニュアルの例を参考に書いてみたのがこれ。

from datetime import datetime, timedelta, tzinfo

class UTC(tzinfo):
    def utcoffset(self, dt):
        return timedelta(0)

    def tzname(self, dt):
        return "UTC"

    def dst(self, dt):
        return timedelta(0)

class JST(tzinfo):
    def utcoffset(self, dt):
        return timedelta(hours=9)

    def tzname(self, dt):
        return "JST"

    def dst(self, dt):
        return timedelta(0)


if __name__ == '__main__':
    now = datetime.now(JST())
    print now
    print now.utcoffset()

    utc_now = now.astimezone(UTC())
    print utc_now
    print utc_now.utcoffset()

datetime.tzinfo のサブクラスとして UTC と JST を定義している。これを使うとタイムゾーンを考慮した時刻を扱うことができる。こんな感じ:

>>> import test_tzinfo
>>> jst = test_tzinfo.JST()
>>> now = datetime.now(jst)
>>> now
datetime.datetime(2015, 11, 23, 11, 21, 51, 47529, tzinfo=<test_tzinfo.JST object at 0x7f9fa1686b50>)
>>> utc = test_tzinfo.UTC()
>>> utc_now = now.astimezone(utc)
>>> utc_now
datetime.datetime(2015, 11, 23, 2, 21, 51, 47529, tzinfo=<test_tzinfo.UTC object at 0x7f9fa1686c90>)

タイムゾーンを簡単に扱えるようにするライブラリもあるようだけど、少なくとも標準の機能だけだとちょっと面倒、という話でした。

Flask-SQLAlchemyのモデルでのリレーションシップ

一昨日のアプリを作る段階で、Flask-SQLAlchemy のモデルでのリレーションシップの作り方を調べたのでメモ。↓ここに書いてある。

 cf. One-to-Many Relationships – Flask SQLAlchemy

簡単に言うと、主キーを持つテーブルにリレーションシップを作成して、外部キーを持つテーブルに db.ForeignKey を設定してやる。
次の例では Category と Format が Book に対して1対多リレーションになっている。つまり、Category と Format が主キーを持っていて、Book が外部キーを持っている。

class Book(db.Model):
    __tablename__ = 'books'
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String)
    volume = db.Column(db.String)
    series = db.Column(db.String)
    series_volume = db.Column(db.String)
    author = db.Column(db.String)
    translator = db.Column(db.String)
    publisher = db.Column(db.String)
    category_id = db.Column(db.Integer, db.ForeignKey('categories.id'))
    format_id = db.Column(db.Integer, db.ForeignKey('formats.id'))
    isbn = db.Column(db.String)
    published_on = db.Column(db.String)
    original_title = db.Column(db.String)
    note = db.Column(db.String)
    keyword = db.Column(db.String)
    disk = db.Column(db.String)
    disposed = db.Column(db.Boolean, default=False)

class Category(db.Model):
    __tablename__ = 'categories'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String)
    books = db.relationship('Book', backref='category', lazy='dynamic')

class Format(db.Model):
    __tablename__ = 'formats'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String)
    books = db.relationship('Book', backref='format', lazy='dynamic')

はじめ、ForeignKey を db.ForeignKey(‘category.id’) とやったらダメだった。モデル名 (category) じゃなくてテーブル名 (categories) でないといけないのね。