oleaut32 variant

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi,

This patch should be applied instead of "oleaut32 variant dates".
That patch 
can be ignored, this one fixes the tests and a few other functions,
and adds 
tests for the recent changes.

After applying, you can delete parsedt.c and parsedt.h, and remove
parsedt.c 
from Makefile.in.

Cheers,
Jon

License: X11

ChangeLog:

  Jon Griffiths <jon_p_griffiths@xxxxxxxxx>

  +dlls/oleaut32/variant.c dlls/oleaut32/oleaut32.spec
    Implement conversions between dates and strings.
    Remove the final cruft associated with the old date code
    BPB's: Roll milliseconds correctly; LOCALE_NOUSEROVERRIDE for num
parsing.
    Implement VarAbs and VarNot.

  +dlls/oleaut32/resource.h
    Add id's for localised month names (for GetAltMonthNames).

  +dlls/oleaut32/tests/vartest.c
    Fix the tests to pass when locale settings are user-overriden.
    Fix those tests which pass now.
    Test copying, formatting, VarAbs, VarNot.




__________________________________
Do you Yahoo!?
Free Pop-Up Blocker - Get it now
http://companion.yahoo.com/
diff -ur --minimal wine/dlls/oleaut32/oleaut32.spec wine-develop/dlls/oleaut32/oleaut32.spec
--- wine/dlls/oleaut32/oleaut32.spec	2003-12-01 22:30:06.000000000 +0000
+++ wine-develop/dlls/oleaut32/oleaut32.spec	2003-12-02 06:42:01.000000000 +0000
@@ -163,7 +163,7 @@
 165 stdcall LHashValOfNameSys(long long wstr)
 166 stdcall LHashValOfNameSysA(long long str)
 167 stub VarXor # stdcall (ptr ptr ptr)
-168 stub VarAbs # stdcall (ptr ptr)
+168 stdcall VarAbs(ptr ptr)
 169 stub VarFix # stdcall (ptr ptr)
 170 stdcall OaBuildVersion()
 171 stub ClearCustData
@@ -357,3 +357,4 @@
 422 stub OleLoadPictureFile
 423 stub OleSavePictureFile
 424 stub OleLoadPicturePath
+@ stub VarI8FromI1
diff -ur --minimal wine/dlls/oleaut32/resource.h wine-develop/dlls/oleaut32/resource.h
--- wine/dlls/oleaut32/resource.h	2003-10-15 20:50:24.000000000 +0000
+++ wine-develop/dlls/oleaut32/resource.h	2003-12-02 07:55:28.000000000 +0000
@@ -20,11 +20,27 @@
 #ifndef WINE_OLEAUT32_RESOURCE_H
 #define WINE_OLEAUT32_RESOURCE_H
 
+/* Localised boolean text */
 #define IDS_TRUE  100
 #define IDS_FALSE 101
 #define IDS_YES   102
 #define IDS_NO    103
 #define IDS_ON    104
 #define IDS_OFF   105
+/* Alternative month names. Note in XP these are localised only for Russian,
+ * Polish and Arabic.
+ */
+#define IDS_MTH_1  106
+#define IDS_MTH_2  107
+#define IDS_MTH_3  108
+#define IDS_MTH_4  109
+#define IDS_MTH_5  110
+#define IDS_MTH_6  111
+#define IDS_MTH_7  112
+#define IDS_MTH_8  113
+#define IDS_MTH_9  114
+#define IDS_MTH_10 115
+#define IDS_MTH_11 116
+#define IDS_MTH_12 117
 
 #endif /* WINE_OLEAUT32_RESOURCE_H */
diff -ur --minimal wine/dlls/oleaut32/tests/vartest.c wine-develop/dlls/oleaut32/tests/vartest.c
--- wine/dlls/oleaut32/tests/vartest.c	2003-12-01 22:30:07.000000000 +0000
+++ wine-develop/dlls/oleaut32/tests/vartest.c	2003-12-02 07:36:37.000000000 +0000
@@ -19,10 +19,6 @@
  *
  * NOTES
  * - Does not test IDispatch, IUnknown, IRecordInfo, DECIMAL, CY, I8/UI8
- * - VarDateFromStr is not implemented yet.
- * - The date and floating point format may not be the exact same
- *   format has the one inwindows depending on what the Internatinal
- *   setting are in windows.
  */
 
 #include <stdarg.h>
@@ -32,10 +28,13 @@
 #include <float.h>
 #include <time.h>
 
+#define NONAMELESSUNION
+#define NONAMELESSSTRUCT
 #include "windef.h"
 #include "winbase.h"
 #include "winsock.h"
 #include "wine/test.h"
+#include "wine/unicode.h"
 #include "winuser.h"
 #include "wingdi.h"
 #include "winnls.h"
@@ -77,11 +76,23 @@
 static INT (WINAPI *pVariantTimeToSystemTime)(double,LPSYSTEMTIME);
 static INT (WINAPI *pDosDateTimeToVariantTime)(USHORT,USHORT,double*);
 static INT (WINAPI *pVariantTimeToDosDateTime)(double,USHORT*,USHORT *);
+static HRESULT (WINAPI *pVarFormatNumber)(LPVARIANT,int,int,int,int,ULONG,BSTR*);
+static HRESULT (WINAPI *pVarFormat)(LPVARIANT,LPOLESTR,int,int,ULONG,BSTR*);
 
 /* Get a conversion function ptr, return if function not available */
 #define CHECKPTR(func) p##func = (void*)GetProcAddress(hOleaut32, #func); \
   if (!p##func) { trace("function " # func " not available, not testing it\n"); return; }
 
+  /* Is a given function exported from oleaut32? */
+#define HAVE_FUNC(func) ((void*)GetProcAddress(hOleaut32, #func) != NULL)
+
+/* Have IRecordInfo data type? */
+#define HAVE_OLEAUT32_RECORD  HAVE_FUNC(SafeArraySetRecordInfo)
+/* Have CY data type? */
+#define HAVE_OLEAUT32_CY      HAVE_FUNC(VarCyAdd)
+/* Have I8/UI8 data type? */
+#define HAVE_OLEAUT32_I8      HAVE_FUNC(VarI8FromI1)
+
 /* When comparing floating point values we cannot expect an exact match
  * because the rounding errors depend on the exact algorithm.
  */
@@ -2267,30 +2278,14 @@
 	for (i = 0; i < NB_OLE_STRINGS; i++)
 	{
             *pDouble=42.0;
-            rc=VarDateFromStr( pOleChar[i], lcid, 0, pDouble );
-            if (strrets_DATE[i].todo_rc) {
-                todo_wine {
-                    ok(rc == strrets_DATE[i].error,
-                       "VarDateFromStr([%d]=\"%s\") rc= %lx instead of %lx",
-                       i,_pTestStrA[i],rc,strrets_DATE[i].error);
-                }
-            } else {
-                ok(rc == strrets_DATE[i].error,
-                   "VarDateFromStr([%d]=\"%s\") rc= %lx instead of %lx",
-                   i,_pTestStrA[i],rc,strrets_DATE[i].error);
-            }
+            rc=VarDateFromStr( pOleChar[i], lcid, LOCALE_NOUSEROVERRIDE, pDouble );
+            ok(i == 94 /* FIXME: Bug in native */ || rc == strrets_DATE[i].error,
+               "VarDateFromStr([%d]=\"%s\") rc= %lx instead of %lx",
+               i,_pTestStrA[i],rc,strrets_DATE[i].error);
             if (rc == 0 && strrets_DATE[i].error == 0) {
-	            if (strrets_DATE[i].todo_rc || strrets_DATE[i].todo_val) {
-		            todo_wine {
-                        ok(EQ_DOUBLE(*pDouble,strrets_DATE[i].retval),
-                           "VarDateFromStr([%d]=\"%s\") got %.15f instead of %.15f",
-                           i,_pTestStrA[i],*pDouble,strrets_DATE[i].retval);
-                    }
-	            } else {
-                    ok(EQ_DOUBLE(*pDouble,strrets_DATE[i].retval),
-                       "VarDateFromStr([%d]=\"%s\") got %.15f instead of %.15f",
-                       i,_pTestStrA[i],*pDouble,strrets_DATE[i].retval);
-                }
+               ok(EQ_DOUBLE(*pDouble,strrets_DATE[i].retval),
+                  "VarDateFromStr([%d]=\"%s\") got %.15f instead of %.15f",
+                  i,_pTestStrA[i],*pDouble,strrets_DATE[i].retval);
             }
 	}
 	/* bool from ...
@@ -2534,53 +2529,39 @@
 	ok(S_OK == VarBstrFromBool( 0xFF, lcid, 0, &bstr ), XOK);
 	ok(!strcmp(WtoA(bstr),"\"True\""),"should be 'True'");
 
-	ok(S_OK == VarBstrFromDate( 0.0, lcid, 0, &bstr ), XOK);
-	todo_wine {
-	    ok(!strcmp(WtoA(bstr),"\"12:00:00 AM\""),
-               "should be '12:00:00 AM', but is %s\n",WtoA(bstr));
-	}
+	ok(S_OK == VarBstrFromDate( 0.0, lcid, LOCALE_NOUSEROVERRIDE, &bstr ), XOK);
+	ok(!strcmp(WtoA(bstr),"\"12:00:00 AM\""),
+       "should be '12:00:00 AM', but is %s\n",WtoA(bstr));
 
-	ok(S_OK == VarBstrFromDate( 3.34, lcid, 0, &bstr ), XOK);
-	todo_wine {
-	    ok(strcmp(WtoA(bstr),"\"1/2/1900 8:09:36 AM\"")==0 ||
-           strcmp(WtoA(bstr),"\"1/2/00 8:09:36 AM\"")==0 /* Win95 */,
-               "should be '1/2/1900 8:09:36 AM', but is %s\n",WtoA(bstr));
-	}
+	ok(S_OK == VarBstrFromDate( 3.34, lcid, LOCALE_NOUSEROVERRIDE, &bstr ), XOK);
+	ok(strcmp(WtoA(bstr),"\"1/2/1900 8:09:36 AM\"")==0 ||
+       strcmp(WtoA(bstr),"\"1/2/00 8:09:36 AM\"")==0 /* Win95 */,
+       "should be '1/2/1900 8:09:36 AM', but is %s\n",WtoA(bstr));
 
-	ok(S_OK == VarBstrFromDate( 3339.34, lcid, 0, &bstr ), XOK);
-	todo_wine {
-	    ok(strcmp(WtoA(bstr),"\"2/20/1909 8:09:36 AM\"")==0 ||
-           strcmp(WtoA(bstr),"\"2/20/09 8:09:36 AM\"")==0 /* Win95 */,
-               "should be '2/20/1909 8:09:36 AM', but is %s\n",WtoA(bstr));
-	}
+	ok(S_OK == VarBstrFromDate( 3339.34, lcid, LOCALE_NOUSEROVERRIDE, &bstr ), XOK);
+	ok(strcmp(WtoA(bstr),"\"2/20/1909 8:09:36 AM\"")==0 ||
+       strcmp(WtoA(bstr),"\"2/20/09 8:09:36 AM\"")==0 /* Win95 */,
+       "should be '2/20/1909 8:09:36 AM', but is %s\n",WtoA(bstr));
 
-	ok(S_OK == VarBstrFromDate( 365.00, lcid, 0, &bstr ), XOK);
-	todo_wine {
-	    ok(strcmp(WtoA(bstr),"\"12/30/1900\"")==0 ||
-           strcmp(WtoA(bstr),"\"12/30/00\"")==0 /* Win95 */,
-               "should be '12/30/1900', but is %s\n",WtoA(bstr));
-	}
+	ok(S_OK == VarBstrFromDate( 365.00, lcid, LOCALE_NOUSEROVERRIDE, &bstr ), XOK);
+	ok(strcmp(WtoA(bstr),"\"12/30/1900\"")==0 ||
+       strcmp(WtoA(bstr),"\"12/30/00\"")==0 /* Win95 */,
+       "should be '12/30/1900', but is %s\n",WtoA(bstr));
 
-	ok(S_OK == VarBstrFromDate( 365.25, lcid, 0, &bstr ), XOK);
-	todo_wine {
-	    ok(strcmp(WtoA(bstr),"\"12/30/1900 6:00:00 AM\"")==0 ||
-           strcmp(WtoA(bstr),"\"12/30/00 6:00:00 AM\"")==0 /* Win95 */,
-               "should be '12/30/1900 6:00:00 AM', but is %s\n",WtoA(bstr));
-	}
+	ok(S_OK == VarBstrFromDate( 365.25, lcid, LOCALE_NOUSEROVERRIDE, &bstr ), XOK);
+	ok(strcmp(WtoA(bstr),"\"12/30/1900 6:00:00 AM\"")==0 ||
+       strcmp(WtoA(bstr),"\"12/30/00 6:00:00 AM\"")==0 /* Win95 */,
+       "should be '12/30/1900 6:00:00 AM', but is %s\n",WtoA(bstr));
 
-	ok(S_OK == VarBstrFromDate( 1461.0, lcid, 0, &bstr ), XOK);
-	todo_wine {
-	    ok(strcmp(WtoA(bstr),"\"12/31/1903\"")==0 ||
-           strcmp(WtoA(bstr),"\"12/31/03\"")==0 /* Win95 */,
-               "should be '12/31/1903', but is %s\n",WtoA(bstr));
-	}
+	ok(S_OK == VarBstrFromDate( 1461.0, lcid, LOCALE_NOUSEROVERRIDE, &bstr ), XOK);
+	ok(strcmp(WtoA(bstr),"\"12/31/1903\"")==0 ||
+       strcmp(WtoA(bstr),"\"12/31/03\"")==0 /* Win95 */,
+       "should be '12/31/1903', but is %s\n",WtoA(bstr));
 
-	ok(S_OK == VarBstrFromDate( 1461.5, lcid, 0, &bstr ), XOK);
-	todo_wine {
-	    ok(strcmp(WtoA(bstr),"\"12/31/1903 12:00:00 PM\"")==0 ||
-           strcmp(WtoA(bstr),"\"12/31/03 12:00:00 PM\"")==0 /* Win95 */,
-               "should be '12/31/1903 12:00:00 PM', but is %s\n",WtoA(bstr));
-	}
+	ok(S_OK == VarBstrFromDate( 1461.5, lcid, LOCALE_NOUSEROVERRIDE, &bstr ), XOK);
+	ok(strcmp(WtoA(bstr),"\"12/31/1903 12:00:00 PM\"")==0 ||
+       strcmp(WtoA(bstr),"\"12/31/03 12:00:00 PM\"")==0 /* Win95 */,
+       "should be '12/31/1903 12:00:00 PM', but is %s\n",WtoA(bstr));
 
 	/* Test variant API...
 	 */
@@ -2649,12 +2630,10 @@
 
 	V_VT(&va) = VT_DATE;
 	V_UNION(&va,date) = 34465.332431;
-	ok(S_OK == VariantChangeTypeEx(&vb, &va, lcid, 0, VT_BSTR ), XOK);
-	todo_wine {
-	    ok(strcmp(WtoA(V_BSTR(&vb)),"\"5/11/1994 7:58:42 AM\"")==0 ||
-           strcmp(WtoA(V_BSTR(&vb)),"\"5/11/94 7:58:42 AM\"")==0 /* Win95 */,
-           "should be 5/11/94 7:58:42 AM got %s",WtoA(V_BSTR(&vb)));
-	}
+	ok(S_OK == VariantChangeTypeEx(&vb, &va, lcid, VARIANT_NOUSEROVERRIDE, VT_BSTR ), XOK);
+	ok(strcmp(WtoA(V_BSTR(&vb)),"\"5/11/1994 7:58:42 AM\"")==0 ||
+       strcmp(WtoA(V_BSTR(&vb)),"\"5/11/94 7:58:42 AM\"")==0 /* Win95 */,
+       "should be 5/11/94 7:58:42 AM got %s",WtoA(V_BSTR(&vb)));
 
 	bstr = pOleChar[4];
 	V_VT(&va) = VT_BSTR;
@@ -2699,17 +2678,9 @@
 		    d = 4.123;
 		    V_UNION(&va,pdblVal) = &d;
 		    rc = VariantCopyInd( &vb, &va );
-            if (vartypes[i].todoind2) {
-                todo_wine {
-		            ok(vartypes[i].vcind2 == rc,
-                               "%d: vt %d, return value %lx, expected was %lx",
-                               i,vartypes[i].ind,rc,vartypes[i].vcind2);
-                }
-            } else {
 		        ok(vartypes[i].vcind2 == rc,
                            "%d: vt %d, return value %lx, expected was %lx",
                            i,vartypes[i].ind,rc,vartypes[i].vcind2);
-            }
 		    V_VT(&va) = VT_R8;
 		    d = 4.123;
 		    V_UNION(&va,dblVal) = d;
@@ -2788,6 +2759,26 @@
   VT_BYREF|VT_RESERVED,
 };
 
