Value of [NSTimeZone localTimeZone] differs between iOS10 and iOS11 GM

We're currently looking into moving our app to iOS 11 when we discovered a portion of our legacy code used to convert UTC date times to localized date time strings is returning differing values between iOS 10 and iOS 11. When running a fresh install of our app on simulators (after resetting content and settings) we see the following output:


10.3.1:

(lldb) po [NSTimeZone localTimeZone]

Local Time Zone (America/Toronto (EDT) offset -14400 (Daylight))


11.0:

(lldb) po [NSTimeZone localTimeZone]

Local Time Zone (America/Los_Angeles (PDT) offset -25200 (Daylight))


I created a fresh project and have the same results when I run the following snippit highlighting the issue:


- (void)logLocalizedDate {

    // Setup: Create a UTC date time
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss zzz"];
    NSDate *utcDate = [dateFormatter dateFromString: @"2017-08-18 00:00:00 UTC"];
    [NSTimeZone setDefaultTimeZone:[NSTimeZone timeZoneWithName:@"UTC"]];
   
    // Now convert it to a local time zone string
    NSString *localizedDateString = [self computeLocalTimeZoneDayStringFromDate:utcDate];
    NSLog(@"Computed Date String: %@", localizedDateString);
}
-(NSString *)computeLocalTimeZoneDayStringFromDate:(NSDate *)dateToCompute
{

    // Create a localized date formatter
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en"]];
    [dateFormatter setDateFormat:@"yyyy-MM-dd"];
  
    // This call differs between iOS10 and iOS 11
    dateFormatter.timeZone = [NSTimeZone localTimeZone];
    
    NSString *date = [dateFormatter stringFromDate:dateToCompute];
    return date;
}


Anything wrong here? We've never had an issue in the past and similar code in our app has been ported from iOS 8 -> 9 -> 10 with no issues. In fact it hasn't changed since 2014.


Thanks

Accepted Reply

The various NSTimeZone properties (

localTimeZone
,
defaultTimeZone
and
systemTimeZone
) are kinda confusing. In general I recommend that folks use the system time zone because app-specific time zones don’t make much sense on Apple’s ‘shrink wrap’ platforms.

Notably, in Swift, these have all collapsed into one

current
property, which is a wrapper around
systemTimeZone
.

Having said that,

localTimeZone
should defer to
defaultTimeZone
which should defer to
systemTimeZone
as long as no one has overridden the default, so the difference in behaviour you’re seeing is a bit weird. I’m curious if you can replicate it on a real device. The simulator is a good general testing facility but real devices represent the acid test.

As to what you should do with your code, my recommendations are based on the assumption that

