NSDatePicker setDate doesn't work as expected - objective-c

I'm trying to set time on a NSDatePicker according to a certain offset that I have in milliseconds. When I set the time, the value that I see is off and I can't figure out why.
Here is the code I use to set the picker:
- (void)setDatePicker{
int targetmillisondsFromMidnight = [self.schedule.targetHour intValue]; //Value is: 61680000 milliseconds whis is equal to 17:08 UTC (or 19:08 in my local time);
NSDate* todayMidnight = [NSCalendar.currentCalendar startOfDayForDate:[NSDate new]];
NSTimeZone* timezone = [NSTimeZone localTimeZone]; //Value is: Local Time Zone (Asia/Jerusalem (GMT‎+2‎) offset 7200)
NSInteger seconds = [timezone secondsFromGMT]; //Value is: 7200
todayMidnight = [todayMidnight dateByAddingTimeInterval:seconds]; // Value is: 2019-12-25 00:00:00 UTC
NSDate* scheduleDate = [NSDate dateWithTimeInterval:targetmillisondsFromMidnight/1000 sinceDate:todayMidnight]; //Value is: 2019-12-25 17:08:00 UTC
NSCalendar *calendar = [NSCalendar currentCalendar];
[calendar setTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"UTC"]];
NSDateComponents *components = [calendar components:(NSCalendarUnitHour | NSCalendarUnitMinute) fromDate:scheduleDate];
[self.datePicker setDate:[calendar dateFromComponents:components] animated:YES];
}
I stop with a breakpoint at the last command in the function and I print some values, the outputs that I get are:
po [components hour] = 17
po [components minute] = 8
po [calendar dateFromComponents:components] = 001-01-01 17:08:00 +0000
So from my understanding the date is set with 17:08.
What I expect to see on the time picker is 19:08, however what I see is 19:28. I can't figure out where those 20 minutes are coming from.

