Python 日付・時刻ライブラリー 逆引きリファレンス

  • 23
    いいね
  • 0
    コメント

Pythonで日時を扱うライブラリーについて、逆引き風にまとめてみました。

この記事の内容は、正確を期するように注意して記述していますが、公式の情報ではありません。
正確な情報をお求めなら公式リファレンス(参考資料#1)を参照してください。

想定する読者

ほとんどが標準モジュールについてのことですので、Python初心者の方か、日時ライブラリーのことをあまりご存じない方向けです。
(私自身まだPython歴が浅いので。)

前提

Python2はv2.7.10、Python3はv3.5.1で確認しています。
(Anaconda 4.0.0 Windows 64bit版を使用。)

Python2とPython3を特に個別に扱っていないものは、共通で使えるものです。
その場合、printについてはカッコつき表記で統一し、実行結果はPython3のものを採用しています。
サンプルコードは、主に対話環境での結果を掲載しています。

拡張モジュールのバージョンは以下の通りです。

dateutil  2.5.1
pytz      2016.2

APIの基本情報

日時に関する標準モジュールとそこに含まれる型について簡単に記載します。
詳しくは、公式リファレンス(参考資料#1)を参照してください。

本文中では、特に説明が無いものについてはfrom datetime import *を施したものとします。

標準モジュール

モジュール 説明
datetimeモジュール 日時に関する標準の型と操作を提供します。
timeモジュール 低レベル(システムレベル)の時間に関するユーティリティーが含まれます。
calendarモジュール 主に、テキストカレンダーに関するユーティリティーが含まれます。

拡張モジュール

モジュール 説明
dateutil 標準モジュールでは不足している日時処理を補ってくれるモジュール。
pytz タイムゾーンを扱うモジュール。詳細は後述。

説明
datetime.date 日付オブジェクト。
datetime.datetime 日時オブジェクト。
datetime.time 時刻オブジェクト。timeモジュールと混同しないように。
datetime.timedelta 2つの日時の差を表すオブジェクト。
datetime.tzinfo タイムゾーン情報の抽象基底クラス。
datetime.timezone タイムゾーン情報の固定オフセットによる実装。
time.struct_time UNIXのstruct tm(構造体)に相当する名前付きタプル。

datetimeモジュールの型階層は以下の通り。
※注:Python3.2未満にはtimezone型は存在しません。

object
    timedelta
    tzinfo
        timezone
    time
    date
        datetime

以降、本文中では型のモジュール名を省略します。

naiveとaware(タイムゾーン有無)

同じdatetimeオブジェクトでも、タイムゾーンが付いているものと付いていないものがあります。
Pythonでは、タイムゾーンが付いているdatetimeaware(な日時)、付いていないdatetimenaive(な日時)と呼びます。

dateオブジェクトは常にnaive、timeオブジェクトはどちらにもなり得ます。

タイムゾーン(tzinfo)のインスタンスとして、Python3.2で追加されたtimezoneオブジェクトが利用できます。
それ以前はtzinfoの実装は標準モジュールに存在しませんでした。
そのような場合は、pytzモジュールを使います。
tzinfoの実装には、dateutilモジュールのサブモジュールtzもあります。

また、任意のオフセット値(JSTなら+09:00)でタイムゾーンを示す場合にはtimezoneで事足りますが、
タイムゾーンの処理を厳密に扱う必要がある場合は、pytzモジュールの使用が推奨されています。
pytzモジュールは、IANAタイムゾーンデータベース(オルソンデータベース)を参照します。
詳しくは、公式リファレンス(参考資料#1)を参照して下さい。

IANAタイムゾーンデータベースについては、以下の記事を参照して下さい。

tz database - Wikipedia
https://ja.wikipedia.org/wiki/Tz_database

naiveとawareの変換方法については、本編中に説明があります。

任意の日時のオブジェクトを生成する

コンストラクターにより生成できます。timeオブジェクトについては省略します。

>>> from datetime import datetime, date, timezone, timedelta
>>>
>>> datetime(2016, 7)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Required argument 'day' (pos 3) not found
>>> datetime(2016, 7, 5)
datetime.datetime(2016, 7, 5, 0, 0)
>>> datetime(2016, 7, 5, 13)
datetime.datetime(2016, 7, 5, 13, 0)
>>> datetime(2016, 7, 5, 13, 45)
datetime.datetime(2016, 7, 5, 13, 45)
>>> datetime(2016, 7, 5, 13, 45, 27)
datetime.datetime(2016, 7, 5, 13, 45, 27)
>>> datetime(2016, 7, 5, 13, 45, 27, 123456)
datetime.datetime(2016, 7, 5, 13, 45, 27, 123456)
>>> datetime(2016, 7, 5, 13, 45, 27, 123456, timezone(timedelta(hours=+9)))
datetime.datetime(2016, 7, 5, 13, 45, 27, 123456, tzinfo=datetime.timezone(datetime.timedelta(0, 32400)))
>>>
>>> date(2016, 7)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Required argument 'day' (pos 3) not found
>>> date(2016, 7, 5)
datetime.date(2016, 7, 5)
>>> date(2016, 7, 5, 0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: function takes at most 3 arguments (4 given)

datetimeの7番目の要素は、マイクロ秒(1/1,000,000秒)です。

タイムスタンプから生成することもできます。
現在日時のタイムスタンプについては、次項を参照してください。

>>> datetime.fromtimestamp(12345678901.234567)
datetime.datetime(2361, 3, 22, 4, 15, 1, 234568)
>>> date.fromtimestamp(12345678901.234567)
datetime.date(2361, 3, 22)

ここで言うタイムスタンプ(float)とは、UNIX時間またはエポック秒と呼ばれる値です。

UNIX時間 - Wikipedia
https://ja.wikipedia.org/wiki/UNIX%E6%99%82%E9%96%93

UNIX時間は整数値ですが、Python上ではfloatで表されます。
小数点以下の値は、Python上ではマイクロ秒として扱われますが、例で分かる通り、floatの小数部で表されるので、精度は落ちます。

datetime.date()メソッドで、日付部分だけをdateとして取り出すことができます。

>>> from datetime import datetime, date
>>>
>>> dt = datetime.now()
>>> dt
datetime.datetime(2016, 7, 14, 14, 24, 46, 157000)
>>> dt.date()
datetime.date(2016, 7, 14)

現在日時のオブジェクトを生成する

datetime.now()は、現在日時を示すdatetimeオブジェクトを返します。
date.today()は、当日を示すdateオブジェクトを返します。

>>> from datetime import datetime, date
>>>
>>> datetime.now()
datetime.datetime(2016, 7, 14, 14, 14, 2, 900000)
>>> date.today()
datetime.date(2016, 7, 14)

timeモジュールのtime()関数で現在日時のタイムスタンプを取得できます。
タイムスタンプについては、前項を参照してください。

>>> import time
>>>
>>> time.time()
1468473337.565 # 2016-07-14 14:15:37.565000

他にも、timeモジュールにstruct_timeを返す(古風な)関数がいくつかありますが、今回は扱いません。
time.gmtime()関数あたりの説明を読んでみて下さい。

文字列から日時・日付へ変換

datetimeモジュールでは、datetime.strptime()関数を使います。
ただし、以下の結果を見てもらうと分かる通り、この関数は融通が利きません。

>>> from datetime import datetime, timezone, timedelta
>>> datetime.strptime("2016/07/05 13:45:06", "%Y/%m/%d %H:%M:%S")
datetime.datetime(2016, 7, 5, 13, 45, 6)
>>> datetime.strptime("2016/07/05 13:45", "%Y/%m/%d %H:%M:%S")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "...\lib\_strptime.py", line 500, in _strptime_datetime
    tt, fraction = _strptime(data_string, format)
  File "...\lib\_strptime.py", line 337, in _strptime
    (data_string, format))
ValueError: time data '2016/07/05 13:45' does not match format '%Y/%m/%d %H:%M:%S'

(※トレース内容は一部加工しています。)

dateutilモジュールのサブモジュールparserparse()関数が強力なので、そちらを使いましょう。

>>> from dateutil.parser import parse
>>>
>>> parse("Tue Aug 23 17:00:35 JST 2016")
datetime.datetime(2016, 8, 23, 17, 0, 35)
>>> parse("2016-07-03T11:22:33Z")
datetime.datetime(2016, 7, 3, 11, 22, 33, tzinfo=tzutc())
>>> parse("2016/07/03 04:05:06")
datetime.datetime(2016, 7, 3, 4, 5, 6)
>>> parse("2016/07/03")
datetime.datetime(2016, 7, 3, 0, 0)

なお、Python2の場合、下記のようなエラーが出ました。

.../dateutil/parser.py:598: UnicodeWarning: Unicode equal comparison failed to convert both arguments to Unicode - interpreting them as being unequal
  elif res.tzname and res.tzname in time.tzname:

この↓記事に書いてあるissueに関係ありそうなんですが、dateutilのバージョンが2.5.1なのに発生しているんですよね。
同類だけど別の(fixされていない)バグなのでしょうか。

twitter - python dateutil unicode warning - Stack Overflow
http://stackoverflow.com/questions/21296475/python-dateutil-unicode-warning

日時・日付から文字列へ変換

ISO8601形式に変換する場合は、date.isoformat()メソッドを使います。
datetimeオブジェクトやdateオブジェクトを単純に文字列に変換する場合は、これが使われます。

任意のフォーマットで文字列に変換したい場合は、strftime()メソッドを使用します。

>>> from datetime import datetime, timezone, timedelta
>>>
>>> dt1 = datetime(2016, 7, 5, 13, 45) # naive
>>> dt2 = datetime(2016, 7, 5, 13, 45, tzinfo=timezone(timedelta(hours=+9))) # aware
>>> d = dt1.date()
>>> dt1, dt2, d
(datetime.datetime(2016, 7, 5, 13, 45), datetime.datetime(2016, 7, 5, 13, 45, tzinfo=datetime.timezone(datetime.timedelta(0, 32400))), datetime.date(2016, 7, 5))
>>>
>>> dt1.isoformat()
'2016-07-05T13:45:00'
>>> str(dt1)
'2016-07-05 13:45:00'
>>>
>>> dt2.isoformat()
'2016-07-05T13:45:00+09:00'
>>> str(dt2)
'2016-07-05 13:45:00+09:00'
>>>
>>> d.isoformat()
'2016-07-05'
>>> str(d)
'2016-07-05'
>>>
>>> dt1.strftime("%Y/%m/%d %H:%M:%S")
'2016/07/05 13:45:00'
>>> dt1.strftime("%Y/%m/%d %H:%M:%S %Z %z")
'2016/07/05 13:45:00  '
>>> dt2.strftime("%Y/%m/%d %H:%M:%S %Z %z")
'2016/07/05 13:45:00 UTC+09:00 +0900'

フォーマットの書式については、下記リファレンスを参照してください。

8.1.8. strftime() と strptime() の振る舞い - 8.1. datetime — 基本的な日付型および時間型 — Python 3.5.1 ドキュメント
http://docs.python.jp/3.5/library/datetime.html#strftime-strptime-behavior

2つの日時を比較する

比較演算子==,!=,<,<=,>,>=による比較が可能です。

datetimedateでは比較できません。
TypeError: can't compare datetime.datetime to datetime.date
また、datetime同士でもawareとnaive間では比較できません。
TypeError: can't compare offset-naive and offset-aware datetimes
どちらかの型にそろえてから比較する必要があります。

>>> from datetime import datetime, date
>>>
>>> dt1 = datetime(2016, 7, 1, 22, 30)
>>> dt2 = datetime(2016, 7, 5, 13, 45)
>>>
>>> dt1 == datetime(2016, 7, 1, 22, 30, 0)
True
>>> dt1 == dt1, dt1 == dt2, dt1 != dt1, dt1 != dt2, dt1 < dt2, dt1 > dt2, dt1 <= dt1, dt2 <= dt1, dt2 >= dt2, dt1 >= dt2
(True, False, False, True, True, False, True, False, True, False)
>>>
>>> d1 = dt1.date()
>>> d2 = dt2.date()
>>>
>>> d1 == date(2016, 7, 1)
True
>>> d1 == d1, d1 == d2, d1 != d1, d1 != d2, d1 < d2, d1 > d2, d1 <= d1, d2 <= d1, d2 >= d2, d1 >= d2
(True, False, False, True, True, False, True, False, True, False)

2つの日時の間隔を計算する

Pythonでは、日時の間隔、差分をtimedeltaオブジェクトで表現します。
deltaは差分(difference⇒Δ)の意。)

timedeltadatetime同士またはdate同士の引き算(-演算)で得られます。

datetimedateでは計算できません。
TypeError: unsupported operand type(s) for -
また、datetime同士でもawareとnaive間では計算できません。
TypeError: can't subtract offset-naive and offset-aware datetimes
どちらかの型にそろえてから計算する必要があります。

>>> from datetime import datetime, date
>>>
>>> d1, d2 = date(2016, 7, 1), date(2016, 7, 3)
>>>
>>> type(d2 - d1)
<class 'datetime.timedelta'>
>>> d2 - d1
datetime.timedelta(2)
>>> str(d2 - d1)
'2 days, 0:00:00'
>>> str(d1 - d2)
'-2 days, 0:00:00'
>>>
>>> dt1, dt2 = datetime(2016, 7, 1, 10, 30), datetime(2016, 7, 3, 9, 10)
>>> dt2 - dt1
datetime.timedelta(1, 81600)
>>> str(dt2 - dt1)
'1 day, 22:40:00'

2つの日時の月数を計算する

前項で書いた通り、2つの日付の差分で返されるのは日数です。
2つの日付の間隔が「何か月間なのか」を知りたい場合はどうしたら良いでしょうか?

そういう場合は、dateutilモジュールのサブモジュールrelativedeltaを使ってみましょう。
基本的にはこれで解決できるはずです。

>>> from datetime import date
>>> from dateutil.relativedelta import relativedelta
>>>
>>> d1, d2 = date(2014, 11, 22), date(2016, 3, 1)
>>> str(d2 - d1)
'465 days, 0:00:00'
>>> delta = relativedelta(d2, d1)
>>> delta
relativedelta(years=+1, months=+3, days=+8)
>>> month_count = delta.years * 12 + delta.months
>>> month_count
15

12か月以上はyearsになってしまいますが、years * 12 + monthsで月数を算出することができました。

ここで少し気になるのが、月末がからんでくる計算、特にうるう年の2月前後の場合です。
そこで、2つの日付をそれぞれ1日ずつずらしていって、その場合のdeltaがどうなるかを確認してみました。

  • 2つの日付を1日ずつずらしてrelativedeltaの結果を調べるスクリプト
from datetime import date, timedelta
from dateutil.relativedelta import relativedelta

d1 = date(2016, 2, 26)
d2 = date(2016, 3, 26)
delta1day = timedelta(days=1)

for x in range(9):
    print(d1, d2, relativedelta(d2, d1))
    d1 += delta1day
    d2 += delta1day
  • 実行結果
2016-02-26 2016-03-26 relativedelta(months=+1)
2016-02-27 2016-03-27 relativedelta(months=+1)
2016-02-28 2016-03-28 relativedelta(months=+1)
2016-02-29 2016-03-29 relativedelta(months=+1)
2016-03-01 2016-03-30 relativedelta(days=+29)
2016-03-02 2016-03-31 relativedelta(days=+29)
2016-03-03 2016-04-01 relativedelta(days=+29)
2016-03-04 2016-04-02 relativedelta(days=+29)
2016-03-05 2016-04-03 relativedelta(days=+29)

さらに先の日付まで見てみましたが、大雑把にいうとどうやら2月の月末をまたぐ場合にだけ特別扱いをしているようです。
それ以外はずっと同じ日数(+29)になりました。

2つの日付の間隔を1日増やしてみると、今度は30日の月の月末をまたいでいる場合とそうでない場合で日数が変わりました。
きっと、月末日で補正をかけているのでしょう。

この辺の性質を理解しておけば大丈夫そうですね。

日付・日時の増減

datetime - datetimedate - datetimedeltaになることは既に書きましたが、
日付・日時の増減については、timedeltaを足し引きすることで、増減されたdatetimedateを得ることができます。

>>> from datetime import datetime, date, timedelta
>>>
>>> dt = datetime(2016, 7, 5, 13, 45)
>>> d = dt.date()
>>>
>>> dt + timedelta(days=5, minutes=168)
datetime.datetime(2016, 7, 10, 16, 33)
>>> d - timedelta(days=13)
datetime.date(2016, 6, 22)

月単位での加減は、月数計算のときと同じく、dateutilモジュールのサブモジュールrelativedeltaを使用します。

>>> from datetime import date
>>> from dateutil.relativedelta import relativedelta
>>>
>>> date(2016, 7, 5) - relativedelta(months=32)
datetime.date(2013, 11, 5)

naiveとawareの相互変換

以下の例は、Python3において、標準モジュールだけでnaiveとawareの相互変換を確認したものです。
Python2(正確にはPython3.2未満)にはtimezone型が存在しないので、代わりにpytzモジュールを使ってください。

naiveなdatetimeをawareなdatetimeにするには、datetime.replace()メソッドを使います。

>>> from datetime import datetime, timezone, timedelta
>>>
>>> jst = timezone(timedelta(hours=+9)) # pytzを使う場合は pytz.timezone('Asia/Tokyo')
>>> jst
datetime.timezone(datetime.timedelta(0, 32400))
>>> dt = datetime(2016, 7, 5, 13, 45) # naive
>>> dt
datetime.datetime(2016, 7, 5, 13, 45)
>>> dt2 = dt.replace(tzinfo=jst) # aware
>>> dt2
datetime.datetime(2016, 7, 5, 13, 45, tzinfo=datetime.timezone(datetime.timedelta(0, 32400)))
>>> str(dt2)
'2016-07-05 13:45:00+09:00'

参考資料#4-2によると、datetime.replace()メソッドでタイムゾーンを付加した場合に、少しおかしな挙動をするという報告がありました。
代替案として、jst.localize(datetime(...))を使う方法が示されています。
私の環境では再現しませんでしたので、pytzの古いバージョンでのバグなのかも知れません。

逆に、awareなdatetimeをnaiveなdatetimeにするには、同じくdatetime.replace()メソッドを使います。
タイムゾーンには、「無し」を意味するNoneを指定します。

# (上記の続き)

>>> dt3 = dt2.replace(tzinfo=None)
>>> dt3
datetime.datetime(2016, 7, 5, 13, 45)
>>> str(dt3)
'2016-07-05 13:45:00'

struct_timedatetimeまたはdateに変換

タイムスタンプ型をstruct_timeで表すことがありますが、これをdatetime型に変換したい場合。

time.mktime()関数とdatetime.fromtimestamp()関数を使って、
struct_timetimestamp(float)datetime
の順に変換します。

>>> from datetime import datetime, date
>>> from time import localtime, mktime
>>>
>>> tm = localtime() # struct_time
>>> t = mktime(tm) # タイムスタンプ(float)に変換
>>> datetime.fromtimestamp(t)
datetime.datetime(2016, 7, 12, 22, 31, 15)
>>> date.fromtimestamp(t)
datetime.date(2016, 7, 12)

struct_timeがタプルであることを利用して、次のように書くこともできます。

# (上記の続き)

>>> datetime(*tm[:6])
datetime.datetime(2016, 7, 12, 22, 31, 15)

tm[:6]は年月日時分秒のタプルが得られますので、それをdatetimeのコンストラクターに6つの引数として渡しています。
これは簡潔に書けますが、可読性が低下するので避けたほうが良いかも知れません。

datetimeまたはdatestruct_timeに変換

前項とは逆に、datetimeオブジェクトやdateオブジェクトをstruct_timeオブジェクトに変換したい場合は、
date.timetuple()メソッドを使うとstruct_timeが得られます。

>>> from datetime import datetime
>>>
>>> dt = datetime(2016, 7, 3, 12, 25)
>>> d = dt.date()
>>>
>>> dt.timetuple()
time.struct_time(tm_year=2016, tm_mon=7, tm_mday=3, tm_hour=12, tm_min=25, tm_sec=0, tm_wday=6, tm_yday=185, tm_isdst=-1)
>>> d.timetuple()
time.struct_time(tm_year=2016, tm_mon=7, tm_mday=3, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=6, tm_yday=185, tm_isdst=-1)

日付・日時のリストを生成する

ここでは日付の例だけ書きます。
日時の場合でも、型と単位を変えればいずれの方法も同様に適用できるはずです。

有限の日付リストを生成するには、timedeltarangeを組み合わせて、内包表記でリストを作る方法がすぐに思いつきました。

>>> from datetime import date, timedelta
>>>
>>> start_date = date(2016, 7, 5)
>>> a = [start_date + timedelta(days=x) for x in range(5)]
>>> a
[datetime.date(2016, 7, 5), datetime.date(2016, 7, 6), datetime.date(2016, 7, 7), datetime.date(2016, 7, 8), datetime.date(2016, 7, 9)]

...が、これだと1行で書けないのが少し残念です。
start_dateの代わりに直接date(2016, 7, 5)を書けばできますが、もちろんそれは無し。)

dateutilモジュールのrruleサブモジュールを使うと、1行で書けます。

>>> from datetime import date
>>> from dateutil.rrule import rrule, DAILY
>>>
>>> a = [x.date() for x in rrule(DAILY, count=5, dtstart=date(2016, 7, 5))]
>>> a
[datetime.date(2016, 7, 5), datetime.date(2016, 7, 6), datetime.date(2016, 7, 7), datetime.date(2016, 7, 8), datetime.date(2016, 7, 9)]

そうそう、こういう風に書きたかったんです。

場合によっては、Pandasモジュール(pandas.date_range)を使った方が便利かも知れません。
詳しくは参考資料#5を参照して下さい。

閏年(うるうどし)の判定

calendarモジュールのisleap関数を使います。

>>> import calendar
>>>
>>> calendar.isleap(2016)
True
>>> calendar.isleap(2015)
False
>>>
>>> from datetime import date
>>>
>>> d = date(2016, 7, 5)
>>> calendar.isleap(d.year)
True

datetimeモジュールに属するオブジェクトのbool評価

datetimeモジュール内の日時関連オブジェクトは、Python3.5の時点では、timedelta(0)と等価のものを除き、すべてTrueと評価されます。
timezoneについては、ドキュメントに記載がありませんでしたので、「すべて」には含みません。)

