So I seem to be getting burned by the iPhone's calendar support again. I've come across an issue with the way core data compares dates. Core data appears to be storing dates using the Gregorian calendar as they come out in a 20XX-MM-dd format. But when I build an NSPredicate this way
[nsRequest setPredicate:[NSPredicate predicateWithFormat:#"publish <= %# AND expires >= %#", [NSDate date], [NSDate date]]]
if the user's device isn't set to a Gregorian calendar then no items will appear, as core data doesn't seem to localizing it's NSDates before comparing against the input dates.
So it would appear that if the date were today '2011-05-31' in Japanese calendar mode core data is using '0023-05-31' since it's the 23rd year of the current emperor, placing the input date about 2000 years before what it's being compared against.
I've found I can work around this like so
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:[[NSLocale preferredLanguages] objectAtIndex:0]] autorelease]];
[dateFormat setDateFormat:#"yyyy-MM-dd HH:mm:ss ZZZ"];
NSString *todayGregorian = [[dateFormat stringFromDate:[NSDate date]] retain];
[dateFormat release];
NSDateFormatter *df = [[NSDateFormatter alloc] init];
[df setDateFormat:#"yyyy-MM-dd HH:mm:ss ZZZ"];
[nsRequest setPredicate:[NSPredicate predicateWithFormat:#"publish <= %# AND expires >= %#", [df dateFromString:todayGregorian], [df dateFromString:todayGregorian]]];
but I'm hoping that maybe I'm being an idiot and there's a better way. Any suggestions?
Please note:
Asking my Japanese users to change
the date format they use isn't an
option.
Also, I still need to store
the core data dates in a Gregorian
format, as these users do sometimes
change their calendars back and
forth.
Real problem
My solution above is wrong. TechZen's answer kicked my head in to gear and I wish I could up vote him 100 times, I ended up solving the issue.
My problem is data going in to Core Data is being sent by the server in GMT and the formmater parsing that data might not necessarily be in GMT so I need to adjust the time going in to Core Data correctly.
You really have no choice but to translate from NSDate to various calendars, especially if your users employ multiple calendars.
Calendar objects are not dates themselves but rather specialized formatters. All dates are actually stored in the GMT standard format regardless of the calendar they originated with. Then they are translated/formatted to the appropriate calendar as needed. We don't see this immediately in apps targeted towards Westerners because contemporary Western calendars are based on the GMT so no translation appears needed.
Date and time programming is deceptively complex. We don't intuitive feel it is complex because so much of the grunt work is usually handled for us. However, when you start doing something unusual, the complexity because readily apparent.
As a rule of thumb, the time to worry with data and time programming is not when the code seems to complex but when it seems to simple.
Related
I have a simple question but it is driving me nuts. I am using an API, and I get a JSon object back, and one of the fields is a date. The date is formatted like this: 2013-05-17T02:00:00.000Z. I cannot seem to get the NSDateformatter correct for this date. Would anyone be willing to give me a hand?
If it is not clear, I am using objective c for an iPhone app. My goal is to get a NSDate object at the end of this. Thanks again for your help.
That's one of the ISO 8601 formats. Parsing it with NSDateFormatter can be tricky at best, which is why I wrote an NSFormatter subclass specifically for parsing and unparsing any ISO 8601 format.
You'll want to get the correct format for that string.
Then you should be able to do something like:
NSString * dateString = jsonDict[#"dateKey"];
NSDateFormatter *df = [[NSDateFormatter alloc] init];
[df setDateFormat:#"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"]
NSDate *date = [df dateFromString:dateString];
The T is in there to separate date from time and the Z specifies Zulu time. (https://stackoverflow.com/a/8405125/1074558)
I have a strange query...
If the date/time is set automatically in my iPhone than the time is correctly displayed as seen below...
And if the date/time is set manually with a different time zone than I am actually in (I am in India and set it to London. Works fine if set to India),it shows wrong results only for two dates calculated by using the method [self.surveyModel.creationDate dateByAddingTimeInterval:60*60*24*30] and similarly for 20 days .. see image below..
I am not setting the default time zone or local time zone for the application, and I am suing the following method to show the result...
- (NSString *)stringWithFormat:(NSString *)format {
NSDateFormatter *outputFormatter = [[NSDateFormatter alloc] init];
[outputFormatter setTimeZone:[NSTimeZone systemTimeZone]];
[outputFormatter setDateFormat:format];
NSString *timestamp_str = [outputFormatter stringFromDate:self];
[outputFormatter release];
return timestamp_str;
}
what am I doing wrong here...?
NOTE: This answer is in reply to the Comment on the Question
It seem the error is caused by regions with Daylight Savings, in this case London, England.
When the date/time is calculated automatically, the iOS DateTime library takes the DayLight Saving into account.
When dealing with the DateTime manually, which you are doing through this code [self.surveyModel.creationDate dateByAddingTimeInterval:60*60*24*30]
You are setting the DateTime without listing the DayLight option enabled. So when you take the NSTimeZone look for the DayLight savings options.
Alright. Our application sends an NSString made out of the current user's username and todays date formatted in yyyyMMddHH. Our server, that is located in sweden, makes the exact same String and compares the two when it gets the call.
Now. We have realized, that if one of our users goes abroad, the timezone will change, resulting in complications.
if the iPhone user were to be in lets say, Seoul, South-korea. His NSString that is sent would be something like:
2011061718
Meanwhile, when our server gets the call, it will recreate its own datestring in this format because it's located in sweden.
2011061711
And therefore deny the user access to the functions on the server side.
To summarize:
How do i programmatically set a default static timezone in my application?
Atm we do this:
NSDate *aDate = [aDateFormatter stringFromDate:[NSDate date]];
And somehow we need the timezone and compare the difference between the user's actual timezone and change it to a swedish-timezone.
Any ideas?
EDIT: Ok. This application is only meant to be released in sweden. And we are using a combination of the user's username and the current date in the format of yyyyMMddHH to make a secure key, the key is meant to update itself whenever a new hour starts. The server, which is located in sweden in the timezone of GMT+1 makes a verification that the user is on an actual device using the application and not someone who has made a client of his own making soap-calls to our service.
Therefore, if one of our users goes outside the timezone, it will reject the user since the strings wont match.
This is why we want to set the default timezone for that function GMT+1 at all times. And this is what we're really looking for.
Thanks. Again.
If you need to make the client format the date, this is how to do it:
NSDate *now = [NSDate date];
NSCalendar *gregorianCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSTimeZone *timeZone = [NSTimeZone timeZoneWithName:#"Europe/Stockholm"]; // Sets the right time.
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:#"en_US_POSIX"]; // Forces the date formatter to accept any format string.
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setCalendar:gregorianCalendar];
[formatter setTimeZone:timeZone];
[formatter setDateFormat:#"yyyyMMddHH"];
[formatter setLocale:locale];
NSString *dateString = [formatter stringFromDate:now];
NSLog(#"Date: %#", dateString);
[locale release];
[gregorianCalendar release];
[formatter release];
The date formatter will do all the work for you, you just need to configure it.
By setting the gregorian calendar, you're using the same calendar as in Sweden.
By setting the timezone, you'll get the time as it would be in Sweden.
By setting the locale to "en_US_POSIX" you make the formatter use the exact format you specify, and not append any AM/PM stuff.
Don't mess about with that. Send complete date time as a string and let the server figure out the times.
NSString *dtString = [[NSDate date] description];
This will create a string with the format
YYYY-MM-DD HH:MM:SS ±HHMM
Use -[NSDateFormatter setTimeZone:]. NSDate has nothing to do with it as it does not care about time zones.
Btw, your code will also fail if the user uses a different calendar.
Also a heads up, if you turn the AM/PM function on the dateformatter will pring out 'AM/PM' even if you dont place it in the format string.
To fix this also add an NSLocale to the DateFormatter.
According to this site:
http://iosdevelopertips.com/cocoa/date-formatter-examples.html
there is a class that handles formatting, which takes in a set of constants/enums (e.g. NSDateFormatterShortStyle) to the "setDateStyle" property/method.
Somehow the NSDateFormatter knows to retrieve the proper locale-specific date format. What I want is to be able to retrieve the default formats based on the user's choice of region format. I have a feeling it is stored in NSLocale, but that does not seem to expose anything that will retrieve the format strings.
Is there a way to extract the formats? It has to be in memory somewhere; I'm hoping the retrieval mechanism is exposed somewhere.
I've looked in several places, but the only answers I get are a lesson on how to create an NSDate from a custom format.
NSDateFormatter *df = [[NSDateFormatter alloc] init];
[df setDateStyle:NSDateFormatterShortStyle];
NSString *dateFormat = [df dateFormat];
NSLog(#"Date format: %#", dateFormat);
[df release];
Just tested on OS X but this should also work in iOS.
For PDT, I would want "-0700".
I'm getting a date in the past to determine how long ago something happened.
NSDate *then = [NSDate dateWithString:#"1976-04-01 12:34:56 -0700"]; // Note the hard-coded time zone at the end
I'll be constructing the date string elsewhere but I don't know how to access the local time zone.
I read the Apple Dates and Times Programming Topics for Cocoa as well as the NSTimeZone and NSDate Class References but it's just too hard for me to put the information together. I could really use a few lines of code just to show how it's used.
Update: While struggling with this, I was writing code using a Command Line template so I could try things quickly. I just tried my previous code on iPhone and I'm getting NSDate may not respond to '+dateWithString:' Sorry if that added to the confusion, who knew Apple would change up such a basic class.
Use NSDateFormatter to build NSDate from a string:
NSDateFormatter *inputFormatter = [[NSDateFormatter alloc] init];
[inputFormatter setDateFormat:#"yyyy-MM-dd HH:mm:ss Z"];
NSDate *formatterDate;
formatterDate = [inputFormatter dateFromString:#"1976-04-01 12:34:56 -0700"];
NSString *dateString = [inputFormatter stringFromDate:formatterDate];
NSLog(#"date:%#", dateString);
This way you get the local time from string, for example the date specified by the string:
"1976-04-01 12:34:56 -0700"
is in time zone -0700, (I'm in time zone GMT +1000) so I get:
2009-11-17 22:13:46.480
cmdline[10593:903] date:1976-04-02
05:34:56 +1000
The time zone offset is dependent on the date in much of the world—those parts of it that use Daylight-Saving Time/Summer Time.
The only correct way is to generate the entire string from date and time-zone together. Use NSDateFormatter for this.
The best way is to probably use a simple calendar formatter
NSCalendarDate * date = [NSCalendarDate calendarDate];
[date setTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"PDT"]];
NSLog([date descriptionWithCalendarFormat:#"%z"]);
which will output '-0700'
or leave out the second line if you want the current time zone of the system (not sure which you were asking for)