Pythonでの日付処理まとめ(チートシート)

この記事は[全部俺 Advent Calendar 2018](https://adventar.org/calendars/3702)の7日目の記事です。
Pythonの日付関連の処理についてまとめます。

# Pythonの日付処理チートシート
## パース編(文字列 → 日付型)
### フォーマットが明確なとき
```python
from datetime import datetime
datetime.strptime("20181207", "%Y%m%d")                                       # => datetime.datetime(2018, 12, 7, 0, 0)
datetime.strptime("20181207123456", "%Y%m%d%H%M%S")                           # => datetime.datetime(2018, 12, 7, 12, 34, 56)
datetime.strptime("2018/12/7 12:34:56.123", "%Y/%m/%d %H:%M:%S.%f")           # => datetime.datetime(2018, 12, 7, 12, 34, 56, 123000)
datetime.strptime("2018-12-07T12:34:56.123456Z", "%Y-%m-%dT%H:%M:%S.%f%z")    # => datetime.datetime(2018, 12, 7, 12, 34, 56, 123456, tzinfo=datetime.timezone.utc)
datetime.strptime("Fri, 7 Dec 2018 12:34:56 GMT", "%a, %d %b %Y %H:%M:%S %Z") # => datetime.datetime(2018, 12, 7, 12, 34, 56)
```
参考：日付フォーマットの詳細については、[こちら](https://docs.python.jp/3/library/datetime.html#strftime-strptime-behavior)を参照してください。


### 雑にパースしたいとき/フォーマットが明確でないとき
```python
from dateutil import parser
parser.parse("20181207")                       # => datetime.datetime(2018, 12, 7, 0, 0)
parser.parse("20181207123456" )                # => datetime.datetime(2018, 12, 7, 12, 34, 56)
parser.parse("2018/12/7 12:34:56.123")         # => datetime.datetime(2018, 12, 7, 12, 34, 56, 123000)
parser.parse("2018-12-07T12:34:56.123456Z")    # => datetime.datetime(2018, 12, 7, 12, 34, 56, 123456, tzinfo=tzutc())
parser.parse("Fri, 7 Dec 2018 12:34:56 GMT")   # => datetime.datetime(2018, 12, 7, 12, 34, 56, tzinfo=tzutc())
```

### そもそも数字ですらない文字列("yesterday"や"明日"など)をパースしたいとき
- 前提
    - 実行日が日本時間で`2018/12/07`
    - `pip install maya`して[maya](https://github.com/kennethreitz/maya)ライブラリが使用できる状態であること

```python
import maya
maya.when("yesterday").datetime()              # => datetime.datetime(2018, 12, 5, 15, 42, 10, 470045, tzinfo=<UTC>)
maya.when("yesterday").local_datetime()        # => datetime.datetime(2018, 12, 6, 0, 41, 6, 489476, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>)
maya.when("last month").local_datetime()       # => datetime.datetime(2018, 11, 7, 0, 43, 1, 814101, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>)
maya.when("明日").local_datetime()              # => datetime.datetime(2018, 12, 8, 0, 48, 42, 83341, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>)
maya.when("завтра").local_datetime()           # => datetime.datetime(2018, 12, 8, 0, 50, 58, 5660, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>)
maya.when("Bukas").local_datetime()            # => datetime.datetime(2018, 12, 8, 0, 53, 4, 874763, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>)
```

※"завтра"はロシア語。"Bukas"はタガログ語。どちらも「明日」という意味らしいです。

## パース編(日付型 → 文字列型)
```python
from datetime import datetime, timezone
datetime(2018, 12, 7).strftime("%Y%m%d")                                                          # => '20181207'
datetime(2018, 12, 7, 12, 34, 56).strftime("%Y%m%d%H%M%S")                                        # => '20181207123456'
datetime(2018, 12, 7, 12, 34, 56, 123000).strftime("%Y/%m/%d %H:%M:%S.%f")                        #=> '2018/12/07 12:34:56.123000'
datetime(2018, 12, 7, 12, 34, 56, 123456, tzinfo=timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f%z") #=> '2018-12-07T12:34:56.123456+0000'
datetime(2018, 12, 7, 12, 34, 56, tzinfo=timezone.utc).strftime("%a, %d %b %Y %H:%M:%S %Z")       #=> 'Fri, 07 Dec 2018 12:34:56 UTC'
```

## パース編(日付型同士の変換)
### date, datetime, pandasの相互変換
```python
from datetime import date, time, datetime
# datetime → date
datetime(2018, 12, 7, 12, 34, 56).date()                   # => datetime.date(2018, 12, 7)
# date → datetime
datetime.combine(date(2018, 12, 7), time())                # => datetime.datetime(2018, 12, 7, 0, 0)

# pandas._libs.tslibs.timestamps.Timestam → date
pd_timestamp = pd.date_range("2018/12/07", "2018/12/07")[0]
pd_timestamp.date()                                        # => datetime.date(2018, 12, 7)
# pandas._libs.tslibs.timestamps.Timestam → datetime
datetime.combine(pd_timestamp.date(), pd_timestamp.time()) # => datetime.datetime(2018, 12, 7, 0, 0)
```

## タイムゾーンの処理
```python
from datetime import datetime, timezone, timedelta
jst = timezone(timedelta(hours=9), 'JST')

# ロケールの確認
import locale
locale.getlocale()                                         # => ('ja_JP', 'UTF-8')

# ローカルでの現在時刻取得(デフォルトロケール：'ja_JP'での例)
datetime.now()                                             # => datetime.datetime(2018, 12, 8, 11, 7, 9, 203409)
datetime.utcnow()                                          # => datetime.datetime(2018, 12, 8, 2, 7, 10, 216128)

# タイムゾーンの設定
datetime(2018, 12, 8, 12, 34, 56).astimezone(timezone.utc) # => datetime.datetime(2018, 12, 8, 3, 34, 56, tzinfo=datetime.timezone.utc)
datetime(2018, 12, 8, 12, 34, 56).astimezone(jst)          # => datetime.datetime(2018, 12, 8, 12, 34, 56, tzinfo=datetime.timezone(datetime.timedelta(0, 32400), 'JST'))

# タイムゾーンの変換
## JST → UTC
datetime.now().astimezone(jst)                             # => datetime.datetime(2018, 12, 8, 2, 8, 17, 143647, tzinfo=datetime.timezone.utc)
## UTC → JST
datetime.utcnow().astimezone(jst)                          # => datetime.datetime(2018, 12, 8, 2, 8, 56, 547187, tzinfo=datetime.timezone(datetime.timedelta(0, 32400), 'JST'))

# mayaを使用する場合
## UTC時間の取得
maya.now().datetime()                                      # => datetime.datetime(2018, 12, 8, 2, 27, 13, 480987, tzinfo=<UTC>)
## ローカルタイムゾーンでの時刻取得
maya.now().local_datetime()                                # => datetime.datetime(2018, 12, 8, 11, 27, 15, 369658, tzinfo=<DstTzInfo 'Asia/Tokyo' JST+9:00:00 STD>)
```

## 演算編
### 日付 + 期間 or 日付 - 期間
```python
from datetime import date, datetime
from dateutil.relativedelta import relativedelta
date(2018, 12, 7) + relativedelta(days=1)                        # => datetime.date(2018, 12, 8)
datetime(2018, 12, 7) + relativedelta(days=1)                    # => datetime.datetime(2018, 12, 8, 0, 0)
datetime(2018, 12, 7) + relativedelta(years=1, months=1, days=1) # => datetime.datetime(2020, 1, 8, 0, 0)
date(2018, 12, 7) + relativedelta(days=-1)                       # => datetime.date(2018, 12, 6)
date(2018, 12, 7) - relativedelta(days=1)                        # => datetime.date(2018, 12, 6)
```

### 月初・月末
```python
from datetime import date, datetime
from dateutil.relativedelta import relativedelta
# 今月の月初
date(2018, 12, 7) + relativedelta(day=1)                         # => datetime.date(2018, 12, 1)
# 今月の月末
date(2018, 12, 7) + relativedelta(day=99)                        # => datetime.date(2018, 12, 31)
# 先月の月初/月末
date(2018, 12, 7) + relativedelta(months=-1, day=1)              # => datetime.date(2018, 11, 1)
date(2018, 12, 7) + relativedelta(months=-1, day=99)             # => datetime.date(2018, 11, 30)
# 来月の月初/月末
date(2018, 12, 7) + relativedelta(months=1, day=1)               # => datetime.date(2019, 1, 1)
date(2018, 12, 7) + relativedelta(months=1, day=99)              # => datetime.date(2019, 1, 31)
# datetime型も同様
datetime(2018, 12, 7) + relativedelta(months=1, day=99)          # => datetime.datetime(2019, 1, 31, 0, 0)
```

## 週数の判定
```python
from datetime import date, datetime
# その日が月の何週目にあたるのか(月曜始まり)
target_date = date(2018, 12, 7)    # datetimeでも同様に処理可能
target_date.isocalendar()[1] - (target_date + relativedelta(day=1)).isocalendar()[1] + 1  # => 2
# その日が年の何周目にあたるのか(月曜始まり)
target_date.isocalendar()          # => (2018, 49, 5):年, 週数, 曜日(1: 月曜、 2: 火曜...)が出力される
target_date.isocalendar()[1]       # => 49
```

## 曜日の判定
```python
from datetime import date, datetime
# 月曜始まりの定数リストを作成
WEEK = ["月", "火", "水", "木", "金", "土", "日"]

# リストから曜日を出力(weekay()は、 0: "月曜日", 1: "火曜日", ...となっている)
WEEK[date(2018, 12, 8).weekday()]       # => '土'
WEEK[datetime(2018, 12, 8).weekday()]   # => '土'

```

## まとめ
[maya](https://github.com/kennethreitz/maya)は、日本語の「明日」だけじゃなく、ロシア語やタガログ語も日付に変換できるぞ！
なお、使い所は不明です。一応、形態素解析と組み合わせて使える・・・のか？

今後も思いついたら適宜追加していく予定です。
指摘・修正や編集リクエストもお待ちしております！