iPhone: Convert date string to a relative time stamp - iphone

I've got a timestamp as a string like:
Thu, 21 May 09 19:10:09 -0700
and I'd like to convert it to a relative time stamp like '20 minutes ago' or '3 days ago'.
What's the best way to do this using Objective-C for the iPhone?

-(NSString *)dateDiff:(NSString *)origDate {
NSDateFormatter *df = [[NSDateFormatter alloc] init];
[df setFormatterBehavior:NSDateFormatterBehavior10_4];
[df setDateFormat:#"EEE, dd MMM yy HH:mm:ss VVVV"];
NSDate *convertedDate = [df dateFromString:origDate];
[df release];
NSDate *todayDate = [NSDate date];
double ti = [convertedDate timeIntervalSinceDate:todayDate];
ti = ti * -1;
if(ti < 1) {
return #"never";
} else if (ti < 60) {
return #"less than a minute ago";
} else if (ti < 3600) {
int diff = round(ti / 60);
return [NSString stringWithFormat:#"%d minutes ago", diff];
} else if (ti < 86400) {
int diff = round(ti / 60 / 60);
return[NSString stringWithFormat:#"%d hours ago", diff];
} else if (ti < 2629743) {
int diff = round(ti / 60 / 60 / 24);
return[NSString stringWithFormat:#"%d days ago", diff];
} else {
return #"never";
}
}

Here are methods from Cocoa to help you to get relevant info (not sure if they are all available in coca-touch).
NSDate * today = [NSDate date];
NSLog(#"today: %#", today);
NSString * str = #"Thu, 21 May 09 19:10:09 -0700";
NSDate * past = [NSDate dateWithNaturalLanguageString:str
locale:[[NSUserDefaults
standardUserDefaults] dictionaryRepresentation]];
NSLog(#"str: %#", str);
NSLog(#"past: %#", past);
NSCalendar *gregorian = [[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];
unsigned int unitFlags = NSYearCalendarUnit | NSMonthCalendarUnit |
NSDayCalendarUnit |
NSHourCalendarUnit | NSMinuteCalendarUnit |
NSSecondCalendarUnit;
NSDateComponents *components = [gregorian components:unitFlags
fromDate:past
toDate:today
options:0];
NSLog(#"months: %d", [components month]);
NSLog(#"days: %d", [components day]);
NSLog(#"hours: %d", [components hour]);
NSLog(#"seconds: %d", [components second]);
The NSDateComponents object seems to hold the difference in relevant units (as specified).
If you specify all units you can then use this method:
void dump(NSDateComponents * t)
{
if ([t year]) NSLog(#"%d years ago", [t year]);
else if ([t month]) NSLog(#"%d months ago", [t month]);
else if ([t day]) NSLog(#"%d days ago", [t day]);
else if ([t minute]) NSLog(#"%d minutes ago", [t minute]);
else if ([t second]) NSLog(#"%d seconds ago", [t second]);
}
If you want to calculate yourself you can have a look at:
NSDate timeIntervalSinceDate
And then use seconds in the algorithm.
Disclaimer: If this interface is getting deprecated (I haven't checked), Apple's preferred way of doing this via NSDateFormatters, as suggested in comments below, looks pretty neat as well - I'll keep my answer for historical reasons, it may still be useful for some to look at the logic used.

I can't edit yet, but I took Gilean's code and made a couple of tweaks and made it a category of NSDateFormatter.
It accepts a format string so it will work w/ arbitrary strings and I added if clauses to have singular events be grammatically correct.
Cheers,
Carl C-M
#interface NSDateFormatter (Extras)
+ (NSString *)dateDifferenceStringFromString:(NSString *)dateString
withFormat:(NSString *)dateFormat;
#end
#implementation NSDateFormatter (Extras)
+ (NSString *)dateDifferenceStringFromString:(NSString *)dateString
withFormat:(NSString *)dateFormat
{
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
[dateFormatter setDateFormat:dateFormat];
NSDate *date = [dateFormatter dateFromString:dateString];
[dateFormatter release];
NSDate *now = [NSDate date];
double time = [date timeIntervalSinceDate:now];
time *= -1;
if(time < 1) {
return dateString;
} else if (time < 60) {
return #"less than a minute ago";
} else if (time < 3600) {
int diff = round(time / 60);
if (diff == 1)
return [NSString stringWithFormat:#"1 minute ago", diff];
return [NSString stringWithFormat:#"%d minutes ago", diff];
} else if (time < 86400) {
int diff = round(time / 60 / 60);
if (diff == 1)
return [NSString stringWithFormat:#"1 hour ago", diff];
return [NSString stringWithFormat:#"%d hours ago", diff];
} else if (time < 604800) {
int diff = round(time / 60 / 60 / 24);
if (diff == 1)
return [NSString stringWithFormat:#"yesterday", diff];
if (diff == 7)
return [NSString stringWithFormat:#"last week", diff];
return[NSString stringWithFormat:#"%d days ago", diff];
} else {
int diff = round(time / 60 / 60 / 24 / 7);
if (diff == 1)
return [NSString stringWithFormat:#"last week", diff];
return [NSString stringWithFormat:#"%d weeks ago", diff];
}
}
#end

In the interest of completeness, based on a #Gilean's answer, here's the complete code for a simple category on NSDate that mimics rails' nifty date helpers. For a refresher on categories, these are instance methods that you would call on NSDate objects. So, if I have an NSDate that represents yesterday, [myDate distanceOfTimeInWordsToNow] => "1 day".
Hope it's useful!
#interface NSDate (NSDate_Relativity)
-(NSString *)distanceOfTimeInWordsSinceDate:(NSDate *)aDate;
-(NSString *)distanceOfTimeInWordsToNow;
#end
#implementation NSDate (NSDate_Relativity)
-(NSString *)distanceOfTimeInWordsToNow {
return [self distanceOfTimeInWordsSinceDate:[NSDate date]];
}
-(NSString *)distanceOfTimeInWordsSinceDate:(NSDate *)aDate {
double interval = [self timeIntervalSinceDate:aDate];
NSString *timeUnit;
int timeValue;
if (interval < 0) {
interval = interval * -1;
}
if (interval< 60) {
return #"seconds";
} else if (interval< 3600) { // minutes
timeValue = round(interval / 60);
if (timeValue == 1) {
timeUnit = #"minute";
} else {
timeUnit = #"minutes";
}
} else if (interval< 86400) {
timeValue = round(interval / 60 / 60);
if (timeValue == 1) {
timeUnit = #"hour";
} else {
timeUnit = #"hours";
}
} else if (interval< 2629743) {
int days = round(interval / 60 / 60 / 24);
if (days < 7) {
timeValue = days;
if (timeValue == 1) {
timeUnit = #"day";
} else {
timeUnit = #"days";
}
} else if (days < 30) {
int weeks = days / 7;
timeValue = weeks;
if (timeValue == 1) {
timeUnit = #"week";
} else {
timeUnit = #"weeks";
}
} else if (days < 365) {
int months = days / 30;
timeValue = months;
if (timeValue == 1) {
timeUnit = #"month";
} else {
timeUnit = #"months";
}
} else if (days < 30000) { // this is roughly 82 years. After that, we'll say 'forever'
int years = days / 365;
timeValue = years;
if (timeValue == 1) {
timeUnit = #"year";
} else {
timeUnit = #"years";
}
} else {
return #"forever ago";
}
}
return [NSString stringWithFormat:#"%d %#", timeValue, timeUnit];
}
#end

There are already a lot of answers that come to the same solution but it can't hurt to have choices. Here's what I came up with.
- (NSString *)stringForTimeIntervalSinceCreated:(NSDate *)dateTime
{
NSDictionary *timeScale = #{#"second":#1,
#"minute":#60,
#"hour":#3600,
#"day":#86400,
#"week":#605800,
#"month":#2629743,
#"year":#31556926};
NSString *scale;
int timeAgo = 0-(int)[dateTime timeIntervalSinceNow];
if (timeAgo < 60) {
scale = #"second";
} else if (timeAgo < 3600) {
scale = #"minute";
} else if (timeAgo < 86400) {
scale = #"hour";
} else if (timeAgo < 605800) {
scale = #"day";
} else if (timeAgo < 2629743) {
scale = #"week";
} else if (timeAgo < 31556926) {
scale = #"month";
} else {
scale = #"year";
}
timeAgo = timeAgo/[[timeScale objectForKey:scale] integerValue];
NSString *s = #"";
if (timeAgo > 1) {
s = #"s";
}
return [NSString stringWithFormat:#"%d %#%# ago", timeAgo, scale, s];
}

I took Carl Coryell-Martin's code and made a simpler NSDate category that doesn't have warnings about the string formatting of the singulars, and also tidys up the week ago singular:
#interface NSDate (Extras)
- (NSString *)differenceString;
#end
#implementation NSDate (Extras)
- (NSString *)differenceString{
NSDate* date = self;
NSDate *now = [NSDate date];
double time = [date timeIntervalSinceDate:now];
time *= -1;
if (time < 60) {
int diff = round(time);
if (diff == 1)
return #"1 second ago";
return [NSString stringWithFormat:#"%d seconds ago", diff];
} else if (time < 3600) {
int diff = round(time / 60);
if (diff == 1)
return #"1 minute ago";
return [NSString stringWithFormat:#"%d minutes ago", diff];
} else if (time < 86400) {
int diff = round(time / 60 / 60);
if (diff == 1)
return #"1 hour ago";
return [NSString stringWithFormat:#"%d hours ago", diff];
} else if (time < 604800) {
int diff = round(time / 60 / 60 / 24);
if (diff == 1)
return #"yesterday";
if (diff == 7)
return #"a week ago";
return[NSString stringWithFormat:#"%d days ago", diff];
} else {
int diff = round(time / 60 / 60 / 24 / 7);
if (diff == 1)
return #"a week ago";
return [NSString stringWithFormat:#"%d weeks ago", diff];
}
}
#end

In Swift
Usage:
let time = NSDate(timeIntervalSince1970: timestamp).timeIntervalSinceNow
let relativeTimeString = NSDate.relativeTimeInString(time)
println(relativeTimeString)
Extension:
extension NSDate {
class func relativeTimeInString(value: NSTimeInterval) -> String {
func getTimeData(value: NSTimeInterval) -> (count: Int, suffix: String) {
let count = Int(floor(value))
let suffix = count != 1 ? "s" : ""
return (count: count, suffix: suffix)
}
let value = -value
switch value {
case 0...15: return "just now"
case 0..<60:
let timeData = getTimeData(value)
return "\(timeData.count) second\(timeData.suffix) ago"
case 0..<3600:
let timeData = getTimeData(value/60)
return "\(timeData.count) minute\(timeData.suffix) ago"
case 0..<86400:
let timeData = getTimeData(value/3600)
return "\(timeData.count) hour\(timeData.suffix) ago"
case 0..<604800:
let timeData = getTimeData(value/86400)
return "\(timeData.count) day\(timeData.suffix) ago"
default:
let timeData = getTimeData(value/604800)
return "\(timeData.count) week\(timeData.suffix) ago"
}
}
}

Use the NSDate class:
timeIntervalSinceDate
returns the interval in seconds.
Quick exercise to implement this in objective-c:
Get time "now" NSDate
Get the NSDate you wish to compare with
Get the interval in seconds using timeIntervalSinceDate
Then implement this pseudo code:
if (x < 60) // x seconds ago
else if( x/60 < 60) // floor(x/60) minutes ago
else if (x/(60*60) < 24) // floor(x/(60*60) hours ago
else if (x/(24*60*60) < 7) // floor(x(24*60*60) days ago
and so on...
then you need to decide whether a month is 30,31 or 28 days. Keep it simple - pick 30.
There might be a better way, but its 2am and this is the first thing that came to mind...

My solution:
- (NSString *) dateToName:(NSDate*)dt withSec:(BOOL)sec {
NSLocale *locale = [NSLocale currentLocale];
NSTimeInterval tI = [[NSDate date] timeIntervalSinceDate:dt];
if (tI < 60) {
if (sec == NO) {
return NSLocalizedString(#"Just Now", #"");
}
return [NSString stringWithFormat:
NSLocalizedString(#"%d seconds ago", #""),(int)tI];
}
if (tI < 3600) {
return [NSString stringWithFormat:
NSLocalizedString(#"%d minutes ago", #""),(int)(tI/60)];
}
if (tI < 86400) {
return [NSString stringWithFormat:
NSLocalizedString(#"%d hours ago", #""),(int)tI/3600];
}
NSDateFormatter *relativeDateFormatter = [[NSDateFormatter alloc] init];
[relativeDateFormatter setTimeStyle:NSDateFormatterNoStyle];
[relativeDateFormatter setDateStyle:NSDateFormatterMediumStyle];
[relativeDateFormatter setDoesRelativeDateFormatting:YES];
[relativeDateFormatter setLocale:locale];
NSString * relativeFormattedString =
[relativeDateFormatter stringForObjectValue:dt];
return relativeFormattedString;
}

Not sure why this isnt in cocoa-touch, i nice standard way of doing this would be great.
Set up some types to keep the data in, it will make it easier if you ever ned to localise it a bit more. (obviously expand if you need more time periods)
typedef struct DayHours {
int Days;
double Hours;
} DayHours;
+ (DayHours) getHourBasedTimeInterval:(double) hourBased withHoursPerDay:(double) hpd
{
int NumberOfDays = (int)(fabs(hourBased) / hpd);
float hoursegment = fabs(hourBased) - (NumberOfDays * hpd);
DayHours dh;
dh.Days = NumberOfDays;
dh.Hours = hoursegment;
return dh;
}
NOTE: I"m using an hour based calculation , as that is what my data is in. NSTimeInterval is second based. I also had to convert between the two.

I saw that there were several time ago functions in snippets of code on Stack Overflow and I wanted one that really gave the clearest sense of the time (since some action occurred). To me this means "time ago" style for short time intervals (5 min ago, 2 hours ago) and specific dates for longer time periods (April 15, 2011 instead of 2 years ago). Basically I thought Facebook did a really good job at this and I wanted to just go by their example (as I'm sure they out a lot of thought into this and it is very easy and clear to understand from the consumer perspective).
After a long time of googling I was pretty surprised to see that no one had implemented this as far as I could tell. Decided that I wanted it bad enough to spend the time writing and thought that I would share.
Hope you enjoy :)
Get the code here: https://github.com/nikilster/NSDate-Time-Ago

Related

NSDate - timeIntevalSinceDate -

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;
}

Smart Formatting of time span

I need a method to format a NSTimeInterval (time span in seconds) into a string to produce something like "about 10 minutes ago", "1h, 20min", or "less than 1 minute".
-(NSString*) formattedTimeSpan:(NSTimeInterval)interval;
Target platform is iOS. Sample code is welcome.
This is a category for NSDate. It's not exactly using an NSTimeInterval, well internally :) I assume you are working with timestamps.
Header file NSDate+PrettyDate.h
#interface NSDate (PrettyDate)
- (NSString *)prettyDate;
#end
Implementation NSDate+PrettyDate.m
#implementation NSDate (PrettyDate)
- (NSString *)prettyDate
{
NSString * prettyTimestamp;
float delta = [self timeIntervalSinceNow] * -1;
if (delta < 60) {
prettyTimestamp = #"just now";
} else if (delta < 120) {
prettyTimestamp = #"one minute ago";
} else if (delta < 3600) {
prettyTimestamp = [NSString stringWithFormat:#"%d minutes ago", (int) floor(delta/60.0) ];
} else if (delta < 7200) {
prettyTimestamp = #"one hour ago";
} else if (delta < 86400) {
prettyTimestamp = [NSString stringWithFormat:#"%d hours ago", (int) floor(delta/3600.0) ];
} else if (delta < ( 86400 * 2 ) ) {
prettyTimestamp = #"one day ago";
} else if (delta < ( 86400 * 7 ) ) {
prettyTimestamp = [NSString stringWithFormat:#"%d days ago", (int) floor(delta/86400.0) ];
} else {
NSDateFormatter * formatter = [[NSDateFormatter alloc] init];
[formatter setDateStyle:NSDateFormatterMediumStyle];
prettyTimestamp = [NSString stringWithFormat:#"on %#", [formatter stringFromDate:self]];
[formatter release];
}
return prettyTimestamp;
}
You might want to refer to Facebook three20 framework. In their NSDateAdditions, they provided a few pretty formats for date. It might be better than you extend it.
Refer to the source at https://github.com/facebook/three20/blob/master/src/Three20Core/Sources/NSDateAdditions.m
- (NSString*)formatShortRelativeTime; will give you "<1m", "50m", "3h", "3d"
Here is one that formats the date in the style of Facebook:
https://github.com/nikilster/NSDate-Time-Ago

How do I convert a number to hours and minutes?

I got help with correct calculation the other day but I've hit a block about how to implement it into code.
-(IBAction)done:(id)sender {
int result = (([startHours.text intValue] * 60) + [startMinutes.text intValue]) -
(([finishHours.text intValue] * 60) + [finishMinutes.text intValue]);
totalHours.text = [NSString stringWithFormat:#"%i", result / 60];
if (result < 0) {
totalHours.text = [NSString stringWithFormat:#"%d", result * -1];
}
The above is the code. However, in the text field it comes out as the total number of minutes. I want to covert it so it would show up as total hours and minutes (10.30). How would I do that in code?
If result is a time in minutes:
int minutes = result%60
int hours = (result - minutes)/60
totalHours.text = [NSString stringWithFormat:#"%d.%d", hours, minutes];
Pheelicks's answer is OK. Just two small additions: handle sign of interval before calculating parts, and use more common time format:
int result = (([startHours.text intValue] * 60) + [startMinutes.text intValue]) -
(([finishHours.text intValue] * 60) + [finishMinutes.text intValue]);
int minutes = abs(result)%60
int hours = (abs(result) - minutes)/60
totalHours.text = [NSString stringWithFormat:#"%02d:%02d", hours, minutes];
Try changing the int to a float.
-(IBAction)done:(id)sender {
float result = (([startHours.text intValue] * 60) + [startMinutes.text intValue]) -
(([finishHours.text intValue] * 60) + [finishMinutes.text intValue]);
totalHours.text = [NSString stringWithFormat:#"%2.2f", result / 60];
if (result < 0) {
totalHours.text = [NSString stringWithFormat:#"%2.2f", result * -1];
}

Parsing JSON dates on IPhone

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:&timestampString];
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];
}
}
}

Fuzzy Date algorithm in Objective-C

I would like to write a fuzzy date method for calculating dates in Objective-C for iPhone. There is a popular explanation here:
Calculate relative time in C#
However it contains missing arguments. How could this be used in Objective-C?. Thanks.
const int SECOND = 1;
const int MINUTE = 60 * SECOND;
const int HOUR = 60 * MINUTE;
const int DAY = 24 * HOUR;
const int MONTH = 30 * DAY;
if (delta < 1 * MINUTE)
{
return ts.Seconds == 1 ? "one second ago" : ts.Seconds + " seconds ago";
}
if (delta < 2 * MINUTE)
{
return "a minute ago";
}
if (delta < 45 * MINUTE)
{
return ts.Minutes + " minutes ago";
}
if (delta < 90 * MINUTE)
{
return "an hour ago";
}
if (delta < 24 * HOUR)
{
return ts.Hours + " hours ago";
}
if (delta < 48 * HOUR)
{
return "yesterday";
}
if (delta < 30 * DAY)
{
return ts.Days + " days ago";
}
if (delta < 12 * MONTH)
{
int months = Convert.ToInt32(Math.Floor((double)ts.Days / 30));
return months <= 1 ? "one month ago" : months + " months ago";
}
else
{
int years = Convert.ToInt32(Math.Floor((double)ts.Days / 365));
return years <= 1 ? "one year ago" : years + " years ago";
}
Dates are represented in Cocoa using the NSDate class. There is a convenient method implemented in NSDate to obtain the delta in seconds between two date instances, timeIntervalSinceDate:. This is called upon an NSDate instance, taking another NSDate object as an argument. It returns an NSTimeInterval (which is a typedef for a double), which is representative of the number of seconds between the two dates.
Given this, it would be fairly simple to adapt the code you have given above to an Objective-C/Cocoa context. Since the delta calculated by NSDate is given in seconds, given two dates, you could easily adapt the code above:
//Constants
#define SECOND 1
#define MINUTE (60 * SECOND)
#define HOUR (60 * MINUTE)
#define DAY (24 * HOUR)
#define MONTH (30 * DAY)
- (NSString*)timeIntervalWithStartDate:(NSDate*)d1 withEndDate:(NSDate*)d2
{
//Calculate the delta in seconds between the two dates
NSTimeInterval delta = [d2 timeIntervalSinceDate:d1];
if (delta < 1 * MINUTE)
{
return delta == 1 ? #"one second ago" : [NSString stringWithFormat:#"%d seconds ago", (int)delta];
}
if (delta < 2 * MINUTE)
{
return #"a minute ago";
}
if (delta < 45 * MINUTE)
{
int minutes = floor((double)delta/MINUTE);
return [NSString stringWithFormat:#"%d minutes ago", minutes];
}
if (delta < 90 * MINUTE)
{
return #"an hour ago";
}
if (delta < 24 * HOUR)
{
int hours = floor((double)delta/HOUR);
return [NSString stringWithFormat:#"%d hours ago", hours];
}
if (delta < 48 * HOUR)
{
return #"yesterday";
}
if (delta < 30 * DAY)
{
int days = floor((double)delta/DAY);
return [NSString stringWithFormat:#"%d days ago", days];
}
if (delta < 12 * MONTH)
{
int months = floor((double)delta/MONTH);
return months <= 1 ? #"one month ago" : [NSString stringWithFormat:#"%d months ago", months];
}
else
{
int years = floor((double)delta/MONTH/12.0);
return years <= 1 ? #"one year ago" : [NSString stringWithFormat:#"%d years ago", years];
}
}
This would then be called, passing the start and end NSDate objects as arguments, and would return an NSString with the time interval.
You can get the delta between two NSDate objects by using the timeIntervalSinceDate: method. That'll give you the delta in seconds.
From that you can figure out minutes/hours/days/moths/years by dividing by the appropriate amount.
As an alternative, you can avoid the error prone calendar arithmetic by relying on the calendar components you can pull from the difference between two dates:
NSDate *nowDate = [[NSDate alloc] init];
NSDate *targetDate = nil; // some other date here of your choosing, obviously nil isn't going to get you very far
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSUInteger unitFlags = NSMonthCalendarUnit | NSWeekCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit;
NSDateComponents *components = [gregorian components:unitFlags
fromDate:dateTime
toDate:nowDate options:0];
NSInteger months = [components month];
NSInteger weeks = [components week];
NSInteger days = [components day];
NSInteger hours = [components hour];
NSInteger minutes = [components minute];
The key is the setup of the unit flags - this allows you to set which units of time you want the date/time to be broken down into. If you just want hours you'd set NSHourCalendarUnit, and that value will just keep on increasing as your dates move further apart, because there isn't a bigger unit to begin incrementing.
Once you have your components, you can proceed with the logic of your choice, perhaps by modifying #alex's conditional flow.
This is what I threw together:
if (months > 1) {
// Simple date/time
if (weeks >3) {
// Almost another month - fuzzy
months++;
}
return [NSString stringWithFormat:#"%ld months ago", (long)months];
}
else if (months == 1) {
if (weeks > 3) {
months++;
// Almost 2 months
return [NSString stringWithFormat:#"%ld months ago", (long)months];
}
// approx 1 month
return [NSString stringWithFormat:#"1 month ago"];
}
// Weeks
else if (weeks > 1) {
if (days > 6) {
// Almost another month - fuzzy
weeks++;
}
return [NSString stringWithFormat:#"%ld weeks ago", (long)weeks];
}
else if (weeks == 1 ||
days > 6) {
if (days > 6) {
weeks++;
// Almost 2 weeks
return [NSString stringWithFormat:#"%ld weeks ago", (long)weeks];
}
return [NSString stringWithFormat:#"1 week ago"];
}
// Days
else if (days > 1) {
if (hours > 20) {
days++;
}
return [NSString stringWithFormat:#"%ld days ago", (long)days];
}
else if (days == 1) {
if (hours > 20) {
days++;
return [NSString stringWithFormat:#"%ld days ago", (long)days];
}
return [NSString stringWithFormat:#"1 day ago"];
}
// Hours
else if (hours > 1) {
if (minutes > 50) {
hours++;
}
return [NSString stringWithFormat:#"%ld hours ago", (long)hours];
}
else if (hours == 1) {
if (minutes > 50) {
hours++;
return [NSString stringWithFormat:#"%ld hours ago", (long)hours];
}
return [NSString stringWithFormat:#"1 hour ago"];
}
// Minutes
else if (minutes > 1) {
return [NSString stringWithFormat:#"%ld minutes ago", (long)minutes];
}
else if (minutes == 1) {
return [NSString stringWithFormat:#"1 minute ago"];
}
else if (minutes < 1) {
return [NSString stringWithFormat:#"Just now"];
}
You can refer to this developer guide from Apple:
http://developer.apple.com/mac/library/documentation/cocoa/Conceptual/DatesAndTimes/Articles/dtCalendricalCalculations.html#//apple_ref/doc/uid/TP40007836-SW1