datetime(日付 時刻)

永井 忠一 2024.10.5


Perl と Python

Perl モジュールのインストール

Linux apt
$ sudo apt install libdatetime-perl libdatetime-hires-perl libdatetime-format-iso8601-perl

(Python では標準ライブラリ)

タイムゾーン

インスタンス化。日付と時刻、タイムゾーンの情報を引数で与えている

PerlPython
use strict;
use warnings;

use DateTime;

my $epoch = DateTime->new(
    year => 1970,
    month => 1,
    day => 1,
    hour => 0,
    minute => 0,
    second => 0,
    nanosecond => 0,
    time_zone => "UTC");

die if not $epoch->time_zone eq DateTime::TimeZone->new(name => "UTC");
print($epoch->rfc3339() . "\n");

$epoch->set_time_zone("local");
die if not $epoch->time_zone eq DateTime::TimeZone->new(name => "local");
die if not $epoch->time_zone eq DateTime::TimeZone->new(name => "Asia/Tokyo");
print($epoch->rfc3339() . "\n");
from datetime import datetime, timezone

epoch = datetime(1970, 1, 1,
                 hour=0,
                 minute=0,
                 second=0,
                 microsecond=0,
                 tzinfo=timezone.utc)

assert epoch.tzinfo == timezone.utc
print(epoch.isoformat())

epoch = epoch.astimezone()
from datetime import timedelta
assert epoch.tzinfo == timezone(timedelta(hours=9))
print(epoch.isoformat())

実行結果

PerlPython
1970-01-01T00:00:00Z
1970-01-01T09:00:00+09:00
1970-01-01T00:00:00+00:00
1970-01-01T09:00:00+09:00

(Perl の iso8601() メソッドでは、タイムゾーンの情報が付与されないため、rfc3339() のほうを利用している)

タイムゾーンを与えない場合の挙動

PerlPython
use strict;
use warnings;

use DateTime;

my $epoch = DateTime->new(
    year => 1970,
    month => 1,
    day => 1,
    hour => 0,
    minute => 0,
    second => 0);

print($epoch->time_zone . "\n");
print($epoch->rfc3339() . "\n");

$epoch->set_time_zone("UTC");
print($epoch->time_zone . "\n");
print($epoch->rfc3339() . "\n");

$epoch->set_time_zone("local");
print($epoch->time_zone . "\n");
print($epoch->rfc3339() . "\n");
from datetime import datetime, timezone

epoch = datetime(1970, 1, 1,
                 hour=0,
                 minute=0,
                 second=0,
                 microsecond=0)

print(epoch.tzinfo)
print(epoch.isoformat())

from datetime import timedelta
epoch = (epoch + timedelta(hours=9)).astimezone(timezone.utc)
print(epoch.tzinfo)
print(epoch.isoformat())

epoch = epoch.astimezone()
print(epoch.tzinfo)
assert epoch.tzinfo == timezone(timedelta(hours=9))
print(epoch.isoformat())

実行結果

PerlPython
DateTime::TimeZone::Floating=HASH(0x5b7acc868448)
1970-01-01T00:00:00
DateTime::TimeZone::UTC=HASH(0x5b7acc840ef0)
1970-01-01T00:00:00Z
DateTime::TimeZone::Asia::Tokyo=HASH(0x5b7acbda98e8)
1970-01-01T09:00:00+09:00
None
1970-01-01T00:00:00
UTC
1970-01-01T00:00:00+00:00
JST
1970-01-01T09:00:00+09:00

(Python では、tzinfo が None のとき、localtime と同じように扱われる)

現在時刻の取得

Perl と Python とも、now() メソッドを利用

PerlPython
use strict;
use warnings;

use DateTime;

my $now = DateTime->now();

print($now->time_zone . "\n");
die if not $now->time_zone eq DateTime::TimeZone->new(name => "UTC");
from datetime import datetime

now = datetime.now()

print(now.tzinfo)
assert now.tzinfo == None

デフォルトのタイムゾーンは、Perl では UTC、Python では None となる


サブ秒の取得

Perl では、別モジュール「DateTime::Hires」が必要になる

PerlPython
use strict;
use warnings;

use DateTime::HiRes;

foreach (1..10) {
    my $now = DateTime::HiRes->now();
    print($now->microsecond  . " us\n");
    print($now->nanosecond  . " ns\n");
}
from datetime import datetime