+/* Determine if a vt is valid for VariantClear() */
+static int IsValidVariantClearVT(VARTYPE vt, VARTYPE extraFlags)
+{
+  int ret = 0;
+
+  /* Only the following flags/types are valid */
+  if ((vt <= VT_LPWSTR || vt == VT_RECORD || vt == VT_CLSID) &&
+      vt != (VARTYPE)15 &&
+      (vt < (VARTYPE)24 || vt > (VARTYPE)31) &&
+      (!(extraFlags & (VT_BYREF|VT_ARRAY)) || vt > VT_NULL) &&
+      (extraFlags == 0 || extraFlags == VT_BYREF || extraFlags == VT_ARRAY ||
+       extraFlags == (VT_ARRAY|VT_BYREF)))
+    ret = 1; /* ok */
+
+  if ((vt == VT_RECORD && !HAVE_OLEAUT32_RECORD) ||
+      ((vt == VT_I8 || vt == VT_UI8) && !HAVE_OLEAUT32_I8))
+    ret = 0; /* Old versions of oleaut32 */
+  return ret;
+}
+
 static void test_VariantClear(void)
 {
   HRESULT hres;
@@ -2823,13 +2814,7 @@
 
       hres = VariantClear(&v);
 
-      /* Only the following flags/types are valid */
-      if ((vt <= VT_LPWSTR || vt == VT_RECORD || vt == VT_CLSID) &&
-          vt != (VARTYPE)15 &&
-          (vt < (VARTYPE)24 || vt > (VARTYPE)31) &&
-          (!(ExtraFlags[i] & (VT_BYREF|VT_ARRAY)) || vt > VT_NULL) &&
-          (ExtraFlags[i] == 0 || ExtraFlags[i] == VT_BYREF || ExtraFlags[i] == VT_ARRAY ||
-           ExtraFlags[i] == (VT_ARRAY|VT_BYREF)))
+      if (IsValidVariantClearVT(vt, ExtraFlags[i]))
         hExpected = S_OK;
 
       ok(hres == hExpected, "VariantClear: expected 0x%lX, got 0x%lX for vt %d | 0x%X\n",
@@ -2838,11 +2823,307 @@
   }
 }
 
+static void test_VariantCopy(void)
+{
+  VARIANTARG vSrc, vDst;
+  VARTYPE vt;
+  size_t i;
+  HRESULT hres, hExpected;
+
+  /* Establish that the failure/other cases are dealt with. Individual tests
+   * for each type should verify that data is copied correctly, references
+   * are updated, etc.
+   */
+
+  /* vSrc == vDst */
+  for (i = 0; i < sizeof(ExtraFlags)/sizeof(ExtraFlags[0]); i++)
+  {
+    for (vt = 0; vt <= VT_BSTR_BLOB; vt++)
+    {
+      memset(&vSrc, 0, sizeof(vSrc));
+      V_VT(&vSrc) = vt | ExtraFlags[i];
+
+      hExpected = DISP_E_BADVARTYPE;
+      /* src is allowed to be a VT_CLSID */
+      if (vt != VT_CLSID && IsValidVariantClearVT(vt, ExtraFlags[i]))
+        hExpected = S_OK;
+
+      hres = VariantCopy(&vSrc, &vSrc);
+
+      ok(hres == hExpected,
+         "Copy(src==dst): expected 0x%lX, got 0x%lX for src==dest vt %d|0x%X\n",
+         hExpected, hres, vt, ExtraFlags[i]);
+    }
+  }
+
+  /* Test that if VariantClear() fails on dest, the function fails. This also
+   * shows that dest is in fact cleared and not just overwritten
+   */
+  memset(&vSrc, 0, sizeof(vSrc));
+  V_VT(&vSrc) = VT_UI1;
+
+  for (i = 0; i < sizeof(ExtraFlags)/sizeof(ExtraFlags[0]); i++)
+  {
+    for (vt = 0; vt <= VT_BSTR_BLOB; vt++)
+    {
+      hExpected = DISP_E_BADVARTYPE;
+
+      memset(&vDst, 0, sizeof(vDst));
+      V_VT(&vDst) = vt | ExtraFlags[i];
+
+      if (IsValidVariantClearVT(vt, ExtraFlags[i]))
+        hExpected = S_OK;
+
+      hres = VariantCopy(&vDst, &vSrc);
+
+      ok(hres == hExpected,
+         "Copy(bad dst): expected 0x%lX, got 0x%lX for dest vt %d|0x%X\n",
+         hExpected, hres, vt, ExtraFlags[i]);
+      if (hres == S_OK)
+        ok(V_VT(&vDst) == VT_UI1,
+           "Copy(bad dst): expected vt = VT_UI1, got %d\n", V_VT(&vDst));
+    }
+  }
+
+  /* Test that VariantClear() checks vSrc for validity before copying */
+  for (i = 0; i < sizeof(ExtraFlags)/sizeof(ExtraFlags[0]); i++)
+  {
+    for (vt = 0; vt <= VT_BSTR_BLOB; vt++)
+    {
+      hExpected = DISP_E_BADVARTYPE;
+
+      memset(&vDst, 0, sizeof(vDst));
+      V_VT(&vDst) = VT_EMPTY;
+
+      memset(&vSrc, 0, sizeof(vSrc));
+      V_VT(&vSrc) = vt | ExtraFlags[i];
+
+      /* src is allowed to be a VT_CLSID */
+      if (vt != VT_CLSID && IsValidVariantClearVT(vt, ExtraFlags[i]))
+        hExpected = S_OK;
+
+      hres = VariantCopy(&vDst, &vSrc);
+
+      ok(hres == hExpected,
+         "Copy(bad src): expected 0x%lX, got 0x%lX for src vt %d|0x%X\n",
+         hExpected, hres, vt, ExtraFlags[i]);
+      if (hres == S_OK)
+        ok(V_VT(&vDst) == (vt|ExtraFlags[i]),
+           "Copy(bad src): expected vt = %d, got %d\n",
+           vt | ExtraFlags[i], V_VT(&vDst));
+    }
+  }
+}
+
+/* Determine if a vt is valid for VariantCopyInd() */
+static int IsValidVariantCopyIndVT(VARTYPE vt, VARTYPE extraFlags)
+{
+  int ret = 0;
+
+  if ((extraFlags & VT_ARRAY) ||
+     (vt > VT_NULL && vt != (VARTYPE)15 && vt < VT_VOID &&
+     !(extraFlags & (VT_VECTOR|VT_RESERVED))))
+  {
+    ret = 1; /* ok */
+  }
+  return ret;
+}
+
+static void test_VariantCopyInd(void)
+{
+  VARIANTARG vSrc, vDst, vRef, vRef2;
+  VARTYPE vt;
+  size_t i;
+  BYTE buffer[64];
+  HRESULT hres, hExpected;
+
+  memset(buffer, 0, sizeof(buffer));
+
+  /* vSrc == vDst */
+  for (i = 0; i < sizeof(ExtraFlags)/sizeof(ExtraFlags[0]); i++)
+  {
+    if (ExtraFlags[i] & VT_ARRAY)
+      continue; /* Native crashes on NULL safearray */
+
+    for (vt = 0; vt <= VT_BSTR_BLOB; vt++)
+    {
+      memset(&vSrc, 0, sizeof(vSrc));
+      V_VT(&vSrc) = vt | ExtraFlags[i];
+
+      hExpected = DISP_E_BADVARTYPE;
+      if (!(ExtraFlags[i] & VT_BYREF))
+      {
+        /* if src is not by-reference, acts as VariantCopy() */
+        if (vt != VT_CLSID && IsValidVariantClearVT(vt, ExtraFlags[i]))
+          hExpected = S_OK;
+      }
+      else
+      {
+        if (vt == VT_SAFEARRAY || vt == VT_BSTR || vt == VT_UNKNOWN ||
+            vt == VT_DISPATCH || vt == VT_RECORD)
+          continue; /* Need valid ptrs for deep copies */
+
+        V_BYREF(&vSrc) = &buffer;
+        hExpected = E_INVALIDARG;
+
+        if ((vt == VT_I8 || vt == VT_UI8) &&
+            ExtraFlags[i] == VT_BYREF)
+        {
+          if (HAVE_OLEAUT32_I8)
+            hExpected = S_OK; /* Only valid if I8 is a known type */
+        }
+        else if (IsValidVariantCopyIndVT(vt, ExtraFlags[i]))
+          hExpected = S_OK;
+      }
+
+      hres = VariantCopyInd(&vSrc, &vSrc);
+
+      ok(hres == hExpected,
+         "CopyInd(src==dst): expected 0x%lX, got 0x%lX for src==dst vt %d|0x%X\n",
+         hExpected, hres, vt, ExtraFlags[i]);
+    }
+  }
+
+  /* Bad dest */
+  memset(&vSrc, 0, sizeof(vSrc));
+  V_VT(&vSrc) = VT_UI1|VT_BYREF;
+  V_BYREF(&vSrc) = &buffer;
+
+  for (i = 0; i < sizeof(ExtraFlags)/sizeof(ExtraFlags[0]); i++)
+  {
+    for (vt = 0; vt <= VT_BSTR_BLOB; vt++)
+    {
+      memset(&vDst, 0, sizeof(vDst));
+      V_VT(&vDst) = vt | ExtraFlags[i];
+
+      hExpected = DISP_E_BADVARTYPE;
+
+      if (IsValidVariantClearVT(vt, ExtraFlags[i]))
+        hExpected = S_OK;
+
+      hres = VariantCopyInd(&vDst, &vSrc);
+
+      ok(hres == hExpected,
+         "CopyInd(bad dst): expected 0x%lX, got 0x%lX for dst vt %d|0x%X\n",
+         hExpected, hres, vt, ExtraFlags[i]);
+      if (hres == S_OK)
+        ok(V_VT(&vDst) == VT_UI1,
+           "CopyInd(bad dst): expected vt = VT_UI1, got %d\n", V_VT(&vDst));
+    }
+  }
+
+  /* bad src */
+  for (i = 0; i < sizeof(ExtraFlags)/sizeof(ExtraFlags[0]); i++)
+  {
+    if (ExtraFlags[i] & VT_ARRAY)
+      continue; /* Native crashes on NULL safearray */
+
+    for (vt = 0; vt <= VT_BSTR_BLOB; vt++)
+    {
+      memset(&vDst, 0, sizeof(vDst));
+      V_VT(&vDst) = VT_EMPTY;
+
+      memset(&vSrc, 0, sizeof(vSrc));
+      V_VT(&vSrc) = vt | ExtraFlags[i];
+
+      hExpected = DISP_E_BADVARTYPE;
+      if (!(ExtraFlags[i] & VT_BYREF))
+      {
+        /* if src is not by-reference, acts as VariantCopy() */
+        if (vt != VT_CLSID && IsValidVariantClearVT(vt, ExtraFlags[i]))
+          hExpected = S_OK;
+      }
+      else
+      {
+        if (vt == VT_SAFEARRAY || vt == VT_BSTR || vt == VT_UNKNOWN ||
+            vt == VT_DISPATCH || vt == VT_RECORD)
+          continue; /* Need valid ptrs for deep copies, see vartype.c */
+
+        V_BYREF(&vSrc) = &buffer;
+
+        hExpected = E_INVALIDARG;
+
+        if ((vt == VT_I8 || vt == VT_UI8) &&
+            ExtraFlags[i] == VT_BYREF)
+        {
+          if (HAVE_OLEAUT32_I8)
+            hExpected = S_OK; /* Only valid if I8 is a known type */
+        }
+        else if (IsValidVariantCopyIndVT(vt, ExtraFlags[i]))
+          hExpected = S_OK;
+      }
+
+      hres = VariantCopyInd(&vDst, &vSrc);
+
+      ok(hres == hExpected,
+         "CopyInd(bad src): expected 0x%lX, got 0x%lX for src vt %d|0x%X\n",
+         hExpected, hres, vt, ExtraFlags[i]);
+      if (hres == S_OK)
+      {
+        if (vt == VT_VARIANT && ExtraFlags[i] == VT_BYREF)
+        {
+          /* Type of vDst should be the type of the referenced variant.
+           * Since we set the buffer to all zeros, its type should be
+           * VT_EMPTY.
+           */
+          ok(V_VT(&vDst) == VT_EMPTY,
+             "CopyInd(bad src): expected dst vt = VT_EMPTY, got %d|0x%X\n",
+             V_VT(&vDst) & VT_TYPEMASK, V_VT(&vDst) & ~VT_TYPEMASK);
+        }
+        else
+        {
+          ok(V_VT(&vDst) == (vt|(ExtraFlags[i] & ~VT_BYREF)),
+             "CopyInd(bad src): expected dst vt = %d|0x%X, got %d|0x%X\n",
+             vt, ExtraFlags[i] & ~VT_BYREF,
+             V_VT(&vDst) & VT_TYPEMASK, V_VT(&vDst) & ~VT_TYPEMASK);
+        }
+      }
+    }
+  }
+
+  /* By-reference variants are dereferenced */
+  V_VT(&vRef) = VT_UI1;
+  V_UI1(&vRef) = 0x77;
+  V_VT(&vSrc) = VT_VARIANT|VT_BYREF;
+  V_VARIANTREF(&vSrc) = &vRef;
+  VariantInit(&vDst);
+
+  hres = VariantCopyInd(&vDst, &vSrc);
+  ok(V_VT(&vDst) == VT_UI1 && V_UI1(&vDst) == 0x77,
+     "CopyInd(deref): expected dst vt = VT_UI1, val 0x77, got %d|0x%X, 0x%2X\n",
+      V_VT(&vDst) & VT_TYPEMASK, V_VT(&vDst) & ~VT_TYPEMASK, V_UI1(&vDst));
+
+  /* By-reference variant to a by-reference type succeeds */
+  V_VT(&vRef) = VT_UI1|VT_BYREF;
+  V_UI1REF(&vRef) = buffer; buffer[0] = 0x88;
+  V_VT(&vSrc) = VT_VARIANT|VT_BYREF;
+  V_VARIANTREF(&vSrc) = &vRef;
+  VariantInit(&vDst);
+
+  hres = VariantCopyInd(&vDst, &vSrc);
+  ok(V_VT(&vDst) == VT_UI1 && V_UI1(&vDst) == 0x88,
+     "CopyInd(deref): expected dst vt = VT_UI1, val 0x77, got %d|0x%X, 0x%2X\n",
+      V_VT(&vDst) & VT_TYPEMASK, V_VT(&vDst) & ~VT_TYPEMASK, V_UI1(&vDst));
+
+  /* But a by-reference variant to a by-reference variant fails */
+  V_VT(&vRef2) = VT_UI1;
+  V_UI1(&vRef2) = 0x77;
+  V_VT(&vRef) = VT_VARIANT|VT_BYREF;
+  V_VARIANTREF(&vRef) = &vRef2;
+  V_VT(&vSrc) = VT_VARIANT|VT_BYREF;
+  V_VARIANTREF(&vSrc) = &vRef;
+  VariantInit(&vDst);
+
+  hres = VariantCopyInd(&vDst, &vSrc);
+  ok(hres == E_INVALIDARG,
+     "CopyInd(ref->ref): expected E_INVALIDARG, got 0x%08lx\n", hres);
+}
+
 /* Macros for converting and testing the result of VarParseNumFromStr */
 #define FAILDIG 255
 #define CONVERTN(str,dig,flags) MultiByteToWideChar(CP_ACP,0,str,-1,buff,sizeof(buff)); \
   memset(rgb, FAILDIG, sizeof(rgb)); memset(&np,-1,sizeof(np)); np.cDig = dig; np.dwInFlags = flags; \
-  hres = VarParseNumFromStr(buff,lcid,0,&np,rgb)
+  hres = VarParseNumFromStr(buff,lcid,LOCALE_NOUSEROVERRIDE,&np,rgb)
 #define CONVERT(str,flags) CONVERTN(str,sizeof(rgb),flags)
 #define EXPECT(a,b,c,d,e,f) ok(hres == (HRESULT)S_OK, "Call failed, hres = %08lx\n", hres); \
   if (hres == (HRESULT)S_OK) { \
@@ -3518,6 +3799,450 @@
   DT2DOS(29221.95833333333,1,1,1,1980,23,0,0); /* 1/1/1980 11:00:00 PM */
 }
 
