Skip to content

Commit 5c27ec0

Browse files
committed
windows: fix filetime conversions and deprecate Nanoseconds
Filetime.Nanoseconds() has a major drawback: the returned int64 is too small to represent Filetime's zero value (January 1, 1601) in terms of nanoseconds since Epoch (00:00:00 UTC, January 1, 1970); MinInt64 only dates back to year 1677. This has real-life implications, e.g., some Windows sub systems (Perflib, to name one) create registry keys with the last write time property set to zero. In this case, ModTime() reports an underflow-affected value of 2185-07-22T00:34:33.709551+01:00. This commit deprecates Nanoseconds() in favor of the new Unix() that returns a well-known pair of seconds and nanoseconds thus is capable to cover the full range of Filetime values. ModTime() uses Unix() accordingly to produce correct time.Time values again. Additionally, ModTimeZero() provides a convenient way to check for a last write time value of zero in analogy to time.Time.IsZero(); no need to specify January 1, 1601 manually.
1 parent 751c3c6 commit 5c27ec0

File tree

3 files changed

+73
-17
lines changed

3 files changed

+73
-17
lines changed

windows/registry/key.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import (
2626
"runtime"
2727
"syscall"
2828
"time"
29+
30+
"golang.org/x/sys/windows"
2931
)
3032

