blob: fafaf0f6d24e816d0a6e9e7758a45b7467d446fc [file] [log] [blame]
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
extension EnergyFormatter {
public enum Unit: Int {
case joule = 11
case kilojoule = 14
case calorie = 1793 // chemistry "calories", abbr "cal"
case kilocalorie = 1794 // kilocalories in general, abbr “kcal”, or “C” in some locales (e.g. US) when usesFoodEnergy is set to YES
// Map Unit to UnitEnergy class to aid with conversions
fileprivate var unitEnergy: UnitEnergy {
switch self {
case .joule:
return UnitEnergy.joules
case .kilojoule:
return UnitEnergy.kilojoules
case .calorie:
return UnitEnergy.calories
case .kilocalorie:
return UnitEnergy.kilocalories
}
}
// Reuse symbols defined in UnitEnergy, except for kilocalories, which is defined as "kCal"
fileprivate var symbol: String {
switch self {
case .kilocalorie:
return "kcal"
default:
return unitEnergy.symbol
}
}
// Return singular, full string representation of the energy unit
fileprivate var singularString: String {
switch self {
case .joule:
return "joule"
case .kilojoule:
return "kilojoule"
case .calorie:
return "calorie"
case .kilocalorie:
return "kilocalorie"
}
}
// Return plural, full string representation of the energy unit
fileprivate var pluralString: String {
return "\(self.singularString)s"
}
}
}
open class EnergyFormatter: Formatter {
public override init() {
numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
unitStyle = .medium
isForFoodEnergyUse = false
super.init()
}
public required init?(coder: NSCoder) {
numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
unitStyle = .medium
isForFoodEnergyUse = false
super.init()
}
/*@NSCopying*/ open var numberFormatter: NumberFormatter! // default is NumberFormatter with NumberFormatter.Style.decimal
open var unitStyle: UnitStyle // default is NSFormattingUnitStyleMedium
open var isForFoodEnergyUse: Bool // default is NO; if it is set to YES, EnergyFormatter.Unit.kilocalorie may be “C” instead of “kcal"
// Format a combination of a number and an unit to a localized string.
open func string(fromValue value: Double, unit: Unit) -> String {
guard let formattedValue = numberFormatter.string(from:NSNumber(value: value)) else {
fatalError("Cannot format \(value) as string")
}
let separator = unitStyle == EnergyFormatter.UnitStyle.short ? "" : " "
return "\(formattedValue)\(separator)\(unitString(fromValue: value, unit: unit))"
}
// Format a number in joules to a localized string with the locale-appropriate unit and an appropriate scale (e.g. 10.3J = 2.46cal in the US locale).
open func string(fromJoules numberInJoules: Double) -> String {
//Convert to the locale-appropriate unit
var unitFromJoules: EnergyFormatter.Unit = .joule
_ = self.unitString(fromJoules: numberInJoules, usedUnit: &unitFromJoules)
//Map the unit to UnitLength type for conversion later
let unitEnergyFromJoules = unitFromJoules.unitEnergy
//Create a measurement object based on the value in joules
let joulesMeasurement = Measurement<UnitEnergy>(value:numberInJoules, unit: .joules)
//Convert the object to the locale-appropriate unit determined above
let unitMeasurement = joulesMeasurement.converted(to: unitEnergyFromJoules)
//Extract the number from the measurement
let numberInUnit = unitMeasurement.value
return string(fromValue: numberInUnit, unit: unitFromJoules)
}
// Return a localized string of the given unit, and if the unit is singular or plural is based on the given number.
open func unitString(fromValue value: Double, unit: Unit) -> String {
//Special case when isForFoodEnergyUse is true
if isForFoodEnergyUse && unit == .kilocalorie {
if unitStyle == .short {
return "C"
} else if unitStyle == .medium {
return "Cal"
} else {
return "Calories"
}
}
if unitStyle == .short || unitStyle == .medium {
return unit.symbol
} else if value == 1.0 {
return unit.singularString
} else {
return unit.pluralString
}
}
// Return the locale-appropriate unit, the same unit used by -stringFromJoules:.
open func unitString(fromJoules numberInJoules: Double, usedUnit unitp: UnsafeMutablePointer<Unit>?) -> String {
//Convert to the locale-appropriate unit
let unitFromJoules: Unit
if numberFormatter.locale.usesCalories {
if numberInJoules > 0 && numberInJoules <= 4184 {
unitFromJoules = .calorie
} else {
unitFromJoules = .kilocalorie
}
} else {
if numberInJoules > 0 && numberInJoules <= 1000 {
unitFromJoules = .joule
} else {
unitFromJoules = .kilojoule
}
}
unitp?.pointee = unitFromJoules
//Map the unit to UnitEnergy type for conversion later
let unitEnergyFromJoules = unitFromJoules.unitEnergy
//Create a measurement object based on the value in joules
let joulesMeasurement = Measurement<UnitEnergy>(value:numberInJoules, unit: .joules)
//Convert the object to the locale-appropriate unit determined above
let unitMeasurement = joulesMeasurement.converted(to: unitEnergyFromJoules)
//Extract the number from the measurement
let numberInUnit = unitMeasurement.value
//Return the appropriate representation of the unit based on the selected unit style
return unitString(fromValue: numberInUnit, unit: unitFromJoules)
}
/// - Experiment: This is a draft API currently under consideration for official import into Foundation as a suitable alternative
/// - Note: Since this API is under consideration it may be either removed or revised in the near future
open override func objectValue(_ string: String) throws -> Any? { return nil }
}
/// TODO: Replace calls to the below function to use Locale.regionCode
/// Temporary workaround due to unpopulated Locale attributes
/// See https://bugs.swift.org/browse/SR-3202
extension Locale {
public var usesCalories: Bool {
switch self.identifier {
case "en_US": return true
case "en_US_POSIX": return true
case "haw_US": return true
case "es_US": return true
case "chr_US": return true
case "en_GB": return true
case "kw_GB": return true
case "cy_GB": return true
case "gv_GB": return true
default: return false
}
}
}