Match a given day and month in any year in an NSDate? - ios

I’d like to set up an NSDate object which can tell me whether a specified date is between two other dates, but disregard year. I have a little something in my app which does something special at Christmas and I’d like to future-proof it for subsequent years. I’m using the following code to check if the current date is between December 1st and December 31st, but I’ve had to specify the year (2013).
I’m not too sure how to go about modifying it to work for any year – since the dates are converted into plain numeric values, can it even be done?
+ (NSDate *)dateForDay:(NSInteger)day month:(NSInteger)month year:(NSInteger)year
{
NSDateComponents *comps = [NSDateComponents new];
[comps setDay:day];
[comps setMonth:month];
[comps setYear:year];
return [[NSCalendar currentCalendar] dateFromComponents:comps];
}
- (BOOL)laterThan:(NSDate *)date
{
if (self == date) {
return NO;
} else {
return [self compare:date] == NSOrderedDescending;
}
}
- (BOOL)earlierThan:(NSDate *)date
{
if (self == date) {
return NO;
} else {
return [self compare:date] == NSOrderedAscending;
}
}

It sounds like all you need to be able to do is determine if an NSDate falls in December. I believe you can do it like this:
NSDate * now = [NSDate date];
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *nowComponents = [gregorian components:NSMonthCalendarUnit fromDate:now];
if ([nowComponents month] == 12) {
// It's December, do something
}
If you don't want to be limited to an entire month you could get the month and day components of your current date.

To tell if a date is later in the year than some given date I'd use something like this:
// yearlessDate is in the form MMddHHmmss
+BOOL date:(NSDate*)theDate isLaterInYearThan:(NSString*)yearlessDate {
NSDateFormatter* fmt = [[NSDateFormatter alloc] init];
fmt.dateFormat = #"MMddHHmmss";
NSString* theDateFormatted = [fmt stringFromDate:theDate];
return [theDateFormatted compareTo:yearlessDate] == NSOrderedDescending;
}
The usual caveats apply re timezone, 12/24 device setting, etc. It would be most optimal to make the DateFormatter a static object.

Related

Set NSDates days to same value

