Sort NSDictionaries in NSMutableArray by NSDate - objective-c

I have a NSMutableArray with dictionaries, all of the dict's contain a NSDate is form of NSString with key 'Date'. I want to sort the NSDictionaries by the Dates. So for example i have the following state of the array:
Dict
Date
20.06.1996 23:30
Dict
Date
04.10.2011 19:00
Dict
Date
20.06.1956 23:39
And I want to sort it, so that it looks like this:
Dict
Date
20.06.1956 23:39
Dict
Date
20.06.1996 23:30
Dict
Date
04.10.2011 19:00
I have already experimented with NSSortDescriptor, but without success...
Update:
I have managed to sort the dates, but I came to this problem: In the dicts there is not only dates, also other objects, and what my code does is it only switches the date values between the dicts, instead of switching the complete dicts around. With this, the other values in the dicts get assigned a wrong date, which is very bad. Can anybody help me? Heres my code:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"savedData.daf"];
NSMutableArray *d = [NSMutableArray arrayWithContentsOfFile:path];
for (int ii = 0; ii < [d count]; ii++) {
NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
if (is24h) {
[dateFormatter setDateFormat:#"dd.MM.yyyy HH:mm"];
}
else {
[dateFormatter setDateFormat:#"dd.MM.yyyy hh:mm a"];
}
[dateFormatter setLocale:[NSLocale currentLocale]];
NSDate *dat = [dateFormatter dateFromString:[[d valueForKey:#"Date"] objectAtIndex:ii]];
NSMutableDictionary *newDict = [[NSMutableDictionary alloc] init];
NSDictionary *oldDict = (NSDictionary *)[d objectAtIndex:ii];
[newDict addEntriesFromDictionary:oldDict];
[newDict setObject:dat forKey:#"Date"];
[d replaceObjectAtIndex:ii withObject:newDict];
[newDict release];
}
NSSortDescriptor *sorter = [[NSSortDescriptor alloc] initWithKey:#"Date" ascending:YES];
NSArray *sorters = [[NSArray alloc] initWithObjects:sorter, nil];
[sorter release];
NSMutableArray *sorted = [NSMutableArray arrayWithArray:[d sortedArrayUsingDescriptors:sorters]];
[sorters release];
NSLog(#"%#",sorted);
for (int ii = 0; ii < [sorted count]; ii++) {
NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
if (is24h) {
[dateFormatter setDateFormat:#"dd.MM.yyyy HH:mm"];
}
else {
[dateFormatter setDateFormat:#"dd.MM.yyyy hh:mm a"];
}
[dateFormatter setLocale:[NSLocale currentLocale]];
NSString *sr = [dateFormatter stringFromDate:[[sorted valueForKey:#"Date"] objectAtIndex:ii]];
NSMutableDictionary *newDict = [[NSMutableDictionary alloc] init];
NSDictionary *oldDict = (NSDictionary *)[d objectAtIndex:ii];
[newDict addEntriesFromDictionary:oldDict];
[newDict setObject:sr forKey:#"Date"];
[sorted replaceObjectAtIndex:ii withObject:newDict];
[newDict release];
}
NSLog(#"before: %#"
""
"after: %#",d,sorted);
[sorted writeToFile:path atomically:YES];

There are many ways to do this, one would be to use NSDate objects instead of NSStrings (or NSStrings formatted according to ISO 8601, so that the lexicographic sort would match the desired sorting). Then you could do:
NSSortDescriptor *descriptor = [NSSortDescriptor sortDescriptorWithKey:#"Date"
ascending:YES];
[array sortUsingDescriptors:[NSArray arrayWithObject:descriptor]];
Or, if you can't (or don't want to) change your data, you can always sort using a block:
[array sortUsingComparator:^(id dict1, id dict2) {
NSDate *date1 = // create NSDate from dict1's Date;
NSDate *date2 = // create NSDate from dict2's Date;
return [date1 compare:date2];
}];
Of course this would probably be slower than the first approach since you'll usually end up creating more than n NSDate objects.

There are a couple of options, one is to put NSDate objects in the dictionary.
One problem with comparing the strings is that you can 't just do a string compare because the year is not in the most significant potion of the string.
So, you will need to write a comparison method to use with:
- (NSArray *)sortedArrayUsingComparator:(NSComparator)cmptr
or perhaps:
- (NSArray *)sortedArrayUsingFunction:(NSInteger (*)(id, id, void *))comparator context:(void *)context
The comparator will need to handle the dd.mm.yyyy hh:mm string format.
Other options include adding another dictionary key with an NSDate representation and sorting on that.

Related

Sorting NSMutablearray by NSString as a date

I have an array, which is filled out with string objects. Inside each object is the name of the object and the string, seperated by " - ", ex. "Object1 - 26.05.2012 ". I would like to sort my array by the date in the string, and not the name, descending Is this possible?
As #Vladimir pointed out, it would be much better to separate the name and the string from each other and sort by the date.
NSMutableArray *newArray = [[NSMutableArray alloc] init];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"dd.MM.yyyy"];
for (NSString *str in yourArray)
{
NSRange rangeForDash = [str rangeOfString:#"-"];
NSString *objectStr = [str substringToIndex:rangeForDash.location];
NSString *dateStr = [str substringFromIndex:rangeForDash.location+1];
NSDate *date = [formatter dateFromString:dateStr];
NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:objectStr, #"object", date, #"date", nil];
[newArray addObject:dic];
}
NSSortDescriptor *sortDesc = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:NO];
[newArray sortUsingDescriptors:[NSArray arrayWithObjects:sortDesc, nil]];
[sortDesc release];
[formatter release];
newArray will be sorted but will have dictionary objects, each containing 2 strings.
Quick and dirty fix - rearrange the string so that the date is first
str = [[str substringFromIndex:someIndex] stringByAppendingString:[str substringToIndex:someIndex]];
then just switch everything back once the array is sorted.

Date difference between UIDatepicker and NSDate

I have a datepicker and I save the value to .plist file with key UserDate in string format.
The value returns from datepicker is in 2013-02-13 11:17:34 +0000 format.
In order to save it into to .plist file I need to convert datepicker to string 2013-02-14T19:17:34Z
In other View, I want to retrieve back the UserDate from plist and compare with the user current date. I want days and hours different.
I convert and pass 2012-02-12 19:17:34 +0800 as destinationDate to compare with [NSDate new].
I managed to calculate the difference between two date. But the result is not correct.
How to get the correct difference result for two dates between iPhone's date and Datepicker's date? Do I need to use a specific timezone?
Extra info: I live in GMT+8. I set the simulator datepicker to Feb 13 2012, yet the result still say the countdown has 18hours and 4 minutes.
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
int units = NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;
NSDateComponents *components = [calendar components:units fromDate:[NSDate date] toDate:destinationDate options:0];
[datelabel setText:[NSString stringWithFormat:#"%d%c %d%c %d%c %d%c %d%c", [components month], 'm', [components day],
'd', [components hour], 'h', [components minute], 'm', [components second], 's']];
NSLog
Edit
Plist file.
save method
NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
// get documents path
NSString *documentsPath = [paths objectAtIndex:0];
// get the path to our Data/plist file
NSString *plistPath = [documentsPath stringByAppendingPathComponent:#"Data.plist"];
// set the variables to the values in the text fields
self.userTitle = myTitle.text;
self.userMessage = myMessage.text;
NSLog(#"METHOD SAVE");
NSLog(#"Datepicker value = %#", myDate.date );
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
[dateFormatter setDateFormat:#"yyyy-MM-dd'T'HH:mm:ss'Z'"];
NSString *mdate = [dateFormatter stringFromDate:myDate.date];
// [dateFormatter setDateFormat:#"yyyy-MM-dd'T'HH:mm:ss'Z'"];
NSLog(#"Converted from date picker, save into plist = %#", mdate );
self.saveDate = mdate;
//self.userDate = myDate.date;
// create dictionary with values in UITextFields
NSDictionary *plistDict = [NSDictionary dictionaryWithObjects: [NSArray arrayWithObjects: userTitle, userMessage, saveDate, nil] forKeys:[NSArray arrayWithObjects: #"Title", #"Message", #"UserDate", nil]]; NSString *error = nil;
// create NSData from dictionary
NSData *plistData = [NSPropertyListSerialization dataFromPropertyList:plistDict format:NSPropertyListXMLFormat_v1_0 errorDescription:&error];
// check is plistData exists
if(plistData)
{
// write plistData to our Data.plist file
[plistData writeToFile:plistPath atomically:YES];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Success" message:#"Your settings have been saved." delegate:self cancelButtonTitle:nil otherButtonTitles:#"Ok",nil];
[alert show];
}
else
{
NSLog(#"Error in saveData: %#", error);
//[error release];
}
viewWillAppear
- (void)viewWillAppear:(BOOL)animated {
[self.navigationController setNavigationBarHidden:YES];
[super viewWillAppear:animated];
// Data.plist code
// get paths from root direcory
NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
// get documents path
NSString *documentsPath = [paths objectAtIndex:0];
// get the path to our Data/plist file
NSString *plistPath = [documentsPath stringByAppendingPathComponent:#"Data.plist"];
// check to see if Data.plist exists in documents
if (![[NSFileManager defaultManager] fileExistsAtPath:plistPath])
{
// if not in documents, get property list from main bundle
plistPath = [[NSBundle mainBundle] pathForResource:#"Data" ofType:#"plist"];
}
// read property list into memory as an NSData object
NSData *plistXML = [[NSFileManager defaultManager] contentsAtPath:plistPath];
NSString *errorDesc = nil;
NSPropertyListFormat format;
// convert static property liost into dictionary object
NSDictionary *temp = (NSDictionary *)[NSPropertyListSerialization propertyListFromData:plistXML mutabilityOption:NSPropertyListMutableContainersAndLeaves format:&format errorDescription:&errorDesc];
if (!temp)
{
NSLog(#"Error reading plist: %#, format: %d", errorDesc, format);
}
NSLog(#"Plist Title: %#", [temp objectForKey:#"Title"]);
NSLog(#"Plist Message: %#", [temp objectForKey:#"Message"]);
NSLog(#"Plist date: %#", [temp objectForKey:#"UserDate"]);
userTitle.text = [NSString localizedStringWithFormat:#"%#", [temp objectForKey:#"Title"]];
userMessage.text = [NSString localizedStringWithFormat:#"%#", [temp objectForKey:#"Message"]];
//http://stackoverflow.com/questions/1070354/how-do-i-get-the-current-date-in-cocoa
NSDate* now = [[NSDate alloc] initWithTimeIntervalSinceNow:3600];
userCoundown.text = [NSString stringWithFormat:#"%#",now];
NSString *uDate = [[NSString alloc] init];
uDate = [temp objectForKey:#"UserDate"];
NSLog(#"Plist date in string - %#", uDate);
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
[dateFormatter setDateFormat:#"yyyy-MM-dd'T'HH:mm:ss'Z'"];
NSDate *ddate = [dateFormatter dateFromString:uDate];
[dateFormatter setDateFormat:#"yyyy-MM-dd H:mm:ss Z"];
NSLog(#"Format date from Plist - %#", [dateFormatter stringFromDate:ddate]);
//NSLog(#"GMT format from Plist - %#", timeStamp);
//http://www.epochconverter.com/#
destinationDate = ddate;
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(updateLabel) userInfo:nil repeats:YES];
}
updatelabel
-(void)updateLabel {
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
int units = NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;
NSDateComponents *components = [calendar components:units fromDate:[NSDate date] toDate:destinationDate options:0];
[datelabel setText:[NSString stringWithFormat:#"%d%c %d%c %d%c %d%c %d%c", [components month], 'm', [components day],
'd', [components hour], 'h', [components minute], 'm', [components second], 's']];
}
NSDate keeps time in GMT. If you have time zones you will have to specify them.
The question is not clear so this is not a complete answer but should provide some help with NSDate in a plist.
Here is an example of creating, writing and reading a plist with a NSDate:
NSDictionary *plistDict = [NSDictionary dictionaryWithObjectsAndKeys:
userTitle, #"Title",
userMessage, #"Message",
saveDate, #"UserDate",
nil];
[plistDict writeToFile:filePath atomically:YES];
NSDictionary *plistDictRead = [NSDictionary dictionaryWithContentsOfFile:filePath];
NSDate *dateRead = [plistDictRead objectForKey:#"UserDate"];
There is no need to convert the NSDate to a string and no need to use NSPropertyListSerialization.
Remember to create the NSDate and then output it with a valid timezone and calendar!
Check a similar question I answered in another post:
UIDatePicker return wrong NSDate

Saving selected date and time into a plist

I would like to know how to save a selected date and time from a date picker into a plist.
NSMutableArray *userArray = [[NSMutableArray alloc] initWithContentsOfFile:plistPath];
NSMutableDictionary *dict = [userArray objectAtIndex:0];
NSDate *date = [NSDate date];
NSDateFormatter *dateformatter = [[NSDateFormatter alloc] init];
[dateformatter setDateFormat:#"MM/dd/yyy hh:mm a"];
[dict setObject:[dateformatter stringFromDate:date] forKey:#"LastLocalDataUpdate"];
[userArray writeToFile:plistPath atomically:YES];
NSUserDefaults is good enough for what you're trying to do:
NSDate *theDate = [NSDate date];
[[NSUserDefaults standardUserDefaults] setObject:theDate forKey:#"Date"];
NSUserDefaults will do it just nicely.

How to split up NSArray into NSDictionary by array object's NSDate day of year?

I have a NSArray, and I sort it by its object's "published" property in descending order, newest first:
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"published" ascending:YES];
NSArray *descps = [[NSArray alloc] initWithObjects:[sortDescriptor reversedSortDescriptor], nil];
[storiesLocal sortUsingDescriptors:descps];
[descps release];
[sortDescriptor release];
So now, I want to split this array up by each day, so that I can use it with a UITableView. In my example each date would be a tableview section header.
So if my example sorted array (storiesLocal) had dates like such:
2010-04-05 10:32:00
2010-04-05 06:20:12
2010-04-02 09:23:02
2010-04-02 03:20:34
2010-04-01 04:22:34
Then I would have tableview headers like "April 5", "April 2", "April 1". Therefore each would have 2, 2 and 1 row under each corresponding header
Essentially, my wanted outcome would be an NSDictionary. It's each key would be a date (2010-04-02), each value would be an NSArray of the correct objects to go with it. All of these should be sorted by date. Newest first.
I've gone through about 3 tries and failed every time, ending up deleting the code I wrote.
Edit: since an NSDictionary is an unordered list, it might be better to have an array of dictionaries, each dict including a key for the date and a key for the stories array, since order is very important.
This isn't tested, but try something like:
NSArray *descps as above.
NSDateFormatter *in_formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"YYYY-MM-dd HH:mm:ss"];
NSDateFormatter *out_formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"MMMM d"];
NSString *todayStr =
[formatter release];
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
for (ObjectType *obj in descps) {
NSDate *date = [in_formatter dateFromString:[obj published]];
NSString *day = [out_formatter stringFromDate:date];
NSArray *array = [dict valueForKey:day];
if (array == nil) {
array = [[NSMutableArray alloc] init];
[dict setValue:array forKey:day];
[array release];
}
[array addObject:obj];
}
[in_formatter release];
[out_formatter release];

Objective-C: [sectionInfo name] of NSFetchedResultsSectionInfo needs to be dateFormatted

I have the following code:
-(NSString *)tableView:(UITableView *)table titleForHeaderInSection:(NSInteger)section {
id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
NSDateFormatter* dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];
NSString *sectionName = [sectionInfo name];
NSLog(#"sectionName %#", sectionName);
NSString *convDate = [dateFormatter stringFromDate: (NSDate *)sectionName];
NSLog(#"convDate %#", convDate);
return [NSString stringWithFormat:#"%#", [sectionInfo name]];
}
I am basically needing to convert the titleforheaderinsection which is a string date like "2009-12-04 00:00:00 +1100" to a nicer looking shorter string. So I have tried converting it using something like dateFormatter setDateStyle, but when i output the NSLog to console i get the following:
2009-12-22 09:42:10.156 app[94614:207] sectionName 2009-12-04 00:00:00 +1100
2009-12-22 09:42:10.157 app[94614:207] convDate (null
Obviously the convDate is not getting anything, but [sectionInfo name] should be a string. I have parsed it into its own NSString variable, so why cant i implement the dateFormatter on it?
A bit more information: I parse the date amongst other things earlier on, with the code snippet being:
if ([currentElement isEqualToString: #"date"]) {
NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init] ;
[dateFormatter setDateFormat:#"eee, dd MMM yyyy"];
NSDate *convDate = [dateFormatter dateFromString:string];
if (self.isComment){
[currentComment setDate: convDate];
}else if (self.isPost)
NSLog(#"convDate is %#", convDate);
[currentPost setDate: convDate];
Now, when I debug this, essentially the raw string is "Sat, 27 Nov 2009 17:16:00 -800" but when i look at the convDate it comes out to be "2009-11-27 00:00:00 +1100". Not sure why, but in any case, thats what gets stored. I would have thought it would match the style i mentioned, so if i change the dateFormatter format to any other type, it would stuff up and convDate become nil.
Looking back at my postController: I have some snippets of interest:
- (NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController == nil) {
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"Post" inManagedObjectContext: ApplicationController.sharedInstance.managedObjectContext]];
NSArray *sortDescriptors = nil;
NSString *sectionNameKeyPath = #"date";
NSPredicate *pred = [NSPredicate predicateWithFormat:[NSString stringWithFormat:#"(PostSite.name like '%#')", self.site.name]];
[fetchRequest setPredicate:pred];
sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc]
initWithKey:#"date" ascending:NO] ];
[fetchRequest setSortDescriptors:sortDescriptors];
fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:
ApplicationController.sharedInstance.managedObjectContext
sectionNameKeyPath:sectionNameKeyPath cacheName:#"PostCache"];
}
return fetchedResultsController;
}
I am hoping to sort by date, and up in my code, in titleForHeaderInSection, format my string date to look more presentable.
Thanks guys
The reason you were getting unexpected behaviour is because during parsing you were setting the date format to
[dateFormatter setDateFormat:#"eee, dd MMM yyyy"];
which has no time information, which is why there was a time difference in the dates.
Also, during parsing you were setting convDate with
NSDate *convDate = [dateFormatter dateFromString:string];
which is storing an NSDate and crucially NSFetchResultsController is calling the description selector on convDate because it is not an NSString.
I'm not entirely sure why NSDateFormatter could not recognise the ISO date format.
The reason your code does not work is because
[sectionInfo name]
returns an NSString object, not an NSDate object, which is required by
[dateFormatter stringFromDate:];
You cannot simply cast an NSString to an NSDate.
Instead,
- (NSString *)tableView:(UITableView *)table titleForHeaderInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
NSString *sectionName = [sectionInfo name];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];
NSString *convDate = [dateFormatter stringFromDate:[dateFormatter dateFromString:sectionName]];
[dateFormatter release];
return convDate;
}
Also, don't autorelease an object unless there is a specific reason to do so.
I found the solution by going to http://iosdevelopertips.com/cocoa/date-formatters-examples-take-2.html and using http://unicode.org/reports/tr35/#Date_Format_Patterns as a guide, I debugged my code until i ensured that the variable is matched with the incoming string