>>> from datetime import *
>>>
>>> bool(datetime.fromtimestamp(0))
True
>>> bool(date.fromtimestamp(0))
True
>>> bool(time(0, 0, 0))
True
>>> bool(timedelta(1))
True
>>> bool(timedelta(0))
False
>>> bool(timezone.utc)
True

ところで、Python2では、午前零時ちょうどのtimeオブジェクトはFalseと評価されます。

>>> from datetime import *

>>> bool(time(0, 0, 0))
False

これは、Python2というよりも、Python3.5未満で存在したバグ(Issue13936)で、Python3.5で修正された挙動です。
Python3.4.3で確認したところ、やはりFalseとなりました。

処理時間計算

他の言語でも使われる方法ですが、(処理後のタイムスタンプ - 処理前のタイムスタンプ)で算出します。

import time

t = time.time()
time.sleep(3.1)
print(time.time() - t)
# => 3.0999999046325684

今回は扱いませんが、コード断片の実行時間を計測するには、下記のモジュールを利用する方法もあります。

27.5. timeit — 小さなコード断片の実行時間計測 — Python 3.5.1 ドキュメント
http://docs.python.jp/3.5/library/timeit.html

付録

直列化サンプル

各オブジェクトをpickleにより直列化したしたときのイメージです。

  • Python2での実行結果
