So, I've got an sorted NSArray that contains NSString object (downloaded from a server), with the format: yyyy-MM-dd.
It's pretty much like this:
NSArray <NSString *> *dates = #[#"2017-06-25",
#"2017-06-26",
#"2017-06-27",
#"2017-06-28",
#"2017-06-30",
#"2017-07-01",
#"2017-07-02",
#"2017-07-03"];
So, today is 2017-06-29, and it's not in the array. How do I get the next nearest one? In this sample is 06-30, but it might be 07-01 if 06-30 doesn't exist...
Update
So people are asking me about what I've attempted to do. So it's like this (not very effective, but work)
Find if today is in the array (if yes, return)
Loop dates:
2.1 Convert dateString to date
2.2 Compare if date is greater than today => return if YES
If not found in step#2, return last object in dates array.
Actual code:
NSDateFormatter *formatter = [NSDateFormatter new];
formatter.dateFormat = #"yyyy-MM-dd";
NSDate *today = [NSDate date];
NSUInteger index = [dates indexOfObject:[formatter stringFromDate:today]];
// Step 1
if (index == NSNotFound) {
// Step 2: Loop converted
NSInteger i = 0;
for (NSString *date in dates) {
// Step2.1: find the next nearest date's index
NSDate *convertedDate = [formmater dateFromString:date];
// Step2.2: Compare
if ([convertedDate intervalSinceDate:today] > 0) {
index = i;
break;
}
i++;
}
// Step 3: Still not found, index = last index
if (index == NSNotFound) index = i-1;
}
return dates[index];
This doesn't look so good because I might reload the dates array pretty much. Can I have a better solution?
Your algorithm is not bad, though your code doesn't appear to implement it (no sort?). If you'd like to improve it consider this:
First there is probably little point in doing a first scan to check for an exact match - that is potentially a linear search (implemented by indexOfObject:) through an unordered array, and if it fails you have to scan again for a close match, just do them at the same time.
Second there is no advantage in sorting, which is at best O(NlogN), as a linear search, O(N), will find you the answer you need.
Here is a sketch:
Convert the date you are searching for from NSString to NSDate, call it, say, target
Set bestMatch, an NSString to nil. Set bestDelta, an NSTimeInterval, to the maximum possible value DBL_MAX.
Iterate over your dates array:
3.1. Convert the string date to an NSDate, say date
3.2. Set delta to the difference between date and target
3.3. If delta is zero you have an exact match, return it
3.4. If delta is better than bestDelta, update bestDelta and bestMatch
After iteration bestMatch is the best match or nil if there wasn't one.
That is a single iteration, O(N), early return on exact match.
HTH
Please find the simplest solution for your problem. Updated solution based on sorting order!
We can use NSPredicate Block to solve.
static NSDateFormatter* formatter = nil;
static NSDate* today = nil;
// return an NSDate for a string given in yyyy-MM-dd
- (NSDate *)dateFromString:(NSString *)string {
if (formatter == nil) {
formatter = [NSDateFormatter new];
formatter.dateFormat = #"yyyy-MM-dd";
}
return [formatter dateFromString:string];
}
// Helps to return today date.
-(NSDate*) getTodayDate {
if (today == nil) {
today = [NSDate date];
}
return today;
}
// Helps to find nearest date from Array using Predicate
-(NSString*)findNearestDate:(NSArray*)dateArray {
today = nil;
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(NSString *dateString, NSDictionary *bind){
// this is the important part, lets get things in NSDate form so we can use them.
NSDate *dob = [self dateFromString:dateString];
NSComparisonResult result = [[self getTodayDate] compare:dob];
if (result == NSOrderedSame || result == NSOrderedAscending) {
return true;
}
return false;
}];
// Apply the predicate block.
NSArray *futureDates = [dateArray filteredArrayUsingPredicate:predicate];
if ([futureDates count] > 0) {
// Sort the Array.
futureDates = [futureDates sortedArrayUsingSelector: #selector(compare:)];
return [futureDates objectAtIndex:0];
}
return nil;
}
NSArray <NSString *> *dates = #[#"2017-06-25",
#"2017-06-26",
#"2017-06-27",
#"2017-06-28",
#"2017-06-30",
#"2017-07-01",
#"2017-07-02",
#"2017-07-03"];
NSLog(#"Nearest Date: %#", [self findNearestDate:dates]);
Answer: Nearest Date: 2017-06-30
1. Input
So you have an array of NSString like this
// input
NSArray<NSString *> * words = #[#"2017-06-25",
#"2017-06-26",
#"2017-06-27",
#"2017-06-28",
#"2017-06-30",
#"2017-07-01",
#"2017-07-02",
#"2017-07-03"];
2. Converting the array of NSString into an array of NSDate
First of all you need to convert the each input string into an NSDate
NSMutableArray<NSDate *> * dates = [NSMutableArray new];
NSDateFormatter * dateFormatter = [NSDateFormatter new];
dateFormatter.dateFormat = #"yyyy-MM-dd";
for (NSString * word in words) {
[dates addObject:[dateFormatter dateFromString:word]];
}
3. Finding the nearestDate
Now you can find the nearest date
NSDate * nearestDate = nil;
NSTimeInterval deltaForNearesttDate = 0;
NSDate * now = [NSDate new];
for (NSDate * date in dates) {
NSTimeInterval delta = fabs([date timeIntervalSinceDate:now]);
if (nearestDate == nil || (delta < deltaForNearesttDate)) {
deltaForNearesttDate = delta;
nearestDate = date;
}
}
4. Conclusion
The result is into the nearestDate variable so
NSLog(#"%#", nearestDate);
Wed Jun 28 00:00:00 2017
Related
I have an array (self.filteredArray) that returns dictionaries in which the key scheduleddate contains today's date (in this case, Apr 12 2021). Console returns the following:
2021-04-12 09:14:30.942723-0700 [58012:24654705] The filtered please (
{
"node_title" = "Elisa Carmichael";
scheduleddate = "Apr 12 2021 1:00 PM";
},
{
"node_title" = "Michael Levy";
scheduleddate = "Apr 10 2021 11:00 AM, Apr 12 2021 5:00 PM";
},
{
"node_title" = "Trisha Johnson";
scheduleddate = "Apr 12 2021 6:00 PM";
}
)
My goal is to arrange these individuals in a "Today" tableview in order of the time that they're scheduled. The below code works perfectly when scheduleddate only contains ONE date and time, ie. Apr 12 2021 1:00 PM. However some individuals are scheduled for multiple dates and times in the same scheduleddate string (in the above, Michael Levy), and this breaks the sorting order.
Is there a way for me to write the below code so that the final array (self.sortedByTime) arranges the returned individuals ONLY by the value separated by "," that contains today's date?
I hope this makes sense. THANK YOU.
ViewController.m
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (tableView == self.todaytableView) {
self.sortedByTime = [self.filteredArray sortedArrayUsingComparator:^NSComparisonResult(NSDictionary *obj1, NSDictionary *obj2)
{
int i;
for (i = 0; i < [self.filteredArray count]; i++) {
}
NSDate *date1 = [dateFormatter dateFromString:obj1[#"scheduleddate"]];
NSDate *date2 = [dateFormatter dateFromString:obj2[#"scheduleddate"]];
return [date1 compare:date2];
}];
self.finalTimes = [[NSMutableArray alloc]initWithArray:self.sortedByTime];
return [self.finalTimes count];
}
My target output:
2021-04-12 09:14:30.942723-0700 [58012:24654705] The filtered please (
{
"node_title" = "Elisa Carmichael";
scheduleddate = "Apr 12 2021 1:00 PM";
},
{
"node_title" = "Michael Levy";
scheduleddate = "Apr 12 2021 5:00 PM";
},
{
"node_title" = "Trisha Johnson";
scheduleddate = "Apr 12 2021 6:00 PM";
}
)
Ok, you want to sort your array of dictionaries by the Date value in the scheduleddate key.
If scheduleddate sometimes contains multiple dates, it would be simpler if you could assume the correct date is the last one. Failing that, do this:
write a function dateTodayFromString(_:) that takes a String and returns a Date.
Have it use the NSString method containsString to determine if it contains a comma. If it doesn't, just apply the date formatter and return the resulting Date.
If it does, use componentsSeparatedByString: ", " to break the multiple dates apart. Convert each one into a Date using your DateFormatter, loop through the array of Dates and use the Calendar function isDateInToday to find a date that is in the current day. If it finds one, return that.
Rewrite your sort function to call your dateTodayFromString(_:) function to get a date from the string in each array entry.
Note that if your array contains more than ≈100 entries you should really convert the array to an array of mutable dictionaries and go through a pass converting all those date strings to Date objects before you try to sort it. converting date strings to dates for every comparison makes the sorting process a lot slower (I seem to remember that it took like 12 times longer on a modest sized array when I tested it, but I'd have to go back and test it.)
Your issue is not about sorting. It's about filtering and mapping.
Why?
You need to filter because you want to reject some values.
You need to "map" because you want to change some values.
So we do a manual filter since we want to map at the same time, ie: a simple for loop, and for each element, we check if within the dates strings, there is a "today" date candidate, and we keep the first one found.
NSArray *array = #[#{#"node_title": #"Elisa Carmichael",
#"scheduleddate": #"Apr 12 2021 1:00 PM"},
#{#"node_title": #"Michael Levy",
#"scheduleddate": #"Apr 10 2021 11:00 AM, Apr 12 2021 5:00 PM"},
#{#"node_title": #"Trisha Johnson",
#"scheduleddate":#"Apr 12 2021 6:00 PM"}];
NSMutableArray *mappedAndfiltered = [[NSMutableArray alloc] init];
for (NSDictionary *aDict in array) {
NSString *scheduledDatesString = aDict[#"scheduleddate"];
NSArray *dates = [scheduledDatesString componentsSeparatedByString:#","];
NSUInteger candidateIndex = [dates indexOfObjectPassingTest:^BOOL(NSString * _Nonnull dateStr, NSUInteger idx, BOOL * _Nonnull stop) {
return [dateStr containsString:#"Apr 12 2021"]; //That's for the tests, but in fact, use your NSDateFormatter and check if the NSDate is today, I was just lazy to redo a formatter for your sample and check if it's within today
}];
if (candidateIndex != NSNotFound) {
[mappedAndfiltered addObject:#{#"node_title": aDict[#"node_title"],
#"scheduleddate": dates[candidateIndex]}];
}
}
NSLog(#"mappedAndfiltered: %#", mappedAndfiltered);
//Now, sort again using your current method
BUT:
I strongly advice to use custom NSObject, a model.
Because you keep manipulating NSString, where they should be NSDate and you keep on doing conversion between them to sort, edit your data, etc. The string representation should be kept for the user only, not the developer. Else, it's prone to errors.
With a quick model:
#interface MyModel: NSObject
#property (nonatomic, copy) NSString *title;
#property (nonatomic, strong) NSArray *dates;
#property (nonatomic, strong) NSDate *dateWithinToday;
-(id)initWithDict:(NSDictionary *)dict;
-(id)initWithTitle:(NSString *)title dateStrings:(NSArray *)commaSeparatedDateStrings;
#end
#implementation MyModel
-(id)initWithDict:(NSDictionary *)dict {
if (self == [self initWithTitle:dict[#"node_title"] dateStrings:[dict[#"scheduleddate"] componentsSeparatedByString:#","]]) {
}
return self;
}
-(id)initWithTitle:(NSString *)title dateStrings:(NSArray *)commaSeparatedDateStrings {
if (self == [super init]) {
_title = title;
NSMutableArray *datesArray = [[NSMutableArray alloc] init];
for (NSString *aDateStr in commaSeparatedDateStrings) {
//Use a "single formatter"
[datesArray addObject:[__dateFormatter dateFromString:aDateStr]];
}
_dates = datesArray;
NSUInteger index = [datesArray indexOfObjectPassingTest:^BOOL(NSDate * _Nonnull date, NSUInteger idx, BOOL * _Nonnull stop) {
return [[NSCalendar currentCalendar] isDateInToday:date];
}];
if (index != NSNotFound) {
_dateWithinToday = datesArray[index];
}
}
return self;
}
-(NSString *)description {
return [NSString stringWithFormat: #"%# - title: %# - todaydate: %# - all dates: %#" , [super description], _title, _dateWithinToday, _dates];
}
#end
And in use:
NSMutableArray *arrayOfObjects = [[NSMutableArray alloc] init];
for (NSDictionary *aDict in array) {
[arrayOfObjects addObject:[[MyModel alloc] initWithDict:aDict]];
}
NSPredicate *todayPredicate = [NSPredicate predicateWithBlock:^BOOL(MyModel * _Nullable aModel, NSDictionary<NSString *,id> * _Nullable bindings) {
return [aModel dateWithinToday] != nil;
}];
NSArray *filtered = [arrayOfObjects filteredArrayUsingPredicate:todayPredicate];
NSArray *sorted = [filtered sortedArrayUsingComparator:^NSComparisonResult(MyModel * _Nonnull obj1, MyModel * _Nonnull obj2) {
return [[obj1 dateWithinToday] compare:[obj2 dateWithinToday]];
}];
NSLog(#"sorted: %#", sorted);
How to convert Datetime timestamp to a NSDate?
How to make the inverse?
My method to convert datetime to a string :
+(NSString*) dateTojson:(NSDate*)date{
return [NSString stringWithFormat:#"/Date(%f)/",(double)([date dateWithTimeIntervalSince1970] * 1000)];
}
My inverse method:
+(NSDate*) jsonToDate:(NSString *)json
{
double milisec = 0;
json = [[[json stringByReplacingOccurrencesOfString:#"/Date(" withString:#""] stringByReplacingOccurrencesOfString:#"/" withString:#""] stringByReplacingOccurrencesOfString:#"-0200" withString:#""];
NSArray *arr = [json componentsSeparatedByString:#"-"];
for(NSString *s in arr) {
if(![s isEqualToString:#""]){
milisec += [s doubleValue];
}
}
NSDate *date = [NSDate dateWithTimeIntervalSince1970:(milisec / 1000.0)];
return date;
}
When i use [self jsonToDate:#"/Date(1495497600)/"] where 1495497600 represents "05/23/2017", the method return me a wrong date (result = "01/18/1970").
Why?
Notes:
i'm not considering the time, only date.
My variable milisec is equals to 1495497600, so i think the problem is the method dateWithTimeIntervalSince1970.
already try some posts like:
Convert milliseconds to NSDate
How to Convert a milliseconds to nsdate in objective C
You don't really need to divide the milliseconds at the end:
NSDate *date = [NSDate dateWithTimeIntervalSince1970:milisec];
Result:
2017-05-23 00:00:00 +0000
I'm trying to create month strings that look like "Jan", "Feb", "Mar"... Here is my code:
- (NSString *)getMonthNameString:(int)monthNumber {
NSDateFormatter *formatter = [NSDateFormatter new];
[formatter setDateFormat:#"MMM"];
NSArray *monthNames = [formatter standaloneMonthSymbols];
NSString *monthName;
if (monthNumber > 0) {
return monthNames[monthNumber - 1];
}
return monthNames[1];
}
So if the month number is 1, I'm expecting the code to provide month name as "Jan" and if it is 2, it has to provide month name as "Feb" and so on. But the problem is that even though I have set the format as MMM, it is still creating month names of type "January", "February" etc instead of "Jan","Feb" etc. How do I sort this out?
Try:
-(NSString*)getMonthNameString:(int)monthNumber
{
NSDateFormatter *formate = [NSDateFormatter new];
[formate setDateFormat:#"MMM"];
NSArray *monthNames = [formate shortMonthSymbols];
NSString *monthName;
if (monthNumber > 0)
{
monthName = [monthNames objectAtIndex:(monthNumber - 1)];
}
return monthName;
}
That's usually not what a NSDateFormatter is for - it is for converting real dates, and not just month numbers.
If you want to stick with it, I suggest
-(NSString*)getMonthNameString:(int)monthNumber {
NSDateFormatter *formatter = [NSDateFormatter new];
return formatter.shortMonthSymbols[monthNumber-1];
}
I see no benefit in the extra check for > 0. This just masks programming errors. You might want to add an assertion to catch that during development. (Why should an invalid number return January anyway?)
Creating formatters is expensive, though - you might want to reuse the same instance over and over again.
Or just access an array directly, i.e.
-(NSString*)getMonthNameString:(int)monthNumber {
return #[#"Jan", #"Feb", ...][monthNumber-1]; // write up to December of course
}
standaloneMonthSymbols is a property of NSDateFormatter. I don't think it uses the dateFormat you've set. Try using shortStandaloneMonthSymbols property instead (or veryShortStandaloneMonthSymbols if you just need one letter symbol).
tahavath is right. Specifically, you want to use the shortStandaloneMonthSymbols property to get it to print "Jan" or "Feb" etc.
Try the following:
-(NSString*)getMonthNameString:(int)monthNumber
{
NSDateFormatter *formate = [NSDateFormatter new];
[formate setDateStyle:NSDateFormatterMediumStyle];
[formate setDateFormat:#"MMM"];
NSArray *monthNames = [formate standaloneMonthSymbols];
NSString *monthName;
if (monthNumber > 0)
{
monthName = [monthNames objectAtIndex:(monthNumber - 1)];
}
return monthName;
}
Check your code #Karuppu MGR
-(NSString*)getMonthNameString:(int)monthNumber
{
NSDateFormatter *formate = [NSDateFormatter new];
[formate setDateFormat:#"MMM"];
NSArray *monthNames = [formate standaloneMonthSymbols];
NSString *monthName;
if (monthNumber > 0 && monthNumber<13)
{
monthName = [monthNames objectAtIndex:(monthNumber - 1)];
// your process is right but , here you have attached "return value " so every time return the monthNames array value.
}
return monthName; // if you pass zero or greathan twelve monthName return nil value
}
This question already has answers here:
NSDate finding nearest date to today
(5 answers)
Closed 7 years ago.
I have an array of objects, each with an NSDate property called "date".
How can I find the object with the closest date to current time?
This will leave closestObject with the object that has the closest date. If you don't want past dates, then get rid of the ABS and make sure interval is positive.
MyObjectType *closestObject;
NSTimeInterval closestInterval = DBL_MAX;
for (MyObjectType *myObject in array) {
NSTimeInterval interval = ABS([myObject.date timeIntervalSinceDate:[NSDate date]]);
if (interval < closestInterval) {
closestInterval = interval;
closestObject = myObject;
}
}
- (NSDate *)closestDateFromArray:(NSArray *)dateArray
{
double smallestDifference = DBL_MAX; // thanks bgfriend0
NSDate *closestDate = nil;
for (NSDate *date in dateArray) {
// suggested by Henri Normak
if ([date timeIntervalSinceNow] <= someThresholdValue) {
return date;
// you could set some value that is a "good enough" value.
// i.e. if the date IS NOW then nothing is going to be closer.
}
if (ABS([date timeIntervalSinceNow]) < smallestDifference) {
smallestDifference = ABS([date timeIntervalSinceNow]);
closestDate = date;
}
}
return closestDate;
}
Something like this should do the trick.
If the array is an array of objects that have a date property then you can do exactly the same just get the date out of the object to compare.
I have a string like this 13, 27, 29 representing days of the month I want to separate them like below into date objects
mayString = [mayString componentsSeparatedByString:#","];
I then want to be able to work out which of these days i.e 13 or 27 or 29 is closest to todays date which obviously taking the above dates would be 27 as the next closest date to current date.
I can grab current day using the below but really stuck on how to get the logic to do this?
//Grab current day from sys date
NSDateFormatter *dayFormatter = [[NSDateFormatter alloc] init];
[dayFormatter setDateFormat:#"dd"];
NSString *dayString = [dayFormatter stringFromDate:[NSDate date]];
I have a partial completed solution but it doesnt seem to give me the correct result of what index in the array is closest to current day (sepDates is an array)
sepDates = [mayString componentsSeparatedByString:#","];
//Day string is todays date i.e 16 (16th)
NSDate *dayFromString = [dayFormatter dateFromString:dayString];
NSLog(#"Day from string %#", dayFromString);
double min = [dayFromString timeIntervalSinceDate:[sepDates objectAtIndex:0]];
NSLog(#"Min %f", min);
//I then want to calculate which of the dates in the sepDates array at index is closest to todays current day 16
int minIndex = 0;
for (int d = 1; d < [sepDates count]; ++d)
{
double currentmin = [dayFromString timeIntervalSinceDate:[sepDates objectAtIndex:d]];
if (currentmin < min) {
min = currentmin;
minIndex = d;
NSLog(#"minIndex = %d", minIndex);
}
}
dayString shouldn't be a string, it should be NSInteger
While iterating through your array with dates, also convert all strings to integer (for example, [currentDayString integerValue])
Actual algorithm of searching the closest day would be to iterate through your initial array and find abs of difference between values in array and current day. Store those differences in separate array. Find minimum value in the second array. Location (index) of the minimal difference will be the same as location of closest day in the first array.
Here is the code snippet from the question that gives correct minIndex
NSArray *sepDates = #[#"13", #"15", #"27", #"29"];
NSDateFormatter *dayFormatter = [[NSDateFormatter alloc] init];
[dayFormatter setDateFormat:#"dd"];
NSString *dayString = [dayFormatter stringFromDate:[NSDate date]];
NSDate *dayFromString = [dayFormatter dateFromString:dayString];
NSLog(#"Day from string %#", dayFromString);
NSInteger min = [[sepDates lastObject] integerValue]; //or set it to some large int
NSLog(#"Min %d", min);
int minIndex = 0;
for (int d = 1; d < [sepDates count]; ++d)
{
NSInteger currentmin = [sepDates[d] integerValue] - [dayString integerValue];
NSLog(#"Current min: %d", currentmin);
//currentmin must be positive since you need next closest day
if (currentmin > 0 && currentmin < min) {
min = currentmin;
minIndex = d;
NSLog(#"minIndex = %d", minIndex);
}
}
Iterate through your dates and calculate the time from the current target and store that date if it is less than previously calculated or if nothing has been calculated (i.e. first element). The results from that will give you the answer.
Do this.
After you have separated the dates. convert them to int and add them to a mutable index set.
then, get todays date as string with format "dd" using date formatter, convert todays date (date string) to int and then add that date to the mutable index set.
NSMutableIndexSet is auto sorted.
then do this
NSInteger closestDateAsInt = [indexSet indexGreaterThanIndex:''today's date as int"];
this will give u the closest next date.
Once u have the closest date's int value. Convert it to date using date components.
Hope this helps. Cheers.