NSDateComponents: How to replace deprecated NSWeekCalendarUnit to calc week start date? - ios

I am currently updating an old iOS App that uses NSDateComponents to calculate the start date of the week a given NSDate is in:
NSDate* someDate = [MyDateFactory dateFromDay:31 month:8 year:2017];
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *comps = [calendar components:(NSYearCalendarUnit | NSWeekCalendarUnit) fromDate:someDate];
// For 2017-08-31 comps is now year=2017, week (obsolete)=35
comps.calendar = calendar;
NSDate* weekStartDate = [comps date];
// weekStartDate = 2017-08-27
This works fine, but NSYearCalendarUnit and NSWeekCalendarUnit are both deprecated. Replacing NSYearCalendarUnit with the new NSCalendarUnitYear is no problem. But what it the correct replacemtn for NSWeekCalendarUnit.
It seems that NSWeekCalendarUnit returns the same value (week of year) as the new NSCalendarUnitWeekOfYear. But this does not result in the same weekStartDate:
// ... same as above. Now use new enum values
NSDateComponents *comps = [calendar components:(NSCalendarUnitYear | NSCalendarUnitWeekOfYear) fromDate:someDate];
// For 2017-08-31 comps is now year=2017, week of year=35 <== Same es above
comps.calendar = calendar;
NSDate* weekStartDate = [comps date];
// weekStartDate = 2017-01-01
So using NSCalendarUnitWeekOfYear returns the same week value (week 35), but it seems that this value is not considered when calculating the date from the the NSDateComponents object. 2017-01-01 is obviously not the date week 35 starts at in 2017.
So, how to solve this?

You can use other methods of NSCalendar instead.
To get the day on which weeks start, use calendar.firstWeekday.
Then put this into some date components:
NSDateComponents *search = [[NSDateComponents alloc] init];
search.weekDay = calendar.firstWeekday;
Now you can use the calendar to find the previous date which matches the components:
NSDate *weekStart = [calendar nextDateAfterDate: someDate matchingComponents: search options: NSCalendarSearchBackwards];

Related

NSCalendar dateFromComponents: gives unexpected results

