@@ -20,31 +20,16 @@ import Foundation
2020
2121public class TimeCompactor : NumberFormatter {
2222
23- public let blankIfZero : Bool
24- public let style : Style
25- public let roundSmallToWhole : Bool
23+ public var blankIfZero : Bool
24+ public var style : Style
25+ public var roundSmallToWhole : Bool
2626
27- let threshold : TimeInterval
28- let netMinuteExtent : TimeInterval
29- let netHourExtent : TimeInterval
30- let netDayExtent : TimeInterval
31- let netYearExtent : TimeInterval
32- let netCenturyExtent : TimeInterval
33- let netMilleniumExtent : TimeInterval
34-
3527 public init ( blankIfZero: Bool = false ,
3628 style: Style = . short,
3729 roundSmallToWhole: Bool = false ) {
3830 self . blankIfZero = blankIfZero
3931 self . style = style
4032 self . roundSmallToWhole = roundSmallToWhole
41- self . threshold = roundSmallToWhole ? 0.5 : 0.05
42- self . netMinuteExtent = Scale . minute. extent - threshold
43- self . netHourExtent = Scale . hour. extent - threshold * Scale. minute. extent
44- self . netDayExtent = Scale . day. extent - threshold * Scale. hour. extent
45- self . netYearExtent = Scale . year. extent - threshold * Scale. day. extent
46- self . netCenturyExtent = Scale . century. extent - threshold * Scale. year. extent
47- self . netMilleniumExtent = Scale . millenium. extent - threshold * Scale. century. extent
4833 super. init ( )
4934 }
5035
@@ -53,55 +38,30 @@ public class TimeCompactor: NumberFormatter {
5338 }
5439
5540 public override func string( from value: NSNumber ) -> String ? {
56- let absValue = abs ( TimeInterval ( truncating: value) )
41+ let rawValue : Double = Double ( truncating: value)
42+ let absValue = abs ( rawValue)
43+ let threshold = TimeCompactor . getThreshold ( roundSmallToWhole)
5744
5845 if blankIfZero, absValue <= threshold { return " " }
5946
60- var scaleSymbol : Scale = . second
61- var netValue = TimeInterval ( truncating: value)
62-
63- switch absValue {
64- case 0.0 ... threshold:
65- // if inside threshold, drop the fraction, to avoid awkward "-0s"
66- netValue = 0.0
67- case threshold ..< netMinuteExtent:
68- _ = 0 // verbatim netValue
69- case netMinuteExtent ..< netHourExtent:
70- netValue /= Scale . minute. extent
71- scaleSymbol = . minute
72- case netHourExtent ..< netDayExtent:
73- netValue /= Scale . hour. extent
74- scaleSymbol = . hour
75- case netDayExtent ..< netYearExtent:
76- netValue /= Scale . day. extent
77- scaleSymbol = . day
78- case netYearExtent ..< netCenturyExtent:
79- netValue /= Scale . year. extent
80- scaleSymbol = . year
81- case netCenturyExtent ..< netMilleniumExtent:
82- netValue /= Scale . century. extent
83- scaleSymbol = . century
84- default :
85- netValue /= Scale . millenium. extent
86- scaleSymbol = . millenium
87- }
47+ let ( scaledValue, scaleSymbol) = TimeCompactor . getScaledValue ( rawValue, roundSmallToWhole)
48+
49+ let showWholeValue : Bool = {
50+ let smallValueThreshold = 100 - threshold
51+ let isLargeNetValue = smallValueThreshold <= abs ( scaledValue)
52+ let roundToWhole = !isLargeNetValue && roundSmallToWhole
53+ return roundToWhole || isLargeNetValue
54+ } ( )
8855
89- let smallValueThreshold = 100 - threshold
90- let isLargeNetValue = smallValueThreshold <= abs ( netValue)
91- let roundToWhole = !isLargeNetValue && roundSmallToWhole
92- let fractionDigitCount = roundToWhole || isLargeNetValue ? 0 : 1
93-
94- self . numberStyle = . decimal
56+ let fractionDigitCount = showWholeValue ? 0 : 1
9557 self . minimumFractionDigits = fractionDigitCount
9658 self . maximumFractionDigits = fractionDigitCount
97- self . usesGroupingSeparator = false
9859
99- guard let raw = super. string ( from: netValue as NSNumber ) else { return nil }
100-
101- guard let lastDigitIndex = raw . lastIndex ( where : { $0 . isNumber } ) else { return nil }
60+ guard let raw = super. string ( from: scaledValue as NSNumber ) ,
61+ let lastDigitIndex = raw . lastIndex ( where : { $0 . isNumber } )
62+ else { return nil }
10263
10364 let afterLastDigitIndex = raw. index ( after: lastDigitIndex)
104-
10565 let prefix = raw. prefix ( upTo: afterLastDigitIndex)
10666
10767 switch style {
@@ -114,3 +74,56 @@ public class TimeCompactor: NumberFormatter {
11474 }
11575 }
11676}
77+
78+ extension TimeCompactor {
79+ private typealias LOOKUP = ( range: Range < Double > , divisor: Double , scale: Scale )
80+
81+ // thresholds
82+ private static let halfDollar : Double = 0.5
83+ private static let nickel : Double = 0.05
84+
85+ // cached lookup tables
86+ private static let halfDollarLookup : [ LOOKUP ] = TimeCompactor . generateLookup ( threshold: halfDollar)
87+ private static let nickelLookup : [ LOOKUP ] = TimeCompactor . generateLookup ( threshold: nickel)
88+
89+ static func getThreshold( _ roundSmallToWhole: Bool ) -> Double {
90+ roundSmallToWhole ? TimeCompactor . halfDollar : TimeCompactor . nickel
91+ }
92+
93+ static func getScaledValue( _ rawValue: Double , _ roundSmallToWhole: Bool ) -> ( Double , Scale ) {
94+ let threshold = getThreshold ( roundSmallToWhole)
95+ let absValue = abs ( rawValue)
96+ if !( 0.0 ... threshold) . contains ( absValue) {
97+ if let ( divisor, scale) = TimeCompactor . lookup ( roundSmallToWhole, absValue) {
98+ let netValue = rawValue / divisor
99+ return ( netValue, scale)
100+ }
101+ }
102+ return ( 0.0 , . second)
103+ }
104+
105+ private static func lookup( _ roundSmallToWhole: Bool , _ absValue: Double ) -> ( divisor: Double , scale: Scale ) ? {
106+ let records = roundSmallToWhole ? TimeCompactor . halfDollarLookup : TimeCompactor . nickelLookup
107+ guard let record = records. first ( where: { $0. range. contains ( absValue) } ) else { return nil }
108+ return ( record. divisor, record. scale)
109+ }
110+
111+ private static func generateLookup( threshold: Double ) -> [ LOOKUP ] {
112+ let netMinuteExtent : Double = Scale . minute. extent - threshold
113+ let netHourExtent : Double = Scale . hour. extent - threshold * Scale. minute. extent
114+ let netDayExtent : Double = Scale . day. extent - threshold * Scale. hour. extent
115+ let netYearExtent : Double = Scale . year. extent - threshold * Scale. day. extent
116+ let netCenturyExtent : Double = Scale . century. extent - threshold * Scale. year. extent
117+ let netMilleniumExtent : Double = Scale . millenium. extent - threshold * Scale. century. extent
118+
119+ return [
120+ ( threshold ..< netMinuteExtent, 1.0 , . second) ,
121+ ( netMinuteExtent ..< netHourExtent, Scale . minute. extent, . minute) ,
122+ ( netHourExtent ..< netDayExtent, Scale . hour. extent, . hour) ,
123+ ( netDayExtent ..< netYearExtent, Scale . day. extent, . day) ,
124+ ( netYearExtent ..< netCenturyExtent, Scale . year. extent, . year) ,
125+ ( netCenturyExtent ..< netMilleniumExtent, Scale . century. extent, . century) ,
126+ ( netMilleniumExtent ..< Double . greatestFiniteMagnitude, Scale . millenium. extent, . millenium) ,
127+ ]
128+ }
129+ }
0 commit comments