|
13 | 13 | */ |
14 | 14 | #include "fossil/io/cstring.h" |
15 | 15 | #include "fossil/io/output.h" |
16 | | -#include <string.h> // For strlen, strnlen, strncasecmp |
17 | | -#include <strings.h> // For strncasecmp on POSIX |
| 16 | +#include <strings.h> |
18 | 17 | #include <stdlib.h> |
19 | | -#include <ctype.h> // For toupper, tolower |
| 18 | +#include <string.h> |
| 19 | +#include <locale.h> |
| 20 | +#include <ctype.h> |
20 | 21 | #include <time.h> |
| 22 | +#include <math.h> |
21 | 23 |
|
22 | 24 | #ifndef HAVE_STRNLEN |
23 | 25 | size_t strnlen(const char *s, size_t maxlen) { |
@@ -57,6 +59,191 @@ void fossil_io_cstring_free(cstring str) { |
57 | 59 | } |
58 | 60 | } |
59 | 61 |
|
| 62 | +// --------------------------------------- |
| 63 | +// Locale-Aware Money String Conversions |
| 64 | +// --------------------------------------- |
| 65 | + |
| 66 | +int fossil_io_cstring_money_to_string(double amount, char *output, size_t size) { |
| 67 | + if (!output || size == 0) return -1; |
| 68 | + |
| 69 | + // Set locale temporarily to the user's default locale |
| 70 | + char *old_locale = setlocale(LC_NUMERIC, NULL); |
| 71 | + setlocale(LC_NUMERIC, ""); |
| 72 | + |
| 73 | + amount = round(amount * 100.0) / 100.0; // Round to 2 decimals |
| 74 | + |
| 75 | + char temp[64]; |
| 76 | + int written = snprintf(temp, sizeof(temp), "%.2f", fabs(amount)); |
| 77 | + if (written < 0 || written >= (int)sizeof(temp)) return -1; |
| 78 | + |
| 79 | + // Determine locale decimal and thousand separators |
| 80 | + struct lconv *lc = localeconv(); |
| 81 | + char decimal_sep = lc && lc->decimal_point ? lc->decimal_point[0] : '.'; |
| 82 | + char thousand_sep = lc && lc->thousands_sep ? lc->thousands_sep[0] : ','; |
| 83 | + |
| 84 | + // Replace decimal point with locale decimal separator |
| 85 | + char *dot = strchr(temp, '.'); |
| 86 | + if (dot) *dot = decimal_sep; |
| 87 | + |
| 88 | + int int_len = dot ? (int)(dot - temp) : (int)strlen(temp); |
| 89 | + int commas = (int_len - 1) / 3; |
| 90 | + int total_len = int_len + commas + (dot ? strlen(dot) : 0); |
| 91 | + |
| 92 | + if ((size_t)(total_len + 3) > size) return -1; |
| 93 | + |
| 94 | + char formatted[128]; |
| 95 | + int fpos = 0; |
| 96 | + |
| 97 | + if (amount < 0) formatted[fpos++] = '-'; |
| 98 | + formatted[fpos++] = '$'; // Keep USD-style symbol |
| 99 | + |
| 100 | + int leading = int_len % 3; |
| 101 | + if (leading == 0) leading = 3; |
| 102 | + |
| 103 | + for (int i = 0; i < int_len; i++) { |
| 104 | + formatted[fpos++] = temp[i]; |
| 105 | + if ((i + 1) % leading == 0 && (i + 1) < int_len) { |
| 106 | + formatted[fpos++] = thousand_sep; |
| 107 | + leading = 3; |
| 108 | + } |
| 109 | + } |
| 110 | + |
| 111 | + if (dot) { |
| 112 | + strcpy(&formatted[fpos], dot); |
| 113 | + fpos += strlen(dot); |
| 114 | + } |
| 115 | + |
| 116 | + formatted[fpos] = '\0'; |
| 117 | + strncpy(output, formatted, size - 1); |
| 118 | + output[size - 1] = '\0'; |
| 119 | + |
| 120 | + // Restore previous locale |
| 121 | + setlocale(LC_NUMERIC, old_locale); |
| 122 | + |
| 123 | + return 0; |
| 124 | +} |
| 125 | + |
| 126 | +int fossil_io_cstring_string_to_money(const char *input, double *amount) { |
| 127 | + if (!input || !amount) return -1; |
| 128 | + |
| 129 | + char buffer[128]; |
| 130 | + size_t j = 0; |
| 131 | + int negative = 0; |
| 132 | + |
| 133 | + // Skip leading spaces |
| 134 | + while (isspace((unsigned char)*input)) input++; |
| 135 | + |
| 136 | + if (*input == '(') { |
| 137 | + negative = 1; |
| 138 | + input++; |
| 139 | + } |
| 140 | + |
| 141 | + struct lconv *lc = localeconv(); |
| 142 | + char decimal_sep = lc && lc->decimal_point ? lc->decimal_point[0] : '.'; |
| 143 | + |
| 144 | + // Copy digits and decimal separator only |
| 145 | + for (size_t i = 0; input[i] && j < sizeof(buffer) - 1; i++) { |
| 146 | + if (isdigit((unsigned char)input[i]) || input[i] == decimal_sep) { |
| 147 | + buffer[j++] = input[i]; |
| 148 | + } |
| 149 | + } |
| 150 | + buffer[j] = '\0'; |
| 151 | + |
| 152 | + if (j == 0) return -1; |
| 153 | + |
| 154 | + // Replace locale decimal with '.' for atof |
| 155 | + for (size_t i = 0; i < j; i++) { |
| 156 | + if (buffer[i] == decimal_sep) buffer[i] = '.'; |
| 157 | + } |
| 158 | + |
| 159 | + *amount = atof(buffer); |
| 160 | + if (negative || strchr(input, '-')) *amount = -*amount; |
| 161 | + |
| 162 | + return 0; |
| 163 | +} |
| 164 | + |
| 165 | +int fossil_io_cstring_money_to_string_currency(double amount, char *output, size_t size, const char *currency) { |
| 166 | + if (!output || size == 0) return -1; |
| 167 | + if (!currency) currency = "$"; |
| 168 | + |
| 169 | + amount = round(amount * 100.0) / 100.0; // Round to 2 decimals |
| 170 | + |
| 171 | + char temp[64]; |
| 172 | + int written = snprintf(temp, sizeof(temp), "%.2f", fabs(amount)); |
| 173 | + if (written < 0 || written >= (int)sizeof(temp)) return -1; |
| 174 | + |
| 175 | + // Replace decimal point with '.' |
| 176 | + char *dot = strchr(temp, '.'); |
| 177 | + int int_len = dot ? (int)(dot - temp) : (int)strlen(temp); |
| 178 | + int commas = (int_len - 1) / 3; |
| 179 | + int total_len = int_len + commas + (dot ? strlen(dot) : 0); |
| 180 | + |
| 181 | + if ((size_t)(total_len + strlen(currency) + 2) > size) return -1; |
| 182 | + |
| 183 | + char formatted[128]; |
| 184 | + int fpos = 0; |
| 185 | + |
| 186 | + if (amount < 0) formatted[fpos++] = '-'; |
| 187 | + strcpy(&formatted[fpos], currency); |
| 188 | + fpos += strlen(currency); |
| 189 | + |
| 190 | + int leading = int_len % 3; |
| 191 | + if (leading == 0) leading = 3; |
| 192 | + |
| 193 | + for (int i = 0; i < int_len; i++) { |
| 194 | + formatted[fpos++] = temp[i]; |
| 195 | + if ((i + 1) % leading == 0 && (i + 1) < int_len) { |
| 196 | + formatted[fpos++] = ','; |
| 197 | + leading = 3; |
| 198 | + } |
| 199 | + } |
| 200 | + |
| 201 | + if (dot) { |
| 202 | + strcpy(&formatted[fpos], dot); |
| 203 | + fpos += strlen(dot); |
| 204 | + } |
| 205 | + |
| 206 | + formatted[fpos] = '\0'; |
| 207 | + strncpy(output, formatted, size - 1); |
| 208 | + output[size - 1] = '\0'; |
| 209 | + |
| 210 | + return 0; |
| 211 | +} |
| 212 | + |
| 213 | +int fossil_io_cstring_string_to_money_currency(const char *input, double *amount) { |
| 214 | + if (!input || !amount) return -1; |
| 215 | + |
| 216 | + char buffer[128]; |
| 217 | + size_t j = 0; |
| 218 | + int negative = 0; |
| 219 | + |
| 220 | + while (isspace((unsigned char)*input)) input++; |
| 221 | + |
| 222 | + if (*input == '(') { |
| 223 | + negative = 1; |
| 224 | + input++; |
| 225 | + } |
| 226 | + |
| 227 | + if (!isdigit((unsigned char)*input) && *input != '-' && *input != '.') { |
| 228 | + // Skip currency symbol |
| 229 | + input++; |
| 230 | + } |
| 231 | + |
| 232 | + for (size_t i = 0; input[i] && j < sizeof(buffer) - 1; i++) { |
| 233 | + if (isdigit((unsigned char)input[i]) || input[i] == '.') { |
| 234 | + buffer[j++] = input[i]; |
| 235 | + } |
| 236 | + } |
| 237 | + buffer[j] = '\0'; |
| 238 | + |
| 239 | + if (j == 0) return -1; |
| 240 | + |
| 241 | + *amount = atof(buffer); |
| 242 | + if (negative || strchr(input, '-')) *amount = -*amount; |
| 243 | + |
| 244 | + return 0; |
| 245 | +} |
| 246 | + |
60 | 247 | // ---------------- Tokenizer ---------------- |
61 | 248 | cstring fossil_io_cstring_token(cstring str, ccstring delim, cstring *saveptr) { |
62 | 249 | if (!saveptr || (!str && !*saveptr)) return NULL; |
|
0 commit comments