How to get localized file size units in Swift - swift

I tried the following code:
let units: [ByteCountFormatter.Units] = [.useBytes, .useKB, .useMB, .useGB, .useTB, .usePB, .useEB, .useZB, .useYBOrHigher]
let localizedDescriptions = units.map { (unit) -> String in
let formatter = ByteCountFormatter()
formatter.includesCount = false
formatter.includesUnit = true
formatter.allowedUnits = [unit]
formatter.countStyle = .file
return formatter.string(fromByteCount: .max)
}
And expect it to be localized according to the documentation.
Class
ByteCountFormatter
A formatter that converts a byte count value
into a localized description that is formatted with the appropriate
byte modifier (KB, MB, GB and so on).
But unfortunately, I got only:
["bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
I tested:
Switch system locale and reload my mac(Saw different file size format in finder: "КБ", "МБ"... instead of "KB", "MB")
Playground/macOS template project.
Switched "Application language" in macOS template project.
PS
In any case thanks for reading this...

You cannot set a locale to ByteCountFormatter, but you can with MeasurementFormatter.
Here is a sample (modify the unitStyle and other properties as you need).
let units: [UnitInformationStorage] = [.bytes, .kilobytes, .megabytes, .gigabytes, .terabytes, .petabytes, .zettabytes, .yottabytes]
let localizedDescriptions = units.map({ unit -> String in
let formatter = MeasurementFormatter()
formatter.unitStyle = .short
formatter.locale = Locale(identifier: "ru_RU") //hard coded here, I guess it takes the current one
return formatter.string(from: unit)
})
Output:
$> ["Б", "кБ", "МБ", "ГБ", "ТБ", "ПБ", "ZB", "YB"]
Zetta & Yotta aren't translated though?
From NSHipster:
ByteCountFormatter, EnergyFormatter, MassFormatter, LengthFormatter, and MKDistanceFormatter are superseded by MeasurementFormatter.
Legacy Measure: ByteCountFormatter
Measurement Formatter Unit: UnitInformationStorage

Related

What is the best way to get a Swift string into a swift-currency/USD format?

How do I get a string into a 'currency' format USD(xxx)?
I am trying to use the following library:
https://github.com/peek-travel/swift-currency
I want to be able to take a string:
var testStr1 = "$30.01"
var testStr2 = "$ 30.01"
and convert this into a currency as I have read from several posts using a double or float is bad, but if I start with a String, what else can I convert it to?
I thought I could use the "import Currency" library to do this, but this is not working.
let updatedString = testStr1.replacingOccurrences(of: "$", with: "")
let formatter = NumberFormatter()
formatter.locale = Locale.current // USA: Locale(identifier: "en_US")
formatter.numberStyle = .decimal
let number = formatter.number(from: test)
var dollars = USD(updatedString)
How do I get a string into a 'currency' format USD(xxx)? If there is a better way to accomplish this?
The basic concept presented by Himanshu works fine, but your problem isn't necessarily making use of an appropriate formatter, but how to fix your input, as the formatter expects a NSNumber and not a String.
So a quick internet check had me looking at Remove all non-numeric characters from a string in swift
So I could take a String, filter out all the "non numerical" junk and then make a Double out of it.
let input = Double(value.filter("0123456789.".contains))
from there I was able to borrow the concept from Himanshu and make a simple format function
func format(_ value: String, locale: Locale = Locale.current) -> String? {
guard let input = Double(value.filter("0123456789.".contains)) else { return nil }
//value.trimmingCharacters(in: .whitespacesAndNewlines)
let currencyFormatter = NumberFormatter()
currencyFormatter.usesGroupingSeparator = true
currencyFormatter.numberStyle = .currency
currencyFormatter.locale = locale
return currencyFormatter.string(from: NSNumber(value: input))
}
I then made use of a Playground to test it using
var testStr1 = "$30.01"
var testStr2 = "$ 30.01"
format(testStr1, locale: Locale(identifier: "en_US")) // $30.01
format(testStr2, locale: Locale(identifier: "en_US")) // $30.01
format(testStr1, locale: Locale(identifier: "fr_FR")) // 30,01 €
format(testStr2, locale: Locale(identifier: "fr_FR")) // 30,01 €
format(testStr1, locale: Locale(identifier: "de_DE")) // 30,01 €
format(testStr2, locale: Locale(identifier: "de_DE")) // 30,01 €
Now, if you specifically want to use USD(xxx) as the format, then you could simply use a basic NumberFormatter and generate your own String from the resulting conversion of the input to a Double
I have read from several posts using a double or float is bad
So, yes, maintaining a currency value as a Double or Float is generally a bad idea, currency values are typically maintained as a Int or Long, but this is due to how Double and Float representation works in computers, for the, general, presentation, you should be fine, but each use case needs be assessed.
let currencyFormatter = NumberFormatter()
currencyFormatter.usesGroupingSeparator = true
currencyFormatter.numberStyle = .currency
// localize to your grouping and decimal separator
currencyFormatter.locale = Locale.current
// We'll force unwrap with the !, if you've got defined data you may need more
error checking
let priceString = currencyFormatter.string(from: 9999.99)!
print(priceString) // Displays $9,999.99 in the US locale
**Forcing a Custom Locale**
You can override the users locale to display specific currency formats by changing the Locale using the identifier.
currencyFormatter.locale = Locale(identifier: "fr_FR")
if let priceString = currencyFormatter.string(from: 9999.99) {
print(priceString) // Displays 9 999,99 € in the French locale
}
currencyFormatter.locale = Locale(identifier: "de_DE")
if let priceString = currencyFormatter.string(from: 9999.99) {
print(priceString) // Displays 9.999,99 € in the German locale
}

How can I parse an ISO 8601 date string in Swift 5.3?

I've tried using ISO8601DateFormatter, but that doesn't seem to be working as expected:
let formatter = ISO8601DateFormatter()
formatter.date(from: "2020-11-16T12:31:43Z") // returns nil
In addition to that, formatting a Date as an ISO 8601 string and parsing it back doesn't seem to work:
formatter.date(from: formatter.string(from: Date())) // returns nil
This is using the Swift REPL — Apple Swift version 5.3 (swiftlang-1200.0.29.2 clang-1200.0.30.1) on macOS 10.15.6.
I could use DateFormatter with a handwritten format, but I'd prefer to avoid that if possible.
Is there a standard way to parse ISO 8601 strings? And how come formatting a string and parsing it back doesn't work?
EDIT: For clarity, here is my complete REPL session with the exact code that I entered, and with unchanged output.
Welcome to Apple Swift version 5.3 (swiftlang-1200.0.29.2 clang-1200.0.30.1).
Type :help for assistance.
1> import Foundation
2> let formatter = ISO8601DateFormatter()
formatter: ISO8601DateFormatter = {
baseNSFormatter#0 = {
baseNSObject#0 = {
isa = NSISO8601DateFormatter
}
}
_formatter = {}
_timeZone = "GMT"
_formatOptions = 1907
}
3> formatter.formatOptions = .withInternetDateTime
4> formatter.date(from: "2020-11-16T12:31:43Z")
$R0: Date? = nil
5> formatter.date(from: formatter.string(from: Date()))
$R1: Date? = nil
I've also tried formatter.formatOptions = [.withInternetDateTime] and formatter.formatOptions.insert(.withInternetDateTime) without success.

