Adding additional unitOptions to MeasurementFormatter in Swift - swift

Is it possible to add custom unitOptions to the MeasurementFormatter in swift? I would like to be able to define ones such as .imperial and .metric
extension UnitEnergy {
static let footPounds = UnitEnergy(symbol: "ft-lbs", converter: UnitConverterLinear(coefficient: 1))
}
var test = Measurement<UnitEnergy>( value: 10, unit: .footPounds)
var formatter = MeasurementFormatter()
formatter.locale = Locale(identifier: "es")
print( formatter.unitOptions )
print( formatter.string(from: test))
formatter.unitOptions = .naturalScale
print( formatter.unitOptions )
print( formatter.string(from: test))
formatter.unitOptions = .providedUnit
print( formatter.unitOptions )
print( formatter.string(from: test))
Output:
UnitOptions(rawValue: 0)
10 J
UnitOptions(rawValue: 2)
2,39 cal
UnitOptions(rawValue: 1)
10 ft-lbs

You could of course create your own class that would take imperial and metric unit options and call measurement formatter with a locale that behaves as you expect and desire. It's the simplest approach and works as you would expect. Using en_US and en_AU worked as I expected and wanted it to for this very purpose.
More information and a guide here: Introduction to MeasurementFormatter.

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 to format dates without the '0' in the beginning with Swift?

I would like to format my dates to not have zeros in the beginning.
For example
04/03/20 -> swift should display as 4/3/20
I basically want to remove the zero that may be in front of the day, month, or year. The purpose of this is not for style purposes but for me to access data in a JSON. That's why it needs to be soo specific.
You can get by using Date & DateFormatter
let formatter = DateFormatter()
formatter.dateFormat = "dd/MM/yy" // change formate as per your requirement
let date = formatter.date(from: "04/03/20") //change "04/03/20" to your input string
formatter.dateFormat = "d/M/yy"
let dateString = formatter.string(from: date!)
print(dateString) // 4/3/20
let date = "04/03/20"
let parts = date.split(separator: "/")
var newDate = ""
for i in 0..<parts.count {
newDate = "\(newDate)\(i == 0 ? "" :"/")\(Int(parts[i])!)"
}
print(newDate) //Result - 4/3/20

How to get localized file size units in 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

How to express current time in words

I am able to get current time, Now I want to output it in words.
let date = Date()
let calendar = Calendar.current
let hour = calendar.component(.hour, from: date)
let minutes = calendar.component(.minute, from: date)
let seconds = calendar.component(.second, from: date)
print("hours = \(hour):\(minutes):\(seconds)")
Output
10:30
How to get this like -
It's half past ten
As #MwcsMac points out in his answer, the key to solving this is Formatter (once known as NSFormatter), particularly by setting the .numberStyle to .spellOut.
Although this will pick up the current locale (and thus language), the trouble is that many other languages than English do not use the same "half-past", "quarter-to" terminology - for example, in German 10:30 is "halb elf", literally "half (to) eleven".
Writing code that assumes that the locale is English/American is really bad practice and will probably get the app rejected if it is offered outside those areas, so the best one could really do is format "10:30" as "ten thirty", "zehn dreißig".
Code with apologies to #MwcsMac:
import Foundation
let date = Date()
let calendar = Calendar.current
let hour = calendar.component(.hour, from: date)
let minute = calendar.component(.minute, from: date)
func spell(_ number: Int, _ localeID: String) -> String {
let formatter = NumberFormatter()
formatter.numberStyle = .spellOut
// Specify the locale or you will inherit the current default locale
formatter.locale = Locale(identifier: localeID)
if let s = formatter.string(from: NSNumber(value: number)) {
// AVOID forced unwrapping at all times!
return s
} else {
return "<Invalid>" // or make return optional and return `nil`
}
}
spell(hour, "EN") + " " + spell(minute, "EN") // "nineteen thirty-three"
spell(hour, "FR") + " " + spell(minute, "FR") // ""dix-neuf trente-trois"
spell(hour, "AR") + " " + spell(minute, "AR") // "تسعة عشر ثلاثة و ثلاثون"
This code will show you that it is possible to achieve the end goal. Keep in mind as stated above you will need create the logic for combinations that you want achieve.
Swift 3
let date = Date()
let calendar = Calendar.current
let hour = calendar.component(.hour, from: date)
let minutes = calendar.component(.minute, from: date)
let seconds = calendar.component(.second, from: date)
func spell(_ number: Int) -> String {
let formatter = NumberFormatter()
formatter.numberStyle = .spellOut // This will convert the number to words.
return formatter.string(from: NSNumber(value: number))!
}
if minutes == 30 {
let hourString = spell(hour)
print("It's half past \(hourString)")
}
This could be a way that you would use a range for the time frames.
if case 1 ... 14 = minutes {
let hourString = spell(hour) // This will give your hour in word form
let minString = spell(minutes) // This will give your minutes in word form
print("It's \(minString) past \(hourString)")
}

Setting medium and long measurement symbols in Swift 3

In Swift when I create custom units I can only define one symbol. With the built in units there can be short, medium and long units. How do you set the other unit styles for a custom unit?
extension UnitEnergy {
static let footPounds = UnitEnergy(symbol: "ft-lbs", converter: UnitConverterLinear(coefficient: 1))
}
var test = Measurement<UnitEnergy>( value: 10, unit: .footPounds)
var formatter = MeasurementFormatter()
formatter.locale = Locale(identifier: "es")
formatter.unitStyle = .short
print( formatter.string(from: test))
formatter.unitStyle = .medium
print( formatter.string(from: test))
formatter.unitStyle = .long
print( formatter.string(from: test))
formatter.unitOptions = .providedUnit
formatter.unitStyle = .short
print( formatter.string(from: test))
formatter.unitStyle = .medium
print( formatter.string(from: test))
formatter.unitStyle = .long
print( formatter.string(from: test))
Output:
10 J
10 J
10 julios
10 ft-lbs
10 ft-lbs
10 ft-lbs
Short answer - you can't. The API does not provide any facility that allows you to provide different symbols for the three unit styles.
For custom units, the MeasurementFormatter only has the one symbol used when defining the custom unit.
Keep in mind that the need is for much more than just three different possible symbols for the three different unit styles. You would actually need three different string formats because some units might have a space or other punctuation, some might not. Some might appear before the value while some appear after the value.
And then there is the issue of localizing the unit. The Foundation framework provides all of this information for all supported languages so MeasurementFormatter can show all three unit styles for all supported languages for all predefined units.
Since the API does support custom units but not the ability to provide unit style specific symbols, I would suggest filing an enhancement request with Apple.
Have the same question, if there is any news please let me know. For now, I solved it like:
extension MeasurementFormatter {
func customString(from unit: Unit) -> String {
guard self.unitStyle == .long else { //I only needed .long but you get the idea
return self.string(from: unit)
}
switch unit {
case UnitEnergy.footPounds: return "foot-pounds"
default: return self.string(from: unit)
}
}