実装
まずは、今回作った Objective-C による実装から示す。関数の機能は単純で、RFC3399 形式で表現された日時を元に NSDate
のオブジェクトを生成する。関数内で NSDate
オブジェクトを alloc
しているが、返す前に autorelease
しているため、荻原(2.0)本 (p.96) で言う「一時的なオブジェクト」となっている。また、関数を呼び出す側で自動解放プールを用意する必要がある。通常の Cocoa アプリの場合は、NSApplication
がイベントループの管理とともに自動解放プールも用意してくれる(→ 「荻原(2.0)本 」p.98)。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import <Foundation/Foundation.h>
// convert a RFC3399 date (& time) into a NSDate object
// NOTE: This function ignores fractions of a second in the RFC3339
// representation.
NSDate *getDateObject(NSString *rfc3339)
{
// Date and Time representation in RFC3399:
// Pattern #1: "YYYY-MM-DDTHH:MM:SSZ"
// 1
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
// [Y|Y|Y|Y|-|M|M|-|D|D|T|H|H|:|M|M|:|S|S|Z]
//
// Pattern #2: "YYYY-MM-DDTHH:MM:SS.sssZ"
// 1 2
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
// [Y|Y|Y|Y|-|M|M|-|D|D|T|H|H|:|M|M|:|S|S|.|s|s|s|Z]
// NOTE: The number of digits in the "sss" part is not defined.
//
// Pattern #3: "YYYY-MM-DDTHH:MM:SS+HH:MM"
// 1 2
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4
// [Y|Y|Y|Y|-|M|M|-|D|D|T|H|H|:|M|M|:|S|S|+|H|H|:|M|M]
//
// Pattern #4: "YYYY-MM-DDTHH:MM:SS.sss+HH:MM"
// 1 2
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8
// [Y|Y|Y|Y|-|M|M|-|D|D|T|H|H|:|M|M|:|S|S|.|s|s|s|+|H|H|:|M|M]
// NOTE: The number of digits in the "sss" part is not defined.
// NSDate format: "YYYY-MM-DD HH:MM:SS +HHMM".
NSCharacterSet *setOfT = [NSCharacterSet characterSetWithCharactersInString:@"tT"];
NSRange tMarkPos = [rfc3339 rangeOfCharacterFromSet:setOfT];
if (tMarkPos.location == NSNotFound) return nil;
// extract date and time part:
NSString *datePart = [rfc3339 substringToIndex:tMarkPos.location];
NSString *timePart = [rfc3339 substringWithRange:NSMakeRange(tMarkPos.location + tMarkPos.length, 8)];
NSString *restPart = [rfc3339 substringFromIndex:tMarkPos.location + tMarkPos.length + 8];
// extract time offset part:
NSString *tzSignPart, *tzHourPart, *tzMinPart;
NSCharacterSet *setOfZ = [NSCharacterSet characterSetWithCharactersInString:@"zZ"];
NSRange tzPos = [restPart rangeOfCharacterFromSet:setOfZ];
if (tzPos.location == NSNotFound) { // Pattern #3 or #4
NSCharacterSet *setOfSign = [NSCharacterSet characterSetWithCharactersInString:@"+-"];
NSRange tzSignPos = [restPart rangeOfCharacterFromSet:setOfSign];
if (tzSignPos.location == NSNotFound) return nil;
tzSignPart = [restPart substringWithRange:tzSignPos];
tzHourPart = [restPart substringWithRange:NSMakeRange(tzSignPos.location + tzSignPos.length, 2)];
tzMinPart = [restPart substringFromIndex:tzSignPos.location + tzSignPos.length + 2 + 1];
} else { // Pattern #1 or #2
// "Z" means UTC.
tzSignPart = @"+";
tzHourPart = @"00";
tzMinPart = @"00";
}
// construct a date string in the NSDate format
NSString *dateStr = [NSString stringWithFormat:@"%@ %@ %@%@%@", datePart, timePart, tzSignPart, tzHourPart, tzMinPart];
NSDate *dateObj = [[NSDate alloc] initWithString:dateStr];
[dateObj autorelease];
return dateObj;
}
void testGetDateObject(NSString *rfc3339)
{
NSLog(@"----------------------------------------");
NSLog(@"RFC3339 Format: %@", rfc3339);
NSDate *date = getDateObject(rfc3339);
NSLog(@"NSDate object: %@", date);
NSString *dateFormat = @"%Y-%m-%d %H:%M:%S %z";
NSString *dateInDefaultTZ = [date descriptionWithCalendarFormat:dateFormat timeZone:nil locale:nil];
NSLog(@"Default TZ: %@", dateInDefaultTZ);
NSTimeZone *aTimeZone = [NSTimeZone timeZoneWithName:@"UTC"];
NSString *dateInUTC = [date descriptionWithCalendarFormat:dateFormat timeZone:aTimeZone locale:nil];
NSLog(@"UTC: %@", dateInUTC);
}
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// insert code here...
testGetDateObject(@"2010-11-22T12:34:56Z");
testGetDateObject(@"2010-11-22t12:34:56z");
testGetDateObject(@"2010-11-22T12:34:56.123Z");
testGetDateObject(@"2010-11-22t12:34:56.123z");
testGetDateObject(@"2010-11-22T12:34:56+09:00");
testGetDateObject(@"2010-11-22t12:34:56+09:00");
testGetDateObject(@"2010-11-22T12:34:56.123+09:00");
testGetDateObject(@"2010-11-22t12.34.56.123+09:00");
testGetDateObject(@"2010-11-22T12:34:56-05:00");
testGetDateObject(@"2010-11-22T12:34:56.123-05:00");
[pool drain];
return 0;
}
この実装では 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
参考文献
荻原 剛志
ソフトバンククリエイティブ ( 2008-05-28 )
ISBN: 9784797346800
関連リンク
関連記事