I want to compare 2 specific days, but, i want to compare month and year, not days. For example, i got one date something like 2016-08-16, and other date like 2016-08-10, i need to compare dates and got equality, because their month are equal.
Therefore, i need a way to make both NSDates with equal days, so i need to make 2016-08-10 to be 2016-08-00. After that i can modify second date as well and make comparison.
I have simple code snipper for equality, but i suppose it consider days and hours, etc. as well:
if ([dateOld compare:dateNew] == NSOrderedDescending) {
NSLog(#"date1 is later than date2");
} else if ([dateOld compare:dateNew] == NSOrderedAscending) {
NSLog(#"date1 is earlier than date2");
} else {
NSLog(#"dates are the same");
}
To make it work i need to "zero" days and time of NSDates. Is there any way to do that?
See this SO answer: Comparing certain components of NSDate?
NSCalendar *calendar = [NSCalendar currentCalendar];
NSInteger desiredComponents = (NSMonthCalendarUnit | NSYearCalendarUnit);
NSDate *firstDate = ...; // one date
NSDate *secondDate = ...; // the other date
NSDateComponents *firstComponents = [calendar components:desiredComponents fromDate:firstDate];
NSDateComponents *secondComponents = [calendar components:desiredComponents fromDate:secondDate];
NSDate *truncatedFirst = [calendar dateFromComponents:firstComponents];
NSDate *truncatedSecond = [calendar dateFromComponents:secondComponents];
NSComparisonResult result = [truncatedFirst compare:truncatedSecond];
if (result == NSOrderedAscending) {
//firstDate is before secondDate
} else if (result == NSOrderedDescending) {
//firstDate is after secondDate
} else {
//firstDate is the same month/year as secondDate
}
If you have two dates,
NSDate *now = // some date
NSDate *other = // some other date
You can do this comparison as:
if ([[NSCalendar currentCalendar] compareDate:now toDate:other toUnitGranularity:NSCalendarUnitMonth] == NSOrderedSame) {
NSLog(#"Same month");
} else {
NSLog(#"Different month");
}

Check if time of NSDate object has passed

Im trying to check if the time of an NSDate has passed. Ive used the following but that obviously has the year so therefore it has passed. How would I check just the time of the NSDate object, only Hour and minutes are important:
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"HH:mm:ss"];
[dateFormat setLocale:[[NSLocale alloc] initWithLocaleIdentifier:#"en_US_POSIX"]];
NSDate *date = [dateFormat dateFromString:openingDay.endTime];
if ([date timeIntervalSinceNow] < 0.0) {
}
But the date is:
2000-01-01 5:30:00 p.m. +0000
The time string has the format 'HH:mm:ss'
I tried looking for the answer as I thought it would be common but no luck.
Edit:
I should have elaborated some more, I get a time from a server which is a string. I want to then check if this time is past. The string is an end time of a shops opening hours, I therefore want to check if the shop has closed.
Edit2:
the string I get from the Json object is end_time:17:30:00. I then want to check if the current time is after this time, if so, then show a closed UIAlertView. My approach was to take this string and turn it into a date object and compare it to the current time. However when I convert it to an NSDate object the year is 2000, which is obviously in the past. I hope I have provided enough.
To get the hour and minutes component, you do this:
NSDateComponents *components = [[NSCalendar currentCalendar] components:(NSCalendarUnitHour | NSCalendarUnitMinute) fromDate:date];
NSInteger hour = [components hour];
NSInteger minute = [components minute];
You can then create helper methods to keep the code organized:
- (NSInteger)hourFromDate:(NSDate *)date {
return [[NSCalendar currentCalendar] component:(NSCalendarUnitHour) fromDate:date];
}
- (NSInteger)minuteFromDate:(NSDate *)date {
return [[NSCalendar currentCalendar] component:(NSCalendarUnitMinute) fromDate:date];
}
note that component:fromDate:returns directly that component (as NSInteger), since it can only take one component type per parameter, while components:fromDate: returns a NSDateComponents, which then you can grab multiple components.
and then just:
NSDate *endDate = [dateFormat dateFromString:openingDay.endTime];
NSDate *today = [NSDate date];
NSInteger endDateHour = [self hourFromDate:endDate];
NSInteger endDateMinute = [self minuteFromDate:endDate];
NSInteger todayHour = [self hourFromDate:date];
NSInteger todayMinute = [self minuteFromDate:date];
BOOL hasEndMinutePassed = endDateMinute > todayMinute;
BOOL hasEndHourPassed = endDateHour > todayHour;
if ((hasEndHourPassed) || (endDateHour == todayHour && hasEndMinutePassed)) {
//Yep, it passed
} else {
//Nope, it didn't
}
I wrote it like this to keep things organized.
You could also write a category:
Header file:
#interface NSDate (Components)
- (NSInteger)hour;
- (NSInteger)minutes;
#end
Implementation file:
#import "NSDate+Components.h"
#implementation NSDate (Components)
- (NSInteger)hour {
return [[NSCalendar currentCalendar] component:NSCalendarUnitHour fromDate:self];
}
- (NSInteger)minute {
return [[NSCalendar currentCalendar] component:NSCalendarUnitMinute fromDate:self];
}
You can go a bit further than that and add the comparation logic inside the Category itself:
Adding this to the header:
- (BOOL)hourAndMinutesPassedFromDate:(NSDate *)date;
and then the implementation:
- (BOOL)hourAndMinutesPassedFromDate:(NSDate *)date {
BOOL hasEndMinutePassed = [self minute] > [date minute];
BOOL hasEndHourPassed = [self hour] > [date hour];
return ((hasEndHourPassed) || ([self hour] == [date hour] && hasEndMinutePassed));
}
Thats it. I didn't test the logic itself (but it should be accurate, i used something like this before), and of course you are free to modify this to fit your needs.

Date components day difference between two dates wrong for today and tomorrow

I want to show "Today" or "Tomorrow" in a label if a due date is actually today or tomorrow, and show it in "dd-mm-yyy" format for the rest.
Everything works almost perfectly apart from the fact that (given that today is 7 Jun):
If I set the due date to today (7 Jun) or tomorrow (8 Jun), the label is updated with the text "Today".
If I set the due date to the day after tomorrow (9 Jun), it shows "Tomorrow" .
This is my code:
- (void)configureDueLabelForCell:(UITableViewCell *)cell withChecklistItem:(ChecklistItem *)item
{
UILabel *label = (UILabel *)[cell viewWithTag:1002];
if (item.shouldRemind) {
int difference = [self dateDiffrenceToDate:item.dueDate];
if (difference == 0) {
label.text = #"Today";
} else if (difference == 1) {
label.text = #"Tomorrow";
} else {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
// [formatter setDateStyle:NSDateFormatterMediumStyle];
[formatter setDateFormat:#"dd-MM-yyyy"];
label.text = [formatter stringFromDate:item.dueDate];
}
} else {
label.text = #"";
}
}
-(int)dateDiffrenceToDate:(NSDate *)dueDate
{
// Manage Date Formation same for both dates
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"dd-MM-yyyy"];
NSDate *startDate = [NSDate date];
NSDate *endDate = dueDate;
unsigned flags = NSDayCalendarUnit;
NSDateComponents *difference = [[NSCalendar currentCalendar] components:flags fromDate:startDate toDate:endDate options:0];
int dayDiff = [difference day];
return dayDiff;
}
I also tried:
// NSDate *startDate = [NSDate date];
// NSDate *endDate = dueDate;
//
// NSTimeInterval secondsBetween = [endDate timeIntervalSinceDate:startDate];
//
// int numberOfDays = secondsBetween / 86400;
// NSLog(#"numberofdays: %d", numberOfDays);
//
// return numberOfDays;
From https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/DatesAndTimes/Articles/dtCalendricalCalculations.html#//apple_ref/doc/uid/TP40007836-SW1
"... this method truncates the results of the calculation to the smallest unit supplied. For instance, if the fromDate: parameter corresponds to Jan 14, 2010 at 11:30 PM and the toDate: parameter corresponds to Jan 15, 2010 at 8:00 AM, there are only 8.5 hours between the two dates. If you ask for the number of days, you get 0, because 8.5 hours is less than 1 day. There may be situations where this should be 1 day. You have to decide which behavior your users expect in a particular case. If you do need to have a calculation that returns the number of days, calculated by the number of midnights between the two dates, you can use a category to NSCalendar similar to the one in Listing 13."

How do I accommodate opening and closing dates NSDate comparator on separate days?

Comparing dates is quite complex. I have an app that is comparing opening and closing dates for stores and it works great for times in the same day, i.e. opening a 8am and closing at 5pm the same day.
Here is the code that compares the time:
if ([self timeCompare:openDate until:closeDate withNow:now]) {
NSLog(#"TIMECOMPARATOR = timeCompare>OPEN");
status = YES;
} else {
NSLog(#"TIMECOMPARATOR = timeCompare>CLOSED");
status = NO;
}
return status;
This calls the following method:
+(BOOL)timeCompare:(NSDate*)date1 until:(NSDate*)date2 withNow:(NSDate*)now{
NSLog(#"TIMECOMPARE = open:%# now:%# close:%#", date1, now, date2);
return ([date1 compare:now] == NSOrderedAscending && [date2 compare:now] == NSOrderedDescending);
}
The problem comes when the closing time is "assumed" by a person but of course not by a computer, to close at the next day, such as 7am to 2am. I obviously mean the next day. How do I accommodate for this to signal the computer to be the next day?
Compare the date's unix time. It will be accurate regardless of date as it is constantly increasing.
First you have to convert the strings "7:00 AM", "10:00 PM" to a NSDate from the current day. This can be done e.g. with the following method:
- (NSDate *)todaysDateFromAMPMString:(NSString *)time
{
NSDateFormatter *fmt = [[NSDateFormatter alloc] init];
[fmt setLocale:[NSLocale localeWithLocaleIdentifier:#"en_US_POSIX"]];
// Get year-month-day for today:
[fmt setDateFormat:#"yyyy-MM-dd "];
NSString *todayString = [fmt stringFromDate:[NSDate date]];
// Append the given time:
NSString *todaysTime = [todayString stringByAppendingString:time];
// Convert date+time string back to NSDate:
[fmt setDateFormat:#"yyyy-MM-dd h:mma"];
NSDate *date = [fmt dateFromString:todaysTime];
return date;
}
Then you can proceed as in https://stackoverflow.com/a/20441330/1187415:
// Some example values:
NSString *strOpenTime = #"10:00 PM";
NSString *strCloseTime = #"2:00 AM";
NSDate *openTime = [self todaysDateFromAMPMString:strOpenTime];
NSDate *closeTime = [self todaysDateFromAMPMString:strCloseTime];
if ([closeTime compare:openTime] != NSOrderedDescending) {
// closeTime is less than or equal to openTime, so add one day:
NSCalendar *cal = [NSCalendar currentCalendar];
NSDateComponents *comp = [[NSDateComponents alloc] init];
[comp setDay:1];
closeTime = [cal dateByAddingComponents:comp toDate:closeTime options:0];
}
NSDate *now = [NSDate date];
if ([now compare:openTime] != NSOrderedAscending &&
[now compare:closeTime] != NSOrderedDescending) {
// Shop is OPEN
} else {
// Shop is CLOSED
}
This is an app design question, not a coding question. You need to define a clear vocabulary for the user to communicate what they mean. I would suggest adding a UISwitch to the screen, with 2 positions on it: "Today" and "Tomorrow". You can add some program logic that takes a guess as to times where you think the user is talking about a date tomorrow, and set the default switch to the "tomorrow" state in that case, but the user should be able to tell you what they mean.
How about you just add a check whether all the three NSDate are having the same date (DDMMYYYY) value?

Convert week number for a certain year to a month name

After searching through SO but apart from this question I found no solutions. I'm thinking about creating a method that would accept the int of the week number and the int of the year and that would return an NSString with the name of the month:
- (NSString *)getMonthNameFromNumber:(int)weekNumber andYear:(int)year
But I can't find a way to approach this problem. Would be glad if anyone could help with advices.
Something like this will do
- (NSString *)monthNameForWeek:(NSUInteger)week inYear:(NSInteger)year {
NSDateComponents * dateComponents = [NSDateComponents new];
dateComponents.year = year;
dateComponents.weekOfYear = week;
dateComponents.weekday = 1; // 1 indicates the first day of the week, which depends on the calendar
NSDate * date = [[NSCalendar currentCalendar] dateFromComponents:dateComponents];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"MMMM"];
return [formatter stringFromDate:date];
}
Note that this is dependent on the current calendar set in the device preferences.
In case this doesn't fit your needs, you can provide a NSCalendar instance and use it to retrieve the date instead of using currentCalendar. By doing so you can configure things like which is the first day of the week and so on. The documentation of NSCalendar is worth a read.
If using a custom calendar is a common case, just change the implementation to something like
- (NSString *)monthNameForWeek:(NSUInteger)week inYear:(NSInteger)year {
[self monthNameForWeek:week inYear:year calendar:[NSCalendar currentCalendar]];
}
- (NSString *)monthNameForWeek:(NSUInteger)week inYear:(NSInteger)year calendar:(NSCalendar *)calendar {
NSDateComponents * dateComponents = [NSDateComponents new];
dateComponents.year = year;
dateComponents.weekOfYear = week;
dateComponents.weekday = 1; // 1 indicates the first day of the week, which depends on the calendar
NSDate * date = [calendar dateFromComponents:dateComponents];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"MMMM"];
return [formatter stringFromDate:date];
}
As an unrelated side note, you should avoid get for methods names, unless you are returning a value indirectly.
With anything to do with dates, you need to involve a calendar. Your question assumes the Gregorian Calendar, but I suggest you change your method declaration to:
- (NSString*)monthNameFromWeek:(NSInteger)week year:(NSInteger)year calendar:(NSCalendar*)cal;
From this, there is also the ambiguity of which day we're talking about. For example (this hasn't been checked), week 4 of 2015 may contain both January and February. Which one is correct? For this example, we'll use a weekday of 1, which indicates Sunday (in the UK Gregorian Calendar), and we'll use whatever month this falls in to.
As such, your code would be:
// Set up our date components
NSDateComponents* comp = [[NSDateComponents alloc] init];
comp.year = year;
comp.weekOfYear = week;
comp.weekday = 1;
// Construct a date from components made, using the calendar
NSDate* date = [cal dateFromComponents:comp];
// Create the month string
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"MMMM"];
return [dateFormatter stringFromDate:date];

Resources