tuneoの日記: Python力を高める:「来年の今日」の求め方 5
GNU dateの以下の実行例のように日付を求める計算をPythonでやろうとしたのだが、意外と頭を抱えた。というか応用問題は現在も考え中だ。
$ date --date="1 year" "+%Yねん%mがつ%dにち %Hじ%Mふん%Sびょう"
2018ねん11がつ23にち 01じ22ふん58びょう
これを実現するならdatetimeモジュールの使用が妥当だろう。
datetimeオブジェクトには年月日時分秒に個別にアクセスする属性があるのだが、これを増減すれば再計算されてうまくいく……なんてのは安直な発想だよな。
>>> import datetime
>>> now = datetime.datetime.now()
>>> now.year
2017
>>> now.year += 1
Traceback (most recent call last):
File "", line 1, in
AttributeError: attribute 'year' of 'datetime.date' objects is not writable
やっぱり。datetimeオブジェクトの属性は書き込めなかった。
待てよ、確かdatetimeオブジェクトに足し算引き算できるtimedeltaオブジェクトってのもあったはず……って、timedeltaオブジェクトを作る際に指定できる最大の単位は「day」なのかよ!
>>> td = datetime.timedelta(year = 1)
Traceback (most recent call last):
File "", line 1, in
TypeError: 'year' is an invalid keyword argument for this function
これでは「1年後」には役に立たん。今日が3月1日以降で来年がうるう年なら、「来年の今日」は365日後ではなく2月29日を含めた366日後になるのだが、自前でうるう年判定とか書くのはイヤだし、calendarモジュールをimportするのも嫌だ。だいたい、うるう年が分かったところで、そんなんちまちま場合分けして計算するコードは書きたくないしな。
さて、頭を抱えつつdatetimeモジュールのリファレンスをつらつら眺めていたら、datetimeオブジェクトには、特定の属性(年月日時分秒etc.)を与えられた値に置き換えた新しいdatetimeオブジェクトを返すreplace()というメソッドがあることが分かった。これで勝ちが見えた。
正解はこちら。
>>> import datetime
>>> now = datetime.datetime.now()
>>> this_day_next_year = now.replace(year = now.year + 1)
ただし、これが通用するのは「n年後/n年前」の場合のみ。しかもdatetimeオブジェクトの仕様上、紀元前のdatetimeオブジェクトは作れないので(この日記の執筆時なら)n ≧ -2017でなければいけない。
また、replace()メソッドのキーワード引数をyearではなく「month/day/hour/minute/second/microsecond」にした場合はいろいろ面倒くさいことになる。以下の例では「3か月後」を求めようとして例外が発生している。
>>> now
datetime.datetime(2017, 11, 23, 1, 27, 16, 960429)
>>> now.replace(month = now.month + 3)
Traceback (most recent call last):
File "", line 1, in
ValueError: month must be in 1..12
つまり「2017年14月23日~」という日付は受け入れてくれないのだ(当然といえば当然だが)。
この辺の制約がない重宝な日付計算モジュールってどこかにないのかな。
月数だけが例外? (スコア:2)
年についてはすでに解決しているし、時分秒とかは UNIX TIME (timestamp) 経由で解決できると思うので、月だけ場合分けすれば、モジュールがなくてもいけるのでは。
泥臭いと言われればそれまでですが。
>>> import datetime
>>> now = datetime.datetime.now()
>>> now
datetime.datetime(2017, 11, 23, 11, 11, 44, 683294)
>>> new_year = now.year
>>> new_month = now.month + 3
>>> if new_month > 12:
... new_year = new_year + (new_month // 12)
... new_month = new_month % 12
...
>>> now.replace(month = new_month, year = new_year)
datetime.datetime(2018, 2, 23, 11, 11, 44, 683294)
Re:月数だけが例外? (スコア:1)
年はほぼ制限がないし、日時分秒はdatetime.timedeltaクラスのオブジェクトを作成してdatetime.datetimeに加減算すればいいので、月だけがピンポイントで抜けてるんですよねぇ……。
Re:月数だけが例外? (スコア:1)
日時分秒が計算済みで確定していてなおかつ一年の通日がわかっているのだから
最後に月を計算するれいじー・えう”ぁりゅえーしょん(違)モジュールを一個
開発してPPANモジュールに公開するという正攻法でいいんじゃないでしょか?
gnu coreutils/shellutils があればそれだけで (スコア:1)
date コマンドを呼び出して終わりという話にしてはいけないのだろうなあ。
Perlの4から5 の端境期時代に汚い書き方であればあまり悩まなかった
という話もあった気がするが、Python 流はまた一味違うということで
でもPythonの解法の結果は綺麗なのではなかろうか?
暦法に反した異常な日付強要さえ願わないでおけば…
// 汎用モジュールというよりも特異なオレサマ変態暦法モジュールが必要?
Re:gnu coreutils/shellutils があればそれだけで (スコア:2)
python three months later
で検索するとよいです。