+#define FMT_NUMBER(vt,val) \
+  VariantInit(&v); V_VT(&v) = vt; val(&v) = 1; \
+  hres = pVarFormatNumber(&v,2,0,0,0,0,&str); \
+  ok(hres == S_OK, "VarFormatNumber (vt %d): returned %8lx\n", vt, hres); \
+  if (hres == S_OK) \
+    ok(str && strcmpW(str,szResult1) == 0, \
+       "VarFormatNumber (vt %d): string different\n", vt)
+
+static void test_VarFormatNumber(void)
+{
+  static WCHAR szSrc1[] = { '1','\0' };
+  static WCHAR szResult1[] = { '1','.','0','0','\0' };
+  static WCHAR szSrc2[] = { '-','1','\0' };
+  static WCHAR szResult2[] = { '(','1','.','0','0',')','\0' };
+  char buff[8];
+  HRESULT hres;
+  VARIANT v;
+  BSTR str = NULL;
+
+  CHECKPTR(VarFormatNumber);
+
+  GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, buff, sizeof(buff)/sizeof(char));
+  if (buff[0] != '.' || buff[1])
+  {
+    trace("Skipping VarFormatNumber tests as decimal seperator is '%s'\n", buff);
+    return;
+  }
+
+  FMT_NUMBER(VT_I1, V_I1);
+  FMT_NUMBER(VT_UI1, V_UI1);
+  FMT_NUMBER(VT_I2, V_I2);
+  FMT_NUMBER(VT_UI2, V_UI2);
+  FMT_NUMBER(VT_I4, V_I4);
+  FMT_NUMBER(VT_UI4, V_UI4);
+  todo_wine {
+  FMT_NUMBER(VT_I8, V_I8);
+  FMT_NUMBER(VT_UI8, V_UI8);
+  }
+  FMT_NUMBER(VT_R4, V_R4);
+  FMT_NUMBER(VT_R8, V_R8);
+  FMT_NUMBER(VT_BOOL, V_BOOL);
+
+  V_VT(&v) = VT_BSTR;
+  V_BSTR(&v) = SysAllocString(szSrc1);
+
+  hres = pVarFormatNumber(&v,2,0,0,0,0,&str);
+  ok(hres == S_OK, "VarFormatNumber (bstr): returned %8lx\n", hres);
+  if (hres == S_OK)
+    ok(str && strcmpW(str, szResult1) == 0, "VarFormatNumber (bstr): string different\n");
+  SysFreeString(V_BSTR(&v));
+  SysFreeString(str);
+
+  V_BSTR(&v) = SysAllocString(szSrc2);
+  hres = pVarFormatNumber(&v,2,0,-1,0,0,&str);
+  ok(hres == S_OK, "VarFormatNumber (bstr): returned %8lx\n", hres);
+  if (hres == S_OK)
+    ok(str && strcmpW(str, szResult2) == 0, "VarFormatNumber (-bstr): string different\n");
+  SysFreeString(V_BSTR(&v));
+  SysFreeString(str);
+}
+
+#define SIGNED_VTBITS (VTBIT_I1|VTBIT_I2|VTBIT_I4|VTBIT_I8|VTBIT_R4|VTBIT_R8)
+
+#define VARFMT(vt,v,val,fmt,ret,str) do { \
+  if (out) SysFreeString(out); out = NULL; \
+  V_VT(&in) = (vt); v(&in) = val; \
+  if (fmt) MultiByteToWideChar(CP_ACP, 0, fmt, -1, buffW, sizeof(buffW)/sizeof(WCHAR)); \
+  hres = pVarFormat(&in,fmt ? buffW : NULL,fd,fw,flags,&out); \
+  if (SUCCEEDED(hres)) WideCharToMultiByte(CP_ACP, 0, out, -1, buff, sizeof(buff),0,0); \
+  else buff[0] = '\0'; \
+  ok(hres == ret && (FAILED(ret) || !strcmp(buff, str)), \
+     "VT %d|0x%04x Format %s: expected 0x%08lx, '%s', got 0x%08lx, '%s'\n", \
+     (vt)&VT_TYPEMASK,(vt)&~VT_TYPEMASK,fmt?fmt:"<null>",ret,str,hres,buff); \
+  } while(0)
+
+typedef struct tagFMTRES
+{
+  LPCSTR fmt;
+  LPCSTR one_res;
+  LPCSTR zero_res;
+} FMTRES;
+
+static const FMTRES VarFormat_results[] =
+{
+  { NULL, "1", "0" },
+  { "", "1", "0" },
+  { "General Number", "1", "0" },
+  { "Percent", "100.00%", "0.00%" },
+  { "Standard", "1.00", "0.00" },
+  { "Scientific","1.00E+00", "0.00E+00" },
+  { "True/False", "True", "False" },
+/*  { "On/Off", "On", "Off" },
+  { "Yes/No", "Yes", "No")}, */
+  { "#", "1", "" },
+  { "##", "1", "" },
+  { "#.#", "1.", "." },
+  { "0", "1", "0" },
+  { "00", "01", "00" },
+  { "0.0", "1.0", "0.0" },
+  { "00\\c\\o\\p\\y", "01copy","00copy" },
+  { "\"pos\";\"neg\"", "pos", "pos" },
+  { "\"pos\";\"neg\";\"zero\"","pos", "zero" }
+};
+
+typedef struct tagFMTDATERES
+{
+  DATE   val;
+  LPCSTR fmt;
+  LPCSTR res;
+} FMTDATERES;
+
+static const FMTDATERES VarFormat_date_results[] =
+{
+  { 0.0, "w", "7" },
+  { 0.0, "w", "6" },
+  { 0.0, "w", "5" },
+  { 0.0, "w", "4" },
+  { 0.0, "w", "3" },
+  { 0.0, "w", "2" },
+  { 0.0, "w", "1" }, /* First 7 entries must remain in this order! */
+  { 2.525, "am/pm", "pm" },
+  { 2.525, "AM/PM", "PM" },
+  { 2.525, "A/P", "P" },
+  { 2.525, "a/p", "p" },
+  { 2.525, "q", "1" },
+  { 2.525, "d", "1" },
+  { 2.525, "dd", "01" },
+  { 2.525, "ddd", "Mon" },
+  { 2.525, "dddd", "Monday" },
+  { 2.525, "mmm", "Jan" },
+  { 2.525, "mmmm", "January" },
+  { 2.525, "y", "1" },
+  { 2.525, "yy", "00" },
+  { 2.525, "yyy", "001" },
+  { 2.525, "yyyy", "1900" },
+  { 2.525, "dd mm yyyy hh:mm:ss", "01 01 1900 12:36:00" },
+  { 2.525, "dd mm yyyy mm", "01 01 1900 01" },
+  { 2.525, "dd mm yyyy :mm", "01 01 1900 :01" },
+  { 2.525, "dd mm yyyy hh:mm", "01 01 1900 12:36" },
+  { 2.525, "mm mm", "01 01" },
+  { 2.525, "mm :mm:ss", "01 :01:00" },
+  { 2.525, "mm :ss:mm", "01 :00:01" },
+  { 2.525, "hh:mm :ss:mm", "12:36 :00:01" },
+  { 2.525, "hh:dd :mm:mm", "12:01 :01:01" },
+  { 2.525, "dd:hh :mm:mm", "01:12 :36:01" },
+  { 2.525, "hh :mm:mm", "12 :36:01" },
+  { 2.525, "dd :mm:mm", "01 :01:01" },
+  { 2.525, "dd :mm:nn", "01 :01:36" },
+  { 2.725, "hh:nn:ss A/P", "05:24:00 P" }
+};
+
+#define VNUMFMT(vt,v) \
+  for (i = 0; i < sizeof(VarFormat_results)/sizeof(FMTRES); i++) \
+  { \
+    VARFMT(vt,v,1,VarFormat_results[i].fmt,S_OK,VarFormat_results[i].one_res); \
+    VARFMT(vt,v,0,VarFormat_results[i].fmt,S_OK,VarFormat_results[i].zero_res); \
+  } \
+  if ((1 << vt) & SIGNED_VTBITS) \
+  { \
+    VARFMT(vt,v,-1,"\"pos\";\"neg\"",S_OK,"neg"); \
+    VARFMT(vt,v,-1,"\"pos\";\"neg\";\"zero\"",S_OK,"neg"); \
+  }
+
+static void test_VarFormat(void)
+{
+  static const WCHAR szTesting[] = { 't','e','s','t','i','n','g','\0' };
+  size_t i;
+  WCHAR buffW[256];
+  char buff[256];
+  VARIANT in;
+  VARIANT_BOOL bTrue = VARIANT_TRUE, bFalse = VARIANT_FALSE;
+  int fd = 0, fw = 0;
+  ULONG flags = 0;
+  BSTR bstrin, out = NULL;
+  HRESULT hres;
+
+  CHECKPTR(VarFormat);
+
+  if (PRIMARYLANGID(LANGIDFROMLCID(GetUserDefaultLCID())) != LANG_ENGLISH)
+  {
+    trace("Skipping VarFormat tests for non english language\n");
+    return;
+  }
+  GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, buff, sizeof(buff)/sizeof(char));
+  if (buff[0] != '.' || buff[1])
+  {
+    trace("Skipping VarFormat tests as decimal seperator is '%s'\n", buff);
+    return;
+  }
+  GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_IDIGITS, buff, sizeof(buff)/sizeof(char));
+  if (buff[0] != '2' || buff[1])
+  {
+    trace("Skipping VarFormat tests as decimal places is '%s'\n", buff);
+    return;
+  }
+
+  VARFMT(VT_BOOL,V_BOOL,VARIANT_TRUE,"True/False",S_OK,"True");
+  VARFMT(VT_BOOL,V_BOOL,VARIANT_FALSE,"True/False",S_OK,"False");
+
+  VNUMFMT(VT_I1,V_I1);
+  VNUMFMT(VT_I2,V_I2);
+  VNUMFMT(VT_I4,V_I4);
+  todo_wine {
+  VNUMFMT(VT_I8,V_I8);
+  }
+  VNUMFMT(VT_INT,V_INT);
+  VNUMFMT(VT_UI1,V_UI1);
+  VNUMFMT(VT_UI2,V_UI2);
+  VNUMFMT(VT_UI4,V_UI4);
+  todo_wine {
+  VNUMFMT(VT_UI8,V_UI8);
+  }
+  VNUMFMT(VT_UINT,V_UINT);
+  VNUMFMT(VT_R4,V_R4);
+  VNUMFMT(VT_R8,V_R8);
+
+  /* Reference types are dereferenced */
+  VARFMT(VT_BOOL|VT_BYREF,V_BOOLREF,&bTrue,"True/False",S_OK,"True");
+  VARFMT(VT_BOOL|VT_BYREF,V_BOOLREF,&bFalse,"True/False",S_OK,"False");
+
+  /* Dates */
+  for (i = 0; i < sizeof(VarFormat_date_results)/sizeof(FMTDATERES); i++)
+  {
+    if (i < 7)
+      fd = i + 1; /* Test first day */
+    else
+      fd = 0;
+    VARFMT(VT_DATE,V_DATE,VarFormat_date_results[i].val,
+           VarFormat_date_results[i].fmt,S_OK,
+           VarFormat_date_results[i].res);
+  }
+
+  /* Strings */
+  bstrin = SysAllocString(szTesting);
+  VARFMT(VT_BSTR,V_BSTR,bstrin,"",S_OK,"testing");
+  VARFMT(VT_BSTR,V_BSTR,bstrin,"@",S_OK,"testing");
+  VARFMT(VT_BSTR,V_BSTR,bstrin,"&",S_OK,"testing");
+  VARFMT(VT_BSTR,V_BSTR,bstrin,"\\x@\\x@",S_OK,"xtxesting");
+  VARFMT(VT_BSTR,V_BSTR,bstrin,"\\x&\\x&",S_OK,"xtxesting");
+  VARFMT(VT_BSTR,V_BSTR,bstrin,"@\\x",S_OK,"txesting");
+  VARFMT(VT_BSTR,V_BSTR,bstrin,"@@@@@@@@",S_OK," testing");
+  VARFMT(VT_BSTR,V_BSTR,bstrin,"@\\x@@@@@@@",S_OK," xtesting");
+  VARFMT(VT_BSTR,V_BSTR,bstrin,"&&&&&&&&",S_OK,"testing");
+  VARFMT(VT_BSTR,V_BSTR,bstrin,"!&&&&&&&",S_OK,"testing");
+  VARFMT(VT_BSTR,V_BSTR,bstrin,"&&&&&&&!",S_OK,"testing");
+  VARFMT(VT_BSTR,V_BSTR,bstrin,">&&",S_OK,"TESTING");
+  VARFMT(VT_BSTR,V_BSTR,bstrin,"<&&",S_OK,"testing");
+  VARFMT(VT_BSTR,V_BSTR,bstrin,"<&>&",S_OK,"testing");
+  SysFreeString(bstrin);
+  /* Numeric values are converted to strings then output */
+  VARFMT(VT_I1,V_I1,1,"<&>&",S_OK,"1");
+
+  /* 'out' is not cleared */
+  out = (BSTR)0x1;
+  pVarFormat(&in,NULL,fd,fw,flags,&out); /* Would crash if out is cleared */
+  out = NULL;
+
+  /* Invalid args */
+  hres = pVarFormat(&in,NULL,fd,fw,flags,NULL);
+  ok(hres == E_INVALIDARG, "Null out: expected E_INVALIDARG, got 0x%08lx\n", hres);
+  hres = pVarFormat(NULL,NULL,fd,fw,flags,&out);
+  ok(hres == E_INVALIDARG, "Null in: expected E_INVALIDARG, got 0x%08lx\n", hres);
+  fd = -1;
+  VARFMT(VT_BOOL,V_BOOL,VARIANT_TRUE,"",E_INVALIDARG,"");
+  fd = 8;
+  VARFMT(VT_BOOL,V_BOOL,VARIANT_TRUE,"",E_INVALIDARG,"");
+  fd = 0; fw = -1;
+  VARFMT(VT_BOOL,V_BOOL,VARIANT_TRUE,"",E_INVALIDARG,"");
+  fw = 4;
+  VARFMT(VT_BOOL,V_BOOL,VARIANT_TRUE,"",E_INVALIDARG,"");
+}
+
+static HRESULT (WINAPI *pVarAbs)(LPVARIANT,LPVARIANT);
+
+#define VARABS(vt,val,rvt,rval) V_VT(&v) = VT_##vt; V_##vt(&v) = val; \
+        memset(&vDst,0,sizeof(vDst)); hres = pVarAbs(&v,&vDst); \
+        ok(hres == S_OK && V_VT(&vDst) == VT_##rvt && V_##rvt(&vDst) == (rval), \
+           "VarAbs: expected 0x0,%d,%d, got 0x%lX,%d,%d\n", VT_##rvt, (int)(rval), \
+           hres, V_VT(&vDst), (int)V_##rvt(&vDst))
+
+static void test_VarAbs(void)
+{
+    static const WCHAR szNum[] = {'-','1','.','1','\0' };
+    HRESULT hres;
+    VARIANT v, vDst;
+    size_t i;
+
+    CHECKPTR(VarAbs);
+
+    /* Test all possible V_VT values.
+     */
+    for (i = 0; i < sizeof(ExtraFlags)/sizeof(ExtraFlags[0]); i++)
+    {
+        VARTYPE vt;
+
+        for (vt = 0; vt <= VT_BSTR_BLOB; vt++)
+        {
+            HRESULT hExpected = DISP_E_BADVARTYPE;
+
+            memset(&v, 0, sizeof(v));
+            V_VT(&v) = vt | ExtraFlags[i];
+            V_VT(&vDst) = VT_EMPTY;
+
+            hres = pVarAbs(&v,&vDst);
+            if (ExtraFlags[i] & (VT_ARRAY|VT_ARRAY) ||
+                (!ExtraFlags[i] && (vt == VT_UNKNOWN || vt == VT_BSTR ||
+                 vt == VT_DISPATCH || vt == VT_ERROR || vt == VT_RECORD)))
+            {
+                hExpected = DISP_E_TYPEMISMATCH;
+            }
+            else if (ExtraFlags[i] || vt >= VT_CLSID || vt == VT_VARIANT)
+            {
+                hExpected = DISP_E_BADVARTYPE;
+            }
+            else if (IsValidVariantClearVT(vt, ExtraFlags[i]))
+                hExpected = S_OK;
+
+            /* Native always fails on some vartypes that should be valid. don't
+             * check that Wine does the same; these are bugs in native.
+             */
+            if (vt == VT_I8 || vt == VT_UI8 || vt == VT_INT || vt == VT_UINT ||
+                vt == VT_I1 || vt == VT_UI2 || vt == VT_UI4)
+                continue;
+            ok(hres == hExpected, "VarAbs: expected 0x%lX, got 0x%lX for vt %d | 0x%X\n",
+               hExpected, hres, vt, ExtraFlags[i]);
+        }
+    }
+
+    /* BOOL->I2, BSTR->R8, all others remain the same */
+    VARABS(BOOL,VARIANT_TRUE,I2,-VARIANT_TRUE);
+    VARABS(BOOL,VARIANT_FALSE,I2,VARIANT_FALSE);
+    VARABS(I2,1,I2,1);
+    VARABS(I2,-1,I2,1);
+    VARABS(I4,1,I4,1);
+    VARABS(I4,-1,I4,1);
+    VARABS(UI1,1,UI1,1);
+    VARABS(R4,1,R4,1);
+    VARABS(R4,-1,R4,1);
+    VARABS(R8,1,R8,1);
+    VARABS(R8,-1,R8,1);
+    V_VT(&v) = VT_BSTR;
+    V_BSTR(&v) = (BSTR)szNum;
+    memset(&vDst,0,sizeof(vDst));
+    hres = pVarAbs(&v,&vDst);
+    ok(hres == S_OK && V_VT(&vDst) == VT_R8 && V_R8(&vDst) == 1.1,
+       "VarAbs: expected 0x0,%d,%g, got 0x%lX,%d,%g\n", VT_R8, 1.1, hres, V_VT(&vDst), V_R8(&vDst));
+}
+
+static HRESULT (WINAPI *pVarNot)(LPVARIANT,LPVARIANT);
+
+#define VARNOT(vt,val,rvt,rval) V_VT(&v) = VT_##vt; V_##vt(&v) = val; \
+        memset(&vDst,0,sizeof(vDst)); hres = pVarNot(&v,&vDst); \
+        ok(hres == S_OK && V_VT(&vDst) == VT_##rvt && V_##rvt(&vDst) == (rval), \
+        "VarNot: expected 0x0,%d,%d, got 0x%lX,%d,%d\n", VT_##rvt, (int)(rval), \
+        hres, V_VT(&vDst), (int)V_##rvt(&vDst))
+
+static void test_VarNot(void)
+{
+    static const WCHAR szNum0[] = {'0','\0' };
+    static const WCHAR szNum1[] = {'1','\0' };
+    HRESULT hres;
+    VARIANT v, vDst;
+    DECIMAL *pdec = &V_DECIMAL(&v);
+    CY *pcy = &V_CY(&v);
+    size_t i;
+
+    CHECKPTR(VarNot);
+
+    /* Test all possible V_VT values */
+    for (i = 0; i < sizeof(ExtraFlags)/sizeof(ExtraFlags[0]); i++)
+    {
+        VARTYPE vt;
+
+        for (vt = 0; vt <= VT_BSTR_BLOB; vt++)
+        {
+            HRESULT hExpected = DISP_E_BADVARTYPE;
+
+            memset(&v, 0, sizeof(v));
+            V_VT(&v) = vt | ExtraFlags[i];
+            V_VT(&vDst) = VT_EMPTY;
+
+            hres = pVarNot(&v,&vDst);
+            switch (V_VT(&v))
+            {
+            case VT_I1:  case VT_UI1: case VT_I2:  case VT_UI2:
+            case VT_INT: case VT_UINT: case VT_I4:  case VT_UI4:
+            case VT_I8:  case VT_UI8: case VT_R4:  case VT_R8:
+            case VT_DECIMAL: case VT_BOOL: case VT_NULL: case VT_EMPTY:
+            case VT_DATE: case VT_CY:
+                hExpected = S_OK;
+                break;
+            case VT_UNKNOWN: case VT_BSTR: case VT_DISPATCH: case VT_ERROR:
+            case VT_RECORD:
+                hExpected = DISP_E_TYPEMISMATCH;
+                break;
+            default:
+                if (IsValidVariantClearVT(vt, ExtraFlags[i]) && vt != VT_CLSID)
+                   hExpected = DISP_E_TYPEMISMATCH;
+                break;
+            }
+
+            hres = pVarNot(&v,&vDst);
+            if (V_VT(&v) == VT_DECIMAL)
+            {
+              todo_wine {
+              ok(hres == hExpected, "VarNot: expected 0x%lX, got 0x%lX vt %d|0x%X\n",
+                 hExpected, hres, vt, ExtraFlags[i]);
+              }
+            }
+            else
+              ok(hres == hExpected, "VarNot: expected 0x%lX, got 0x%lX vt %d|0x%X\n",
+                 hExpected, hres, vt, ExtraFlags[i]);
+        }
+    }
+    /* R4,R8,BSTR,DECIMAL,CY->I4, all others remain the same */
+    VARNOT(BOOL,VARIANT_TRUE,BOOL,VARIANT_FALSE);
+    VARNOT(BOOL,VARIANT_FALSE,BOOL,VARIANT_TRUE);
+    VARNOT(I2,-1,I2,0);
+    VARNOT(I2,0,I2,-1);
+    VARNOT(I2,1,I2,-2);
+    VARNOT(I4,1,I4,-2);
+    VARNOT(I4,0,I4,-1);
+    VARNOT(UI1,1,UI1,254);
+    VARNOT(UI1,0,UI1,255);
+    VARNOT(R4,1,I4,-2);
+    VARNOT(R4,0,I4,-1);
+    VARNOT(R8,1,I4,-2);
+    VARNOT(R8,0,I4,-1);
+    VARNOT(BSTR,(BSTR)szNum0,I4,-1);
+    VARNOT(BSTR,(BSTR)szNum1,I4,-2);
+
+    todo_wine {
+    V_VT(&v) = VT_DECIMAL;
+    pdec->u.s.sign = DECIMAL_NEG;
+    pdec->u.s.scale = 0;
+    pdec->Hi32 = 0;
+    pdec->u1.s1.Mid32 = 0;
+    pdec->u1.s1.Lo32 = 1;
+    VARNOT(DECIMAL,*pdec,I4,0);
+    pcy->int64 = 10000;
+    VARNOT(CY,*pcy,I4,-2);
+    }
+}
+
 START_TEST(vartest)
 {
   hOleaut32 = LoadLibraryA("oleaut32.dll");
@@ -3525,6 +4250,8 @@
   test_variant();
   test_VariantInit();
   test_VariantClear();
+  test_VariantCopy();
+  test_VariantCopyInd();
   test_VarParseNumFromStr();
   test_VarNumFromParseNum();
   test_VarUdateFromDate();
@@ -3533,4 +4260,8 @@
   test_VariantTimeToSystemTime();
   test_DosDateTimeToVariantTime();
   test_VariantTimeToDosDateTime();
+  test_VarFormatNumber();
+  test_VarFormat();
+  test_VarAbs();
+  test_VarNot();
 }
diff -ur --minimal wine/dlls/oleaut32/variant.c wine-develop/dlls/oleaut32/variant.c
--- wine/dlls/oleaut32/variant.c	2003-12-01 22:30:07.000000000 +0000
+++ wine-develop/dlls/oleaut32/variant.c	2003-12-02 07:40:56.000000000 +0000
@@ -20,20 +20,6 @@
  * You should have received a copy of the GNU Lesser General Public
  * License along with this library; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- *
- * NOTES
- *   This implements the low-level and hi-level APIs for manipulating VARIANTs.
- *   The low-level APIs are used to do data coercion between different data types.
- *   The hi-level APIs are built on top of these low-level APIs and handle
- *   initialization, copying, destroying and changing the type of VARIANTs.
- *
- * TODO:
- *   - The Variant APIs do not support international languages, currency
- *     types, number formating and calendar.  They only support U.S. English format.
- *   - The Variant APIs do not the following types: IUknown, IDispatch, DECIMAL and SafeArray.
- *   - The parsing of date for the VarDateFromStr is not complete.
- *   - The date manipulations do not support dates prior to 1900.
- *   - The parsing does not accept as many formats as the Windows implementation.
  */
 
 #include "config.h"
@@ -59,7 +45,6 @@
 #include "wine/debug.h"
 #include "wine/unicode.h"
 #include "winerror.h"
-#include "parsedt.h"
 #include "typelib.h"
 #include "winternl.h"
 #include "variant.h"
@@ -116,471 +101,6 @@
 #define BUFFER_MAX 1024
 static char pBuffer[BUFFER_MAX];
 
-/*
- * Note a leap year is one that is a multiple of 4
- * but not of a 100.  Except if it is a multiple of
- * 400 then it is a leap year.
- */
-
-/*
- * Use 365 days/year and a manual calculation for leap year days
- * to keep arithmetic simple
- */
-static const double DAYS_IN_ONE_YEAR = 365.0;
-
-/******************************************************************************
- *	   DateTimeStringToTm	[INTERNAL]
- *
- * Converts a string representation of a date and/or time to a tm structure.
- *
- * Note this function uses the postgresql date parsing functions found
- * in the parsedt.c file.
- *
- * Returns TRUE if successful.
- *
- * Note: This function does not parse the day of the week,
- * daylight savings time. It will only fill the followin fields in
- * the tm struct, tm_sec, tm_min, tm_hour, tm_year, tm_day, tm_mon.
- *
- ******************************************************************************/
-static BOOL DateTimeStringToTm( OLECHAR* strIn, DWORD dwFlags, struct tm* pTm )
-{
-	BOOL res = FALSE;
-	double		fsec;
-	int 		tzp;
-	int 		dtype;
-	int 		nf;
-	char	   *field[MAXDATEFIELDS];
-	int 		ftype[MAXDATEFIELDS];
-	char		lowstr[MAXDATELEN + 1];
-	char* strDateTime = NULL;
-
-	/* Convert the string to ASCII since this is the only format
-	 * postgesql can handle.
-	 */
-	strDateTime = HEAP_strdupWtoA( GetProcessHeap(), 0, strIn );
-
-	if( strDateTime != NULL )
-	{
-		/* Make sure we don't go over the maximum length
-		 * accepted by postgesql.
-		 */
-		if( strlen( strDateTime ) <= MAXDATELEN )
-		{
-			if( ParseDateTime( strDateTime, lowstr, field, ftype, MAXDATEFIELDS, &nf) == 0 )
-			{
-				if( dwFlags & VAR_DATEVALUEONLY )
-				{
-					/* Get the date information.
-					 * It returns 0 if date information was
-					 * present and 1 if only time information was present.
-					 * -1 if an error occures.
-					 */
-					if( DecodeDateTime(field, ftype, nf, &dtype, pTm, &fsec, &tzp) == 0 )
-					{
-						/* Eliminate the time information since we
-						 * were asked to get date information only.
-						 */
-						pTm->tm_sec = 0;
-						pTm->tm_min = 0;
-						pTm->tm_hour = 0;
-						res = TRUE;
-					}
-				}
-				if( dwFlags & VAR_TIMEVALUEONLY )
-				{
-					/* Get time information only.
-					 */
-					if( DecodeTimeOnly(field, ftype, nf, &dtype, pTm, &fsec) == 0 )
-					{
-						res = TRUE;
-					}
-				}
-				else
-				{
-					/* Get both date and time information.
-					 * It returns 0 if date information was
-					 * present and 1 if only time information was present.
-					 * -1 if an error occures.
-					 */
-					if( DecodeDateTime(field, ftype, nf, &dtype, pTm, &fsec, &tzp) != -1 )
-					{
-						res = TRUE;
-					}
-				}
-			}
-		}
-		HeapFree( GetProcessHeap(), 0, strDateTime );
-	}
-
-	return res;
-}
-
-
-
-
-
-
-/******************************************************************************
- *	   TmToDATE 	[INTERNAL]
- *
- * The date is implemented using an 8 byte floating-point number.
- * Days are represented by whole numbers increments starting with 0.00 has
- * being December 30 1899, midnight.
- * The hours are expressed as the fractional part of the number.
- * December 30 1899 at midnight = 0.00
- * January 1 1900 at midnight = 2.00
- * January 4 1900 at 6 AM = 5.25
- * January 4 1900 at noon = 5.50
- * December 29 1899 at midnight = -1.00
- * December 18 1899 at midnight = -12.00
- * December 18 1899 at 6AM = -12.25
- * December 18 1899 at 6PM = -12.75
- * December 19 1899 at midnight = -11.00
- * The tm structure is as follows:
- * struct tm {
- *		  int tm_sec;	   seconds after the minute - [0,59]
- *		  int tm_min;	   minutes after the hour - [0,59]
- *		  int tm_hour;	   hours since midnight - [0,23]
- *		  int tm_mday;	   day of the month - [1,31]
- *		  int tm_mon;	   months since January - [0,11]
- *		  int tm_year;	   years
- *		  int tm_wday;	   days since Sunday - [0,6]
- *		  int tm_yday;	   days since January 1 - [0,365]
- *		  int tm_isdst;    daylight savings time flag
- *		  };
- *
- * Note: This function does not use the tm_wday, tm_yday, tm_wday,
- * and tm_isdst fields of the tm structure. And only converts years
- * after 1900.
- *
- * Returns TRUE if successful.
- */
-static BOOL TmToDATE( struct tm* pTm, DATE *pDateOut )
-{
-    int leapYear = 0;
-
-    /* Hmmm... An uninitialized Date in VB is December 30 1899 so
-       Start at 0. This is the way DATE is defined. */
-
-    /* Start at 1. This is the way DATE is defined.
-     * January 1, 1900 at Midnight is 1.00.
-     * January 1, 1900 at 6AM is 1.25.
-     * and so on.
-     */
-    *pDateOut = 1;
-
-    if( (pTm->tm_year - 1900) >= 0 ) {
-
-        /* Add the number of days corresponding to
-         * tm_year.
-         */
-        *pDateOut += (pTm->tm_year - 1900) * 365;
-
-        /* Add the leap days in the previous years between now and 1900.
-         * Note a leap year is one that is a multiple of 4
-         * but not of a 100.  Except if it is a multiple of
-         * 400 then it is a leap year.
-         * Copied + reversed functionality into TmToDate
-         */
-        *pDateOut += ( (pTm->tm_year - 1) / 4 ) - ( 1900 / 4 );
-        *pDateOut -= ( (pTm->tm_year - 1) / 100 ) - ( 1900 / 100 );
-        *pDateOut += ( (pTm->tm_year - 1) / 400 ) - ( 1900 / 400 );
-
-        /* Set the leap year flag if the
-         * current year specified by tm_year is a
-         * leap year. This will be used to add a day
-         * to the day count.
-         */
-        if( isleap( pTm->tm_year ) )
-            leapYear = 1;
-
-        /* Add the number of days corresponding to
-         * the month. (remember tm_mon is 0..11)
-         */
-        switch( pTm->tm_mon )
-        {
-        case 1:
-            *pDateOut += 31;
-            break;
-        case 2:
-            *pDateOut += ( 59 + leapYear );
-            break;
-        case 3:
-            *pDateOut += ( 90 + leapYear );
-            break;
-        case 4:
-            *pDateOut += ( 120 + leapYear );
-            break;
-        case 5:
-            *pDateOut += ( 151 + leapYear );
-            break;
-        case 6:
-            *pDateOut += ( 181 + leapYear );
-            break;
-        case 7:
-            *pDateOut += ( 212 + leapYear );
-            break;
-        case 8:
-            *pDateOut += ( 243 + leapYear );
-            break;
-        case 9:
-            *pDateOut += ( 273 + leapYear );
-            break;
-        case 10:
-            *pDateOut += ( 304 + leapYear );
-            break;
-        case 11:
-            *pDateOut += ( 334 + leapYear );
-            break;
-        }
-        /* Add the number of days in this month.
-         */
-        *pDateOut += pTm->tm_mday;
-
-        /* Add the number of seconds, minutes, and hours
-         * to the DATE. Note these are the fractional part
-         * of the DATE so seconds / number of seconds in a day.
-         */
-    } else {
-        *pDateOut = 0;
-    }
-
-    *pDateOut += pTm->tm_hour / 24.0;
-    *pDateOut += pTm->tm_min / 1440.0;
-    *pDateOut += pTm->tm_sec / 86400.0;
-    return TRUE;
-}
-
-/******************************************************************************
- *	   DateToTm 	[INTERNAL]
- *
- * This function converts a windows DATE to a tm structure.
- *
- * It does not fill all the fields of the tm structure.
- * Here is a list of the fields that are filled:
- * tm_sec, tm_min, tm_hour, tm_year, tm_day, tm_mon.
- *
- * Note this function does not support dates before the January 1, 1900
- * or ( dateIn < 2.0 ).
- *
- * Returns TRUE if successful.
- */
-BOOL DateToTm( DATE dateIn, DWORD dwFlags, struct tm* pTm )
-{
-    double decimalPart = 0.0;
-    double wholePart = 0.0;
-
-    memset(pTm,0,sizeof(*pTm));
-
-    /* Because of the nature of DATE format which
-     * associates 2.0 to January 1, 1900. We will
-     * remove 1.0 from the whole part of the DATE
-     * so that in the following code 1.0
-     * will correspond to January 1, 1900.
-     * This simplifies the processing of the DATE value.
-     */
-    decimalPart = fmod( dateIn, 1.0 ); /* Do this before the -1, otherwise 0.xx goes negative */
-    dateIn -= 1.0;
-    wholePart = (double) floor( dateIn );
-
-    if( !(dwFlags & VAR_TIMEVALUEONLY) )
-    {
-        unsigned int nDay = 0;
-        int leapYear = 0;
-        double yearsSince1900 = 0;
-
-        /* Hard code dates smaller than January 1, 1900. */
-        if( dateIn < 2.0 ) {
-            pTm->tm_year = 1899;
-            pTm->tm_mon  = 11; /* December as tm_mon is 0..11 */
-            if( dateIn < 1.0 ) {
-                pTm->tm_mday  = 30;
-                dateIn = dateIn * -1.0; /* Ensure +ve for time calculation */
-                decimalPart = decimalPart * -1.0; /* Ensure +ve for time calculation */
-            } else {
-                pTm->tm_mday  = 31;
-            }
-
-        } else {
-
-            /* Start at 1900, this is where the DATE time 0.0 starts.
-             */
-            pTm->tm_year = 1900;
-            /* find in what year the day in the "wholePart" falls into.
-             * add the value to the year field.
-             */
-            yearsSince1900 = floor( (wholePart / DAYS_IN_ONE_YEAR) + 0.001 );
-            pTm->tm_year += yearsSince1900;
-            /* determine if this is a leap year.
-             */
-            if( isleap( pTm->tm_year ) )
-            {
-                leapYear = 1;
-                wholePart++;
-            }
-
-            /* find what day of that year the "wholePart" corresponds to.
-             * Note: nDay is in [1-366] format
-             */
-            nDay = (((unsigned int) wholePart) - ((pTm->tm_year-1900) * DAYS_IN_ONE_YEAR ));
-
-            /* Remove the leap days in the previous years between now and 1900.
-             * Note a leap year is one that is a multiple of 4
-             * but not of a 100.  Except if it is a multiple of
-             * 400 then it is a leap year.
-             * Copied + reversed functionality from TmToDate
-             */
-            nDay -= ( (pTm->tm_year - 1) / 4 ) - ( 1900 / 4 );
-            nDay += ( (pTm->tm_year - 1) / 100 ) - ( 1900 / 100 );
-            nDay -= ( (pTm->tm_year - 1) / 400 ) - ( 1900 / 400 );
-
-            /* Set the tm_yday value.
-             * Note: The day must be converted from [1-366] to [0-365]
-             */
-            /*pTm->tm_yday = nDay - 1;*/
-            /* find which month this day corresponds to.
-             */
-            if( nDay <= 31 )
-            {
-                pTm->tm_mday = nDay;
-                pTm->tm_mon = 0;
-            }
-            else if( nDay <= ( 59 + leapYear ) )
-            {
-                pTm->tm_mday = nDay - 31;
-                pTm->tm_mon = 1;
-            }
-            else if( nDay <= ( 90 + leapYear ) )
-            {
-                pTm->tm_mday = nDay - ( 59 + leapYear );
-                pTm->tm_mon = 2;
-            }
-            else if( nDay <= ( 120 + leapYear ) )
-            {
-                pTm->tm_mday = nDay - ( 90 + leapYear );
-                pTm->tm_mon = 3;
-            }
-            else if( nDay <= ( 151 + leapYear ) )
-            {
-                pTm->tm_mday = nDay - ( 120 + leapYear );
-                pTm->tm_mon = 4;
-            }
-            else if( nDay <= ( 181 + leapYear ) )
-            {
-                pTm->tm_mday = nDay - ( 151 + leapYear );
-                pTm->tm_mon = 5;
-            }
-            else if( nDay <= ( 212 + leapYear ) )
-            {
-                pTm->tm_mday = nDay - ( 181 + leapYear );
-                pTm->tm_mon = 6;
-            }
-            else if( nDay <= ( 243 + leapYear ) )
-            {
-                pTm->tm_mday = nDay - ( 212 + leapYear );
-                pTm->tm_mon = 7;
-            }
-            else if( nDay <= ( 273 + leapYear ) )
-            {
-                pTm->tm_mday = nDay - ( 243 + leapYear );
-                pTm->tm_mon = 8;
-            }
-            else if( nDay <= ( 304 + leapYear ) )
-            {
-                pTm->tm_mday = nDay - ( 273 + leapYear );
-                pTm->tm_mon = 9;
-            }
-            else if( nDay <= ( 334 + leapYear ) )
-            {
-                pTm->tm_mday = nDay - ( 304 + leapYear );
-                pTm->tm_mon = 10;
-            }
-            else if( nDay <= ( 365 + leapYear ) )
-            {
-                pTm->tm_mday = nDay - ( 334 + leapYear );
-                pTm->tm_mon = 11;
-            }
-        }
-    }
-    if( !(dwFlags & VAR_DATEVALUEONLY) )
-    {
-        /* find the number of seconds in this day.
-         * fractional part times, hours, minutes, seconds.
-         * Note: 0.1 is hack to ensure figures come out in whole numbers
-         *   due to floating point inaccuracies
-         */
-        pTm->tm_hour = (int) ( decimalPart * 24 );
-        pTm->tm_min = (int) ( ( ( decimalPart * 24 ) - pTm->tm_hour ) * 60 );
-        /* Note: 0.1 is hack to ensure seconds come out in whole numbers
-             due to floating point inaccuracies */
-        pTm->tm_sec = (int) (( ( ( decimalPart * 24 * 60 ) - ( pTm->tm_hour * 60 ) - pTm->tm_min ) * 60 ) + 0.1);
-    }
-    return TRUE;
-}
-
-
-
-/******************************************************************************
- *	   SizeOfVariantData   	[INTERNAL]
- *
- * This function finds the size of the data referenced by a Variant based
- * the type "vt" of the Variant.
- */
-static int SizeOfVariantData( VARIANT* parg )
-{
-    int size = 0;
-    switch( V_VT(parg) & VT_TYPEMASK )
-    {
-    case( VT_I2 ):
-        size = sizeof(short);
-        break;
-    case( VT_INT ):
-        size = sizeof(int);
-        break;
-    case( VT_I4 ):
-        size = sizeof(long);
-        break;
-    case( VT_UI1 ):
-        size = sizeof(BYTE);
-        break;
-    case( VT_UI2 ):
-        size = sizeof(unsigned short);
-        break;
-    case( VT_UINT ):
-        size = sizeof(unsigned int);
-        break;
-    case( VT_UI4 ):
-        size = sizeof(unsigned long);
-        break;
-    case( VT_R4 ):
-        size = sizeof(float);
-        break;
-    case( VT_R8 ):
-        size = sizeof(double);
-        break;
-    case( VT_DATE ):
-        size = sizeof(DATE);
-        break;
-    case( VT_BOOL ):
-        size = sizeof(VARIANT_BOOL);
-        break;
-    case( VT_BSTR ):
-    case( VT_DISPATCH ):
-    case( VT_UNKNOWN ):
-        size = sizeof(void*);
-        break;
-    case( VT_CY ):
-	size = sizeof(CY);
-	break;
-    case( VT_DECIMAL ):		/* hmm, tricky, DECIMAL is only VT_BYREF */
-    default:
-        FIXME("Add size information for type vt=%d\n", V_VT(parg) & VT_TYPEMASK );
-        break;
-    }
-
-    return size;
-}
 /******************************************************************************
  *	   StringDupAtoBstr		[INTERNAL]
  *
@@ -590,7 +110,7 @@
 	BSTR bstr = NULL;
 	OLECHAR* pNewString = NULL;
 	UNICODE_STRING usBuffer;
-	
+
 	RtlCreateUnicodeStringFromAsciiz( &usBuffer, strIn );
 	pNewString = usBuffer.Buffer;
 
@@ -1290,7 +810,10 @@
 			res = VarBstrFromR8( V_UNION(ps,dblVal), lcid, 0, &V_UNION(pd,bstrVal) );
 			break;
 		case( VT_DATE ):
-			res = VarBstrFromDate( V_UNION(ps,date), lcid, 0, &V_UNION(pd,bstrVal) );
+            if (dwFlags & VARIANT_NOUSEROVERRIDE)
+              res = VarBstrFromDate( V_UNION(ps,date), lcid, LOCALE_NOUSEROVERRIDE, &V_UNION(pd,bstrVal) );
+            else
+			  res = VarBstrFromDate( V_UNION(ps,date), lcid, 0, &V_UNION(pd,bstrVal) );
 			break;
 		case( VT_BOOL ):
 			if (dwFlags & VARIANT_ALPHABOOL)
@@ -1655,7 +1178,7 @@
 {
   HRESULT hres = S_OK;
 
-  TRACE("(%p)\n", pVarg);
+  TRACE("(%p->(%s%s))\n", pVarg, debugstr_VT(pVarg), debugstr_VF(pVarg));
 
   hres = VARIANT_ValidateType(V_VT(pVarg));
 
@@ -1700,7 +1223,40 @@
 }
 
 /******************************************************************************
- *		VariantCopy	[OLEAUT32.10]
+ * Copy an IRecordInfo object contained in a variant.
+ */
+static HRESULT VARIANT_CopyIRecordInfo(struct __tagBRECORD* pBr)
+{
+  HRESULT hres = S_OK;
+
+  if (pBr->pRecInfo)
+  {
+    ULONG ulSize;
+
+    hres = IRecordInfo_GetSize(pBr->pRecInfo, &ulSize);
+    if (SUCCEEDED(hres))
+    {
+      PVOID pvRecord = HeapAlloc(GetProcessHeap(), 0, ulSize);
+      if (!pvRecord)
+        hres = E_OUTOFMEMORY;
+      else
+      {
+        memcpy(pvRecord, pBr->pvRecord, ulSize);
+        pBr->pvRecord = pvRecord;
+
+        hres = IRecordInfo_RecordCopy(pBr->pRecInfo, pvRecord, pvRecord);
+        if (SUCCEEDED(hres))
+          IRecordInfo_AddRef(pBr->pRecInfo);
+      }
+    }
+  }
+  else if (pBr->pvRecord)
+    hres = E_INVALIDARG;
+  return hres;
+}
+
+/******************************************************************************
+ *    VariantCopy  [OLEAUT32.10]
  *
  * Copy a variant.
  *
@@ -1710,92 +1266,104 @@
  *
  * RETURNS
  *  Success: S_OK. pvargDest contains a copy of pvargSrc.
- *  Failure: An HRESULT error code indicating the error.
+ *  Failure: DISP_E_BADVARTYPE, if either variant has an invalid type.
+ *           E_OUTOFMEMORY, if memory cannot be allocated. Otherwise an
+ *           HRESULT error code from SafeArrayCopy(), IRecordInfo_GetSize(),
+ *           or IRecordInfo_RecordCopy(), depending on the type of pvargSrc.
  *
  * NOTES
- *  pvargDest is always freed, and may be equal to pvargSrc.
- *  If pvargSrc is by-reference, pvargDest is by-reference also.
+ *  - If pvargSrc == pvargDest, this function does nothing, and succeeds if
+ *    pvargSrc is valid. Otherwise, pvargDest is always cleared using
+ *    VariantClear() before pvargSrc is copied to it. If clearing pvargDest
+ *    fails, so does this function.
+ *  - VT_CLSID is a valid type type for pvargSrc, but not for pvargDest.
+ *  - For by-value non-intrinsic types, a deep copy is made, i.e. The whole value
+ *    is copied rather than just any pointers to it.
+ *  - For by-value object types the object pointer is copied and the objects
+ *    reference count increased using IUnknown_AddRef().
+ *  - For all by-reference types, only the referencing pointer is copied.
  */
 HRESULT WINAPI VariantCopy(VARIANTARG* pvargDest, VARIANTARG* pvargSrc)
 {
-  HRESULT res = S_OK;
+  HRESULT hres = S_OK;
 
-  TRACE("(%p, %p), vt=%d\n", pvargDest, pvargSrc, V_VT(pvargSrc));
+  TRACE("(%p->(%s%s),%p->(%s%s))\n", pvargDest, debugstr_VT(pvargDest),
+        debugstr_VF(pvargDest), pvargSrc, debugstr_VT(pvargSrc),
+        debugstr_VF(pvargSrc));
 
-  res = ValidateVariantType( V_VT(pvargSrc) );
+  if (V_TYPE(pvargSrc) == VT_CLSID || /* VT_CLSID is a special case */
+      FAILED(VARIANT_ValidateType(V_VT(pvargSrc))))
+    return DISP_E_BADVARTYPE;
 
-  /* If the pointer are to the same variant we don't need
-   * to do anything.
-   */
-  if( pvargDest != pvargSrc && res == S_OK )
+  if (pvargSrc != pvargDest &&
+      SUCCEEDED(hres = VariantClear(pvargDest)))
   {
-    VariantClear( pvargDest ); /* result is not checked */
+    *pvargDest = *pvargSrc; /* Shallow copy the value */
 
-    if( V_VT(pvargSrc) & VT_BYREF )
-    {
-      /* In the case of byreference we only need
-       * to copy the pointer.
-       */
-      pvargDest->n1.n2.n3 = pvargSrc->n1.n2.n3;
-      V_VT(pvargDest) = V_VT(pvargSrc);
-    }
-    else
+    if (!V_ISBYREF(pvargSrc))
     {
-      /*
-       * The VT_ARRAY flag is another way to designate a safe array.
-       */
-      if (V_VT(pvargSrc) & VT_ARRAY)
+      if (V_ISARRAY(pvargSrc))
       {
-	SafeArrayCopy(V_UNION(pvargSrc,parray), &V_UNION(pvargDest,parray));
+        if (V_ARRAY(pvargSrc))
+          hres = SafeArrayCopy(V_ARRAY(pvargSrc), &V_ARRAY(pvargDest));
       }
-      else
+      else if (V_VT(pvargSrc) == VT_BSTR)
       {
-	/* In the case of by value we need to
-	 * copy the actual value. In the case of
-	 * VT_BSTR a copy of the string is made,
-	 * if VT_DISPATCH or VT_IUNKNOWN AddRef is
-	 * called to increment the object's reference count.
-	 */
-	switch( V_VT(pvargSrc) & VT_TYPEMASK )
-	{
-	  case( VT_BSTR ):
-	    V_UNION(pvargDest,bstrVal) = SYSDUPSTRING( V_UNION(pvargSrc,bstrVal) );
-	    break;
-	  case( VT_DISPATCH ):
-	    V_UNION(pvargDest,pdispVal) = V_UNION(pvargSrc,pdispVal);
-	    if (V_UNION(pvargDest,pdispVal)!=NULL)
-	      IDispatch_AddRef(V_UNION(pvargDest,pdispVal));
-	    break;
-	  case( VT_VARIANT ):
-	    VariantCopy(V_UNION(pvargDest,pvarVal),V_UNION(pvargSrc,pvarVal));
-	    break;
-	  case( VT_UNKNOWN ):
-	    V_UNION(pvargDest,punkVal) = V_UNION(pvargSrc,punkVal);
-	    if (V_UNION(pvargDest,pdispVal)!=NULL)
-	      IUnknown_AddRef(V_UNION(pvargDest,punkVal));
-	    break;
-	  case( VT_SAFEARRAY ):
-	    SafeArrayCopy(V_UNION(pvargSrc,parray), &V_UNION(pvargDest,parray));
-	    break;
-	  default:
-	    pvargDest->n1.n2.n3 = pvargSrc->n1.n2.n3;
-	    break;
-	}
+        if (V_BSTR(pvargSrc))
+        {
+          V_BSTR(pvargDest) = SysAllocStringLen(V_BSTR(pvargSrc), SysStringLen(V_BSTR(pvargSrc)));
+          if (!V_BSTR(pvargDest))
+            hres = E_OUTOFMEMORY;
+        }
+      }
+      else if (V_VT(pvargSrc) == VT_RECORD)
+      {
+        hres = VARIANT_CopyIRecordInfo(&V_UNION(pvargDest,brecVal));
+      }
+      else if (V_VT(pvargSrc) == VT_DISPATCH ||
+               V_VT(pvargSrc) == VT_UNKNOWN)
+      {
+        if (V_UNKNOWN(pvargSrc))
+          IUnknown_AddRef(V_UNKNOWN(pvargSrc));
       }
-      V_VT(pvargDest) = V_VT(pvargSrc);
-      dump_Variant(pvargDest);
     }
   }
-
-  return res;
+  return hres;
 }
 
+/* Return the byte size of a variants data */
+static inline size_t VARIANT_DataSize(const VARIANT* pv)
+{
+  switch (V_TYPE(pv))
+  {
+  case VT_I1:
+  case VT_UI1:   return sizeof(BYTE); break;
+  case VT_I2:
+  case VT_UI2:   return sizeof(SHORT); break;
+  case VT_INT:
+  case VT_UINT:
+  case VT_I4:
+  case VT_UI4:   return sizeof(LONG); break;
+  case VT_I8:
+  case VT_UI8:   return sizeof(LONGLONG); break;
+  case VT_R4:    return sizeof(float); break;
+  case VT_R8:    return sizeof(double); break;
+  case VT_DATE:  return sizeof(DATE); break;
+  case VT_BOOL:  return sizeof(VARIANT_BOOL); break;
+  case VT_DISPATCH:
+  case VT_UNKNOWN:
+  case VT_BSTR:  return sizeof(void*); break;
+  case VT_CY:    return sizeof(CY); break;
+  case VT_ERROR: return sizeof(SCODE); break;
+  }
+  TRACE("Shouldn't be called for vt %s%s!\n", debugstr_VT(pv), debugstr_VF(pv));
+  return 0;
+}
 
 /******************************************************************************
- *		VariantCopyInd	[OLEAUT32.11]
- *
+ *    VariantCopyInd  [OLEAUT32.11]
  *
- * Copy a variant, dereferencing if it is by-reference.
+ * Copy a variant, dereferencing it it is by-reference.
  *
  * PARAMS
  *  pvargDest [O] Destination for copy
@@ -1804,138 +1372,117 @@
  * RETURNS
  *  Success: S_OK. pvargDest contains a copy of pvargSrc.
  *  Failure: An HRESULT error code indicating the error.
- *  
+ *
  * NOTES
- *  pvargDest is always freed, and may be equal to pvargSrc.
- *  If pvargSrc is not by-reference, this function acts as VariantCopy().
+ *  Failure: DISP_E_BADVARTYPE, if either variant has an invalid by-value type.
+ *           E_INVALIDARG, if pvargSrc  is an invalid by-reference type.
+ *           E_OUTOFMEMORY, if memory cannot be allocated. Otherwise an
+ *           HRESULT error code from SafeArrayCopy(), IRecordInfo_GetSize(),
+ *           or IRecordInfo_RecordCopy(), depending on the type of pvargSrc.
+ *
+ * NOTES
+ *  - If pvargSrc is by-value, this function behaves exactly as VariantCopy().
+ *  - If pvargSrc is by-reference, the value copied to pvargDest is the pointed-to
+ *    value.
+ *  - if pvargSrc == pvargDest, this function dereferences in place. Otherwise,
+ *    pvargDest is always cleared using VariantClear() before pvargSrc is copied
+ *    to it. If clearing pvargDest fails, so does this function.
  */
 HRESULT WINAPI VariantCopyInd(VARIANT* pvargDest, VARIANTARG* pvargSrc)
 {
-  HRESULT res = S_OK;
-
-  TRACE("(%p, %p)\n", pvargDest, pvargSrc);
+  VARIANTARG vTmp, *pSrc = pvargSrc;
+  VARTYPE vt;
+  HRESULT hres = S_OK;
 
-  res = ValidateVariantType( V_VT(pvargSrc) );
+  TRACE("(%p->(%s%s),%p->(%s%s))\n", pvargDest, debugstr_VT(pvargDest),
+        debugstr_VF(pvargDest), pvargSrc, debugstr_VT(pvargSrc),
+        debugstr_VF(pvargSrc));
 
-  if( res != S_OK )
-    return res;
+  if (!V_ISBYREF(pvargSrc))
+    return VariantCopy(pvargDest, pvargSrc);
 
-  if( V_VT(pvargSrc) & VT_BYREF )
+  /* Argument checking is more lax than VariantCopy()... */
+  vt = V_TYPE(pvargSrc);
+  if (V_ISARRAY(pvargSrc) ||
+     (vt > VT_NULL && vt != (VARTYPE)15 && vt < VT_VOID &&
+     !(V_VT(pvargSrc) & (VT_VECTOR|VT_RESERVED))))
   {
-    VARIANTARG varg;
-    VariantInit( &varg );
+    /* OK */
+  }
+  else
+    return E_INVALIDARG; /* ...And the return value for invalid types differs too */
 
-    /* handle the in place copy.
+  if (pvargSrc == pvargDest)
+  {
+    /* In place copy. Use a shallow copy of pvargSrc & init pvargDest.
+     * This avoids an expensive VariantCopy() call - e.g. SafeArrayCopy().
      */
-    if( pvargDest == pvargSrc )
-    {
-      /* we will use a copy of the source instead.
-       */
-      res = VariantCopy( &varg, pvargSrc );
-      pvargSrc = &varg;
-    }
-
-    if( res == S_OK )
-    {
-      res = VariantClear( pvargDest );
-
-      if( res == S_OK )
-      {
-	/*
-	 * The VT_ARRAY flag is another way to designate a safearray variant.
-	 */
-	if ( V_VT(pvargSrc) & VT_ARRAY)
-	{
-	  SafeArrayCopy(*V_UNION(pvargSrc,pparray), &V_UNION(pvargDest,parray));
-	}
-	else
-	{
-	  /* In the case of by reference we need
-	   * to copy the date pointed to by the variant.
-	   */
-
-	  /* Get the variant type.
-	   */
-	  switch( V_VT(pvargSrc) & VT_TYPEMASK )
-	  {
-	    case( VT_BSTR ):
-	      V_UNION(pvargDest,bstrVal) = SYSDUPSTRING( *(V_UNION(pvargSrc,pbstrVal)) );
-	      break;
-	    case( VT_DISPATCH ):
-	      V_UNION(pvargDest,pdispVal) = *V_UNION(pvargSrc,ppdispVal);
-	      if (V_UNION(pvargDest,pdispVal)!=NULL)
-		IDispatch_AddRef(V_UNION(pvargDest,pdispVal));
-	      break;
-	    case( VT_VARIANT ):
-	      {
-		/* Prevent from cycling.  According to tests on
-		 * VariantCopyInd in Windows and the documentation
-		 * this API dereferences the inner Variants to only one depth.
-		 * If the inner Variant itself contains an
-		 * other inner variant the E_INVALIDARG error is
-		 * returned.
-		 */
-		if( pvargSrc->n1.n2.wReserved1 & PROCESSING_INNER_VARIANT )
-		{
-		  /* If we get here we are attempting to deference
-		   * an inner variant that that is itself contained
-		   * in an inner variant so report E_INVALIDARG error.
-		   */
-		  res = E_INVALIDARG;
-		}
-		else
-		{
-		  /* Set the processing inner variant flag.
-		   * We will set this flag in the inner variant
-		   * that will be passed to the VariantCopyInd function.
-		   */
-		  (V_UNION(pvargSrc,pvarVal))->n1.n2.wReserved1 |= PROCESSING_INNER_VARIANT;
-
-		  /* Dereference the inner variant.
-		   */
-		  res = VariantCopyInd( pvargDest, V_UNION(pvargSrc,pvarVal) );
-		  /* We must also copy its type, I think.
-		   */
-		  V_VT(pvargSrc) = V_VT(V_UNION(pvargSrc,pvarVal));
-		}
-	      }
-	      break;
-	    case( VT_UNKNOWN ):
-	      V_UNION(pvargDest,punkVal) = *V_UNION(pvargSrc,ppunkVal);
-	      if (V_UNION(pvargDest,pdispVal)!=NULL)
-		IUnknown_AddRef(V_UNION(pvargDest,punkVal));
-	      break;
-	    case( VT_SAFEARRAY ):
-	      SafeArrayCopy(*V_UNION(pvargSrc,pparray), &V_UNION(pvargDest,parray));
-	      break;
-	    default:
-	      /* This is a by reference Variant which means that the union
-	       * part of the Variant contains a pointer to some data of
-	       * type "V_VT(pvargSrc) & VT_TYPEMASK".
-	       * We will deference this data in a generic fashion using
-	       * the void pointer "Variant.u.byref".
-	       * We will copy this data into the union of the destination
-	       * Variant.
-	       */
-	      memcpy( &pvargDest->n1.n2.n3, V_UNION(pvargSrc,byref), SizeOfVariantData( pvargSrc ) );
-	      break;
-	  }
-	}
+    vTmp = *pvargSrc;
+    pSrc = &vTmp;
+    V_VT(pvargDest) = VT_EMPTY;
+  }
+  else
+  {
+    /* Copy into another variant. Free the variant in pvargDest */
+    if (FAILED(hres = VariantClear(pvargDest)))
+      return hres;
+  }
 
-        if (res == S_OK) V_VT(pvargDest) = V_VT(pvargSrc) & VT_TYPEMASK;
-      }
-    }
+  if (V_ISARRAY(pSrc))
+  {
+    /* Native doesn't check that *V_ARRAYREF(pSrc) is valid */
+    hres = SafeArrayCopy(*V_ARRAYREF(pSrc), &V_ARRAY(pvargDest));
+  }
+  else if (V_VT(pSrc) == (VT_BSTR|VT_BYREF))
+  {
+    /* Native doesn't check that *V_BSTRREF(pSrc) is valid */
+    V_BSTR(pvargDest) = SysAllocStringLen(*V_BSTRREF(pSrc), SysStringLen(*V_BSTRREF(pSrc)));
+  }
+  else if (V_VT(pSrc) == (VT_RECORD|VT_BYREF))
+  {
+    V_UNION(pvargDest,brecVal) = V_UNION(pvargSrc,brecVal);
+    hres = VARIANT_CopyIRecordInfo(&V_UNION(pvargDest,brecVal));
+  }
+  else if (V_VT(pSrc) == (VT_DISPATCH|VT_BYREF) ||
+           V_VT(pSrc) == (VT_UNKNOWN|VT_BYREF))
+  {
+    /* Native doesn't check that *V_UNKNOWNREF(pSrc) is valid */
+    V_UNKNOWN(pvargDest) = *V_UNKNOWNREF(pSrc);
+    if (*V_UNKNOWNREF(pSrc))
+      IUnknown_AddRef(*V_UNKNOWNREF(pSrc));
+  }
+  else if (V_VT(pSrc) == (VT_VARIANT|VT_BYREF))
+  {
+    /* Native doesn't check that *V_VARIANTREF(pSrc) is valid */
+    if (V_VT(V_VARIANTREF(pSrc)) == (VT_VARIANT|VT_BYREF))
+      hres = E_INVALIDARG; /* Don't dereference more than one level */
+    else
+      hres = VariantCopyInd(pvargDest, V_VARIANTREF(pSrc));
 
-    /* this should not fail.
-     */
-    VariantClear( &varg );
+    /* Use the dereferenced variants type value, not VT_VARIANT */
+    goto VariantCopyInd_Return;
+  }
+  else if (V_VT(pSrc) == (VT_DECIMAL|VT_BYREF))
+  {
+    memcpy(&DEC_SCALE(&V_DECIMAL(pvargDest)), &DEC_SCALE(V_DECIMALREF(pSrc)),
+           sizeof(DECIMAL) - sizeof(USHORT));
   }
   else
   {
-    res = VariantCopy( pvargDest, pvargSrc );
+    /* Copy the pointed to data into this variant */
+    memcpy(&V_BYREF(pvargDest), V_BYREF(pSrc), VARIANT_DataSize(pSrc));
   }
 
-  return res;
+  V_VT(pvargDest) = V_VT(pSrc) & ~VT_BYREF;
+
+VariantCopyInd_Return:
+
+  if (pSrc != pvargSrc)
+    VariantClear(pSrc);
+
+  TRACE("returning 0x%08lx, %p->(%s%s)\n", hres, pvargDest,
+        debugstr_VT(pvargDest), debugstr_VF(pvargDest));
+  return hres;
 }
 
 /******************************************************************************
@@ -1996,7 +1543,7 @@
  * RETURNS
  *  Success: S_OK. pvargDest contains the converted value.
  *  Failure: An HRESULT error code describing the failure.
- *    
+ *
  * NOTES
  *  The LCID used for the conversion is LOCALE_USER_DEFAULT.
  *  See VariantChangeTypeEx.
@@ -2987,53 +2534,575 @@
 	return S_OK;
 }
 
+/* Date string parsing */
+#define DP_TIMESEP 0x01 /* Time seperator ( _must_ remain 0x1, used as a bitmask) */
+#define DP_DATESEP 0x02 /* Date seperator */
+#define DP_MONTH   0x04 /* Month name */
+#define DP_AM      0x08 /* AM */
+#define DP_PM      0x10 /* PM */
+
+typedef struct tagDATEPARSE
+{
+    DWORD dwCount;      /* Number of fields found so far (maximum 6) */
+    DWORD dwParseFlags; /* Global parse flags (DP_ Flags above) */
+    DWORD dwFlags[6];   /* Flags for each field */
+    DWORD dwValues[6];  /* Value of each field */
+} DATEPARSE;
+
+#define TIMEFLAG(i) ((dp.dwFlags[i] & DP_TIMESEP) << i)
+
+#define IsLeapYear(y) (((y % 4) == 0) && (((y % 100) != 0) || ((y % 400) == 0)))
+
+/* Determine if a day is valid in a given month of a given year */
+static BOOL VARIANT_IsValidMonthDay(DWORD day, DWORD month, DWORD year)
+{
+  static const BYTE days[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+
+  if (day && month && month < 13)
+  {
+    if (day <= days[month] || (month == 2 && day == 29 && IsLeapYear(year)))
+      return TRUE;
+  }
+  return FALSE;
+}
+
+/* Possible orders for 3 numbers making up a date */
+#define ORDER_MDY 0x01
+#define ORDER_YMD 0x02
+#define ORDER_YDM 0x04
+#define ORDER_DMY 0x08
+#define ORDER_MYD 0x10 /* Synthetic order, used only for funky 2 digit dates */
+
+/* Determine a date for a particular locale, from 3 numbers */
+static inline HRESULT VARIANT_MakeDate(DATEPARSE *dp, DWORD iDate,
+                                       DWORD offset, SYSTEMTIME *st)
+{
+  DWORD dwAllOrders, dwTry, dwCount = 0, v1, v2, v3;
+
+  if (!dp->dwCount)
+  {
+    v1 = 30; /* Default to (Variant) 0 date part */
+    v2 = 12;
+    v3 = 1899;
+    goto VARIANT_MakeDate_OK;
+  }
+
+  v1 = dp->dwValues[offset + 0];
+  v2 = dp->dwValues[offset + 1];
+  if (dp->dwCount == 2)
+  {
+    SYSTEMTIME current;
+    GetSystemTime(&current);
+    v3 = current.wYear;
+  }
+  else
+    v3 = dp->dwValues[offset + 2];
+
+  TRACE("(%ld,%ld,%ld,%ld,%ld)\n", v1, v2, v3, iDate, offset);
+
+  /* If one number must be a month (Because a month name was given), then only
+   * consider orders with the month in that position.
+   * If we took the current year as 'v3', then only allow a year in that position.
+   */
+  if (dp->dwFlags[offset + 0] & DP_MONTH)
+  {
+    dwAllOrders = ORDER_MDY;
+  }
+  else if (dp->dwFlags[offset + 1] & DP_MONTH)
+  {
+    dwAllOrders = ORDER_DMY;
+    if (dp->dwCount > 2)
+      dwAllOrders |= ORDER_YMD;
+  }
+  else if (dp->dwCount > 2 && dp->dwFlags[offset + 2] & DP_MONTH)
+  {
+    dwAllOrders = ORDER_YDM;
+  }
+  else
+  {
+    dwAllOrders = ORDER_MDY|ORDER_DMY;
+    if (dp->dwCount > 2)
+      dwAllOrders |= (ORDER_YMD|ORDER_YDM);
+  }
+
+VARIANT_MakeDate_Start:
+  TRACE("dwAllOrders is 0x%08lx\n", dwAllOrders);
+
+  while (dwAllOrders)
+  {
+    DWORD dwTemp;
+
+    if (dwCount == 0)
+    {
+      /* First: Try the order given by iDate */
+      switch (iDate)
+      {
+      case 0:  dwTry = dwAllOrders & ORDER_MDY; break;
+      case 1:  dwTry = dwAllOrders & ORDER_DMY; break;
+      default: dwTry = dwAllOrders & ORDER_YMD; break;
+      }
+    }
+    else if (dwCount == 1)
+    {
+      /* Second: Try all the orders compatable with iDate */
+      switch (iDate)
+      {
+      case 0:  dwTry = dwAllOrders & ~(ORDER_DMY|ORDER_YDM); break;
+      case 1:  dwTry = dwAllOrders & ~(ORDER_MDY|ORDER_YMD|ORDER_MYD); break;
+      default: dwTry = dwAllOrders & ~(ORDER_DMY|ORDER_YDM); break;
+      }
+    }
+    else
+    {
+      /* Finally: Try any remaining orders */
+      dwTry = dwAllOrders;
+    }
+
+    TRACE("Attempt %ld, dwTry is 0x%08lx\n", dwCount, dwTry);
+
+    dwCount++;
+    if (!dwTry)
+      continue;
+
+#define DATE_SWAP(x,y) do { dwTemp = x; x = y; y = dwTemp; } while (0)
+
+    if (dwTry & ORDER_MDY)
+    {
+      if (VARIANT_IsValidMonthDay(v2,v1,v3))
+      {
+        DATE_SWAP(v1,v2);
+        goto VARIANT_MakeDate_OK;
+      }
+      dwAllOrders &= ~ORDER_MDY;
+    }
+    if (dwTry & ORDER_YMD)
+    {
+      if (VARIANT_IsValidMonthDay(v3,v2,v1))
+      {
+        DATE_SWAP(v1,v3);
+        goto VARIANT_MakeDate_OK;
+      }
+      dwAllOrders &= ~ORDER_YMD;
+    }
+    if (dwTry & ORDER_YDM)
+    {
+      if (VARIANT_IsValidMonthDay(v2,v3,v1))
+      {
+        DATE_SWAP(v1,v2);
+        DATE_SWAP(v2,v3);
+        goto VARIANT_MakeDate_OK;
+      }
+      dwAllOrders &= ~ORDER_YDM;
+    }
+    if (dwTry & ORDER_DMY)
+    {
+      if (VARIANT_IsValidMonthDay(v1,v2,v3))
+        goto VARIANT_MakeDate_OK;
+      dwAllOrders &= ~ORDER_DMY;
+    }
+    if (dwTry & ORDER_MYD)
+    {
+      /* Only occurs if we are trying a 2 year date as M/Y not D/M */
+      if (VARIANT_IsValidMonthDay(v3,v1,v2))
+      {
+        DATE_SWAP(v1,v3);
+        DATE_SWAP(v2,v3);
+        goto VARIANT_MakeDate_OK;
+      }
+      dwAllOrders &= ~ORDER_MYD;
+    }
+  }
+
+  if (dp->dwCount == 2)
+  {
+    /* We couldn't make a date as D/M or M/D, so try M/Y or Y/M */
+    v3 = 1; /* 1st of the month */
+    dwAllOrders = ORDER_YMD|ORDER_MYD;
+    dp->dwCount = 0; /* Don't return to this code path again */
+    dwCount = 0;
+    goto VARIANT_MakeDate_Start;
+  }
+
+  /* No valid dates were able to be constructed */
+  return DISP_E_TYPEMISMATCH;
+
+VARIANT_MakeDate_OK:
+
+  /* Check that the time part is ok */
+  if (st->wHour > 23 || st->wMinute > 59 || st->wSecond > 59)
+    return DISP_E_TYPEMISMATCH;
+
+  TRACE("Time %d %d %d\n", st->wHour, st->wMinute, st->wSecond);
+  if (st->wHour < 12 && (dp->dwParseFlags & DP_PM))
+    st->wHour += 12;
+  else if (st->wHour == 12 && (dp->dwParseFlags & DP_AM))
+    st->wHour = 0;
+  TRACE("Time %d %d %d\n", st->wHour, st->wMinute, st->wSecond);
+
+  st->wDay = v1;
+  st->wMonth = v2;
+  /* FIXME: For 2 digit dates, I'm not sure if 30 is hard coded or not. It may
+   * be retrieved from:
+   * HKCU\Control Panel\International\Calendars\TwoDigitYearMax
+   * But Wine doesn't have/use that key as at the time of writing.
+   */
+  st->wYear = v3 < 30 ? 2000 + v3 : v3 < 100 ? 1900 + v3 : v3;
+  TRACE("Returning date %ld/%ld/%d\n", v1, v2, st->wYear);
+  return S_OK;
+}
+
 /******************************************************************************
- *		VarDateFromStr		[OLEAUT32.94]
- * The string representing the date is composed of two parts, a date and time.
+ * VarDateFromStr [OLEAUT32.94]
  *
- * The format of the time is has follows:
- * hh[:mm][:ss][AM|PM]
- * Whitespace can be inserted anywhere between these tokens.  A whitespace consists
- * of space and/or tab characters, which are ignored.
+ * Convert a VT_BSTR to at VT_DATE.
  *
- * The formats for the date part are has follows:
- * mm/[dd/][yy]yy
- * [dd/]mm/[yy]yy
- * [yy]yy/mm/dd
- * January dd[,] [yy]yy
- * dd January [yy]yy
- * [yy]yy January dd
- * Whitespace can be inserted anywhere between these tokens.
+ * PARAMS
+ *  strIn    [I] String to convert
+ *  lcid     [I] Locale identifier for the conversion
+ *  dwFlags  [I] Flags affecting the conversion (VAR_ flags from "oleauto.h")
+ *  pdateOut [O] Destination for the converted value
  *
- * The formats for the date and time string are has follows.
- * date[whitespace][time]
- * [time][whitespace]date
+ * RETURNS
+ *  Success: S_OK. pdateOut contains the converted value.
+ *  FAILURE: An HRESULT error code indicating the prolem.
  *
- * These are the only characters allowed in a string representing a date and time:
- * [A-Z] [a-z] [0-9] ':' '-' '/' ',' ' ' '\t'
+ * NOTES
+ *  Any date format that can be created using the date formats from lcid
+ *  (Either from kernel Nls functions, variant conversion or formatting) is a
+ *  valid input to this function. In addition, a few more esoteric formats are
+ *  also supported for compatability with the native version. The date is
+ *  interpreted according to the date settings in the control panel, unless
+ *  the date is invalid in that format, in which the most compatable format
+ *  that produces a valid date will be used.
  */
 HRESULT WINAPI VarDateFromStr(OLECHAR* strIn, LCID lcid, ULONG dwFlags, DATE* pdateOut)
 {
-    HRESULT ret = S_OK;
-    struct tm TM;
+  static const USHORT ParseDateTokens[] =
+  {
+    LOCALE_SMONTHNAME1, LOCALE_SMONTHNAME2, LOCALE_SMONTHNAME3, LOCALE_SMONTHNAME4,
+    LOCALE_SMONTHNAME5, LOCALE_SMONTHNAME6, LOCALE_SMONTHNAME7, LOCALE_SMONTHNAME8,
+    LOCALE_SMONTHNAME9, LOCALE_SMONTHNAME10, LOCALE_SMONTHNAME11, LOCALE_SMONTHNAME12,
+    LOCALE_SMONTHNAME13,
+    LOCALE_SABBREVMONTHNAME1, LOCALE_SABBREVMONTHNAME2, LOCALE_SABBREVMONTHNAME3,
+    LOCALE_SABBREVMONTHNAME4, LOCALE_SABBREVMONTHNAME5, LOCALE_SABBREVMONTHNAME6,
+    LOCALE_SABBREVMONTHNAME7, LOCALE_SABBREVMONTHNAME8, LOCALE_SABBREVMONTHNAME9,
+    LOCALE_SABBREVMONTHNAME10, LOCALE_SABBREVMONTHNAME11, LOCALE_SABBREVMONTHNAME12,
+    LOCALE_SABBREVMONTHNAME13,
+    LOCALE_SDAYNAME1, LOCALE_SDAYNAME2, LOCALE_SDAYNAME3, LOCALE_SDAYNAME4,
+    LOCALE_SDAYNAME5, LOCALE_SDAYNAME6, LOCALE_SDAYNAME7,
+    LOCALE_SABBREVDAYNAME1, LOCALE_SABBREVDAYNAME2, LOCALE_SABBREVDAYNAME3,
+    LOCALE_SABBREVDAYNAME4, LOCALE_SABBREVDAYNAME5, LOCALE_SABBREVDAYNAME6,
+    LOCALE_SABBREVDAYNAME7,
+    LOCALE_S1159, LOCALE_S2359
+  };
+  static const BYTE ParseDateMonths[] =
+  {
+    1,2,3,4,5,6,7,8,9,10,11,12,13,
+    1,2,3,4,5,6,7,8,9,10,11,12,13
+  };
+  size_t i;
+  BSTR tokens[sizeof(ParseDateTokens)/sizeof(ParseDateTokens[0])];
+  DATEPARSE dp;
+  DWORD dwDateSeps = 0, iDate = 0;
+  HRESULT hRet = S_OK;
 
-    memset( &TM, 0, sizeof(TM) );
+  if ((dwFlags & (VAR_TIMEVALUEONLY|VAR_DATEVALUEONLY)) ==
+      (VAR_TIMEVALUEONLY|VAR_DATEVALUEONLY))
+    return E_INVALIDARG;
 
-    TRACE("( %p, %lx, %lx, %p ), stub\n", strIn, lcid, dwFlags, pdateOut );
+  if (!strIn)
+    return DISP_E_TYPEMISMATCH;
 
-    if( DateTimeStringToTm( strIn, dwFlags, &TM ) )
+  *pdateOut = 0.0;
+
+  TRACE("(%s,0x%08lx,0x%08lx,%p)\n", debugstr_w(strIn), lcid, dwFlags, pdateOut);
+
+  memset(&dp, 0, sizeof(dp));
+
+  GetLocaleInfoW(lcid, LOCALE_IDATE|LOCALE_RETURN_NUMBER|(dwFlags & LOCALE_NOUSEROVERRIDE),
+                 (LPWSTR)&iDate, sizeof(iDate)/sizeof(WCHAR));
+  TRACE("iDate is %ld\n", iDate);
+
+  /* Get the month/day/am/pm tokens for this locale */
+  for (i = 0; i < sizeof(tokens)/sizeof(tokens[0]); i++)
+  {
+    WCHAR buff[128];
+    LCTYPE lctype =  ParseDateTokens[i] | (dwFlags & LOCALE_NOUSEROVERRIDE);
+
+    /* FIXME: Alternate calendars - should use GetCalendarInfo() and/or
+     *        GetAltMonthNames(). We should really cache these strings too.
+     */
+    buff[0] = '\0';
+    GetLocaleInfoW(lcid, lctype, buff, sizeof(buff)/sizeof(WCHAR));
+    tokens[i] = SysAllocString(buff);
+    TRACE("token %d is %s\n", i, debugstr_w(tokens[i]));
+  }
+
+  /* Parse the string into our structure */
+  while (*strIn)
+  {
+    if (dp.dwCount > 6)
+      break;
+
+    if (isdigitW(*strIn))
     {
-        if( TmToDATE( &TM, pdateOut ) == FALSE )
+      dp.dwValues[dp.dwCount] = strtoulW(strIn, &strIn, 10);
+      dp.dwCount++;
+      strIn--;
+    }
+    else if (isalpha(*strIn))
+    {
+      BOOL bFound = FALSE;
+
+      for (i = 0; i < sizeof(tokens)/sizeof(tokens[0]); i++)
+      {
+        DWORD dwLen = strlenW(tokens[i]);
+        if (dwLen && !strncmpiW(strIn, tokens[i], dwLen))
         {
-            ret = E_INVALIDARG;
+          if (i <= 25)
+          {
+            dp.dwValues[dp.dwCount] = ParseDateMonths[i];
+            dp.dwFlags[dp.dwCount] |= (DP_MONTH|DP_DATESEP);
+            dp.dwCount++;
+          }
+          else if (i > 39)
+          {
+            if (!dp.dwCount || dp.dwParseFlags & (DP_AM|DP_PM))
+              hRet = DISP_E_TYPEMISMATCH;
+            else
+            {
+              dp.dwFlags[dp.dwCount - 1] |= (i == 40 ? DP_AM : DP_PM);
+              dp.dwParseFlags |= (i == 40 ? DP_AM : DP_PM);
+            }
+          }
+          strIn += (dwLen - 1);
+          bFound = TRUE;
+          break;
         }
+      }
+
+      if (!bFound)
+      {
+        if ((*strIn == 'a' || *strIn == 'A' || *strIn == 'p' || *strIn == 'P') &&
+            (dp.dwCount && !(dp.dwParseFlags & (DP_AM|DP_PM))))
+        {
+          /* Special case - 'a' and 'p' are recognised as short for am/pm */
+          if (*strIn == 'a' || *strIn == 'A')
+          {
+            dp.dwFlags[dp.dwCount - 1] |= DP_AM;
+            dp.dwParseFlags |=  DP_AM;
+          }
+          else
+          {
+            dp.dwFlags[dp.dwCount - 1] |= DP_PM;
+            dp.dwParseFlags |=  DP_PM;
+          }
+          strIn++;
+        }
+        else
+        {
+          TRACE("No matching token for %s\n", debugstr_w(strIn));
+          hRet = DISP_E_TYPEMISMATCH;
+          break;
+        }
+      }
+    }
+    else if (*strIn == ':' ||  *strIn == '.')
+    {
+      if (!dp.dwCount || !strIn[1])
+        hRet = DISP_E_TYPEMISMATCH;
+      else
+        dp.dwFlags[dp.dwCount - 1] |= DP_TIMESEP;
+    }
+    else if (*strIn == '-' || *strIn == '/')
+    {
+      dwDateSeps++;
+      if (dwDateSeps > 2 || !dp.dwCount || !strIn[1])
+        hRet = DISP_E_TYPEMISMATCH;
+      else
+        dp.dwFlags[dp.dwCount - 1] |= DP_DATESEP;
+    }
+    else if (*strIn == ',' || isspaceW(*strIn))
+    {
+      if (*strIn == ',' && !strIn[1])
+        hRet = DISP_E_TYPEMISMATCH;
     }
     else
     {
-        ret = DISP_E_TYPEMISMATCH;
+      hRet = DISP_E_TYPEMISMATCH;
     }
-    TRACE("Return value %f\n", *pdateOut);
-	return ret;
+    strIn++;
+  }
+
+  if (!dp.dwCount || dp.dwCount > 6 ||
+      (dp.dwCount == 1 && !(dp.dwParseFlags & (DP_AM|DP_PM))))
+    hRet = DISP_E_TYPEMISMATCH;
+
+  if (SUCCEEDED(hRet))
+  {
+    SYSTEMTIME st;
+    DWORD dwOffset = 0; /* Start of date fields in dp.dwValues */
+
+    st.wDayOfWeek = st.wHour = st.wMinute = st.wSecond = st.wMilliseconds = 0;
+
+    /* Figure out which numbers correspond to which fields.
+     *
+     * This switch statement works based on the fact that native interprets any
+     * fields that are not joined with a time seperator ('.' or ':') as date
+     * fields. Thus we construct a value from 0-32 where each set bit indicates
+     * a time field. This encapsulates the hundreds of permutations of 2-6 fields.
+     * For valid permutations, we set dwOffset to point to the first date field
+     * and shorten dp.dwCount by the number of time fields found. The real
+     * magic here occurs in VARIANT_MakeDate() above, where we determine what
+     * each date number must represent in the context of iDate.
+     */
+    TRACE("0x%08lx\n", TIMEFLAG(0)|TIMEFLAG(1)|TIMEFLAG(2)|TIMEFLAG(3)|TIMEFLAG(4));
+
+    switch (TIMEFLAG(0)|TIMEFLAG(1)|TIMEFLAG(2)|TIMEFLAG(3)|TIMEFLAG(4))
+    {
+    case 0x1: /* TT TTDD TTDDD */
+      if (dp.dwCount > 3 &&
+          ((dp.dwFlags[2] & (DP_AM|DP_PM)) || (dp.dwFlags[3] & (DP_AM|DP_PM)) ||
+          (dp.dwFlags[4] & (DP_AM|DP_PM))))
+        hRet = DISP_E_TYPEMISMATCH;
+      else if (dp.dwCount != 2 && dp.dwCount != 4 && dp.dwCount != 5)
+        hRet = DISP_E_TYPEMISMATCH;
+      st.wHour = dp.dwValues[0];
+      st.wMinute  = dp.dwValues[1];
+      dp.dwCount -= 2;
+      dwOffset = 2;
+      break;
+
+    case 0x3: /* TTT TTTDD TTTDDD */
+      if (dp.dwCount > 4 &&
+          ((dp.dwFlags[3] & (DP_AM|DP_PM)) || (dp.dwFlags[4] & (DP_AM|DP_PM)) ||
+          (dp.dwFlags[5] & (DP_AM|DP_PM))))
+        hRet = DISP_E_TYPEMISMATCH;
+      else if (dp.dwCount != 3 && dp.dwCount != 5 && dp.dwCount != 6)
+        hRet = DISP_E_TYPEMISMATCH;
+      st.wHour   = dp.dwValues[0];
+      st.wMinute = dp.dwValues[1];
+      st.wSecond = dp.dwValues[2];
+      dwOffset = 3;
+      dp.dwCount -= 3;
+      break;
+
+    case 0x4: /* DDTT */
+      if (dp.dwCount != 4 ||
+          (dp.dwFlags[0] & (DP_AM|DP_PM)) || (dp.dwFlags[1] & (DP_AM|DP_PM)))
+        hRet = DISP_E_TYPEMISMATCH;
+
+      st.wHour = dp.dwValues[2];
+      st.wMinute  = dp.dwValues[3];
+      dp.dwCount -= 2;
+      break;
+
+   case 0x0: /* T DD DDD TDDD TDDD */
+      if (dp.dwCount == 1 && (dp.dwParseFlags & (DP_AM|DP_PM)))
+      {
+        st.wHour = dp.dwValues[0]; /* T */
+        dp.dwCount = 0;
+        break;
+      }
+      else if (dp.dwCount > 4 || (dp.dwCount < 3 && dp.dwParseFlags & (DP_AM|DP_PM)))
+      {
+        hRet = DISP_E_TYPEMISMATCH;
+      }
+      else if (dp.dwCount == 3)
+      {
+        if (dp.dwFlags[0] & (DP_AM|DP_PM)) /* TDD */
+        {
+          dp.dwCount = 2;
+          st.wHour = dp.dwValues[0];
+          dwOffset = 1;
+          break;
+        }
+        if (dp.dwFlags[2] & (DP_AM|DP_PM)) /* DDT */
+        {
+          dp.dwCount = 2;
+          st.wHour = dp.dwValues[2];
+          break;
+        }
+        else if (dp.dwParseFlags & (DP_AM|DP_PM))
+          hRet = DISP_E_TYPEMISMATCH;
+      }
+      else if (dp.dwCount == 4)
+      {
+        dp.dwCount = 3;
+        if (dp.dwFlags[0] & (DP_AM|DP_PM)) /* TDDD */
+        {
+          st.wHour = dp.dwValues[0];
+          dwOffset = 1;
+        }
+        else if (dp.dwFlags[3] & (DP_AM|DP_PM)) /* DDDT */
+        {
+          st.wHour = dp.dwValues[3];
+        }
+        else
+          hRet = DISP_E_TYPEMISMATCH;
+        break;
+      }
+      /* .. fall through .. */
+
+    case 0x8: /* DDDTT */
+      if ((dp.dwCount == 2 && (dp.dwParseFlags & (DP_AM|DP_PM))) ||
+          (dp.dwCount == 5 && ((dp.dwFlags[0] & (DP_AM|DP_PM)) ||
+           (dp.dwFlags[1] & (DP_AM|DP_PM)) || (dp.dwFlags[2] & (DP_AM|DP_PM)))) ||
+           dp.dwCount == 4 || dp.dwCount == 6)
+        hRet = DISP_E_TYPEMISMATCH;
+      st.wHour   = dp.dwValues[3];
+      st.wMinute = dp.dwValues[4];
+      if (dp.dwCount == 5)
+        dp.dwCount -= 2;
+      break;
+
+    case 0xC: /* DDTTT */
+      if (dp.dwCount != 5 ||
+          (dp.dwFlags[0] & (DP_AM|DP_PM)) || (dp.dwFlags[1] & (DP_AM|DP_PM)))
+        hRet = DISP_E_TYPEMISMATCH;
+      st.wHour   = dp.dwValues[2];
+      st.wMinute = dp.dwValues[3];
+      st.wSecond = dp.dwValues[4];
+      dp.dwCount -= 3;
+      break;
+
+    case 0x18: /* DDDTTT */
+      if ((dp.dwFlags[0] & (DP_AM|DP_PM)) || (dp.dwFlags[1] & (DP_AM|DP_PM)) ||
+          (dp.dwFlags[2] & (DP_AM|DP_PM)))
+        hRet = DISP_E_TYPEMISMATCH;
+      st.wHour   = dp.dwValues[3];
+      st.wMinute = dp.dwValues[4];
+      st.wSecond = dp.dwValues[5];
+      dp.dwCount -= 3;
+      break;
+
+    default:
+      hRet = DISP_E_TYPEMISMATCH;
+      break;
+    }
+
+    if (SUCCEEDED(hRet))
+    {
+      hRet = VARIANT_MakeDate(&dp, iDate, dwOffset, &st);
+
+      if (dwFlags & VAR_TIMEVALUEONLY)
+      {
+        st.wYear = 1899;
+        st.wMonth = 12;
+        st.wDay = 30;
+      }
+      else if (dwFlags & VAR_DATEVALUEONLY)
+       st.wHour = st.wMinute = st.wSecond = 0;
+
+      /* Finally, convert the value to a VT_DATE */
+      if (SUCCEEDED(hRet))
+        hRet = SystemTimeToVariantTime(&st, pdateOut) ? S_OK : DISP_E_TYPEMISMATCH;
+    }
+  }
+
+  for (i = 0; i < sizeof(tokens)/sizeof(tokens[0]); i++)
+    SysFreeString(tokens[i]);
+  return hRet;
 }
 
 /******************************************************************************
@@ -3184,56 +3253,73 @@
 
 
 /******************************************************************************
- *		VarBstrFromDate		[OLEAUT32.114]
+ *    VarBstrFromDate    [OLEAUT32.114]
  *
- * The date is implemented using an 8 byte floating-point number.
- * Days are represented by whole numbers increments starting with 0.00 as
- * being December 30 1899, midnight.
- * The hours are expressed as the fractional part of the number.
- * December 30 1899 at midnight = 0.00
- * January 1 1900 at midnight = 2.00
- * January 4 1900 at 6 AM = 5.25
- * January 4 1900 at noon = 5.50
- * December 29 1899 at midnight = -1.00
- * December 18 1899 at midnight = -12.00
- * December 18 1899 at 6AM = -12.25
- * December 18 1899 at 6PM = -12.75
- * December 19 1899 at midnight = -11.00
- * The tm structure is as follows:
- * struct tm {
- *		  int tm_sec;	   seconds after the minute - [0,59]
- *		  int tm_min;	   minutes after the hour - [0,59]
- *		  int tm_hour;	   hours since midnight - [0,23]
- *		  int tm_mday;	   day of the month - [1,31]
- *		  int tm_mon;	   months since January - [0,11]
- *		  int tm_year;	   years
- *		  int tm_wday;	   days since Sunday - [0,6]
- *		  int tm_yday;	   days since January 1 - [0,365]
- *		  int tm_isdst;    daylight savings time flag
- *		  };
+ * Convert a VT_DATE to a VT_BSTR.
+ *
+ * PARAMS
+ *  dateIn   [I] Source
+ *  lcid     [I] LCID for the conversion
+ *  dwFlags  [I] Flags controlling the conversion (VAR_ flags from "oleauto.h")
+ *  pbstrOut [O] Destination
+ *
+ * RETURNS
+ *  Success: S_OK.
+ *  Failure: E_INVALIDARG, if pbstrOut or dateIn is invalid.
+ *           E_OUTOFMEMORY, if memory allocation fails.
  */
 HRESULT WINAPI VarBstrFromDate(DATE dateIn, LCID lcid, ULONG dwFlags, BSTR* pbstrOut)
 {
-    struct tm TM;
-    memset( &TM, 0, sizeof(TM) );
+  SYSTEMTIME st;
+  DWORD dwFormatFlags = dwFlags & LOCALE_NOUSEROVERRIDE;
+  WCHAR date[128], *time;
 
-    TRACE("( %20.20f, %ld, %ld, %p ), stub\n", dateIn, lcid, dwFlags, pbstrOut );
+  TRACE("(%g,0x%08lx,0x%08lx,%p)\n", dateIn, lcid, dwFlags, pbstrOut);
 
-    if( DateToTm( dateIn, dwFlags, &TM ) == FALSE )
-			{
-        return E_INVALIDARG;
-		}
+  if (!pbstrOut || !VariantTimeToSystemTime(dateIn, &st))
+    return E_INVALIDARG;
 
-    if( dwFlags & VAR_DATEVALUEONLY )
-			strftime( pBuffer, BUFFER_MAX, "%x", &TM );
-    else if( dwFlags & VAR_TIMEVALUEONLY )
-			strftime( pBuffer, BUFFER_MAX, "%X", &TM );
-		else
-        strftime( pBuffer, BUFFER_MAX, "%x %X", &TM );
+  *pbstrOut = NULL;
 
-        TRACE("result: %s\n", pBuffer);
-		*pbstrOut = StringDupAtoBstr( pBuffer );
-	return S_OK;
+  if (dwFlags & VAR_CALENDAR_THAI)
+      st.wYear += 553; /* Use the Thai buddhist calendar year */
+  else if (dwFlags & (VAR_CALENDAR_HIJRI|VAR_CALENDAR_GREGORIAN))
+      FIXME("VAR_CALENDAR_HIJRI/VAR_CALENDAR_GREGORIAN not handled\n");
+
+  if (dwFlags & LOCALE_USE_NLS)
+    dwFlags &= ~(VAR_TIMEVALUEONLY|VAR_DATEVALUEONLY);
+  else
+  {
+    double whole = dateIn < 0 ? ceil(dateIn) : floor(dateIn);
+    double partial = dateIn - whole;
+
+    if (whole == 0.0)
+      dwFlags |= VAR_TIMEVALUEONLY;
+    else if (partial < 1e-12)
+      dwFlags |= VAR_DATEVALUEONLY;
+  }
+
+  if (dwFlags & VAR_TIMEVALUEONLY)
+    date[0] = '\0';
+  else
+    if (!GetDateFormatW(lcid, dwFormatFlags|DATE_SHORTDATE, &st, NULL, date,
+                        sizeof(date)/sizeof(WCHAR)))
+      return E_INVALIDARG;
+
+  if (!(dwFlags & VAR_DATEVALUEONLY))
+  {
+    time = date + strlenW(date);
+    if (time != date)
+      *time++ = ' ';
+    if (!GetTimeFormatW(lcid, dwFormatFlags, &st, NULL, time,
+                        sizeof(date)/sizeof(WCHAR)-(time-date)))
+      return E_INVALIDARG;
+  }
+
+  *pbstrOut = SysAllocString(date);
+  if (*pbstrOut)
+    TRACE("returning %s\n", debugstr_w(*pbstrOut));
+  return *pbstrOut ? S_OK : E_OUTOFMEMORY;
 }
 
 /******************************************************************************
@@ -4543,7 +4629,7 @@
   lpUdate->st.wSecond = timePart;
   timePart -= lpUdate->st.wSecond;
   lpUdate->st.wMilliseconds = 0;
-  if (timePart > 0.0005)
+  if (timePart > 0.5)
   {
     /* Round the milliseconds, adjusting the time/date forward if needed */
     if (lpUdate->st.wSecond < 59)
@@ -4583,12 +4669,9 @@
 void VARIANT_GetLocalisedNumberChars(VARIANT_NUMBER_CHARS *lpChars, LCID lcid, DWORD dwFlags)
 {
   static const VARIANT_NUMBER_CHARS defaultChars = { '-','+','.',',','$',0,'.',',' };
-  LCTYPE lctype = 0;
+  LCTYPE lctype = dwFlags & LOCALE_NOUSEROVERRIDE;
   WCHAR buff[4];
 
-  if (dwFlags & VARIANT_NOUSEROVERRIDE)
-    lctype |= LOCALE_NOUSEROVERRIDE;
-
   memcpy(lpChars, &defaultChars, sizeof(defaultChars));
   GET_NUMBER_TEXT(LOCALE_SNEGATIVESIGN, cNegativeSymbol);
   GET_NUMBER_TEXT(LOCALE_SPOSITIVESIGN, cPositiveSymbol);
@@ -4625,7 +4708,7 @@
  * PARAMS
  *  lpszStr [I]   String to parse number from
  *  lcid    [I]   Locale Id for the conversion
- *  dwFlags [I]   Apparently not used
+ *  dwFlags [I]   0, or LOCALE_NOUSEROVERRIDE to use system default number chars
  *  pNumprs [I/O] Destination for parsed number
  *  rgbDig  [O]   Destination for digits read in
  *
@@ -4656,7 +4739,7 @@
   int iMaxDigits = sizeof(rgbTmp) / sizeof(BYTE);
   int cchUsed = 0;
 
-  TRACE("(%s,%ld,%ld,%p,%p)\n", debugstr_w(lpszStr), lcid, dwFlags, pNumprs, rgbDig);
+  TRACE("(%s,%ld,0x%08lx,%p,%p)\n", debugstr_w(lpszStr), lcid, dwFlags, pNumprs, rgbDig);
 
   if (pNumprs->dwInFlags & NUMPRS_HEX_OCT)
     FIXME("dwInFlags & NUMPRS_HEX_OCT not yet implemented!\n");
@@ -6058,33 +6141,192 @@
 }
 
 /**********************************************************************
- *              VarNot [OLEAUT32.174]
+ * VarAbs [OLEAUT32.168]
  *
+ * Convert a variant to its absolute value.
+ *
+ * PARAMS
+ *  pVarIn  [I] Source variant
+ *  pVarOut [O] Destination for converted value
+ *
+ * RETURNS
+ *  Success: S_OK. pVarOut contains the absolute value of pVarIn.
+ *  Failure: An HRESULT error code indicating the error.
+ *
+ * NOTES
+ *  - This function does not process by-reference variants.
+ *  - The type of the value stored in pVarOut depends on the type of pVarIn,
+ *    according to the following table:
+ *| Input Type       Output Type
+ *| ----------       -----------
+ *| VT_BOOL          VT_I2
+ *| VT_BSTR          VT_R8
+ *| (All others)     Unchanged
  */
-HRESULT WINAPI VarNot(LPVARIANT in, LPVARIANT result)
+HRESULT WINAPI VarAbs(LPVARIANT pVarIn, LPVARIANT pVarOut)
 {
-    HRESULT rc = E_FAIL;
+    VARIANT varIn;
+    HRESULT hRet = S_OK;
 
-    TRACE("Var In:\n");
-    dump_Variant(in);
+    TRACE("(%p->(%s%s),%p)\n", pVarIn, debugstr_VT(pVarIn),
+          debugstr_VF(pVarIn), pVarOut);
 
-    if ((V_VT(in)&VT_TYPEMASK) == VT_BOOL) {
+    if (V_ISARRAY(pVarIn) || V_VT(pVarIn) == VT_UNKNOWN ||
+        V_VT(pVarIn) == VT_DISPATCH || V_VT(pVarIn) == VT_RECORD ||
+        V_VT(pVarIn) == VT_ERROR)
+        return DISP_E_TYPEMISMATCH;
 
-        V_VT(result) = VT_BOOL;
-        if (V_BOOL(in)) {
-            V_BOOL(result) = VARIANT_FALSE;
-        } else {
-            V_BOOL(result) = VARIANT_TRUE;
-        }
-        rc = S_OK;
+    *pVarOut = *pVarIn; /* Shallow copy the value, and invert it if needed */
 
-    } else {
-        FIXME("VarNot stub\n");
+#define ABS_CASE(typ,min) \
+    case VT_##typ: if (V_##typ(pVarIn) == min) hRet = DISP_E_OVERFLOW; \
+                  else if (V_##typ(pVarIn) < 0) V_##typ(pVarOut) = -V_##typ(pVarIn); \
+                  break
+
+    switch (V_VT(pVarIn))
+    {
+    ABS_CASE(I1,I1_MIN);
+    case VT_BOOL:
+        V_VT(pVarOut) = VT_I2;
+        /* BOOL->I2, Fall through ... */
+    ABS_CASE(I2,I2_MIN);
+    case VT_INT:
+    ABS_CASE(I4,I4_MIN);
+    ABS_CASE(I8,I8_MIN);
+    ABS_CASE(R4,R4_MIN);
+    case VT_BSTR:
+        hRet = VarR8FromStr(V_BSTR(pVarIn), LOCALE_USER_DEFAULT, 0, &V_R8(&varIn));
+        if (FAILED(hRet))
+            break;
+        V_VT(pVarOut) = VT_R8;
+        pVarIn = &varIn;
+        /* Fall through ... */
+    case VT_DATE:
+    ABS_CASE(R8,R8_MIN);
+    case VT_CY:
+        /* FIXME: Waiting for this function to be implemented */
+#if 0
+        hRet = VarCyAbs(V_CY(pVarIn), & V_CY(pVarOut));
+#endif
+        break;
+    case VT_DECIMAL:
+        DEC_SIGN(&V_DECIMAL(pVarOut)) &= ~DECIMAL_NEG;
+        break;
+    case VT_UI1:
+    case VT_UI2:
+    case VT_UINT:
+    case VT_UI4:
+    case VT_UI8:
+    case VT_EMPTY:
+    case VT_NULL:
+        /* No-Op */
+        break;
+    default:
+        hRet = DISP_E_BADVARTYPE;
     }
 
-    TRACE("rc=%d, Result:\n", (int) rc);
-    dump_Variant(result);
-    return rc;
+    return hRet;
+}
+
+/**********************************************************************
+ *              VarNot [OLEAUT32.174]
+ *
+ * Perform a not operation on a variant.
+ *
+ * PARAMS
+ *  pVarIn  [I] Source variant
+ *  pVarOut [O] Destination for converted value
+ *
+ * RETURNS
+ *  Success: S_OK. pVarOut contains the converted value.
+ *  Failure: An HRESULT error code indicating the error.
+ *
+ * NOTES
+ *  - Strictly speaking, this function performs a bitwise ones compliment
+ *    on the variants value (after possibly converting to VT_I4, see below).
+ *    This only behaves like a boolean not operation if the value in
+ *    pVarIn is either VARIANT_TRUE or VARIANT_FALSE and the type is signed.
+ *  - To perform a genuine not operation, convert the variant to a VT_BOOL
+ *    before calling this function.
+ *  - This function does not process by-reference variants.
+ *  - The type of the value stored in pVarOut depends on the type of pVarIn,
+ *    according to the following table:
+ *| Input Type       Output Type
+ *| ----------       -----------
+ *| VT_R4            VT_I4
+ *| VT_R8            VT_I4
+ *| VT_BSTR          VT_I4
+ *| VT_DECIMAL       VT_I4
+ *| VT_CY            VT_I4
+ *| (All others)     Unchanged
+ */
+HRESULT WINAPI VarNot(LPVARIANT pVarIn, LPVARIANT pVarOut)
+{
+    VARIANT varIn;
+    HRESULT hRet = S_OK;
+
+    TRACE("(%p->(%s%s),%p)\n", pVarIn, debugstr_VT(pVarIn),
+          debugstr_VF(pVarIn), pVarOut);
+
+    V_VT(pVarOut) = V_VT(pVarIn);
+
+    switch (V_VT(pVarIn))
+    {
+    case VT_I1:  V_I1(pVarOut) = ~V_I1(pVarIn); break;
+    case VT_UI1: V_UI1(pVarOut) = ~V_UI1(pVarIn); break;
+    case VT_BOOL:
+    case VT_I2:  V_I2(pVarOut) = ~V_I2(pVarIn); break;
+    case VT_UI2: V_UI2(pVarOut) = ~V_UI2(pVarIn); break;
+    case VT_DECIMAL:
+        /* FIXME  Waiting for this function to be implemented */
+/*        hRet = VarI4FromDec(&V_DECIMAL(pVarIn), &V_I4(&varIn)); */
+        hRet = DISP_E_OVERFLOW;
+        if (FAILED(hRet))
+            break;
+        pVarIn = &varIn;
+        V_VT(pVarOut) = VT_I4;
+        /* Fall through ... */
+    case VT_INT:
+    case VT_I4:  V_I4(pVarOut) = ~V_I4(pVarIn); break;
+    case VT_UINT:
+    case VT_UI4: V_UI4(pVarOut) = ~V_UI4(pVarIn); break;
+    case VT_I8:  V_I8(pVarOut) = ~V_I8(pVarIn); break;
+    case VT_UI8: V_UI8(pVarOut) = ~V_UI8(pVarIn); break;
+    case VT_R4:
+        hRet = VarI4FromR4(V_R4(pVarIn), &V_I4(pVarOut));
+        V_I4(pVarOut) = ~V_I4(pVarOut);
+        V_VT(pVarOut) = VT_I4;
+        break;
+    case VT_BSTR:
+        hRet = VarR8FromStr(V_BSTR(pVarIn), LOCALE_USER_DEFAULT, 0, &V_R8(&varIn));
+        if (FAILED(hRet))
+            break;
+        pVarIn = &varIn;
+        /* Fall through ... */
+    case VT_DATE:
+    case VT_R8:
+        hRet = VarI4FromR8(V_R8(pVarIn), &V_I4(pVarOut));
+        V_I4(pVarOut) = ~V_I4(pVarOut);
+        V_VT(pVarOut) = VT_I4;
+        break;
+    case VT_CY:
+        /* FIXME: */
+        break;
+    case VT_EMPTY:
+    case VT_NULL:
+        /* No-Op */
+        break;
+    default:
+        if (V_TYPE(pVarIn) == VT_CLSID || /* VT_CLSID is a special case */
+            FAILED(VARIANT_ValidateType(V_VT(pVarIn))))
+            hRet = DISP_E_BADVARTYPE;
+        else
+            hRet = DISP_E_TYPEMISMATCH;
+    }
+    if (FAILED(hRet))
+      V_VT(pVarOut) = VT_EMPTY;
+
+    return hRet;
 }
 
 /**********************************************************************

[Index of Archives]     [Gimp for Windows]     [Red Hat]     [Samba]     [Yosemite Camping]     [Graphics Cards]     [Wine Home]

  Powered by Linux