for iDummy in range(10):
    print(datetime.now().microsecond, 'us')

(私の環境では、Perl で nano sec. 分解能のタイムスタンプは取得できなかった。)Perl の実行結果の例

Perl
494635 us
494635000 ns
494711 us
494711000 ns
494739 us
494739000 ns
494759 us
494759000 ns
494776 us
494776000 ns
494791 us
494791000 ns
494806 us
494806000 ns
494821 us
494821000 ns
494835 us
494835000 ns
494849 us
494849000 ns

(nano sec. 台は 000 となり、実際には取得できていない)


日付時刻の処理

要素の値の変更

PerlPython
use strict;
use warnings;

use DateTime;

my $epoch = DateTime->new(
    year => 1970,
    month => 1,
    day => 1);

$epoch->set_year(2000);
print("$epoch\n");
from datetime import datetime, timezone

epoch = datetime(1970, 1, 1,
                 tzinfo=timezone.utc)

epoch = epoch.replace(year=2000)
print(epoch)

Perl では、set_*() メソッドが、それぞれ用意されている

Python では、replace() の引数に、各要素を渡すことができる

時間 間隔

翌日、翌月、翌年を求める

PerlPython
use strict;
use warnings;

use DateTime;

my $now = DateTime->now(time_zone => 'local');
print(($now + DateTime::Duration->new(days => 1)) . "\n");

$now->add(days => 1);
print("$now\n");

$now = DateTime->now(time_zone => 'local');
print(($now + DateTime::Duration->new(months => 1)) . "\n");

$now->add(months => 1);
print("$now\n");

$now = DateTime->now(time_zone => 'local');
print(($now + DateTime::Duration->new(years => 1)) . "\n");

$now->add(years => 1);
print("$now\n");
from datetime import datetime, timedelta

now = datetime.now()
print(now + timedelta(days=1))

from dateutil.relativedelta import relativedelta
print(now + relativedelta(months=1))

print(now + relativedelta(years=1))

前日、前月、前年を求める

PerlPython
use strict;
use warnings;

use DateTime;

my $now = DateTime->now(time_zone => 'local');
print(($now - DateTime::Duration->new(days => 1)) . "\n");
die if not ($now - DateTime::Duration->new(days => 1)) eq ($now + DateTime::Duration->new(days => -1));

$now->subtract(days => 1);
print("$now\n");

$now = DateTime->now(time_zone => 'local');
print(($now - DateTime::Duration->new(months => 1)) . "\n");
die if not ($now - DateTime::Duration->new(months => 1)) eq ($now + DateTime::Duration->new(months => -1));

$now->subtract(months => 1);
print("$now\n");

$now = DateTime->now(time_zone => 'local');
print(($now - DateTime::Duration->new(years => 1)) . "\n");
die if not ($now - DateTime::Duration->new(years => 1)) eq ($now + DateTime::Duration->new(years => -1));

$now->subtract(years => 1);
print("$now\n");
from datetime import datetime, timedelta

now = datetime.now()
print(now - timedelta(days=1))
assert (now - timedelta(days=1)) == (now + timedelta(days=-1))

from dateutil.relativedelta import relativedelta
print(now - relativedelta(months=1))
assert (now - relativedelta(months=1)) == (now + relativedelta(months=-1))

print(now - relativedelta(years=1))
assert (now - relativedelta(years=1)) == (now + relativedelta(years=-1))

(Perl では、Duration を使った演算のほかに、add()、subtract() メソッドが利用できる)

(Python では、前年・翌年と前月・翌月を timedelta では求められない。別途 relativedelta が用意される)

時刻と時刻の引き算

PerlPython
use strict;
use warnings;

use DateTime;

print((DateTime->now() - DateTime->now()) . "\n");
print((DateTime->now() - DateTime->now())->seconds . "\n");
from datetime import datetime

print(type(datetime.now() - datetime.now()))
print((datetime.now() - datetime.now()).seconds)

実行結果の例

PerlPython
DateTime::Duration=HASH(0x6458dc80f7b0)
0
<class 'datetime.timedelta'>
0

演算の結果は、それぞれ Duration、timedelta となる

(Perl では、time_zone が TimeZone::Floating のものと、タイムゾーンを持つ DateTime との引き算が行える)

(Pytnon では、tzinfo が None のものと、タイムゾーンを持つ datetime との引き算は、TypeError となる)

    print(datetime.now(timezone.utc) - datetime.now())
          ~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~