DateFormatter localized date format printing "PM" when it should be "AM"

Here's a reduced version of my formatter class:
class MyDateFormatters {
let timeFormatter : DateFormatter
init(timeZone:Int) {
self.timeFormatter = {
let tz = TimeZone(secondsFromGMT: timezone)
let df = DateFormatter()
df.timeZone = tz
df.setLocalizedDateFormatFromTemplate("hhmm")
return df
}()
}
}
In my viewDidLoad, I'm deriving a time string from raw data. But it's coming out wrong in a weird way: I'm seeing "PM" where I should see "AM"!
Here's the raw data (comes in thru JSON):
let timeZone = -14400
let sunriseRaw = 1570620145
Now, if just make a plain vanilla date formatter and print out sunriseRaw, this is what I get:
let sunrise = Date(timeIntervalSince1970: Double(sunriseRaw))
let df = DateFormatter()
df.dateFormat = "hh:mm a"
df.timeZone = TimeZone(secondsFromGMT: timeZone)
print(df.string(from: sunrise))
// 07:22 AM
And that is correct. Okay, now I'll do with my date formatter:
let myf = MyDateFormatters(timeZone:timeZone).timeFormatter
let sunriseString = myf.string(from: sunrise)
print(sunriseString)
// 7:22 PM
How can this be? My class is doing exactly the same thing, except that it uses a localized date format with a template. Why am I seeing "PM" instead of "AM"
You've been caught by an unbelievably obscure bug in your code. Look very, very closely:
init(timeZone:Int) {
// ^
self.timeFormatter = {
let tz = TimeZone(secondsFromGMT: timezone)
// ^
So the timezone you are using in your formatter is not the same as the timeZone that arrived as parameter into your initializer.
So, you may, ask, what is timezone? Why didn't the compiler flag it? Sigh, it's because Foundation injects the entirety of C / Darwin into your code. timezone is a Darwin value. Open time.h and you'll see it. Tricky, eh?
(Unfortunately I don't think there's a way to get the compiler to warn about this kind of thing. Using Darwin values without explicit namespacing is legal, and you wouldn't want it not to be.)

NSNumberFormatter.number for currency format not working in Device but works in simulator

I've been trying to implement currency format based on passing my custom language identifier.
Below is my code
func currencyFormatter(language:String, amount:String) -> String {
let nsFormatter = NumberFormatter()
nsFormatter.numberStyle = .currency
nsFormatter.currencySymbol = ""
var formattedString: String?
var amountInNumber:NSNumber!
if let number = nsFormatter.number(from: amount)
{
amountInNumber = number.doubleValue as NSNumber
}
nsFormatter.locale = Locale(identifier: language)
formattedString = ((amountInNumber?.intValue) != nil) ? nsFormatter.string(from: amountInNumber) : amount
guard let finalString = formattedString else {
return ""
}
return finalString
}
I am trying to pass language as "fr-FR" and amount as "1234.45" then expecting output is "1 234,45".
This is working fine in simulator but not working in device (returning same value 1234.45)
Do i missed anything. Please help!
Thanks in advance
The decimal separator is locale-dependent, therefore parsing "1234.45"
fails if the locale's separator is not a period.
It the input string uses a fixed format with a period as decimal separator
then you can set the formatter's locale to "en_US_POSIX" for the conversion
from a string to a number. Then set it to the desired locale for the conversion
from number to a string.
Example:
func currencyFormatter(language: String, amount: String) -> String {
let nsFormatter = NumberFormatter()
nsFormatter.locale = Locale(identifier: "en_US_POSIX")
nsFormatter.numberStyle = .decimal
guard let number = nsFormatter.number(from: amount) else {
return amount
}
nsFormatter.locale = Locale(identifier: language)
nsFormatter.numberStyle = .currency
return nsFormatter.string(from: number) ?? amount
}
print(currencyFormatter(language: "fr-FR", amount: "1234.45"))
// 1 234,45 €

Elegant way of stripping string of currency characters?

Currently Im receiving a string from a service that contains currency characters (Ex. "$123.44", "123,44 €"). I would like to strip these stings of their currency characters in order to perform some calculations on these prices (adding money, subtracting money...etc).
I know I could replace occurrences of certain characters and then re-add them later after the calculations, but I get the impression that there might be a better solution out there.
Any ideas?
You can use NSNumberFormatter to convert a formatted price into a Double value
func getNumber(formattedPrice: String, localeID: String) -> Double? {
let formatter = NSNumberFormatter()
formatter.numberStyle = .CurrencyStyle
formatter.locale = NSLocale(localeIdentifier: localeID)
return formatter.numberFromString(formattedPrice)?.doubleValue
}
but you'll need to specify the locale
getNumber("$123.44", localeID: "en_US") // 123.44
getNumber("123,44€", localeID: "it_IT") // 123.44
getNumber("£123.44", localeID: "en_UK") // 123.44
You can use an NSCharacterSet to root out those characters, like this:
var dollarStr = "$1.50"
var yuanStr = "¥18.25"
var euroStr = "20.75 €"
let strArray = [dollarStr, yuanStr, euroStr]
let charSet = NSCharacterSet(charactersInString: "$¥€ ")
for str in strArray {
let trimmedStr = str.stringByTrimmingCharactersInSet(charSet)
print(trimmedStr)
}
prints out :
1.50
18.25
20.75