Finding the locale-dependent first day of the week - ios

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);

Related

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

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];

Trying to determine if current date is 3 days or less from the end of the month in iOS

I am trying to determine if the current date is in fact three days or less from the end of the month. In other words, if I am in August, then I would like to be alerted if it is the 28,29,30, or 31st. If I am in February, then I would like to be notified when it is the 25,26,27, or 28 (or even 29). In the case of a leap year, I would be alerted from 26th onwards.
My problem is that I am not sure how to perform such a check so that it works for any month. Here is my code that I have thus far:
-(BOOL)monthEndCheck {
NSDateComponents *components = [[NSCalendar currentCalendar] components:NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear fromDate:[NSDate date]];
NSInteger day = [components day];
NSInteger month = [components month];
NSInteger year = [components year];
if (month is 3 days or less from the end of the month for any month) {
return YES;
} else {
return NO;
}
}
Because there are months with 28, 30, and 31 days, I would like a dynamic solution, rather than creating a whole series of if/else statements for each and every condition. Is there a way to do this?
This is how you get the last day of the month:
NSDate *curDate = [NSDate date];
NSCalendar* calendar = [NSCalendar currentCalendar];
NSDateComponents* comps = [calendar components:NSYearCalendarUnit|NSMonthCalendarUnit|NSWeekCalendarUnit|NSWeekdayCalendarUnit fromDate:curDate]; // Get necessary date components
// set last of month
[comps setMonth:[comps month]+1];
[comps setDay:0];
NSDate *tDateMonth = [calendar dateFromComponents:comps];
NSLog(#"%#", tDateMonth);
Source: Getting the last day of a month
EDIT (another source): How to retrive Last date of month give month as parameter in iphone
Now you can simply count from the current date.
If < 3 do whatever you wanted to do.
Maybe something like this:
NSTimeInterval distanceBetweenDates = [date1 timeIntervalSinceDate:date2];
double timeInSecondsFor3Days = 280000; //Better use NSDateComponents here!
NSInteger hoursBetweenDates = distanceBetweenDates / timeInSecondsFor3Days;
However I did not test that^^
EDIT: Thanks to Aaron. Do NSDateComponents to calculate the time for three days instead!
First you have to compute the start of the current day (i.e. today at 00.00).
Otherwise, the current day will not count as a full day when computing the
difference between today and the start of the next month.
NSDate *now = [NSDate date];
NSCalendar *cal = [NSCalendar currentCalendar];
NSDate *startOfToday;
[cal rangeOfUnit:NSCalendarUnitDay startDate:&startOfToday interval:NULL forDate:now];
Computing the start of the next month can be done with rangeOfUnit:...
(using a "statement expression" to be fancy :)
NSDate *startOfNextMonth = ({
NSDate *startOfThisMonth;
NSTimeInterval lengthOfThisMonth;
[cal rangeOfUnit:NSCalendarUnitMonth startDate:&startOfThisMonth interval:&lengthOfThisMonth forDate:now];
[startOfThisMonth dateByAddingTimeInterval:lengthOfThisMonth];
});
And finally the difference in days:
NSDateComponents *comp = [cal components:NSCalendarUnitDay fromDate:startOfToday toDate:startOfNextMonth options:0];
if (comp.day < 4) {
// ...
}

Numeric value: day of the week

Im changing text value based on the day of the week, I was able to achieve this using string values, however I want to be able to achieve on a numeric value instead - to remove issues with different languages. For example, if today is monday do... but I want if today is day 1 then do. Ive tried the code below but it gives me a numeric value of 0;
NSDateFormatter *dayofweekformatter = [[NSDateFormatter alloc] init];
[dayofweekformatter setDateFormat:#"E"];
NSString *DayOfWeek = [dayofweekformatter stringFromDate:[NSDate date]];
NSInteger weekDay = [DayOfWeek integerValue];
NSLog(#"The day of the week is: %d", weekDay);
Is it possible to do this?
According to this link, "E" on its own will give you the day of the week as a textual format, i.e.: "Mon"/"Tue" etc.
If you want the day of the week as an integer, you should use a lower case "e" or "c".
A different implementation would be to use NSCalendar and NSDateComponents to determine the day of the week as this is more likely to take into consideration different settings across different devices based on the users preferred calendar.
NSDate *date = [NSDate date];
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *dateComponents = [calendar components:NSCalendarUnitWeekday fromDate:date];
NSLog(#"day of the week: %i", [dateComponents weekday]);
Here is solution proposed on Apple forums https://discussions.apple.com/thread/1700102?start=0&tstart=0 (I am not the author)
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *weekdayComponents =[gregorian components:NSWeekdayCalendarUnit fromDate:dateOfInterest];
NSInteger weekday = [weekdayComponents weekday];
// weekday 1 = Sunday for Gregorian calendar
[gregorian release];

How do I create the current date (or any date) as an NSDate without hours, minutes and seconds?

I need to create an NSDate of the current date (or on any NSDate) without the hours, minutes and seconds and keep it as an NSDate, in as few lines of as possible?
I need to perform some calculations on dates and having hours, minutes and seconds cause problems, I need absolute dates (I hope absolute is the right phrase). Its for an iPhone app.
I'm creating dates with [NSDate date] which add hours, minutes and seconds. Also I'm adding months to dates which I think caters for day light savings as I get 2300 hours on some dates.
So ideally I need a function to create absolute NSDates from NSDates.
I know I asked a similar question earlier, but I need to end up with NSDate not a string and I'm a little concerned about specifying a date format e.g. yyyy etc.
First, you have to understand that even if you strip hours, minutes and seconds from an NSDate, it will still represent a single moment in time. You will never get an NSDate to represent an entire day like 2011-01-28; it will always be 2011-01-28 00:00:00 +00:00. That implies that you have to take time zones into account, as well.
Here is a method (to be implemented as an NSDate category) that returns an NSDate at midnight UTC on the same day as self:
- (NSDate *)midnightUTC {
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
[calendar setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
NSDateComponents *dateComponents = [calendar components:NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit
fromDate:self];
[dateComponents setHour:0];
[dateComponents setMinute:0];
[dateComponents setSecond:0];
NSDate *midnightUTC = [calendar dateFromComponents:dateComponents];
[calendar release];
return midnightUTC;
}
NSDate *oldDate = [NSDate date];
NSCalendarUnit requiredDateComponents = NSYearCalendarUnit| NSMonthCalendarUnit | NSDayCalendarUnit;
NSDateComponents *components = [[NSCalendar currentCalendar] components:requiredDateComponents fromDate:oldDate];
NSDate *newDate = [[NSCalendar currentCalendar] dateFromComponents:components];

How can I get the monday of a week in a special date in Objective-C?

e.g. 01.10.2010 is friday => 27.09.2010 is monday.
I have no idea how to manage this one. btw: how can I calculate with dates?
For time/date calculations use NSDateComponents.
Listing 2 Getting the Sunday in the current week
NSDate *today = [[NSDate alloc] init];
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
// Get the weekday component of the current date
NSDateComponents *weekdayComponents = [gregorian components:NSWeekdayCalendarUnit fromDate:today];
/*
Create a date components to represent the number of days to subtract from the current date.
The weekday value for Sunday in the Gregorian calendar is 1, so subtract 1 from the number of days to subtract from the date in question. (If today's Sunday, subtract 0 days.)
*/
NSDateComponents *componentsToSubtract = [[NSDateComponents alloc] init];
[componentsToSubtract setDay: 0 - ([weekdayComponents weekday] - 1)];
NSDate *beginningOfWeek = [gregorian dateByAddingComponents:componentsToSubtract toDate:today options:0];
/*
Optional step:
beginningOfWeek now has the same hour, minute, and second as the original date (today).
To normalize to midnight, extract the year, month, and day components and create a new date from those components.
*/
NSDateComponents *components =
[gregorian components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit)
fromDate: beginningOfWeek];
beginningOfWeek = [gregorian dateFromComponents:components];
In later versions, there is a smarter way:
NSCalendar *cal = [NSCalendar currentCalendar];
[cal setFirstWeekday:2]; //2 is monday. 1:Sunday .. 7:Saturday don't set it, if user's locale should determine the start of a week
NSDate *now = [NSDate date];
NSDate *monday;
[cal rangeOfUnit:NSWeekCalendarUnit // we want to have the start of the week
startDate:&monday // we will write the date object to monday
interval:NULL // we don't care for the seconds a week has
forDate:now]; // we want the monday of today's week
If you actually change the weekday that represents the start of the week (Sunday vs. Monday), you should change it back after this snippet.

Resources