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.
Related
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
i know how to get difference between two NSDate as follow
NSTimeInterval timeInterval = [[NSDate date] timeIntervalSinceDate:anyPreviousDate];
and i know it will return me NSTimeInterval in positive seconds. what I want to know is what it will return if my anyPreviousDate is greater than [NSDate date] i.e. anyPreviousDate has not been passed it will come in future.
just curious if anybody has done that before.
Thanks in advance.
I have found another very nice approach to do the same...
here is the code, i thought to share it with stackoverflow.
Cocoa has couple of methods for this:
in NSDate
– isEqualToDate:
– earlierDate:
– laterDate:
– compare:
When you use - (NSComparisonResult)compare:(NSDate *)anotherDate ,you get back one of these:
The receiver and anotherDate are exactly equal to each other, NSOrderedSame
The receiver is later in time than anotherDate, NSOrderedDescending
The receiver is earlier in time than anotherDate, NSOrderedAscending.
example:
NSDate * now = [NSDate date];
NSDate * mile = [[NSDate alloc] initWithString:#"2001-03-24 10:45:32 +0600"];
NSComparisonResult result = [now compare:mile];
NSLog(#"%#", now);
NSLog(#"%#", mile);
switch (result)
{
case NSOrderedAscending: NSLog(#"%# is in future from %#", mile, now); break;
case NSOrderedDescending: NSLog(#"%# is in past from %#", mile, now); break;
case NSOrderedSame: NSLog(#"%# is the same as %#", mile, now); break;
default: NSLog(#"erorr dates %#, %#", mile, now); break;
}
[mile release];
if([previousDate compare:[NSDate date]] == NSOrderedDescending){
// Previous date is greater than current date.i.e. previous date
//is still to come
}else{
//Previous date is smaller then current date.i.e. previous date
//has passed.
}
compare method of NSDate object returns NSComparisonResult, which is an enum.
NSComparisonResult has following values.
NSOrderedSame is returned if left and right operands are equal
NSOrderedAscending is returned if left operand is smaller than the right operand
NSOrderedDescending is returned if ft operand is greater than the right operand
If anyPreviousDate is actually ten seconds in the future, then your code will return -10.0. It happens quite often that you define NSDates that are some time in the future (for example to do something one minute from now), so this isn't unusual at all.
this's a screenshot to see
NSDate * savedDate = [recordsDic[record.transactionId] modifiedDate];
NSDate * newDate = record.modifiedDate;
NSComparisonResult comparisonResult = [newDate compare:savedDate];
NSTimeInterval timeInterval = [newDate timeIntervalSinceDate:savedDate];
NSLog(#"\nsavedDate: %# \nnewDate : %# \n===> timeInterval: %f",savedDate,newDate,timeInterval);
if (comparisonResult == NSOrderedSame) {
NSLog(#"they are same!!!!");
} else {
NSLog(#"they are NOT same!!!!");
}
Console log:
2019-04-11 17:26:47.903059+0800 xxxxx[19268:24419134]
savedDate: Thu Apr 11 15:47:23 2019
newDate : Thu Apr 11 15:47:23 2019
===> timeInterval: 0.000365
2019-04-11 17:26:47.903193+0800 xxxxx[19268:24419134] they are NOT same!!!!
Can you believe this!? but That's true, I spent almost a whole day figuring this out. cause this won't consistently happen!!!
so I strongly recommend:
1. Do NOT use instance method "- (NSComparisonResult)compare:(NSDate *)other;" to compare, you would see something really wired and you can not figure out.
2. timeIntervalSinceDate is more precise.
I'm parsing an array and want to weed out records from before now.
I've got this code:
int i = 0;
for (i=0; i < tempArray.count; i++) {
currentObjectArray = tempArray[i];
NSString *dateString = [currentObjectArray valueForKey:#"ScheduleTime" ];
NSDate *schedule = [dateFormatter dateFromString:dateString];
NSLog(#"schedule: %lu", (unsigned long) schedule );
NSLog(#"now: %lu", (unsigned long)[NSDate date] );
NSTimeInterval distanceBetweenDates = [schedule timeIntervalSinceDate: schedule];
NSLog(#"distanceBetweenDates: %lu", (unsigned long)distanceBetweenDates );
result:
schedule: 16436914033316069376
now: 6174145184
distanceBetweenDates: 0
but the two resulting numbers are incorrect, thus the result is incorrect. Could someone please tell me what I'm doing wrong? Thanks
UPDATE: Thanks to answers below, I've updated my code as follows:
NSString *dateString = [currentObjectArray valueForKey:#"ScheduleTime" ];
NSDate *schedule = [dateFormatter dateFromString:dateString];
float s = [schedule timeIntervalSince1970];
NSLog(#" %f", s );
NSTimeInterval timeInterval = [currentObjectArray timeIntervalSinceNow];
if (timeInterval > 0) {
NSLog(#"YES");
} else {
NSLog(#"NO");
The schedule date format is: "YYYY-MM-DD'T'HH:mm:ss"
Update2: I forgot to add in the local time zone. Thanks for all the help.
These two lines don't do what you think they do.
NSLog(#"schedule: %lu", (unsigned long) schedule );
NSLog(#"now: %lu", (unsigned long)[NSDate date] );
Performing this type cast is asking the system to return you an unsigned long representation of the pointer to the object, which is a memory address and not at all related to time. It is likely that you actually wanted to ask for the NSTimeInterval values.
NSLog(#"schedule: %f", [schedule timeIntervalSince1970] );
NSLog(#"now: %f", [[NSDate date] timeIntervalSince1970] );
Compounding your confusion, you have also misunderstood this line:
NSTimeInterval distanceBetweenDates = [schedule timeIntervalSinceDate: schedule];
You are asking the system to tell you how many seconds are between schedule and schedule; which is obviously always going to be 0 since they are identical. Instead, you probably meant one of:
NSTimeInterval distanceBetweenDates1 = [[NSDate date] timeIntervalSinceDate:schedule];
NSTimeInterval distanceBetweenDates2 = [schedule timeIntervalSinceDate:[NSDate date]];
You only need to check if the time interval is negative or positive to determine if a time comes before or after, respectively.
- (BOOL)isDateInPast:(NSDate *)date {
NSTimeInterval timeInterval = [date timeIntervalSinceNow];
if (timeInterval < 0) {
return YES;
} else {
return NO;
}
}
Note that this doesn't check the condition where the time interval is 0 (the present).
EDIT: Adding to this for further clarification. Your loop code could look something like this...
NSMutableArray *datesOnlyInFuture = [NSMutableArray array];
for (NSDate *date in dateArray) {
if (![self isDateInPast:date]) {
[datesOnlyInFuture addObject:date];
}
}
NSLog(#"Future only dates: %#", datesOnlyInFuture);
This will actually create a new array for you. Clearly plenty of optimizations should be made. For example timeIntervalSinceNow is going to be different each time it is called, so you could pass in a constant date that is set before the loop starts so you're always checking against the same date/time.
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Questions asking for code must demonstrate a minimal understanding of the problem being solved. Include attempted solutions, why they didn't work, and the expected results. See also: Stack Overflow question checklist
Closed 9 years ago.
Improve this question
My question is: how do I load a specific image on a specific date and time in iOS?
I have searched the net but did not find anything useful.
I have a list of images in an imageArray and want every image to be shown on a specific date, time and order.
Fx. say I want to load MyImage on MyDateAndTime. How can I do this?
Image 1 - DateAndTime 1
Image 2 - DateAndTime 2
Image 3 - DateAndTime 3
Any suggestions is appreciated, please provide some source code if possible.
I put simple logic, edit it as per your requirement otherwise if you have any query related to my answer then please tells to me.
Best way is store your image with Name of dateTime (dd_MM_yyyy_HH_mm_ss) and access image name such like,
NSString *imageName
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"dd_MM_yyyy_HH_mm_ss"];
imageName = [NSString stringWithFormat:#"%#.png", [formatter stringFromDate:[NSDate date]]]; // here you can set specific dateTime, i putted current dateTime
Here you get imageName such like 19_10_2013_6_13_21.png
And by this image name you can get image from bundle or document directory.
If all you want is to show a different image every minute, use this, otherwise skip below to see helpful date information.
NSTimeInterval secondsInMinute = 60;
[NSTimer timerWithTimeInterval:secondsInMinute target:self selector:#selector(minuteChanged:) userInfo:nil repeats:YES];
- (void)minuteChanged:(id)sender {
// change image here
}
You question could have many different answers, do you want to create this date dynamically? or is it a a predefined date? One solution is to get get the timeInterval of the date you are looking for.
NSDate* rightNow = [NSDate date];
NSTimeInterval timeInterval = [rightNow timeIntervalSince1970];
// since time intervals are in seconds we can just append the
// date as easily as adding time
NSInteger secondsInMinute = 60;
NSInteger minutesInHour = 60;
NSInteger hoursInDay = 24;
NSInteger daysInWeek = 7;
NSInteger secondsInWeek = secondsInMinute * minutesInHour * hoursInDay * daysInWeek;
timeInterval = timeInterval + secondsInWeek;
NSDate* aWeekInFuture = [NSDate dateWithTimeIntervalSince1970:timeInterval];
that i would say is the easiest to under stand to set a date, but you could also use components to set a future date dynamically. This leads into some problems but here is how it's done.
NSDate* rightNow = [NSDate date];
NSCalendar* calendar = [NSCalendar currentCalendar];
NSDateComponents* dateCompenents = [calendar components:(NSDayCalendarUnit | NSWeekCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit) fromDate:rightNow];
[dateCompenents setDay:dateCompenents.day + 7];
NSDate* aWeekInFuture = [calendar dateFromComponents:dateCompenents];
to help explain this, here is some console logs
(lldb) po rightNow
$0 = 0x0b933440 2013-10-19 12:43:55 +0000
(lldb) po aWeekInFuture
$1 = 0x0ba32a60 2013-10-26 04:00:00 +0000
you see how the date is accurate for the day, year, month, but look at the exact time, the current time (right now) is 12:43:55 but the week in he future is 4:00:00 this is because i did not ask for the NSMinutesCalendarUnit, NSHoursCalendarUnit, NSSecondsCalendarUnit... so if i wanted a perfect date that would be inadequate unless i ask for every single thing, but you specifically may not need to be so accurate in fact you may even want to set your own time.
Now if you want a static date, a date the user enters, you will need to use NSDateFormatter example below
NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setCalendar:currentCalendar];
[dateFormatter setDateFormat:#"mm/dd/yyyy"];
NSDate* birthdayDate = [dateFormatter dateFromString:#"10/05/2013"];
Now you wanted to know how would you know if today is the specified date that is saved. Lets say you stored the date in NSUserDefaults or on a server or some place, the easiest way to compare the dates is with the compare function of an NSDate
NSDate* rightNow = [NSDate date];
NSDate* storedDate = [[NSUserDefaults standardUserDefaults] objectForKey#"storedDate"] // some date from server, or UserDefaults
NSComparisonResult = [rightNow compare:storedDate];
this is a bit inadequate since it test for perfection but it will return values of NSOrderedSame if they are equal, NSOrderedDescending if storedDate is behind rightNow, and NSOrderedAscending if storedDate is in front of rightNow. This is all specific down to the time interval. If you just want a generic day, you will have to test it via components
NSDate* rightNow = [NSDate date];
NSDate* birthdayDate = [[NSUserDefaults standardUserDefaults] objectForKey#"birthday"]
NSDateComponents* todayComponents = [currentCalendar components:(NSDayCalendarUnit | NSMonthCalendarUnit) fromDate:rightNow];
NSDateComponents* birthdayComponents = [currentCalendar components:(NSDayCalendarUnit | NSMonthCalendarUnit) fromDate:birthdayDate];
BOOL dayIsTheSame = ( todayComponents.day == birthdayComponents.day );
BOOL monthIsTheSame = ( todayComponents.month == birthdayComponents.month );
BOOL todayIsBirthday = ( dayIsTheSame && monthIsTheSame );
if (todayIsBirthday) {
[self.imgViewBirthday setImage[UIImage imageNamed:#"cake.png"]];
}
In your question you specified an array of images, lets say you have a different image depending on which hour it is, or which minute, you would use the component, todayComponent.minute after asking for the NSMinutesCalendarUnit as the index of this array;
UIImage* currentImageToDisplay = [self.arrayOfImage objectAtIndex:todayComponent.minute];
self.imageView.image = currentImageToDisplay;
References:
NSDate,
NSDateFormatter,
NSDateComponents,
NSCalendar,
NSTimer
If I understood your problem now, one fancy approach using a recursive block you might check out is this:
#import <Foundation/Foundation.h>
#import <dispatch/dispatch.h>
int main(int argc, const char * argv[])
{
#autoreleasepool {
NSArray* dates = #[#1, #1, #1, #1, #1, #1, #1, #1, #1];
NSArray* urls = #[#"A", #"B", #"C", #"D", #"E", #"F", #"G", #"H", #"I"];
NSEnumerator* dateIter = [dates objectEnumerator];
NSEnumerator* urlIter = [urls objectEnumerator];
typedef void(^block_t)(NSEnumerator* dateIter, NSEnumerator* urlIter);
block_t asyncFunc;
__block __weak block_t _asyncFunc = asyncFunc = ^(NSEnumerator* dateIter, NSEnumerator* urlIter) {
NSNumber* date = [dateIter nextObject];
NSString* url = [urlIter nextObject];
if (date != nil && url != nil) {
double delayInSeconds = [date doubleValue];
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_global_queue(0, 0), ^(void){
NSLog(#"%#", url);
_asyncFunc(dateIter, urlIter);
});
}
else {
printf("\n");
return;
}
};
// start:
asyncFunc(dateIter, urlIter);
sleep (10);
}
return 0;
}
Note:
The "dates" are actually "delays" and the URLs are actually just strings in this example. You should be able to adjust this as you like. Of course, NSLog(#"%#", url); would actually display your image.
Also, the block asyncFunc is asynchronous!
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.