3133
const (
@@ -193,18 +195,23 @@ type KeyInfo struct {
193195
ValueCount uint32
194196
MaxValueNameLen uint32 // size of the key's longest value name, in Unicode characters, not including the terminating zero byte
195197
MaxValueLen uint32 // longest data component among the key's values, in bytes
196-
lastWriteTime syscall.Filetime
198+
lastWriteTime windows.Filetime
197199
}
198200

199201
// ModTime returns the key's last write time.
200202
func (ki *KeyInfo) ModTime() time.Time {
201-
return time.Unix(0, ki.lastWriteTime.Nanoseconds())
203+
return time.Unix(ki.lastWriteTime.Unix())
204+
}
205+
206+
// ModTimeZero reports whether the key's last write time is zero.
207+
func (ki *KeyInfo) ModTimeZero() bool {
208+
return ki.lastWriteTime.LowDateTime == 0 && ki.lastWriteTime.HighDateTime == 0
202209
}
203210

204211
// Stat retrieves information about the open key k.
205212
func (k Key) Stat() (*KeyInfo, error) {
206213
var ki KeyInfo
207-
err := syscall.RegQueryInfoKey(syscall.Handle(k), nil, nil, nil,
214+
err := windows.RegQueryInfoKey(windows.Handle(k), nil, nil, nil,
208215
&ki.SubKeyCount, &ki.MaxSubKeyLen, nil, &ki.ValueCount,
209216
&ki.MaxValueNameLen, &ki.MaxValueLen, nil, &ki.lastWriteTime)
210217
if err != nil {

windows/syscall_windows.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -738,7 +738,7 @@ func Ftruncate(fd Handle, length int64) (err error) {
738738
func Gettimeofday(tv *Timeval) (err error) {
739739
var ft Filetime
740740
GetSystemTimeAsFileTime(&ft)
741-
*tv = NsecToTimeval(ft.Nanoseconds())
741+
*tv = FiletimeToTimeval(ft)
742742
return nil
743743
}
744744

@@ -771,8 +771,8 @@ func Utimes(path string, tv []Timeval) (err error) {
771771
return e
772772
}
773773
defer CloseHandle(h)
774-
a := NsecToFiletime(tv[0].Nanoseconds())
775-
w := NsecToFiletime(tv[1].Nanoseconds())
774+
a := TimevalToFiletime(tv[0])
775+
w := TimevalToFiletime(tv[1])
776776
return SetFileTime(h, nil, &a, &w)
777777
}
778778

windows/types_windows.go

Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -763,10 +763,33 @@ func (tv *Timeval) Nanoseconds() int64 {
763763
return (int64(tv.Sec)*1e6 + int64(tv.Usec)) * 1e3
764764
}
765765

766+
func TimevalToFiletime(tv Timeval) Filetime {
767+
// Convert to 100-nanosecond intervals
768+
hsec := uint64(tv.Sec)*1e7 + uint64(tv.Usec)/100
769+
// Change starting time to January 1, 1601
770+
// Note: No overflow here (11644473600*1e7 < math.MaxUint64/1e2)
771+
hsec += 116444736000000000
772+
// Split into high / low.
773+
return Filetime{
774+
LowDateTime: uint32(hsec & 0xffffffff),
775+
HighDateTime: uint32(hsec >> 32 & 0xffffffff),
776+
}
777+
}
778+
779+
// NsecToTimeval converts a nanosecond value nsec to a Timeval tv. The result is
780+
// undefined if the nanosecond value cannot be represented by a Timeval (values
781+
// equivalent to dates before the year 1901 or after the year 2038).
766782
func NsecToTimeval(nsec int64) (tv Timeval) {
767-
tv.Sec = int32(nsec / 1e9)
768-
tv.Usec = int32(nsec % 1e9 / 1e3)
769-
return
783+
// Ignore overflow (math.MaxInt64/1e9 > math.MaxInt32)
784+
sec := int32(nsec / 1e9)
785+
usec := int32(nsec % 1e9 / 1e3)
786+
if usec < 0 {
787+
usec += 1e6
788+
sec--
789+
}
790+
tv.Sec = sec
791+
tv.Usec = usec
792+
return tv
770793
}
771794

772795
type Overlapped struct {
@@ -789,22 +812,48 @@ type Filetime struct {
789812
HighDateTime uint32
790813
}
791814

792-
// Nanoseconds returns Filetime ft in nanoseconds
815+
// Unix returns ft in seconds and nanoseconds
793816
// since Epoch (00:00:00 UTC, January 1, 1970).
794-
func (ft *Filetime) Nanoseconds() int64 {
817+
func (ft *Filetime) Unix() (sec, nsec int64) {
795818
// 100-nanosecond intervals since January 1, 1601
796-
nsec := int64(ft.HighDateTime)<<32 + int64(ft.LowDateTime)
797-
// change starting time to the Epoch (00:00:00 UTC, January 1, 1970)
798-
nsec -= 116444736000000000
799-
// convert into nanoseconds
800-
nsec *= 100
801-
return nsec
819+
hsec := uint64(ft.HighDateTime)<<32 + uint64(ft.LowDateTime)
820+
// Convert _before_ gauging; the nanosecond difference between Epoch (00:00:00
821+
// UTC, January 1, 1970) and Filetime's zero offset (January 1, 1601) is out
822+
// of bounds for int64: -11644473600*1e7*1e2 < math.MinInt64
823+
sec = int64(hsec/1e7) - 11644473600
824+
nsec = int64(hsec%1e7) * 100
825+
return sec, nsec
826+
}
827+
828+
// Nanoseconds returns ft in nanoseconds since Epoch (00:00:00 UTC,
829+
// January 1, 1970). The result is undefined if the Filetime cannot be
830+
// represented by an int64 (values equivalent to dates before the year
831+
// 1677 or after the year 2262). Note that this explicitly excludes the
832+
// zero value of Filetime, which is equivalent to January 1, 1601.
833+
//
834+
// Deprecated: use Unix instead, which returns both seconds and
835+
// nanoseconds thus covers the full available range of Filetime.
836+
func (ft *Filetime) Nanoseconds() int64 {
837+
sec, nsec := ft.Unix()
838+
return (sec*1e9 + nsec)
839+
}
840+
841+
// FiletimeToTimeval converts a Filetime ft to a Timeval tv. The result is
842+
// undefined if the Filetime cannot be represented by a Timeval (values
843+
// equivalent to dates before the year 1901 or after the year 2038).
844+
func FiletimeToTimeval(ft Filetime) (tv Timeval) {
845+
sec, nsec := ft.Unix()
846+
// Ignore overflow (math.MaxUint64*1e2/1e9 > math.MaxInt32)
847+
tv.Sec = int32(sec)
848+
tv.Usec = int32(nsec / 1e3)
849+
return tv
802850
}
803851

804852
func NsecToFiletime(nsec int64) (ft Filetime) {
805853
// convert into 100-nanosecond
806854
nsec /= 100
807855
// change starting time to January 1, 1601
856+
// note: no overflow here (11644473600*1e7 < math.MaxInt64/1e1)
808857
nsec += 116444736000000000
809858
// split into high / low
810859
ft.LowDateTime = uint32(nsec & 0xffffffff)

0 commit comments

Comments
 (0)