ObjectiveResource + Ruby on Rails でおまじないを書かないと日付が正しく渡らない

iPhone用サーバーアプリは ObjectiveResource + Ruby on Rails で超簡単!!の中で書いた おまじない

[ObjectiveResourceDateFormatter setSerializeFormat:DateTime]; //  --  (2)

ですが、これを書かないとiPhone側で設定した日付の前日がRails側のデータベースに書かれてしまいます。その調査で1日近くかかってしまいました。TimeZone は日本でしか使われないアプリを書いているときには意識しませんが、世界中で使われるソフトである iPhoneRuby on Rails ではたまに意識しないといけません。

ObjectiveResourceにDEBUGオプションを付けてコンパイルすると送受信しているXMLがコンソールに表示されます。または、Rails側で受け取ったXMLを logger.debug(request.post_row) で書かせる事もできます。ここでは Rails側にのログに受け取ったXMLを表示できるようしてログを見ると

rocessing TodosController#update to xml (for 127.0.0.1 at 2010-02-02 00:39:43) [PUT]
  Parameters: {"id"=>"1", "todo"=>{"task"=>"打合せ", "due"=>Tue Feb 23 15:00:00 UTC 2010}}
--- <todo><due type="datetime">2010-02-24</due><task>打合せ</task></todo>     <--- 受け取ったXML
  Todo Load (0.2ms)   SELECT * FROM "todos" WHERE ("todos"."id" = 1) 
  Todo Update (0.6ms)   UPDATE "todos" SET "updated_at" = '2010-02-01 15:39:43', "due" = '2010-02-23 15:00:00' WHERE "id" = 1

2010-02-24日を受け取っているのに、なぜかSQL文をみると 日付は 2010-02-23日 です !!


これは、なぜかというと RailsXMLで日時 (datetime) を受け取った場合は UTC に変換してしまうからです。

以下は XMLを受け取る Railsのライブラリー ActiveSupport::CoreExtensions::Hash::Conversionsの一部です。

    "date"         => Proc.new  { |date|    ::Date.parse(date) },
    "datetime"     => Proc.new  { |time|    ::Time.parse(time).utc rescue ::DateTime.parse(time).utc },

datetime の場合、.utcUTCに変換しています。
よくよくログを見ると SQL の日時は 2010-02-23 15:00:00 でこれは、2010-02-24 00:00:00 の 9時間(日本の時差)前です。
XMLの日時データには時間もTimeZoneも無いので、現地時間である日本時間に解釈されているのです。


さて、ObjectiveResource内の日時(NSDate)を文字列に変換するのは ObjectiveResourceDateFormatter.m の以下のコードです。

static NSString *dateTimeFormatString = @"yyyy-MM-dd'T'HH:mm:ss'Z'";
static NSString *dateFormatString = @"yyyy-MM-dd";

 ・・・・

+ (NSString *)formatDate:(NSDate *)date {
	
	NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease];
	[formatter setFormatterBehavior:NSDateFormatterBehavior10_4];
	if(_dateFormat == Date) {
		[formatter setDateFormat:dateFormatString];
	}
	else {
		[formatter setDateFormat:dateTimeFormatString];		
	}
	[formatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"GMT"]];
	return [formatter stringFromDate:date];
}

ObjectiveResourceもRailsの通信時にはTimeZoneは GMTUTC に設定しています。しかも、日本時間で動いているiPhoneの場合、+9時間の時差も加わります。それなのになぜ Rails には -9時間された日時が渡るのでしょうか?

みなさんは、気が付かれたと思いますが XML上では時間やTimeZoneがありません !! 上のコードで _dateFormat は Date になっているようです。

もう少し、 ObjectiveResourceDateFormatter.h, ObjectiveResourceDateFormatter.m をみると

// ObjectiveResourceDateFormatter.h
typedef enum {
	Date = 0,
	DateTime,
} ORSDateFormat;

// ObjectiveResourceDateFormatter.m 
+ (void)setSerializeFormat:(ORSDateFormat)dateFormat {
	_dateFormat = dateFormat;
}

となっていて、明示的には _dateFormat が設定されていません(!?)ので 値は 0 ≡ Date です。


そこで、おまじないのコードを書くことで _dateFormat の値を DateTime に設定してあげると

Processing TodosController#update to xml (for 127.0.0.1 at 2010-02-02 01:04:09) [PUT]
  Parameters: {"id"=>"1", "todo"=>{"task"=>"打合せ", "due"=>Wed Feb 24 00:00:00 UTC 2010}}
--- <todo><due type="datetime">2010-02-24T00:00:00Z</due><task>打合せ</task></todo> <--- 受け取ったXML
  Todo Load (0.2ms)   SELECT * FROM "todos" WHERE ("todos"."id" = 1) 
  Todo Update (0.4ms)   UPDATE "todos" SET "updated_at" = '2010-02-01 16:04:09', "due" = '2010-02-24 00:00:00' WHERE "id" = 1

XMLの日時データの最後に Z があるのでUTC TimeZoneと解釈され UTCに変換しても -9時間されず。正しい日時が伝わります ^^)