NSDate compare always returns NSOrderedSame - ios

I have a problem with NSDate comparison. I tried out several solutions that I found while googling my problem but nothing changes: I compare two dates and I always get NSOrderedSame or difference between NSTimeIntervals = 0.0000;
My code looks like this:
NSDateFormatter *inputFormatter = [[NSDateFormatter alloc] init];
[inputFormatter setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
for (checklisteModel *updateRecord in updateArray)
{
NSDate *serverDate = [inputFormatter dateFromString:updateRecord.last_update];
NSString *lastUpdateFromLocalRecord = [self getLastUpdateForChecklisteItemWithID:updateRecord.checklisteID];
NSDate *localDate = [inputFormatter dateFromString:lastUpdateFromLocalRecord];
NSDateFormatter* fmt = [[NSDateFormatter alloc] init];
NSLog(#"checkliste ID: %i",updateRecord.checklisteID);
NSLog(#"server Date: ----- %#",[fmt stringFromDate:serverDate]);
NSLog(#"local Date: ----- %#",lastUpdateFromLocalRecord);
NSLog(#"difference of dates: %f",[serverDate timeIntervalSinceDate:localDate]);
NSTimeInterval distanceBetweenDates = [serverDate timeIntervalSinceDate:localDate];
double secondsInMinute = 60;
NSInteger secondsBetweenDates = distanceBetweenDates / secondsInMinute;
if (secondsBetweenDates == 0)
NSLog(#"seconds between == 0");
else if (secondsBetweenDates < 0)
NSLog(#"seconds between < 0");
else
NSLog(#"seconds between > 0");
//last_update on server is earlier than local
if ([serverDate compare:localDate] == NSOrderedAscending)
{
NSLog(#"Server data is earlier than local data");
NSLog([NSString stringWithFormat:#"server data: %#, local data: %#",updateRecord.last_update,lastUpdateFromLocalRecord]);
}
// server data is later than local data
else if ([serverDate compare:localDate] == NSOrderedDescending)
{
NSLog(#"Server data is later than local data");
NSLog([NSString stringWithFormat:#"server data: %#, local data: %#",updateRecord.last_update,lastUpdateFromLocalRecord]);
}
else
{
NSLog(#"Server data and local data are the same");
}
}
And part of my debugging shows this:
checkliste ID: 21
server Date: -----
local Date: ----- 2012-10-29 10:13:46
difference of dates: 3810.000000
seconds between > 0
Server data is later than local data
server data: 2012-10-29 11:17:16, local data: 2012-10-29 10:13:46

NSLog(#"server Date: ----- %#",updateRecord.last_update);
You're not logging serverDate here, so as Hot Licks suggested, there's a good chance that it's nil. You can send messages to nil in Objective-C, and the result is 0 or nil. What do you think the value of NSOrderedSame is? I'll bet a dollar that it's 0.
[inputFormatter setDateFormat:#"yyyy-MM-dd HH:mm"];
I notice that your date formatter's input format doesn't exactly match what you're logging, so maybe the difference between the expected input and what you're providing is enough to prevent the formatter from creating a date. Try adding seconds to the date format for inputFormatter.

Related

Find nearest date in string array

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

Algorithm: Resolution Time

I have an enquiry that needs to be resolved in fixed hour (Critical in 4hr, Important in 10hr and Normal in 24 hr). The enquiry res hours can be increased if there's a non working hour in between or a holiday. Non working can be full day or some hour in a specific day.
For eg: A critical enquiry raised at 08:02am on Monday should get resolved by 01:00pm
//Working hours of week
Mon 09:00am - 01:00pm
Tue Holiday
Wed 09:00am - 05:00pm(non working between 01:00pm - 02:00pm)
Thu 11:00am - 03:00pm(non working between 01:00pm - 02:00pm)
Fri 09:00am - 05:00pm(non working between 01:00pm - 02:00pm)
The inputs are enquiry type and enquiry log time. Output required is the resolution time.
My Approach:
Add all res hours to an enquiry as if all hrs are working. So, if log time is 08:02am for a critical enq add 4hrs i.e. 12:02pm as res time.
Enter while(true) loop where I check if next hr is a work time. If yes skip that hr else add that to res time and continue.
But this approach does not give me correct results when log time is non working.
NSDate *enqExpiration = nil;
int hoursNeededToCoverEnq = 0;
switch (enqType) {
case normalPriority:
hoursNeededToCoverEnq = lowResHours;
enqExpiration = [NSDate dateWithTimeInterval:lowResHours*hour sinceDate:capturedTime];
break;
case importantPriority:
hoursNeededToCoverEnq = mediumResHours;
enqExpiration = [NSDate dateWithTimeInterval:mediumResHours*hour sinceDate:capturedTime];
break;
case criticalPriority:
hoursNeededToCoverEnq = highResHours;
enqExpiration = [NSDate dateWithTimeInterval:highResHours*hour sinceDate:capturedTime];
break;
}
int aggregatedHrs = 0;
while(true){
capturedTime = [NSDate dateWithTimeInterval:1*hour sinceDate:capturedTime];
NSDateFormatter *dayFormat = [[NSDateFormatter alloc] init];
[dayFormat setTimeZone: [NSTimeZone timeZoneWithAbbreviation:#"GMT"]];
[dayFormat setDateFormat:#"EEEE"];
NSString* capturedDay = [dayFormat stringFromDate:capturedTime];
[dayFormat setDateFormat:#"dd/MM/yyyy"];
NSString* todayDate = [dayFormat stringFromDate:capturedTime];
if([weekends containsObject:capturedDay]){
enqExpiration = [NSDate dateWithTimeInterval:1*hour sinceDate:enqExpiration];
continue;
}else if([holidays containsObject:todayDate]){
enqExpiration = [NSDate dateWithTimeInterval:1*hour sinceDate:enqExpiration];
continue;
}else{
NSString *openTime, *closeTime;
for(int i=0; i<weekdaysTime.count; i++){
if([[((NSMutableDictionary*)[weekdaysTime objectAtIndex:i]) valueForKey:#"workingDay"] isEqualToString:capturedDay]){
openTime = [((NSMutableDictionary*)[weekdaysTime objectAtIndex:i]) valueForKey:#"openingTime"];
closeTime = [((NSMutableDictionary*)[weekdaysTime objectAtIndex:i]) valueForKey:#"closingTime"];
break;
}
}
NSDate *openTimeToday = nil, *closeTimeToday = nil;
[dayFormat setDateFormat:#"dd/MM/yyyy hh:mmaa"];
openTimeToday = [dayFormat dateFromString:[[todayDate stringByAppendingString:#" "] stringByAppendingString:openTime]];
closeTimeToday = [dayFormat dateFromString:[[todayDate stringByAppendingString:#" "] stringByAppendingString:closeTime]];
if([capturedTime compare:openTimeToday]==NSOrderedAscending || [capturedTime compare:closeTimeToday]==NSOrderedDescending ){
enqExpiration = [NSDate dateWithTimeInterval:1*hour sinceDate:enqExpiration];
continue;
}
}
aggregatedHrs ++;
if( aggregatedHrs == hoursNeededToCoverEnq )
break;
}
return enqExpiration;

ios8 - seeing if a record is past or future

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.

IOS 6 NSDateFormatter

Please help me with the dateformatter on IOS6, please see the code below
NSString stringDate = #"12/31/9999";
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc]init];
[dateFormatter setDateFormat:#"MM/dd/yyyy"];
NSDate *dateCheck = [dateFormatter dateFromString:stringDate];
NSLog(#"Date = %#", dateCheck);
Output is
Date = 1999-12-31 08:00:00 +0000
This was the output when converting the string date to date 12/31/9999.
From the previous version of IOS6 the output is
Date = 9999-12-31 08:00:00 +0000 // Correct
I made a fix for this for my company's enterprise applications.
It should fix this issue for date formatters using a known format string (like the ones we use to parse dates from our sqlite database).
However, it will not fix:
NSDateFormatters that have isLenient set to true.
NSDateFormatters that use a style, instead of a format string, for parsing.
It does not seem to cause negative side effects on iOS 5 or 5.1. I have not tested anything earlier than that. However, I do mess with the internals of NSDateFormatter a bit, so this may not pass the App Store submission process. However, if you write programs under the Enterprise program (or just use ad hoc deployment), this shouldn't be a problem. Also, it will try to get out of the way if you have isLenient on, but there are no guarantees that you won't run into any issues.
I would like to stress that this is a Temporary Solution. I have not tested this in all possible situations, so you should implement this at your own risk.
I created the following category:
NSDateFormatter+HotFix.h
#import <Foundation/Foundation.h>
#interface NSDateFormatter (HotFix)
- (NSDate*)dateFromString:(NSString *)string;
#end
NSDateFormatter+HotFix.m
#import "NSDateFormatter+HotFix.h"
#import <objc/runtime.h>
#implementation NSDateFormatter (HotFix)
- (NSDate*)dateFromString:(NSString *)string
{
if (!string) return nil;
//HACK: Use the original implementation
void* baseFormatter = nil;
object_getInstanceVariable(self, "_formatter", &baseFormatter);
if (!baseFormatter) return nil;
//Use the underlying CFDateFormatter to parse the string
CFDateRef rawDate = CFDateFormatterCreateDateFromString(kCFAllocatorDefault, (CFDateFormatterRef)baseFormatter, (CFStringRef)string, NULL);
NSDate* source = (NSDate*)rawDate;
//We do not support lenient parsing of dates (or styles), period.
if (source && !self.isLenient && self.dateStyle == NSDateFormatterNoStyle && self.timeStyle == NSDateFormatterNoStyle)
{
//If it worked, then find out if the format string included a year (any cluster of 1 to 5 y characters)
NSString* format = [self dateFormat];
NSRegularExpression* regex = [NSRegularExpression regularExpressionWithPattern:#"y{1,5}" options:NSRegularExpressionCaseInsensitive error:NULL];
NSArray* matches = [regex matchesInString:format options:0 range:NSMakeRange(0, [format length])];
if ([matches count] > 0)
{
for (NSTextCheckingResult* result in matches)
{
//Check for the y grouping being contained within quotes. If so, ignore it
if (result.range.location > 0 && result.range.location + result.range.length < [format length] - 1)
{
if ([format characterAtIndex:result.range.location - 1] == '\'' &&
[format characterAtIndex:result.range.location + result.range.length + 1] == '\'') continue;
}
NSString* possibleYearString = [string substringWithRange:result.range];
NSInteger possibleYear = [possibleYearString integerValue];
if (possibleYear > 3500)
{
NSCalendar* calendar = [NSCalendar currentCalendar];
NSDateComponents* dateComp = [calendar components:NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit fromDate:source];
dateComp.year = possibleYear;
return [calendar dateFromComponents:dateComp];
}
}
}
}
return [source autorelease];
}
#end
It will replace the existing dateFromString method of NSDateFormatter. It works by trying to parse the string normally, then checking to see if the formatString has a set of year formatting characters inside it. If it does, it manually pulls the year out and checks if it is greater than 3500. Finally, if this is the case, it rewrites the output to have the correctly parsed year.
Simply include it in your project and it will take effect. You do not need to import the header into every file that uses a NSDateFormatter, just having the .m compiled in will modify the class. If you have any other categories that change dateFromString: then the effects of this class cannot be defined.
I hope this helps.

creating formatted NSDate using NSDateFormatter or mktime

I'm just trying to create a NSDate object with string as:2011-02-10 4:30:45
Take a look at the code:
Approach OBJC:
-(NSDate *)get_date_df:(NSString *)dstr
{
NSLog(#"1> %#",dstr);
NSDateFormatter *df_in = [[[NSDateFormatter alloc] init]autorelease];
[df_in setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
NSDate *d = [df_in dateFromString:dstr];
NSLog(#"2> %#",d);
NSLog(#"3> %#",[df_in stringFromDate:d]);
return d;
}
And here's the output:
1> 2011-02-10 4:30:45
2> 2011-02-09 23:00:45 +0000
3> 2011-02-10 04:30:45
So why is 2> getting printed wrong?
Suppose at 2011-02-10 4:20:45, I try setting up a LocalNotification with that date, it fires instantly because the processor assumes the time is already expired!
UILocalNotification *lnf = [[UILocalNotification alloc] init];
[lnf setFireDate:[self get_date_df:#"2011-02-10 4:30:45"]];
[lnf setAlertBody:#"WTF"];
[[UIApplication sharedApplication]scheduleLocalNotification:lnf];
[lnf release];
And, I've already tried setting up various TimeZones and NSLocale for en_US, en_FR. No help.
Approach C:
And FYI now I'm switching to C, trying something with:
-(NSDate *)get_date_c:(const char *)date_str
{
char date[10][6] = {0};
sscanf(date_str,"%[0-9] - %[0-9] - %[0-9] %[0-9] : %[0-9] : %[0-9]",date[0],date[1],date[2],date[3],date[4],date[5]);
struct tm *t_info = (struct tm *)malloc(sizeof(struct tm));
t_info->tm_sec = atoi(date[5]);
t_info->tm_min = atoi(date[4]);
t_info->tm_hour = atoi(date[3]);
t_info->tm_mday = atoi(date[2]);
t_info->tm_mon = atoi(date[1]);
t_info->tm_year = atoi(date[0]);
NSLog(#"ans: %f",(double)mktime(t_info));
NSDate *d = [NSDate dateWithTimeIntervalSince1970:mktime(t_info)];
free(t_info);
return d;
}
Output:
ans: -1.000000
There again I'm stuck with converting time_t to NSTimeInterval.
So, thanks in advance if you could help me out with any approach :(
In your input string and format yyyy-MM-dd HH:mm:ss you didn't specify the timezone. So it defaults to UTC.

Resources