Index: src/OFApplication.m ================================================================== --- src/OFApplication.m +++ src/OFApplication.m @@ -18,10 +18,11 @@ #include #include #include +#include #include #import "OFApplication.h" #import "OFString.h" #import "OFArray.h" @@ -98,10 +99,12 @@ id delegate; #ifdef OF_WINDOWS wchar_t **wargv, **wenvp; int wargc, si = 0; #endif + + setlocale(LC_ALL, ""); if ([cls isSubclassOfClass: [OFApplication class]]) { fprintf(stderr, "FATAL ERROR:\n Class %s is a subclass of " "class OFApplication, but class\n %s was specified as " "application delegate!\n Most likely, you wanted to " Index: src/OFNumber.m ================================================================== --- src/OFNumber.m +++ src/OFNumber.m @@ -15,11 +15,10 @@ */ #include "config.h" #include -#include #include #import "OFNumber.h" #import "OFString.h" #import "OFXMLElement.h" @@ -905,37 +904,21 @@ case OF_NUMBER_TYPE_INTPTR: return [OFString stringWithFormat: @"%jd", [self intMaxValue]]; case OF_NUMBER_TYPE_FLOAT: ret = [OFMutableString stringWithFormat: @"%g", _value.float_]; - { - void *pool = objc_autoreleasePoolPush(); - OFString *decimalPoint = [OFString stringWithUTF8String: - localeconv()->decimal_point]; - - if (![ret containsString: decimalPoint]) - [ret appendFormat: @"%@0", decimalPoint]; - - objc_autoreleasePoolPop(pool); - } + if (![ret containsString: @"."]) + [ret appendString: @".0"]; [ret makeImmutable]; return ret; case OF_NUMBER_TYPE_DOUBLE: ret = [OFMutableString stringWithFormat: @"%g", _value.double_]; - { - void *pool = objc_autoreleasePoolPush(); - OFString *decimalPoint = [OFString stringWithUTF8String: - localeconv()->decimal_point]; - - if (![ret containsString: decimalPoint]) - [ret appendFormat: @"%@0", decimalPoint]; - - objc_autoreleasePoolPop(pool); - } + if (![ret containsString: @"."]) + [ret appendString: @".0"]; [ret makeImmutable]; return ret; default: Index: src/OFString.m ================================================================== --- src/OFString.m +++ src/OFString.m @@ -28,10 +28,11 @@ #import "OFString_UTF8.h" #import "OFString_UTF8+Private.h" #import "OFArray.h" #import "OFDictionary.h" #import "OFDataArray.h" +#import "OFSystemInfo.h" #ifdef OF_HAVE_FILES # import "OFFile.h" #endif #import "OFURL.h" #ifdef OF_HAVE_SOCKETS @@ -2333,11 +2334,14 @@ } - (float)floatValue { void *pool = objc_autoreleasePoolPush(); - const char *UTF8String = [self UTF8String]; + OFString *decimalPoint = [OFSystemInfo decimalPoint]; + const char *UTF8String = [[self + stringByReplacingOccurrencesOfString: @"." + withString: decimalPoint] UTF8String]; char *endPointer = NULL; float value; while (*UTF8String == ' ' || *UTF8String == '\t' || *UTF8String == '\n' || *UTF8String == '\r' || *UTF8String == '\f') @@ -2359,11 +2363,14 @@ } - (double)doubleValue { void *pool = objc_autoreleasePoolPush(); - const char *UTF8String = [self UTF8String]; + OFString *decimalPoint = [OFSystemInfo decimalPoint]; + const char *UTF8String = [[self + stringByReplacingOccurrencesOfString: @"." + withString: decimalPoint] UTF8String]; char *endPointer = NULL; double value; while (*UTF8String == ' ' || *UTF8String == '\t' || *UTF8String == '\n' || *UTF8String == '\r' || *UTF8String == '\f') Index: src/OFSystemInfo.h ================================================================== --- src/OFSystemInfo.h +++ src/OFSystemInfo.h @@ -49,10 +49,17 @@ * * @return The native 8-bit string encoding of the operating system */ + (of_string_encoding_t)native8BitEncoding; +/*! + * @brief Returns the decimal point in the system's locale. + * + * @return The decimal point in the system's locale + */ ++ (OFString*)decimalPoint; + /*! * @brief Returns the path where user data for the application can be stored. * * On Unix systems, this adheres to the XDG Base Directory specification.@n * On Mac OS X and iOS, it uses the `NSApplicationSupportDirectory` directory.@n Index: src/OFSystemInfo.m ================================================================== --- src/OFSystemInfo.m +++ src/OFSystemInfo.m @@ -22,10 +22,11 @@ #include /* include any libc header to get the libc defines */ #ifdef __GLIBC__ # undef __USE_XOPEN #endif +#include #include #include "platform.h" #ifdef OF_MAC_OS_X @@ -146,10 +147,15 @@ + (of_string_encoding_t)native8BitEncoding { /* FIXME */ return OF_STRING_ENCODING_UTF_8; } + ++ (OFString*)decimalPoint +{ + return [OFString stringWithUTF8String: localeconv()->decimal_point]; +} + (OFString*)userDataPath { #if defined(OF_MAC_OS_X) || defined(OF_IOS) void *pool = objc_autoreleasePoolPush(); Index: src/of_asprintf.m ================================================================== --- src/of_asprintf.m +++ src/of_asprintf.m @@ -24,10 +24,11 @@ #include #include #import "OFString.h" +#import "OFSystemInfo.h" #define MAX_SUBFORMAT_LEN 64 #ifndef HAVE_ASPRINTF /* @@ -64,10 +65,11 @@ LENGTH_MODIFIER_J, LENGTH_MODIFIER_Z, LENGTH_MODIFIER_T, LENGTH_MODIFIER_CAPITAL_L } lengthModifier; + bool useLocale; }; #ifndef HAVE_ASPRINTF static int vasprintf(char **string, const char *format, va_list arguments) @@ -163,10 +165,14 @@ case '0': if (!appendSubformat(ctx, ctx->format + ctx->i, 1)) return false; break; + case ',': + /* ObjFW extension: Use decimal point from locale */ + ctx->useLocale = true; + break; default: ctx->state = STATE_FORMAT_FIELD_WIDTH; ctx->i--; break; @@ -499,10 +505,39 @@ va_arg(ctx->arguments, long double)); break; default: return false; } + + /* + * Ugly hack to undo locale, as there is nothing such as + * asprintf_l in POSIX. + */ + if (!ctx->useLocale) { + void *pool = objc_autoreleasePoolPush(); + char *tmp2; + + @try { + OFMutableString *tmpStr = [OFMutableString + stringWithUTF8String: tmp + length: tmpLen]; + OFString *decimalPoint = + [OFSystemInfo decimalPoint]; + [tmpStr replaceOccurrencesOfString: decimalPoint + withString: @"."]; + if ([tmpStr UTF8StringLength] > INT_MAX) + return false; + tmpLen = (int)[tmpStr UTF8StringLength]; + tmp2 = malloc(tmpLen); + memcpy(tmp2, [tmpStr UTF8String], tmpLen); + } @finally { + free(tmp); + objc_autoreleasePoolPop(pool); + } + + tmp = tmp2; + } break; case 'c': switch (ctx->lengthModifier) { case LENGTH_MODIFIER_NONE: @@ -610,10 +645,11 @@ } memset(ctx->subformat, 0, MAX_SUBFORMAT_LEN); ctx->subformatLen = 0; ctx->lengthModifier = LENGTH_MODIFIER_NONE; + ctx->useLocale = false; ctx->last = ctx->i + 1; ctx->state = STATE_STRING; return true; @@ -639,10 +675,11 @@ va_copy(ctx.arguments, arguments); ctx.bufferLen = 0; ctx.last = 0; ctx.state = STATE_STRING; ctx.lengthModifier = LENGTH_MODIFIER_NONE; + ctx.useLocale = false; if ((ctx.buffer = malloc(1)) == NULL) return -1; for (ctx.i = 0; ctx.i < ctx.formatLen; ctx.i++) { Index: tests/OFStringTests.m ================================================================== --- tests/OFStringTests.m +++ tests/OFStringTests.m @@ -522,21 +522,17 @@ OFInvalidFormatException, [@"$" hexadecimalValue]) EXPECT_EXCEPTION(@"Detect invalid chars in -[hexadecimalValue] #4", OFInvalidFormatException, [@"$ " hexadecimalValue]) EXPECT_EXCEPTION(@"Detect invalid chars in -[floatValue] #1", - OFInvalidFormatException, [@"0,0" floatValue]) - EXPECT_EXCEPTION(@"Detect invalid chars in -[floatValue] #2", OFInvalidFormatException, [@"0.0a" floatValue]) - EXPECT_EXCEPTION(@"Detect invalid chars in -[floatValue] #3", + EXPECT_EXCEPTION(@"Detect invalid chars in -[floatValue] #2", OFInvalidFormatException, [@"0 0" floatValue]) EXPECT_EXCEPTION(@"Detect invalid chars in -[doubleValue] #1", - OFInvalidFormatException, [@"0,0" floatValue]) - EXPECT_EXCEPTION(@"Detect invalid chars in -[doubleValue] #2", OFInvalidFormatException, [@"0.0a" floatValue]) - EXPECT_EXCEPTION(@"Detect invalid chars in -[doubleValue] #3", + EXPECT_EXCEPTION(@"Detect invalid chars in -[doubleValue] #2", OFInvalidFormatException, [@"0 0" floatValue]) EXPECT_EXCEPTION(@"Detect out of range in -[decimalValue]", OFOutOfRangeException, [@"12345678901234567890123456789012345678901234567890" Index: utils/ofhttp/OFHTTP.m ================================================================== --- utils/ofhttp/OFHTTP.m +++ utils/ofhttp/OFHTTP.m @@ -711,19 +711,19 @@ type = @"unknown"; if (_length >= 0) { if (_resumedFrom + _length >= GIBIBYTE) lengthString = [OFString stringWithFormat: - @"%.2f GiB", + @"%,.2f GiB", (float)(_resumedFrom + _length) / GIBIBYTE]; else if (_resumedFrom + _length >= MEBIBYTE) lengthString = [OFString stringWithFormat: - @"%.2f MiB", + @"%,.2f MiB", (float)(_resumedFrom + _length) / MEBIBYTE]; else if (_resumedFrom + _length >= KIBIBYTE) lengthString = [OFString stringWithFormat: - @"%.2f KiB", + @"%,.2f KiB", (float)(_resumedFrom + _length) / KIBIBYTE]; else lengthString = [OFString stringWithFormat: @"%jd bytes", _resumedFrom + _length]; } else Index: utils/ofhttp/ProgressBar.m ================================================================== --- utils/ofhttp/ProgressBar.m +++ utils/ofhttp/ProgressBar.m @@ -125,11 +125,11 @@ for (size_t i = 0; i < barWidth - (size_t)bars - 1; i++) [of_stdout writeString: @" "]; } - [of_stdout writeFormat: @"▏ %6.2f%% ", percent]; + [of_stdout writeFormat: @"▏ %,6.2f%% ", percent]; if (percent == 100) { double timeInterval = -[_startDate timeIntervalSinceNow]; _BPS = (float)_received / (float)timeInterval; @@ -137,39 +137,39 @@ } if (isinf(_ETA)) [of_stdout writeString: @"--:--:-- "]; else if (_ETA >= 99 * 3600) - [of_stdout writeFormat: @"%4.2f d ", _ETA / (24 * 3600)]; + [of_stdout writeFormat: @"%,4.2f d ", _ETA / (24 * 3600)]; else [of_stdout writeFormat: @"%2u:%02u:%02u ", (uint8_t)(_ETA / 3600), (uint8_t)(_ETA / 60) % 60, (uint8_t)_ETA % 60]; if (_BPS >= GIBIBYTE) - [of_stdout writeFormat: @"%7.2f GiB/s", _BPS / GIBIBYTE]; + [of_stdout writeFormat: @"%,7.2f GiB/s", _BPS / GIBIBYTE]; else if (_BPS >= MEBIBYTE) - [of_stdout writeFormat: @"%7.2f MiB/s", _BPS / MEBIBYTE]; + [of_stdout writeFormat: @"%,7.2f MiB/s", _BPS / MEBIBYTE]; else if (_BPS >= KIBIBYTE) - [of_stdout writeFormat: @"%7.2f KiB/s", _BPS / KIBIBYTE]; + [of_stdout writeFormat: @"%,7.2f KiB/s", _BPS / KIBIBYTE]; else - [of_stdout writeFormat: @"%7.2f B/s ", _BPS]; + [of_stdout writeFormat: @"%,7.2f B/s ", _BPS]; } - (void)_drawReceived { if (_resumedFrom + _received >= GIBIBYTE) [of_stdout writeFormat: - @"\r %7.2f GiB ", + @"\r %,7.2f GiB ", (float)(_resumedFrom + _received) / GIBIBYTE]; else if (_resumedFrom + _received >= MEBIBYTE) [of_stdout writeFormat: - @"\r %7.2f MiB ", + @"\r %,7.2f MiB ", (float)(_resumedFrom + _received) / MEBIBYTE]; else if (_resumedFrom + _received >= KIBIBYTE) [of_stdout writeFormat: - @"\r %7.2f KiB ", + @"\r %,7.2f KiB ", (float)(_resumedFrom + _received) / KIBIBYTE]; else [of_stdout writeFormat: @"\r %jd bytes ", _resumedFrom + _received]; @@ -176,17 +176,17 @@ if (_stopped) _BPS = (float)_received / -(float)[_startDate timeIntervalSinceNow]; if (_BPS >= GIBIBYTE) - [of_stdout writeFormat: @"%7.2f GiB/s", _BPS / GIBIBYTE]; + [of_stdout writeFormat: @"%,7.2f GiB/s", _BPS / GIBIBYTE]; else if (_BPS >= MEBIBYTE) - [of_stdout writeFormat: @"%7.2f MiB/s", _BPS / MEBIBYTE]; + [of_stdout writeFormat: @"%,7.2f MiB/s", _BPS / MEBIBYTE]; else if (_BPS >= KIBIBYTE) - [of_stdout writeFormat: @"%7.2f KiB/s", _BPS / KIBIBYTE]; + [of_stdout writeFormat: @"%,7.2f KiB/s", _BPS / KIBIBYTE]; else - [of_stdout writeFormat: @"%7.2f B/s ", _BPS]; + [of_stdout writeFormat: @"%,7.2f B/s ", _BPS]; } - (void)draw { if (_length > 0)