[Swift] Do not implement the format from date / time, number, currency, data size, list, person's name, number with unit to String by yourself.

** Reiwa 1998 ……? ** **

This article is the 25th day of the last day of iOS Advent Calendar 2020. Since I started writing the article, I've noticed that this content is valid for platforms other than his iOS that can use Swift. It's already late, so I'll keep going like this ...


If you want Swift to convert, for example, Date to String, November 28, 2020 23:59, use DateFormatter.

However, the format of this data to String is pre-prepared with Formatter for various elements other than Date.

--Date and time

Why you should use Formatter

The implementation for formatting each data to String may seem straightforward.

Then, if you want the date, Date to be"2020/11/28 21:31", take the year, month, day, hour, and minute and "\ (year)/\ (month)/\ (day) \ (hour) Let's say: \ (minute) ". But please wait. What if there is a user who has "24 hours display" turned off (= 9:31 pm) in the iPhone settings? What if the calendar is the Japanese calendar (= Reiwa 2 years) instead of the Western calendar? In the first place, if you are in the United States, the notation " Nov 28, 2020 at 9:31 PM " is better.

In addition, there are some areas where the number 1,234.56 (Sennihyakusanjuyontengoroku) and the decimal point are written as "," instead of ".", Which means (Ichitennisanyon? Goroku). It will be.

What if you also want to display the size of the data, 134217728 bytes, like "134.2 MB"? Would you like to program yourself to prefix K, M, G with how many times you divide by 1024? How should we handle the numbers after the decimal point? This may be enough for now, but what if you come with T, P, E?

There are endless things to consider when it comes to converting to String. If there is a leak in the implementation, a person who is "born in 1998" will appear.

So let's quietly use each Formatter already provided in Foundation. I'll also post a few lines of samples, so it might be useful someday to put "I had this Formatter "in the corner of my head.

Prologue

You can jump by clicking the class name. The information is as of the time of writing the article.

Availability iOS macOS tvOS watchOS Linux etc. (Swift)
DateFormatter 2.0+ 10.0+ 9.0+ 2.0+ ✅ Supported
DateComponentsFormatter 8.0+ 10.10+ 9.0+ 2.0+ ❌ Unimplemented
RelativeDateTimeFormatter 13.0+ 10.15+ 13.0+ 6.0+ ❌ Unimplemented
DateIntervalFormatter 8.0+ 10.10+ 9.0+ 2.0+ ✅ Supported
ISO8601DateFormatter 10.0+ 10.12+ 10.0+ 3.0+ ✅ Supported
NumberFormatter 2.0+ 10.0+ 9.0+ 2.0+ ✅ Supported
ByteCountFormatter 6.0+ 10.8+ 9.0+ 2.0+ ✅ Supported
ListFormatter 13.0+ 10.15+ 13.0+ 6.0+ ❌ Unimplemented
PersonNameComponentsFormatter 9.0+ 10.11+ 9.0+ 2.0+ ❌ Unimplemented
MeasurementFormatter 10.0+ 10.12+ 10.0+ 3.0+ :warning: Unimplemented

: warning: Click here before integration into MeasurementFormatter

Availability iOS macOS tvOS watchOS Linux etc. (Swift)
LengthFormatter 8.0+ 10.10+ 9.0+ 2.0+ ✅ Supported
MassFormatter 8.0+ 10.10+ 9.0+ 2.0+ ✅ Supported
EnergyFormatter 8.0+ 10.10+ 9.0+ 2.0+ ✅ Supported

DateFormatter

It is probably how to use this DateFormatter that many articles are hit by Google search. You can get a formatted String from Date and vice versa.

let date = Date(timeIntervalSince1970: 1608856860)

let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .short

formatter.locale = Locale(identifier: "ja_JP")
print(formatter.string(from: date)) // 2020/12/25 9:41

formatter.locale = Locale(identifier: "en_US")
print(formatter.string(from: date)) // Dec 25, 2020 at 9:41 AM

formatter.locale = Locale(identifier: "zh_HK")
print(formatter.string(from: date)) //December 25, 2020 9 pm:41

