1 /** 2 * Locale information routines. 3 */ 4 module dlocale; 5 6 import std.datetime; 7 import core.stdc.time; 8 import core.stdc.locale; 9 import std.conv; 10 import std..string; 11 import std.process : environment; 12 import std.math : abs; 13 14 version(Windows) { 15 import core.sys.windows.windows; 16 import core.sys.windows.winnls; 17 import std.algorithm : min; 18 import std..string : strip; 19 } 20 21 /// Info about locale. 22 struct Locale 23 { 24 /// Locale name. For example, ru-RU. 25 string name; 26 27 /// Code set 28 string codeSet; 29 30 // Date/time related block 31 32 /// Era 33 string era; 34 /// Full week's day name, starting with Sunday. 35 string[7] weekDays; 36 /// Abbreviated week's day name, starting with Sun. 37 string[7] abbrWeekDays; 38 39 /// Full month name. 40 string[12] monthes; 41 /// Abbreviated month name. 42 string[12] abbrMonthes; 43 44 /// AM and PM values, if any 45 string am, pm; 46 /// String that can be used as a format string to represent time and date in a locale-specific way. 47 string dateTime; 48 /// String that can be used as a format string to represent date in a locale-specific way. 49 string date; 50 /// String that can be used as a format string to represent time in a locale-specific way. 51 string time; 52 53 // Numeric related block 54 55 /// Decimal point character (decimal dot, decimal comma, etc.). 56 string decimalPoint; 57 /// Separator character for thousands (groups of three digits). 58 string thousandSeparator; 59 60 // Monetary 61 62 /// Currency symbol in native format. 63 string currency; 64 /// Currency symbol in international format. 65 string intCurrency; 66 /// Currency native place for positive amounts 1 - symbol precedes value, 0 - follows it. 67 byte currencyPositivePlace; 68 /// Currency native place for negative amounts 1 - symbol precedes value, 0 - follows it. 69 byte currencyNegativePlace; 70 /// Currency international place for positive amounts 1 - symbol precedes value, 0 - follows it. 71 byte intCurrencyPositivePlace; 72 /// Currency international place for negative amounts 1 - symbol precedes value, 0 - follows it. 73 byte intCurrencyNegativePlace; 74 /// Native digits after decimal place for money. 75 byte fracDigits; 76 /// International digits after decimal place for money. 77 byte intFracDigits; 78 79 string toString() { return name; } 80 } 81 82 /** 83 * Sets default locale for library. Automatically loads locale info for future use. 84 * Params: 85 * localeName = Locale system name. For example, C.UTF-8 or u_RU.UTF-8. 86 */ 87 export void setDefaultLocale(string localeName) 88 { 89 defaultLocale = initDateformatLocale(localeName); 90 } 91 92 /** 93 * Locale for library. 94 * Params: 95 * localeName = Locale system name. For example, C.UTF-8 or Ru_RU.UTF-8. 96 * Returns: Locale object with locale info. 97 */ 98 export Locale initDateformatLocale(string localeName) 99 { 100 // Need collect information or it already collected? 101 auto plocale = localeName in locales; 102 if (plocale != null) 103 return *plocale; 104 105 Locale locale; 106 locale.name = localeName; 107 108 // Setting current locale for C routines. 109 setlocale(LC_ALL, localeName.toStringz); 110 111 auto moment = DateTime(2021, 1, 3, 0, 0, 0); 112 113 // AM and PM words 114 locale.am = _format(moment, "%p"); 115 moment += hours(15); 116 locale.pm = _format(moment, "%p"); 117 118 version(Posix) 119 { 120 import cheaders.langinfo; 121 122 /// Get item from locale definition. 123 string discover(nl_item what) 124 { 125 return to!string(nl_langinfo(what)); 126 } 127 128 /// Fills locale info into array 129 void fillArray(string[] array, nl_item start, nl_item end) 130 { 131 for (auto item = start; item <= end; item++) 132 { 133 auto index = item - start; 134 array[index] = discover(item); 135 } 136 } 137 138 locale.codeSet = discover(nl_item.CODESET); 139 140 // Date, time and both locale specific formats. 141 locale.era = discover(nl_item.ERA); 142 locale.dateTime = discover(nl_item.D_T_FMT); 143 locale.date = discover(nl_item.D_FMT); 144 locale.time = discover(nl_item.T_FMT); 145 146 // Days of week 147 fillArray(locale.weekDays, nl_item.DAY_1, nl_item.DAY_7); 148 fillArray(locale.abbrWeekDays, nl_item.ABDAY_1, nl_item.ABDAY_7); 149 150 // Monthes 151 fillArray(locale.monthes, nl_item.MON_1, nl_item.MON_12); 152 fillArray(locale.abbrMonthes, nl_item.ABMON_1, nl_item.ABMON_12); 153 154 // Numeric 155 locale.decimalPoint = discover(nl_item.__DECIMAL_POINT); 156 locale.thousandSeparator = discover(nl_item.__THOUSANDS_SEP); 157 158 // Currency 159 locale.currency = discover(nl_item.__CURRENCY_SYMBOL).strip; 160 locale.intCurrency = discover(nl_item.__INT_CURR_SYMBOL).strip; 161 auto st = discover(nl_item.__N_CS_PRECEDES); 162 locale.currencyPositivePlace = st.length > 0 ? abs(cast(byte)st[0]) : 0; 163 st = discover(nl_item.__P_CS_PRECEDES); 164 locale.currencyNegativePlace = st.length > 0 ? abs(cast(byte)st[0]) : 0; 165 166 st = discover(nl_item.__INT_N_CS_PRECEDES); 167 locale.intCurrencyPositivePlace = st.length > 0 ? abs(cast(byte)st[0]) : 0; 168 st = discover(nl_item.__INT_P_CS_PRECEDES); 169 locale.intCurrencyNegativePlace = st.length > 0 ? abs(cast(byte)st[0]) : 0; 170 st = discover(nl_item.__INT_FRAC_DIGITS); 171 auto b = cast(byte)st[0]; 172 locale.intFracDigits = st.length > 0 ? (b < 1 ? 0 : b) : 0; 173 st = discover(nl_item.__FRAC_DIGITS); 174 b = cast(byte)st[0]; 175 locale.fracDigits = st.length > 0 ? (b < 1 ? 0 : b) : 0; 176 } 177 else 178 version(Windows) 179 { 180 // Using Windows API to collect info 181 auto LOCALE_SSHORTTIME = 0x00000079; 182 183 LCID lcid = windowsLocales.get(localeName, LOCALE_USER_DEFAULT); 184 185 locale.date = getAndConvertFormat(lcid, LOCALE_SSHORTDATE); 186 locale.time = getAndConvertFormat(lcid, LOCALE_SSHORTTIME); 187 locale.dateTime = getAndConvertFormat(lcid, LOCALE_SLONGDATE) 188 ~ " " 189 ~ getAndConvertFormat(lcid, LOCALE_SSHORTTIME); 190 191 // Week days 192 for (auto wday = LOCALE_SDAYNAME1; wday < LOCALE_SDAYNAME7; wday++) 193 locale.weekDays[wday - LOCALE_SDAYNAME1 + 1] = discover(lcid, wday); 194 195 locale.weekDays[0] = discover(lcid, LOCALE_SDAYNAME7); 196 197 for (auto wday = LOCALE_SABBREVDAYNAME1; wday < LOCALE_SABBREVDAYNAME7; wday++) 198 locale.abbrWeekDays[wday - LOCALE_SABBREVDAYNAME1 + 1] = discover(lcid, wday); 199 200 locale.abbrWeekDays[0] = discover(lcid, LOCALE_SABBREVDAYNAME7); 201 202 // Monthes 203 for (auto wmon = LOCALE_SMONTHNAME1; wmon <= LOCALE_SMONTHNAME12; wmon++) 204 locale.monthes[wmon - LOCALE_SMONTHNAME1] = discover(lcid, wmon); 205 206 for (auto wmon = LOCALE_SABBREVMONTHNAME1; wmon <= LOCALE_SABBREVMONTHNAME12; wmon++) 207 locale.abbrMonthes[wmon - LOCALE_SABBREVMONTHNAME1] = discover(lcid, wmon); 208 209 locale.codeSet = discover(lcid, LOCALE_IDEFAULTCODEPAGE); 210 211 // TODO Locale ERA 212 213 locale.decimalPoint = discover(lcid, LOCALE_SDECIMAL); 214 locale.thousandSeparator = discover(lcid, LOCALE_STHOUSAND); 215 locale.currency = discover(lcid, LOCALE_SCURRENCY); 216 immutable auto posCurrency = to!int(discover(lcid, LOCALE_ICURRENCY)); 217 locale.currencyPositivePlace = (posCurrency == 0 || posCurrency == 2) ? 1 : 0; 218 locale.currencyNegativePlace = (posCurrency == 0 || posCurrency == 2) ? 1 : 0; 219 locale.fracDigits = to!byte(discover(lcid, LOCALE_ICURRDIGITS)); 220 221 locale.intCurrency = locale.currency; 222 locale.intCurrencyPositivePlace = locale.currencyPositivePlace; 223 locale.intCurrencyNegativePlace = locale.currencyNegativePlace; 224 locale.intFracDigits = to!byte(discover(lcid, LOCALE_ICURRDIGITS)); 225 } 226 else // Unknown OS. Use standard D library to determine parameters. 227 { 228 determineLocaleData(locale); 229 230 locale.dateTime = "%Y-%m-%dT%H:%M:%S"; 231 locale.date = "%Y-%m-%d"; 232 locale.time = "%H:%M:%S"; 233 } 234 235 // Remember locale settings 236 locales[localeName] = locale; 237 238 return locale; 239 } 240 241 private immutable auto BUF_SIZE = 127; 242 243 /// Using standard C strftime for unknown systems. 244 private string _format(DateTime date, string formatString) 245 { 246 char[] buf = new char[BUF_SIZE]; 247 char* p = &(buf[0]); 248 auto tm = (cast(SysTime)date).toTM; 249 strftime(p, BUF_SIZE, formatString.toStringz, &tm); 250 251 return to!string(fromStringz(p)); 252 } 253 254 /// Determine some parameters for specified locale 255 private void determineLocaleData(Locale locale) 256 { 257 auto moment = DateTime(2021, 1, 3, 0, 0, 0); 258 do 259 { 260 locale.weekDays[moment.dayOfWeek] = _format(moment, "%A"); 261 locale.abbrWeekDays[moment.dayOfWeek] = _format(moment, "%a"); 262 263 moment += days(1); 264 265 } while (moment.dayOfWeek != DayOfWeek.sun); 266 267 do 268 { 269 auto curMonth = moment.month - 1; 270 locale.monthes[curMonth] = _format(moment, "%B"); 271 locale.abbrMonthes[curMonth] =_format(moment, "%b"); 272 273 moment.add!"months"(1); 274 } while (moment.year == 2021); 275 } 276 277 /// Known locales 278 private Locale[string] locales; 279 /// Default locale. Autodetect language. 280 private Locale defaultLocale; 281 version(Windows) 282 { 283 LCID[string] windowsLocales; 284 } 285 286 /// Loading default locale. 287 static this() 288 { 289 version(Posix) 290 { 291 setDefaultLocale(environment.get("LANG", "C")); 292 } 293 version(Windows) 294 { 295 windowsLocales = ["aa": 0x1000, "aa-DJ": 0x1000, "aa-ER": 0x1000, "aa-ET": 0x1000, "af": 0x0036, "af-NA": 0x1000, 296 "af-ZA": 0x0436, "agq": 0x1000, "agq-CM": 0x1000, "ak": 0x1000, "ak-GH": 0x1000, "sq": 0x001C, 297 "sq-AL": 0x041C, "sq-MK": 0x1000, "gsw": 0x0084, "gsw-FR": 0x0484, "gsw-LI": 0x1000, "gsw-CH": 0x1000, 298 "am": 0x005E, "am-ET": 0x045E, "ar": 0x0001, "ar-DZ": 0x1401, "ar-BH": 0x3C01, "ar-TD": 0x1000, 299 "ar-KM": 0x1000, "ar-DJ": 0x1000, "ar-EG": 0x0c01, "ar-ER": 0x1000, "ar-IQ": 0x0801, "ar-IL": 0x1000, 300 "ar-JO": 0x2C01, "ar-KW": 0x3401, "ar-LB": 0x3001, "ar-LY": 0x1001, "ar-MR": 0x1000, "ar-MA": 0x1801, 301 "ar-OM": 0x2001, "ar-PS": 0x1000, "ar-QA": 0x4001, "ar-SA": 0x0401, "ar-SO": 0x1000, "ar-SS": 0x1000, 302 "ar-SD": 0x1000, "ar-SY": 0x2801, "ar-TN": 0x1C01, "ar-AE": 0x3801, "ar-001": 0x1000, "ar-YE": 0x2401, 303 "hy": 0x002B, "hy-AM": 0x042B, "as": 0x004D, "as-IN": 0x044D, "ast": 0x1000, "ast-ES": 0x1000, 304 "asa": 0x1000, "asa-TZ": 0x1000, "az-Cyrl": 0x742C, "az-Cyrl-AZ": 0x082C, "az": 0x002C, "az-Latn": 0x782C, 305 "az-Latn-AZ": 0x042C, "ksf": 0x1000, "ksf-CM": 0x1000, "bm": 0x1000, "bm-Latn-ML": 0x1000, "bn": 0x0045, 306 "bn-BD": 0x0845, "bn-IN": 0x0445, "bas": 0x1000, "bas-CM": 0x1000, "ba": 0x006D, "ba-RU": 0x046D, 307 "eu": 0x002D, "eu-ES": 0x042D, "be": 0x0023, "be-BY": 0x0423, "bem": 0x1000, "bem-ZM": 0x1000, 308 "bez": 0x1000, "bez-TZ": 0x1000, "byn": 0x1000, "byn-ER": 0x1000, "brx": 0x1000, "brx-IN": 0x1000, 309 "bs-Cyrl": 0x641A, "bs-Cyrl-BA": 0x201A, "bs-Latn": 0x681A, "bs": 0x781A, "bs-Latn-BA": 0x141A, "br": 0x007E, 310 "br-FR": 0x047E, "bg": 0x0002, "bg-BG": 0x0402, "my": 0x0055, "my-MM": 0x0455, "ca": 0x0003, 311 "ca-AD": 0x1000, "ca-FR": 0x1000, "ca-IT": 0x1000, "ca-ES": 0x0403, "ceb": 0x1000, "ceb-Latn": 0x1000, 312 "ceb-Latn-PH": 0x1000, "tzm-Latn-MA": 0x1000, "ku": 0x0092, "ku-Arab": 0x7c92, "ku-Arab-IQ": 0x0492, "ccp": 0x1000, 313 "ccp-Cakm": 0x1000, "ccp-Cakm-BD": 0x1000, "ccp-Cakm-IN": 0x1000, "cd-RU": 0x1000, "chr": 0x005C, "chr-Cher": 0x7c5C, 314 "chr-Cher-US": 0x045C, "cgg": 0x1000, "cgg-UG": 0x1000, "zh-Hans": 0x0004, "zh": 0x7804, "zh-CN": 0x0804, 315 "zh-SG": 0x1004, "zh-Hant": 0x7C04, "zh-HK": 0x0C04, "zh-MO": 0x1404, "zh-TW": 0x0404, "cu-RU": 0x1000, 316 "swc": 0x1000, "swc-CD": 0x1000, "kw": 0x1000, "kw-GB": 0x1000, "co": 0x0083, "co-FR": 0x0483, 317 "hr,": 0x001A, "hr-HR": 0x041A, "hr-BA": 0x101A, "cs": 0x0005, "cs-CZ": 0x0405, "da": 0x0006, 318 "da-DK": 0x0406, "da-GL": 0x1000, "prs": 0x008C, "prs-AF": 0x048C, "dv": 0x0065, "dv-MV": 0x0465, 319 "dua": 0x1000, "dua-CM": 0x1000, "nl": 0x0013, "nl-AW": 0x1000, "nl-BE": 0x0813, "nl-BQ": 0x1000, 320 "nl-CW": 0x1000, "nl-NL": 0x0413, "nl-SX": 0x1000, "nl-SR": 0x1000, "dz": 0x1000, "dz-BT": 0x0C51, 321 "ebu": 0x1000, "ebu-KE": 0x1000, "en": 0x0009, "en-AS": 0x1000, "en-AI": 0x1000, "en-AG": 0x1000, 322 "en-AU": 0x0C09, "en-AT": 0x1000, "en-BS": 0x1000, "en-BB": 0x1000, "en-BE": 0x1000, "en-BZ": 0x2809, 323 "en-BM": 0x1000, "en-BW": 0x1000, "en-IO": 0x1000, "en-VG": 0x1000, "en-BI": 0x1000, "en-CM": 0x1000, 324 "en-CA": 0x1009, "en-029": 0x2409, "en-KY": 0x1000, "en-CX": 0x1000, "en-CC": 0x1000, "en-CK": 0x1000, 325 "en-CY": 0x1000, "en-DK": 0x1000, "en-DM": 0x1000, "en-ER": 0x1000, "en-150": 0x1000, "en-FK": 0x1000, 326 "en-FI": 0x1000, "en-FJ": 0x1000, "en-GM": 0x1000, "en-DE": 0x1000, "en-GH": 0x1000, "en-GI": 0x1000, 327 "en-GD": 0x1000, "en-GU": 0x1000, "en-GG": 0x1000, "en-GY": 0x1000, "en-HK": 0x3C09, "en-IN": 0x4009, 328 "en-IE": 0x1809, "en-IM": 0x1000, "en-IL": 0x1000, "en-JM": 0x2009, "en-JE": 0x1000, "en-KE": 0x1000, 329 "en-KI": 0x1000, "en-LS": 0x1000, "en-LR": 0x1000, "en-MO": 0x1000, "en-MG": 0x1000, "en-MW": 0x1000, 330 "en-MY": 0x4409, "en-MT": 0x1000, "en-MH": 0x1000, "en-MU": 0x1000, "en-FM": 0x1000, "en-MS": 0x1000, 331 "en-NA": 0x1000, "en-NR": 0x1000, "en-NL": 0x1000, "en-NZ": 0x1409, "en-NG": 0x1000, "en-NU": 0x1000, 332 "en-NF": 0x1000, "en-MP": 0x1000, "en-PK": 0x1000, "en-PW": 0x1000, "en-PG": 0x1000, "en-PN": 0x1000, 333 "en-PR": 0x1000, "en-PH": 0x3409, "en-RW": 0x1000, "en-KN": 0x1000, "en-LC": 0x1000, "en-VC": 0x1000, 334 "en-WS": 0x1000, "en-SC": 0x1000, "en-SL": 0x1000, "en-SG": 0x4809, "en-SX": 0x1000, "en-SI": 0x1000, 335 "en-SB": 0x1000, "en-ZA": 0x1C09, "en-SS": 0x1000, "en-SH": 0x1000, "en-SD": 0x1000, "en-SZ": 0x1000, 336 "en-SE": 0x1000, "en-CH": 0x1000, "en-TZ": 0x1000, "en-TK": 0x1000, "en-TO": 0x1000, "en-TT": 0x2c09, 337 "en-TC": 0x1000, "en-TV": 0x1000, "en-UG": 0x1000, "en-AE": 0x4C09, "en-GB": 0x0809, "en-US": 0x0409, 338 "en-UM": 0x1000, "en-VI": 0x1000, "en-VU": 0x1000, "en-001": 0x1000, "en-ZM": 0x1000, "en-ZW": 0x3009, 339 "eo": 0x1000, "eo-001": 0x1000, "et": 0x0025, "et-EE": 0x0425, "ee": 0x1000, "ee-GH": 0x1000, 340 "ee-TG": 0x1000, "ewo": 0x1000, "ewo-CM": 0x1000, "fo": 0x0038, "fo-DK": 0x1000, "fo-FO": 0x0438, 341 "fil": 0x0064, "fil-PH": 0x0464, "fi": 0x000B, "fi-FI": 0x040B, "fr": 0x000C, "fr-DZ": 0x1000, 342 "fr-BE": 0x080C, "fr-BJ": 0x1000, "fr-BF": 0x1000, "fr-BI": 0x1000, "fr-CM": 0x2c0C, "fr-CA": 0x0c0C, 343 "fr-CF": 0x1000, "fr-TD": 0x1000, "fr-KM": 0x1000, "fr-CG": 0x1000, "fr-CD": 0x240C, "fr-CI": 0x300C, 344 "fr-DJ": 0x1000, "fr-GQ": 0x1000, "fr-FR": 0x040C, "fr-GF": 0x1000, "fr-PF": 0x1000, "fr-GA": 0x1000, 345 "fr-GP": 0x1000, "fr-GN": 0x1000, "fr-HT": 0x3c0C, "fr-LU": 0x140C, "fr-MG": 0x1000, "fr-ML": 0x340C, 346 "fr-MQ": 0x1000, "fr-MR": 0x1000, "fr-MU": 0x1000, "fr-YT": 0x1000, "fr-MA": 0x380C, "fr-NC": 0x1000, 347 "fr-NE": 0x1000, "fr-MC": 0x180C, "fr-RE": 0x200C, "fr-RW": 0x1000, "fr-BL": 0x1000, "fr-MF": 0x1000, 348 "fr-PM": 0x1000, "fr-SN": 0x280C, "fr-SC": 0x1000, "fr-CH": 0x100C, "fr-SY": 0x1000, "fr-TG": 0x1000, 349 "fr-TN": 0x1000, "fr-VU": 0x1000, "fr-WF": 0x1000, "fy": 0x0062, "fy-NL": 0x0462, "fur": 0x1000, 350 "fur-IT": 0x1000, "ff": 0x0067, "ff-Latn": 0x7C67, "ff-Latn-BF": 0x1000, "ff-CM": 0x1000, "ff-Latn-CM": 0x1000, 351 "ff-Latn-GM": 0x1000, "ff-Latn-GH": 0x1000, "ff-GN": 0x1000, "ff-Latn-GN": 0x1000, "ff-Latn-GW": 0x1000, "ff-Latn-LR": 0x1000, 352 "ff-MR": 0x1000, "ff-Latn-MR": 0x1000, "ff-Latn-NE": 0x1000, "ff-NG": 0x1000, "ff-Latn-NG": 0x1000, "ff-Latn-SN": 0x0867, 353 "ff-Latn-SL": 0x1000, "gl": 0x0056, "gl-ES": 0x0456, "lg": 0x1000, "lg-UG": 0x1000, "ka": 0x0037, 354 "ka-GE": 0x0437, "de": 0x0007, "de-AT": 0x0C07, "de-BE": 0x1000, "de-DE": 0x0407, "de-IT": 0x1000, 355 "de-LI": 0x1407, "de-LU": 0x1007, "de-CH": 0x0807, "el": 0x0008, "el-CY": 0x1000, "el-GR": 0x0408, 356 "kl": 0x006F, "kl-GL": 0x046F, "gn": 0x0074, "gn-PY": 0x0474, "gu": 0x0047, "gu-IN": 0x0447, 357 "guz": 0x1000, "guz-KE": 0x1000, "ha": 0x0068, "ha-Latn": 0x7C68, "ha-Latn-GH": 0x1000, "ha-Latn-NE": 0x1000, 358 "ha-Latn-NG": 0x0468, "haw": 0x0075, "haw-US": 0x0475, "he": 0x000D, "he-IL": 0x040D, "hi": 0x0039, 359 "hi-IN": 0x0439, "hu": 0x000E, "hu-HU": 0x040E, "is": 0x000F, "is-IS": 0x040F, "ig": 0x0070, 360 "ig-NG": 0x0470, "id": 0x0021, "id-ID": 0x0421, "ia": 0x1000, "ia-FR": 0x1000, "ia-001": 0x1000, 361 "iu": 0x005D, "iu-Latn": 0x7C5D, "iu-Latn-CA": 0x085D, "iu-Cans": 0x785D, "iu-Cans-CA": 0x045d, "ga": 0x003C, 362 "ga-IE": 0x083C, "it": 0x0010, "it-IT": 0x0410, "it-SM": 0x1000, "it-CH": 0x0810, "it-VA": 0x1000, 363 "ja": 0x0011, "ja-JP": 0x0411, "jv": 0x1000, "jv-Latn": 0x1000, "jv-Latn-ID": 0x1000, "dyo": 0x1000, 364 "dyo-SN": 0x1000, "kea": 0x1000, "kea-CV": 0x1000, "kab": 0x1000, "kab-DZ": 0x1000, "kkj": 0x1000, 365 "kkj-CM": 0x1000, "kln": 0x1000, "kln-KE": 0x1000, "kam": 0x1000, "kam-KE": 0x1000, "kn": 0x004B, 366 "kn-IN": 0x044B, "ks": 0x0060, "ks-Arab": 0x0460, "ks-Arab-IN": 0x1000, "kk": 0x003F, "kk-KZ": 0x043F, 367 "km": 0x0053, "km-KH": 0x0453, "quc": 0x0086, "quc-Latn-GT": 0x0486, "ki": 0x1000, "ki-KE": 0x1000, 368 "rw": 0x0087, "rw-RW": 0x0487, "sw": 0x0041, "sw-KE": 0x0441, "sw-TZ": 0x1000, "sw-UG": 0x1000, 369 "kok": 0x0057, "kok-IN": 0x0457, "ko": 0x0012, "ko-KR": 0x0412, "ko-KP": 0x1000, "khq": 0x1000, 370 "khq-ML": 0x1000, "ses": 0x1000, "ses-ML": 0x1000, "nmg": 0x1000, "nmg-CM": 0x1000, "ky": 0x0040, 371 "ky-KG": 0x0440, "ku-Arab-IR": 0x1000, "lkt": 0x1000, "lkt-US": 0x1000, "lag": 0x1000, "lag-TZ": 0x1000, 372 "lo": 0x0054, "lo-LA": 0x0454, "lv": 0x0026, "lv-LV": 0x0426, "ln": 0x1000, "ln-AO": 0x1000, 373 "ln-CF": 0x1000, "ln-CG": 0x1000, "ln-CD": 0x1000, "lt": 0x0027, "lt-LT": 0x0427, "nds": 0x1000, 374 "nds-DE": 0x1000, "nds-NL": 0x1000, "dsb": 0x7C2E, "dsb-DE": 0x082E, "lu": 0x1000, "lu-CD": 0x1000, 375 "luo": 0x1000, "luo-KE": 0x1000, "lb": 0x006E, "lb-LU": 0x046E, "luy": 0x1000, "luy-KE": 0x1000, 376 "mk": 0x002F, "mk-MK": 0x042F, "jmc": 0x1000, "jmc-TZ": 0x1000, "mgh": 0x1000, "mgh-MZ": 0x1000, 377 "kde": 0x1000, "kde-TZ": 0x1000, "mg": 0x1000, "mg-MG": 0x1000, "ms": 0x003E, "ms-BN": 0x083E, 378 "ms-MY": 0x043E, "ml": 0x004C, "ml-IN": 0x044C, "mt": 0x003A, "mt-MT": 0x043A, "gv": 0x1000, 379 "gv-IM": 0x1000, "mi": 0x0081, "mi-NZ": 0x0481, "arn": 0x007A, "arn-CL": 0x047A, "mr": 0x004E, 380 "mr-IN": 0x044E, "mas": 0x1000, "mas-KE": 0x1000, "mas-TZ": 0x1000, "mzn-IR": 0x1000, "mer": 0x1000, 381 "mer-KE": 0x1000, "mgo": 0x1000, "mgo-CM": 0x1000, "moh": 0x007C, "moh-CA": 0x047C, "mn": 0x0050, 382 "mn-Cyrl": 0x7850, "mn-MN": 0x0450, "mn-Mong": 0x7C50, "mn-Mong-CN": 0x0850, "mn-Mong-MN": 0x0C50, "mfe": 0x1000, 383 "mfe-MU": 0x1000, "mua": 0x1000, "mua-CM": 0x1000, "nqo": 0x1000, "nqo-GN": 0x1000, "naq": 0x1000, 384 "naq-NA": 0x1000, "ne": 0x0061, "ne-IN": 0x0861, "ne-NP": 0x0461, "nnh": 0x1000, "nnh-CM": 0x1000, 385 "jgo": 0x1000, "jgo-CM": 0x1000, "lrc-IQ": 0x1000, "lrc-IR": 0x1000, "nd": 0x1000, "nd-ZW": 0x1000, 386 "no": 0x0014, "nb": 0x7C14, "nb-NO": 0x0414, "nn": 0x7814, "nn-NO": 0x0814, "nb-SJ": 0x1000, 387 "nus": 0x1000, "nus-SD": 0x1000, "nus-SS": 0x1000, "nyn": 0x1000, "nyn-UG": 0x1000, "oc": 0x0082, 388 "oc-FR": 0x0482, "or": 0x0048, "or-IN": 0x0448, "om": 0x0072, "om-ET": 0x0472, "om-KE": 0x1000, 389 "os": 0x1000, "os-GE": 0x1000, "os-RU": 0x1000, "ps": 0x0063, "ps-AF": 0x0463, "ps-PK": 0x1000, 390 "fa": 0x0029, "fa-AF": 0x1000, "fa-IR": 0x0429, "pl": 0x0015, "pl-PL": 0x0415, "pt": 0x0016, 391 "pt-AO": 0x1000, "pt-BR": 0x0416, "pt-CV": 0x1000, "pt-GQ": 0x1000, "pt-GW": 0x1000, "pt-LU": 0x1000, 392 "pt-MO": 0x1000, "pt-MZ": 0x1000, "pt-PT": 0x0816, "pt-ST": 0x1000, "pt-CH": 0x1000, "pt-TL": 0x1000, 393 "prg-001": 0x1000, "qps-ploca": 0x05FE, "qps-ploc": 0x0501, "qps-plocm": 0x09FF, "pa": 0x0046, "pa-Arab": 0x7C46, 394 "pa-IN": 0x0446, "pa-Arab-PK": 0x0846, "quz": 0x006B, "quz-BO": 0x046B, "quz-EC": 0x086B, "quz-PE": 0x0C6B, 395 "ksh": 0x1000, "ksh-DE": 0x1000, "ro": 0x0018, "ro-MD": 0x0818, "ro-RO": 0x0418, "rm": 0x0017, 396 "rm-CH": 0x0417, "rof": 0x1000, "rof-TZ": 0x1000, "rn": 0x1000, "rn-BI": 0x1000, "ru": 0x0019, 397 "ru-BY": 0x1000, "ru-KZ": 0x1000, "ru-KG": 0x1000, "ru-MD": 0x0819, "ru-RU": 0x0419, "ru-UA": 0x1000, 398 "rwk": 0x1000, "rwk-TZ": 0x1000, "ssy": 0x1000, "ssy-ER": 0x1000, "sah": 0x0085, "sah-RU": 0x0485, 399 "saq": 0x1000, "saq-KE": 0x1000, "smn": 0x703B, "smn-FI": 0x243B, "smj": 0x7C3B, "smj-NO": 0x103B, 400 "smj-SE": 0x143B, "se": 0x003B, "se-FI": 0x0C3B, "se-NO": 0x043B, "se-SE": 0x083B, "sms": 0x743B, 401 "sms-FI": 0x203B, "sma": 0x783B, "sma-NO": 0x183B, "sma-SE": 0x1C3B, "sg": 0x1000, "sg-CF": 0x1000, 402 "sbp": 0x1000, "sbp-TZ": 0x1000, "sa": 0x004F, "sa-IN": 0x044F, "gd": 0x0091, "gd-GB": 0x0491, 403 "seh": 0x1000, "seh-MZ": 0x1000, "sr-Cyrl": 0x6C1A, "sr-Cyrl-BA": 0x1C1A, "sr-Cyrl-ME": 0x301A, "sr-Cyrl-RS": 0x281A, 404 "sr-Cyrl-CS": 0x0C1A, "sr-Latn": 0x701A, "sr": 0x7C1A, "sr-Latn-BA": 0x181A, "sr-Latn-ME": 0x2c1A, "sr-Latn-RS": 0x241A, 405 "sr-Latn-CS": 0x081A, "nso": 0x006C, "nso-ZA": 0x046C, "tn": 0x0032, "tn-BW": 0x0832, "tn-ZA": 0x0432, 406 "ksb": 0x1000, "ksb-TZ": 0x1000, "sn": 0x1000, "sn-Latn": 0x1000, "sn-Latn-ZW": 0x1000, "sd": 0x0059, 407 "sd-Arab": 0x7C59, "sd-Arab-PK": 0x0859, "si": 0x005B, "si-LK": 0x045B, "sk": 0x001B, "sk-SK": 0x041B, 408 "sl": 0x0024, "sl-SI": 0x0424, "xog": 0x1000, "xog-UG": 0x1000, "so": 0x0077, "so-DJ": 0x1000, 409 "so-ET": 0x1000, "so-KE": 0x1000, "so-SO": 0x0477, "st": 0x0030, "st-ZA": 0x0430, "nr": 0x1000, 410 "nr-ZA": 0x1000, "st-LS": 0x1000, "es": 0x000A, "es-AR": 0x2C0A, "es-BZ": 0x1000, "es-VE": 0x200A, 411 "es-BO": 0x400A, "es-BR": 0x1000, "es-CL": 0x340A, "es-CO": 0x240A, "es-CR": 0x140A, "es-CU": 0x5c0A, 412 "es-DO": 0x1c0A, "es-EC": 0x300A, "es-SV": 0x440A, "es-GQ": 0x1000, "es-GT": 0x100A, "es-HN": 0x480A, 413 "es-419": 0x580A, "es-MX": 0x080A, "es-NI": 0x4C0A, "es-PA": 0x180A, "es-PY": 0x3C0A, "es-PE": 0x280A, 414 "es-PH": 0x1000, "es-PR": 0x500A, "es-ES_tradnl": 0x040A, "es-ES": 0x0c0A, "es-US": 0x540A, "es-UY": 0x380A, 415 "zgh": 0x1000, "zgh-Tfng-MA": 0x1000, "zgh-Tfng": 0x1000, "ss": 0x1000, "ss-ZA": 0x1000, "ss-SZ": 0x1000, 416 "sv": 0x001D, "sv-AX": 0x1000, "sv-FI": 0x081D, "sv-SE": 0x041D, "syr": 0x005A, "syr-SY": 0x045A, 417 "shi": 0x1000, "shi-Tfng": 0x1000, "shi-Tfng-MA": 0x1000, "shi-Latn": 0x1000, "shi-Latn-MA": 0x1000, "dav": 0x1000, 418 "dav-KE": 0x1000, "tg": 0x0028, "tg-Cyrl": 0x7C28, "tg-Cyrl-TJ": 0x0428, "tzm": 0x005F, "tzm-Latn": 0x7C5F, 419 "tzm-Latn-DZ": 0x085F, "ta": 0x0049, "ta-IN": 0x0449, "ta-MY": 0x1000, "ta-SG": 0x1000, "ta-LK": 0x0849, 420 "twq": 0x1000, "twq-NE": 0x1000, "tt": 0x0044, "tt-RU": 0x0444, "te": 0x004A, "te-IN": 0x044A, 421 "teo": 0x1000, "teo-KE": 0x1000, "teo-UG": 0x1000, "th": 0x001E, "th-TH": 0x041E, "bo": 0x0051, 422 "bo-IN": 0x1000, "bo-CN": 0x0451, "tig": 0x1000, "tig-ER": 0x1000, "ti": 0x0073, "ti-ER": 0x0873, 423 "ti-ET": 0x0473, "to": 0x1000, "to-TO": 0x1000, "ts": 0x0031, "ts-ZA": 0x0431, "tr": 0x001F, 424 "tr-CY": 0x1000, "tr-TR": 0x041F, "tk": 0x0042, "tk-TM": 0x0442, "uk": 0x0022, "uk-UA": 0x0422, 425 "hsb": 0x002E, "hsb-DE": 0x042E, "ur": 0x0020, "ur-IN": 0x0820, "ur-PK": 0x0420, "ug": 0x0080, 426 "ug-CN": 0x0480, "uz-Arab": 0x1000, "uz-Arab-AF": 0x1000, "uz-Cyrl": 0x7843, "uz-Cyrl-UZ": 0x0843, "uz": 0x0043, 427 "uz-Latn": 0x7C43, "uz-Latn-UZ": 0x0443, "vai": 0x1000, "vai-Vaii": 0x1000, "vai-Vaii-LR": 0x1000, "vai-Latn-LR": 0x1000, 428 "vai-Latn": 0x1000, "ca-ES-valencia": 0x0803, "ve": 0x0033, "ve-ZA": 0x0433, "vi": 0x002A, "vi-VN": 0x042A, 429 "vo": 0x1000, "vo-001": 0x1000, "vun": 0x1000, "vun-TZ": 0x1000, "wae": 0x1000, "wae-CH": 0x1000, 430 "cy": 0x0052, "cy-GB": 0x0452, "wal": 0x1000, "wal-ET": 0x1000, "wo": 0x0088, "wo-SN": 0x0488, 431 "xh": 0x0034, "xh-ZA": 0x0434, "yav": 0x1000, "yav-CM": 0x1000, "ii": 0x0078, "ii-CN": 0x0478, 432 "yo": 0x006A, "yo-BJ": 0x1000, "yo-NG": 0x046A, "dje": 0x1000, "dje-NE": 0x1000, "zu": 0x0035, 433 "zu-ZA": 0x0435]; 434 435 436 wchar[255] buf; 437 auto p = &buf[0]; 438 auto size = cast(int)buf.length - 1; 439 440 auto LOCALE_SNAME = 0x0000005c; 441 GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SNAME, p, size); 442 443 setDefaultLocale(to!string(p.fromStringz)); 444 } 445 } 446 447 version(Windows) 448 { 449 private string discover(LCID lcid, LCTYPE type) 450 { 451 wchar[255] buf; 452 auto p = &buf[0]; 453 auto size = cast(int)buf.length - 1; 454 455 GetLocaleInfoW(lcid, type, p, size); 456 return to!string(p.fromStringz); 457 } 458 459 /// Converts MS date-time format style to strftime, that library uses 460 private string getAndConvertFormat(LCID lcid, LCTYPE type) 461 { 462 // Get parameter from Windows 463 return convertMs2posixFormat(discover(lcid, type)); 464 } 465 466 // Convert Windows style format to Unix 467 string convertMs2posixFormat(string source) 468 { 469 string makeConvert(string source, char symbol, string[] subst, out uint c) 470 { 471 auto part = source[ 1 .. min(subst.length, source.length) ]; 472 c = 0; 473 while (part.length > 0) 474 { 475 if (part[0] != symbol) 476 break; 477 c++; 478 part = part[ 1 .. $ ]; 479 } 480 auto result = subst[c]; 481 c++; 482 return result; 483 } 484 485 char[] target; 486 487 while (source.length > 0) 488 { 489 auto ch = source[0]; 490 string part; 491 uint c; 492 switch (ch) 493 { 494 case 'g': 495 case 't': 496 source = source[ 1 .. $ ]; 497 break; 498 case 'd': 499 part = makeConvert(source, ch, ["%d", "%d", "%a", "%A"], c); 500 target ~= part; 501 source = source[ c .. $ ]; 502 break; 503 case 'M': 504 part = makeConvert(source, ch, ["%m", "%m", "%b", "%B"], c); 505 target ~= part; 506 source = source[ c .. $ ]; 507 break; 508 case 'y': 509 part = makeConvert(source, ch, ["%y", "%y", "yyy", "%Y", "%Y"], c); 510 target ~= part; 511 source = source[ c .. $ ]; 512 break; 513 case 'h': 514 part = makeConvert(source, ch, ["%I", "%I"], c); 515 target ~= part; 516 source = source[ c .. $ ]; 517 break; 518 case 'H': 519 part = makeConvert(source, ch, ["%H", "%H"], c); 520 target ~= part; 521 source = source[ c .. $ ]; 522 break; 523 case 'm': 524 part = makeConvert(source, ch, ["%M", "%M"], c); 525 target ~= part; 526 source = source[ c .. $ ]; 527 break; 528 case 's': 529 part = makeConvert(source, ch, ["%S", "%S"], c); 530 target ~= part; 531 source = source[ c .. $ ]; 532 break; 533 default: 534 source = source[ 1 .. $ ]; 535 target ~= ch; 536 } 537 } 538 539 return to!string(target).strip; 540 } 541 } 542 543 // Unittest for supported systems. 544 unittest 545 { 546 import std.stdio; 547 writeln("Default system locale is " ~ defaultLocale.name); 548 549 Locale locale; 550 version(Posix) 551 locale = initDateformatLocale("C"); 552 else 553 version(Windows) 554 locale = initDateformatLocale("en"); 555 else 556 locale = initDateformatLocale("en-US"); 557 setDefaultLocale(locale.name); 558 writeln("Test's locale is " ~ locale.name); 559 560 assert(locale.weekDays[0] == "Sunday"); 561 assert(locale.weekDays[1] == "Monday"); 562 assert(locale.weekDays[2] == "Tuesday"); 563 assert(locale.weekDays[3] == "Wednesday"); 564 assert(locale.weekDays[4] == "Thursday"); 565 assert(locale.weekDays[5] == "Friday"); 566 assert(locale.weekDays[6] == "Saturday"); 567 568 assert(locale.abbrWeekDays[0] == "Sun"); 569 assert(locale.abbrWeekDays[1] == "Mon"); 570 assert(locale.abbrWeekDays[2] == "Tue"); 571 assert(locale.abbrWeekDays[3] == "Wed"); 572 assert(locale.abbrWeekDays[4] == "Thu"); 573 assert(locale.abbrWeekDays[5] == "Fri"); 574 assert(locale.abbrWeekDays[6] == "Sat"); 575 576 assert(locale.monthes[0] == "January"); 577 assert(locale.monthes[1] == "February"); 578 assert(locale.monthes[2] == "March"); 579 assert(locale.monthes[3] == "April"); 580 assert(locale.monthes[4] == "May"); 581 assert(locale.monthes[5] == "June"); 582 assert(locale.monthes[6] == "July"); 583 assert(locale.monthes[7] == "August"); 584 assert(locale.monthes[8] == "September"); 585 assert(locale.monthes[9] == "October"); 586 assert(locale.monthes[10] == "November"); 587 assert(locale.monthes[11] == "December"); 588 589 assert(locale.abbrMonthes[0] == "Jan"); 590 assert(locale.abbrMonthes[1] == "Feb"); 591 assert(locale.abbrMonthes[2] == "Mar"); 592 assert(locale.abbrMonthes[3] == "Apr"); 593 assert(locale.abbrMonthes[4] == "May"); 594 assert(locale.abbrMonthes[5] == "Jun"); 595 assert(locale.abbrMonthes[6] == "Jul"); 596 assert(locale.abbrMonthes[7] == "Aug"); 597 assert(locale.abbrMonthes[8] == "Sep"); 598 assert(locale.abbrMonthes[9] == "Oct"); 599 assert(locale.abbrMonthes[10] == "Nov"); 600 assert(locale.abbrMonthes[11] == "Dec"); 601 602 assert(locale.am == "AM"); 603 assert(locale.pm == "PM"); 604 605 version(Posix) 606 { 607 writeln("Posix tests"); 608 609 assert(locale.codeSet == "ANSI_X3.4-1968"); 610 assert(locale.era == ""); 611 assert(locale.dateTime !is null && locale.dateTime.length > 2); 612 assert(locale.date !is null && locale.date.length > 2); 613 assert(locale.time !is null && locale.time.length > 2); 614 615 assert(locale.decimalPoint == "."); 616 assert(locale.thousandSeparator == ""); 617 618 assert(locale.currency == ""); 619 assert(locale.currencyNegativePlace == 1); 620 assert(locale.currencyPositivePlace == 1); 621 assert(locale.intCurrency == ""); 622 assert(locale.intCurrencyNegativePlace == 1); 623 assert(locale.intCurrencyPositivePlace == 1); 624 625 assert(locale.fracDigits == 0); 626 assert(locale.intFracDigits == 0); 627 } 628 629 version(Windows) 630 { 631 writeln("Windows tests"); 632 633 assert(locale.codeSet == "437"); 634 assert(locale.era == ""); 635 assert(locale.dateTime !is null && locale.dateTime.length > 2); 636 assert(locale.date !is null && locale.date.length > 2); 637 assert(locale.time !is null && locale.time.length > 2); 638 639 assert(locale.decimalPoint == "."); 640 assert(locale.thousandSeparator == ","); 641 642 assert(locale.currency == "$"); 643 assert(locale.currencyNegativePlace == 1); 644 assert(locale.currencyPositivePlace == 1); 645 assert(locale.intCurrency == "$"); 646 assert(locale.intCurrencyNegativePlace == 1); 647 assert(locale.intCurrencyPositivePlace == 1); 648 649 assert(locale.fracDigits == 2); 650 assert(locale.intFracDigits == 2); 651 652 assert(convertMs2posixFormat("d") == "%d"); 653 assert(convertMs2posixFormat("dd") == "%d"); 654 assert(convertMs2posixFormat("ddd") == "%a"); 655 assert(convertMs2posixFormat("dddd") == "%A"); 656 657 assert(convertMs2posixFormat("M") == "%m"); 658 assert(convertMs2posixFormat("MM") == "%m"); 659 assert(convertMs2posixFormat("MMM") == "%b"); 660 assert(convertMs2posixFormat("MMMM") == "%B"); 661 662 assert(convertMs2posixFormat("y") == "%y"); 663 assert(convertMs2posixFormat("yy") == "%y"); 664 assert(convertMs2posixFormat("yyyy") == "%Y"); 665 assert(convertMs2posixFormat("yyyyy") == "%Y"); 666 667 assert(convertMs2posixFormat("g") == ""); 668 assert(convertMs2posixFormat("gg") == ""); 669 670 assert(convertMs2posixFormat("dd.MM.yyyy g") == "%d.%m.%Y"); 671 } 672 }