Seems to be trivial but I couldn't figure out how to prevent the currency value from Rounding in Swift.
Below is my code:
let halfYearlyPrice = 71.99
var perMonthPrice = (halfYearlyPrice as Double) / 6.0
let currencyFormatter = NumberFormatter()
currencyFormatter.usesGroupingSeparator = true
currencyFormatter.numberStyle = .currency
currencyFormatter.locale = Locale.init(identifier: "en-US")
if let formattedPrice = currencyFormatter.string(from: perMonthPrice as NSNumber) {
print("formattedPrice: ", formattedPrice)
print("\(formattedPrice) / month")
}
The output is
formattedPrice: $12.00
$12.00 / month
I'm wondering how can I ensure the formattedPrice is 11.99?
Thanks in advance.
While you should use Decimal to more accurately represent base-10 values, using a double isn't the root cause of your problem here.
The default roundingMode of a NumberFormatter is .halfEven, which is going to round 11.998 up to 12.00. In fact any value >= 11.995 will end up as 12.00.
You need to set the rounding mode to .down.
let halfYearlyPrice = Decimal(71.99)
var perMonthPrice = halfYearlyPrice / 6
let currencyFormatter = NumberFormatter()
currencyFormatter.usesGroupingSeparator = true
currencyFormatter.numberStyle = .currency
currencyFormatter.locale = Locale.init(identifier: "en-US")
currencyFormatter.roundingMode = .down
if let formattedPrice = currencyFormatter.string(from: perMonthPrice as NSNumber) {
print("formattedPrice: ", formattedPrice)
print("\(formattedPrice) / month")
}
Output:
formattedPrice: $11.99
$11.99 / month
You will get this result even if you don't use Decimal, but rounding errors can accrue if you perform numerous currency operations using floating point values rather than Decimal.
I want to format a negative number and be left with only two decimal places.
But when the number is a negative -0.00 I want it to be unsigned. For example:
let negativeNumber = -0.008
let result = 0.00
I try do this with numberFormatter but i get this result -0.00, and i want this result 0.00
This is the formatter i use:
static func formatValue(_ value: Double, currency: Currency? = nil, options: [FormatterOption] = [], locale: Locale = Locale.current) -> String {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.locale = locale
let defaultOptions: [FormatterOption] = [.minFractionDigits(2),
.maxFractionDigits(currency?.decimals ?? 2),
.roundingMode(.down)]
var completeDecimals = false
for option in defaultOptions + options {
switch option {
case .minFractionDigits(let minValue):
if let decimals = currency?.decimals, minValue > decimals {
continue
}
formatter.minimumFractionDigits = minValue
case .maxFractionDigits(let maxValue):
if let decimals = currency?.decimals, maxValue > decimals {
continue
}
formatter.maximumFractionDigits = maxValue
case .roundingMode(let mode):
formatter.roundingMode =
}
let formattedNumber = formatter.format(from: value)
return formattedNumber
}
I have a string what I get from the server which looks like:
You blocked until 2022-01-01T11:00:00.350Z
And now I want to convert it to human-readable date like
2022-01-01 11:00
For this I tried to find the date first:
let types: NSTextCheckingResult.CheckingType = [.date]
if let detector = try? NSDataDetector(types: types.rawValue) {
let range = NSMakeRange(0, message.count)
let matches = detector.matches(in: message,
options: NSRegularExpression.MatchingOptions(rawValue: 0),
range: range)
if !matches.isEmpty {
for match in matches {
print(match.date)
let aSubstring = NSString(string: message).substring(with: match.range)
print(aSubstring)
}
}
}
So as a result match.date returned me 2022-01-01 11:00:00 +0000 but the result of aSubstring is until 2021-08-02T11:38:10.214Z.
So I'm curious why it includes until into the substring and how can I avoid that?
A possible solution is to extract the ISO8601 string omitting seconds, fractional seconds and the time zone with Regular Expression and then get the first 10 (date) and the last 5 characters (time)
let string = "You blocked until 2022-01-01T11:00:00.350Z"
if let range = string.range(of: "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}", options: .regularExpression) {
let trimmedString = String(string[range])
let humanReadable = "\(trimmedString.prefix(10)) \(trimmedString.suffix(5))"
print(humanReadable) // 2022-01-01 11:00
}
However there is a caveat: The date is in UTC. If you want the date to be displayed in the current time zone you have to use a date formatter (actually two)
let string = "You blocked until 2022-01-01T11:00:00.350Z"
if let range = string.range(of: "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}", options: .regularExpression) {
let trimmedString = String(string[range]) + "Z"
let inputFormatter = ISO8601DateFormatter()
if let date = inputFormatter.date(from: trimmedString) {
let outputFormatter = DateFormatter()
outputFormatter.locale = Locale(identifier: "en_US")
outputFormatter.dateFormat = "yyyy-MM-dd HH:mm"
let humanReadable = outputFormatter.string(from: date)
print(humanReadable)
}
}
if the date part is at the end of the string, then
you could try something a bit more convoluted but workable. Something like this:
struct ContentView: View {
#State var txt = ""
func humanDate(_ str: String) -> String? {
// convert the string to a date
let dateFormatter1 = DateFormatter()
dateFormatter1.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
// re-format the date to your liking
if let date = dateFormatter1.date(from: str) {
let dateFormatter2 = DateFormatter()
dateFormatter2.dateFormat = "yyyy-MM-dd HH:mm"
// dateFormatter2.dateFormat = "yyyy MMMM EEEE HHa:mm" // even more human readable
return dateFormatter2.string(from: date)
} else {
return nil
}
}
var body: some View {
Text(txt).onAppear {
let inputString = "You blocked until 2022-01-01T11:00:00.350Z"
if let dateStr = inputString.split(separator: " ").last {
if let theDate = humanDate(String(dateStr)) {
print("\n----> " + theDate + "\n")
txt = theDate
}
}
}
}
}
I am fairly new to Swift and having a great deal of trouble finding a way to add a space as a thousand separator.
What I am hoping to achieve is taking the result of a calculation and displaying it in a textfield so that the format is:
2 358 000
instead of
2358000
for example.
I am not sure if I should be formatting the Int value and then converting it to a String, or adding the space after the Int value is converted to a String. Any help would be greatly appreciated.
You can use NSNumberFormatter to specify a different grouping separator as follow:
update: Xcode 11.5 • Swift 5.2
extension Formatter {
static let withSeparator: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.groupingSeparator = " "
return formatter
}()
}
extension Numeric {
var formattedWithSeparator: String { Formatter.withSeparator.string(for: self) ?? "" }
}
2358000.formattedWithSeparator // "2 358 000"
2358000.99.formattedWithSeparator // "2 358 000.99"
let int = 2358000
let intFormatted = int.formattedWithSeparator // "2 358 000"
let decimal: Decimal = 2358000
let decimalFormatted = decimal.formattedWithSeparator // "2 358 000"
let decimalWithFractionalDigits: Decimal = 2358000.99
let decimalWithFractionalDigitsFormatted = decimalWithFractionalDigits.formattedWithSeparator // "2 358 000.99"
If you need to display your value as currency with current locale or with a fixed locale:
extension Formatter {
static let number = NumberFormatter()
}
extension Locale {
static let englishUS: Locale = .init(identifier: "en_US")
static let frenchFR: Locale = .init(identifier: "fr_FR")
static let portugueseBR: Locale = .init(identifier: "pt_BR")
// ... and so on
}
extension Numeric {
func formatted(with groupingSeparator: String? = nil, style: NumberFormatter.Style, locale: Locale = .current) -> String {
Formatter.number.locale = locale
Formatter.number.numberStyle = style
if let groupingSeparator = groupingSeparator {
Formatter.number.groupingSeparator = groupingSeparator
}
return Formatter.number.string(for: self) ?? ""
}
// Localized
var currency: String { formatted(style: .currency) }
// Fixed locales
var currencyUS: String { formatted(style: .currency, locale: .englishUS) }
var currencyFR: String { formatted(style: .currency, locale: .frenchFR) }
var currencyBR: String { formatted(style: .currency, locale: .portugueseBR) }
// ... and so on
var calculator: String { formatted(groupingSeparator: " ", style: .decimal) }
}
Usage:
1234.99.currency // "$1,234.99"
1234.99.currencyUS // "$1,234.99"
1234.99.currencyFR // "1 234,99 €"
1234.99.currencyBR // "R$ 1.234,99"
1234.99.calculator // "1 234.99"
Note: If you would like to have a space with the same width of a period you can use "\u{2008}"
unicode spaces
formatter.groupingSeparator = "\u{2008}"
You want to use NSNumberFormatter:
let fmt = NSNumberFormatter()
fmt.numberStyle = .DecimalStyle
fmt.stringFromNumber(2358000) // with my locale, "2,358,000"
fmt.locale = NSLocale(localeIdentifier: "fr_FR")
fmt.stringFromNumber(2358000) // "2 358 000"
With Swift 5, when you need to format the display of numbers, NumberFormatter is the right tool.
NumberFormatter has a property called numberStyle. numberStyle can be set to a value of NumberFormatter.Style.decimal in order to set the formatter's style to decimal.
Therefore, in the simplest case when you want to format a number with decimal style, you can use the following Playground code:
import Foundation
let formatter = NumberFormatter()
formatter.numberStyle = NumberFormatter.Style.decimal
let amount = 2358000
let formattedString = formatter.string(for: amount)
print(String(describing: formattedString))
According to the user's current locale, this code will print Optional("2,358,000") for en_US or Optional("2 358 000") for fr_FR.
Note that the following code snippet that uses the NumberFormatter's locale property set to Locale.current is equivalent to the previous Playground code:
import Foundation
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.locale = Locale.current
let amount = 2358000
let formattedString = formatter.string(for: amount)
print(String(describing: formattedString))
The Playground code below that uses the NumberFormatter's groupingSeparator property set to Locale.current.groupingSeparator is also equivalent to the former:
import Foundation
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.groupingSeparator = Locale.current.groupingSeparator
let amount = 2358000
let formattedString = formatter.string(for: amount)
print(String(describing: formattedString))
Otherwise, if you want to set the number formatting with a specific locale formatting style, you may use the following Playground code:
import Foundation
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.locale = Locale(identifier: "fr_FR")
let amount = 2358000
let formattedString = formatter.string(for: amount)
print(String(describing: formattedString))
// prints: Optional("2 358 000")
However, if what you really want is to enforce a specific grouping separator, you may use the Playground code below:
import Foundation
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.groupingSeparator = " "
let amount = 2358000
let formattedString = formatter.string(for: amount)
print(String(describing: formattedString))
// prints: Optional("2 358 000")
Leo Dabus's answer translated to Swift 3:
Into any .swift file, out of a class:
struct Number {
static let withSeparator: NumberFormatter = {
let formatter = NumberFormatter()
formatter.groupingSeparator = " " // or possibly "." / ","
formatter.numberStyle = .decimal
return formatter
}()
}
extension Integer {
var stringWithSepator: String {
return Number.withSeparator.string(from: NSNumber(value: hashValue)) ?? ""
}
}
Usage:
let myInteger = 2358000
let myString = myInteger.stringWithSeparator // "2 358 000"
Code:
//5000000
let formatter = NumberFormatter()
formatter.groupingSeparator = " "
formatter.locale = Locale(identifier: "en_US")
formatter.numberStyle = .decimal.
Output:
5 000 000
I was looking for a currency format like $100,000.00
I accomplished it customizing the implementation Leo Dabus like this
extension Formatter {
static let withSeparator: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.currencyGroupingSeparator = ","
formatter.locale = Locale(identifier: "en_US") //for USA's currency patter
return formatter
}()
}
extension Numeric {
var formattedWithSeparator: String {
return Formatter.withSeparator.string(for: self) ?? ""
}
}
Try this
func addPoints(inputNumber: NSMutableString){
var count: Int = inputNumber.length
while count >= 4 {
count = count - 3
inputNumber.insert(" ", at: count) // you also can use ","
}
print(inputNumber)
}
The call:
addPoints(inputNumber: "123456")
The result:
123 456 (or 123,456)
In iOS 13 it appears that in certain Locales, NumberFormatter appends a Unicode NO-BREAK SPACE or NARROW NO-BREAK SPACE to a string representation of the number, even when NumberFormatter's currencySymbol property is set to "" (blank).
Here's an example of this:
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .currency
numberFormatter.currencySymbol = ""
numberFormatter.locale = Locale(identifier: "uk_US (current)")
let numberString = numberFormatter.string(from: NSNumber(value: 1))!
print ("-->\(numberString)<--")
Does anyone know a way of suppressing that appended space, short of having to filter it out of every converted string in other code?
You can set your own positive and negative formats to avoid such cases. Note that you should always set the locale before setting the formats and there is no need to initialize a NSNumber object from your value, you can use Formatter's string(for:) method:
let numberFormatter = NumberFormatter()
numberFormatter.locale = Locale(identifier: "uk_US")
numberFormatter.numberStyle = .currency
numberFormatter.positiveFormat = "##0.00"
numberFormatter.negativeFormat = "-##0.00"
let value: Double = 1
let numberString = numberFormatter.string(for: value) ?? "" // "1,00"
print ("-->\(numberString)<--") // "-->1,00<--\n"
For locales where there might not have fraction digits:
let numberFormatter = NumberFormatter()
// numberFormatter.locale = Locale(identifier: "ja_JP")
numberFormatter.numberStyle = .currency
let digits = numberFormatter.minimumFractionDigits
let zeros = digits == 0 ? "" : "." + String(repeating: "0", count: digits)
numberFormatter.positiveFormat = "##0" + zeros
numberFormatter.negativeFormat = "-##0" + zeros
let value: Double = 1
let numberString = numberFormatter.string(for: value) ?? "" // "1"
print ("-->\(numberString)<--") // "-->1<--\n"