LoginSignup
17
15

More than 5 years have passed since last update.

Time::Piece に関するとりとめのないコト

Last updated at Posted at 2013-11-05
  1. Time::Piece->strptime() で返されるオブジェクトは GMT
  2. Time::Piece->strptime() では1899年以前の日付は解釈できない
  3. gmtime->strftime('%Z') は,オブジェクトが GMT であるにもかかわらず,localtime のタイムゾーン名を返す (%z も同様) ⇒ 追記 そのうち治りそう (2015-04-10)
  4. Time::Piece のオブジェクトは,自身が localtimegmtime であるかのフラグを持っている。だがタイムゾーンはもっていない。(#tzoffset は呼び出される度に算出する)

Time::Piece->strptime() で返される値は GMT

use Time::Piece;

Time::Piece->strptime('2013-11-03 12:34:56', '%Y-%m-%d %H:%M:%S')->strftime;
# => 日, 03 11月 2013 12:34:56 UTC

これは %z 等でタイムゾーンを解釈させても,かわらない。

Time::Piece->strptime('2013-11-03 +0900', '%Y-%m-%d %z')->strftime;
# => 土, 02 11月 2013 15:00:00 UTC

Time::Piece->strptime() では1899年以前の日付は解釈できない

Time::Piece->strptime('1868-10-23', '%Y-%m-%d');
# => Error parsing time at ...

どうしてこうなるのかは Time::Piece の Parse.xs の _strptime() でも読んでください。

なお Time::Piece オブジェクトが1899年以前を表象できないわけではない。

use Time::Piece;
use Time::Local;

$t = Time::Local::timelocal(0,0,0, 23, 10-1, 1868);
# => -3193291139

localtime($t)->strftime;
# => 金, 23 10月 1868 00:00:00 JST

Time::LocalTime::Local で西暦999年以前を設定できないという点もあるのだけれど。そのへんは perldoc でもみてください。

gmtime->strftime('%Z') は,オブジェクトが GMT であるにもかかわらず,localtime のタイムゾーン名を返す (%z も同様)

localtime->strftime('%z %Z');
# => +0900 JST
gmtime->strftime('%z %Z');
# => +0900 JST

これはバグだと思うので,一応 Pull Request はだしてある。

追記 (2015-04-10) 1.29_03 で取り込まれたので 1.30 以降では治るかな? https://github.com/rjbs/Time-Piece/commit/ecc37f3ec3b03a917076f474d3c4c64640384b01

ちなみに C の strftime() はきちんと localtimegmtime%z の出力をだしわける。

#include <stdio.h>
#include <time.h>

int main(int argc, char *argv[])
{
    char buf[1024];
    time_t t = time(NULL);

    strftime(buf, 1024, "%z; %Z; %c\n", localtime(&t));
    puts(buf);  /* => +0900; JST; Sun Nov  3 17:42:42 2013 */

    strftime(buf, 1024, "%z; %Z; %c\n", gmtime(&t));
    puts(buf);  /* => +0000; GMT; Sun Nov  3 08:42:42 2013 */

    return 0;
}

Time::Piece#strftime の出力がおかしい原因としては Perl のソースの Util.c で定義されている init_tm()localtime で得られる結果をコピーしており,Time::Piece#strftime ではその init_tm() を使って作成した構造体を C の strftime() にわたしているからである。
なので Pull Request はわりと adhoc な解決策になっている。

Time::Piece のオブジェクトは,自身が localtimegmtime であるかのフラグを持っている。だがタイムゾーンはもっていない。(#tzoffset は呼び出される度に算出する)

フラグ (c_islocal) については以下の通り。

use Time::Piece;
use Data::Dumper;

say Data::Dumper->new([ scalar localtime ])
        ->Indent(1)->Terse(1)->Useqq(1)->Dump();
# => bless( [
#   33,                 # c_sec
#   9,                  # c_min
#   18,                 # c_hour
#   3,                  # c_mday
#   10,                 # c_mon
#   113,                # c_year
#   0,                  # c_wday
#   306,                # c_yday
#   0,                  # c_isdst
#   "1383469773",       # c_epoch
#   1                   # c_islocal
# ], 'Time::Piece' )

いくつかの Time::Piece のメソッドは,このフラグをみて挙動をかえている。

Time::Piece::c_islocal 定数を使って,このフラグにアクセスすることができる (が,推奨されない,と思う)。

use Time::Piece;

my $lt = localtime;
$lt->[Time::Piece::c_islocal];  # => 1
my $gt = gmtime;
$gt->[Time::Piece::c_islocal];  # => 0

ちなみに,BSD や glibc の struct tm は,islocal のようなフラグのかわりに,UTC からのオフセットとタイムゾーンの文字列を保持している。Time::Piece もそのようになっていたら便利だったのに……

/* time.h from GNU C Library */
/* ...... snip snip snip ...... */

struct tm
{
  int tm_sec;                   /* Seconds.     [0-60] (1 leap second) */
  int tm_min;                   /* Minutes.     [0-59] */
  int tm_hour;                  /* Hours.       [0-23] */
  int tm_mday;                  /* Day.         [1-31] */
  int tm_mon;                   /* Month.       [0-11] */
  int tm_year;                  /* Year - 1900.  */
  int tm_wday;                  /* Day of week. [0-6] */
  int tm_yday;                  /* Days in year.[0-365] */
  int tm_isdst;                 /* DST.         [-1/0/1]*/

#ifdef  __USE_BSD
  long int tm_gmtoff;           /* Seconds east of UTC.  */
  __const char *tm_zone;        /* Timezone abbreviation.  */
#else
  long int __tm_gmtoff;         /* Seconds east of UTC.  */
  __const char *__tm_zone;      /* Timezone abbreviation.  */
#endif
};

/* ...... snip snip snip ...... */

Time::Piece では,#tzoffset (GMT の場合は常に 0) の呼び出しごとに GMT との差分を算出している。つまり,Time::Piece オブジェクトが生成された時点の TZ ではなく,#tzoffset を呼び出した時点での TZ をもとに差分を算出するので,(状況によっては) 注意が必要である。

タイムゾーン,オフセットを保持していないので,(複数箇所のタイムゾーンの時刻オブジェクトを利用するケースにおいて) そのようなものを内包する日時コンテナオブジェクトとして扱ったりシリアライズに使ったりするには不適当である。

(もちろん,実質 local time しか扱わないアプリケーションにおいては,十分な要件をみたしている)

17
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
15