>>> from datetime import datetime
>>> import pickle
>>>
>>> dt = datetime(2016, 7, 5, 13, 45)
>>>
>>> pickle.dumps(dt) # datetime
"cdatetime\ndatetime\np0\n(S'\\x07\\xe0\\x07\\x05\\r-\\x00\\x00\\x00\\x00'\np1\ntp2\nRp3\n."
>>> pickle.dumps(dt.date()) # date
"cdatetime\ndate\np0\n(S'\\x07\\xe0\\x07\\x05'\np1\ntp2\nRp3\n."
>>> pickle.dumps(dt.timetuple()) # struct_time
'ctime\nstruct_time\np0\n((I2016\nI7\nI5\nI13\nI45\nI0\nI1\nI187\nI-1\ntp1\n(dp2\ntp3\nRp4\n.'
>>> pickle.dumps(dt - dt) # timedelta
'cdatetime\ntimedelta\np0\n(I0\nI0\nI0\ntp1\nRp2\n.'
  • Python3での実行結果
>>> from datetime import datetime
>>> import pickle
>>>
>>> dt = datetime(2016, 7, 5, 13, 45)
>>> pickle.dumps(dt) # datetime
b'\x80\x03cdatetime\ndatetime\nq\x00C\n\x07\xe0\x07\x05\r-\x00\x00\x00\x00q\x01\x85q\x02Rq\x03.'
>>> pickle.dumps(dt.date()) # date
b'\x80\x03cdatetime\ndate\nq\x00C\x04\x07\xe0\x07\x05q\x01\x85q\x02Rq\x03.'
>>> pickle.dumps(dt.timetuple()) # struct_time
b'\x80\x03ctime\nstruct_time\nq\x00(M\xe0\x07K\x07K\x05K\rK-K\x00K\x01K\xbbJ\xff\xff\xff\xfftq\x01}q\x02(X\x07\x00\x00\x00tm_zoneq\x03NX\t\x00\x00\x00tm_gmtoffq\x04Nu\x86q\x05Rq\x06.'
>>> pickle.dumps(dt - dt) # timedelta
b'\x80\x03cdatetime\ntimedelta\nq\x00K\x00K\x00K\x00\x87q\x01Rq\x02.'

