Skip to content

Commit 7d60a67

Browse files
David DunnDavid Dunn
authored andcommitted
Added a new test suite and updated code to cater for the new tests
1 parent 7726f57 commit 7d60a67

File tree

2 files changed

+580
-172
lines changed

2 files changed

+580
-172
lines changed

Foundation/NSByteCountFormatter.swift

Lines changed: 193 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ extension ByteCountFormatter {
2828
// Can use any unit in showing the number.
2929
public static let useAll = Units(rawValue: 0x0FFFF)
3030
}
31-
31+
3232
public enum CountStyle : Int {
3333

3434
// Specifies display of file or storage byte counts. The actual behavior for this is platform-specific; on OS X 10.8, this uses the decimal style, but that may change over time.
@@ -47,7 +47,7 @@ open class ByteCountFormatter : Formatter {
4747
}
4848

4949
public required init?(coder: NSCoder) {
50-
super.init(coder: coder)
50+
NSUnimplemented()
5151
}
5252

5353
/* Specify the units that can be used in the output. If NSByteCountFormatterUseDefault, uses platform-appropriate settings; otherwise will only use the specified units. This is the default value. Note that ZB and YB cannot be covered by the range of possible values, but you can still choose to use these units to get fractional display ("0.0035 ZB" for instance).
@@ -82,39 +82,31 @@ open class ByteCountFormatter : Formatter {
8282
/* Specify the formatting context for the formatted string. Default is NSFormattingContextUnknown.
8383
*/
8484
open var formattingContext: Context = .unknown
85-
85+
8686
/* A variable to store the actual bytes passed into the methods. This value is used if the includesActualByteCount property is set.
87-
*/
87+
*/
8888
private var actualBytes: String = ""
8989

9090
/* Create an instance of NumberFormatter for use in various methods
91-
*/
91+
*/
9292
private let numberFormatter = NumberFormatter()
9393

9494
/* Shortcut for converting a byte count into a string without creating an NSByteCountFormatter and an NSNumber. If you need to specify options other than countStyle, create an instance of NSByteCountFormatter first.
95-
*/
95+
*/
9696
open class func string(fromByteCount byteCount: Int64, countStyle: ByteCountFormatter.CountStyle) -> String {
9797
let formatter = ByteCountFormatter()
9898
formatter.countStyle = countStyle
9999
return formatter.string(fromByteCount: byteCount)
100100
}
101101

102102
/* Convenience method on string(for:):. Convert a byte count into a string without creating an NSNumber.
103-
*/
103+
*/
104104
open func string(fromByteCount byteCount: Int64) -> String {
105105
//Convert actual bytes to a formatted string for use later
106-
if includesActualByteCount {
107-
numberFormatter.numberStyle = .decimal
108-
actualBytes = numberFormatter.string(from: NSNumber(value: byteCount))!
109-
}
110-
111-
if allowedUnits != .useDefault && allowedUnits != .useAll {
112-
if countStyle == .file || countStyle == .decimal {
113-
return unitsToUseFor(byteCount: byteCount, byteSize: decimalByteSize)
114-
} else {
115-
return unitsToUseFor(byteCount: byteCount, byteSize: binaryByteSize)
116-
}
117-
} else if countStyle == .decimal || countStyle == .file {
106+
numberFormatter.numberStyle = .decimal
107+
actualBytes = numberFormatter.string(from: NSNumber(value: byteCount))!
108+
109+
if countStyle == .decimal || countStyle == .file {
118110
return convertValue(fromByteCount: byteCount, for: decimalByteSize)
119111
} else {
120112
return convertValue(fromByteCount: byteCount, for: binaryByteSize)
@@ -131,61 +123,143 @@ open class ByteCountFormatter : Formatter {
131123
return string(fromByteCount: Int64(value))
132124
}
133125

134-
/* If allowedUnits has been set this function will ensure the correct unit is used and conversion is done. The conversion is done by making use of the divide method.
135-
*/
136-
private func unitsToUseFor(byteCount: Int64, byteSize: [Unit: Double]) -> String {
137-
let bytes = Double(byteCount)
138-
139-
if bytes == 0 {
140-
return "Zero \(Unit.KB)"
141-
} else if bytes == 1 {
142-
return formatNumberFor(bytes: bytes, unit: Unit.byte)
143-
}
144-
145-
switch allowedUnits {
146-
case Units.useBytes: return formatNumberFor(bytes: bytes, unit: Unit.bytes)
147-
case Units.useKB: return divide(bytes, by: byteSize, for: .KB)
148-
case Units.useMB: return divide(bytes, by: byteSize, for: .MB)
149-
case Units.useGB: return divide(bytes, by: byteSize, for: .GB)
150-
case Units.useTB: return divide(bytes, by: byteSize, for: .TB)
151-
case Units.usePB: return divide(bytes, by: byteSize, for: .PB)
152-
case Units.useEB: return divide(bytes, by: byteSize, for: .EB)
153-
case Units.useZB: return divide(bytes, by: byteSize, for: .ZB)
154-
default: return divide(bytes, by: byteSize, for: .YB)
155-
156-
}
157-
}
158-
159126
/* This method accepts a byteCount and a byteSize value. Checks to see what range the byteCount falls into and then converts to the units determined by that range. The range to be used is decided by the byteSize parameter. The conversion is done by making use of the divide method.
160-
*/
127+
*/
161128
private func convertValue(fromByteCount byteCount: Int64, for byteSize: [Unit: Double]) -> String {
162129
let byte = Double(byteCount)
163-
if byte == 0 && allowsNonnumericFormatting {
164-
return "Zero \(Unit.KB)"
165-
} else if byte == 1 {
166-
return "\(byteCount) \(Unit.byte)"
167-
130+
if byte == 0, allowsNonnumericFormatting, allowedUnits == .useDefault, includesUnit, includesCount {
131+
return partsToIncludeFor(value: "Zero", unit: Unit.KB)
132+
} else if byte == 1 || byte == -1 {
133+
if allowedUnits.contains(.useAll) || allowedUnits == .useDefault {
134+
return formatNumberFor(bytes: byte, unit: Unit.byte)
135+
} else {
136+
return valueToUseFor(byteCount: byte, unit: allowedUnits)
137+
}
168138
} else if byte < byteSize[Unit.KB]! && byte > -byteSize[Unit.KB]!{
169-
return formatNumberFor(bytes: byte, unit: Unit.bytes)
170-
139+
if allowedUnits.contains(.useAll) || allowedUnits == .useDefault {
140+
return formatNumberFor(bytes: byte, unit: Unit.bytes)
141+
} else {
142+
return valueToUseFor(byteCount: byte, unit: allowedUnits)
143+
}
171144
} else if byte < byteSize[Unit.MB]! && byte > -byteSize[Unit.MB]! {
172-
return divide(byte, by: byteSize, for: .KB)
145+
if allowedUnits.contains(.useAll) || allowedUnits == .useDefault {
146+
return divide(byte, by: byteSize, for: .KB)
147+
}
148+
return valueToUseFor(byteCount: byte, unit: allowedUnits)
173149

174150
} else if byte < byteSize[Unit.GB]! && byte > -byteSize[Unit.GB]! {
175-
return divide(byte, by: byteSize, for: .MB)
151+
if allowedUnits.contains(.useAll) || allowedUnits == .useDefault {
152+
return divide(byte, by: byteSize, for: .MB)
153+
}
154+
return valueToUseFor(byteCount: byte, unit: allowedUnits)
176155

177156
} else if byte < byteSize[Unit.TB]! && byte > -byteSize[Unit.TB]! {
178-
return divide(byte, by: byteSize, for: .GB)
157+
if allowedUnits.contains(.useAll) || allowedUnits == .useDefault {
158+
return divide(byte, by: byteSize, for: .GB)
159+
}
160+
return valueToUseFor(byteCount: byte, unit: allowedUnits)
179161

180162
} else if byte < byteSize[Unit.PB]! && byte > -byteSize[Unit.PB]! {
181-
return divide(byte, by: byteSize, for: .TB)
163+
if allowedUnits.contains(.useAll) || allowedUnits == .useDefault {
164+
return divide(byte, by: byteSize, for: .TB)
165+
}
166+
return valueToUseFor(byteCount: byte, unit: allowedUnits)
182167

183168
} else if byte < byteSize[Unit.EB]! && byte > -byteSize[Unit.EB]! {
184-
return divide(byte, by: byteSize, for: .PB)
169+
if allowedUnits.contains(.useAll) || allowedUnits == .useDefault {
170+
return divide(byte, by: byteSize, for: .PB)
171+
}
172+
return valueToUseFor(byteCount: byte, unit: allowedUnits)
185173

186174
} else {
187-
return divide(byte, by: byteSize, for: .EB)
175+
if allowedUnits.contains(.useAll) || allowedUnits == .useDefault {
176+
return divide(byte, by: byteSize, for: .EB)
177+
}
178+
return valueToUseFor(byteCount: byte, unit: allowedUnits)
179+
}
180+
}
181+
182+
/*
183+
A helper method to deal with the Option Set, caters for setting an individual value or passing in an array of values.
184+
Returns the correct value based on the units that are allowed for use.
185+
*/
186+
private func valueToUseFor(byteCount: Double, unit: ByteCountFormatter.Units) -> String {
187+
var byteSize: [Unit: Double]
188+
189+
//Check to see whether we're using 1000bytes per KB or 1024 per KB
190+
if countStyle == .decimal || countStyle == .file {
191+
byteSize = decimalByteSize
192+
} else {
193+
byteSize = binaryByteSize
194+
}
195+
if byteCount == 0, allowsNonnumericFormatting, includesCount, includesUnit {
196+
return partsToIncludeFor(value: "Zero", unit: Unit.KB)
188197
}
198+
//Handles the cases where allowedUnits is set to a specific individual value. e.g. allowedUnits = .useTB
199+
switch allowedUnits {
200+
case Units.useBytes: return partsToIncludeFor(value: actualBytes, unit: Unit.bytes)
201+
case Units.useKB: return divide(byteCount, by: byteSize, for: .KB)
202+
case Units.useMB: return divide(byteCount, by: byteSize, for: .MB)
203+
case Units.useGB: return divide(byteCount, by: byteSize, for: .GB)
204+
case Units.useTB: return divide(byteCount, by: byteSize, for: .TB)
205+
case Units.usePB: return divide(byteCount, by: byteSize, for: .PB)
206+
case Units.useEB: return divide(byteCount, by: byteSize, for: .EB)
207+
case Units.useZB: return divide(byteCount, by: byteSize, for: .ZB)
208+
case Units.useYBOrHigher: return divide(byteCount, by: byteSize, for: .YB)
209+
default: break
210+
}
211+
212+
//Initialise an array that will hold all the units we can use
213+
var unitsToUse: [Unit] = []
214+
215+
//Based on what units have been selected for use, build an array out of them.
216+
if unit.contains(.useBytes) && byteCount == 1 {
217+
unitsToUse.append(.byte)
218+
} else if unit.contains(.useBytes) {
219+
unitsToUse.append(.bytes)
220+
}
221+
if unit.contains(.useKB) {
222+
unitsToUse.append(.KB)
223+
}
224+
if unit.contains(.useMB) {
225+
unitsToUse.append(.MB)
226+
}
227+
if unit.contains(.useGB) {
228+
unitsToUse.append(.GB)
229+
}
230+
if unit.contains(.useTB) {
231+
unitsToUse.append(.TB)
232+
}
233+
if unit.contains(.usePB) {
234+
unitsToUse.append(.PB)
235+
}
236+
if unit.contains(.useEB) {
237+
unitsToUse.append(.EB)
238+
}
239+
if unit.contains(.useZB) {
240+
unitsToUse.append(.ZB)
241+
}
242+
if unit.contains(.useYBOrHigher) {
243+
unitsToUse.append(.YB)
244+
}
245+
246+
247+
var counter = 0
248+
for _ in unitsToUse {
249+
counter += 1
250+
if counter > unitsToUse.count - 1 {
251+
counter = unitsToUse.count - 1
252+
}
253+
/*
254+
The units are appended to the array in asceding order, so if the value for byteCount is smaller than the byteSize value of the next unit
255+
in the Array we use the previous unit. e.g. if byteCount = 1000, and AllowedUnits = [.useKB, .useGB] check to see if byteCount is smaller
256+
than a GB in bytes(pow(1000, 3)) and if so, we'll use the previous unit which is KB in this case.
257+
*/
258+
if byteCount < byteSize[unitsToUse[counter]]! {
259+
return divide(byteCount, by: byteSize, for: unitsToUse[counter - 1])
260+
}
261+
}
262+
return divide(byteCount, by: byteSize, for: unitsToUse[counter])
189263
}
190264

191265
// Coverts the number of bytes to the correct value given a specified unit, then passes the value and unit to formattedValue
@@ -200,36 +274,34 @@ open class ByteCountFormatter : Formatter {
200274
//Formats the byte value using the NumberFormatter class based on set properties and the unit passed in as a parameter.
201275
private func formatNumberFor(bytes: Double, unit: Unit) -> String {
202276

203-
numberFormatter.numberStyle = .decimal
204-
205277
switch (zeroPadsFractionDigits, isAdaptive) {
206278
//zeroPadsFractionDigits is true, isAdaptive is true
207279
case (true, true):
208280
switch unit {
209281
case .bytes, .byte, .KB:
210-
numberFormatter.minimumFractionDigits = 0
211-
numberFormatter.maximumFractionDigits = 0
212-
let result = numberFormatter.string(from: NSNumber(value: bytes))
213-
return partsToIncludeFor(value: result!, unit: unit)
282+
let result = String(format: "%.0f", bytes)
283+
return partsToIncludeFor(value: result, unit: unit)
214284
case .MB:
215-
numberFormatter.minimumFractionDigits = 1
216-
numberFormatter.maximumFractionDigits = 1
217-
let result = numberFormatter.string(from: NSNumber(value: bytes))
218-
return partsToIncludeFor(value: result!, unit: unit)
285+
let result = String(format: "%.1f", bytes)
286+
return partsToIncludeFor(value: result, unit: unit)
219287
default:
220-
numberFormatter.minimumFractionDigits = 2
221-
numberFormatter.maximumFractionDigits = 2
222-
let result = numberFormatter.string(from: NSNumber(value: bytes))
223-
return partsToIncludeFor(value: result!, unit: unit)
288+
let result = String(format: "%.2f", bytes)
289+
return partsToIncludeFor(value: result, unit: unit)
224290
}
225291
//zeroPadsFractionDigits is true, isAdaptive is false
226292
case (true, false):
227293
if unit == .byte || unit == .bytes {
228-
return partsToIncludeFor(value: "\(bytes)", unit: unit)
294+
numberFormatter.maximumFractionDigits = 0
295+
let result = numberFormatter.string(from: NSNumber(value: bytes))
296+
return partsToIncludeFor(value: result!, unit: unit)
229297
} else {
230-
numberFormatter.usesSignificantDigits = true
231-
numberFormatter.minimumSignificantDigits = 3
232-
numberFormatter.maximumSignificantDigits = 3
298+
if lengthOfInt(number: Int(bytes)) == 3 {
299+
numberFormatter.maximumFractionDigits = 1
300+
} else {
301+
numberFormatter.usesSignificantDigits = true
302+
numberFormatter.maximumSignificantDigits = 3
303+
numberFormatter.minimumSignificantDigits = 3
304+
}
233305
let result = numberFormatter.string(from: NSNumber(value: bytes))
234306
return partsToIncludeFor(value: result!, unit: unit)
235307
}
@@ -247,25 +319,54 @@ open class ByteCountFormatter : Formatter {
247319
let result = numberFormatter.string(from: NSNumber(value: bytes))
248320
return partsToIncludeFor(value: result!, unit: unit)
249321
default:
250-
numberFormatter.minimumFractionDigits = 0
251-
numberFormatter.maximumFractionDigits = 2
252-
let result = numberFormatter.string(from: NSNumber(value: bytes))
253-
return partsToIncludeFor(value: result!, unit: unit)
322+
let result: String
323+
//Need to add in an extra case for negative numbers as NumberFormatter formats 0.005 to 0 rather than
324+
// 0.01
325+
if bytes < 0 {
326+
let negBytes = round(bytes * 100) / 100
327+
result = numberFormatter.string(from: NSNumber(value: negBytes))!
328+
} else {
329+
numberFormatter.minimumFractionDigits = 0
330+
numberFormatter.maximumFractionDigits = 2
331+
result = numberFormatter.string(from: NSNumber(value: bytes))!
332+
}
333+
334+
335+
return partsToIncludeFor(value: result, unit: unit)
254336
}
255337
//zeroPadsFractionDigits is false, isAdaptive is false
256338
case (false, false):
257339
if unit == .byte || unit == .bytes {
258-
return partsToIncludeFor(value: "\(bytes)", unit: unit)
340+
numberFormatter.minimumFractionDigits = 0
341+
numberFormatter.maximumFractionDigits = 0
342+
let result = numberFormatter.string(from: NSNumber(value: bytes))
343+
return partsToIncludeFor(value: result!, unit: unit)
259344
} else {
260-
numberFormatter.usesSignificantDigits = true
261-
numberFormatter.minimumSignificantDigits = 3
262-
numberFormatter.maximumSignificantDigits = 3
345+
if lengthOfInt(number: Int(bytes)) > 3 {
346+
numberFormatter.maximumFractionDigits = 0
347+
} else {
348+
numberFormatter.usesSignificantDigits = true
349+
numberFormatter.maximumSignificantDigits = 3
350+
}
263351
let result = numberFormatter.string(from: NSNumber(value: bytes))
264352
return partsToIncludeFor(value: result!, unit: unit)
265353
}
266354
}
267355
}
268356

357+
// A helper method to return the length of an int
358+
private func lengthOfInt(number: Int) -> Int {
359+
var num = abs(number)
360+
var length: [Int] = []
361+
362+
while num > 0 {
363+
let remainder = num % 10
364+
length.append(remainder)
365+
num /= 10
366+
}
367+
return length.count
368+
}
369+
269370
// Returns the correct string based on the includesValue and includesUnit properties
270371
private func partsToIncludeFor(value: String, unit: Unit) -> String {
271372
if includesActualByteCount, includesUnit, includesCount {
@@ -276,7 +377,11 @@ open class ByteCountFormatter : Formatter {
276377
} else if includesCount, includesUnit {
277378
return "\(value) \(unit)"
278379
} else if includesCount, !includesUnit {
279-
return "\(value)"
380+
if value == "Zero", allowedUnits == .useDefault {
381+
return "0"
382+
} else {
383+
return value
384+
}
280385
} else if !includesCount, includesUnit {
281386
return "\(unit)"
282387
} else {
@@ -298,10 +403,9 @@ open class ByteCountFormatter : Formatter {
298403
case YB
299404
}
300405
// Maps each unit to it's corresponding value in bytes for decimal
301-
private let decimalByteSize: [Unit: Double] = [.byte: 1, .KB: 1000, .MB: pow(1000, 2), .GB: pow(1000, 3), .TB: pow(1000, 4), .PB: pow(1000, 5), .EB: pow(1000, 6), .ZB: pow(1000, 7), .YB: pow(1000, 8)]
406+
private let decimalByteSize: [Unit: Double] = [.byte: 1, .bytes: 1, .KB: 1000, .MB: pow(1000, 2), .GB: pow(1000, 3), .TB: pow(1000, 4), .PB: pow(1000, 5), .EB: pow(1000, 6), .ZB: pow(1000, 7), .YB: pow(1000, 8)]
302407

303408
// Maps each unit to it's corresponding value in bytes for binary
304-
private let binaryByteSize: [Unit: Double] = [.byte: 1, .KB: 1024, .MB: pow(1024, 2), .GB: pow(1024, 3), .TB: pow(1024, 4), .PB: pow(1024, 5), .EB: pow(1024, 6), .ZB: pow(1024, 7), .YB: pow(1024, 8)]
409+
private let binaryByteSize: [Unit: Double] = [.byte: 1, .bytes: 1, .KB: 1024, .MB: pow(1024, 2), .GB: pow(1024, 3), .TB: pow(1024, 4), .PB: pow(1024, 5), .EB: pow(1024, 6), .ZB: pow(1024, 7), .YB: pow(1024, 8)]
305410

306-
}
307-
411+
}

0 commit comments

Comments
 (0)