[PATCH v5 0/3] MR10504: Draft: vbscript: Implement GetLocale / SetLocale and honor locale in Format*
Implements `GetLocale` and `SetLocale` (previously stubbed as `E_NOTIMPL`) and makes the locale-sensitive `Format*` built-ins actually respect the script's current locale. This mimics the jscript implementation. -- v5: vbscript: Pass script LCID to VariantChangeType in to_string helper. vbscript: Use script LCID in Format* and call SetThreadLocale. https://gitlab.winehq.org/wine/wine/-/merge_requests/10504
From: Francis De Brabandere <francisdb@gmail.com> --- dlls/vbscript/global.c | 51 ++++++++++++++++++++++++++++++++++--- dlls/vbscript/tests/api.vbs | 51 +++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 4 deletions(-) diff --git a/dlls/vbscript/global.c b/dlls/vbscript/global.c index f1afcfade89..5aad11f75d8 100644 --- a/dlls/vbscript/global.c +++ b/dlls/vbscript/global.c @@ -2229,8 +2229,51 @@ static HRESULT Global_Second(BuiltinDisp *This, VARIANT *arg, unsigned args_cnt, static HRESULT Global_SetLocale(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, VARIANT *res) { - FIXME("\n"); - return E_NOTIMPL; + LCID old_lcid = This->ctx->lcid; + LCID new_lcid; + HRESULT hres; + + TRACE("%s\n", args_cnt ? debugstr_variant(args) : "()"); + + if(!args_cnt) { + This->ctx->lcid = GetUserDefaultLCID(); + return return_int(res, old_lcid); + } + + switch(V_VT(args)) { + case VT_NULL: + return MAKE_VBSERROR(VBSE_ILLEGAL_NULL_USE); + case VT_EMPTY: + This->ctx->lcid = GetUserDefaultLCID(); + return return_int(res, old_lcid); + case VT_BSTR: { + int i; + /* Try numeric conversion first (e.g. "1033") */ + hres = to_int(args, &i); + if(SUCCEEDED(hres)) { + new_lcid = i; + }else { + /* Try as locale name (e.g. "en-us") */ + new_lcid = LocaleNameToLCID(V_BSTR(args), 0); + } + break; + } + default: { + int i; + hres = to_int(args, &i); + if(FAILED(hres)) + return hres; + new_lcid = i; + break; + } + } + + This->ctx->lcid = new_lcid; + return_int(res, old_lcid); + if(!IsValidLocale(new_lcid, LCID_INSTALLED)) + return MAKE_VBSERROR(VBSE_LOCALE_SETTING_NOT_SUPPORTED); + + return S_OK; } static HRESULT Global_DateValue(BuiltinDisp *This, VARIANT *arg, unsigned args_cnt, VARIANT *res) @@ -3386,8 +3429,8 @@ static HRESULT Global_FormatPercent(BuiltinDisp *This, VARIANT *args, unsigned a static HRESULT Global_GetLocale(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, VARIANT *res) { - FIXME("\n"); - return E_NOTIMPL; + TRACE("() = %#lx\n", This->ctx->lcid); + return return_int(res, This->ctx->lcid); } static HRESULT Global_FormatDateTime(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, VARIANT *res) diff --git a/dlls/vbscript/tests/api.vbs b/dlls/vbscript/tests/api.vbs index 2a4f69f0f59..91b4bd10bb6 100644 --- a/dlls/vbscript/tests/api.vbs +++ b/dlls/vbscript/tests/api.vbs @@ -2794,4 +2794,55 @@ end sub call testFilterError() +' GetLocale/SetLocale tests +Dim origLocale +origLocale = GetLocale() +Call ok(getVT(GetLocale()) = "VT_I4", "getVT(GetLocale()) = " & getVT(GetLocale())) + +Dim prevLocale +prevLocale = SetLocale(1033) +Call ok(getVT(prevLocale) = "VT_I4*", "getVT(SetLocale result) = " & getVT(prevLocale)) +Call ok(prevLocale = origLocale, "SetLocale(1033) returned " & prevLocale & " expected " & origLocale) +Call ok(GetLocale() = 1033, "GetLocale() after SetLocale(1033) = " & GetLocale()) + +prevLocale = SetLocale(1031) +Call ok(prevLocale = 1033, "SetLocale(1031) returned " & prevLocale & " expected 1033") +Call ok(GetLocale() = 1031, "GetLocale() after SetLocale(1031) = " & GetLocale()) + +' SetLocale with string locale name +prevLocale = SetLocale("en-us") +Call ok(prevLocale = 1031, "SetLocale(""en-us"") returned " & prevLocale & " expected 1031") +Call ok(GetLocale() = 1033, "GetLocale() after SetLocale(""en-us"") = " & GetLocale()) + +' SetLocale with numeric string +prevLocale = SetLocale("1031") +Call ok(prevLocale = 1033, "SetLocale(""1031"") returned " & prevLocale & " expected 1033") +Call ok(GetLocale() = 1031, "GetLocale() after SetLocale(""1031"") = " & GetLocale()) + +' SetLocale() with no args resets to system default +SetLocale(1033) +prevLocale = SetLocale() +Call ok(prevLocale = 1033, "SetLocale() returned " & prevLocale & " expected 1033") + +sub testSetLocaleError() + on error resume next + + ' SetLocale(Null) should raise error 94 + call Err.clear() + call SetLocale(Null) + Call ok(Err.number = 94, "SetLocale(Null) Err.number = " & Err.number) + + ' SetLocale with invalid numeric LCID should raise error 447 + call Err.clear() + dim prev + prev = SetLocale(99999) + Call ok(Err.number = 447, "SetLocale(99999) Err.number = " & Err.number) +end sub + +call testSetLocaleError() + +' Restore original locale +SetLocale(origLocale) +Call ok(GetLocale() = origLocale, "GetLocale() after restore = " & GetLocale() & " expected " & origLocale) + Call reportSuccess() -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10504
From: Francis De Brabandere <francisdb@gmail.com> The SetLocale/GetLocale commit stored the new LCID on the script context but nothing read it: FormatNumber, FormatCurrency, FormatPercent and FormatDateTime all delegated to oleaut32 helpers that hardcode LOCALE_USER_DEFAULT, so scripts could not actually switch their formatting locale. Replicate the relevant oleaut32 number/currency format logic with the script LCID parameterized, and call GetDateFormatW / GetTimeFormatW directly for FormatDateTime (vbGeneralDate still falls back to VarFormatDateTime). SetLocale now also calls SetThreadLocale so that code paths which do read the thread locale see the change, matching Windows behavior observed with cscript. Also reorder SetLocale to validate before mutating ctx->lcid. --- dlls/vbscript/global.c | 357 ++++++++++++++++++++++++----------- dlls/vbscript/tests/lang.vbs | 37 ++++ 2 files changed, 288 insertions(+), 106 deletions(-) diff --git a/dlls/vbscript/global.c b/dlls/vbscript/global.c index 5aad11f75d8..3167aca131b 100644 --- a/dlls/vbscript/global.c +++ b/dlls/vbscript/global.c @@ -2235,45 +2235,38 @@ static HRESULT Global_SetLocale(BuiltinDisp *This, VARIANT *args, unsigned args_ TRACE("%s\n", args_cnt ? debugstr_variant(args) : "()"); - if(!args_cnt) { - This->ctx->lcid = GetUserDefaultLCID(); - return return_int(res, old_lcid); - } - - switch(V_VT(args)) { - case VT_NULL: - return MAKE_VBSERROR(VBSE_ILLEGAL_NULL_USE); - case VT_EMPTY: - This->ctx->lcid = GetUserDefaultLCID(); - return return_int(res, old_lcid); - case VT_BSTR: { - int i; - /* Try numeric conversion first (e.g. "1033") */ - hres = to_int(args, &i); - if(SUCCEEDED(hres)) { + if(!args_cnt || V_VT(args) == VT_EMPTY) { + new_lcid = GetUserDefaultLCID(); + }else { + switch(V_VT(args)) { + case VT_NULL: + return MAKE_VBSERROR(VBSE_ILLEGAL_NULL_USE); + case VT_BSTR: { + int i; + /* Try numeric conversion first (e.g. "1033"), then locale name (e.g. "en-us"). */ + if(SUCCEEDED(to_int(args, &i))) + new_lcid = i; + else + new_lcid = LocaleNameToLCID(V_BSTR(args), 0); + break; + } + default: { + int i; + hres = to_int(args, &i); + if(FAILED(hres)) + return hres; new_lcid = i; - }else { - /* Try as locale name (e.g. "en-us") */ - new_lcid = LocaleNameToLCID(V_BSTR(args), 0); + break; } - break; - } - default: { - int i; - hres = to_int(args, &i); - if(FAILED(hres)) - return hres; - new_lcid = i; - break; - } + } + + if(!IsValidLocale(new_lcid, LCID_INSTALLED)) + return MAKE_VBSERROR(VBSE_LOCALE_SETTING_NOT_SUPPORTED); } This->ctx->lcid = new_lcid; - return_int(res, old_lcid); - if(!IsValidLocale(new_lcid, LCID_INSTALLED)) - return MAKE_VBSERROR(VBSE_LOCALE_SETTING_NOT_SUPPORTED); - - return S_OK; + SetThreadLocale(new_lcid); + return return_int(res, old_lcid); } static HRESULT Global_DateValue(BuiltinDisp *This, VARIANT *arg, unsigned args_cnt, VARIANT *res) @@ -3331,99 +3324,213 @@ static HRESULT Global_ScriptEngineBuildVersion(BuiltinDisp *This, VARIANT *arg, return return_int(res, VBSCRIPT_BUILD_VERSION); } -static HRESULT Global_FormatNumber(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, VARIANT *res) +#define LOCNUM_INTO(lcid, type, field) GetLocaleInfoW((lcid), (type)|LOCALE_RETURN_NUMBER, \ + (LPWSTR)&(field), sizeof(field)/sizeof(WCHAR)) + +/* LCID_US gives the intermediate double-to-BSTR conversion a stable canonical + * form (period decimal, no grouping) that GetNumberFormatW / GetCurrencyFormatW + * will then reformat using the target locale. */ +#define LCID_EN_US MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT) + +static HRESULT format_number_lcid(LCID lcid, VARIANT *var, int ndigits, int nleading, + int nparens, int ngrouping, BSTR *out) { - union - { - struct - { - int num_dig, inc_lead, use_parens, group; - } s; - int val[4]; - } int_args = { .s.num_dig = -1, .s.inc_lead = -2, .s.use_parens = -2, .s.group = -2 }; + WCHAR buff[256], decimal[8], thousands[8]; + NUMBERFMTW fmt; + VARIANT v; HRESULT hres; - BSTR str; - int i; - TRACE("\n"); + *out = NULL; + V_VT(&v) = VT_EMPTY; + hres = VariantCopyInd(&v, var); + if(FAILED(hres)) return hres; + hres = VariantChangeTypeEx(&v, &v, LCID_EN_US, 0, VT_BSTR); + if(FAILED(hres)) return hres; - assert(1 <= args_cnt && args_cnt <= 5); + if(ndigits < 0) + LOCNUM_INTO(lcid, LOCALE_IDIGITS, fmt.NumDigits); + else + fmt.NumDigits = ndigits; - for (i = 1; i < args_cnt; ++i) - { - if (V_VT(args+i) == VT_ERROR) continue; - if (V_VT(args+i) == VT_NULL) return MAKE_VBSERROR(VBSE_ILLEGAL_NULL_USE); - if (FAILED(hres = to_int(args+i, &int_args.val[i-1]))) return hres; + if(nleading == -2) + LOCNUM_INTO(lcid, LOCALE_ILZERO, fmt.LeadingZero); + else + fmt.LeadingZero = nleading == -1 ? 1 : 0; + + if(ngrouping == -2) { + WCHAR grouping[10] = {0}; + GetLocaleInfoW(lcid, LOCALE_SGROUPING, grouping, ARRAY_SIZE(grouping)); + fmt.Grouping = grouping[2] == '2' ? 32 : grouping[0] - '0'; + }else { + fmt.Grouping = ngrouping == -1 ? 3 : 0; } - hres = VarFormatNumber(args, int_args.s.num_dig, int_args.s.inc_lead, int_args.s.use_parens, - int_args.s.group, 0, &str); - if (FAILED(hres)) return hres; + if(nparens == -2) + LOCNUM_INTO(lcid, LOCALE_INEGNUMBER, fmt.NegativeOrder); + else + fmt.NegativeOrder = nparens == -1 ? 0 : 1; - return return_bstr(res, str); + fmt.lpDecimalSep = decimal; + GetLocaleInfoW(lcid, LOCALE_SDECIMAL, decimal, ARRAY_SIZE(decimal)); + fmt.lpThousandSep = thousands; + GetLocaleInfoW(lcid, LOCALE_STHOUSAND, thousands, ARRAY_SIZE(thousands)); + + if(!GetNumberFormatW(lcid, 0, V_BSTR(&v), &fmt, buff, ARRAY_SIZE(buff))) { + SysFreeString(V_BSTR(&v)); + return DISP_E_TYPEMISMATCH; + } + SysFreeString(V_BSTR(&v)); + + *out = SysAllocString(buff); + return *out ? S_OK : E_OUTOFMEMORY; } -static HRESULT Global_FormatCurrency(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, VARIANT *res) +static HRESULT format_currency_lcid(LCID lcid, VARIANT *var, int ndigits, int nleading, + int nparens, int ngrouping, BSTR *out) { - union - { - struct - { - int num_dig, inc_lead, use_parens, group; - } s; - int val[4]; - } int_args = { .s.num_dig = -1, .s.inc_lead = -2, .s.use_parens = -2, .s.group = -2 }; + WCHAR buff[256], decimal[8], thousands[4], currency[13]; + CURRENCYFMTW fmt; + VARIANT v; + HRESULT hres; + CY cy; + + *out = NULL; + if(V_VT(var) == VT_BSTR || V_VT(var) == (VT_BSTR|VT_BYREF)) { + hres = VarCyFromStr(V_ISBYREF(var) ? *V_BSTRREF(var) : V_BSTR(var), lcid, 0, &cy); + if(FAILED(hres)) return hres; + V_VT(&v) = VT_CY; + V_CY(&v) = cy; + }else { + V_VT(&v) = VT_EMPTY; + hres = VariantCopyInd(&v, var); + if(FAILED(hres)) return hres; + } + hres = VariantChangeTypeEx(&v, &v, lcid, 0, VT_BSTR); + if(FAILED(hres)) return hres; + + if(ndigits < 0) + LOCNUM_INTO(lcid, LOCALE_IDIGITS, fmt.NumDigits); + else + fmt.NumDigits = ndigits; + + if(nleading == -2) + LOCNUM_INTO(lcid, LOCALE_ILZERO, fmt.LeadingZero); + else + fmt.LeadingZero = nleading == -1 ? 1 : 0; + + if(ngrouping == -2) { + WCHAR grouping[10] = {0}; + GetLocaleInfoW(lcid, LOCALE_SGROUPING, grouping, ARRAY_SIZE(grouping)); + fmt.Grouping = grouping[2] == '2' ? 32 : grouping[0] - '0'; + }else { + fmt.Grouping = ngrouping == -1 ? 3 : 0; + } + + if(nparens == -2) + LOCNUM_INTO(lcid, LOCALE_INEGCURR, fmt.NegativeOrder); + else + fmt.NegativeOrder = nparens == -1 ? 0 : 1; + + LOCNUM_INTO(lcid, LOCALE_ICURRENCY, fmt.PositiveOrder); + fmt.lpDecimalSep = decimal; + GetLocaleInfoW(lcid, LOCALE_SDECIMAL, decimal, ARRAY_SIZE(decimal)); + fmt.lpThousandSep = thousands; + GetLocaleInfoW(lcid, LOCALE_STHOUSAND, thousands, ARRAY_SIZE(thousands)); + fmt.lpCurrencySymbol = currency; + GetLocaleInfoW(lcid, LOCALE_SCURRENCY, currency, ARRAY_SIZE(currency)); + + if(!GetCurrencyFormatW(lcid, 0, V_BSTR(&v), &fmt, buff, ARRAY_SIZE(buff))) { + SysFreeString(V_BSTR(&v)); + return DISP_E_TYPEMISMATCH; + } + SysFreeString(V_BSTR(&v)); + + *out = SysAllocString(buff); + return *out ? S_OK : E_OUTOFMEMORY; +} + +static HRESULT parse_format_args(VARIANT *args, unsigned args_cnt, int *vals) +{ + HRESULT hres; + unsigned i; + for(i = 1; i < args_cnt; ++i) { + if(V_VT(args+i) == VT_ERROR) continue; + if(V_VT(args+i) == VT_NULL) return MAKE_VBSERROR(VBSE_ILLEGAL_NULL_USE); + hres = to_int(args+i, &vals[i-1]); + if(FAILED(hres)) return hres; + } + return S_OK; +} + +static HRESULT Global_FormatNumber(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, VARIANT *res) +{ + int a[4] = {-1, -2, -2, -2}; HRESULT hres; BSTR str; - int i; TRACE("\n"); - assert(1 <= args_cnt && args_cnt <= 5); - for (i = 1; i < args_cnt; ++i) - { - if (V_VT(args+i) == VT_ERROR) continue; - if (V_VT(args+i) == VT_NULL) return MAKE_VBSERROR(VBSE_ILLEGAL_NULL_USE); - if (FAILED(hres = to_int(args+i, &int_args.val[i-1]))) return hres; - } + hres = parse_format_args(args, args_cnt, a); + if(FAILED(hres)) return hres; + hres = format_number_lcid(This->ctx->lcid, args, a[0], a[1], a[2], a[3], &str); + if(FAILED(hres)) return hres; + return return_bstr(res, str); +} + +static HRESULT Global_FormatCurrency(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, VARIANT *res) +{ + int a[4] = {-1, -2, -2, -2}; + HRESULT hres; + BSTR str; - hres = VarFormatCurrency(args, int_args.s.num_dig, int_args.s.inc_lead, int_args.s.use_parens, - int_args.s.group, 0, &str); - if (FAILED(hres)) return hres; + TRACE("\n"); + assert(1 <= args_cnt && args_cnt <= 5); + hres = parse_format_args(args, args_cnt, a); + if(FAILED(hres)) return hres; + hres = format_currency_lcid(This->ctx->lcid, args, a[0], a[1], a[2], a[3], &str); + if(FAILED(hres)) return hres; return return_bstr(res, str); } static HRESULT Global_FormatPercent(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, VARIANT *res) { - union - { - struct - { - int num_dig, inc_lead, use_parens, group; - } s; - int val[4]; - } int_args = { .s.num_dig = -1, .s.inc_lead = -2, .s.use_parens = -2, .s.group = -2 }; + int a[4] = {-1, -2, -2, -2}; + WCHAR buff[258]; + DWORD len; + VARIANT v; HRESULT hres; BSTR str; - int i; TRACE("\n"); - assert(1 <= args_cnt && args_cnt <= 5); - for (i = 1; i < args_cnt; ++i) - { - if (V_VT(args+i) == VT_ERROR) continue; - if (V_VT(args+i) == VT_NULL) return MAKE_VBSERROR(VBSE_ILLEGAL_NULL_USE); - if (FAILED(hres = to_int(args+i, &int_args.val[i-1]))) return hres; - } - - hres = VarFormatPercent(args, int_args.s.num_dig, int_args.s.inc_lead, int_args.s.use_parens, - int_args.s.group, 0, &str); - if (FAILED(hres)) return hres; + hres = parse_format_args(args, args_cnt, a); + if(FAILED(hres)) return hres; + V_VT(&v) = VT_EMPTY; + hres = VariantCopyInd(&v, args); + if(FAILED(hres)) return hres; + hres = VariantChangeTypeEx(&v, &v, This->ctx->lcid, 0, VT_R8); + if(FAILED(hres)) return hres; + if(V_R8(&v) > (1e300 / 100.0)) return DISP_E_OVERFLOW; + V_R8(&v) *= 100.0; + + hres = format_number_lcid(This->ctx->lcid, &v, a[0], a[1], a[2], a[3], &str); + if(FAILED(hres)) return hres; + + len = lstrlenW(str); + if(len && str[len-1] == ')') { + memcpy(buff, str, (len-1) * sizeof(WCHAR)); + lstrcpyW(buff + len - 1, L"%)"); + }else { + memcpy(buff, str, len * sizeof(WCHAR)); + lstrcpyW(buff + len, L"%"); + } + SysFreeString(str); + str = SysAllocString(buff); + if(!str) return E_OUTOFMEMORY; return return_bstr(res, str); } @@ -3436,6 +3543,10 @@ static HRESULT Global_GetLocale(BuiltinDisp *This, VARIANT *args, unsigned args_ static HRESULT Global_FormatDateTime(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, VARIANT *res) { int format = 0; + LCID lcid = This->ctx->lcid; + WCHAR buff[256]; + VARIANT v; + SYSTEMTIME st; HRESULT hres; BSTR str; @@ -3443,21 +3554,55 @@ static HRESULT Global_FormatDateTime(BuiltinDisp *This, VARIANT *args, unsigned assert(1 <= args_cnt && args_cnt <= 2); - if (V_VT(args) == VT_NULL) + if(V_VT(args) == VT_NULL) return MAKE_VBSERROR(VBSE_TYPE_MISMATCH); - if (args_cnt == 2) - { - if (V_VT(args+1) == VT_NULL) return MAKE_VBSERROR(VBSE_ILLEGAL_NULL_USE); - if (V_VT(args+1) != VT_ERROR) - { - if (FAILED(hres = to_int(args+1, &format))) return hres; - } + if(args_cnt == 2 && V_VT(args+1) != VT_ERROR) { + if(V_VT(args+1) == VT_NULL) return MAKE_VBSERROR(VBSE_ILLEGAL_NULL_USE); + hres = to_int(args+1, &format); + if(FAILED(hres)) return hres; } - hres = VarFormatDateTime(args, format, 0, &str); - if (FAILED(hres)) return hres; + /* vbGeneralDate (0) falls back to oleaut32 — the "show date if date, time if + * time, both otherwise" logic depends on the tokenizer; ctx->lcid does not + * yet propagate. */ + if(format == 0) { + hres = VarFormatDateTime(args, format, 0, &str); + if(FAILED(hres)) return hres; + return return_bstr(res, str); + } + + V_VT(&v) = VT_EMPTY; + hres = VariantCopyInd(&v, args); + if(FAILED(hres)) return hres; + hres = VariantChangeTypeEx(&v, &v, lcid, 0, VT_DATE); + if(FAILED(hres)) return hres; + if(!VariantTimeToSystemTime(V_DATE(&v), &st)) + return E_INVALIDARG; + + switch(format) { + case 1: /* vbLongDate */ + if(!GetDateFormatW(lcid, DATE_LONGDATE, &st, NULL, buff, ARRAY_SIZE(buff))) + return E_FAIL; + break; + case 2: /* vbShortDate */ + if(!GetDateFormatW(lcid, DATE_SHORTDATE, &st, NULL, buff, ARRAY_SIZE(buff))) + return E_FAIL; + break; + case 3: /* vbLongTime */ + if(!GetTimeFormatW(lcid, 0, &st, NULL, buff, ARRAY_SIZE(buff))) + return E_FAIL; + break; + case 4: /* vbShortTime */ + if(!GetTimeFormatW(lcid, TIME_NOSECONDS|TIME_FORCE24HOURFORMAT, &st, NULL, buff, ARRAY_SIZE(buff))) + return E_FAIL; + break; + default: + return E_INVALIDARG; + } + str = SysAllocString(buff); + if(!str) return E_OUTOFMEMORY; return return_bstr(res, str); } diff --git a/dlls/vbscript/tests/lang.vbs b/dlls/vbscript/tests/lang.vbs index b3a75cfc63c..7a4f152bdfd 100644 --- a/dlls/vbscript/tests/lang.vbs +++ b/dlls/vbscript/tests/lang.vbs @@ -3528,4 +3528,41 @@ Call ok(Err.Number = 506, "New non-class variable: err.number = " & Err.Number) On Error GoTo 0 +' === GetLocale / SetLocale / locale-sensitive Format* === +Dim origLcid +origLcid = GetLocale() +Call ok(origLcid > 0, "GetLocale initial: " & origLcid) + +' SetLocale returns the previous LCID. +Call ok(SetLocale(1033) = origLcid, "SetLocale(1033) return value") +Call ok(GetLocale() = 1033, "GetLocale after SetLocale(1033): " & GetLocale()) + +' en-US formatting +Call ok(FormatNumber(1234567.89) = "1,234,567.89", "FormatNumber en-US: " & FormatNumber(1234567.89)) +Call ok(FormatCurrency(1234567.89) = "$1,234,567.89", "FormatCurrency en-US: " & FormatCurrency(1234567.89)) +Call ok(FormatPercent(0.1234) = "12.34%", "FormatPercent en-US: " & FormatPercent(0.1234)) +Call ok(FormatDateTime(DateSerial(2026,3,15), 1) = "Sunday, March 15, 2026", _ + "FormatDateTime en-US: " & FormatDateTime(DateSerial(2026,3,15), 1)) + +' de-DE: '.' thousands, ',' decimal +Call SetLocale(1031) +Call ok(GetLocale() = 1031, "GetLocale after SetLocale(1031)") +Call ok(FormatNumber(1234567.89) = "1.234.567,89", "FormatNumber de-DE: " & FormatNumber(1234567.89)) +Call ok(FormatPercent(0.1234) = "12,34%", "FormatPercent de-DE: " & FormatPercent(0.1234)) + +' ja-JP: Anglo number formatting +Call SetLocale(1041) +Call ok(GetLocale() = 1041, "GetLocale after SetLocale(1041)") +Call ok(FormatNumber(1234567.89) = "1,234,567.89", "FormatNumber ja-JP: " & FormatNumber(1234567.89)) +Call ok(FormatPercent(0.1234) = "12.34%", "FormatPercent ja-JP: " & FormatPercent(0.1234)) + +' CStr goes through VariantChangeType with LCID=0 (LOCALE_USER_DEFAULT). +' Plumbing ctx->lcid into that path is follow-up work. +Call SetLocale(1031) +Call todo_wine_ok(CStr(1.5) = "1,5", "CStr(1.5) de-DE: " & CStr(1.5)) + +' Restore original locale. +Call SetLocale(origLcid) +Call ok(GetLocale() = origLcid, "restore: GetLocale = " & GetLocale()) + reportSuccess() -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10504
From: Francis De Brabandere <francisdb@gmail.com> CStr and other string coercions of numeric/date variants went through VariantChangeType(..., 0, VT_BSTR), so they always formatted with the user default locale regardless of SetLocale. Thread the script LCID through the to_string() helper (and the shared err_string_prop) so conversions honor the script-level locale. --- dlls/vbscript/global.c | 88 ++++++++++++++++++------------------ dlls/vbscript/tests/lang.vbs | 7 +-- 2 files changed, 48 insertions(+), 47 deletions(-) diff --git a/dlls/vbscript/global.c b/dlls/vbscript/global.c index 3167aca131b..6596ccc9f50 100644 --- a/dlls/vbscript/global.c +++ b/dlls/vbscript/global.c @@ -453,13 +453,13 @@ static HRESULT to_float(VARIANT *v, float *ret) return S_OK; } -static HRESULT to_string(VARIANT *v, BSTR *ret) +static HRESULT to_string(LCID lcid, VARIANT *v, BSTR *ret) { VARIANT dst; HRESULT hres; V_VT(&dst) = VT_EMPTY; - hres = VariantChangeType(&dst, v, VARIANT_LOCALBOOL, VT_BSTR); + hres = VariantChangeTypeEx(&dst, v, lcid, VARIANT_LOCALBOOL, VT_BSTR); if(FAILED(hres)) return hres; @@ -836,7 +836,7 @@ static HRESULT Global_CStr(BuiltinDisp *This, VARIANT *arg, unsigned args_cnt, V if(V_VT(arg) == VT_NULL) return MAKE_VBSERROR(VBSE_ILLEGAL_NULL_USE); - hres = to_string(arg, &str); + hres = to_string(This->ctx->lcid, arg, &str); if(FAILED(hres)) return hres; @@ -1283,7 +1283,7 @@ static HRESULT Global_Len(BuiltinDisp *This, VARIANT *arg, unsigned args_cnt, VA if(V_VT(arg) != VT_BSTR) { BSTR str; - hres = to_string(arg, &str); + hres = to_string(This->ctx->lcid, arg, &str); if(FAILED(hres)) return hres; @@ -1313,7 +1313,7 @@ static HRESULT Global_Left(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, if(V_VT(args) == VT_BSTR) { str = V_BSTR(args); }else { - hres = to_string(args, &conv_str); + hres = to_string(This->ctx->lcid, args, &conv_str); if(FAILED(hres)) return hres; str = conv_str; @@ -1370,7 +1370,7 @@ static HRESULT Global_Right(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, if(V_VT(args) == VT_BSTR) { str = V_BSTR(args); }else { - hres = to_string(args, &conv_str); + hres = to_string(This->ctx->lcid, args, &conv_str); if(FAILED(hres)) return hres; str = conv_str; @@ -1438,7 +1438,7 @@ static HRESULT Global_Mid(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, V if(V_VT(args) == VT_BSTR) { str = V_BSTR(args); }else { - hres = to_string(args, &conv_str); + hres = to_string(This->ctx->lcid, args, &conv_str); if(FAILED(hres)) return hres; str = conv_str; @@ -1496,11 +1496,11 @@ static HRESULT Global_StrComp(BuiltinDisp *This, VARIANT *args, unsigned args_cn else mode = 0; - hres = to_string(args, &left); + hres = to_string(This->ctx->lcid, args, &left); if(FAILED(hres)) return hres; - hres = to_string(args+1, &right); + hres = to_string(This->ctx->lcid, args+1, &right); if(FAILED(hres)) { SysFreeString(left); @@ -1526,7 +1526,7 @@ static HRESULT Global_LCase(BuiltinDisp *This, VARIANT *arg, unsigned args_cnt, return return_null(res); } - hres = to_string(arg, &str); + hres = to_string(This->ctx->lcid, arg, &str); if(FAILED(hres)) return hres; @@ -1555,7 +1555,7 @@ static HRESULT Global_UCase(BuiltinDisp *This, VARIANT *arg, unsigned args_cnt, return return_null(res); } - hres = to_string(arg, &str); + hres = to_string(This->ctx->lcid, arg, &str); if(FAILED(hres)) return hres; @@ -1584,7 +1584,7 @@ static HRESULT Global_LTrim(BuiltinDisp *This, VARIANT *arg, unsigned args_cnt, if(V_VT(arg) == VT_BSTR) { str = V_BSTR(arg); }else { - hres = to_string(arg, &conv_str); + hres = to_string(This->ctx->lcid, arg, &conv_str); if(FAILED(hres)) return hres; str = conv_str; @@ -1611,7 +1611,7 @@ static HRESULT Global_RTrim(BuiltinDisp *This, VARIANT *arg, unsigned args_cnt, if(V_VT(arg) == VT_BSTR) { str = V_BSTR(arg); }else { - hres = to_string(arg, &conv_str); + hres = to_string(This->ctx->lcid, arg, &conv_str); if(FAILED(hres)) return hres; str = conv_str; @@ -1638,7 +1638,7 @@ static HRESULT Global_Trim(BuiltinDisp *This, VARIANT *arg, unsigned args_cnt, V if(V_VT(arg) == VT_BSTR) { str = V_BSTR(arg); }else { - hres = to_string(arg, &conv_str); + hres = to_string(This->ctx->lcid, arg, &conv_str); if(FAILED(hres)) return hres; str = conv_str; @@ -1779,7 +1779,7 @@ static HRESULT Global_InStr(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, return return_null(res); if(V_VT(str1v) != VT_BSTR) { - hres = to_string(str1v, &str1); + hres = to_string(This->ctx->lcid, str1v, &str1); if(FAILED(hres)) return hres; } @@ -1787,7 +1787,7 @@ static HRESULT Global_InStr(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, str1 = V_BSTR(str1v); if(V_VT(str2v) != VT_BSTR) { - hres = to_string(str2v, &str2); + hres = to_string(This->ctx->lcid, str2v, &str2); if(FAILED(hres)){ if(V_VT(str1v) != VT_BSTR) SysFreeString(str1); @@ -1843,7 +1843,7 @@ static HRESULT Global_Asc(BuiltinDisp *This, VARIANT *arg, unsigned args_cnt, VA str = V_BSTR(arg); break; default: - hres = to_string(arg, &conv_str); + hres = to_string(This->ctx->lcid, arg, &conv_str); if(FAILED(hres)) return hres; str = conv_str; @@ -1941,7 +1941,7 @@ static HRESULT Global_AscW(BuiltinDisp *This, VARIANT *arg, unsigned args_cnt, V str = V_BSTR(arg); break; default: - hres = to_string(arg, &conv_str); + hres = to_string(This->ctx->lcid, arg, &conv_str); if(FAILED(hres)) return hres; str = conv_str; @@ -2368,7 +2368,7 @@ static HRESULT Global_MsgBox(BuiltinDisp *This, VARIANT *args, unsigned args_cnt assert(1 <= args_cnt && args_cnt <= 5); - hres = to_string(args, &prompt); + hres = to_string(This->ctx->lcid, args, &prompt); if(FAILED(hres)) return hres; @@ -2376,7 +2376,7 @@ static HRESULT Global_MsgBox(BuiltinDisp *This, VARIANT *args, unsigned args_cnt hres = to_int(args+1, &type); if(SUCCEEDED(hres) && args_cnt > 2) - hres = to_string(args+2, &title); + hres = to_string(This->ctx->lcid, args+2, &title); if(SUCCEEDED(hres) && args_cnt > 3) { FIXME("unsupported arg_cnt %d\n", args_cnt); @@ -2494,7 +2494,7 @@ static HRESULT Global_DateAdd(BuiltinDisp *This, VARIANT *args, unsigned args_cn if (V_VT(args + 2) == VT_NULL) return return_null(res); - hres = to_string(args, &interval); + hres = to_string(This->ctx->lcid, args, &interval); if (SUCCEEDED(hres)) hres = to_int(args + 1, &count); if (SUCCEEDED(hres)) @@ -2691,7 +2691,7 @@ static HRESULT Global_Filter(BuiltinDisp *This, VARIANT *args, unsigned args_cnt if(V_VT(args+1) == VT_BSTR) { search = V_BSTR(args+1); }else { - hres = to_string(args+1, &conv_search); + hres = to_string(This->ctx->lcid, args+1, &conv_search); if(FAILED(hres)) return hres; search = conv_search; @@ -2746,7 +2746,7 @@ static HRESULT Global_Filter(BuiltinDisp *This, VARIANT *args, unsigned args_cnt if(V_VT(&data[i]) == VT_BSTR) { str = V_BSTR(&data[i]); }else { - hres = to_string(&data[i], &conv_str); + hres = to_string(This->ctx->lcid, &data[i], &conv_str); if(FAILED(hres)) { SafeArrayUnaccessData(sa); goto done; @@ -2855,7 +2855,7 @@ static HRESULT Global_Join(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, if (V_VT(args + 1) == VT_NULL) return MAKE_VBSERROR(VBSE_ILLEGAL_NULL_USE); if (V_VT(args + 1) != VT_BSTR) { - hres = to_string(args + 1, &free_delimiter); + hres = to_string(This->ctx->lcid, args + 1, &free_delimiter); if (FAILED(hres)) return hres; delimiter = free_delimiter; @@ -2891,7 +2891,7 @@ static HRESULT Global_Join(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, } for (i = 0; i < count; i++) { if (V_VT(&data[i]) != VT_BSTR) { - hres = to_string(&data[i], &str); + hres = to_string(This->ctx->lcid, &data[i], &str); if (FAILED(hres)) goto cleanup_data; } else { @@ -2957,7 +2957,7 @@ static HRESULT Global_Split(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, return MAKE_VBSERROR(VBSE_ILLEGAL_NULL_USE); if(V_VT(args) != VT_BSTR) { - hres = to_string(args, &string); + hres = to_string(This->ctx->lcid, args, &string); if(FAILED(hres)) return hres; }else { @@ -2966,7 +2966,7 @@ static HRESULT Global_Split(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, if(args_cnt > 1) { if(V_VT(args+1) != VT_BSTR) { - hres = to_string(args+1, &delimiter); + hres = to_string(This->ctx->lcid, args+1, &delimiter); if(FAILED(hres)) goto done; }else { @@ -3107,7 +3107,7 @@ static HRESULT Global_Replace(BuiltinDisp *This, VARIANT *args, unsigned args_cn if(V_VT(args) != VT_BSTR) { - hres = to_string(args, &string); + hres = to_string(This->ctx->lcid, args, &string); if(FAILED(hres)) return hres; }else { @@ -3115,7 +3115,7 @@ static HRESULT Global_Replace(BuiltinDisp *This, VARIANT *args, unsigned args_cn } if(V_VT(args+1) != VT_BSTR) { - hres = to_string(args+1, &find); + hres = to_string(This->ctx->lcid, args+1, &find); if(FAILED(hres)) goto error; }else { @@ -3123,7 +3123,7 @@ static HRESULT Global_Replace(BuiltinDisp *This, VARIANT *args, unsigned args_cn } if(V_VT(args+2) != VT_BSTR) { - hres = to_string(args+2, &replace); + hres = to_string(This->ctx->lcid, args+2, &replace); if(FAILED(hres)) goto error; }else { @@ -3188,7 +3188,7 @@ static HRESULT Global_StrReverse(BuiltinDisp *This, VARIANT *arg, unsigned args_ TRACE("%s\n", debugstr_variant(arg)); - hres = to_string(arg, &ret); + hres = to_string(This->ctx->lcid, arg, &ret); if(FAILED(hres)) return hres; @@ -3235,7 +3235,7 @@ static HRESULT Global_InStrRev(BuiltinDisp *This, VARIANT *args, unsigned args_c } if(V_VT(args) != VT_BSTR) { - hres = to_string(args, &str1); + hres = to_string(This->ctx->lcid, args, &str1); if(FAILED(hres)) return hres; } @@ -3243,7 +3243,7 @@ static HRESULT Global_InStrRev(BuiltinDisp *This, VARIANT *args, unsigned args_c str1 = V_BSTR(args); if(V_VT(args+1) != VT_BSTR) { - hres = to_string(args+1, &str2); + hres = to_string(This->ctx->lcid, args+1, &str2); if(FAILED(hres)) { if(V_VT(args) != VT_BSTR) SysFreeString(str1); @@ -3748,7 +3748,7 @@ static HRESULT Global_Escape(BuiltinDisp *This, VARIANT *arg, unsigned args_cnt, if(V_VT(arg) == VT_BSTR) { str = V_BSTR(arg); }else { - hres = to_string(arg, &conv_str); + hres = to_string(This->ctx->lcid, arg, &conv_str); if(FAILED(hres)) return hres; str = conv_str; @@ -3813,7 +3813,7 @@ static HRESULT Global_Unescape(BuiltinDisp *This, VARIANT *arg, unsigned args_cn if(V_VT(arg) == VT_BSTR) { str = V_BSTR(arg); }else { - hres = to_string(arg, &conv_str); + hres = to_string(This->ctx->lcid, arg, &conv_str); if(FAILED(hres)) return hres; str = conv_str; @@ -3884,7 +3884,7 @@ static HRESULT dispatch_to_string(script_ctx_t *ctx, IDispatch *disp, BSTR *ret) *ret = V_BSTR(&v); return S_OK; } - hres = to_string(&v, ret); + hres = to_string(ctx->lcid, &v, ret); VariantClear(&v); return hres; } @@ -4277,7 +4277,7 @@ static const builtin_prop_t global_props[] = { {L"Year", Global_Year, 0, 1} }; -static HRESULT err_string_prop(BSTR *prop, VARIANT *args, unsigned args_cnt, VARIANT *res) +static HRESULT err_string_prop(BuiltinDisp *This, BSTR *prop, VARIANT *args, unsigned args_cnt, VARIANT *res) { BSTR str; HRESULT hres; @@ -4285,7 +4285,7 @@ static HRESULT err_string_prop(BSTR *prop, VARIANT *args, unsigned args_cnt, VAR if(!args_cnt) return return_string(res, *prop ? *prop : L""); - hres = to_string(args, &str); + hres = to_string(This->ctx->lcid, args, &str); if(FAILED(hres)) return hres; @@ -4297,7 +4297,7 @@ static HRESULT err_string_prop(BSTR *prop, VARIANT *args, unsigned args_cnt, VAR static HRESULT Err_Description(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, VARIANT *res) { TRACE("\n"); - return err_string_prop(&This->ctx->ei.bstrDescription, args, args_cnt, res); + return err_string_prop(This, &This->ctx->ei.bstrDescription, args, args_cnt, res); } static HRESULT Err_HelpContext(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, VARIANT *res) @@ -4315,7 +4315,7 @@ static HRESULT Err_HelpContext(BuiltinDisp *This, VARIANT *args, unsigned args_c static HRESULT Err_HelpFile(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, VARIANT *res) { TRACE("\n"); - return err_string_prop(&This->ctx->ei.bstrHelpFile, args, args_cnt, res); + return err_string_prop(This, &This->ctx->ei.bstrHelpFile, args, args_cnt, res); } static HRESULT Err_Number(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, VARIANT *res) @@ -4336,7 +4336,7 @@ static HRESULT Err_Number(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, V static HRESULT Err_Source(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, VARIANT *res) { TRACE("\n"); - return err_string_prop(&This->ctx->ei.bstrSource, args, args_cnt, res); + return err_string_prop(This, &This->ctx->ei.bstrSource, args, args_cnt, res); } static HRESULT Err_Clear(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, VARIANT *res) @@ -4362,11 +4362,11 @@ static HRESULT Err_Raise(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, VA return E_INVALIDARG; if(args_cnt >= 2) - hres = to_string(args + 1, &source); + hres = to_string(This->ctx->lcid, args + 1, &source); if(args_cnt >= 3 && SUCCEEDED(hres)) - hres = to_string(args + 2, &description); + hres = to_string(This->ctx->lcid, args + 2, &description); if(args_cnt >= 4 && SUCCEEDED(hres)) - hres = to_string(args + 3, &helpfile); + hres = to_string(This->ctx->lcid, args + 3, &helpfile); if(args_cnt >= 5 && SUCCEEDED(hres)) hres = to_int(args + 4, &helpcontext); diff --git a/dlls/vbscript/tests/lang.vbs b/dlls/vbscript/tests/lang.vbs index 7a4f152bdfd..a362eaa5a15 100644 --- a/dlls/vbscript/tests/lang.vbs +++ b/dlls/vbscript/tests/lang.vbs @@ -3556,10 +3556,11 @@ Call ok(GetLocale() = 1041, "GetLocale after SetLocale(1041)") Call ok(FormatNumber(1234567.89) = "1,234,567.89", "FormatNumber ja-JP: " & FormatNumber(1234567.89)) Call ok(FormatPercent(0.1234) = "12.34%", "FormatPercent ja-JP: " & FormatPercent(0.1234)) -' CStr goes through VariantChangeType with LCID=0 (LOCALE_USER_DEFAULT). -' Plumbing ctx->lcid into that path is follow-up work. +' Variant coercion to string (CStr etc.) uses the script LCID. Call SetLocale(1031) -Call todo_wine_ok(CStr(1.5) = "1,5", "CStr(1.5) de-DE: " & CStr(1.5)) +Call ok(CStr(1.5) = "1,5", "CStr(1.5) de-DE: " & CStr(1.5)) +Call SetLocale(1033) +Call ok(CStr(1.5) = "1.5", "CStr(1.5) en-US: " & CStr(1.5)) ' Restore original locale. Call SetLocale(origLcid) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10504
participants (2)
-
Francis De Brabandere -
Francis De Brabandere (@francisdb)