Merge pull request #252 from huskar-t/master

fix: GetVariantDate return with milliseconds
diff --git a/variant_date_386.go b/variant_date_386.go
index 1b970f6..973a217 100644
--- a/variant_date_386.go
+++ b/variant_date_386.go
@@ -1,22 +1,47 @@
+//go:build windows && 386
 // +build windows,386
 
 package ole
 
 import (
 	"errors"
+	"math"
 	"syscall"
 	"time"
 	"unsafe"
 )
 
+const ONETHOUSANDMILLISECONDS = 0.0000115740740740
+
 // GetVariantDate converts COM Variant Time value to Go time.Time.
 func GetVariantDate(value uint64) (time.Time, error) {
+	halfSecond := ONETHOUSANDMILLISECONDS / 2.0
+	dVariantTime := math.Float64frombits(value)
 	var st syscall.Systemtime
-	v1 := uint32(value)
-	v2 := uint32(value >> 32)
+	adjustedVariantTime := dVariantTime - halfSecond
+	uAdjustedVariantTime := math.Float64bits(adjustedVariantTime)
+	v1 := uint32(uAdjustedVariantTime)
+	v2 := uint32(uAdjustedVariantTime >> 32)
 	r, _, _ := procVariantTimeToSystemTime.Call(uintptr(v1), uintptr(v2), uintptr(unsafe.Pointer(&st)))
 	if r != 0 {
-		return time.Date(int(st.Year), time.Month(st.Month), int(st.Day), int(st.Hour), int(st.Minute), int(st.Second), int(st.Milliseconds/1000), time.UTC), nil
+		fraction := dVariantTime - float64(int(dVariantTime))
+		hours := (fraction - float64(int(fraction))) * 24
+		minutes := (hours - float64(int(hours))) * 60
+		seconds := (minutes - float64(int(minutes))) * 60
+		milliseconds := (seconds - float64(int(seconds))) * 1000
+		milliseconds = milliseconds + 0.5
+		if milliseconds < 1.0 || milliseconds > 999.0 {
+			var st2 syscall.Systemtime
+			v1 = uint32(value)
+			v2 = uint32(value >> 32)
+			r2, _, _ := procVariantTimeToSystemTime.Call(uintptr(v1), uintptr(v2), uintptr(unsafe.Pointer(&st2)))
+			if r2 != 0 {
+				return time.Date(int(st2.Year), time.Month(st2.Month), int(st2.Day), int(st2.Hour), int(st2.Minute), int(st2.Second), 0, time.UTC), nil
+			} else {
+				return time.Now(), errors.New("Could not convert to time, passing current time.")
+			}
+		}
+		return time.Date(int(st.Year), time.Month(st.Month), int(st.Day), int(st.Hour), int(st.Minute), int(st.Second), int(int16(milliseconds))*1e6, time.UTC), nil
 	}
 	return time.Now(), errors.New("Could not convert to time, passing current time.")
 }
