objective-c create and compare time - running from different countries - objective-c

I have an app that is used in different countries. In my app I am performing a simple calculation that states if the current time is later than 3pm, do something.
This is how I am creating the 3pm.
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSDateComponents *dateAttempt = [[NSDateComponents alloc] init];
[dateAttempt setYear:2016];
[dateAttempt setMonth:05];
[dateAttempt setDay:18];
[dateAttempt setHour:15];
[dateAttempt setMinute:00];
NSDate *threePm = [calendar dateFromComponents:dateAttempt];
NSLog(#"%#", dateAttempt);
NSLog(#"%#", threePm);
I am creating the app in NewYork. When I run the above code I get...
2016-05-18 11:17:53.815 x[1312:37272] 2016-05-18 19:00:00 +0000
I guess that makes sense because NewYork is 4 hours behind UTC. (it's 11am at the time of me writing this)
So the threePm is giving me the UTC equivalent of 3pm in NY, and its working as expected. The problem is when I change the time on the laptop simulator to simulate being in another country. For example Greece.
If I switch the time on the pc to greece, the same code above gives a different result due to a different time adjustment to UTC.
How can I say... no matter where in the world the app is running, if time is later than 3pm (EST)... do something?

NSDate stores absolute time - actually the time offset from a fixed point in UTC time.
NSDateComponents has a timeZone property which is used as the basis for the conversion to NSDate, and this will default to the current timezone - as your results show.
If you always want 3pm in New York set this property. New York is one of the standard time zones: America/New_York; so you can set this property using:
dateAttempt.timeZone = [NSTimeZone timeZoneWithName:#"America/New_York"];
An NSTimeZone understands daylight savings and dependent on the date you set the conversion will translate from EST or EDT as appropriate.
HTH

Related

DateTime conversion in iOS when device's timezone change

I get time in EDT from my server in format yyyy-MM-ddTHH:mm:ss for instance 2014-05-21T09:30:00. I convert these to NSDate and save in sqlite on iphone using code below
NSString *strDate = #"2014-05-21T09:30:00";
NSDateFormatter *df = [[NSDateFormatter alloc] init];
[df setTimeZone:[NSTimeZone timeZoneWithName:#"EDT"]];
[df setDateFormat:#"yyyy-MM-dd'T'HH:mm:ss"];
NSDate *date = [df dateFromString:strDate];
It works fine until I change the timezone on the device. If I change time on device time gets messed up. Any idea what am I doing wrong here?
Device's timezone is Eastern and I save 8:00AM
I change device's timezone to Pacific and time changes to 5:00AM
I have an update process that updates all datetime values again and it goes back to 8AM
I now change device's timezone to Eastern and time goes to 11AM
I run the update process again and time goes back to 8AM
How can I save these times so no matter which timezone users is in, they always see eastern time.
Thanks,
D.
NSDate itself doesn't know about timezone; it's just a simple wrapper around a double value, which represents the number of seconds since some fixed time (I think Jan 1 2001 or something) UTC.
What you need is a NSDateFormatter, similar to the one you use to parse the date, to transform the NSDate into an NSString using the EDT timezone.

UIDatepicker returns wrong value

my UIDatepicker returns a wrong value and I don´t understand why. See picture.
It shows 0 hours and 1 min but the label shows -1 hour.
I don´t find an time zone to set up. I expected that my app uses the time zone of the machine it´s on.
My action to fill the label:
- (IBAction)datePickerDateChanged:(id)sender {
[_timerOutput setText:[NSString stringWithFormat:#"%#", [_timePicker date]]];
}
I´m on a German system and I set the UIDatepicker to German but it still shows hours and mins.
Any idea what I´m doing wrong?
Ronald
This is the date and time in GMT wintertime (== UTC). use a NSDateFormatter to adjust it, it should use the default timezone by default. you also can set another.
try
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];
[dateFormatter setTimeStyle:NSDateFormatterMediumStyle];
NSString *myDateString = [dateFormatter stringFromDate: [_timePicker date]];
NSLog(#"%#", myDateString);
Some theory:
A NSDate object does not represent what we call a date in every-days sense, like a day or a time of range, or what ever. It represents a single point in time. with sub-millisecond precision. And it does so by counting time intervals. From the beginning of a certain era. and by definition it is keeping UTC timezone as reference.
The internal counter will always be counting in UTC-context, it is your responsibility to display it correctly. But the NSDateFormatter is of huge help for that.
BTW: That your displayed time is a UTC time formatted one is also shown by +0000, as it has 00 hours and 00 minutes timezone offset from UTC.

How to best debug NSDate / NSTimezone related code

I know that NSDate does not represent a timezone, but perhaps someone can advise me on how to best debug its relationship to other classes, for example NSDatePicker, CalCalendarStore.
As an example, I want to set the date for a date picker to 01-01-2012. I do something like this:
newDate = [NSDate date];
NSCalendar *calendar = [NSCalendar autoupdatingCurrentCalendar];
NSDateComponents *components = [calendar components:(NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit) fromDate:newDate];
[components setMonth:1];
[components setDay:1];
newDate = [calendar dateFromComponents:components];
This works fine in my datepicker, but as soon as I try debugging date-related code, things get very confusing (with TZ-related shifts in the hour, sometimes even day). Can someone advise me on how to do this?
If I should use a date formatter (probably!), how should I do this so I can easily debug without cluttering up the code too much?
NSDate is just a wrapper around unix time and as such is not time zone aware at all, its actually your date formatter that puts a time zone of any kind on it.
Dealing with similar problems I ended up just forcing all my NSDate objects to use UTC dates (when not worrying about the "time" components themselves).
You have to use a new NSCalendar instance with the timezone property set to UTC to make those dates, but this way you're dealing with consistant dates, just use a UTC time zone set formatter to display them, or a UTC set Calendar to break them up into components as needed.

NSDate is 5 hours off

I run the following code:
NSDate *now = [NSDate date];
NSLog(#"now: %#", now);
and get :
2011-09-16 16:14:16.434 iSavemore[1229:7907] now: 2011-09-16 21:14:16 +0000
As you can see i'm running this at 16:14:16 (4:14 pm) but NSDate is returning 21:16:16 (9:14 pm!). Is this an Xcode4 issue or NSDate issue?
NSDate defaults to the Universal timezone (aka GMT).
I'm guessing you're somewhere on the East Coast, 5 hours behind UTC.
Try adding this to your date formatter...
NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
[dateFormatter setLocale:[NSLocale currentLocale]];
...and you should see your local time.
If you want to use a specified locale, rather than 'currentLocale', create a NSLocale for the relevant locale.
NSLocale *usLoc = [[NSLocale alloc] initWithLocaleIdentifier:#"en_US"];
[dateFormatter setLocale:usLoc];
...actually that's US (so possibly not Central).
More specific timezone help can be found here...
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSDateFormatter_Class/Reference/Reference.html
However, if you want to show expiry time, wouldn't you still want it in the user's currentLocale?
If you look at the output you'll see that the log includes the timezone:
2011-09-16 16:14:16.434 iSavemore[1229:7907] now: 2011-09-16 21:14:16 +0000
^^^^^^
The time stamp of your log is local time. I assume you're in a timezone that is 5 hours ahead of UTC.
A NSDate refers to a particular point in time. It's up to you to display this however you want; usually with an NSDateFormatter.
This is the reason why you'll see plenty of recommendations against storing a time, or a date as anything other than an NSDate. If you try and store it as a string you'll run into a lot of trouble later on when trying to handle the display in different timezones.
Try setting the time-zone of your NSDate to one that is fitting your need, for example [NSTimeZone localTimeZone]
Just a wild guess here, but maybe it has something to do with time zones?

Date since 1600 to NSDate?

I have a date that's stored as a number of days since January 1, 1600 that I need to deal with. This is a legacy date format that I need to read many, many times in my application.
Previously, I'd been creating a calendar, empty date components and root date like this:
self.gregorian = [[[NSCalendar alloc] initWithCalendarIdentifier: NSGregorianCalendar
] autorelease];
id rootComponents = [[[NSDateComponents alloc] init] autorelease];
[rootComponents setYear: 1600];
[rootComponents setMonth: 1];
[rootComponents setDay: 1];
self.rootDate = [gregorian dateFromComponents: rootComponents];
self.offset = [[[NSDateComponents alloc] init] autorelease];
Then, to convert the integer later to a date, I use this:
[offset setDay: theLegacyDate];
id eventDate = [gregorian dateByAddingComponents: offset
toDate: rootDate
options: 0];
(I never change any values in offset anywhere else.)
The problem is I'm getting a different time for rootDate on iOS vs. Mac OS X. On Mac OS X, I'm getting midnight. On iOS, I'm getting 8:12:28. (So far, it seems to be consistent about this.) When I add my number of days later, the weird time stays.
OS | legacyDate | rootDate | eventDate
======== | ========== | ==========================|==========================
Mac OS X | 143671 | 1600-01-01 00:00:00 -0800 | 1993-05-11 00:00:00 -0700
iOS | 143671 | 1600-01-01 08:12:28 +0000 | 1993-05-11 07:12:28 +0000
In the previous release of my product, I didn't care about the time; now I do. Why the weird time on iOS, and what should I do about it? (I'm assuming the hour difference is DST.)
I've tried setting the hour, minute and second of rootComponents to 0. This has no impact. If I set them to something other than 0, it adds them to 8:12:28. I've been wondering if this has something to do with leap seconds or other cumulative clock changes.
Or is this entirely the wrong approach to use on iOS?
I imagine you're right about the leap seconds/cumulative clock changes accounting for the time issue. Are the dates you're dealing with actually in the past, or is it purely an arbitrary epoch?
In either case, you could try defining a new epoch that's much closer to present day (say, the Cocoa epoch). Calculate a day delta between the new epoch and the old and save it as a constant. When you need to process a date, apply this delta to the date and then use your existing NSCalendar technique, but with your new epoch instead of the old. That will hopefully avoid the clock drift issue you're seeing.
It looks like the right answer is to make things simpler. Instead of making a rootDate, I just build the date from components every time. This should be no slower, and is still keeps the code really close to the idea.
Initial setup:
self.gregorian = [[[NSCalendar alloc] initWithCalendarIdentifier: NSGregorianCalendar
] autorelease];
self.components = [[[NSDateComponents alloc] init] autorelease];
[components setYear: 1600];
[components setMonth: 1];
(Obviously, properties and ivars are adjusted.)
Later, to actually convert a legacy date to a NSDate:
[components setDay: 1 + theLegacyDate];
id eventDate = [gregorian dateFromComponents: components];
This has these advantages for me:
It users fewer ivars.
It's less code.
It always returns midnight on that day, regardless of whether DST is in effect.
Note that iOS takes into account very obscure rules for various time zones. It is most likely that midnight, Jan 1st. 1600 in your timezone actually was at 7:12:28 UTC. There have been many cases where people complained about bugs in date conversions and then someone figured out that actually they are in a time zone that made some strange calendar change many years ago.
You need to find out first what exact NSDate your data represents. "Number of days since Jan 1st 1600" is nonsense, because you need a time zone! What you should do: Find a "legacy" number where you know what day it is supposed to represent. For example, if you "know" that 143671 is supposed to be 11th May 1993 in your time zone, then start with that date as the root date and add (x - 143671) days to it.