What? pic.twitter.com/E2dOZYFXNc
& mdash; treastrain / Tanaka.R (@treastrain) September 25, 2020
** 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
DateFormatter
DateComponentsFormatter
RelativeDateTimeFormatter
DateIntervalFormatter
ISO8601DateFormatter
--Number, currencyNumberFormatter
--Data sizeByteCountFormatter
--Combine listsListFormatter
--People's namesPersonNameComponentsFormatter
--UnitMeasurementFormatter
--LengthFormatter
, MassFormatter
, EnergyFormatter
are now Deprecated and integrated into MeasurementFormatter
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.
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
.
Dimension
UnitAcceleration
UnitAngle
UnitArea
UnitConcentrationMass
UnitDispersion
UnitDuration
UnitElectricCharge
UnitElectricCurrent
UnitElectricPotentialDifference
UnitElectricResistance
UnitEnergy
UnitFrequency
UnitFuelEfficiency
UnitIlluminance
UnitInformationStorage
UnitLength
UnitMass
UnitPower
UnitPressure
UnitSpeed
UnitTemperature
UnitVolume
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
The following WWDC 2020 videos are very helpful for these Formatters. Please check it.