source: trunk/macosx/NSStringAdditions.m

Last change on this file was 14341, checked in by livings124, 7 years ago

Yosemite: use NSDateComponentsFormatter instead of custom time string code

  • Property svn:keywords set to Date Rev Author Id
File size: 12.1 KB
Line 
1/******************************************************************************
2 * $Id: NSStringAdditions.m 14341 2014-10-17 05:12:00Z mikedld $
3 *
4 * Copyright (c) 2005-2012 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#import "NSApplicationAdditions.h"
26#import "NSStringAdditions.h"
27
28#import "transmission.h"
29#import "utils.h"
30
31@interface NSString (Private)
32
33+ (NSString *) stringForFileSizeLion: (uint64_t) size showUnitUnless: (NSString *) notAllowedUnit unitsUsed: (NSString **) unitUsed;
34
35+ (NSString *) stringForSpeed: (CGFloat) speed kb: (NSString *) kb mb: (NSString *) mb gb: (NSString *) gb;
36
37@end
38
39@implementation NSString (NSStringAdditions)
40
41+ (NSString *) ellipsis
42{
43        return [NSString stringWithUTF8String: "\xE2\x80\xA6"];
44}
45
46- (NSString *) stringByAppendingEllipsis
47{
48        return [self stringByAppendingString: [NSString ellipsis]];
49}
50
51#warning use localizedStringWithFormat: directly when 10.8-only
52+ (NSString *) formattedUInteger: (NSUInteger) value
53{
54    if ([NSApp isOnMountainLionOrBetter])
55        return [NSString localizedStringWithFormat: @"%lu", value];
56    else
57    {
58        static NSNumberFormatter * numberFormatter;
59        static dispatch_once_t onceToken;
60        dispatch_once(&onceToken, ^{
61            numberFormatter = [[NSNumberFormatter alloc] init];
62            [numberFormatter setNumberStyle: NSNumberFormatterDecimalStyle];
63            [numberFormatter setMaximumFractionDigits: 0];
64        });
65       
66        return [numberFormatter stringFromNumber: [NSNumber numberWithUnsignedInteger: value]];
67    }
68}
69
70#warning should we take long long instead?
71+ (NSString *) stringForFileSize: (uint64_t) size
72{
73    if ([NSApp isOnMountainLionOrBetter])
74        return [NSByteCountFormatterMtLion stringFromByteCount: size countStyle: NSByteCountFormatterCountStyleFile];
75    else
76        return [self stringForFileSizeLion: size showUnitUnless: nil unitsUsed: nil];
77}
78
79#warning should we take long long instead?
80+ (NSString *) stringForFilePartialSize: (uint64_t) partialSize fullSize: (uint64_t) fullSize
81{
82    NSString * partialString, * fullString;
83    if ([NSApp isOnMountainLionOrBetter])
84    {
85        NSByteCountFormatter * fileSizeFormatter = [[NSByteCountFormatterMtLion alloc] init];
86       
87        fullString = [fileSizeFormatter stringFromByteCount: fullSize];
88       
89        //figure out the magniture of the two, since we can't rely on comparing the units because of localization and pluralization issues (for example, "1 byte of 2 bytes")
90        BOOL partialUnitsSame;
91        if (partialSize == 0)
92            partialUnitsSame = YES; //we want to just show "0" when we have no partial data, so always set to the same units
93        else
94        {
95            const unsigned int magnitudePartial = log(partialSize)/log(1000);
96            const unsigned int magnitudeFull = fullSize < 1000 ? 0 : log(fullSize)/log(1000); //we have to catch 0 with a special case, so might as well avoid the math for all of magnitude 0
97            partialUnitsSame = magnitudePartial == magnitudeFull;
98        }
99       
100        [fileSizeFormatter setIncludesUnit: !partialUnitsSame];
101        partialString = [fileSizeFormatter stringFromByteCount: partialSize];
102       
103        [fileSizeFormatter release];
104    }
105    else
106    {
107        NSString * units;
108        fullString = [self stringForFileSizeLion: fullSize showUnitUnless: nil unitsUsed: &units];
109        partialString = [self stringForFileSizeLion: partialSize showUnitUnless: units unitsUsed: nil];
110    }
111   
112    return [NSString stringWithFormat: NSLocalizedString(@"%@ of %@", "file size string"), partialString, fullString];
113}
114
115+ (NSString *) stringForSpeed: (CGFloat) speed
116{
117    return [self stringForSpeed: speed
118                kb: NSLocalizedString(@"KB/s", "Transfer speed (kilobytes per second)")
119                mb: NSLocalizedString(@"MB/s", "Transfer speed (megabytes per second)")
120                gb: NSLocalizedString(@"GB/s", "Transfer speed (gigabytes per second)")];
121}
122
123+ (NSString *) stringForSpeedAbbrev: (CGFloat) speed
124{
125    return [self stringForSpeed: speed kb: @"K" mb: @"M" gb: @"G"];
126}
127
128+ (NSString *) stringForRatio: (CGFloat) ratio
129{
130    //N/A is different than libtransmission's
131    if ((int)ratio == TR_RATIO_NA)
132        return NSLocalizedString(@"N/A", "No Ratio");
133    else if ((int)ratio == TR_RATIO_INF)
134        return [NSString stringWithUTF8String: "\xE2\x88\x9E"];
135    else
136    {
137        if (ratio < 10.0)
138            return [NSString localizedStringWithFormat: @"%.2f", tr_truncd(ratio, 2)];
139        else if (ratio < 100.0)
140            return [NSString localizedStringWithFormat: @"%.1f", tr_truncd(ratio, 1)];
141        else
142            return [NSString localizedStringWithFormat: @"%.0f", tr_truncd(ratio, 0)];
143    }
144}
145
146+ (NSString *) percentString: (CGFloat) progress longDecimals: (BOOL) longDecimals
147{
148    if (progress >= 1.0)
149        return [NSString localizedStringWithFormat: @"%d%%", 100];
150    else if (longDecimals)
151        return [NSString localizedStringWithFormat: @"%.2f%%", tr_truncd(progress * 100.0, 2)];
152    else
153        return [NSString localizedStringWithFormat: @"%.1f%%", tr_truncd(progress * 100.0, 1)];
154}
155
156+ (NSString *) timeString: (uint64_t) seconds includesTimeRemainingPhrase: (BOOL) includesTimeRemainingPhrase showSeconds: (BOOL) showSeconds
157{
158    return [NSString timeString: seconds
159    includesTimeRemainingPhrase: includesTimeRemainingPhrase
160                    showSeconds: showSeconds
161                      maxFields: NSUIntegerMax];
162}
163
164+ (NSString *) timeString: (uint64_t) seconds includesTimeRemainingPhrase: (BOOL) includesTimeRemainingPhrase showSeconds: (BOOL) showSeconds maxFields: (NSUInteger) max
165{
166    NSAssert(![NSApp isOnYosemiteOrBetter], @"you should be using NSDateComponentsFormatter on >= 10.10");
167    NSParameterAssert(max > 0);
168   
169    NSMutableArray * timeArray = [NSMutableArray arrayWithCapacity: MIN(max, 5)];
170    NSUInteger remaining = seconds; //causes problems for some users when it's a uint64_t
171   
172    if (seconds >= 31557600) //official amount of seconds in one year
173    {
174        const NSUInteger years = remaining / 31557600;
175        if (years == 1)
176            [timeArray addObject: NSLocalizedString(@"1 year", "time string")];
177        else
178            [timeArray addObject: [NSString stringWithFormat: NSLocalizedString(@"%u years", "time string"), years]];
179        remaining %= 31557600;
180        --max;
181    }
182    if (max > 0 && seconds >= (24 * 60 * 60))
183    {
184        const NSUInteger days = remaining / (24 * 60 * 60);
185        if (days == 1)
186            [timeArray addObject: NSLocalizedString(@"1 day", "time string")];
187        else
188            [timeArray addObject: [NSString stringWithFormat: NSLocalizedString(@"%u days", "time string"), days]];
189        remaining %= (24 * 60 * 60);
190        --max;
191    }
192    if (max > 0 && seconds >= (60 * 60))
193    {
194        [timeArray addObject: [NSString stringWithFormat: NSLocalizedString(@"%u hr", "time string"), remaining / (60 * 60)]];
195        remaining %= (60 * 60);
196        --max;
197    }
198    if (max > 0 && (!showSeconds || seconds >= 60))
199    {
200        [timeArray addObject: [NSString stringWithFormat: NSLocalizedString(@"%u min", "time string"), remaining / 60]];
201        remaining %= 60;
202        --max;
203    }
204    if (max > 0 && showSeconds)
205        [timeArray addObject: [NSString stringWithFormat: NSLocalizedString(@"%u sec", "time string"), remaining]];
206   
207    NSString * timeString = [timeArray componentsJoinedByString: @" "];
208   
209    if (includesTimeRemainingPhrase) {
210        timeString = [NSString stringWithFormat: NSLocalizedString(@"%@ remaining", "time remaining string"), timeString];
211    }
212   
213    return timeString;
214}
215
216- (NSComparisonResult) compareNumeric: (NSString *) string
217{
218    const NSStringCompareOptions comparisonOptions = NSNumericSearch | NSForcedOrderingSearch;
219    return [self compare: string options: comparisonOptions range: NSMakeRange(0, [self length]) locale: [NSLocale currentLocale]];
220}
221
222- (NSArray *) betterComponentsSeparatedByCharactersInSet: (NSCharacterSet *) separators
223{
224    NSMutableArray * components = [NSMutableArray array];
225   
226    NSCharacterSet * includededCharSet = [separators invertedSet];
227    NSUInteger index = 0;
228    const NSUInteger fullLength = [self length];
229    do
230    {
231        const NSUInteger start = [self rangeOfCharacterFromSet: includededCharSet options: 0 range: NSMakeRange(index, fullLength - index)].location;
232        if (start == NSNotFound)
233            break;
234       
235        const NSRange endRange = [self rangeOfCharacterFromSet: separators options: 0 range: NSMakeRange(start, fullLength - start)];
236        if (endRange.location == NSNotFound)
237        {
238            [components addObject: [self substringFromIndex: start]];
239            break;
240        }
241       
242        [components addObject: [self substringWithRange: NSMakeRange(start, endRange.location - start)]];
243       
244        index = NSMaxRange(endRange);
245    }
246    while (YES);
247   
248    return components;
249}
250
251@end
252
253@implementation NSString (Private)
254
255+ (NSString *) stringForFileSizeLion: (uint64_t) size showUnitUnless: (NSString *) notAllowedUnit unitsUsed: (NSString **) unitUsed
256{
257    double convertedSize;
258    NSString * unit;
259    NSUInteger decimals;
260    if (size < pow(1000, 2))
261    {
262        convertedSize = size / 1000.0;
263        unit = NSLocalizedString(@"KB", "File size - kilobytes");
264        decimals = convertedSize >= 10.0 ? 0 : 1;
265    }
266    else if (size < pow(1000, 3))
267    {
268        convertedSize = size / powf(1000.0, 2);
269        unit = NSLocalizedString(@"MB", "File size - megabytes");
270        decimals = 1;
271    }
272    else if (size < pow(1000, 4))
273    {
274        convertedSize = size / powf(1000.0, 3);
275        unit = NSLocalizedString(@"GB", "File size - gigabytes");
276        decimals = 2;
277    }
278    else
279    {
280        convertedSize = size / powf(1000.0, 4);
281        unit = NSLocalizedString(@"TB", "File size - terabytes");
282        decimals = 2;
283    }
284   
285    //match Finder's behavior
286    NSNumberFormatter * numberFormatter = [[NSNumberFormatter alloc] init];
287    [numberFormatter setNumberStyle: NSNumberFormatterDecimalStyle];
288    [numberFormatter setMinimumFractionDigits: 0];
289    [numberFormatter setMaximumFractionDigits: decimals];
290   
291    NSString * fileSizeString = [numberFormatter stringFromNumber: [NSNumber numberWithFloat: convertedSize]];
292    [numberFormatter release];
293   
294    if (!notAllowedUnit || ![unit isEqualToString: notAllowedUnit])
295        fileSizeString = [fileSizeString stringByAppendingFormat: @" %@", unit];
296   
297    if (unitUsed)
298        *unitUsed = unit;
299   
300    return fileSizeString;
301}
302
303+ (NSString *) stringForSpeed: (CGFloat) speed kb: (NSString *) kb mb: (NSString *) mb gb: (NSString *) gb
304{
305    if (speed <= 999.95) //0.0 KB/s to 999.9 KB/s
306        return [NSString localizedStringWithFormat: @"%.1f %@", speed, kb];
307   
308    speed /= 1000.0;
309   
310    if (speed <= 99.995) //1.00 MB/s to 99.99 MB/s
311        return [NSString localizedStringWithFormat: @"%.2f %@", speed, mb];
312    else if (speed <= 999.95) //100.0 MB/s to 999.9 MB/s
313        return [NSString localizedStringWithFormat: @"%.1f %@", speed, mb];
314    else //insane speeds
315        return [NSString localizedStringWithFormat: @"%.2f %@", (speed / 1000.0), gb];
316}
317
318@end
Note: See TracBrowser for help on using the repository browser.