Forgive me as I'm new to Objective C.
I am getting back dates from a .NET webservice in the /Date(xxxxxxxxxxxxx-xxxx)/ format. I'm looking for some direction on how to best parse this into an NSDate object. I've tried using dateWithTimeIntervalSince1970 on it but it comes back with a date in the year 1969 for a date I know is in 2006.
Looking for some direction on the proper way to handle JSON dates.
Thanks in advance!
I just wrote this for iOS 4.0+ (because it uses NSRegularExpression). It handles dates with or without timezone offsets. Seems to work pretty well, what do you think?
+ (NSDate *)mfDateFromDotNetJSONString:(NSString *)string {
static NSRegularExpression *dateRegEx = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dateRegEx = [[NSRegularExpression alloc] initWithPattern:#"^\\/date\\((-?\\d++)(?:([+-])(\\d{2})(\\d{2}))?\\)\\/$" options:NSRegularExpressionCaseInsensitive error:nil];
});
NSTextCheckingResult *regexResult = [dateRegEx firstMatchInString:string options:0 range:NSMakeRange(0, [string length])];
if (regexResult) {
// milliseconds
NSTimeInterval seconds = [[string substringWithRange:[regexResult rangeAtIndex:1]] doubleValue] / 1000.0;
// timezone offset
if ([regexResult rangeAtIndex:2].location != NSNotFound) {
NSString *sign = [string substringWithRange:[regexResult rangeAtIndex:2]];
// hours
seconds += [[NSString stringWithFormat:#"%#%#", sign, [string substringWithRange:[regexResult rangeAtIndex:3]]] doubleValue] * 60.0 * 60.0;
// minutes
seconds += [[NSString stringWithFormat:#"%#%#", sign, [string substringWithRange:[regexResult rangeAtIndex:4]]] doubleValue] * 60.0;
}
return [NSDate dateWithTimeIntervalSince1970:seconds];
}
return nil;
}
I was in the same boat whilst using json-framework which doesn't support the date format as it's not official JSON. My source is from an API built using JSON.Net. This is what I came up with:
- (NSDate*) getDateFromJSON:(NSString *)dateString
{
// Expect date in this format "/Date(1268123281843)/"
int startPos = [dateString rangeOfString:#"("].location+1;
int endPos = [dateString rangeOfString:#")"].location;
NSRange range = NSMakeRange(startPos,endPos-startPos);
unsigned long long milliseconds = [[dateString substringWithRange:range] longLongValue];
NSLog(#"%llu",milliseconds);
NSTimeInterval interval = milliseconds/1000;
return [NSDate dateWithTimeIntervalSince1970:interval];
}
I don't have the appended portion in the date format that you do so I haven't dealt with that like the answer above. No error catching either, it's all new to me at this point.
I actually found the snippet with NSRegularExpression pretty useful, till i came up with another solution that uses NSCharecterSet for stipping off the milliseconds.
+ (NSDate*) dateFromJSONString:(NSString *)dateString
{
NSCharacterSet *charactersToRemove = [[ NSCharacterSet decimalDigitCharacterSet ] invertedSet ];
NSString* milliseconds = [dateString stringByTrimmingCharactersInSet:charactersToRemove];
if (milliseconds != nil && ![milliseconds isEqualToString:#"62135596800000"]) {
NSTimeInterval seconds = [milliseconds doubleValue] / 1000;
return [NSDate dateWithTimeIntervalSince1970:seconds];
}
return nil;
}
Saves a lot of the manual string processing and makes the code much cleaner.
As a .NET programmer learning Objective-C I had the same problem when I tried to consume a .Net WebService.
At first I thought I would be able to use the NSDateFormatter...
I found a really good reference for it's symbols here, but I quickly realized that I needed to convert the number from milliseconds to seconds.
I wrote the code to do it...
I'm still learning Obj-C but I dont think It should've been this hard...
- (NSDate *) getJSONDate{
NSString* header = #"/Date(";
uint headerLength = [header length];
NSString* timestampString;
NSScanner* scanner = [[NSScanner alloc] initWithString:self];
[scanner setScanLocation:headerLength];
[scanner scanUpToString:#")" intoString:×tampString];
NSCharacterSet* timezoneDelimiter = [NSCharacterSet characterSetWithCharactersInString:#"+-"];
NSRange rangeOfTimezoneSymbol = [timestampString rangeOfCharacterFromSet:timezoneDelimiter];
[scanner dealloc];
if (rangeOfTimezoneSymbol.length!=0) {
scanner = [[NSScanner alloc] initWithString:timestampString];
NSRange rangeOfFirstNumber;
rangeOfFirstNumber.location = 0;
rangeOfFirstNumber.length = rangeOfTimezoneSymbol.location;
NSRange rangeOfSecondNumber;
rangeOfSecondNumber.location = rangeOfTimezoneSymbol.location + 1;
rangeOfSecondNumber.length = [timestampString length] - rangeOfSecondNumber.location;
NSString* firstNumberString = [timestampString substringWithRange:rangeOfFirstNumber];
NSString* secondNumberString = [timestampString substringWithRange:rangeOfSecondNumber];
unsigned long long firstNumber = [firstNumberString longLongValue];
uint secondNumber = [secondNumberString intValue];
NSTimeInterval interval = firstNumber/1000;
return [NSDate dateWithTimeIntervalSince1970:interval];
}
unsigned long long firstNumber = [timestampString longLongValue];
NSTimeInterval interval = firstNumber/1000;
return [NSDate dateWithTimeIntervalSince1970:interval];
}
Hopefully someone can provide a better Obj-C solution.
If not I may keep this or look for a way to change the serialization format in .NET
EDIT:
About that JSON DateTime format...
If you have any control on the service it would probably be best to convert the date to a string in your DataContract objects.
Formatting to RFC1123 seems like a good idea to me right now. As I can probably pick it up easily using a NSDateFormatter.
Quote from Rick Strahl
There's no JavaScript date literal and Microsoft engineered a custom date format that is essentially a marked up string. The format is a string that's encoded and contains the standard new Date(milliseconds since 1970) value.
Theory: MS encoded the C# DateTime in JSON as milliseconds since 1970.
Solution:
NSString*
dateAsString = #"/Date(1353720343336+0000)/";
dateAsString = [dateAsString stringByReplacingOccurrencesOfString:#"/Date("
withString:#""];
dateAsString = [dateAsString stringByReplacingOccurrencesOfString:#"+0000)/"
withString:#""];
unsigned long long milliseconds = [dateAsString longLongValue];
NSTimeInterval interval = milliseconds/1000;
NSDate* date = [NSDate dateWithTimeIntervalSince1970:interval];
This is the shortest solution I can think of.
Use an NSDateFormatter's dateFromString: method after setting the date format.
-(NSString*)convertToUTCTime:(NSString*)strDate{
NSDate *currentDate = [NSDate date];
myDate = [commonDateFormatter dateFromString: strDate];
NSTimeInterval distanceBetweenDates = [currentDate timeIntervalSinceDate:myDate];
return [self stringFromTimeInterval:distanceBetweenDates];
}
- (NSString *)stringFromTimeInterval:(NSTimeInterval)interval {
NSInteger ti = (NSInteger)interval;
NSInteger minutes = (ti / 60) % 60;
NSInteger hours = (ti / 3600);
if (hours > 24) {
NSInteger days = hours/24;
if (days > 30) {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"EEE d MMM, h:mm a"];
//[dateFormatter setTimeZone:[NSTimeZone timeZoneWithName:#"IST"]];
NSString *daydate = [dateFormatter stringFromDate:myDate];
return daydate;
}
else{
return [NSString stringWithFormat:#" %2ldd",(long)days];
}
}else{
if (hours == 0 && minutes < 1) {
return [NSString stringWithFormat:#"Today"];
}
else if (hours == 0 && minutes < 60){
return [NSString stringWithFormat:#"%2ldm ",(long)minutes];
}
else{
return [NSString stringWithFormat:#" %2ldh",(long)hours];
}
}
}
Related
Need to get the last modified time for IOS application inside the document folder with UTF time format?
Try following code, that will help
+ (NSString*) fnGetModifiedTime: (NSString*)inFolderPath {
NSFileManager* fm = [NSFileManager defaultManager];
NSDictionary* attrs = [fm attributesOfItemAtPath:inFolderPath error:nil];
int theDirModified = 0;
if (attrs != nil) {
NSDate *date = (NSDate*)[attrs objectForKey: NSFileModificationDate];
NSDate* theGlobalDate = [self toGlobalTime:date];
theDirModified = [theGlobalDate timeIntervalSince1970];
}
return [NSString stringWithFormat:#"%i",theDirModified];
}
+(NSDate *) toGlobalTime:(NSDate*) inDate
{
NSTimeZone *tz = [NSTimeZone defaultTimeZone];
NSInteger seconds = -[tz secondsFromGMTForDate: inDate];
return [NSDate dateWithTimeInterval: seconds sinceDate: inDate];
}
The application on which in am currently working is an iPhone app and the backend is in .Net. The iPhone application consumes WCF RestFul API. The database that the server is using is MS SQL and the Client-side in objective-c(xcode4.5,iOS6). I store date in the form of smallDateTime on server-side. On getting this date back in the JSON format, i convert it to NSDate with the below mentioned code.Following is a sample date string that i receive from the server (serverDate=/Date(1356527635798+0500)/).
Following is iPhone side code for conversion.
+ (NSDate *)convertServerDateToNSDate:(NSString *)serverDate
{
serverDate = [serverDate stringByReplacingOccurrencesOfString:#"\"" withString:#""];
serverDate = [serverDate stringByReplacingOccurrencesOfString:#"\\" withString:#""];
if(serverDate == nil)
return nil;
NSDate * nsDate;
NSCharacterSet * tempSet1 = [NSCharacterSet characterSetWithCharactersInString:#"("];
serverDate = [serverDate stringByReplacingOccurrencesOfString:#"-" withString:#"+"];
NSCharacterSet * tempSet2 = [NSCharacterSet characterSetWithCharactersInString:#"+"];
NSRange startRange = [serverDate rangeOfCharacterFromSet:tempSet1];
NSRange endRange = [serverDate rangeOfCharacterFromSet:tempSet2];
NSRange actualRange;
actualRange.location = startRange.location + 1;
actualRange.length = endRange.location - actualRange.location;
NSString * tempString = [serverDate substringWithRange:actualRange];
double timeInSecs = [tempString doubleValue]/1000;
nsDate = [NSDate dateWithTimeIntervalSince1970:timeInSecs];
return nsDate;
}
The Date conversion is perfect on iOS-Simulator 6.0, But date is converted one day less on my ipod. I tried to search it and do it myself but everytime i am getting 1 day less than the actually date.
1) What is the problem ?
2) How can i resolve it ?
3) I ensured that locale is same both on simulator and on iPod.
Any help in this will be much appreciated.
I have 2 text fields where users can insert 2 time (ie. 12.00pm and 15.00pm) and a label that return the hours (ie 3 hours). I'm using timeIntervalSinceDate but I'm getting it in seconds (10800 sec which is equal to 3 hours). How can I get the value in hours? here the code:
-(IBAction)calcoloBlockTime{
NSString *blockOff = [NSString stringWithFormat:#"%#", [offBlock text]];
NSString *blockIn= [NSString stringWithFormat: #"%#", [inBlock text]];
NSDateFormatter *dateFormatter =[[NSDateFormatter alloc]init];
[dateFormatter setDateFormat:#"HHmm"];
NSDate *dateFromString = [dateFormatter dateFromString:blockOff];
NSDate *dateFromString2 = [dateFormatter dateFromString:blockIn];
NSLog(#"Time elapsed: %f", [dateFromString2 timeIntervalSinceDate:dateFromString]);
}
thanks in advance.
If you are only interested in hours you can write:
NSInteger hours = 10800 / 3600; // You know that in an hour there is 3600 seconds
If you want minutes etc you will continue with the result of 10800 modulus 3600, which will give you the seconds which are left after dividing it by 3600.
NSInteger temp = 10800 % 3600;
NSInteger minutes = temp / 60;
And to get the seconds left after this you write:
NSInteger seconds = minutes % 60;
EDIT
myLabel.text = [NSString stringWithFormat:#"%d hours", hours];
If you want to set the hours on the label.
hours = seconds / 3600; Simple!!! And, it seems there is no way to get the hour from NSDate. You can use NSDateComponents, but they are too costly for your requirement.
I made a class method that returns a string with a formatted date of the remaining time between an NSDate and now.
+(NSString *)TimeRemainingUntilDate:(NSDate *)date {
NSTimeInterval interval = [date timeIntervalSinceNow];
NSString * timeRemaining = nil;
if (interval > 0) {
div_t d = div(interval, 86400);
int day = d.quot;
div_t h = div(d.rem, 3600);
int hour = h.quot;
div_t m = div(h.rem, 60);
int min = m.quot;
NSString * nbday = nil;
if(day > 1)
nbday = #"days";
else if(day == 1)
nbday = #"day";
else
nbday = #"";
NSString * nbhour = nil;
if(hour > 1)
nbhour = #"hours";
else if (hour == 1)
nbhour = #"hour";
else
nbhour = #"";
NSString * nbmin = nil;
if(min > 1)
nbmin = #"mins";
else
nbmin = #"min";
timeRemaining = [NSString stringWithFormat:#"%#%# %#%# %#%#",day ? [NSNumber numberWithInt:day] : #"",nbday,hour ? [NSNumber numberWithInt:hour] : #"",nbhour,min ? [NSNumber numberWithInt:min] : #"",nbmin];
}
else
timeRemaining = #"Over";
return timeRemaining;
}
I have the following code. This is getting calculated in cellForRowAtIndexPath, so I think it could be a little expensive. Is there a better way to calculate a time period?
+(NSString*)toShortTimeIntervalString:(NSString*)sDate
{
NSDateFormatter* df = [[NSDateFormatter alloc]init];
[df setDateFormat:#"yyyy-MM-dd'T'HH:mm:ssZ"];
NSDate* date = [df dateFromString:[sDate stringByReplacingOccurrencesOfString:#"Z" withString:#"-0000"]];
[df release];
NSDate* today = [[NSDate alloc] init];
NSDate *d = date; //[_twitter_dateFormatter dateFromString:sDate];
NSTimeInterval interval = [today timeIntervalSinceDate:d];
[today release];
//TODO: added ABS wrapper
double res = 0;
NSString* result;
if(interval > SECONDS_IN_WEEK)
{
res = fabs(interval / SECONDS_IN_WEEK);
result = [NSString stringWithFormat:#"%1.0fw ago", res];
}
else if(interval > SECONDS_IN_DAY)
{
res = fabs(interval / SECONDS_IN_DAY);
result = [NSString stringWithFormat:#"%1.0fd ago", res];
}
else if (interval > SECONDS_IN_HOUR){
res = fabs(interval / SECONDS_IN_HOUR);
result = [NSString stringWithFormat:#"%1.0fh ago", res];
}
else if (interval > SECONDS_IN_MIN) {
res = fabs(interval / SECONDS_IN_MIN);
result = [NSString stringWithFormat:#"%1.0fm ago", res];
}
else
{
interval = fabs(interval);
result = [NSString stringWithFormat:#"%1.0fs ago", interval];
}
return result;
}
This doesn't look too bad, and I doubt you'll be seeing too much of a performance hit from it. To make it more efficient, you might consider only creating one NSDataFormatter (saving it in an instance variable or static variable) and reusing it. Or even better, if you could convert everything to NSDates beforehand, then you wouldn't have to use the formatter every time.
Are you actually seeing any performance issues here, though? Before you try and optimize, you should use Instruments to investigate what's actually taking up time.
Not really, but there are a few categories out there which encapsulate that logic and make it a one line call for you.
Recently I've been in a situation to perform complex calculation (plotting graph for series of years), and I was using NSDate within loops. After analyze with time profiler NSDate, NSDateFormater, NSCalendar was the root cause. I switched to use the C type (time_t + timeinfo) and it drastically improves the speed.
Although they are much faster, they have less functionality, if you need to deal with TimeZone etc, NSDate is probably easier to deal with.
How do you get a datetime column in SQLite with Objective C?
I have a table with 4 fields: pk, datetime, value1 and value2. pk (primary key), value1 and value2 are integers so I am using:
int value1 = sqlite3_column_int(statement, 2);
int value1 = sqlite3_column_int(statement, 3);
But what should I use for datetime?
In SQLite, there is no date/time column type per se, so one ends up representing dates either as Julian date values (real columns) or in strings (text columns). SQLite is also very particular in how dates are represented in strings, yyyy-MM-dd HH:mm:ss (only).
These are some methods that I wrote for working with SQLite dates from Objective-C. These methods are implemented in a category on NSDate.
Be sure to check out the functionality that SQLite offers for working with Julian dates. I have found these to be quite useful (http://www.sqlite.org/lang_datefunc.html). A function for deriving an NSDate's julianDay is included in the code example.
It looks like this subject was also covered here.
Persisting Dates to SQLite3 in an iPhone Application
+ (NSDate *) dateWithSQLiteRepresentation: (NSString *) myString;
{
NSAssert3(myString, #"%s: %d; %s; Invalid argument. myString == nil", __FILE__, __LINE__, __PRETTY_FUNCTION__);
return [[self sqlLiteDateFormatter] dateFromString: myString];
}
+ (NSDate *) dateWithSQLiteRepresentation: (NSString *) myString timeZone: (NSString *) myTimeZone;
{
NSString * dateWithTimezone = nil;
NSDate * result = nil;
NSAssert3(myString, #"%s: %d; %s; Invalid argument. myString == nil", __FILE__, __LINE__, __PRETTY_FUNCTION__);
NSAssert3(myTimeZone, #"%s: %d; %s; Invalid argument. myTimeZone == nil", __FILE__, __LINE__, __PRETTY_FUNCTION__);
dateWithTimezone = [[NSString alloc] initWithFormat: #"%# %#", myString, myTimeZone];
result = [[self sqlLiteDateFormatterWithTimezone] dateFromString: dateWithTimezone];
[dateWithTimezone release];
return result;
}
+ (NSString *) sqlLiteDateFormat;
{
return #"yyyy-MM-dd HH:mm:ss";
}
+ (NSString *) sqlLiteDateFormatWithTimeZone;
{
static NSString * result = nil;
if (!result) {
result = [[NSString alloc] initWithFormat: #"%# zzz", [self sqlLiteDateFormat]];
}
return result;
}
+ (NSDateFormatter *) sqlLiteDateFormatter;
{
static NSDateFormatter * _result = nil;
if (!_result) {
_result = [[NSDateFormatter alloc] init];
[_result setDateFormat: [self sqlLiteDateFormat]];
}
return _result;
}
+ (NSDateFormatter *) sqlLiteDateFormatterWithTimezone;
{
static NSDateFormatter * _result = nil;
if (!_result) {
_result = [[NSDateFormatter alloc] init];
[_result setDateFormat: [self sqlLiteDateFormatWithTimeZone]];
}
return _result;
}
- (NSString *) sqlLiteDateRepresentation;
{
NSString * result = nil;
result = [[NSDate sqlLiteDateFormatter] stringFromDate: self];
return result;
}
- (NSTimeInterval) unixTime;
{
NSTimeInterval result = [self timeIntervalSince1970];
return result;
}
#define SECONDS_PER_DAY 86400
#define JULIAN_DAY_OF_ZERO_UNIX_TIME 2440587.5
- (NSTimeInterval) julianDay;
{
return [self unixTime]/SECONDS_PER_DAY + JULIAN_DAY_OF_ZERO_UNIX_TIME;
}
+ (NSDate *) dateWithJulianDay: (NSTimeInterval) myTimeInterval
{
NSDate *result = [self dateWithTimeIntervalSince1970: (myTimeInterval - JULIAN_DAY_OF_ZERO_UNIX_TIME) * SECONDS_PER_DAY];
return result;
}
If you can define the database, then ou could also use REAL (SQLite data type) as the type for the datetime, then load it with sqlite3_column_double(). This will return a variable of the type double.
Then you can use [NSDate dateWithTimeIntervalSince1970:double_value] to get an NSDate object.
Please note that the category solution above has a problem in that it is subject to the locale settings on the user's device. For example, for midnight April 5th 2010 sqlLiteDateRepresentation above would return 2010/04/05 00:00:00 for most people's machines, however I have encountered a scenario where a user's locale settings caused the same function to produce "2010/04/05 12:00:00 a.m." which when used in my query does not return any rows. This seems to follow from the documentation of NSDateFormatter: "In general, you are encouraged to use format styles (see timeStyle, dateStyle, and NSDateFormatterStyle) rather than using custom format strings, since the format for a given style reflects a user’s preferences. Format styles also reflect the locale setting." Although I didn't see a good way to use the timeStyle/dateStyle to get the same format as yyyy/MM/dd HH:mm:ss that SQLite seems to need. I fear your best bet is likely a custom solution where you ensure that the time is definitely written in 24H format, don't allow locale settings to cause bugs.
If you just want to get an NSDate from an SQLite datetime field, where dateTime is the field returned from the database:
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
NSDate *date = [dateFormatter dateFromString:dateTime];
Now Work!
NSString *dateValueS = [[NSString alloc]
initWithUTF8String:(char*) sqlite3_column_text(statement,2)];