-logLocalizedDate
is just test code that you’ve included to illustrate the problem, and thus it’s not code that you expect to deploy. If that’s wrong, please let me know, ’cause that code is quite worrying (-:

So, focusing on

-computeLocalTimeZoneDayStringFromDate:
, I have a bunch of suggestions:
  • NSDateFormatter
    defaults to the system time zone, so you should just delete line 22.
  • Your code is relying on the fact that the

    en
    locale you set in line 18 gives you the Gregorian calendar. That’s true, but not something I’d want to rely on. I’d be happier use
    en_US_POSIX
    , as discussed in QA1480 NSDateFormatter and Internet Dates.
  • Better yet, use

    NSISO8601DateFormatter
    for dealing with fixed-format dates (assuming your deployment target allows that).
  • Date formatters can be kinda slow, so it’s generally a good idea to cache them. This is especially true when dealing with fixed-format dates, where you don’t have to adapt to the user changing their locale settings.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Replies

Yes, but the time zone isn't "in" your app, it's in the device, or simulator in this case.


Looking in the settings for Xcode 9 simulator, there weirdly isn't a Date & Time section. I'd assume, therefore, that the date/time is locked to the host Mac settings. Is your Mac set to LA time?


(I dunno whether the missing Date & Time settings is a bug or not.)

The various NSTimeZone properties (

localTimeZone
,
defaultTimeZone
and
systemTimeZone
) are kinda confusing. In general I recommend that folks use the system time zone because app-specific time zones don’t make much sense on Apple’s ‘shrink wrap’ platforms.

Notably, in Swift, these have all collapsed into one

current
property, which is a wrapper around
systemTimeZone
.

Having said that,

localTimeZone
should defer to
defaultTimeZone
which should defer to
systemTimeZone
as long as no one has overridden the default, so the difference in behaviour you’re seeing is a bit weird. I’m curious if you can replicate it on a real device. The simulator is a good general testing facility but real devices represent the acid test.

As to what you should do with your code, my recommendations are based on the assumption that

-logLocalizedDate
is just test code that you’ve included to illustrate the problem, and thus it’s not code that you expect to deploy. If that’s wrong, please let me know, ’cause that code is quite worrying (-:

So, focusing on

-computeLocalTimeZoneDayStringFromDate:
, I have a bunch of suggestions:
  • NSDateFormatter
    defaults to the system time zone, so you should just delete line 22.
  • Your code is relying on the fact that the

    en
    locale you set in line 18 gives you the Gregorian calendar. That’s true, but not something I’d want to rely on. I’d be happier use
    en_US_POSIX
    , as discussed in QA1480 NSDateFormatter and Internet Dates.
  • Better yet, use

    NSISO8601DateFormatter
    for dealing with fixed-format dates (assuming your deployment target allows that).
  • Date formatters can be kinda slow, so it’s generally a good idea to cache them. This is especially true when dealing with fixed-format dates, where you don’t have to adapt to the user changing their locale settings.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

I'm having a similar problem, in particular around our unit testing. We have a test observer that overrides NSTimeZone.default so all our tests can rely on the same time zone. However for the iOS 11 SDK, many tests are failing because the date formatters and our usage of TimeZone.current or TimeZone.autoupdatingCurrent are no longer respecting this override. Some guidance here and an explanation of what exactly changed would be greatly appreciated.


Thanks!

I’m not sure what guidance you’re looking for here. The iOS 11 behaviour is the iOS 11 behaviour. If that’s causing problems for your test setup, you should definitely file a bug about that (please post your bug number, just for the record). However, you still have to figure out a way to restructure your tests to deal with this behaviour, and such a solution is likely to be very tightly bound to how your tests are structured.

For example, if you run your unit tests using the standard XCTest infrastructure then you can force the time zone (at least in the simulator) by setting the

TZ
environment variable. In my case I set
TZ
to
UTC
at which point
NSTimeZone.systemTimeZone
always comes back at UTC.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
  • We have a test plan and I have added an environment variable as part of the Configuration file.

    "environmentVariableEntries" : [ { "key" : "TZ", "value" : "GMT" } ],

    But the timezone is still unchanged in the tests. Calendar and Timezone objects still reflects my machine's timezone. What am I missing?

Add a Comment

I guess I'm trying to understand why test assertions that were working in the 10 SDK and now failing in the 11 SDK. I'd like to see some documentation on what exactly changed between the versions so we can validate our logic.

Hi eskimo!


Thanks for your reply. You're 100% correct that logLocalizedDate is test code to highlight the issue haha. Not to worry - it's not production on our end at all. We also cache the date formatters we're using. As for the issue:


We have duplicated the issue on different devices (iOS 9/10 vs iOS 11) by running code similar to the above code. All devices are running in the same timezone. We've worked around the issue by no longer deferring to the localTimeZone at all (like you suggested) and instead deferring to the default by deleting line 22. I've filed a bug with Apple for clarity on the behaviour change but we'll stick with this change going forward. Also looking into changing the locale to be more universally friendly as per your comment on using `en_US_POSIX` and the referring issue - great resource BTW.

Thanks for the help!

@jateb wrote:

I've filed a bug with Apple for clarity on the behaviour change

Thanks. What was that bug number btw?

blunge wrote:

I'd like to see some documentation on what exactly changed between the versions so we can validate our logic.

The best way to request that is to file a bug against the documentation. As I mentioned earlier, it sounds like the Foundation folks are in the process of collapsing all of these time zone properties into one ‘current time zone’ property (as reflected by the Swift API), and I agree that it’d be nice if the documentation reflected that.

Please post your bug number, just for the record.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

The bug number (if I'm reading this correctly in Bug Reporter) is 34392012.

Thanks for the help Quinn. Moving away from setting NSTimeZone.default and using the TZ env variable solved our issues. I'll try to reproduce the 10/11 SDK discrepency behavior on our original logic in a sample project and open a bug.

Hi Eskimo, I was reading your instructions, but I don't fully understand these steps:

For example, if you run your unit tests using the standard XCTest infrastructure then you can force the time zone (at least in the simulator) by setting the

TZ
environment variable. In my case I set
TZ
to
UTC
at which point
NSTimeZone.systemTimeZone
always comes back at UTC.

How can I set that environment variable? Through the terminal? Through XCode?
Thanks in advance!

How can I set that environment variable? Through the terminal? Through Xcode?

I’m not sure about Terminal but in Xcode you can set environment variables using the scheme editor (Product > Scheme > Edit Scheme). If you select the Run action on the left and then the Arguments tab on the right, you’ll see an Environment Variables list.

There is a similar option for the Test action, although in this case you get a checkbox that allows you to use the settings from Run action (“Use the Run action’s arguments and environment variables”).

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
With Xcode 12.3 and iOS 14.3, the TZ environment variable seems to work only the first time after you erase settings & content on the Simulator - after that, if you run a different scheme with a different TZ value, it "sticks" to the first value you used. So it works, but if you have a scheme per country, let's say, then you will have to restart the simulator to correctly simulate a new country. Would sure be nice if it worked properly.
If you have a scheme per country say, for generating appropriate screenshots and testing, this can be somewhat annoying. I believe running UITests always restarts the device first, so this behavior only appears if you are running the simulator manually. Probably explains why Apple has not found and fixed this one.

Would sure be nice if it worked properly.

Quite. If you haven’t done so already, I encourage you to file an enhancement request describing your specific requirements.

Please post your bug number, just for the record.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"