Edited 07/08/13: Apple has an excellent set of WWDC videos that really helped me understand the various date and time classes in Objective-C, and how to correctly perform time calculations/manipulations with them.
"Solutions to Common Date and Time Challenges" (HD video, SD video, slides (PDF)) (WWDC 2013)
"Performing Calendar Calculations" (SD video, slides (PDF)) (WWDC 2011)
Note: links require a free Apple Developer membership.
In this SO question, I asked how I might go about calculating a certain date and time ("next Sunday at 5 PM"). Thanks to the answers I got, I came up with the following code:
- (NSDate *) toPacificTime
{
NSTimeZone *tz = [NSTimeZone timeZoneWithName:#"America/Los_Angeles"];
NSInteger seconds = [tz secondsFromGMTForDate: self];
return [NSDate dateWithTimeInterval: seconds sinceDate: self];
}
- (void)handleLiveShowReminders
{
NSDate *gmtNow = [NSDate date];
NSDate *now = [gmtNow toPacificTime];
NSCalendar *calendar = [[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] autorelease];
[calendar setTimeZone:[NSTimeZone timeZoneWithName:#"America/Los_Angeles"]];
NSDateComponents *dateComponents = [calendar components:NSWeekdayCalendarUnit fromDate:now];
NSInteger weekday = [dateComponents weekday];
NSInteger daysTillNextSunday = 8 - weekday;
int secondsInDay = 86400; // 24 * 60 * 60
NSDate *nextSunday = [now dateByAddingTimeInterval:secondsInDay * daysTillNextSunday];
NSDateComponents *components = [calendar components:NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit fromDate:nextSunday];
[components setHour:17];
[components setMinute:00];
[components setTimeZone:[NSTimeZone timeZoneWithName:#"America/Los_Angeles"]];
NSDate *nextSunday5PM = [calendar dateFromComponents:components];
warningInterval = -300; // we want the notification to fire 5 minutes beforehand
NSDate *alertDate = [nextSunday5PM dateByAddingTimeInterval:(NSTimeInterval)warningInterval];
UILocalNotification* notifyAlarm = [[[UILocalNotification alloc] init] autorelease];
if (notifyAlarm)
{
notifyAlarm.fireDate = alertDate;
notifyAlarm.timeZone = [NSTimeZone timeZoneWithName:#"America/Los_Angeles"];
notifyAlarm.repeatInterval = NSWeekCalendarUnit;
notifyAlarm.soundName = #"alert.aif";
notifyAlarm.alertBody = #"LIVE SHOW REMINDER: The live show is about to start!";
[[UIApplication sharedApplication] scheduleLocalNotification:notifyAlarm];
}
}
The problem is that, although this code works for me, it's not working for anyone who's not in the PST timezone, as can be seen by this debug output I received from a user in EST:
number of notifications = 1
Notification #1
===================
Body: LIVE SHOW REMINDER: The live show is about to start!
Details: <UIConcreteLocalNotification: 0x1d5f2200>
{fire date = Sunday, December 9, 2012, 4:30:00 PM Pacific Standard Time,
time zone = America/Los_Angeles (PST) offset -28800,
repeat interval = NSWeekCalendarUnit
repeat count = UILocalNotificationInfiniteRepeatCount,
next fire date = Sunday, December 9, 2012, 4:30:00 PM Eastern Standard Time,
user info = (null)}
What am I doing wrong here?
One problem with your logic, just adding 86,400 to your time interval might not be what you want. If for example, the user experiences daylight-savings, then you will be off by an hour, or even a day if it happens near midnight. A better method is to ask the NSCalendar for the result, which takes into account the users local time zone.
NSDate *start = yourDate;
NSDateComponents *oneDay = [NSDateComponents new];
[oneDay setDay:1];
NSCalendar *cal = [NSCalendar currentCalendar];
NSDate *end = [cal dateByAddingComponents:oneDay toDate:start options:0];
And NSDate is relative to UTC/GMT. When you "adjust" if for Pacific time you create utter confusion.
Related
I have a very interesting problem: When calculating the number of days between two dates, my calculator gives the wrong results for the month of March only. I have two text fields, one for each date. If I enter 3/7/12 in date1, and 3/13/12 in date2, the result is 7, which is correct (I am counting the first day as well). But when I enter date1 = 3/7/12 and date2 = 3/14/12, the result is still 7, but it should be 8. Likewise, if I enter date1 = 3/7/12 and date2 = 3/23/12, the result should be 17, but it is 16. If I change the month to April so that date1 = 4/7/12 and date2 = 4/23/12, the result is 17. Every month is working as intended, only the month of March is giving me wrong results. Does anyone have any idea what I am missing? Is this a timezone problem? How do I fix it? Here is my code:
NSDateFormatter *dateFormatter1 = [[NSDateFormatter alloc] init];
[dateFormatter1 setDateFormat:#"MM/dd/yyyy"];
NSDate *startdate1 = [dateFormatter1 dateFromString: date1.text];
NSDateFormatter *dateFormatter2 = [[NSDateFormatter alloc] init];
[dateFormatter2 setDateFormat:#"MM/dd/yyyy"];
NSDate *enddate2 = [dateFormatter2 dateFromString: date2.text];
int start1 = [startdate1 timeIntervalSince1970];
int end2 = [enddate2 timeIntervalSince1970];
double difference12 = end2-start1;
int days12;
days12 =(int)((double)difference12/(3600.0*24.00)+1);
result12.text = [[NSString alloc] initWithFormat:#"%i", days12]
If you use NSCalendar, you won't need to deal with Daylight Savings Time calculation.
int days = [[[NSCalendar currentCalendar] components:NSDayCalendarUnit
fromDate:startDate1
toDate:endDate2
options:0] day] + 1;
Most like this is a daylight savings issue (March 11th, 2012). Set the date formatters' timezones to:
[NSTimeZone timeZoneForSecondsFromGMT:0];
This should fix the issue.
To add to others, please do not rely on number of seconds in a day being 86400 all the time, it won't. Be sure to watch Session 211 Performing Calendar Calculations from WWDC 2011.
You can think of NSDate as a point in time. If you want to calculate number of days between two dates, this is a calendar issue and there are more than one calendars (like the Mayan calendar).
What you need is NSDateComponents:
NSDate *dateToday = [NSDate date];
NSDateComponents *dateBComponent = [[NSDateComponents alloc] init];
[dateBComponent setDay:-10];
NSDate *date10DaysAgo = [[NSCalendar currentCalendar] dateByAddingComponents:dateBComponent
toDate:dateToday
options:0];
NSCalendar *calendar = [[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] autorelease];
NSDateComponents *components = [calendar components:NSDayCalendarUnit
fromDate:date10DaysAgo
toDate:dateToday
options:0];
NSLog(#"Difference: %d", components.day);
I am using the following NSCalendar method to get the number of days in a calendar year:
NSRange range = [gregorian rangeOfUnit:NSDayCalendarUnit
inUnit:NSYearCalendarUnit
forDate:date];
I am expecting a range.length return value of type NSRange which is 365 or 366 (days).
However, this code returns a range.length of 31 for a date value of 2005-06-06.
What is wrong with my code?
This is the full code snippet:
NSCalendar *gregorian = [[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];
[subArray enumerateObjectsUsingBlock:
^(NSDate *date, NSUInteger idx, BOOL *stop) {
NSUInteger numberOfDays = [gregorian ordinalityOfUnit:NSDayCalendarUnit
inUnit:NSYearCalendarUnit forDate:date];
}];
This calculates the number of days of a year of a given date:
NSDate *someDate = [NSDate date];
NSDate *beginningOfYear;
NSTimeInterval lengthOfYear;
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
[gregorian rangeOfUnit:NSYearCalendarUnit
startDate:&beginningOfYear
interval:&lengthOfYear
forDate:someDate];
NSDate *nextYear = [beginningOfYear dateByAddingTimeInterval:lengthOfYear];
NSInteger startDay = [gregorian ordinalityOfUnit:NSDayCalendarUnit
inUnit:NSEraCalendarUnit
forDate:beginningOfYear];
NSInteger endDay = [gregorian ordinalityOfUnit:NSDayCalendarUnit
inUnit:NSEraCalendarUnit
forDate:nextYear];
NSInteger daysInYear = endDay - startDay;
Sad caveat: This does not work correctly for year 1582.
The year 1582, the year when Gregor introduced the currently widespread used calendar, needed a fix to align solar with calendar years. So they went with the pragmatic solution: They just dropped October 5-14. (They were not crazy enough to change weekdays, too). As a result the year 1582 only has 355 days.
Addendum: The code above only works correctly for years after 1582. It returns 365 days for the year 1500, for example, even though this year was a leap year in the then used julian calendar. The gregorian calendar starts at October 15, 1582. Computations made on the gregorian calendar are just not defined before that date. So in this way Apple's implementation is correct. I'm not aware of a correct implementation for years before 1583 on Cocoa.
Here's a relatively clean version. It's a category on NSCalendar.
- (NSInteger) daysInYear:(NSInteger) year {
NSDateComponents *dateComponents = [[NSDateComponents alloc] init];
dateComponents.year = year;
dateComponents.month = dateComponents.day = 1;
NSDate *firstOfYear = [self dateFromComponents:dateComponents];
dateComponents.year = year + 1;
NSDate *firstOfFollowingYear = [self dateFromComponents:dateComponents];
return [[self components:NSDayCalendarUnit
fromDate:firstOfYear
toDate:firstOfFollowingYear
options:0] day];
}
Thanks to AKV for pointing out that -components:fromDate:toDate:options will work in this case.
This doesn't seem to work for 1582 or prior, which per Nikolai Ruhe's answer is because Gregorian calendar calculations simply aren't defined prior to then.
What about finding number of days in a given year as: Although this is not the relative solution for the question.
-(NSInteger)daysBetweenTwoDates:(NSDate *)fromDateTime andDate:(NSDate *)toDateTime{
NSDate *fromDate;
NSDate *toDate;
NSCalendar *calendar = [NSCalendar currentCalendar];
[calendar rangeOfUnit:NSDayCalendarUnit startDate:&fromDate interval:NULL forDate:fromDateTime];
[calendar rangeOfUnit:NSDayCalendarUnit startDate:&toDate interval:NULL forDate:toDateTime];
NSDateComponents *difference = [calendar components:NSDayCalendarUnit fromDate:fromDate toDate:toDate options:0];
return [difference day];
}
-(void)someMethod {
NSString *yourYear=#"2012";
NSDate *date1 = [NSDate dateWithString:[NSString stringWithFormat:#"%#-01-01 00:00:00 +0000",yourYear]];
NSDate *date2 = [NSDate dateWithString:[NSString stringWithFormat:#"%#-12-31 00:00:00 +0000",yourYear]];
NSInteger numberOfDays=[self daysBetweenTwoDates:date1 andDate:date2];
NSLog(#"There are %ld days in between the two dates.", numberOfDays);
}
Edit:
As dateWithString: is deprecated, you can use
NSDateFormatter *dateFormatter=[[NSDateFormatter alloc] init];
[dateFormatter dateFormat]=#"dd-MM-yyyy";
NSDate *date=[dateFormatter dateFromString:dateString];
to form date1 and date2
You are getting the number of days in the month of your date.
You could use the same function and just pass in a date with February as the month. If range.length returns 28, the Total days in the year will be 365, if it returns 29, then the number of days will be 366.
I found the codes to calculate days difference between two dates here.
I write a method :
-(NSInteger)daysWithinEraFromDate:(NSDate *) startDate toDate:(NSDate *) endDate
{
NSCalendar *gregorian = [[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];
NSInteger startDay=[gregorian ordinalityOfUnit:NSDayCalendarUnit
inUnit: NSEraCalendarUnit forDate:startDate];
NSInteger endDay=[gregorian ordinalityOfUnit:NSDayCalendarUnit
inUnit: NSEraCalendarUnit forDate:endDate];
return endDay-startDay;
}
This method has a problem: it can't consider the timezone thing. Even I add a line like this:
[gregorian setTimeZone:[NSTimeZone localTimeZone]];
My test code is like this:
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
NSString *strDate = #"2012-09-03 23:00:00";
NSDate *dateStart = [dateFormat dateFromString:strDate];
strDate = #"2012-09-04 01:00:00";
NSDate *dateEnd = [dateFormat dateFromString:strDate];
NSLog(#"Days difference between %# and %# is: %d days",[dateFormat stringFromDate:dateStart],[dateFormat stringFromDate:dateEnd],[self daysWithinEraFromDate:dateStart toDate:dateEnd]);
The result is:
Days difference between 2012-09-03 23:00:00 and 2012-09-04 01:00:00 is: 0 days
I want to get 1 day as result by the number of midnights between the two dates. My timezone is GMT +8. But this calculation is based on GMT, so I get the wrong days number. Is there anyway to solve this problem? Thank you.
Scott Lemmon's method can solve my problem. I rewrite my code like this:
-(NSInteger)daysWithinEraFromDate:(NSDate *) startDate toDate:(NSDate *) endDate
{
NSCalendar *gregorian = [[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];
[gregorian setTimeZone:[NSTimeZone localTimeZone]];
NSDate *newDate1 = [startDate dateByAddingTimeInterval:[[NSTimeZone localTimeZone] secondsFromGMT]];
NSDate *newDate2 = [endDate dateByAddingTimeInterval:[[NSTimeZone localTimeZone] secondsFromGMT]];
NSInteger startDay=[gregorian ordinalityOfUnit:NSDayCalendarUnit
inUnit: NSEraCalendarUnit forDate:newDate1];
NSInteger endDay=[gregorian ordinalityOfUnit:NSDayCalendarUnit
inUnit: NSEraCalendarUnit forDate:newDate2];
return endDay-startDay;
}
If the time zone offset isn't working, how about just add or subtract it manually instead?
In your case NSDate *newDate = [oldDate dateByAddingTimeInterval:(-8 * 60 * 60)]; to subtract off your +8 hours.
Or if you want to find the GMT offset automatically as well then it would simply be NSDate *newDate = [oldDate dateByAddingTimeInterval:(-[[NSTimeZone localTimeZone] secondsFromGMT])
Another thought:
A perhaps easier solution would be to just disregard the time information altogether. Just set it to the same arbitrary number for both dates, then as long as the dates come from the same timezone you will always get the correct number of mid-nights between them, regardless of GMT offset.
What you really want is the NSDate method timeIntervalSinceDate:, and take that result and if it's more than 0 but less than 86400 (the number of seconds in a day), that's one day. Otherwise, divide your result by 86400 and you'll get the number of days.
The way you currently have your code, there's only 2 hours between the two days and that's why you are seeing a result of 0 and not one.
Edit - and to determine if midnight has happened, let's try this function I just wrote off the top of my head:
- (NSDate *) getMidnightDateFromDate: (NSDate *) originalDate
{
NSDateComponents *components = [[NSCalendar currentCalendar] components:NSIntegerMax fromDate:originalDate];
[components setHour:0];
[components setMinute:0];
[components setSecond:0];
NSDate *midnight = [[NSCalendar currentCalendar] dateFromComponents:components];
return(midnight);
}
- (BOOL) howManyDaysDifferenceBetween: startDate and: endDate
{
NSDate * firstMidnight = [self getMidnightDateFromDate: startDate];
NSDate * secondMidnight = [self getMidnightDateFromDate: endDate];
NSTimeInterval timeBetween = [firstMidnight timeIntervalSinceDate: secondMidnight];
NSInteger numberOfDays = (timeBetween / 86400);
return(numberOfDays);
}
which I'm basing off Dave Delong's answer to this question. No guarantees that my code will work (I didn't test it), but I think the concept is sound.
Does anyone know if there is a way to set the first day of the week on a NSCalendar, or is there a calendar that already has Monday as the first day of the week, instead of Sunday.
I'm currently working on an app that is based around a week's worth of work, and it needs to start on Monday, not Sunday. I can most likely do some work to work around this, but there will be a lot of corner cases. I'd prefer the platform do it for me.
Thanks in advance
Here's some the code that I'm using. it's saturday now, so what I would hope is that weekday would be 6, instead of 7. that would mean that Sunday would be 7 instead of rolling over to 0
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
[gregorian setFirstWeekday:0];
unsigned unitFlags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSWeekCalendarUnit | NSWeekdayCalendarUnit;
NSDateComponents *todaysDate = [gregorian components:unitFlags fromDate:[NSDate date]];
int dayOfWeek = todaysDate.weekday;
Edit: This does not check the edge case where the beginning of the week starts in the prior month. Some updated code to cover this: https://stackoverflow.com/a/14688780/308315
In case anyone is still paying attention to this, you need to use
ordinalityOfUnit:inUnit:forDate:
and set firstWeekday to 2. (1 == Sunday and 7 == Saturday)
Here's the code:
NSCalendar *gregorian = [[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] autorelease];
[gregorian setFirstWeekday:2]; // Sunday == 1, Saturday == 7
NSUInteger adjustedWeekdayOrdinal = [gregorian ordinalityOfUnit:NSWeekdayCalendarUnit inUnit:NSWeekCalendarUnit forDate:[NSDate date]];
NSLog(#"Adjusted weekday ordinal: %d", adjustedWeekdayOrdinal);
Remember, the ordinals for weekdays start at 1 for the first day of the week, not zero.
Documentation link.
This code constructs a date that is set to Monday of the current week:
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDate *today = [NSDate date];
NSDate *beginningOfWeek = nil;
BOOL ok = [gregorian rangeOfUnit:NSWeekCalendarUnit startDate:&beginningOfWeek
interval:NULL forDate: today];
setFirstWeekday: on the NSCalendar object.
Sets the index of the first weekday for the receiver.
- (void)setFirstWeekday:(NSUInteger)weekday
Should do the trick.
In my opinion this settings should be dynamic according to the user locale.
Therefore one should use:
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
[gregorian setLocale:[NSLocale currentLocale]];
This will cause the calendar to set the first week day according to the user locale automatically. Unless you are developing your app for a specific purpose/user locale (or prefer to allow the user to choose this day).
I've done it like this.
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDate *today = [NSDate date];
NSDateComponents *compForWeekday = [gregorian components:(NSWeekdayCalendarUnit) fromDate:today];
NSInteger weekDayAsNumber = [compForWeekday weekday]; // The week day as number but with sunday starting as 1
weekDayAsNumber = ((weekDayAsNumber + 5) % 7) + 1; // Transforming so that monday = 1 and sunday = 7
I had trouble with a lot of the answers here. . maybe it was just me. .
Here's an answer that works for me:
- (NSDate*)firstDayOfWeek
{
NSCalendar* cal = [[NSCalendar currentCalendar] copy];
[cal setFirstWeekday:2]; //Override locale to make week start on Monday
NSDate* startOfTheWeek;
NSTimeInterval interval;
[cal rangeOfUnit:NSWeekCalendarUnit startDate:&startOfTheWeek interval:&interval forDate:self];
return startOfTheWeek;
}
- (NSDate*)lastDayOfWeek
{
NSCalendar* cal = [[NSCalendar currentCalendar] copy];
[cal setFirstWeekday:2]; //Override locale to make week start on Monday
NSDate* startOfTheWeek;
NSTimeInterval interval;
[cal rangeOfUnit:NSWeekCalendarUnit startDate:&startOfTheWeek interval:&interval forDate:self];
return [startOfTheWeek dateByAddingTimeInterval:interval - 1];
}
Update:
As pointed out (elsewhere) by #vikingosegundo, in general its best to let the local determine which day is the start of the week, however in this case the OP was asking for the start of the week to occur on Monday, hence we copy the system calendar, and override the firstWeekDay.
The problem with Kris' answer is the edge case where the beginning of the week starts in the prior month. Here's some easier code and it also checks the edge case:
// Finds the date for the first day of the week
- (NSDate *)getFirstDayOfTheWeekFromDate:(NSDate *)givenDate
{
NSCalendar *calendar = [NSCalendar currentCalendar];
// Edge case where beginning of week starts in the prior month
NSDateComponents *edgeCase = [[NSDateComponents alloc] init];
[edgeCase setMonth:2];
[edgeCase setDay:1];
[edgeCase setYear:2013];
NSDate *edgeCaseDate = [calendar dateFromComponents:edgeCase];
NSDateComponents *components = [calendar components:NSYearCalendarUnit|NSMonthCalendarUnit|NSWeekCalendarUnit|NSWeekdayCalendarUnit fromDate:edgeCaseDate];
[components setWeekday:1]; // 1 == Sunday, 7 == Saturday
[components setWeek:[components week]];
NSLog(#"Edge case date is %# and beginning of that week is %#", edgeCaseDate , [calendar dateFromComponents:components]);
// Find Sunday for the given date
components = [calendar components:NSYearCalendarUnit|NSMonthCalendarUnit|NSWeekCalendarUnit|NSWeekdayCalendarUnit fromDate:givenDate];
[components setWeekday:1]; // 1 == Sunday, 7 == Saturday
[components setWeek:[components week]];
NSLog(#"Original date is %# and beginning of week is %#", givenDate , [calendar dateFromComponents:components]);
return [calendar dateFromComponents:components];
}
I see misunderstanding in the other messages. The first weekday, whichever it is, has number 1 not 0. By default Sunday=1 as in the "Introduction to Date and Time Programming Guide for Cocoa: Calendrical Calculations":
"The weekday value for Sunday in the Gregorian calendar is 1"
For the Monday as a first workday the only remedy I have is brute force condition to fix the calculation
NSCalendar *cal=[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *comps = [cal components:NSWeekdayCalendarUnit fromDate:[NSDate date]];
// set to 7 if it's Sunday otherwise decrease weekday number
NSInteger weekday=[comps weekday]==1?7:[comps weekday]-1;
Below also covers the edge case,
- (NSDate *)getFirstDayOfTheWeekFromDate:(NSDate *)givenDate
{
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *components = [calendar components:NSYearCalendarUnit|NSMonthCalendarUnit|NSWeekCalendarUnit|NSWeekdayCalendarUnit fromDate:givenDate];
[components setWeekday:2]; // 1 == Sunday, 7 == Saturday
if([[calendar dateFromComponents:components] compare: curDate] == NSOrderedDescending) // if start is later in time than end
{
[components setWeek:[components week]-1];
}
return [calendar dateFromComponents:components];
}
You can just change .firstWeekday of the calendar.
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
calendar.firstWeekday = 2;
Then use rangeOfUnit:startDate:interval:forDate: to get the first day
NSDate *startOfWeek;
[calendar rangeOfUnit:NSCalendarUnitWeekOfYear startDate:&startOfWeek interval:nil forDate:[NSdate date]];
Try this:
NSCalendar *yourCal = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]
[yourCal setFirstWeekday:0];
Iv found out the way to display any weekday name using nscalender..using the following code..
Just open your console from xcode menu bar to see the results.Copy Paste the following code in your viewDidLoad method to get the first day of the week
NSDate *today = [NSDate date];
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"MM/dd/yyyy :EEEE"];
NSString *dateString = [dateFormat stringFromDate:today];
NSLog(#"date: %#", dateString);
[dateFormat release];
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *components = [gregorian components:NSWeekdayCalendarUnit | NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit fromDate:today];
[components setDay:([components day]-([components weekday]-1))];
NSDate *beginningOfWeek = [gregorian dateFromComponents:components];
NSDateFormatter *dateFormat_first = [[NSDateFormatter alloc] init];
[dateFormat_first setDateFormat:#"MM/dd/yyyy :EEEE"];
NSString *dateString_first = [dateFormat_first stringFromDate:beginningOfWeek];
NSLog(#"First_date: %#", dateString_first);
The Output will be:
date: 02/11/2010 :Thursday
First_date: 02/07/2010 :Sunday
since i had run this program on 2/11/2010 u will get the desired output depending on the current date.
Similarly if u want to get the first working day of the week i.e Monday's date then just modify the code a bit:
CHANGE :[components setDay:([components day]-([components weekday]-1))];
TO
[components setDay:([components day]-([components weekday]-2))];
to get Mondays date for that week..
Similarly u can try to find the date of any of seven workdays by changing the integer -1,-2 and so on...
Hope u r question is answered..
Thanks,
Bonson Dias
The ISO 8601 calendar appears to have it's first weekday set to monday by default.
Using the Calendar nextWeekend (iOS 10 or later) and ordinality (thanks #kris-markel). I've gotten Monday as first of the week for the en_US calendar.
Here is an example of it with fallback to firstWeekday:
extension Calendar {
var firstWorkWeekday: Int {
guard #available(iOS 10.0, *) else{
return self.firstWeekday
}
guard let endOfWeekend = self.nextWeekend(startingAfter: Date())?.end else {
return self.firstWeekday
}
return self.ordinality(of: .weekday, in: .weekOfYear, for: endOfWeekend) ?? self.firstWeekday
}
}
The Swift solution (note, use .yearForWeekOfYear, not .year):
let now = Date()
let cal = Calendar.current
var weekComponents = cal.dateComponents([.yearForWeekOfYear, .weekOfYear,
.weekday], from: now)
//weekComponents.weekday = 1 // if your week starts on Sunday
weekComponents.weekday = 2 // if your week starts on Monday
cal.date(from: weekComponents) // returns date with first day of the week
… is there a calendar that already has Monday as the first day of the week, instead of Sunday.
Someday, there will be.
My simple way of doing this is to get Monday = 0, Sunday = 6:
NSDateComponents *dateComponents = [[NSCalendar currentCalendar] components:NSWeekdayCalendarUnit fromDate:[NSDate date]];
NSInteger dayNumStartingFromMonday = ([dateComponents weekday] - 2 + 7) % 7; //normal: Sunday is 1, Monday is 2
I want to set the NSDate time with my desired hours:minutes:seconds
currently im working with NSDate component but it is not giving the desired result
[comps setHour: -hours];
[comps setMinute:0];
[comps setSecond:0];
NSDate *minDate = [calendar_c dateFromComponents:comps];
This works great as an NSDate category.
/** Returns a new NSDate object with the time set to the indicated hour,
* minute, and second.
* #param hour The hour to use in the new date.
* #param minute The number of minutes to use in the new date.
* #param second The number of seconds to use in the new date.
*/
-(NSDate *) dateWithHour:(NSInteger)hour
minute:(NSInteger)minute
second:(NSInteger)second
{
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *components = [calendar components: NSYearCalendarUnit|
NSMonthCalendarUnit|
NSDayCalendarUnit
fromDate:self];
[components setHour:hour];
[components setMinute:minute];
[components setSecond:second];
NSDate *newDate = [calendar dateFromComponents:components];
return newDate;
}
With the above category, if you have an existing date you want to change the time on, you do so like this:
NSDate *newDate = [someDate dateWithHour:10 minute:30 second:00];
If, however, you are trying to add or subtract hours from an existing date, a category method to do that is also straightforward:
/** Returns a new date with the given number of hours added or subtracted.
* #param hours The number of hours to add or subtract from the date.
*/
-(NSDate*)dateByAddingHours:(NSInteger)hours
{
NSDateComponents *components = [[NSDateComponents alloc] init];
[components setHour:hours];
return [[NSCalendar currentCalendar]
dateByAddingComponents:components toDate:self options:0];
}
Your approach should work fine. I needed a solution for this type problem (setting the individual date components) and the following code works as expected for me. My situation: I wanted to create a date object that used the current date but had the time set to a value that was passed in as a string.
NSString *string = #"7:00";
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:#"en_US_POSIX"];
NSDateFormatter *timeOnlyFormatter = [[NSDateFormatter alloc] init];
[timeOnlyFormatter setLocale:locale];
[timeOnlyFormatter setDateFormat:#"h:mm"];
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDate *today = [NSDate date];
NSDateComponents *todayComps = [calendar components:(NSDayCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit) fromDate:today];
NSDateComponents *comps = [calendar components:(NSHourCalendarUnit | NSMinuteCalendarUnit) fromDate:[timeOnlyFormatter dateFromString:string]];
comps.day = todayComps.day;
comps.month = todayComps.month;
comps.year = todayComps.year;
NSDate *date = [calendar dateFromComponents:comps];
[calendar release];
[timeOnlyFormatter release];
[locale release];
One thing to note is that you really have to pay attention to time zones when you are judging whether a time appears to be accurate. For example, in my app, when you stop at a breakpoint, you will see the time in GMT (so it looks different than the input time, which is in my local time), but when the time is actually displayed on screen in the app, it is being formatted to display in the local timezone. You may need to take this into consideration to determine whether the result is actually different from what you would expect.
If this does not help, can you elaborate on "not giving the desired result"? What result is it giving and how does that compare to what you expected?
is Swift2
extension NSDate {
func dateWithHour (hour: Int, minute:Int, second:Int) ->NSDate?{
let calendar = NSCalendar.currentCalendar(),
components = calendar.components([.Day,.Month,.Year], fromDate: self)
components.hour = hour;
components.minute = minute;
components.second = second;
return calendar.dateFromComponents(components)
}
}
You can set 0 to hour, min, and second.
NSDateFormatter *tFmt = [[NSDateFormatter alloc] init];
tFmt.dateFormat = #"yyyy-MM-dd";
NSString *strNowDate = [NSString stringWithFormat:#"%# 00:00:00",[tFmt stringFromDate:[NSDate date]]];
NSDate *nowDate = [NSDate dateWithString:strNowDate formatString:#"yyyy-MM-dd HH:mm:ss"];
Swift 5 solution (based on #dattk answer) for those who fear Deprecation warnings :)
func date(withHour hour: Int, withMinute minute: Int, withSeconds second: Int) -> Date? {
let now = Date()
let calendar = NSCalendar.current
var components = calendar.dateComponents([.day,.month,.year], from: now)
components.hour = hour
components.minute = minute
components.second = second
return calendar.date(from: components)
}
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSDateComponents *comps = [calendar components:(NSCalendarUnitHour | NSCalendarUnitMinute) fromDate:[NSDate date]];
comps.hour = 0;
comps.minute = 15;
NSDate *date = [calendar dateFromComponents:comps];
iOS 8