[PATCH v2 0/1] MR10509: Draft: vbscript: Implement DateValue and TimeValue functions.
-- v2: vbscript: Implement DateValue and TimeValue functions. https://gitlab.winehq.org/wine/wine/-/merge_requests/10509
From: Francis De Brabandere <francisdb@gmail.com> --- dlls/vbscript/global.c | 54 ++++++++++++++++-- dlls/vbscript/tests/api.vbs | 108 ++++++++++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+), 4 deletions(-) diff --git a/dlls/vbscript/global.c b/dlls/vbscript/global.c index 1d244b8207f..ff8f71d58a2 100644 --- a/dlls/vbscript/global.c +++ b/dlls/vbscript/global.c @@ -2189,14 +2189,60 @@ static HRESULT Global_SetLocale(BuiltinDisp *This, VARIANT *args, unsigned args_ static HRESULT Global_DateValue(BuiltinDisp *This, VARIANT *arg, unsigned args_cnt, VARIANT *res) { - FIXME("\n"); - return E_NOTIMPL; + VARIANT v; + HRESULT hres; + DATE date; + + TRACE("(%s)\n", debugstr_variant(arg)); + + if(V_VT(arg) == VT_NULL) + return MAKE_VBSERROR(VBSE_ILLEGAL_NULL_USE); + + if(V_VT(arg) == VT_DATE) { + date = V_DATE(arg); + }else if(V_VT(arg) == VT_BSTR) { + V_VT(&v) = VT_EMPTY; + /* FIXME: Use VariantChangeTypeEx with script lcid for locale-aware parsing (applies to CDate and other converters too). */ + hres = VariantChangeType(&v, arg, 0, VT_DATE); + if(FAILED(hres)) + return MAKE_VBSERROR(VBSE_TYPE_MISMATCH); + date = V_DATE(&v); + }else { + return MAKE_VBSERROR(VBSE_TYPE_MISMATCH); + } + + /* Truncate toward zero to remove time fraction */ + date = date < 0 ? ceil(date) : floor(date); + return return_date(res, date); } static HRESULT Global_TimeValue(BuiltinDisp *This, VARIANT *arg, unsigned args_cnt, VARIANT *res) { - FIXME("\n"); - return E_NOTIMPL; + VARIANT v; + HRESULT hres; + DATE date; + + TRACE("(%s)\n", debugstr_variant(arg)); + + if(V_VT(arg) == VT_NULL) + return MAKE_VBSERROR(VBSE_ILLEGAL_NULL_USE); + + if(V_VT(arg) == VT_DATE) { + date = V_DATE(arg); + }else if(V_VT(arg) == VT_BSTR) { + V_VT(&v) = VT_EMPTY; + /* FIXME: Use VariantChangeTypeEx with script lcid for locale-aware parsing (applies to CDate and other converters too). */ + hres = VariantChangeType(&v, arg, 0, VT_DATE); + if(FAILED(hres)) + return MAKE_VBSERROR(VBSE_TYPE_MISMATCH); + date = V_DATE(&v); + }else { + return MAKE_VBSERROR(VBSE_TYPE_MISMATCH); + } + + /* Extract time fraction, removing the date part */ + date = date - (date < 0 ? ceil(date) : floor(date)); + return return_date(res, date); } static HRESULT Global_DateSerial(BuiltinDisp *This, VARIANT *args, unsigned args_cnt, VARIANT *res) diff --git a/dlls/vbscript/tests/api.vbs b/dlls/vbscript/tests/api.vbs index e18d887306b..8b37daec7a5 100644 --- a/dlls/vbscript/tests/api.vbs +++ b/dlls/vbscript/tests/api.vbs @@ -2624,4 +2624,112 @@ end sub call testFormatNumber() call testFormatNumberError() +' DateValue tests +Dim dv +dv = DateValue("2026-03-30") +Call ok(getVT(dv) = "VT_DATE*", "getVT(DateValue) = " & getVT(dv)) +Call ok(Year(dv) = 2026, "DateValue Year = " & Year(dv)) +Call ok(Month(dv) = 3, "DateValue Month = " & Month(dv)) +Call ok(Day(dv) = 30, "DateValue Day = " & Day(dv)) + +' DateValue strips time component +dv = DateValue("2026-03-30 14:30:00") +Call ok(Hour(dv) = 0, "DateValue Hour should be 0, got " & Hour(dv)) +Call ok(Minute(dv) = 0, "DateValue Minute should be 0, got " & Minute(dv)) +Call ok(Second(dv) = 0, "DateValue Second should be 0, got " & Second(dv)) +Call ok(Year(dv) = 2026, "DateValue Year = " & Year(dv)) + +' DateValue of time-only string gives date zero (Dec 30, 1899) +dv = DateValue("14:30:00") +Call ok(Year(dv) = 1899, "DateValue time-only Year = " & Year(dv)) +Call ok(Month(dv) = 12, "DateValue time-only Month = " & Month(dv)) +Call ok(Day(dv) = 30, "DateValue time-only Day = " & Day(dv)) + +' DateValue accepts VT_DATE input +dv = DateValue(CDate("2026-03-30 14:30:00")) +Call ok(Hour(dv) = 0, "DateValue CDate Hour = " & Hour(dv)) +Call ok(Year(dv) = 2026, "DateValue CDate Year = " & Year(dv)) + +' TimeValue tests +Dim tv +tv = TimeValue("14:30:45") +Call ok(getVT(tv) = "VT_DATE*", "getVT(TimeValue) = " & getVT(tv)) +Call ok(Hour(tv) = 14, "TimeValue Hour = " & Hour(tv)) +Call ok(Minute(tv) = 30, "TimeValue Minute = " & Minute(tv)) +Call ok(Second(tv) = 45, "TimeValue Second = " & Second(tv)) + +' TimeValue strips date component +tv = TimeValue("2026-03-30 14:30:45") +Call ok(Hour(tv) = 14, "TimeValue datetime Hour = " & Hour(tv)) +Call ok(Year(tv) = 1899, "TimeValue datetime Year = " & Year(tv)) + +' TimeValue of date-only string gives midnight +tv = TimeValue("2026-03-30") +Call ok(Hour(tv) = 0, "TimeValue date-only Hour = " & Hour(tv)) +Call ok(Minute(tv) = 0, "TimeValue date-only Minute = " & Minute(tv)) + +' TimeValue accepts VT_DATE input +tv = TimeValue(CDate("2026-03-30 14:30:45")) +Call ok(Hour(tv) = 14, "TimeValue CDate Hour = " & Hour(tv)) +Call ok(Year(tv) = 1899, "TimeValue CDate Year = " & Year(tv)) + +sub testDateValueError() + on error resume next + dim r + + call Err.clear() + r = DateValue(Null) + Call ok(Err.number = 94, "DateValue(Null) Err.number = " & Err.number) + + call Err.clear() + r = DateValue("not a date") + Call ok(Err.number = 13, "DateValue(invalid) Err.number = " & Err.number) + + call Err.clear() + r = DateValue("") + Call ok(Err.number = 13, "DateValue("""") Err.number = " & Err.number) + + call Err.clear() + r = DateValue(44000) + Call ok(Err.number = 13, "DateValue(number) Err.number = " & Err.number) + + call Err.clear() + r = TimeValue(Null) + Call ok(Err.number = 94, "TimeValue(Null) Err.number = " & Err.number) + + call Err.clear() + r = TimeValue("not a time") + Call ok(Err.number = 13, "TimeValue(invalid) Err.number = " & Err.number) + + call Err.clear() + r = TimeValue("") + Call ok(Err.number = 13, "TimeValue("""") Err.number = " & Err.number) +end sub + +call testDateValueError() + +' Locale-sensitive date parsing: "3/4/2026" is ambiguous (March 4 vs April 3) +' DateValue and CDate should use the script locale set by SetLocale. +' FIXME: VariantChangeType uses thread locale, not script lcid. +sub testDateValueLocale() + on error resume next + dim origLcid, ambigDate + + origLcid = GetLocale() + + SetLocale(1033) ' en-US: month/day/year + ambigDate = DateValue("3/4/2026") + Call ok(Month(ambigDate) = 3, "DateValue en-US Month = " & Month(ambigDate)) + Call ok(Day(ambigDate) = 4, "DateValue en-US Day = " & Day(ambigDate)) + + SetLocale(1036) ' fr-FR: day/month/year + ambigDate = DateValue("3/4/2026") + todo_wine_ok Month(ambigDate) = 4, "DateValue fr-FR Month = " & Month(ambigDate) + todo_wine_ok Day(ambigDate) = 3, "DateValue fr-FR Day = " & Day(ambigDate) + + if not IsEmpty(origLcid) then SetLocale(origLcid) +end sub + +call testDateValueLocale() + Call reportSuccess() -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10509
Marking as draft. To properly implement the locale issues we need to first have !10504 merged. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10509#note_134326
participants (2)
-
Francis De Brabandere -
Francis De Brabandere (@francisdb)