I'm trying to write a method that takes an NSDate as input and returns the first day of the week that the date falls in. I've been using NSDateComponents to try and set the year, week of year, and day of week; but I get unexpected behavior. For example :
NSDateComponents* comps = [[NSDateComponents alloc]init];
[comps setYear:2015];
[comps setWeekday:1];
[comps setWeekOfYear:1];
NSLog(#"%#\n%#\n\n", comps, [formatter stringFromDate:[calendar dateFromComponents:comps]]);
Results in:
<NSDateComponents: 0x7fbc24cc9870>
Calendar Year: 2015
Week of Year: 1
Weekday: 1
Dec 27 2016
All strangeness aside, here is my method that's supposed to return the first day of the week from an NSDate:
- (NSDate *)dateAtBeginningOfDayForDate:(NSDate *)inputDate
{
// Use the user's current calendar and time zone
NSCalendar *calendar = [NSCalendar currentCalendar];
NSTimeZone *timeZone = [NSTimeZone systemTimeZone];
[calendar setTimeZone:timeZone];
NSDateComponents *dateComps = [calendar components:NSCalendarUnitYear | NSCalendarUnitWeekOfYear | NSCalendarUnitWeekday fromDate:inputDate];
NSDateComponents* startOfWeek = [[NSDateComponents alloc]init];
// Set the time components manually
[startOfWeek setWeekOfYear:dateComps.weekOfYear];
[startOfWeek setYear:dateComps.year];
[startOfWeek setWeekday:0];
// Convert to date
NSDate *beginningOfDay = [calendar dateFromComponents:startOfWeek];
return beginningOfDay;
}
Follow this Ry’s Objective-C Tutorial...
http://rypress.com/tutorials/objective-c/data-types/dates

Finding the locale-dependent first day of the week

Given an NSDate, how do I find the first day of that date's week, given the user's locale. For example, I've heard that some countries treat Monday as the first day of the week and others use Sunday. I need to return the preceding Monday in the first case but the preceding Sunday in the latter case.
My best effort thus far always returns the preceding Sunday, regardless of the device settings applied:
NSCalendar *calendar = [NSCalendar currentCalendar];
[calendar setLocale:[NSLocale currentLocale]];
NSDateComponents *components = [calendar components:NSYearForWeekOfYearCalendarUnit | NSYearCalendarUnit | NSMonthCalendarUnit | NSWeekCalendarUnit | NSWeekdayCalendarUnit fromDate:originalDate];
[components setWeekday:1];
NSDate *firstDayOfWeek = [calendar dateFromComponents:components];
Bonus question: on iOS, which setting drives this? Is it the 'Region Format'?
Try changing:
[components setWeekday:1];
to:
[components setWeekday:[calendar firstWeekday]];
You should also remove the NSYearForWeekOfYearCalendarUnit and NSWeekCalendarUnit components.
Bonus Question: "Region Format" should be the setting that changes the first day of the week.
An smarter way for old style for those who do not want to set calendar firstWeekday.
NSDate *date = [NSDate dateWithTimeIntervalSince1970:1483620311.228];
NSLog(#"current date ===> : %#", date);
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSDate *previousMonday = [calendar nextDateAfterDate:date
matchingUnit:NSCalendarUnitWeekday
value:2 //use 1-7 for Sunday to Saturday week day.
options:NSCalendarMatchNextTime | NSCalendarSearchBackwards];
NSLog(#"previousMonday date ===> : %#", previousMonday);

Cocoa get first day in week

How to get the first day of a week for a date
this seems more easy at it is, since :
when the week starts with sunday, i need to get back the sunday date
if it starts on monday, i need to get the monday date
the input date is any date in week with time... i tried several approaches, but the edge case make it difficould
i made a function, which however doesn't work in 100% (not sure about the [components setDay: -weekday + 2];)
- (NSDate *)firstDateOfWeek {
NSCalendar * calendar = [NSCalendar currentCalendar];
NSDateComponents *weekdayComponents = [calendar components:(NSDayCalendarUnit | NSWeekdayCalendarUnit) fromDate:self];
// because sunday is 0, we need it to be 6
NSInteger weekday = (([weekdayComponents weekday] + 6) % 7);
NSDateComponents *components = [calendar components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSWeekdayCalendarUnit) fromDate:self];
[components setDay: -weekday + 2];
return [calendar dateFromComponents:components];
}
It is easier to use the rangeOfUnit calendar method, which handles "start of the week" correctly according to the current locale:
NSDate *date = your date;
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDate *startOfWeek;
[calendar rangeOfUnit:NSWeekOfYearCalendarUnit
startDate:&startOfWeek
interval:NULL
forDate:date];
Using NSDateComponents, it would work as follows (assuming that a week has 7 days):
NSDate *date = your date;
NSDateComponents *comp = [calendar components:NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit|NSWeekdayCalendarUnit
fromDate:date];
NSDate *startOfDay = [calendar dateFromComponents:comp];
NSInteger diff = (NSInteger)[calendar firstWeekday] - (NSInteger)[comp weekday];
if (diff > 0)
diff -= 7;
NSDateComponents *subtract = [[NSDateComponents alloc] init];
[subtract setDay:diff];
NSDate *startOfWeek = [calendar dateByAddingComponents:subtract toDate:startOfDay options:0];

Why am I getting wrong time output with correct timezone?

Why am I getting this output time:
2013-01-12 18:24:37.783 Code testings[10328:c07] 0001-01-01 17:12:52 +0000
when I run this code:
NSCalendar *calendar = [NSCalendar currentCalendar];
calendar.timeZone = [NSTimeZone localTimeZone];
NSDateComponents *components = [calendar components:NSHourCalendarUnit fromDate:[NSDate date]];
components.hour = 17;
components.minute = 47;
components.second = 0;
NSDate *fire = [calendar dateFromComponents:components];
NSLog(#"%#", fire);
I have tried default timezone, system timezone. And it always gives me an output with different minutes and seconds! plus wrong date 0001-01-01!!!! Any idea why?
Additional Tests:
When running this code:
NSCalendar *calendar = [NSCalendar currentCalendar];
calendar.timeZone = [NSTimeZone localTimeZone];
NSDateComponents *components = [calendar components:NSHourCalendarUnit fromDate:[NSDate date]];
components.hour = (components.hour + 1) % 24;
components.minute = 0;
components.second = 0;
NSDate *fire = [calendar dateFromComponents:components];
NSLog(#"%#", fire);
It gives me this output:
2013-01-12 18:41:41.552 Code testings[10648:c07] 0001-01-01 18:25:52 +0000
2013-01-12 18:42:16.274 Code testings[10648:c07] 0001-01-01 18:25:52 +0000
2013-01-12 18:42:30.310 Code testings[10648:c07] 0001-01-01 18:25:52 +0000
You are mising the year, month and day components.
Do:
NSDateComponents *components = [calendar components:NSUIntegerMax fromDate:[NSDate date]];
Now that should give you the correct date.
Side note: since the NSCalendarUnit is a bitfield typedef for NSUInteger, I pass in NSUIntegerMax to retrieve all possible calendar units. That way I don't have to have a massive bitwise OR statement.
You also need to request the year, month and day with the calendar components: method.
From Apple docs:
An instance of NSDateComponents is not responsible for answering
questions about a date beyond the information with which it was
initialized.
NSDateComponents *components = [calendar components:NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit fromDate:[NSDate date]];
NSLog of the date will display the time with the default timezone.

dynamically create date for previous sunday at 12:00 AM

I'm struggling to figure out how to dynamically create a date object for the most previous sunday at 12:00 AM
I was thinking I could get today's date and then subtract the current day of the week + 1 at which point I could just subtract the time of the day do get down to 12AM.
so far I have the current day of the week:
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *comps = [gregorian components:NSWeekdayCalendarUnit fromDate:[NSDate date]];
int weekday = [comps weekday];
at which point I can get today's date and subtract the difference of weekday * seconds in a day. However, how can I get today's time in seconds ??
No need to manually calculate seconds (which is dangerous anyway because of daylight saving etc.). The following should do exactly what you want:
NSDate *today = [NSDate date];
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
[calendar setLocale:[[NSLocale alloc] initWithLocaleIdentifier:#"en-US"]]; // force US locale, because other countries (e.g. the rest of the world) might use different weekday numbering
NSDateComponents *nowComponents = [calendar components:NSYearCalendarUnit | NSWeekCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit fromDate:today];
[nowComponents setWeekday:1]; //Sunday
[nowComponents setHour:0]; // 12:00 AM = midnight (12:00 PM would be 12)
[nowComponents setMinute:0];
[nowComponents setSecond:0];
NSDate *previousSunday = [calendar dateFromComponents:nowComponents];
I'll leave worrying about transitions between daylight savings time to you:
NSCalendar *gregorian = [[NSCalendar alloc]initWithCalendarIdentifier:NSGregorianCalendar];
NSDate *date = [NSDate date];
NSLog(#"date %#", date);
NSDateComponents *componentsToday = [gregorian components:NSWeekdayCalendarUnit fromDate:date]; // NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit |
NSInteger days = componentsToday.weekday - 1;
NSLog(#"Days=%d", days);
NSDate *lastSunday = [date dateByAddingTimeInterval:-days*60*60*24];
NSLog(#"lastSunday %#", lastSunday);
NSDateComponents *componentsSunday = [gregorian components:NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit fromDate:lastSunday];
[componentsSunday setHour:0];
[componentsSunday setMinute:0];
[componentsSunday setSecond:0];
NSDate *targetDate = [gregorian dateFromComponents:componentsSunday];
NSLog(#"TargetDate %#", targetDate);
#AndreasLey solution in Swift 2.0:
func getLastSunday() -> NSDate?
{
let today = NSDate();
let calendar : NSCalendar! = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)
calendar.locale = NSLocale(localeIdentifier: "en-US") // force US locale, because other countries (e.g. the rest of the world) might use different weekday numbering
let flags : NSCalendarUnit = [.Year , .Month, .Weekday, .WeekOfMonth, .Minute , .Second]
let nowComponents = calendar.components(flags, fromDate: today)
nowComponents.weekday = 1 //Sunday
nowComponents.hour = 0 // 12:00 AM = midnight (12:00 PM would be 12)
nowComponents.minute = 0
nowComponents.second = 0;
let previousSunday = calendar.dateFromComponents(nowComponents);
return previousSunday
}
This code is setting 12:00 am in your selected date from UIDatePicker.
NSDate *oldDateSelected = datePicker.date;
unsigned unitFlags = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay;
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *comps = [calendar components:unitFlags fromDate:oldDateSelected];
comps.hour = 00;
comps.minute = 00;
comps.second = 44;
NSDate *newDate = [calendar dateFromComponents:comps];
return newDate;

Resources