Search code examples
swiftfoundationnstimezone

The purpose behind TimeZone vs NSTimeZone


Why there is a TimeZone with an API significantly different than NSTimeZone?

Say NSTimeZone has init(name: String) and TimeZone does not.

NSTimeZone has abbreviation property and in TimeZone abbreviation is a function that takes Date as a parameter (and nil is not an option)

Interestingly enough TimeZone(identifier:) digests abbreviations just fine.


Solution

  • The Foundation framework offers quite a few type pairs where the API of the original NS-prefixed type, as imported from the original Objective-C code, was less than optimal for Swift. Among these type pairs are

    and others.

    TimeZone and NSTimeZone are one such pair, where NSTimeZone is an Objective-C type which is exposed to Swift as-is, while TimeZone is a native Swift wrapper around NSTimeZone with an updated API. That updated API is intentionally different, because the original Objective-C API may be suboptimal in Swift.

    For example, as you note, NSTimeZone offers both an abbreviation property and an -abbreviationForDate: method, while TimeZone only offers the method. It's important to note that the abbreviation property just returns the result of calling -abbreviationForDate:, passing in [NSDate now]; i.e., it exists only because Objective-C doesn't support default arguments for methods, so abbreviation is a convenience. Swift, however, does have default arguments, so TimeZone.abbreviation(for:) unifies these: if you want to pass a specific Date, you can, but you don't have to.

    There are other reasons for disparate APIs on paired types like this:

    • -[NSTimeZone initWithName:] is an API that's poorly named: although its parameter is called "name", the actual argument is supposed to be a time zone identifier, which has a completely different format. This has to be called out in the NSTimeZone documentation in several places (e.g., "Although many NSTimeZone symbols include the word “name”, they actually refer to IDs."), which isn't great: ideally, the API should speak for itself. When TimeZone was introduced, the Foundation team had an opportunity to improve on the name of the API, so they introduced the same initializer on TimeZone as TimeZone.init(identifier:)

    • Following the Foundation conventions of mutable collections, NSData is an immutable data type which has a mutable counterpart in NSMutableData. Beside the complications that come from NSMutableData being a subclass of NSData (and NSData being a "class cluster"), Swift doesn't need to make this sort of distinction: when you work with value types, you don't need both mutable and immutable variants — you get mutability with var/let. Data, then, is a value type implementation which unifies the interfaces that NSData and NSMutableData offer into a single type (also removing some warts along the way)

    • Many language features in Swift are difficult to replicate on existing Objective-C types, or would require a complete rethink of the type and its interface in order to be effective in Swift. This is the case with NSAttributedString/NSMutableAttributedString and AttributedString: NSAttributedString is an extremely dynamic type, offering few available tools for convenience and safety; AttributedString makes use of a wide range of tools that Swift provides to offer type safety, autocompletion, and a host of conveniences that necessarily require a completely different API surface

    In all, it's very rare for the Swift variant of a type pair like this to offer less API than its Objective-C counterpart — and where API was removed, it's almost always for safety, ergonomics, or in favor of better APIs. But in general, the Swift variants typically have significantly more functionality, even if how that functionality is accessed is a little different.