2010-11-24

日付文字列の変換 - RFC3339 から NSDate へ

実装

まずは、今回作った Objective-C による実装から示す。関数の機能は単純で、RFC3399 形式で表現された日時を元に NSDate のオブジェクトを生成する。関数内で NSDate オブジェクトを alloc しているが、返す前に autorelease しているため、荻原(2.0)本 (p.96) で言う「一時的なオブジェクト」となっている。また、関数を呼び出す側で自動解放プールを用意する必要がある。通常の Cocoa アプリの場合は、NSApplication がイベントループの管理とともに自動解放プールも用意してくれる(→ 「荻原(2.0)本」p.98)。

この実装では NSString の機能のうち、以下のメソッドを多用している。

- (NSRange)rangeOfCharacterFromSet:(NSCharacterSet*)aSet
aSet で渡された文字集合中の文字が最初に見つかった場所を返す。
- (NSString*)substringToIndex:(NSUInteger)aIndex
先頭から位置 aIndex までの部分文字列を返す。
- (NSString*)substringWithRange:(NSRange)aRange
aRange.location を開始位置とし、aRange.length を長さとする部分文字列を返す。
- (NSString*)substringFromIndex:(NSUInteger)
aIndex を開始位置としする末尾までの部分文字列を返す。

上記の substring...の戻り値はいずれも一時的なオブジェクトになっている。

参照仕様

GData API の Protocl Reference によれば、各種日付には RFC3339 フォーマットが使われる、とある。これは GData API が使うデータフォーマット(Atom 形式; RFC 4287 で定義されている)の仕様によるものだ。

(「RFC 4287」より)
A Date construct is an element whose content MUST conform to the "date-time" production in [RFC3339]. In addition, an uppercase "T" character MUST be used to separate date and time, and an uppercase "Z" character MUST be present in the absence of a numeric time zone offset.

[...snip...]

Example Date constructs:
<updated>2003-12-13T18:30:02Z</updated>
<updated>2003-12-13T18:30:02.25Z</updated>
<updated>2003-12-13T18:30:02+01:00</updated>
<updated>2003-12-13T18:30:02.25+01:00</updated>

上記の中で例として挙げられている 4 つのパターンを見れば書式のおよそは理解できる。

より詳細な 書式の定義は RFC 3339 にある。

date-fullyear   = 4DIGIT
date-month      = 2DIGIT  ; 01-12
date-mday       = 2DIGIT  ; 01-28, 01-29, 01-30, 01-31 based on
                          ; month/year
time-hour       = 2DIGIT  ; 00-23
time-minute     = 2DIGIT  ; 00-59
time-second     = 2DIGIT  ; 00-58, 00-59, 00-60 based on leap second
                          ; rules
time-secfrac    = "." 1*DIGIT
time-numoffset  = ("+" / "-") time-hour ":" time-minute
time-offset     = "Z" / time-numoffset

partial-time    = time-hour ":" time-minute ":" time-second
                  [time-secfrac]
full-date       = date-fullyear "-" date-month "-" date-mday
full-time       = partial-time time-offset

date-time       = full-date "T" full-time

この定義から、日時の表現は「日付」と「時刻」に二分され、その区切りが文字「T」であること、また「時刻」は「時刻本体」と「時差」に分かれることが読みとれる。

「時差」については、時刻による差分で表現したもの(「+09:00」や「-5:00」など)か、あるいは文字「Z」を用いる(時差として文字「Z」を指定した場合、その時刻は UTC を意味する)。

さらに「時刻本体」の表現については、「時」「分」「秒」をそれぞれ 2 桁の十進数で表現する(1 桁の場合は先頭に「0」を付加する)。また「秒」については 1 秒以下を小数表現で「秒」に付けても良い、となっている。

GData API での時刻の取扱い

実際に GData API が返すフィードを見ると、updated 要素等は以下のようになっていて、確かに上記の書式に従っていることがわかる。

<published>2010-10-03T21:18:00.000+09:00</published>
<updated>2010-10-03T21:18:00.840+09:00</updated>

一方、GData Objective-C Library が返す時刻データは GDataDateTime クラスのインスタンスになっていて、そこから文字列データを取り出すと RFC3339 フォーマットの文字列となる。ただし、それを作る部分は以下のようになっているため、「秒数」の小数点以下が無視されている(GData Objective-C Client ver. 1.10.0)。これは、Cocoa の NSDateComponents が小数点以下の秒数をサポートしていないことによるものだと思われる(以下のコード中の dateComponents は NSDateComponents のインスタンスを保持している)。

