NSDate - Adding Days doesn't change year value - ios

I have a Label which i want to hold a specific date. The initial date should be the current Date, and the user should be able to step through the time by the interval of a day. After a little research, I found out, that it would be best to set NSDateComponents and add them to the current Date.
My problem is as following: When I step through the time, everything seems fine, until i reach the end/beginning of a year. For instance, the date hold in the label is the 01.01.2014, when I tap the "previousDayButton" it would become 31.12.2014. I thought about asking for the date and then setting the year manually, but I can't possibly think of THAT to be the solution.
So here is my Code:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[self setupInterface];
}
- (void)setupInterface{
orderDate = [NSDate date];
[self updateDateLbl];
}
- (void)updateDateLbl{
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc]init];
[dateFormatter setDateFormat:#"dd.MM.YYYY"];
dateLbl.text = [dateFormatter stringFromDate:orderDate];
}
- (IBAction)nextDay:(id)sender {
NSDateComponents *comps = [[NSDateComponents alloc]init];
comps.day = 1;
orderDate = [[NSCalendar currentCalendar] dateByAddingComponents:comps toDate:orderDate options:0];
[self updateDateLbl];
}
- (IBAction)previousDay:(id)sender {
NSDateComponents *comps = [[NSDateComponents alloc]init];
comps.day = -1;
orderDate = [[NSCalendar currentCalendar] dateByAddingComponents:comps toDate:orderDate options:0];
[self updateDateLbl];
}
I would love some help :)

Use Xcode help for NSDateFormatter. From the documentation:
It uses yyyy to specify the year component. A common mistake is to use YYYY. yyyy specifies the calendar year whereas YYYY specifies the year (of “Week of Year”), used in the ISO year-week calendar. In most cases, yyyy and YYYY yield the same number, however they may be different. Typically you should use the calendar year.
2014 started on a Wednesday. Tuesday, 31st Dec. 2013, is in the same week - week 1 of 2014. That's what YYYY displays. yyyy displays 2013.

Related

How to add Date if today is last day of the month.?

