@@ -20,31 +20,16 @@ import Foundation
20
20
21
21
public class TimeCompactor : NumberFormatter {
22
22
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
26
26
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
-
35
27
public init ( blankIfZero: Bool = false ,
36
28
style: Style = . short,
37
29
roundSmallToWhole: Bool = false ) {
38
30
self . blankIfZero = blankIfZero
39
31
self . style = style
40
32
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
48
33
super. init ( )
49
34
}
50
35
@@ -53,55 +38,30 @@ public class TimeCompactor: NumberFormatter {
53
38
}
54
39
55
40
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)
57
44
58
45
if blankIfZero, absValue <= threshold { return " " }
59
46
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
+ } ( )
88
55
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
95
57
self . minimumFractionDigits = fractionDigitCount
96
58
self . maximumFractionDigits = fractionDigitCount
97
- self . usesGroupingSeparator = false
98
59
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 }
102
63
103
64
let afterLastDigitIndex = raw. index ( after: lastDigitIndex)
104
-
105
65
let prefix = raw. prefix ( upTo: afterLastDigitIndex)
106
66
107
67
switch style {
@@ -114,3 +74,56 @@ public class TimeCompactor: NumberFormatter {
114
74
}
115
75
}
116
76
}
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