Try this code. Date picker uses while setting time current timezone and uses UTC offset based on passed date (001-01-01 17:08:00 +0000) and looks for offset in timezone database at this timepoint. Because at that time (year zero) there were no timezones, it could not be found in tz database so time zone offset was calculated based on mean solar time, so you got offset 2:20 (approx.) for your area.
- (void)setDatePicker {
int targetmillisondsFromMidnight = 61680000; //Value is: 61680000 milliseconds whis is equal to 17:08 UTC (or 19:08 in my local time);
NSCalendar *calendar = NSCalendar.currentCalendar;
NSTimeZone* timezone = [NSTimeZone timeZoneWithName:#"Asia/Jerusalem"]; //Value is: Local Time Zone (Asia/Jerusalem (GMT‎+2‎) offset 7200)
calendar.timeZone = timezone;
NSDate* todayMidnight = [calendar startOfDayForDate:[NSDate new]];
NSInteger seconds = [timezone secondsFromGMT]; //Value is: 7200
todayMidnight = [todayMidnight dateByAddingTimeInterval:seconds]; // Value is: 2019-12-25 00:00:00 UTC
NSDate* scheduleDate = [NSDate dateWithTimeInterval:targetmillisondsFromMidnight/1000 sinceDate:todayMidnight]; //Value is: 2019-12-25 17:08:00 UTC
NSDate *date = [scheduleDate dateByAddingTimeInterval:seconds];
self.datePicker.timeZone = [NSTimeZone timeZoneWithAbbreviation:#"UTC"];
[self.datePicker setDate:date animated:YES];
}

Related

NSDate conversion is messed due to timezone

I'm trying to put a time value on a time picker, the problem is that the time gets messed up because of time zone issue. Here's the code snippet I use:
int targetmillisondsFromMidnight = [self.schedule.targetHour intValue]; // 59580000
NSDate* todayMidnight = [NSCalendar.currentCalendar startOfDayForDate:[NSDate new]]; //2019-12-23 00:00:00 UTC
NSTimeZone* timezone = [NSTimeZone defaultTimeZone];
NSInteger seconds = [timezone secondsFromGMT];
todayMidnight = [todayMidnight dateByAddingTimeInterval:seconds];
NSDate* scheduleDate = [NSDate dateWithTimeInterval:targetmillisondsFromMidnight/1000 sinceDate:todayMidnight]; //2019-12-23 16:33:00 UTC
NSCalendar *calendar = [NSCalendar currentCalendar];
[calendar setTimeZone:[NSTimeZone defaultTimeZone]];
NSDateComponents *components = [calendar components:(NSCalendarUnitHour | NSCalendarUnitMinute) fromDate:scheduleDate];
[self.datePicker setTimeZone:[NSTimeZone defaultTimeZone]];
[self.datePicker setDate:[calendar dateFromComponents:components] animated:YES];
When I do po [components hour] instead of getting 16 as in the scheduleDate I get 18.
How can I fix this?
I tried to change the time zones to localTimeZone and timeZoneWithAbbreviation:#"GMT" but nothing seems to work.
It looks like you do superfluous conversion
Let's consider the following code
NSDate *date = [NSDate new]; // << in UTC always
NSCalendar *calendar = [NSCalendar currentCalendar];
[calendar setTimeZone:[NSTimeZone defaultTimeZone]]; // << in local
NSDateComponents *components = [calendar components:
(NSCalendarUnitHour | NSCalendarUnitMinute) fromDate:date]; // << converted to local
debug output:
(lldb) po date
2019-12-24 09:53:12 +0000
(lldb) po components
<NSDateComponents: 0x600003c78710> {
Hour: 10
Minute: 53

NSDate is wrong when doing 'dateFromString'

Im here in the UK and when working with dates in iOS they are always out by one hour (one hour behind), what I need is the correct time from an NSDate. Ive done the following, but i get two different times:
NSDate *today = [NSDate date];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc]init];
dateFormatter.dateFormat = #"yyyy-MM-dd HH:mm:ss";
[dateFormatter setTimeZone:[NSTimeZone systemTimeZone]];
NSLog(#"NSDate %#",today);
NSLog(#"Time %#", [dateFormatter stringFromDate:today]);
NSDate*stringDate = [dateFormatter dateFromString:[dateFormatter stringFromDate:today]];
NSLog(#"Time date %#",stringDate);
Here is what is logged:
NSDate 2015-07-01 16:07:22 +0000
Time 2015-07-01 17:07:22
Time date 2015-07-01 16:07:22 +0000
Why is this happening? Am i missing something obvious? Surely if the string date is correct, then doing dateFromString should yield the correct results?
The reason I need an NSDate is so I can get the correct amount of seconds using [myTime timeIntervalSince1970]
2015-07-01 17:07:22 //is the correct date
Im expecting an NSDate object that is correct to my date and time.
Update
The answers below helped me find where I was going wrong, so I changed my approach, I was able to get the current timestamp doing the following:
NSString *timeStamp = [dateFormatter stringFromDate:[NSDate date]];
NSDate *curdate = [dateFormatter dateFromString:timeStamp];
int unix_timestamp = [curdate timeIntervalSince1970];
NSDate* referenceDate = [NSDate dateWithTimeIntervalSince1970: 0];
NSTimeZone* timeZone = [NSTimeZone systemTimeZone];
int offset = (int)[timeZone secondsFromGMTForDate: referenceDate];
int currentTimestamp = unix_timestamp + offset;
NSLog(#"CUrrent time stamp %d",currentTimestamp);
NSDate is an absolute moment of time, it does not have a timezone. The date object you have is correct: it is exactly the moment that code was executed.
If you need string representation of that moment of time in a specific time zone, use stringFromDate: just like you did.
If you need to know number values of hour/minute in a specific time zone, use -[NSCalendar components:fromDate:].
[calendar setTimeZone:...];
NSDateComponents* components = [calendar components:(NSHourCalendarUnit|NSMinuteCalendarUnit) fromDate:date];
The date formatter defaults to the local timezone. If you want a different timezone specify it. NSLog of a date used the 'NSDatedescription` method that defaults to GMT (UTC).
Examining the code:
NSDate *today = [NSDate date];
// Creates today's data in GMT (UTC) All NSDates are referenced to GMT.
NSLog(#"NSDate %#",today); (moved up for explanation ordering)
// NSDate 2015-07-01 16:07:22 +0000
// Displays the date in GMT
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc]init];
dateFormatter.dateFormat = #"yyyy-MM-dd HH:mm:ss";
[dateFormatter setTimeZone:[NSTimeZone systemTimeZone]];
// Creates a date for matter with the system timezone
NSLog(#"Time %#", [dateFormatter stringFromDate:today]);
// Time 2015-07-01 17:07:22
// Creates a string representation in the system timezone and displays it
NSDate*stringDate = [dateFormatter dateFromString:[dateFormatter stringFromDate:today]];
// Creates a date from the string taking into the system timezone
NSLog(#"Time date %#",stringDate);
// Time date 2015-07-01 16:07:22 +0000
// Displays the date in GMT.

Converting any date to local date issue

I am stuck somewhere in NSDate and NSDateFormatter due to timezone issues.
I need to send time to server only in UTC(which is converted to unix time).
Here is my few steps what I am doing:
Selecting a date from the calender should be added with the current time and converted to UTC.
Compare the selected date with the current date. Just to know whether selected date is a past or future date. (Few other operations are to be done based on past/future/current date).
I have tried this code:
In a Category on NSDate :
-(NSDate *) toLocalTime{
NSDate* sourceDate = self;
NSTimeZone* sourceTimeZone = [NSTimeZone timeZoneWithName:#"UTC"];
NSTimeZone* destinationTimeZone = [NSTimeZone systemTimeZone];
NSInteger sourceGMTOffset = [sourceTimeZone secondsFromGMTForDate:sourceDate];
NSInteger destinationGMTOffset = [destinationTimeZone secondsFromGMTForDate:sourceDate];
NSTimeInterval interval = destinationGMTOffset - sourceGMTOffset;
NSDate* destinationDate = [[NSDate alloc] initWithTimeInterval:interval sinceDate:sourceDate];
return destinationDate;
}
But the problem exists when I try to convert date to local, (at times I am not sure the current time is in local timezone or not). If they are in UTC then the above method works fine.
If the time is already in local timezone then it again adds the interval and I am getting incorrect time.
I am out of ideas, please help me.
Any idea will be highly appreciated.
NSDate represents the time since Jan 1st 1970 in UTC. Never try to pretend it is anything else. Never try to think of an NSDate as being in a particular local time.
So what you need is a date from a calendar + an offset which represents the time since midnight today.
To get today 0:00am UTC, you first need a Gregorian calendar for the UTC time zone.
NSTimeZone* utcTimeZone = [NSTimeZone timeZoneWithName:#"UTC"];
NSCalendar* gregorian = [[NSCalendar alloc] initWithCalendarIdentifier: NSGregorianCalendar];
[gregorian setTimeZone: utcTimeZone];
Now you use date components to get the hours, minutes and seconds since midnight UTC
NSUInteger unitFlags = NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;
NSDate *date = [NSDate date];
NSDateComponents *comps = [gregorian components: unitFlags fromDate:date];
If you have a date that is midnight UTC on the date from your calendar, you can get midnight UTC + your hours, minutes and seconds like this:
NSDate* theDateIWant = [gregorian dateByAddingComponents: comps
toDate: midnightUTCDateFromCalendar
options: 0];
NSLog(#"The final date is %#", theDateIWant);

NSDate results from -(NSDate *)dateFromComponents:(NSDateComponents *)comps

I want to get a NSDate object with NSDateComponents but the hour of the NSDate is 1 hour lower then in the components.
NSCalendar *cal = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
[cal setLocale:[NSLocale currentLocale]];
[cal setTimeZone:[NSTimeZone localTimeZone]];
NSDateComponents *hourComp = [[NSDateComponents alloc] init];
hourComp.timeZone = [NSTimeZone localTimeZone];
[hourComp setHour:18];
NSLog(#"%#",hourComp);
NSLog(#"%#",[cal dateFromComponents:hourComp]);
NSLog(#"%#",[NSDate date]);
Console Output:
2012-04-10 19:10:57.505 TestDate[9861:f803] <NSDateComponents: 0x6b6ad30>
TimeZone: Europe/Berlin (CEST) offset 7200 (Daylight)
Hour: 18
2012-04-10 19:10:57.507 TestDate[9861:f803] 0001-01-01 17:06:32 +0000
2012-04-10 19:10:57.508 TestDate[9861:f803] 2012-04-10 17:10:57 +0000
What I want is 18:xx:xx
I note that you're in Europe, and one hour ahead of GMT. When you set the date to 18:xx it thinks you are setting it in Europe, and so it subtracts one hour before storing it in GMT. When you print it out, you're not asking it to be formatted for local time, so it's giving you GMT, which is 17:xx.
You need to use an NSDateFormatter, and set its locale and time zone - just as you did with the NSCalendar object - and then call stringFromDate on it, passing the date you've created.

Producing an NSDate with fixed time

I am trying to produce an NSDate with fixed hour and minutes. I need this to make an equal comparison with other date stored in CoreData.
So far I wrote this code:
NSDate date = [NSDate date];
unsigned int flags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit;
NSCalendar* calendar = [NSCalendar currentCalendar];
NSDateComponents* components = [calendar components:flags fromDate:date];
NSDate* newDate = [calendar dateFromComponents:components];
however with a breakpoint in xcode4 I can see the values:
Printing description of date:
2012-01-10 11:20:47 +0000
Printing description of newDate:
2012-01-09 23:00:00 +0000
Why newDate is one day back in respect of date ?
/* EDIT */
I also have tried to set manually all the components, but calendar dateFromComponents always give back same one hour back date, seems ignoring the components.
components.hour=0;
components.minute=0;
components.second=0;
components.timeZone=[NSTimeZone localTimeZone];
This is the description of component after being set:
<NSDateComponents: 0x7464de0>
TimeZone: Europe/Rome (CET) offset 3600
Calendar Year: 2012
Month: 1
Day: 10
Hour: 0
Minute: 0
Second: 0
which is exactly what I would like to have, but the calculated date with this component is still
Printing description of newDate:
2012-01-09 23:00:00 +0000
I wonder why I cannot get a precise NSDate even with specifying all the components in an NSDateComponents. Just because NSCalendar is ignoring my requirements, what's the meaning of components ?
What am I doing wrong ?
I guess you are +01:00 time zone. Actually the NSDate always gives values in GMT. So if it is Jan 10th, 00:00, then at the same time GMT time is Jan 9th, 23:00.
Even, while printing the following line
2012-01-10 11:20:47 +0000,
it should have printed 1 hour less than your current time. Please check.
Use this....
NSDate *date = [NSDate date];
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
[gregorian setLocale:[NSLocale currentLocale]];
NSDateComponents *nowComponents = [gregorian components:NSYearCalendarUnit | NSWeekCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit fromDate:date];
NSDate* newDate = [gregorian dateFromComponents:nowComponents];
NSLog(#"%#\n%#",date,newDate);
You may have problems with time zones, try setting a time zone for the calendar.