I am stuck a problem where i need to create a stepper by 7 day. I code for that but in case of last days of month it will remain continue with same month rather than it should be change in next month as well.
Same case needs to be implemented for the year.
For e.g if today is 30 dec 2016 then by adding 7 day it needs to be change as 7 jan 2017. Thanks in advance.
Try this. Here I have added 7 days from a particular date.
// Enter current date
NSString *currentDate = #"2016-12-30";
// Set number of days to add
int addDaysCount = 7;
// Set date formatter
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"yyyy-MM-dd"];
// Convert string to NSDate
NSDate *dateFromString = [dateFormatter dateFromString:currentDate];
// Initialize date component
NSDateComponents *dateComponents = [[NSDateComponents alloc] init];
[dateComponents setDay:addDaysCount];
// Retrieve date with increased days count
NSDate *newDate = [[NSCalendar currentCalendar]
dateByAddingComponents:dateComponents
toDate:dateFromString options:0];
NSLog(#"Current date: %#", [dateFormatter stringFromDate:dateFromString]);
NSLog(#"Updated date: %#", [dateFormatter stringFromDate:newDate]);
NSDate *now = [NSDate date];
NSDate *sevenDaysAgo = [now dateByAddingTimeInterval:+7*24*60*60];
Here add 7 days add to your current date

NSDate counting is not right. How to add a week without doing the math? [duplicate]

I have in the past used the below function to add on a specific time interval using NSDateComponents to an existing date.
(NSDate *)dateByAddingComponents:(NSDateComponents *)comps
toDate:(NSDate *)date
options:(NSCalendarOptions)opts
From iOS8 the week value is deprecated for NSDateComponents, which means I can't achieve what I want to do: generate a new NSDate by adding a certain number of weeks to a given NSDate.
Any help would be greatly appreciated.
Just use weekOfYear:
Apple docs for NSDateComponents week:
Deprecation Statement
Use weekOfYear or weekOfMonth instead, depending on what you intend.
NSDate *date = [NSDate date];
NSDateComponents *comp = [NSDateComponents new];
comp.weekOfYear = 3;
NSDate *date1 = [[NSCalendar currentCalendar] dateByAddingComponents:comp toDate:date options:0];
NSLog(#"date: %#", date);
NSLog(#"date1: %#", date1);
Output:
date: 2015-01-13 04:06:26 +0000
date1: 2015-02-03 04:06:26 +0000
If you use week you get this warning:
'week' is deprecated: first deprecated in ... - Use weekOfMonth or weekOfYear, depending on which you mean
When using the weekOfMonth or weekOfYear as a delta they work the same. Where they are different is when they are used to obtain the week number where you will get the week of the month with a range of 6 or the week of the year with a range of 53.
Update: As Zaph said in his answer, Apple actually recommends using weekOfYear or weekOfMonth instead of the answer I provided. View Zaph's answer for details.
You'll probably quickly realize that you're overthinking it, but here's how you can add a certain number of weeks to a date even though the week value's been deprecated, ex:
NSDateComponents *comp = [NSDateComponents new];
int numberOfDaysInAWeek = 7;
int weeks = 3; // <-- this example adds 3 weeks
comp.day = weeks * numberOfDaysInAWeek;
NSDate *date = [[NSCalendar currentCalendar] dateByAddingComponents:comp toDate:date options:0];
I prefer to use dateByAddingUnit. It's more intuitive
return [NSDate[[NSCalendar currentCalendar] dateByAddingUnit:NSCalendarUnitMonth value:3 toDate:toDate options:0];
You can add a category on NSDate with the following method:
- (NSDate *) addWeeks:(NSInteger)weeks
{
NSCalendar *gregorian=[[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSDateComponents *components=[[NSDateComponents alloc] init];
components.day = weeks * 7;
return [gregorian dateByAddingComponents:components toDate:self options:0];
}

Convert week number for a certain year to a month name

After searching through SO but apart from this question I found no solutions. I'm thinking about creating a method that would accept the int of the week number and the int of the year and that would return an NSString with the name of the month:
- (NSString *)getMonthNameFromNumber:(int)weekNumber andYear:(int)year
But I can't find a way to approach this problem. Would be glad if anyone could help with advices.
Something like this will do
- (NSString *)monthNameForWeek:(NSUInteger)week inYear:(NSInteger)year {
NSDateComponents * dateComponents = [NSDateComponents new];
dateComponents.year = year;
dateComponents.weekOfYear = week;
dateComponents.weekday = 1; // 1 indicates the first day of the week, which depends on the calendar
NSDate * date = [[NSCalendar currentCalendar] dateFromComponents:dateComponents];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"MMMM"];
return [formatter stringFromDate:date];
}
Note that this is dependent on the current calendar set in the device preferences.
In case this doesn't fit your needs, you can provide a NSCalendar instance and use it to retrieve the date instead of using currentCalendar. By doing so you can configure things like which is the first day of the week and so on. The documentation of NSCalendar is worth a read.
If using a custom calendar is a common case, just change the implementation to something like
- (NSString *)monthNameForWeek:(NSUInteger)week inYear:(NSInteger)year {
[self monthNameForWeek:week inYear:year calendar:[NSCalendar currentCalendar]];
}
- (NSString *)monthNameForWeek:(NSUInteger)week inYear:(NSInteger)year calendar:(NSCalendar *)calendar {
NSDateComponents * dateComponents = [NSDateComponents new];
dateComponents.year = year;
dateComponents.weekOfYear = week;
dateComponents.weekday = 1; // 1 indicates the first day of the week, which depends on the calendar
NSDate * date = [calendar dateFromComponents:dateComponents];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"MMMM"];
return [formatter stringFromDate:date];
}
As an unrelated side note, you should avoid get for methods names, unless you are returning a value indirectly.
With anything to do with dates, you need to involve a calendar. Your question assumes the Gregorian Calendar, but I suggest you change your method declaration to:
- (NSString*)monthNameFromWeek:(NSInteger)week year:(NSInteger)year calendar:(NSCalendar*)cal;
From this, there is also the ambiguity of which day we're talking about. For example (this hasn't been checked), week 4 of 2015 may contain both January and February. Which one is correct? For this example, we'll use a weekday of 1, which indicates Sunday (in the UK Gregorian Calendar), and we'll use whatever month this falls in to.
As such, your code would be:
// Set up our date components
NSDateComponents* comp = [[NSDateComponents alloc] init];
comp.year = year;
comp.weekOfYear = week;
comp.weekday = 1;
// Construct a date from components made, using the calendar
NSDate* date = [cal dateFromComponents:comp];
// Create the month string
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"MMMM"];
return [dateFormatter stringFromDate:date];

How can I treat dates as if there were no DST adjustment?

Edit: My solution is below.
I have a sort of calendar-related app where the user is able to jump backward and forward certain integer numbers of days. In testing the app around midnight, I realized there is a DST-related problem, and I'm looking for the best solution.
The app is not really based on any calendar in iOS. However, I display the Gregorian date and time so the user has a reference to a familiar date and time.
One of the built in jumps in time is 73 days, which is about 1/5 year. Common dates used will be March equinox +/- integer multiples of 73 days. The problem comes, for example, when jumping from March equinox to a date 73 days previous, because the March equinox is likely to be in DST, whereas the other is not. It depends on the timezone setting on the system. The app, however, has nothing to do with timezone, so I'd rather display times as if there were no such thing as DST, yet still using the local timezone. I hope that makes sense.
Any suggestions on how to achieve this? If the question is too vague, maybe I can add more detail, but I don't want to confuse things by adding too much.
EDIT: I've decide to link sample screen prints from the app to illustrate what's going on. This picture:
http://victorspictures.com/trollart/h37a90c77#h56122c20
shows the configuration for this year's equinox. The caption explains how to read the device. Follow to the next two pictures in the gallery to see how to use the device to calculate the date of Easter.
Anyway, swipe gestures allow the user to move +/- one day or +/- 73 days, which is where the problem arises. I'm adding time to the date display, and maybe that is really all I need to do.
Solution: Celeda's suggestion
Added this method:
- (NSDate *)setNoon: (NSDate *)date
{
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *components = [calendar components:NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit fromDate:date];
[components setHour:12];
NSDate *todayNoon = [calendar dateFromComponents:components];
return todayNoon;
}
Then, everywhere a jump was made, it was simply encapsulated in the new method:
newDate = [self setNoon:[previousCode]];
It appears, based on your comment to Hot Licks's answer, that you have some number d representing days since the 1975 vernal equinox (which was at 1975-03-21 05:57:00 UTC), and you want figure out the corresponding day on the Gregorian calendar.
We can use the NSDate, NSCalendar, and NSDateComponents classes to perform this computation. To avoid the effects of DST, we need to set the time zones of the calendar and formatter to UTC.
NSInteger d = 73 * 189; // days since the 1975 vernal equinox
NSTimeZone *utcTimeZone = [NSTimeZone timeZoneWithName:#"UTC"];
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
calendar.timeZone = utcTimeZone;
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = #"yyyy-MM-dd";
formatter.timeZone = utcTimeZone;
NSDateComponents *epochComponents = [[NSDateComponents alloc] init];
epochComponents.year = 1975;
epochComponents.month = 3;
epochComponents.day = 21;
epochComponents.hour = 5;
epochComponents.minute = 57;
epochComponents.timeZone = utcTimeZone;
NSDate *epochDate = [calendar dateFromComponents:epochComponents];
NSLog(#"epochDate=%#", epochDate);
NSLog(#"epochDate from formatter=%#", [formatter stringFromDate:epochDate]);
NSDateComponents *offsetComponents = [[NSDateComponents alloc] init];
offsetComponents.day = d;
NSDate *computedDate = [calendar dateByAddingComponents:offsetComponents toDate:epochDate options:0];
NSLog(#"computedDate=%#", computedDate);
NSLog(#"computedDate from formatter=%#", [formatter stringFromDate:computedDate]);
Try commenting out the lines that set the time zone to UTC on the calendar and/or the formatter to see how DST changes the results.
For more information, read the Date and Time Programming Guide.

Number of days between two NSDate dates with time zone

I calculate number of days between two dates:
NSDateComponents *datesDiff = [calendar components: NSDayCalendarUnit
fromDate: someDate
toDate: currentDate
options: 0];
But this method has one disadvantage - it doesn't take in account time zone.
So, for example, if I'm in +2GMT and local time is 1:00AM, current date is yesterday.
How to compare dates in specified time zone (without 'hacking')?
PS: Preventing answers with calculation of time difference, I need difference of actual days:
yesterday 23:00 vs. today 1:00 - 1 day
yesterday 1:00 vs. today 23:00 - 1 day
today 1:00 vs. today 23:00 - 0 days
(all this in current time zone)
I don't know if it meets your criteria of not being hacky, but a fairly simple way seems to be defining a GMT adjusted date something like this:
NSDate *newDate = [oldDate dateByAddingTimeInterval:(-[[NSTimeZone localTimeZone] secondsFromGMT])
See this question for more details.
Why don't you configure the dateformatter to default all dates to GMT time and then compare.
NSDate *now = [NSDate date];
NSCalendar *gregorianCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSTimeZone *timeZone = [NSTimeZone timeZoneWithName:#"GMT"]; // Sets to GMT time.
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setCalendar:gregorianCalendar];
[formatter setTimeZone:timeZone];
[formatter setDateFormat:#"yyyyMMddHH"];
NSString *dateString = [formatter stringFromDate:now];
// do whatever with the dates
[gregorianCalendar release];
[formatter release];

Resources