diff --git a/windows/registry/key.go b/windows/registry/key.go index 39aeeb644..25aa1f4f6 100644 --- a/windows/registry/key.go +++ b/windows/registry/key.go @@ -198,7 +198,20 @@ type KeyInfo struct { // ModTime returns the key's last write time. func (ki *KeyInfo) ModTime() time.Time { - return time.Unix(0, ki.lastWriteTime.Nanoseconds()) + lastHigh, lastLow := ki.lastWriteTime.HighDateTime, ki.lastWriteTime.LowDateTime + // 100-nanosecond intervals since January 1, 1601 + hsec := uint64(lastHigh)<<32 + uint64(lastLow) + // Convert _before_ gauging; the nanosecond difference between Epoch (00:00:00 + // UTC, January 1, 1970) and Filetime's zero offset (January 1, 1601) is out + // of bounds for int64: -11644473600*1e7*1e2 < math.MinInt64 + sec := int64(hsec/1e7) - 11644473600 + nsec := int64(hsec%1e7) * 100 + return time.Unix(sec, nsec) +} + +// ModTimeZero reports whether the key's last write time is zero. +func (ki *KeyInfo) ModTimeZero() bool { + return ki.lastWriteTime.LowDateTime == 0 && ki.lastWriteTime.HighDateTime == 0 } // Stat retrieves information about the open key k. diff --git a/windows/registry/registry_test.go b/windows/registry/registry_test.go index 6e7bec505..995acc96b 100644 --- a/windows/registry/registry_test.go +++ b/windows/registry/registry_test.go @@ -9,6 +9,7 @@ package registry_test import ( "bytes" "crypto/rand" + "errors" "os" "syscall" "testing" @@ -674,3 +675,30 @@ func GetDynamicTimeZoneInformation(dtzi *DynamicTimezoneinformation) (rc uint32, } return } + +func TestModTimeZeroValue(t *testing.T) { + k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\009`, registry.READ) + if err != nil { + if errors.Is(err, syscall.ERROR_FILE_NOT_FOUND) { + t.Skip("Perflib key not found; skipping") + } + t.Fatal(err) + } + defer k.Close() + + // Modification time of Perflib keys is known to be set to + // Filetime's zero value: get stats and check. + stats, err := k.Stat() + if err != nil { + t.Fatal(err) + } + // First verify input is zero (assume ModTimeZero uses it directly). + if !stats.ModTimeZero() { + t.Error("Modification time of Perflib key should be zero") + } + // Then check ModTime directly thus conversion implicitly. + modTime := stats.ModTime() + if !modTime.Equal(time.Date(1601, time.January, 1, 0, 0, 0, 0, time.UTC)) { + t.Errorf("ModTime should be 1601-01-01, but is %v", modTime) + } +}