Specifies the style for String with dateStyle and timeStyle. Specifies the locale for locale to be String (otherwise Locale.current is used).

_ Please do not put String directly into DateFormatter.dateFormat and convert it to Date String. ** The WWDC session also says, "Don't do it. You'll almost certainly get the wrong result." _

let date = Date(timeIntervalSince1970: 1608856860)
let formatter = DateFormatter()
formatter.dateFormat = "dMMM" //❌ Never stop ❌

formatter.locale = Locale(identifier: "en_US")
print(formatter.string(from: date)) //25Dec ❌ Accident

formatter.locale = Locale(identifier: "ja_JP")
print(formatter.string(from: date)) //25 December ❌ Major accident

/* ---------- */

formatter.locale = Locale(identifier: "en_US")
//✅ For example, setLocalizedDateFormatFromTemplate(_:)use
formatter.setLocalizedDateFormatFromTemplate("dMMM")
print(formatter.string(from: date)) // Dec 25

formatter.locale = Locale(identifier: "ja_JP")
//✅ For example, setLocalizedDateFormatFromTemplate(_:)use
formatter.setLocalizedDateFormatFromTemplate("dMMM")
print(formatter.string(from: date)) //December 25

DateComponentsFormatter

The one related to the time was DateFormatter, The time-related thing is DateComponentsFormatter.

//When the locale is Japanese environment
//Hayabusa No. 44 Shin-Hakodate Hokuto~Time required in Tokyo
let components = DateComponents(hour: 3, minute: 58)

let formatter = DateComponentsFormatter()

formatter.unitsStyle = .abbreviated
print(formatter.string(from: components)) //3 hours 58 minutes

formatter.unitsStyle = .positional
print(formatter.string(from: components)) // 3:58

Specifies the style to make String with unitsStyle. You will get a String that matches the locale in your execution environment.

RelativeDateTimeFormatter

As the class name suggests, this is a Formatter that returns the relative date and time relationship. By changing dateTimeStyle to .named, localized words such as "yesterday", "today", "tomorrow", etc. are returned.

let formatter = RelativeDateTimeFormatter()
formatter.locale = Locale(identifier: "ja_JP")
formatter.dateTimeStyle = .named

print(formatter.localizedString(from: DateComponents(month: -1))) //last month
print(formatter.localizedString(from: DateComponents(month: 0))) //this month
print(formatter.localizedString(from: DateComponents(month: 1))) //next month

print(formatter.localizedString(from: DateComponents(day: -3))) //3 days ago
print(formatter.localizedString(from: DateComponents(day: -2))) //day before yesterday
print(formatter.localizedString(from: DateComponents(day: -1))) //yesterday
print(formatter.localizedString(from: DateComponents(day: 0))) //today
print(formatter.localizedString(from: DateComponents(day: 1))) //tomorrow
print(formatter.localizedString(from: DateComponents(day: 2))) //day after tomorrow
print(formatter.localizedString(from: DateComponents(day: 3))) //3 days later

DateIntervalFormatter

Use DateIntervalFormatter to format the date and time range. You can see that it differs from "~" and "–" depending on the region. You can also customize it by specifying a format string in dateTemplate. This string uses the Patterns defined in the Date Format Patterns of Unicode Technical Standard # 35 (https://unicode.org/reports/tr35/tr35-dates.html#Date_Format_Patterns).

let formatter = DateIntervalFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .short

let startDate = Date(timeIntervalSince1970: 1608856860)
let endDate = Date(timeInterval: 86400, since: startDate)

formatter.locale = Locale(identifier: "en_US")
print(formatter.string(from: startDate, to: endDate)) // 12/25/20, 9:41 AM – 12/26/20, 9:41 AM

formatter.locale = Locale(identifier: "zh_HK")
print(formatter.string(from: startDate, to: endDate)) // 25/12/2020 noon 9:41 to 26/12/2020 noon 9:41

formatter.locale = Locale(identifier: "ja_JP")
print(formatter.string(from: startDate, to: endDate)) // 2020/12/25 9:41~2020/12/26 9:41

formatter.dateTemplate = "EEE, MMM d, ''yyyy"
print(formatter.string(from: startDate, to: endDate)) //December 25, 2020(Money)~ 26th(soil)

