パスワードを忘れた? アカウント作成
13462280 journal
日記

tuneoの日記: Python力を高める:「来年の今日」の求め方 5

日記 by tuneo

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日~」という日付は受け入れてくれないのだ(当然といえば当然だが)。

この辺の制約がない重宝な日付計算モジュールってどこかにないのかな。

この議論は、tuneo (2938)によって テキとトモのテキ禁止として作成されたが、今となっては 新たにコメントを付けることはできません。
  • by ogino (1668) on 2017年11月23日 11時13分 (#3317361) 日記

    年についてはすでに解決しているし、時分秒とかは 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)

  • date コマンドを呼び出して終わりという話にしてはいけないのだろうなあ。
    Perlの4から5 の端境期時代に汚い書き方であればあまり悩まなかった
    という話もあった気がするが、Python 流はまた一味違うということで
    でもPythonの解法の結果は綺麗なのではなかろうか?
    暦法に反した異常な日付強要さえ願わないでおけば…

    // 汎用モジュールというよりも特異なオレサマ変態暦法モジュールが必要?

typodupeerror

アレゲは一日にしてならず -- アレゲ見習い

読み込み中...