When trying to add a month using the suggested 'correct' method on all the stackoverflow questions I can find, the date I get back always loses an hour due to DST.
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSDateComponents *component = [[NSDateComponents alloc] init];
[component setDay:29];
[component setMonth:3];
[component setYear:2015];
NSDate *date = [gregorian dateFromComponents:component];
NSLog(#"Date: %#", date);
NSDateComponents *monthComponent = [[NSDateComponents alloc] init];
monthComponent.month = 1;
NSDate *newDate = [gregorian dateByAddingComponents:monthComponent toDate:date options:0];
NSLog(#"newDate: %#", newDate);
The output of this code is:
Date: 2015-03-29 00:00:00 +0000
newDate: 2015-04-28 23:00:00 +0000
How can I add a month across a DST change and keep the time at midnight?
Thanks for any help.
Example in response to gnasher729 below:
In the example the first two dates would be set at midnight and the second two would be 23:00. What would be the best practice to implement something like this while keeping all the outputted dates to the desired midnight time?
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSDateComponents *initialDateComponent = [[NSDateComponents alloc] init];
[initialDateComponent setDay:29];
[initialDateComponent setMonth:1];
[initialDateComponent setYear:2015];
NSDate *date = [gregorian initialDateComponent];
NSLog(#"Date: %#", date);
NSDateComponents *monthComponent = [[NSDateComponents alloc] init];
monthComponent.month = 1;
monthComponent.hour = 0;
NSMutableArray *eventAlarmDate = [[NSMutableArray alloc] init];
int numberOfRepeatingMonths = 4;
for(int x = 0; 0 < numberOfRepeatingMonths; x++) {
date = [gregorian dateByAddingComponents:monthComponent toDate:date options:0];
[eventAlarmDate addObject:date];
}
You are actually asking us how to introduce a bug in your code.
The result is correct. Adding one month during a DST change will and must add one hour more or one hour less than full days.
NSDate always displays its date in UTC, which is British time without DST correction. The date calculated is with DST corrections, and it depends on your location. If you are in India, midnight is at 5:30 am UTC (or 18:30 pm UTC on the previous day, not sure).
Related
I have 2 button, one for increase the current date and one for decrease the date, so can you tell me how can I manage it I have using to show the current date in textfield by this way:
NSDateFormatter *dateformate=[[NSDateFormatter alloc]init];
[dateformate setDateFormat:#"dd/MM/YYYY"];
date_String=[dateformate stringFromDate:[NSDate date]];
_dateTF.text = date_String;
NSDate *decreaseDate= [NSDate dateWithTimeInterval:-60 * 60 * 24 sinceDate:<somedate>];
NSDate *inceraseDate= [NSDate dateWithTimeInterval:60 * 60 * 24 sinceDate:<somedate>];
The above code snippet will helps you to get the previous and next date.
You can use this code,
//This code will give you next date from current date
NSDate *today = [[NSDate alloc] init];
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *offsetComponents = [[NSDateComponents alloc] init];
[offsetComponents setDay:1]; // you can change this as per your requirement, this will increase +1 date
NSDate *nextDate = [gregorian dateByAddingComponents:offsetComponents toDate:today options:0];
// This code will give previous date from current date
NSDate *today1 = [[NSDate alloc] init];
NSCalendar *gregorian1 = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *offsetComponents1 = [[NSDateComponents alloc] init];
[offsetComponents1 setDay:-1]; // you can change this as per your requirement, this will decrease -1 date
NSDate *previousDate = [gregorian1 dateByAddingComponents:offsetComponents1 toDate:today1 options:0];
You can change date formate, with you desire date format. Hope this helps.
Happy coding.
I have created a calendar in my app, using the date object this way:
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *weekdayComponents = [gregorian components:(NSDayCalendarUnit | NSYearCalendarUnit | NSMonthCalendarUnit | NSMinuteCalendarUnit)fromDate:[NSDate date]];
NSInteger day = [weekdayComponents day];
NSInteger month = [weekdayComponents month];
NSInteger year = [weekdayComponents year];
m_dateFormatter.dateFormat = #"dd/MM/yyyy";
[gregorian setTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"UTC"]];
NSDateComponents *timeZoneComps=[[NSDateComponents alloc] init];
[timeZoneComps setDay:day];
[timeZoneComps setMonth:month];
[timeZoneComps setYear:year];
[timeZoneComps setHour:00];
[timeZoneComps setMinute:00];
[timeZoneComps setSecond:01];
m_currentDate = [gregorian dateFromComponents:timeZoneComps];
When the user wants to go next month, I highlight the first date of that month. So, in this case, the date will be 1-06-2014,00:00:01.
Here is the code:
- (void)showNextMonth
{
// Move the date context to the next month
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *dateComps = [[NSDateComponents alloc] init];
[dateComps setMonth:1];
m_currentMonthContext =[gregorian dateByAddingComponents:dateComps toDate:m_currentMonthContext options:0];
NSDateComponents *weekdayComponents1 = [gregorian components:(NSDayCalendarUnit | NSWeekdayCalendarUnit | NSYearCalendarUnit | NSMonthCalendarUnit) fromDate:m_currentMonthContext];
NSInteger nextMonth = [weekdayComponents1 month];
NSInteger nextyear = [weekdayComponents1 year];
NSDateComponents *weekdayComponents2 = [gregorian components:(NSDayCalendarUnit | NSWeekdayCalendarUnit | NSYearCalendarUnit | NSMonthCalendarUnit) fromDate:m_currentDate];
NSInteger currentDay = [weekdayComponents2 day];
NSInteger currentMonth = [weekdayComponents2 month];
NSInteger currentYear = [weekdayComponents2 year];
NSInteger selectedDay = 1;
if(nextMonth == currentMonth && nextyear == currentYear)
{
selectedDay = currentDay;
}
NSInteger month = nextMonth;
[gregorian setTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"UTC"]];
NSDateComponents *timeZoneComps=[[NSDateComponents alloc] init];
[timeZoneComps setDay:selectedDay];
[timeZoneComps setMonth:month];
[timeZoneComps setYear:nextyear];
[timeZoneComps setHour:00];
[timeZoneComps setMinute:00];
[timeZoneComps setSecond:01];
m_currentMonthContext =[gregorian dateFromComponents:timeZoneComps];
[self createCalendar];
}
When m_currentMonthContext is calculated on the second to last line of the above method, its value is 1-06-2014,00:00:01.
createCalendar implementation:
-(void)createCalendar
{
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *weekdayComponents = [gregorian components:(NSDayCalendarUnit | NSWeekdayCalendarUnit | NSYearCalendarUnit | NSMonthCalendarUnit)fromDate:m_currentMonthContext];
NSInteger month = [weekdayComponents month];
NSInteger year = [weekdayComponents year];
}
Here I get month as 5 and year as 2014, but the date is 1-06-2014. This happens only in US time zone, in all other time zones it is working fine.
So I want to know how to handle timezones effectively, or in other sense, how to make sure that NSDate does not change even if time zone changes.
The proximate cause is that the time zone is not consistently set on the calendar when calculating dates and date components. Sometimes you set the time zone to UTC, and sometimes not, which is going to cause inconsistencies, as sometimes offsets for local time will be applied, and sometimes not.
In detail, in your situation, m_currentMonthContext is an NSDate which represents the UTC time one second after midnight on June 1st, 2014. In your createCalendar method, you create a calendar that is the local time of the user, and calculate the components for such a date. In all time zones in the US, it is still the month of May one second after midnight on June 1st, 2014 UTC. An example in code, that can be run in isolation:
NSCalendar *utcCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
[utcCalendar setTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"UTC"]];
NSCalendar *localCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDate *june = [NSDate dateWithTimeIntervalSince1970:1401580801];
NSDateComponents *utcComponents = [utcCalendar components:(NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit) fromDate:june];
NSDateComponents *localComponents = [localCalendar components:(NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit) fromDate:june];
NSLog(#"utc : %#", utcComponents);
NSLog(#"local: %#", localComponents);
Here in MDT time zone, this logs:
utc :
Calendar Year: 2014
Month: 6
Leap month: no
Day: 1
local:
Calendar Year: 2014
Month: 5
Leap month: no
Day: 31
To recap, you're keeping a date in memory that's been calculated to represent a certain calendar date in UTC time, and then calculating the calendar date in the user's local time, but it seems you have an incorrect expectation that calendars for different time zones will interpret the same date the same way.
So, what to do? Your example is pretty complex, but it seems there's no need at all to store date components sometimes in UTC time zone and sometimes not - be consistent. Now, it also seems to me that you can be much much simpler in your code if you just want to find the first day of the next month.:
NSCalendar *cal = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *comps = [cal components:(NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit) fromDate:[NSDate date]];
[comps setMonth:[comps month] + 1];
[comps setDay:1];
I tested this with December 15th, 2014, and it worked to create January 1st, 2015 in my local time. Hopefully that is consistent behavior.
To sum up - it's very likely a bug to not use a consistent calendar for your date component calculations. Sometimes having UTC and sometimes local is going to cause you nightmares. It seems like you should always calculate in local time, but I don't know the whole context of your application so can't make a blanket statement for that. Also, it should be safe to not rely on incessant conversions between dates and date components, and instead have the date component be your source of truth. That is, I mean it seems convoluted to convert date components to dates always to store in instance variables, but then to immediately convert the dates back into date components every time they're used - it seems better to just work with date components as much as possible.
From the comment, I hope I understand your question correctly. You can try this code:-
NSDate * nowDate = [NSDate date];
NSLog(#"nowDate: %#",nowDate);
NSDateFormatter *df = [NSDateFormatter new];
[df setDateFormat:#"dd/MM/yyyy HH:mm"];
df.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:[NSTimeZone localTimeZone].secondsFromGMT];
NSString *localDate = [df stringFromDate:nowDate];
NSLog(#"localDate: %#", localDate);
Output:
2014-05-24 23:03:06.205 TestTimeZone[10214:60b] nowDate: 2014-05-24
15:03:06 +0000
2014-05-24 23:03:06.209 TestTimeZone[10214:60b] localDate: 24/05/2014
23:03
[NSDate date] always return GMT+0 date, no matter where is your timezone. May be just use this? At the same time I used NSDateFormatter to set to my local date based on my laptop. You can try to change to a few different timezones on your mac while running the above code on simulator. [NSDate date] might be just what you need.
I'm trying to get the ordinal number of a week using NSCalendar so that I can calculate the number of weeks between two dates, however the method I'm using is demonstrating some weird behaviour.
I'm expecting a new week to begin every Sunday at 00:00:00, but instead it seems to happen at 23:58:45. I've tried changing the firstWeekday property of the calendar but that doesn't have any effect.
Example Code (note: 2014-03-09 is a Sunday)
- (void)testTimeWeeksBegins
{
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
// Make NSDates
NSDateComponents *comps = [[NSDateComponents alloc] init];
comps.year = 2014;
comps.month = 3;
comps.day = 9;
comps.hour = 23;
comps.minute = 58;
comps.second = 44;
NSDateComponents *compsToAdd = [[NSDateComponents alloc] init];
compsToAdd.second = 1;
NSDate *date23_58_44 = [calendar dateFromComponents:comps];
NSDate *date23_58_45 = [calendar dateByAddingComponents:compsToAdd toDate:date23_58_44 options:0];
// Calculate ordinality of week on both dates
NSUInteger ord23_58_44 = [calendar ordinalityOfUnit:NSWeekOfYearCalendarUnit inUnit:NSEraCalendarUnit forDate:date23_58_44];
NSUInteger ord23_58_45 = [calendar ordinalityOfUnit:NSWeekOfYearCalendarUnit inUnit:NSEraCalendarUnit forDate:date23_58_45];
// Output
NSLog(#"Date is %# and week number is %d", date23_58_44, ord23_58_44);
NSLog(#"Date is %# and week number is %d", date23_58_45, ord23_58_45);
}
Output
Date is 2014-03-09 23:58:44 +0000 and week number is 105043
Date is 2014-03-09 23:58:45 +0000 and week number is 105044
Am I being stupid and missing something obvious or is this a bug? I suppose my workaround would be to use a date with a time after 23:58:45, to ensure no problems in future?
I am new to objective c. I wish to do the following:
Convert 24 hour format to 12 hour and then add +2 to hour and display it like: 4:00 pm
I get the 12 hour format but after adding +2 to it , the time is displayed always as "am", i.e even if it is 4 pm it is displayed as 4 am. Below is my code:
NSDate *now = [NSDate date];
NSDateFormatter *timeFormatter=[[NSDateFormatter alloc]init];
timeFormatter.dateFormat=#"hh:00 a";
NSString *currentHour=[timeFormatter stringFromDate:now ];
lblcurrentHour.text=[NSString stringWithFormat:#"%#",currentHour];
NSLog(#"%#",currentHour);
int hour=[[timeFormatter stringFromDate:now]intValue];
NSDateFormatter *dateFormatter1 = [[NSDateFormatter alloc] init];
dateFormatter1.dateFormat = #"HH:mm";
NSDate *date1 = [dateFormatter1 dateFromString:[NSString stringWithFormat:#"%02d:00",hour+=3]];
dateFormatter1.dateFormat = #"hh:mm a";
lblnextHour.text = [dateFormatter1 stringFromDate:date1]; // prints 4:00 am not pm
How do i solve this? Where am i getting wrong?
If I understand your requirements correctly, you want to take the current time and display the minutes as :00, anchoring to the current hour. Then you want to add two hours and display that time. The following code prints 04:00 AM and 06:00 AM to the console (local time is 0421.)
For calendrical calculations, I would avoid using NSDateFormatter as you are doing when you compute the time two hours from now. There are too many ways that can go astray. For example, what happens when the now time is 2300?
A good reference on calendrical calculations in Cocoa is here
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[])
{
#autoreleasepool {
// use gregorian calendar for calendrical calculations
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
// get current date
NSDate *date = [NSDate date];
NSCalendarUnit units = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit;
units |= NSHourCalendarUnit | NSMinuteCalendarUnit;
NSDateComponents *currentComponents = [gregorian components:units fromDate:date];
// change the minutes to 0
currentComponents.minute = 0;
date = [gregorian dateFromComponents:currentComponents];
// format and display the time
NSDateFormatter *timeFormatter = [[NSDateFormatter alloc] init];
timeFormatter.dateFormat = #"hh:mm a";
NSString *currentTimeString = [timeFormatter stringFromDate:date];
NSLog(#"Current hour = %#",currentTimeString);
// add two hours
NSDateComponents *incrementalComponents = [[NSDateComponents alloc] init];
incrementalComponents.hour = 2;
NSDate *twoHoursLater = [gregorian dateByAddingComponents:incrementalComponents toDate:date options:0];
// format and display new time
NSString *twoHoursLaterStr = [timeFormatter stringFromDate:twoHoursLater];
NSLog(#"Two hours later = %#",twoHoursLaterStr);
}
return 0;
}
Try this:
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"hh:mm:ss a"];
NSLog(#"Today's Date and Time: %#", [formatter stringFromDate:[NSDate date]]);
Output:
Today's Date and Time: 02:43:33 PM
Scenario:
I have an expense tracking iOS Application and I am storing expenses from a expense detail view controller into a table view (with fetched results controller) that shows the list of expenses along with the category and amount and date. I do have a date attribute in my entity "Money" which is a parent entity for either an expense or an income.
Question:
What I want is to basically categorize my expenses for a given week, a month, or year and display it as the section header title for example : (Oct 1- Oct 7, 2012) and it shows expenses amount and related stuff according to that particular week. Two buttons are provided in that view, if I would press the right button, it will increment the week by a week (Oct 1- Oct 7, 2012 now shows Oct8 - Oct 15, 2012) and similarly the left button would decrement the week by a week.
How would I accomplish that? I am trying the following code - doesn't work.
- (void)weekCalculation
{
NSDate *today = [NSDate date]; // present date (current date)
NSCalendar* calendar = [NSCalendar currentCalendar];
NSDateComponents* comps = [calendar components:NSYearForWeekOfYearCalendarUnit |NSYearCalendarUnit|NSMonthCalendarUnit|NSWeekCalendarUnit|NSWeekdayCalendarUnit fromDate:today];
[comps setWeekday:1]; // 1: Sunday
firstDateOfTheWeek = [[calendar dateFromComponents:comps] retain];
[comps setWeekday:7]; // 7: Saturday
lastDateOfTheWeek = [[calendar dateFromComponents:comps] retain];
NSLog(#" first date of week =%#", firstDateOfTheWeek);
NSLog(#" last date of week =%#", lastDateOfTheWeek);
firstDateOfWeek = [dateFormatter stringFromDate:firstDateOfTheWeek];
lastDateOfWeek = [dateFormatter stringFromDate:lastDateOfTheWeek];
}
Code for incrementing date -
- (IBAction)showNextDates:(id)sender
{
int addDaysCount = 7;
NSDateComponents *dateComponents = [[[NSDateComponents alloc] init] autorelease];
[dateComponents setDay:addDaysCount];
NSDate *newDate1 = [[NSCalendar currentCalendar]
dateByAddingComponents:dateComponents
toDate:firstDateOfTheWeek options:0];
NSDate *newDate2 = [[NSCalendar currentCalendar]
dateByAddingComponents:dateComponents
toDate:lastDateOfTheWeek options:0];
NSLog(#" new dates =%# %#", newDate1, newDate2);
}
Suppose the week shows like this (Nov4, 2012 - Nov10, 2012) and I press the increment button, I see in the console, date changes to Nov11,2012 and Nov.17, 2012 which is right but if I press the increment button again, it shows the same date again (Nov 11, 2012 and Nov.17, 2012).
Please help me out here.
Declare currentDate as an #property in your class. And try this.
#property(nonatomic, retain) NSDate *currentDate;
Initially set
self.currentDate = [NSDate date];
before calling this method.
- (void)weekCalculation
{
NSCalendar* calendar = [NSCalendar currentCalendar];
NSDateComponents* comps = [calendar components:NSYearForWeekOfYearCalendarUnit |NSYearCalendarUnit|NSMonthCalendarUnit|NSWeekCalendarUnit|NSWeekdayCalendarUnit fromDate:self.currentDate];
[comps setWeekday:1]; // 1: Sunday
firstDateOfTheWeek = [[calendar dateFromComponents:comps] retain];
[comps setWeekday:7]; // 7: Saturday
lastDateOfTheWeek = [[calendar dateFromComponents:comps] retain];
NSLog(#" first date of week =%#", firstDateOfTheWeek);
NSLog(#" last date of week =%#", lastDateOfTheWeek);
firstDateOfWeek = [dateFormatter stringFromDate:firstDateOfTheWeek];
lastDateOfWeek = [dateFormatter stringFromDate:lastDateOfTheWeek];
}
Once the view is loaded self.currentDate value should be updated from showNextDates. Make sure it is not getting reset anywhere else.
- (IBAction)showNextDates:(id)sender
{
int addDaysCount = 7;
NSDateComponents *dateComponents = [[[NSDateComponents alloc] init] autorelease];
[dateComponents setDay:addDaysCount];
NSDate *newDate1 = [[NSCalendar currentCalendar]
dateByAddingComponents:dateComponents
toDate:firstDateOfTheWeek options:0];
NSDate *newDate2 = [[NSCalendar currentCalendar]
dateByAddingComponents:dateComponents
toDate:lastDateOfTheWeek options:0];
NSLog(#" new dates =%# %#", newDate1, newDate2);
self.currentDate = newDate1;
}
I had needed similar thing in one of my old projects and achieved it via the code below. It sets this weeks date to an NSDate variable and adds/removes 7 days from day component in each button click. Here is the code:
NSCalendar *calendar = [NSCalendar currentCalendar];
[calendar setTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"GMT"]];
NSDate *date = [NSDate date];
NSDateComponents *components = [calendar components:(NSYearCalendarUnit|NSWeekdayCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit) fromDate:date ];
//
[components setDay:([components day] - ([components weekday] )+2)];
self.currentDate = [calendar dateFromComponents:components];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = #"dd.MM.yyyy HH:mm:ss";
self.datelabel.text = [formatter stringFromDate:self.currentDate];
The code above calculates this week start and sets it to currentDate variable. I Have two UIButtons with UIActions named prevClick and nextClick which calls the method that sets the next or previous weekstart:
- (IBAction)prevClick:(id)sender {
[self addRemoveWeek:NO];
}
-(void)addRemoveWeek:(BOOL)add{
NSCalendar *calendar = [NSCalendar currentCalendar];
[calendar setTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"GMT"]];
NSDateComponents *components = [calendar components:(NSYearCalendarUnit|NSWeekdayCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit) fromDate:self.currentDate ];
components.day = add?components.day+7:components.day-7;
self.currentDate = [calendar dateFromComponents:components];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = #"dd.MM.yyyy HH:mm:ss";
self.datelabel.text = [formatter stringFromDate:self.currentDate];
}
- (IBAction)nextClk:(id)sender {
[self addRemoveWeek: YES];
}