- (NSString *)stringValue {
  return [self RFC3339String];
}

- (NSString *)RFC3339String {
  NSDateComponents *dateComponents = [self dateComponents];
  NSInteger offset = [self offsetSeconds];

  NSString *timeString = @""; // timeString like "T15:10:46-08:00"

  if ([self hasTime]) {

    NSString *timeOffsetString; // timeOffsetString like "-08:00"

    if ([self isUniversalTime]) {
     timeOffsetString = @"Z";
    } else if (offset == NSUndefinedDateComponent) {
      // unknown offset is rendered as -00:00 per
      // http://www.ietf.org/rfc/rfc3339.txt section 4.3
      timeOffsetString = @"-00:00";
    } else {
      NSString *sign = @"+";
      if (offset < 0) {
        sign = @"-";
        offset = -offset;
      }
      timeOffsetString = [NSString stringWithFormat:@"%@%02ld:%02ld",
        sign, (long)(offset/(60*60)) % 24, (long)(offset / 60) % 60];
    }
    timeString = [NSString stringWithFormat:@"T%02ld:%02ld:%0l2d%@",
      (long)[dateComponents hour], (long)[dateComponents minute],
      (long)[dateComponents second], timeOffsetString];
  }

  // full dateString like "2006-11-17T15:10:46-08:00"
  NSString *dateString = [NSString stringWithFormat:@"%04ld-%02ld-%02ld%@",
    (long)[dateComponents year], (long)[dateComponents month],
    (long)[dateComponents day], timeString];

  return dateString;
}

おまけ

最初に挙げたプログラムを Xcode で Command Line Tool としてビルドし、実行するとコンソールには以下のように出力される。水平線で区切られた 4 行が、testGetDateObject の一回の呼び出しによるもので、最初が引数として与えられた RFC3339 形式の日時文字列、2 行目が getDateObject による変換結果(の NSDate のインスタンス)、3 行目がそのインスタンスをデフォルトのタイムゾーン(ウチではもちろん JST)で評価した結果、最後の 4 行目は同インスタンスを UTC で評価した結果となっている。

