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!
Related
My app ingests data from a web service (PHP) which provides dates in this format:
endDate = {
date = "2020-09-30 16:16:08.000000";
timezone = "-04:00";
"timezone_type" = 1;
};
This is the code I have been using to convert to NSDate, and it works as far as I can tell, in every test, but it fails on a few devices according to user reports and debug logs.
Note that the correct conversion of this date determines if content is unlocked in the app, so when it fails, customers contact us about it.
NSDictionary* dateDict = [responseDict objectForKey:#"endDate"];
NSString* strEndDate = [dateDict objectForKey:#"date"];
NSString* strOffset = [dateDict objectForKey:#"timezone"];
NSTimeInterval zoneSeconds = 0;
NSRange rng = [strOffset rangeOfString:#":"];
if (rng.location != NSNotFound && rng.location >= 1)
{
NSString* hoursOnly = [strOffset substringToIndex:rng.location];
NSInteger offsetValue = [hoursOnly integerValue];
zoneSeconds = (3600 * offsetValue);
}
NSDateFormatter* df = [[NSDateFormatter alloc] init];
NSTimeZone *timeZone = [NSTimeZone timeZoneForSecondsFromGMT:zoneSeconds];
[df setTimeZone:timeZone];
[df setDateFormat:#"yyyy-MM-dd HH:mm:ss.000000"];
NSDate* newEndDate = [df dateFromString:strEndDate];
However, debug logs from a few users show that the dateFromString call is failing and returning nil.
We have one user who has 2 iOS devices, and using the same account (same date) the app performs as expected on one of them, but fails on the other. Same Apple ID, both running iOS12. Debug logs show both devices received the same date from the server, yet one of them failed to convert the date from a string to NSDate.
My assumption so far is that there is some setting or configuration on the device(s) where this fails that is different. But I have fiddled with calendar and date settings all day, and cannot get this to fail. I know the user in question has both devices configured to the same time zone.
Is there a better, more correct way to do this date conversion which might be more robust?
When using an arbitrary date format it's highly recommended to set the locale of the date formatter to the fixed value en_US_POSIX.
Rather than calculating the seconds from GMT it might be more efficient to strip the milliseconds with regular expression, append the string time zone and use an appropriate date format.
This code uses more contemporary syntax to set date formatter properties with dot notation and dictionary literal key subscription
NSDictionary *dateDict = responseDict[#"endDate"];
NSString *strEndDate = dateDict[#"date"];
NSString *strTimeZone = dateDict[#"timezone"];
NSString *dateWithoutMilliseconds = [strEndDate stringByReplacingOccurrencesOfString:#"\\.\\d+" withString:#"" options:NSRegularExpressionSearch range:NSMakeRange(0, strEndDate.length)];
NSString *dateWithTimeZone = [NSString stringWithFormat:#"%#%#", dateWithoutMilliseconds, strTimeZone];
NSDateFormatter *df = [[NSDateFormatter alloc] init];
df.locale = [NSLocale localeWithLocaleIdentifier:#"en_US_POSIX"];
df.dateFormat = #"yyyy-MM-dd HH:mm:ssZZZZZ"];
NSDate *newEndDate = [df dateFromString:dateWithTimeZone];
The question was actually similar to (What is the best way to deal with the NSDateFormatter locale "feechur"?) as was suggested originally, but it was this other question (NSDateFormatter fails to return a datetime for UK region with 12 hour clock set) which really made it click for me - its the UK region with the 12hour clock which causes the code to fail, but the dateFormatter was easily fixed by simply setting the locale to "un_US_POSIX" as suggested in the answer to that question (it was also suggested below by vadian - I did not try his code however). Thank you to everyone who contributed hints and leads!
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'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.
I just can't figure out what's going on here... Essentially, my code saves an object (to server) and logs the time in NSUserDefaults. The next time a user tries to save an object, I check to see if the date is in-between the stored time, and the stored time plus 2 hours. So basically, I want to prevent the user from saving an additional object within 2 hours of the other. I have the code written and finished... Here's the catch, it works 100% of the time on my end (Central Standard Time), but I have a beta tester in California (Pacific Time) that has issues with it. The times being recorded are correct, but the 2 hour windows are off, and often don't get overwritten... There's a few other places in the app where times can be updated, but the code is essentially, the exact same.
Here's the code in the "save object method":
//Combine date and time
NSCalendar *gregorianCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *components1 = [gregorianCalendar components:NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit fromDate:self.pickedDate];
NSDateComponents *components2 = [gregorianCalendar components:NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit fromDate:self.pickedTime];
NSDateComponents *components3 = [[NSDateComponents alloc] init];
[components3 setYear:components1.year];
[components3 setMonth:components1.month];
[components3 setDay:components1.day];
[components3 setHour:components2.hour];
[components3 setMinute:components2.minute];
[components3 setSecond:components2.second];
//Generate a new NSDate from components3.
NSDate *combinedDate = [gregorianCalendar dateFromComponents:components3];
//Create "plus 2 hour" date
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDate *time = [defaults objectForKey:#"lastObject_time"];
NSTimeInterval twoHours = 2 * 3600;
NSDate *plusTwoHours = [time dateByAddingTimeInterval:twoHours];
if (time) {
//There is a stored time
if (![self date:combinedDate isBetweenDate:time andDate:plusTwoHours]) {
//After 2 hours, allow save. After save, check if it's the LATEST date, compared to what we have stored
if ([combinedDate compare:[defaults objectForKey:#"lastObject_time"]] == NSOrderedDescending) {
//Save as most recent time
[defaults setObject:combinedDate forKey:#"lastObject_time"];
[defaults synchronize];
}
} else {
//Before 2 hours, prevent save
}
} else {
//No stored time, allow save
}
Here's my isBetweenDate method
-(BOOL)date:(NSDate*)date isBetweenDate:(NSDate*)beginDate andDate:(NSDate*)endDate {
if ([date compare:beginDate] == NSOrderedAscending)
return NO;
if ([date compare:endDate] == NSOrderedDescending)
return NO;
return YES;
}
Like I said, the times are stored according to their timezone, and I'm not thinking it's a timezone issue. My beta tester has had a few occasions of saving at 7 AM (stored time is 7 AM, blocked until 9 AM), then saves at 12 PM (which is allowed), but then the range doesn't update to 12-2 PM, and is stuck at 7-9 AM), and then they're allowed to save again at 12:01 PM Could there be a reason why it wouldn't work across timezones? Does anyone see anything that jumps out at them? Had similar experiences? Or is there an overall better way to do this?
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.