ISO8601DateFormatter

A Formatter that can convert between ISO 8601 format time string and Date. You can also customize it with formatOptions.

let formatter = ISO8601DateFormatter()

let dateString = "2020-12-25T09:41:00Z"
let date = formatter.date(from: dateString)
print(date) // Optional(2020-12-25 09:41:00 +0000)

/* ---------- */

formatter.formatOptions = [.withYear, .withMonth, .withDay, .withTime]

let date = Date(timeIntervalSince1970: 1608889260)
let dateString = formatter.string(from: date)
print(dateString) // 20201225T094100

NumberFormatter

NumberFormatter allows you to format numbers and currencies. There are too many functions, and this alone will make the volume of one article or more, so only a simple sample ...

let formatter = NumberFormatter()

//Comma is inserted every 3 digits
formatter.numberStyle = .decimal
formatter.locale = Locale(identifier: "ja_JP")
print(formatter.string(from: 1234.56)) // Optional("1,234.56")

//Decimal point(decimalSeparator)Is represented by "comma" instead of "dot" in some areas
formatter.numberStyle = .decimal
formatter.locale = Locale(identifier: "fr_FR")
print(formatter.string(from: 1234.56)) // Optional("1 234,56")

/* ---------- */

//Percentage display
formatter.numberStyle = .percent
formatter.locale = Locale(identifier: "ja_JP")
print(formatter.string(from: 0.95)) // Optional("95%")

//In some areas the percentage comes before the number
formatter.numberStyle = .percent
formatter.locale = Locale(identifier: "tr_TR")
print(formatter.string(from: 0.95)) // Optional("%95")

//The percent sign (percentSymbol) is "%There are some areas that are not
formatter.numberStyle = .percent
formatter.locale = Locale(identifier: "ar_EG")
print(formatter.string(from: 0.95)) // Optional("٩٥٪؜")

/* ---------- */

//currency"ja_JP"
formatter.numberStyle = .currency
formatter.locale = Locale(identifier: "ja_JP")
// formatter.currencyCode = "JPY" //If not specified, by locale
print(formatter.string(from: 1234.56)) // Optional("¥1,235") //Rounded

//currency"en_US"
formatter.numberStyle = .currency
formatter.locale = Locale(identifier: "en_US")
print(formatter.string(from: 1234.56)) // Optional("$1,234.56")

//currency"zh_HK"
formatter.numberStyle = .currency
formatter.locale = Locale(identifier: "zh_HK")
print(formatter.string(from: 1234.56)) // Optional("HK$1,234.56")

/* ---------- */

//Put a half-width space at the beginning to match the character string width
formatter.numberStyle = .decimal
formatter.formatWidth = 6
print(formatter.string(from: 100)) // Optional("   100")

/* ---------- */

//Other numberStyle ("ja_JP")
formatter.locale = Locale(identifier: "ja_JP")

formatter.numberStyle = .scientific
print(formatter.string(from: 123456789)) // Optional("1.23456789E8")

formatter.numberStyle = .spellOut
print(formatter.string(from: 123456789)) // Optional("123,456,789")

formatter.numberStyle = .ordinal
print(formatter.string(from: 2)) // Optional("No. 2")

formatter.numberStyle = .currencyAccounting
print(formatter.string(from: -12345)) // Optional("(¥12,345)") //Negative numbers become parentheses

formatter.numberStyle = .currencyISOCode
print(formatter.string(from: 12345)) // Optional("JPY 12,345")

formatter.numberStyle = .currencyPlural
print(formatter.string(from: 12345)) // Optional("12,345 yen")

/* ---------- */

//Specify positivePrefix or negativePrefix
formatter.numberStyle = .currency
formatter.positivePrefix = "△"
formatter.negativePrefix = "▲"
print(formatter.string(from: 1234.56)) // Optional("△1,234.56")
print(formatter.string(from: -1234.56)) // Optional("▲1,234.56")

ByteCountFormatter ByteCountFormatter | Apple Developer Documentation

You can get the byte size formatted like KB or MB. It supports up to YB, but even if you include Int64.max, it is 9.22 EB, so ZB and YB are only displayed in decimal numbers.

