Search code examples
iosswiftdatelocalizationnsdateformatter

Swift DateFormatter properties not changing


This is a very puzzling situation, I've read lots of documentation on the NSDateFormatter but I can't seem to be able to set the localized date format template. Using the dateFormat property works but using setLocalizedDateFormatFromTemplate does not, essentially I have the following code in swift 5.3:

First in a terminal open a swift REPL and type the following:

import Foundation // takes a few secs

var ftt = DateFormatter()
ftt.locale = Locale(identifier: "en_US")
ftt.setLocalizedDateFormatFromTemplate("'Deliver on' MMMM d 'at' h:mm a 'sharp'")

After running I get the following output:

ftt: DateFormatter = {
  baseNSFormatter@0 = {
    baseNSObject@0 = {
      isa = NSDateFormatter
    }
  }
  _attributes = 3 key/value pairs {
    [0] = {
      key = "locale"
      value =
    }
    [1] = {
      key = "formatterBehavior"
      value = Int64(1040)
    }
    [2] = {
      key = "dateFormat"
      value = "MMMM d, h:mm a"
    }
  }
  _formatter = {}
  _counter = 0
  _cacheGeneration = 3
  _behavior = 0
}

As you can see from the output neither the locale or the dateFormat have been stored. Formatting a date results in the following:

 14> ftt.string(from: Date())
$R1: String = "October 21, 8:19 PM"

I've made sure that the locale identifier is correct, I've followed a few tutorials on DateFormatter such as:

And checked usage of the setLocalizedDateFormatFromTemplate as well as Apple's documentation and make sure to call it after setting locale.

If I assign the dateFormat property directly I get the desired result:

17> ftt.dateFormat = "'Deliver on' MMMM d 'at' h:mm a 'sharp'"
ftt: DateFormatter = {
  baseNSFormatter@0 = {
    baseNSObject@0 = {
      isa = NSDateFormatter
    }
  }
  _attributes = 3 key/value pairs {
    [0] = {
      key = "locale"
      value =
    }
    [1] = {
      key = "formatterBehavior"
      value = Int64(1040)
    }
    [2] = {
      key = "dateFormat"
      value = "\'Deliver on\' MMMM d \'at\' h:mm a \'sharp\'"
    }
  }
  _formatter =
  _counter = 0
  _cacheGeneration = 2
  _behavior = 0
}

18> ftt.string(from: Date())
$R2: String = "Deliver on October 21 at 8:25 PM sharp"

What's going on?! am I missing something obvious? I want to understand the behavior.

Thanks in advance!


Solution

  • I think you misunderstand what setLocalizedDateFormatFromTemplate does. Its documentation says:

    Calling this method is equivalent to, but not necessarily implemented as, setting the dateFormat property to the result of calling the dateFormat(fromTemplate:options:locale:) method, passing no options and the locale property value.

    Now what does dateFormat(fromTemplate:options:locale:) do? Lets see:

    Return Value

    A localized date format string representing the date format components given in template, arranged appropriately for the locale specified by locale.

    The returned string may not contain exactly those components given in template, but may—for example—have locale-specific adjustments applied.

    So dateFormat(fromTemplate:options:locale:) tries to localise the template to a specified locale. If no locale is specified, it uses Locale.current. For example:

    // this produces "MM/dd/yyyy"
    DateFormatter.dateFormat(fromTemplate: "yyyy dd MM", options: 0, locale: Locale(identifier: "en-US"))
    

    That explains why it removes all the quoted strings in your format, because the localisation engine doesn't recognise your quoted strings, so to produce a "localised" version of your date format, the best it can do is to remove them. As far as it is concerned, the quoted strings could be in a different language!

    So it's not that setLocalizedDateFormatFromTemplate did not change dateFormat. It did change it to "MMMM d, h:mm a", which is what iOS thinks is the best "localised" version of the format

    "'Deliver on' MMMM d 'at' h:mm a 'sharp'"
    

    In this situation, you should set dateFormat directly, rather than setLocalizedDateFormatFromTemplate, as you don't want a localised date format.