diff --git a/variant_date_386_test.go b/variant_date_386_test.go
new file mode 100644
index 0000000..b572b7f
--- /dev/null
+++ b/variant_date_386_test.go
@@ -0,0 +1,111 @@
+//go:build windows && 386
+// +build windows,386
+
+package ole
+
+import (
+	"errors"
+	"math"
+	"reflect"
+	"syscall"
+	"testing"
+	"time"
+	"unsafe"
+)
+
+func TestGetVariantDate(t *testing.T) {
+	type args struct {
+		value uint64
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    time.Time
+		wantErr bool
+	}{
+		{
+			name:    "2023-10-30 23:30:30:000",
+			args:    args{value: math.Float64bits(45229.9795138889)},
+			want:    time.Date(2023, 10, 30, 23, 30, 30, 0, time.UTC),
+			wantErr: false,
+		},
+		{
+			name:    "2023-10-30 23:30:30:355",
+			args:    args{value: math.Float64bits(45229.979518)},
+			want:    time.Date(2023, 10, 30, 23, 30, 30, 355000000, time.UTC),
+			wantErr: false,
+		},
+		{
+			name:    "2023-10-30 23:30:30:960",
+			args:    args{value: math.Float64bits(45229.979525)},
+			want:    time.Date(2023, 10, 30, 23, 30, 30, 960000000, time.UTC),
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := GetVariantDate(tt.args.value)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("GetVariantDate() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("GetVariantDate() got = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func getVariantDateWithoutMillSeconds(value uint64) (time.Time, error) {
+	var st syscall.Systemtime
+	v1 := uint32(value)
+	v2 := uint32(value >> 32)
+	r, _, _ := procVariantTimeToSystemTime.Call(uintptr(v1), uintptr(v2), uintptr(unsafe.Pointer(&st)))
+	if r != 0 {
+		return time.Date(int(st.Year), time.Month(st.Month), int(st.Day), int(st.Hour), int(st.Minute), int(st.Second), int(st.Milliseconds/1000), time.UTC), nil
+	}
+	return time.Now(), errors.New("Could not convert to time, passing current time.")
+}
+
+func TestGetVariantDateWithoutMillSeconds(t *testing.T) {
+	type args struct {
+		value uint64
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    time.Time
+		wantErr bool
+	}{
+		{
+			name:    "2023-10-30 23:30:30:000",
+			args:    args{value: math.Float64bits(45229.9795138889)},
+			want:    time.Date(2023, 10, 30, 23, 30, 30, 0, time.UTC),
+			wantErr: false,
+		},
+		{
+			name:    "2023-10-30 23:30:30:355",
+			args:    args{value: math.Float64bits(45229.979518)},
+			want:    time.Date(2023, 10, 30, 23, 30, 30, 0, time.UTC),
+			wantErr: false,
+		},
+		{
+			name:    "2023-10-30 23:30:30:960",
+			args:    args{value: math.Float64bits(45229.979525)},
+			want:    time.Date(2023, 10, 30, 23, 30, 31, 0, time.UTC),
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := getVariantDateWithoutMillSeconds(tt.args.value)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("GetVariantDate() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("GetVariantDate() got = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/variant_date_amd64.go b/variant_date_amd64.go
index 6952f1f..5929ad7 100644
--- a/variant_date_amd64.go
+++ b/variant_date_amd64.go
@@ -1,20 +1,43 @@
+//go:build windows && amd64
 // +build windows,amd64
 
 package ole
 
 import (
 	"errors"
+	"math"
 	"syscall"
 	"time"
 	"unsafe"
 )
 
+const ONETHOUSANDMILLISECONDS = 0.0000115740740740
+
 // GetVariantDate converts COM Variant Time value to Go time.Time.
 func GetVariantDate(value uint64) (time.Time, error) {
+	halfSecond := ONETHOUSANDMILLISECONDS / 2.0
+	dVariantTime := math.Float64frombits(value)
 	var st syscall.Systemtime
-	r, _, _ := procVariantTimeToSystemTime.Call(uintptr(value), uintptr(unsafe.Pointer(&st)))
+	adjustedVariantTime := dVariantTime - halfSecond
+	uAdjustedVariantTime := math.Float64bits(adjustedVariantTime)
+	r, _, _ := procVariantTimeToSystemTime.Call(uintptr(uAdjustedVariantTime), uintptr(unsafe.Pointer(&st)))
 	if r != 0 {
-		return time.Date(int(st.Year), time.Month(st.Month), int(st.Day), int(st.Hour), int(st.Minute), int(st.Second), int(st.Milliseconds/1000), time.UTC), nil
+		fraction := dVariantTime - float64(int(dVariantTime))
+		hours := (fraction - float64(int(fraction))) * 24
+		minutes := (hours - float64(int(hours))) * 60
+		seconds := (minutes - float64(int(minutes))) * 60
+		milliseconds := (seconds - float64(int(seconds))) * 1000
+		milliseconds = milliseconds + 0.5
+		if milliseconds < 1.0 || milliseconds > 999.0 {
+			var st2 syscall.Systemtime
+			r2, _, _ := procVariantTimeToSystemTime.Call(uintptr(value), uintptr(unsafe.Pointer(&st2)))
+			if r2 != 0 {
+				return time.Date(int(st2.Year), time.Month(st2.Month), int(st2.Day), int(st2.Hour), int(st2.Minute), int(st2.Second), 0, time.UTC), nil
+			} else {
+				return time.Now(), errors.New("Could not convert to time, passing current time.")
+			}
+		}
+		return time.Date(int(st.Year), time.Month(st.Month), int(st.Day), int(st.Hour), int(st.Minute), int(st.Second), int(int16(milliseconds))*1e6, time.UTC), nil
 	}
 	return time.Now(), errors.New("Could not convert to time, passing current time.")
 }
diff --git a/variant_date_amd64_test.go b/variant_date_amd64_test.go
new file mode 100644
index 0000000..fe273a9
--- /dev/null
+++ b/variant_date_amd64_test.go
@@ -0,0 +1,109 @@
+//go:build windows && amd64
+// +build windows,amd64
+
+package ole
+
+import (
+	"errors"
+	"math"
+	"reflect"
+	"syscall"
+	"testing"
+	"time"
+	"unsafe"
+)
+
+func TestGetVariantDate(t *testing.T) {
+	type args struct {
+		value uint64
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    time.Time
+		wantErr bool
+	}{
+		{
+			name:    "2023-10-30 23:30:30:000",
+			args:    args{value: math.Float64bits(45229.9795138889)},
+			want:    time.Date(2023, 10, 30, 23, 30, 30, 0, time.UTC),
+			wantErr: false,
+		},
+		{
+			name:    "2023-10-30 23:30:30:355",
+			args:    args{value: math.Float64bits(45229.979518)},
+			want:    time.Date(2023, 10, 30, 23, 30, 30, 355000000, time.UTC),
+			wantErr: false,
+		},
+		{
+			name:    "2023-10-30 23:30:30:960",
+			args:    args{value: math.Float64bits(45229.979525)},
+			want:    time.Date(2023, 10, 30, 23, 30, 30, 960000000, time.UTC),
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := GetVariantDate(tt.args.value)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("GetVariantDate() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("GetVariantDate() got = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func getVariantDateWithoutMillSeconds(value uint64) (time.Time, error) {
+	var st syscall.Systemtime
+	r, _, _ := procVariantTimeToSystemTime.Call(uintptr(value), uintptr(unsafe.Pointer(&st)))
+	if r != 0 {
+		return time.Date(int(st.Year), time.Month(st.Month), int(st.Day), int(st.Hour), int(st.Minute), int(st.Second), int(st.Milliseconds/1000), time.UTC), nil
+	}
+	return time.Now(), errors.New("Could not convert to time, passing current time.")
+}
+
+func TestGetVariantDateWithoutMillSeconds(t *testing.T) {
+	type args struct {
+		value uint64
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    time.Time
+		wantErr bool
+	}{
+		{
+			name:    "2023-10-30 23:30:30:000",
+			args:    args{value: math.Float64bits(45229.9795138889)},
+			want:    time.Date(2023, 10, 30, 23, 30, 30, 0, time.UTC),
+			wantErr: false,
+		},
+		{
+			name:    "2023-10-30 23:30:30:355",
+			args:    args{value: math.Float64bits(45229.979518)},
+			want:    time.Date(2023, 10, 30, 23, 30, 30, 0, time.UTC),
+			wantErr: false,
+		},
+		{
+			name:    "2023-10-30 23:30:30:960",
+			args:    args{value: math.Float64bits(45229.979525)},
+			want:    time.Date(2023, 10, 30, 23, 30, 31, 0, time.UTC),
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := getVariantDateWithoutMillSeconds(tt.args.value)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("GetVariantDate() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("GetVariantDate() got = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}