
Swift Formatstyle
- 1.6k installs
- 944 repo stars
- Updated July 15, 2026
- dpearson2699/swift-ios-skills
swift-formatstyle is an agent skill for format and parse numbers, currency, dates, and lists with swift formatstyle apis.
About
The swift-formatstyle skill is designed for format and parse numbers, currency, dates, and lists with Swift FormatStyle APIs. Swift FormatStyle Format values for human-readable display using the FormatStyle protocol and Foundation's concrete format styles. Replaces legacy Formatter subclasses with a type-safe, composable, cacheable API. Invoke when the user formats numbers, currency, dates, or measurements in SwiftUI or Foundation.
- Quick Reference.
- SwiftUI Integration.
- Custom FormatStyle.
- Common Mistakes.
- Review Checklist.
Swift Formatstyle by the numbers
- 1,596 all-time installs (skills.sh)
- +104 installs in the week ending Jul 29, 2026 (Skillselion tracking)
- Ranked #151 of 1,039 Mobile Development skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Jul 31, 2026 (Skillselion catalog sync)
swift-formatstyle capabilities & compatibility
- Capabilities
- quick reference · swiftui integration · custom formatstyle · common mistakes
What swift-formatstyle says it does
Format and parse values for display using the FormatStyle and ParseableFormatStyle protocols and Foundation's concrete styles. Use when formatting numbers (integers, floating-point
Format and parse values for display using the FormatStyle and ParseableFormatStyle protocols and Foundation's concrete styles. Use when formatting numbers (inte
npx skills add https://github.com/dpearson2699/swift-ios-skills --skill swift-formatstyleAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.6k |
|---|---|
| repo stars | ★ 944 |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 15, 2026 |
| Repository | dpearson2699/swift-ios-skills ↗ |
How do I format and parse numbers, currency, dates, and lists with swift formatstyle apis?
Format and parse numbers, currency, dates, and lists with Swift FormatStyle APIs.
Who is it for?
iOS developers using Foundation FormatStyle and ParseableFormatStyle.
Skip if: Skip for UI layout tasks without formatting and parsing needs.
When should I use this skill?
User formats numbers, currency, dates, or measurements in SwiftUI or Foundation.
What you get
Completed swift-formatstyle workflow with documented commands, files, and expected deliverables.
- FormatStyle Swift snippets
- Localized display strings
- Custom FormatStyle implementations
By the numbers
- Documents 10+ concrete FormatStyle types including currency, duration, measurement, and ListFormatStyle
- FormatStyle base availability starts at iOS 15+; Duration styles require iOS 16+
Files
Swift FormatStyle
Format values for human-readable display using the FormatStyle protocol and Foundation's concrete format styles. Replaces legacy Formatter subclasses with a type-safe, composable, cacheable API.
Locale-aware display is an i18n concern even when the app is not adding new languages. When reviewing user-facing FormatStyle output or SwiftUI Text(_:format:), always include an actionable preview/test step for the exact rendered UI in representative locales such as en_US, de_DE, ar_SA, and ja_JP; check separators, numbering systems, calendars, currency and unit conventions, text direction, and layout-sensitive output. Keep this skill focused on FormatStyle, ParseableFormatStyle, parsing, and reusable formatter API design; route broader localization work such as String Catalogs, bundles, plurals, localized copy, and RTL layout review to ios-localization. Do not use "not adding languages" as the reason to skip ios-localization; locale-sensitive formatting can be a localization review issue without translation work.
Docs: FormatStyle
Contents
- Quick Reference
- Numbers
- Decimals
- Currency
- Percentages
- Dates
- Durations
- Measurements
- Person Names
- Lists
- Byte Counts
- URLs
- SwiftUI Integration
- Custom FormatStyle
- Common Mistakes
- Review Checklist
Quick Reference
| Type | Style Access | Example |
|---|---|---|
Int, Double | .number | 42.formatted(.number.precision(.fractionLength(2))) → "42.00" |
Decimal | .number, .percent, .currency(code:) | Decimal(string: "0.1")!.formatted(.percent) -> "10%" |
| Currency | .currency(code:) | 29.99.formatted(.currency(code: "USD")) -> "$29.99" |
| Percent | .percent | 0.85.formatted(.percent) → "85%" |
Date | .dateTime | Date.now.formatted(.dateTime.month().day().year()) |
| Date range | .interval | (date1..<date2).formatted(.interval) |
| Relative date | .relative(presentation:unitsStyle:) | date.formatted(.relative(presentation: .named)) → "yesterday" |
Duration | .time(pattern:) | Duration.seconds(3661).formatted(.time(pattern: .hourMinuteSecond)) → "1:01:01" |
Duration | .units(allowed:width:) | Duration.seconds(90).formatted(.units(allowed: [.minutes, .seconds])) → "1 min, 30 sec" |
Measurement | .measurement(width:) | Measurement(value: 72, unit: UnitTemperature.fahrenheit).formatted(.measurement(width: .abbreviated)) |
PersonNameComponents | .name(style:) | name.formatted(.name(style: .short)) → "Tom" |
[String] | .list(type:width:) | ["A","B","C"].formatted(.list(type: .and)) → "A, B, and C" |
| Byte count | .byteCount(style:) | Int64(1_048_576).formatted(.byteCount(style: .memory)) → "1 MB" |
URL | .url | url.formatted(.url.scheme(.never).host().path()) |
Numbers
// Default locale-aware formatting
let n = 1234567.formatted() // "1,234,567" (en_US)
// Precision
1234.5.formatted(.number.precision(.fractionLength(0...2))) // "1,234.5"
1234.5.formatted(.number.precision(.significantDigits(3))) // "1,230"
// Rounding
1234.formatted(.number.rounded(rule: .down, increment: 100)) // "1,200"
// Grouping
1234567.formatted(.number.grouping(.never)) // "1234567"
// Notation
1_200_000.formatted(.number.notation(.compactName)) // "1.2M"
42.formatted(.number.notation(.scientific)) // "4.2E1"
// Sign display
(-42).formatted(.number.sign(strategy: .always())) // "+42" / "-42"
// Locale override
42.formatted(.number.locale(Locale(identifier: "de_DE"))) // "42"Docs: IntegerFormatStyle, FloatingPointFormatStyle
Decimals
Use Decimal.FormatStyle for exact decimal values, especially money-like values that should not pass through binary floating-point.
let amount = Decimal(string: "12345.67")!
amount.formatted(.number) // "12,345.67" (en_US)
amount.formatted(.number.grouping(.never)) // "12345.67"
Decimal(string: "0.1")!.formatted(.percent) // "10%"
amount.formatted(.currency(code: "USD")) // "$12,345.67"
// Parsing with the same style
let price = try? Decimal("$3,500.63", format: .currency(code: "USD"))Docs: Decimal.FormatStyle
Currency
29.99.formatted(.currency(code: "USD")) // "$29.99"
29.99.formatted(.currency(code: "EUR")) // "€29.99"
29.99.formatted(.currency(code: "JPY")) // "¥30"
// Customize precision
let style = FloatingPointFormatStyle<Double>.Currency(code: "USD")
.precision(.fractionLength(0))
1234.56.formatted(style) // "$1,235"Percentages
0.85.formatted(.percent) // "85%"
0.8567.formatted(.percent.precision(.fractionLength(1))) // "85.7%"
42.formatted(.percent) // "42%" (integer)Dates
let now = Date.now
// Components
now.formatted(.dateTime.year().month().day()) // "Apr 22, 2026"
now.formatted(.dateTime.hour().minute()) // "4:30 PM"
now.formatted(.dateTime.weekday(.wide).month(.wide).day()) // "Wednesday, April 22"
// Predefined styles
now.formatted(date: .long, time: .shortened) // "April 22, 2026 at 4:30 PM"
now.formatted(date: .abbreviated, time: .omitted) // "Apr 22, 2026"
// ISO 8601
now.formatted(.iso8601) // "2026-04-22T16:30:00Z"
// Relative
let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: .now)!
yesterday.formatted(.relative(presentation: .named)) // "yesterday"
yesterday.formatted(.relative(presentation: .numeric)) // "1 day ago"
// Treat relative strings as standalone text; embedding them inside another
// sentence can be grammatically wrong in some locales. Recommend previewing or
// testing the exact screen in representative locales before approving copy.
Text(yesterday, format: .relative(presentation: .named))
// Interval
(date1..<date2).formatted(.interval.month().day().hour().minute())
// Components (countdown-style)
(date1..<date2).formatted(.components(style: .wide, fields: [.day, .hour]))
// "2 days, 5 hours"Docs: Date.FormatStyle, Date.RelativeFormatStyle, Date.IntervalFormatStyle
Anchored Relative Dates (iOS 18+)
Date.AnchoredRelativeFormatStyle formats relative to a fixed anchor date rather than the current moment. It requires iOS 18+.
Docs: Date.AnchoredRelativeFormatStyle
Durations
Duration (iOS 16+) has two format styles:
Docs: Duration.TimeFormatStyle, Duration.UnitsFormatStyle
TimeFormatStyle — compact separator-based
let d = Duration.seconds(3661)
d.formatted(.time(pattern: .hourMinuteSecond)) // "1:01:01"
d.formatted(.time(pattern: .hourMinute)) // "1:01"
d.formatted(.time(pattern: .minuteSecond)) // "61:01"
// Fractional seconds
Duration.seconds(3.75).formatted(
.time(pattern: .minuteSecond(padMinuteToLength: 2, fractionalSecondsLength: 2))
) // "00:03.75"UnitsFormatStyle — labeled units
Duration.seconds(3661).formatted(
.units(allowed: [.hours, .minutes, .seconds], width: .abbreviated)
) // "1 hr, 1 min, 1 sec"
Duration.seconds(90).formatted(
.units(allowed: [.minutes, .seconds], width: .wide)
) // "1 minute, 30 seconds"
Duration.seconds(90).formatted(
.units(allowed: [.minutes, .seconds], width: .narrow)
) // "1m 30s"
// Limit unit count
Duration.seconds(3661).formatted(
.units(allowed: [.hours, .minutes, .seconds], width: .abbreviated, maximumUnitCount: 2)
) // "1 hr, 1 min"Measurements
let temp = Measurement(value: 72, unit: UnitTemperature.fahrenheit)
temp.formatted(.measurement(width: .wide)) // "72 degrees Fahrenheit"
temp.formatted(.measurement(width: .abbreviated)) // "72°F"
temp.formatted(.measurement(width: .narrow)) // "72°"
let dist = Measurement(value: 5, unit: UnitLength.kilometers)
dist.formatted(.measurement(width: .abbreviated, usage: .road)) // "3.1 mi" (en_US)Docs: Measurement.FormatStyle
Person Names
var name = PersonNameComponents()
name.givenName = "Thomas"
name.familyName = "Clark"
name.middleName = "Louis"
name.namePrefix = "Dr."
name.nickname = "Tom"
name.nameSuffix = "Esq."
name.formatted(.name(style: .long)) // "Dr. Thomas Louis Clark Esq."
name.formatted(.name(style: .medium)) // "Thomas Clark"
name.formatted(.name(style: .short)) // "Tom"
name.formatted(.name(style: .abbreviated)) // "TC"Style resolution follows priority: script → user preferences → locale → developer setting.
Docs: PersonNameComponents.FormatStyle
Lists
["Alice", "Bob", "Charlie"].formatted(.list(type: .and))
// "Alice, Bob, and Charlie"
["Alice", "Bob", "Charlie"].formatted(.list(type: .or))
// "Alice, Bob, or Charlie"
// With member formatting
[1, 2, 3].formatted(.list(memberStyle: .number, type: .and))
// "1, 2, and 3"
// Narrow width
["A", "B", "C"].formatted(.list(type: .and, width: .narrow))
// "A, B, C"Docs: ListFormatStyle
Byte Counts
Int64(1_048_576).formatted(.byteCount(style: .memory)) // "1 MB"
Int64(1_048_576).formatted(.byteCount(style: .file)) // "1 MB"
Int64(1_048_576).formatted(.byteCount(style: .binary)) // "1 MiB"Docs: ByteCountFormatStyle
URLs
URL.FormatStyle requires iOS 16+. The default style includes scheme, host, and path. Treat port, query, and fragment as opt-in display components; add .port(.always), .query(.always), or .fragment(.always) only when those components should be visible.
let url = URL(string: "https://www.example.com:8080/path?q=1#section")!
url.formatted()
// "https://www.example.com/path"
url.formatted(.url.scheme(.never).host().path())
// "www.example.com/path"
url.formatted(.url.scheme(.never).host().path().query(.always))
// "www.example.com/path?q=1"
url.formatted(.url.scheme(.never).host().path().fragment(.always))
// "www.example.com/path#section"When auditing URL component choices, recommend previewing or testing the exact rendered URL text in representative locales, especially when choosing whether to show or hide scheme, path, query, port, or fragment.
Docs: URL.FormatStyle
SwiftUI Integration
Text accepts a format: parameter, keeping formatting out of the view model.
// Inline format style
Text(price, format: .currency(code: "USD"))
Text(date, format: .dateTime.month().day().year())
Text(duration, format: .units(allowed: [.minutes, .seconds]))
// Timer-style (live updating)
Text(.now, style: .timer)
Text(.now, style: .relative)
Text(timerInterval: start...end)Prefer `Text(_:format:)` over string interpolation — it allows SwiftUI to re-render only the formatted value and supports accessibility scaling. For every SwiftUI formatted Text review, include a representative-locale preview, UI test, or snapshot test recommendation for the exact screen.
Custom FormatStyle
Conform to FormatStyle for domain-specific formatting. Conform to ParseableFormatStyle if you also need parsing. FormatStyle refines Decodable, Encodable, and Hashable, and Foundation caches identical customized style instances, so reusable value-style formatters are cheap.
struct AbbreviatedCountStyle: FormatStyle {
func format(_ value: Int) -> String {
switch value {
case ..<1_000:
return "\(value)"
case 1_000..<1_000_000:
return String(format: "%.1fK", Double(value) / 1_000)
default:
return String(format: "%.1fM", Double(value) / 1_000_000)
}
}
}
extension FormatStyle where Self == AbbreviatedCountStyle {
static var abbreviatedCount: AbbreviatedCountStyle { .init() }
}
// Usage
let followers = 12_500
Text(followers, format: .abbreviatedCount) // "12.5K"For parseable custom styles, pair formatting with a parse strategy and use the same conventions in both directions. Prefer this only when users edit or import the formatted value; display-only styles should stay as FormatStyle.
Docs: ParseableFormatStyle
Common Mistakes
| Mistake | Fix |
|---|---|
Using legacy NumberFormatter / DateFormatter in new code | Use FormatStyle (iOS 15+). Foundation caches format style instances automatically. |
String interpolation for formatted numbers in Text | Use Text(value, format:) for locale correctness and accessibility |
| Hardcoding locale in format styles | Omit .locale() to inherit the user's current locale by default |
Assuming URL.formatted() preserves query strings, ports, or fragments | Default URL formatting includes scheme, host, and path only; opt in with .query(.always), .port(.always), or .fragment(.always) |
| Embedding relative date output inside larger sentences | Use Date.RelativeFormatStyle output as standalone text; localized grammar may not fit interpolation |
| Forgetting availability checks | URL.FormatStyle and Duration format styles require iOS 16+; Date.AnchoredRelativeFormatStyle requires iOS 18+ |
Using .time(pattern:) for labeled duration display | Use .units(allowed:width:) for "1 hr, 30 min" style output |
Creating Formatter instances in body or tight loops | FormatStyle instances are value types cached by Foundation; safe to create inline |
Formatting Duration with DateComponentsFormatter | Use Duration.TimeFormatStyle or Duration.UnitsFormatStyle directly |
Ignoring usage: parameter for measurements | Specify .road, .asProvided, etc. for locale-aware unit conversion |
| Using binary floating-point for exact decimal display/parsing | Use Decimal.FormatStyle and matching parse strategies for exact decimal values |
Review Checklist
- [ ]
FormatStyleused instead of legacyFormattersubclasses for iOS 15+ targets - [ ]
URL.FormatStyleandDurationstyles gated to iOS 16+; anchored relative dates gated to iOS 18+ - [ ]
Text(_:format:)used instead of pre-formatting strings for SwiftUI text - [ ] Every user-facing formatted value or SwiftUI formatted
Textincludes an explicit representative-locale preview/test recommendation - [ ] No hardcoded locale unless explicitly needed (e.g., server communication)
- [ ] Decimal values use
Decimal.FormatStylewhen exact decimal formatting or parsing matters - [ ] URL formatting explicitly includes query, port, or fragment only when those components should display, with representative-locale preview for user-facing URL text
- [ ] Relative date strings are used standalone, not interpolated into larger localized sentences, and previewed in representative locales
- [ ] Duration formatting uses
Duration.TimeFormatStyleorDuration.UnitsFormatStyle - [ ] Currency codes are ISO 4217 strings, not hardcoded symbols
- [ ] Measurement formatting includes
usage:for user-facing display - [ ] Custom FormatStyle types conform to
Codable+Hashablefor caching
References
{
"skill_name": "swift-formatstyle",
"evals": [
{
"id": 0,
"name": "decimal-format-parse",
"prompt": "In an iOS 17 finance screen, replace a NumberFormatter-based amount display with FormatStyle. The amount is Decimal, needs USD display, and imported strings like \"$3,500.63\" should parse back to Decimal. Show the preferred Swift pattern and mention any locale caveats.",
"expected_output": "Uses Decimal.FormatStyle for exact decimal display, .currency(code:) with an ISO 4217 code, a matching parse strategy or Decimal initializer for parsing, and locale-aware testing guidance without falling back to NumberFormatter.",
"files": [],
"assertions": [
"Uses `Decimal.FormatStyle` or `Decimal` with `.currency(code:)` rather than binary floating-point formatting.",
"Shows or describes parsing a formatted currency string back into `Decimal` with a matching format style or parse strategy.",
"Uses ISO 4217 currency codes and does not hardcode currency symbols.",
"Mentions locale-sensitive display or parsing behavior without routing the whole implementation to ios-localization."
]
},
{
"id": 1,
"name": "url-relative-availability",
"prompt": "Audit this SwiftUI snippet for iOS 16: `Text(eventURL.formatted())` and `Text(\"Updated \\(updatedAt.formatted(.relative(presentation: .named)))\")`. The product manager expects URL query strings to remain visible and wants the relative date sentence to work in every locale.",
"expected_output": "Flags that URL.FormatStyle is iOS 16+, default URL formatting omits query/port/fragment, recommends explicitly opting into query display when needed, and warns that Date.RelativeFormatStyle output should be standalone rather than embedded in another sentence.",
"files": [],
"assertions": [
"States that `URL.FormatStyle` is available on iOS 16+.",
"Correctly says `URL.formatted()` defaults to scheme, host, and path and omits query strings or ports.",
"Recommends explicit URL component display options such as `.query(.always)` when query display is required.",
"Warns that relative date strings should be standalone because embedding can be grammatically wrong in localized UI."
]
},
{
"id": 2,
"name": "localization-boundary",
"prompt": "A team is not translating the app yet, but they need a custom ParseableFormatStyle for follower counts like 12.5K, a Date.IntervalFormatStyle review, and URL display formatting. Should this be owned by ios-localization or another skill? Keep the answer short.",
"expected_output": "Routes custom FormatStyle, ParseableFormatStyle, Date.IntervalFormatStyle, and URL.FormatStyle design to swift-formatstyle while still mentioning minimal locale-aware testing concerns and preserving ios-localization for broader String Catalogs, plurals, copy, and RTL work.",
"files": [],
"assertions": [
"Routes custom `FormatStyle` or `ParseableFormatStyle` work to `swift-formatstyle`.",
"Routes `Date.IntervalFormatStyle` and `URL.FormatStyle` design to `swift-formatstyle` unless the request is mainly localization QA.",
"Mentions that user-facing formatted output still needs locale preview or testing even without translation.",
"Does not collapse broader localization work such as String Catalogs, plurals, localized copy, or RTL layout into `swift-formatstyle`."
]
}
]
}
Related skills
How it compares
Pick swift-formatstyle over generic localization skills when migrating Swift UI code from Formatter subclasses to type-safe FormatStyle APIs.
FAQ
What does swift-formatstyle do?
Format and parse numbers, currency, dates, and lists with Swift FormatStyle APIs.
When should I use swift-formatstyle?
User formats numbers, currency, dates, or measurements in SwiftUI or Foundation.
Is swift-formatstyle safe to install?
Review the Security Audits panel on this page before installing in production.