TypeError: can't subtract offset-naive and offset-aware datetimes

指定した要素で切り詰める

指定した要素まで残して、切り捨てる

PerlPython
use strict;
use warnings;

use DateTime;

my $now = DateTime->now();

$now->truncate(to => "day");
print("$now\n");
from datetime import datetime

now = datetime.now()

now = datetime(now.year, now.month, now.day)
print(now)

Perl の truncate() には、「truncate(to => "year")」、「truncate(to => "month")」など、日付時刻の要素を指定することができる

要素に、秒を指定した場合

Perl
use strict;
use warnings;

use DateTime::HiRes;

my $now = DateTime::HiRes->now();

print($now->microsecond . "\n");
$now->truncate(to => "second");
print($now->microsecond . "\n");

(Python には、truncate() は無い)

前後関係の比較

PerlPython
use strict;
use warnings;

use DateTime;

my $primary = DateTime->now();
my $secondary = DateTime->now();

die if not $primary <= $secondary;
from datetime import datetime

primary = datetime.now();
secondary = datetime.now();

assert primary <= secondary

(Perl では、time_zone が TimeZone::Floating のものと、タイムゾーンを持つ DateTime との比較が行える)

(Pytnon では、tzinfo が None のものと、タイムゾーンを持つ datetime との比較は、TypeError となる)

    assert datetime.now(timezone.utc) <= datetime.now()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: can't compare offset-naive and offset-aware datetimes

月の最終日を得る

PerlPython
use strict;
use warnings;

use DateTime;

my $now = DateTime->now(time_zone => 'local');
print((DateTime->last_day_of_month(year => $now->year, month => $now->month))->day . "\n");
from datetime import datetime

now = datetime.now()

from calendar import monthrange 
print(monthrange(now.year, now.month)[1])

ユーティリティの、last_day_of_month() や calendar を使わないで求める場合

PerlPython
use strict;
use warnings;

use DateTime;

my $now = DateTime->now(time_zone => 'local');

$now->add(months => 1);
$now->set_day(1);
$now->subtract(days => 1);

print($now->day . "\n");
from datetime import datetime, timedelta

now = datetime.now()

from dateutil.relativedelta import relativedelta
now = now + relativedelta(months=1)
now = now.replace(day=1)
now = now - timedelta(days=1)

print(now.day)

(Python では、dateutil が必要になる)


文字列への変換

Perl と Python とも、strftime() が利用できる

PerlPython
use strict;
use warnings;

use DateTime;

my $now = DateTime->now(time_zone => 'local');

print($now->strftime("%Y-%m-%d %H:%M:%S") . "\n");
from datetime import datetime

print(datetime.now().strftime("%Y-%m-%d %H:%M:%S"))

文字列のパース

「ISO 8061」形式の文字列を準備

IPython
$ ipython3
Python 3.12.3 (main, Jul 31 2024, 17:43:48) [GCC 13.2.0]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.20.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: from datetime import datetime, timezone
   ...: datetime(1970, 1, 1, tzinfo=timezone.utc).isoformat()
Out[1]: '1970-01-01T00:00:00+00:00'

Perl では、「DateTime::Format::*」に様々なパーサーが用意されている(Format::ISO8601 モジュールを利用)

Python では、fromisoformat() が(isoformat() の逆演算として)実装されている

PerlPython
use strict;
use warnings;

use DateTime::Format::ISO8601;

my $epoch = DateTime::Format::ISO8601->parse_datetime("1970-01-01T00:00:00+00:00");
my $verify = DateTime->new(year => 1970, month => 1, day => 1, time_zone => "UTC");

die if not $epoch eq $verify;
die if not $epoch->time_zone eq DateTime::TimeZone->new(name => "UTC");
print($epoch->rfc3339() . "\n");
from datetime import datetime, timezone

epoch = datetime.fromisoformat('1970-01-01T00:00:00+00:00')
verify = datetime(1970, 1, 1, tzinfo=timezone.utc)

assert epoch == verify
assert epoch.tzinfo == timezone.utc
print(epoch.isoformat())

文字列「1970-01-01T00:00:00+00:00」から、日付時刻のインスタンスを正しく構築できている

《データベースの、TIMESTAMP 型とのバインディングについて》


参照したドキュメント


© 2024 Tadakazu Nagai