let formatter = ByteCountFormatter()

formatter.countStyle = .file
print(formatter.string(fromByteCount: 134217728)) // 134.2 MB

formatter.countStyle = .memory
print(formatter.string(fromByteCount: 134217728)) // 128 MB

formatter.countStyle = .decimal
print(formatter.string(fromByteCount: 134217728)) // 134.2 MB

formatter.countStyle = .binary
print(formatter.string(fromByteCount: 134217728)) // 128 MB

ListFormatter ListFormatter | Apple Developer Documentation

You can convert the elements of an array into a single string. The argument type of ListFormatter.string (from:) is [Any], so you can put another Formatter in ListFormatter.itemFormatter.

var languages: [String] = []
let formatter = ListFormatter()

// "ja_JP"
formatter.locale = Locale(identifier: "ja_JP")

languages = ["English", "Japanese", "Chinese"]
print(formatter.string(from: languages)) // Optional("English, Japanese, Chinese")

languages = ["English", "Japanese"]
print(formatter.string(from: languages)) // Optional("English, Japanese")


// "en_US"
formatter.locale = Locale(identifier: "en_US")

languages = ["English", "Japanese", "Chinese"]
print(formatter.string(from: languages)) // Optional("English, Japanese, and Chinese")

languages = ["English", "Japanese"]
print(formatter.string(from: languages)) // Optional("English and Japanese")

/* ---------- */

//Combination with other Formatter
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .percent //Display as a percentage

let listFormatter = ListFormatter()
listFormatter.locale = Locale(identifier: "en_US")
listFormatter.itemFormatter = numberFormatter

let items = [0.85, 0.12, 0.34]
print(listFormatter.string(from: items)) // Optional("85%, 12%, and 34%")

PersonNameComponentsFormatter PersonNameComponentsFormatter | Apple Developer Documentation

Returns a properly formatted string of people's names. Conversely, you can also extract each element of the name (last name, first name, etc.) from the character string.

let formatter = PersonNameComponentsFormatter()
var components = PersonNameComponents()
components.familyName = "Kabayaki"
components.middleName = "eel"
components.givenName = "Taro"
components.nickname = "KT"

print(formatter.string(from: components)) //Kabayaki Taro

formatter.style = .short
print(formatter.string(from: components)) // KT

formatter.style = .medium
print(formatter.string(from: components)) //Kabayaki Taro

formatter.style = .long
print(formatter.string(from: components)) //Kabayaki Taro Eel

formatter.style = .abbreviated
print(formatter.string(from: components)) //Kabayaki

/* ---------- */

var components = formatter.personNameComponents(from: "Kamayaki-san Taro")
print(components?.familyName) // Optional("Kabayaki")
print(components?.middleName) // nil
print(components?.givenName) // Optional("Taro")

components = formatter.personNameComponents(from: "John Appleseed")
print(components?.familyName) // Optional("Appleseed")
print(components?.middleName) // nil
print(components?.givenName) // Optional("John")

The PersonNameComponentsFormatter is also introduced in the following talk of iOSDC Japan for your reference.

MeasurementFormatter

You can get a formatted string by specifying a value with Measurement <UnitType> and putting it in MeasurementFormatter.string (from:). There are as many as 22 types of Units in UnitType.

Show a list of `UnitType` and links to documents
let formatter = MeasurementFormatter()
formatter.numberFormatter.locale = Locale(identifier: "ja_JP")

//length
let length = Measurement<UnitLength>(value: 100, unit: .meters)
print(formatter.string(from: length)) // 0.1 km

//temperature
let temperature = Measurement<UnitTemperature>(value: 68.0, unit: .fahrenheit) //68 degrees Fahrenheit
print(formatter.string(from: temperature)) // 20°C

//speed
let speed = Measurement<UnitSpeed>(value: 100, unit: .kilometersPerHour)
print(formatter.string(from: speed)) // 100 km/h

reference

The following WWDC 2020 videos are very helpful for these Formatters. Please check it.

Recommended Posts

[Swift] Do not implement the format from date / time, number, currency, data size, list, person's name, number with unit to String by yourself.