参考資料

#1.
(公式ドキュメント・標準ライブラリーリファレンス)

8.1. datetime — 基本的な日付型および時間型 — Python 3.5.1 ドキュメント
http://docs.python.jp/3.5/library/datetime.html#module-datetime
8.1. datetime — 基本的な日付型および時間型 — Python 2.7.x ドキュメント
http://docs.python.jp/2/library/datetime.html#module-datetime

datetime以外のモジュールのリンクは冗長なので省きます。上記リンクのページのどこかにリンクがあるのでそこから飛んでください。)

#2.
dateutil - powerful extensions to datetime — dateutil 2.5.3 documentation
https://dateutil.readthedocs.io/en/stable/

#3.
pytz 2016.4 : Python Package Index
https://pypi.python.org/pypi/pytz/

#4-1.
Pythonの日付処理とTimeZone | Nekoya Press
http://nekoya.github.io/blog/2013/06/21/python-datetime/

#4-2.
Pythonでdatetimeにtzinfoを付与するのにreplaceを使ってはいけない | Nekoya Press
http://nekoya.github.io/blog/2013/07/05/python-datetime-with-jst/

#5.
Python pandas で日時関連のデータ操作をカンタンに - StatsFragments
http://sinhrks.hatenablog.com/entry/2014/11/09/183603