Given an NSDate, find the last day of fourth prior month - objective-c

I am trying to calculate an NSDate object based on the current date. If the current date is April 1st, 2015, I need to generate the date, December 31, 2014. If the current date is April 30th, 2015, I STILL need to generate the date, December 31, 2014. If however, it is May 1st, 2015, I need to generate January 31st, 2015. In other words, whatever month I am in, I need the date of the end of the month, from four months ago, regardless of where I am in the current month.
The code I have thus far is:
NSCalendar *theCalendar = [NSCalendar currentCalendar];
NSDateComponents *dayComponent = [[NSDateComponents alloc] init];
[dayComponent setDay:-90];
NSDate *nextDate = [theCalendar dateByAddingComponents:dayComponent toDate:[NSDate date] options:0];
NSLog(#"The date I am getting is: %#", nextDate);
The above code gives me the date value of exactly 90 days prior to the current date, but I need the date to always be the end of the month that is 4 months earlier.

As you've already discovered, you need a starting date and a calendar:
NSDate *startingDate = [NSDate date];
NSCalendar *calendar = [NSCalendar autoupdatingCurrentCalendar];
You'll need the components of the current date but only down to the current month, because you don't care about the specific day within the month:
NSDateComponents *components = [calendar
components:NSCalendarUnitEra | NSCalendarUnitYear | NSCalendarUnitMonth
fromDate:startingDate];
You say you want the last day of the fourth prior month. Since months have different numbers of days, the last day varies depending on the month. But all months have first days, and those first days are always numbered 1. So it's easiest to compute “the last day of the fourth prior month” by first going back three months:
components.month -= 3;
Then, go one day prior to that month:
components.day = -1;
Finally, you need to get clear in your head that an NSDate represents an instant in time, but a day (like “April 1st, 2015”) is an interval of time, starting and ending at specific instants. If you're going to represent a whole day using an NSDate, you're going to be storing one instant within that interval. You don't want to store the first or last instant (which will both be midnights); that causes problems for some days in some time zones. Instead, use noon as your instant:
components.hour = 12;
Now you're ready to ask the calendar for a new NSDate:
NSDate *lastDayOfFourthPriorMonth = [calendar dateFromComponents:components];

You want to get familiar with NSCalendar and NSDateComponents. If you read those two API documents, you will be able to assemble the answer.
Conceptually, you want to use NSCalendar components:fromDate: to get the components (day, month, year) of the current date.
You will then walk that month value back 4 months. Now if that wraps past January, you know you need to determine how much, so that you adjust and stay within months [1..12]. Further, you'll need to decrement the year at that point too.
Knowing the month, you can find the last day of that month through several means; the crudest of which is to maintain your own enum...but there's probably a better way using NSCalendar that will also account for February in leap years.
At the end, you can build the resultant date from the components you've assembled, using the NSCalendar method dateFromComponents:.
See #rob mayoff's excellent answer which is a more concrete realization of this theory and IMO, the correct answer.

Related

Calculate Days Between Dates, Ignoring Certain Weekdays

I am developing a scheduling application, and part of it are events that repeat, say, daily in a given schedule. This schedule is only 'active' on certain days—think of it like a work week, where Saturdays and Sundays nothing happens.
I'd like to calculate the number of consecutive days between two dates, excluding those certain weekdays. I have a method that uses NSCalendar to do this, ignoring the excluded days, but did not see any methods on it (or related classes) that'd allow me to ignore certain days.
+ (NSInteger) OKDaysBetweenDate:(nonnull NSDate *) fromDateTime
andDate:(nonnull NSDate *) toDateTime {
NSDate *fromDate;
NSDate *toDate;
NSCalendar *calendar = [NSCalendar currentCalendar];
[calendar rangeOfUnit:NSCalendarUnitDay startDate:&fromDate
interval:NULL forDate:fromDateTime];
[calendar rangeOfUnit:NSCalendarUnitDay startDate:&toDate
interval:NULL forDate:toDateTime];
NSDateComponents *difference = [calendar components:NSCalendarUnitDay
fromDate:fromDate toDate:toDate options:0];
return [difference day];
}
A possibility I have thought of is checking how many weeks are between the two dates, multiplying that by the number of excluded days a week, and subtracting it from the end result, but that doesn't seem particularly clean to me.
Is there a better, more Cocoa-y (perhaps using NSCalendar or friends) way of implementing this?
Thanks.

nsdate format to last digit in year, then day of year

The following code will output this (current day is Jan 15, 2015):
2015015
I need it to output this:
5015
I want it in the format of yddd, where y is the last number in the year, and ddd is the day of the year. so January 1st, 2000 is 0001 and December 31st, 2007 is 7365.
NSDate *julianLabel = [NDDate date];
NSDateFormatter *julianFormatter = [[NSDateFormatter alloc] init];
[julianFormatter setDateFormat:#"yddd"];
self.julian.text = [julianFormatter stringFromDate:julianLabel];
Surely you could just use substringFromIndex on the string, something that should work for the next eight thousand years or so (a). In other words, something like:
self.julian.text = [[julianFormatter stringFromDate:julianLabel] substringFromIndex:3];
(a) It'll work as per your specification until we reach the year 10,000 but keep in mind you'll start getting duplicates in 2025. I'm assuming that's not a problem due to the detail provided.

Programmatically getting the date "next Sunday at 5PM"

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.
I'm writing an app for a friend's podcast. She broadcasts her show live every Sunday at 5PM, and I would like to write some code in my app to optionally schedule a local notification for that time, so that the user is reminded of when the next live show is. How would I go about getting an NSDate object that represents "the next Sunday, at 5 PM Pacific time." (obviously this would have to be converted into whatever timezone the user is using)
First get the current day of the week:
NSDate *now = [NSDate date];
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSDateComponents *dateComponents = [calendar components:NSCalendarUnitWeekday | NSCalendarUnitHour fromDate:now];
NSInteger weekday = [dateComponents weekday];
The Apple docs define a weekday as:
Weekday units are the numbers 1 through n, where n is the number of
days in the week. For example, in the Gregorian calendar, n is 7 and
Sunday is represented by 1.
Next figure out how many days to add to get to the next sunday at 5:
NSDate *nextSunday = nil;
if (weekday == 1 && [dateComponents hour] < 5) {
// The next Sunday is today
nextSunday = now;
} else {
NSInteger daysTillNextSunday = 8 - weekday;
int secondsInDay = 86400; // 24 * 60 * 60
nextSunday = [now dateByAddingTimeInterval:secondsInDay * daysTillNextSunday];
}
To get it at 5:00 you can just change the hour and minute on nextSunday to 5:00. Take a look at get current date from [NSDate date] but set the time to 10:00 am

NSDateFormatter "w"

In my app, I used "w" to format date:
With "w", does a new week start on Sundays?
The date is local right?
NSDateFormatter will use your location settings (See NSLocal).
So If canadian is your local and canadian calendars starts on Saturday this is your week.
For your question about the week element,
The following is from HERE, your NSDateFormatter uses the ISO Standard.
Week date is an alternative date representation used in many
commercial and industrial applications. It is: YYYY-Www-D
where YYYY is the Year in the Gregorian calendar, ww is the week of
the year between 01 (the first week) and 52 or 53 (the last week), and
D is the day in the week between 1 (Monday) and 7 (Sunday).
Example: 2003-W14-2 represents the second day of the fourteenth week
of 2003.
This means that for the Gregorian calendar, the weeks start on Mondays.
In additions to PascalTurbo's post, if you need to, you can explicitly set set the timezone for your date formatter like the following example:
NSDateFormatter *dateFormatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:#"GMT"];

Pick a day in a cycle based on a starting date and the current date

An app allows a user to define an arbitrary set of days like so:
Day 1 - Pick flowers
Day 2 - Have coffee
Day 3 - Go swimming
This set of days may have only one entry or it may have many. Suppose that this cycle of days begins on March 23, 2010.
Is there a general algorithm that can determine which day in a cycle corresponds to a given calendar day?
On March 24th, the algorithm should return Day 2, March 25th should be Day 3, March 26th should be Day 1, and so on...
More specifically, I am writing this code for a Cocoa application. So, barring the existence of a more general technique can the Calendar classes help me?
NSCalendar *gregorian = ...;
NSDate *startDate = ...;
NSDate *currentDate = [NSDate date];
NSUInteger unitFlags = NSDayCalendarUnit;
NSDateComponents *components = [gregorian components:unitFlags fromDate:startDate toDate:currentDate options:0];
NSInteger days = [components day];
days now contains the number of days which have elapsed since the start date. This can be used as an index (zero-based) into your array of activities. You can use the modulus operator with the length of the cycle as the dividend to compute indices after the first transit around the cycle.
Calculate the Julian date (or a variant) of the start date and the current date (This part is left as an exercise to the reader). The current day of the cycle is then (currentDate - startDate) % lengthOfCycle + 1.
After typing this out I figured out the answer and it is straightforward.
Calculate the number of days between the starting date and the current date then take that number modulo the total number of days in the cycle and you have the current day. Easy peezy.