I have some Date Strings on server, which I want to parse in my app, get dates and display it on calendar which my app has and not the device calendar.
To do this I am doing it this way
_dateFromatter = [[NSDateFormatter alloc] init];
[_dateFromatter setDateFormat:#"dd-MMM-yyyy"];
[_dateFromatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:#"en_US"]];
[_dateFromatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"UTC"]];
_gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
[_gregorian setTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"UTC"]];
NSDate *historyDate = [_dateFromatter dateFromString:[data objectForKey:#"dateStringFromServer"]];
if(historyDate != nil)
{
NSDateComponents *weekdayComponents = [_gregorian components:(NSDayCalendarUnit | NSWeekdayCalendarUnit | NSYearCalendarUnit | NSMonthCalendarUnit)fromDate:historyDate];
NSInteger year = [weekdayComponents year];
NSInteger month = [weekdayComponents month];
NSInteger day = [weekdayComponents day];
NSDateComponents *timeZoneComps=[[NSDateComponents alloc] init];
[timeZoneComps setYear:year];
[timeZoneComps setMonth:month];
[timeZoneComps setDay:day];
[timeZoneComps setHour:00];
[timeZoneComps setMinute:00];
[timeZoneComps setSecond:01];
NSDate *convertedHistoryDate =[_gregorian dateFromComponents:timeZoneComps];
}
I am adding a break point and making some checks when the execution stops at a break point.
Now here, on server the string is 8-Jul-2014, and If I mouse hover the date I see 2014-07-08 05:30:00 IST, and if I click on info I get 2014-07-08 00:00:00 +0000,
Secondly Now If I change the timezone for NSCalendar and dateformatter to localtime zone and If I check get this 2014-07-08 00:00:00 IST and inside info I get 2014-07-07 18:30:00 +0000
So I am not understanding these two cases. Also I want to know whenever I get dates from server , does time zone matters and what time zone it should be?
Because my users can be in any timezone? so when they sync data they should get whatever dates are present on server. I mean if 8-Jul-2014 is present on server then, if user in US is syncing it should not make it 7-Jul -2014 it should remain 8 -Jul -2014 only.
Regards
Ranjit
Regarding Dates, they are actually same represented by different timezone..
2014-07-08 05:30:00 IST/+05:30
2014-07-08 00:00:00 +0000
2014-07-08 00:00:00 IST/+05:30
2014-07-07 18:30:00 +0000
You need to set the formatter timezone correct while converting date to string and visa versa...
[inputDateFormatter setTimeZone:[NSTimeZone timeZoneWithName:#"UTC"]];//server timezone
[outputDateFormatter setTimeZone:[NSTimeZone systemTimeZone]];//user's timezone
Related
I've spent a good few hours trying to make this work. Here's what I'm trying to do:
Take input from a UITextField in the form HH:mm AM/PM
Convert that string into an NSDate object, and update the month/day/year properties of that NSDate to reflect the curren month/day/year.
Add 4 hours to the time of the NSDate object.
That last bit isn't working. It works for times such as 12:00 PM and 12:30 PM but for times such as 2:30 PM, it will output 4:30 PM rather than 6:30 PM as expected. Here is my code, broken up to reflect those three tasks.
Task 1 - checking to see if the text input was HH:mm AM/PM
NSDateFormatter *format = [[NSDateFormatter alloc] init];
[format setLocale:[[NSLocale alloc] initWithLocaleIdentifier:#"en_US"]];
[format setTimeZone:[NSTimeZone systemTimeZone]];
[format setDateFormat:#"HH:mm a"];
NSString *dateString = textField.text;
NSLog(#"input datestring: %#", dateString);
NSDate *parsed = [format dateFromString:dateString];
if (parsed) {
NSLog(#"datestring is valid, %#", parsed);
}
Task 2- Updating the m/d/y components of that date to reflect today's.
//gregorian calendar, get the hour and minute components from the input time
NSCalendar *greg = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSDateComponents *components = [greg components: NSCalendarUnitHour | NSCalendarUnitMinute fromDate:parsed];
//get the month/day/year components from the current date
NSDateComponents *comps = [[NSCalendar currentCalendar] components:NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear fromDate:[NSDate date]];
//set the components of the original date to the month/day/year components of today
[components setYear:comps.year];
[components setDay:comps.day];
[components setMonth:comps.month];
//create the new date.
NSDate* newDate = [greg dateFromComponents:components];
NSLog(#"########### %#", newDate);
Task 3 - Add 4 hours.
int hours = 4 ;
NSString *output;
NSDateComponents *add = [[NSDateComponents alloc] init];
[add setHour:hours];
newDate = [greg dateByAddingComponents:add toDate:newDate options:0];
NSDateFormatter *df = [[NSDateFormatter alloc] init];
[df setDateStyle:NSDateFormatterShortStyle];
[df setTimeStyle:NSDateFormatterShortStyle];
output = [df stringFromDate:newDate];
NSLog(#"new date: %#", output);
Log:
2015-08-27 16:03:35.672 Del Taco[1960:117431] input datestring: 2:30 pm
2015-08-27 16:03:35.673 Del Taco[1960:117431] datestring is valid, 2000-01-01 20:30:00 +0000
2015-08-27 16:03:35.673 Del Taco[1960:117431] ########### 2015-08-27 19:30:00 +0000
2015-08-27 16:03:35.674 Del Taco[1960:117431] new date: 8/27/15, 4:30 PM
I think I have this figured out. It was my date formatter where I set the format like this:
#"HH:mm a"
I went to Unicode's website to double check that I was doing this right, and ended up changing my format string to:
#"h:mm a"
And things seem to be working out well!
I am trying trying to get a create an object of NSDate this way
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
// [gregorian setTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"UTC"]];
NSDateComponents *weekdayComponents = [gregorian components:(NSDayCalendarUnit | NSYearCalendarUnit | NSMonthCalendarUnit | NSMinuteCalendarUnit)fromDate:[NSDate date]];
NSInteger day = [weekdayComponents day];
NSInteger month = [weekdayComponents month];
NSInteger year = [weekdayComponents year];
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 I log the currentDate, I get this
"2014-07-07 06:00:01 +0000"
If I save this to plist I get this
07-Jul-2014 11:30:01 am
If I use "UTC" as the time zone
I get it as
"2014-07-07 00:00:01 +0000"
and inside plist it shows as
07-Jul-2014 5:30:01 am
I am not understanding what is going on, Can anybody help me out with this
I want to know, if I dont set any timeZone what timeZone will be used?
Do we need to explicitly set UTC as timeZone or we need to use systemTimeZone.
The thing you need to realise about NSDate is that there is no such thing as day, week, month, year, timezone, etc...
An NSDate is purely a point in time. It has no information about what time zone it is etc...
Effectively it is just a number. You can think of it as using UTC if that helps.
The classes that give you things like time zone are NSCalendar, NSDateFormatter, etc...
I'm guessing you're in a timezone of UTC+6? Thats why when you create a date with 00:00:01 and you log it out then it displays as 06:00:01.
This is nothing to do with the NSDate. What you are seeing in the console is a string. It is a representation of the date object using the default time zone (your own).
I'd have to see you code for saving to a plist but I suspect this is the same problem. You are saving something and assuming it is using a particular time zone when in reality it isn't.
I have a calendar app, in which I highlight current date,
I have followed the following steps, at present my device time is 11:59 am,
Indian time zone, I change it to first 9.am, then I change time zone to san jose ,
I get time as 25th june 9.34 pm, now in device calendar it shows 25th june, but
in my app it still shows 26th june.
I am getting current date this way
-(void)initialize
{
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
[gregorian setTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"UTC"]];
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 setTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"UTC"]];
m_dateFormatter.dateFormat = #"dd/MM/yyyy";
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];
}
So, I need your help in this regard.
I think that [NSTimeZone systemTimeZone] will always give you the current timezone as detected (or set) by the device. Since the timezone is cached in your app you can also call [NSTimeZone resetSystemTimeZone] to make sure that the info is up to date. So, in your code:
[NSTimeZone resetSystemTimeZone];
[m_dateFormatter setTimeZone:[NSTimeZone systemTimeZone]];
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.
This is how I setup my datePicker
self.datePicker = [[UIDatePicker alloc] init];
self.datePicker.timeZone = [NSTimeZone localTimeZone];
This is how I save the date that I selected
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateStyle:NSDateFormatterMediumStyle];
[formatter setTimeZone:[NSTimeZone localTimeZone]];
dateToSave = [formatter dateFromString:self.dateTextField.text];
NSLog(#"date saved = %#", dateToSave);
If I select Nov 18 2013 from the date picker, the NSLog shows
date saved = 2013-11-17 16:00:00 +0000
However, somewhere in my code, I need to get the difference in days between today's date and the date that I selected in the datepicker.
NSDateComponents *dateComponents = [[NSCalendar currentCalendar] components:NSDayCalendarUnit fromDate:[NSDate date] toDate:dateSaved options:0];
NSLog(#"number of days => %i", [dateComponents day]);
Today is Nov 10. The date I saved is Nov 18. But the number of days difference is 7, instead of 8.
Your time zone is -8. 2013-11-17 16:00:00 +0000 equals to 2013-11-18 00:00:00 -0800.
Use [NSTimeZone timeZoneForSecondsFromGMT:0] instead of [NSTimeZone localTimeZone]
(This answer refers to the updated question about calculating the number of days
between two dates.)
The problem is that [NSDate date] is the current date+time, not the start of the current day. For example, if
[NSDate date] = "2013-11-10 10:00:00"
dateSaved = "2013-11-18 00:00:00" (both in your *local* timezone)
then the difference between
these two dates is "7 days and 14 hours". Therefore you get 7 as the number of days.
So you have to calculate the start of the current day first:
NSDate *startOfDay;
[[NSCalendar currentCalendar] rangeOfUnit:NSDayCalendarUnit
startDate:&startOfDay
interval:NULL
forDate:[NSDate date]];
and then use it in the calculation of the difference:
NSDateComponents *dateComponents = [[NSCalendar currentCalendar] components:NSDayCalendarUnit
fromDate:startOfDay
toDate:dateSaved
options:0];
NSDateFormatter *date_form=[[NSDateFormatter alloc]init];
[date_form setDateFormat:#"dd/MM/yyyy"];
NSDate *seletected_date = [datepicker date];
NSString *dateToSave=[[NSString alloc] initWithFormat:#"%#",[date_form stringFromDate:seletected_date]];
NSLog(#"date saved = %#", dateToSave);
Remove localtimezone