NSCalendar, why does setting the firstWeekday doesn't effect calculation outcome? - ios

i need to calculate the weekday for a given date, however, depending on the calendar a week can star on Monday and somwhere on Sunday
so i wanted to set it, to start on Monday, using
[[NSCalendar currentCalendar] setFirstWeekday:2];
however, the calculation outcome is the same
{
[[NSCalendar currentCalendar] setFirstWeekday:1];
NSDateComponents *weekdayComponents = [[NSCalendar currentCalendar] components:(NSDayCalendarUnit | NSWeekdayCalendarUnit) fromDate:date];
NSInteger weekday = [weekdayComponents weekday] - 1;
NSLog(#"%d", weekday);
}
{
[[NSCalendar currentCalendar] setFirstWeekday:2];
NSDateComponents *weekdayComponents = [[NSCalendar currentCalendar] components:(NSDayCalendarUnit | NSWeekdayCalendarUnit) fromDate:date];
NSInteger weekday = [weekdayComponents weekday] - 1;
NSLog(#"%d", weekday);
}
returns same numbers, but why?

The behavior you see is correct. The weekday component is not affected by the firstWeekday property. If you have a date representing a sunday it will always be a sunday wether you start your week on that sunday or on monday. What this should affect is the week number in the week property of your date components.

I believe you need to use the ordinalityOfUnit:inUnit:forDate: method rather than attempting to extract the date components. So something like this:
NSUInteger weekday = [[NSCalendar currentCalendar] ordinalityOfUnit:NSWeekdayCalendarUnit inUnit:NSWeekCalendarUnit forDate:date];
Basically that call is asking for the day (NSWeekdayCalendarUnit) in the week (NSWeekCalendarUnit) for the given date.
If that doesn't work as is, you may need to create your own calendar, rather than trying to modifying the first week day on the currentCalendar.
For example:
NSCalendarIdentifier calendarIdentifier = [[NSCalendar currentCalendar] calendarIdentifier];
NSCalendar *calendar = [[[NSCalendar alloc] initWithCalendarIdentifier:calendarIdentifier] autorelease];
[calendar setFirstWeekday:2];
Then use that new calendar object rather than [NSCalendar currentCalendar] in the ordinalityOfUnit:inUnit:forDate: call.

A useful function to get corrected weekday number for different firstWeekday cases
Swift 3.0
func dayOfWeek(day: Int, month: Int, year: Int) -> Int {
let calendar = Calendar.current
let dateComponents = DateComponents(year: year, month: month, day: day)
guard let date = calendar.date(from: dateComponents) else {
fatalError("Can't create date form specified date components")
}
var weekday = calendar.component(.weekday, from: date)
//handling the case when calendar starts from Monday: firstWeekday == 2
if calendar.firstWeekday == 2 {
weekday = (weekday == 1) ? 7 : (weekday - 1)
}
return weekday
}

Related

Wrong Year and weekOfYear in Gregorian Calendar objective-c

+ (NSCalendar*)getGregorianCalendarInstance {
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
return calendar;
}
+ (NSInteger)currentGregorianWeekOfYear:(NSDate*) date {
NSCalendar *gregorianCalendar = [self getGregorianCalendarInstance];
gregorianCalendar.firstWeekday = 2; // Monday = 2
NSDateComponents *components = [gregorianCalendar components:NSCalendarUnitWeekOfYear fromDate:date];
NSUInteger weekOfYear = [components weekOfYear];
return weekOfYear;
}
+ (NSInteger)currentGregorianYear:(NSDate*) date {
NSCalendar *gregorianCalendar = [self getGregorianCalendarInstance];
gregorianCalendar.firstWeekday = 2;
[gregorianCalendar setTimeZone:[NSTimeZone defaultTimeZone]];
gregorianCalendar.locale = [NSLocale localeWithLocaleIdentifier:#"en_US_POSIX"];
NSDateComponents *comps = [gregorianCalendar components: NSCalendarUnitYear fromDate:date];
NSInteger year = [comps year];
return year;
}
week number returns 52 instead of 1 in currentGregorianWeekOfYear function and year returns 2019 instead of 2020 in currentGregorianYear function for date = 2019-12-30 15:00:00 +0000
Specific reason to use the Gregorian calendar is to display 1st January in the 1st week of the year. With the ISO calendar, 1st January will be indicated as the 52nd or 53rd week of the year.
I already checked multiple references but it does not solve my problem:
WeekOfYear overflowing to 1 in 53rd week in gregorian calendar?
NSDateComponents weekOfYear returns wrong value
Swift: Gregorian Calendar and weeks/weekdays - inconsistent behavior

Get last tuesday in month ios

Can get last weekday in the month through NSDateComponents? For example: last monday in month or last friday in month. etc
Just another way to solve. Find the first day of the next month, then search backwards for Tuesday.
let calendar = NSCalendar.currentCalendar()
let today = NSDate()
let components = calendar.components([.Year, .Month], fromDate: today)
components.month += 1
// If today is 2016-07-12 then nextMonth will be the
// first day of the next month: 2016-08-01
if let nextMonth = calendar.dateFromComponents(components) {
// Search backwards for weekday = Tuesday
let options: NSCalendarOptions = [.MatchPreviousTimePreservingSmallerUnits, .SearchBackwards]
calendar.nextDateAfterDate(nextMonth, matchingUnit: .Weekday, value: 3, options: options)
}
Here's a solution; tested, and fairly robust, I think.
We'll let NSCalendar walk through the month, a day at a time, and pull out all matching weekdays as NSDates. Then you can answer questions like, "The 3rd Wednesday of this month"
I believe the comments are clear about what is happening, and why.
If you need further clarification, I'll be happy to do so.
//What weekday are we interested in? 1 = Sunday . . . 7 = Saturday
NSInteger targetWeekday = 1;
//Using this methodology, GMT timezone is important to set
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
calendar.timeZone = [NSTimeZone timeZoneWithAbbreviation:#"GMT"];
//set the components to the first of the current month
NSDateComponents *startComponents = [calendar components:NSCalendarUnitMonth | NSCalendarUnitYear fromDate:[NSDate date]];
startComponents.day = 1;
//the enumeration starts "afterDate", so shift the start back one day (86400 seconds) to include the 1st of the month
NSDate *startDate = [[calendar dateFromComponents:startComponents] dateByAddingTimeInterval:-86400];
//the enumeration searches for a match; we'll match at the midnight hour and find every occurance of midnight
NSDateComponents *dayByDay = [[NSDateComponents alloc] init];
dayByDay.hour = 0;
//I've opted to put all matching weekdays of the month into an array, so you can find any instance easily
__block NSMutableArray *foundDates = [NSMutableArray arrayWithCapacity:5];
[calendar enumerateDatesStartingAfterDate:startDate
matchingComponents:dayByDay
options:NSCalendarMatchPreviousTimePreservingSmallerUnits
usingBlock:^(NSDate *date, BOOL exactMatch, BOOL *stop){
NSDateComponents *thisComponents = [calendar components:NSCalendarUnitMonth | NSCalendarUnitWeekday fromDate:date];
//as long as the month stays the same... (or the year, if you wanted to)
if (thisComponents.month == startComponents.month) {
//does this date match our target weekday search?
if (thisComponents.weekday == targetWeekday) {
//then add it to our result array
[foundDates addObject:date];
}
//once the month has changed, we're done
} else {
*stop = YES;
}
}];
//Now, with our search result array, we can find the 1st, last, or any specific occurance of that weekday on that month
NSLog(#"Found these: %#", foundDates);
So, if you only wanted the last one, then just use [foundDates lastObject]
Try this code. This works for me.
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *comps = [gregorian components:NSWeekdayCalendarUnit fromDate:[NSDate date]];
int weekday = [comps weekday];
int lastTues;
int lastSatDay;
if (weekday==1) {
lastTues=5;
lastSatDay=1;
}
else if (weekday==2)
{
lastTues=6;
lastSatDay=2;
}
else if (weekday==3)
{
lastTues=7;
lastSatDay=3;
}
else if (weekday==4)
{
lastTues=1;
lastSatDay=4;
}
else if (weekday==5)
{
lastTues=2;
lastSatDay=5;
}
else if (weekday==6)
{
lastTues=3;
lastSatDay=6;
}
else if (weekday==7)
{
lastTues=4;
lastSatDay=7;
}
NSDate *lastTuesDay = [[NSDate date] addTimeInterval:-3600*24*(lastTues)];
NSDate *lastSaturday = [[NSDate date] addTimeInterval:-3600*24*(lastSatDay)];

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) {
// ...
}

Get all NSDate of a month (and the dates in adjacent weeks)

Given a date (for example today 4 Feb 2014), I would like to get a list of NSDate instances that go from Monday 27 Jan 2014 to Sunday 2 Mar 2014.
Basically all the dates in the current month, plus the dates from the last week of the previous month, and the first week of the next month, if they share the same week with some of the dates in the current month.
How can I achieve this?
I can think of a way to obtain this (pseudo-code below), but it's way too long and complicated. Is there any simpler way, like a method that the SDK provides to short cut?
Extract the month component from [NSDate date] (today)
Construct the first day of the month
Calculate its weekday
If it's Wednesday (3rd day of the week) then add today-1, today-2 to the list and so on
Repeat from step 2, but with the last day of the month
Also to all the elitists out there, just because I'm not posting any code, doesn't mean this is not a coding question. The problem is real (construct a calendar grid of a month), and finding the right algorithm/method before coding is much better than playing around and manually do the maths with NSDate and NSCalendar (very error prone, as I will need to take into account all the weird cases). I figure many people have already encountered this same problem and if they could share some pointers, great. If you don't want to answer, no need to reply.
I'm going to post very general code. You are going to need to make it specific to your needs.
//Setup the calendar object
NSCalendar *calendar = [NSCalendar currentCalendar];
//Get the current date's month
NSUInteger month = [dateComponents month];
//Create an NSDate for the first and last day of the month
NSDateComponents *comp = [calendar components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit) fromDate:[NSDate date]];
[comp setDay:1];
NSDate *firstOfMonth = [calendar dateFromComponents:comp];
[comp setMonth:[comp month]+1];
[comp setDay:0];
NSDate *lastOfMonth = [calendar dateFromComponents:comp];
//Now get the first and last week number from there
NSUInteger unitFlags = NSWeekCalendarUnit;
//Create a date component object from today's date.
NSDateComponents *firstDateComponents = [calendar components:unitFlags
fromDate:firstOfMonth // for current date
options:0];
NSDateComponents *lastDateComponents = [calendar components:unitFlags
fromDate:lastOfMonth // for current date
options:0];
NSUInteger firstWeek = [firstDateComponents week];
NSUInteger lastWeek = [lastDateComponents week];
Now that you have the first and last weeks you can start at the first day of the first week and go through the last day of the last week to set up your calendar. Good luck.
Swift 5:
extension Date {
var startOfMonth: Date {
let calendar = Calendar(identifier: .gregorian)
let components = calendar.dateComponents([.year, .month], from: self)
return calendar.date(from: components)!
}
var endOfMonth: Date {
var components = DateComponents()
components.month = 1
components.second = -1
return Calendar(identifier: .gregorian).date(byAdding: components, to: startOfMonth)!
}
}
let date = Date()
let firstWeek = Calendar.current.dateComponents([.weekOfYear], from: date.startOfMonth)
let lastWeek = Calendar.current.dateComponents([.weekOfYear], from: date.endOfMonth)

Adding a day to current date in iOS

I saw this post: iOS and finding Tomorrow.
The code provided is:
units = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit;
NSDateComponents* comps = [[NSCalendar currentCalendar] components:units fromDate:[NSDate date]];
// Add one day
comps.day = comps.day+1; // no worries: even if it is the end of the month it will wrap to the next month, see doc
// Recompose a new date, without any time information (so this will be at midnight)
NSDate* tomorrowMidnight = [[NSCalendar currentCalendar] dateFromComponents:comps];
Problem: I need to add a day to my current date so that for example, the difference between 21st Jun 2013 and 21st May 2013 is 1 month instead of 0 month.
The code I am using:
NSDate *selectedDate = [picker2 date];
unsigned int unitFlags = NSHourCalendarUnit | NSMinuteCalendarUnit | NSDayCalendarUnit | NSMonthCalendarUnit;
NSDateComponents *conversionInfo = [currCalendar components:unitFlags fromDate:selectedDate toDate:[NSDate date] options:0];
DLog(#"%i",conversionInfo.month);
DLog(#"%i",conversionInfo.day);
conversionInfo.day +=1;
DLog(#"%i",conversionInfo.day);
DLog(#"%i",conversionInfo.month);
int months = [conversionInfo month];
But when I tried to get the difference between 21st Jun 2013 and 21st May 2013 -> still returns me 0 month instead of 1 month.
Need some help on this.
Form a date component with number of days you want to add to your original date. From the current calendar form the date by adding the this component to your original date.
NSDateComponents *dateComponents = [NSDateComponents new];
dateComponents.day = 1;
NSDate *newDate = [[NSCalendar currentCalendar]dateByAddingComponents:dateComponents
toDate: selectedDate
options:0];
Here is a method I use:
+ (NSDate *)addDays:(NSInteger)days toDate:(NSDate *)originalDate {
NSDateComponents *components= [[NSDateComponents alloc] init];
[components setDay:days];
NSCalendar *calendar = [NSCalendar currentCalendar];
return [calendar dateByAddingComponents:components toDate:originalDate options:0];
}
With it, you can add as many days as you want. It also works with negative numbers, so you can subtract days.
Simple and straight forward solution :
NSDate *currentDate = [NSDate date];
NSDate *nextDate = [currentDate dateByAddingTimeInterval:(24*3600)];
This is working fine for me.
For swift googlers:
NSCalendar.currentCalendar().dateByAddingUnit(NSCalendarUnit.CalendarUnitDay, value: 1, toDate: date, options: nil)!
First Thing,you are assigning day instead of month to NSDateComponent.
Execute following code and find difference between inDate And newDate
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *components = [calendar components:NSCalendarUnitMonth fromDate:inDate];
NSInteger monthFlag = 1;
components.month = minuteFlag;
// Retrieve date with increased days count
NSDate *newDate = [[NSCalendar currentCalendar]
dateByAddingComponents:components
toDate:monthFlag
options:0];
To add any number of any components to the date, for example: 2 days or 2 months, 1 year, do the following:
Calendar.current.date(byAdding: DateComponents(month: -1), to: date)! //this create date with previous month for date.
you can initialize DateComponents with anything:
public init(calendar: Calendar? = default, timeZone: TimeZone? = default, era: Int? = default, year: Int? = default, month: Int? = default, day: Int? = default, hour: Int? = default, minute: Int? = default, second: Int? = default, nanosecond: Int? = default, weekday: Int? = default, weekdayOrdinal: Int? = default, quarter: Int? = default, weekOfMonth: Int? = default, weekOfYear: Int? = default, yearForWeekOfYear: Int? = default)

Resources