2010-11-24 22:06:14.819 ExpApp[24929:a0b] ----------------------------------------
2010-11-24 22:06:14.821 ExpApp[24929:a0b] RFC3339 Format: 2010-11-22T12:34:56Z
2010-11-24 22:06:14.824 ExpApp[24929:a0b] NSDate object: 2010-11-22 21:34:56 +0900
2010-11-24 22:06:14.824 ExpApp[24929:a0b] Default TZ: 2010-11-22 21:34:56 +0900
2010-11-24 22:06:14.825 ExpApp[24929:a0b] UTC: 2010-11-22 12:34:56 +0000
2010-11-24 22:06:14.825 ExpApp[24929:a0b] ----------------------------------------
2010-11-24 22:06:14.825 ExpApp[24929:a0b] RFC3339 Format: 2010-11-22t12:34:56z
2010-11-24 22:06:14.826 ExpApp[24929:a0b] NSDate object: 2010-11-22 21:34:56 +0900
2010-11-24 22:06:14.826 ExpApp[24929:a0b] Default TZ: 2010-11-22 21:34:56 +0900
2010-11-24 22:06:14.826 ExpApp[24929:a0b] UTC: 2010-11-22 12:34:56 +0000
2010-11-24 22:06:14.827 ExpApp[24929:a0b] ----------------------------------------
2010-11-24 22:06:14.827 ExpApp[24929:a0b] RFC3339 Format: 2010-11-22T12:34:56.123Z
2010-11-24 22:06:14.827 ExpApp[24929:a0b] NSDate object: 2010-11-22 21:34:56 +0900
2010-11-24 22:06:14.828 ExpApp[24929:a0b] Default TZ: 2010-11-22 21:34:56 +0900
2010-11-24 22:06:14.828 ExpApp[24929:a0b] UTC: 2010-11-22 12:34:56 +0000
2010-11-24 22:06:14.828 ExpApp[24929:a0b] ----------------------------------------
2010-11-24 22:06:14.829 ExpApp[24929:a0b] RFC3339 Format: 2010-11-22t12:34:56.123z
2010-11-24 22:06:14.829 ExpApp[24929:a0b] NSDate object: 2010-11-22 21:34:56 +0900
2010-11-24 22:06:14.829 ExpApp[24929:a0b] Default TZ: 2010-11-22 21:34:56 +0900
2010-11-24 22:06:14.830 ExpApp[24929:a0b] UTC: 2010-11-22 12:34:56 +0000
2010-11-24 22:06:14.830 ExpApp[24929:a0b] ----------------------------------------
2010-11-24 22:06:14.830 ExpApp[24929:a0b] RFC3339 Format: 2010-11-22T12:34:56+09:00
2010-11-24 22:06:14.831 ExpApp[24929:a0b] NSDate object: 2010-11-22 12:34:56 +0900
2010-11-24 22:06:14.831 ExpApp[24929:a0b] Default TZ: 2010-11-22 12:34:56 +0900
2010-11-24 22:06:14.832 ExpApp[24929:a0b] UTC: 2010-11-22 03:34:56 +0000
2010-11-24 22:06:14.832 ExpApp[24929:a0b] ----------------------------------------
2010-11-24 22:06:14.832 ExpApp[24929:a0b] RFC3339 Format: 2010-11-22t12:34:56+09:00
2010-11-24 22:06:14.833 ExpApp[24929:a0b] NSDate object: 2010-11-22 12:34:56 +0900
2010-11-24 22:06:14.833 ExpApp[24929:a0b] Default TZ: 2010-11-22 12:34:56 +0900
2010-11-24 22:06:14.834 ExpApp[24929:a0b] UTC: 2010-11-22 03:34:56 +0000
2010-11-24 22:06:14.834 ExpApp[24929:a0b] ----------------------------------------
2010-11-24 22:06:14.834 ExpApp[24929:a0b] RFC3339 Format: 2010-11-22T12:34:56.123+09:00
2010-11-24 22:06:14.835 ExpApp[24929:a0b] NSDate object: 2010-11-22 12:34:56 +0900
2010-11-24 22:06:14.836 ExpApp[24929:a0b] Default TZ: 2010-11-22 12:34:56 +0900
2010-11-24 22:06:14.836 ExpApp[24929:a0b] UTC: 2010-11-22 03:34:56 +0000
2010-11-24 22:06:14.836 ExpApp[24929:a0b] ----------------------------------------
2010-11-24 22:06:14.837 ExpApp[24929:a0b] RFC3339 Format: 2010-11-22t12.34.56.123+09:00
2010-11-24 22:06:14.837 ExpApp[24929:a0b] NSDate object: 2010-11-22 12:34:56 +0900
2010-11-24 22:06:14.838 ExpApp[24929:a0b] Default TZ: 2010-11-22 12:34:56 +0900
2010-11-24 22:06:14.838 ExpApp[24929:a0b] UTC: 2010-11-22 03:34:56 +0000
2010-11-24 22:06:14.838 ExpApp[24929:a0b] ----------------------------------------
2010-11-24 22:06:14.839 ExpApp[24929:a0b] RFC3339 Format: 2010-11-22T12:34:56-05:00
2010-11-24 22:06:14.839 ExpApp[24929:a0b] NSDate object: 2010-11-23 02:34:56 +0900
2010-11-24 22:06:14.839 ExpApp[24929:a0b] Default TZ: 2010-11-23 02:34:56 +0900
2010-11-24 22:06:14.840 ExpApp[24929:a0b] UTC: 2010-11-22 17:34:56 +0000
2010-11-24 22:06:14.840 ExpApp[24929:a0b] ----------------------------------------
2010-11-24 22:06:14.840 ExpApp[24929:a0b] RFC3339 Format: 2010-11-22T12:34:56.123-05:00
2010-11-24 22:06:14.841 ExpApp[24929:a0b] NSDate object: 2010-11-23 02:34:56 +0900
2010-11-24 22:06:14.841 ExpApp[24929:a0b] Default TZ: 2010-11-23 02:34:56 +0900
2010-11-24 22:06:14.842 ExpApp[24929:a0b] UTC: 2010-11-22 17:34:56 +0000

参考文献

詳解 Objective-C 2.0
荻原 剛志
ソフトバンククリエイティブ ( 2008-05-28 )
ISBN: 9784797346800

関連リンク

関連記事

0 件のコメント:

コメントを投稿