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 }