1package client
2
3import (
4	"strconv"
5	"time"
6)
7
8const (
9	nanoSecondsPerSecond = 1000000000
10	nanosInTick          = 100
11	ticksPerSecond       = nanoSecondsPerSecond / nanosInTick
12)
13
14// ParseTicks parses dates represented as Active Directory LargeInts into times.
15// Not all time fields are represented this way,
16// so be sure to test that your particular time returns expected results.
17// Some time fields represented as LargeInts include accountExpires, lastLogon, lastLogonTimestamp, and pwdLastSet.
18// More: https://social.technet.microsoft.com/wiki/contents/articles/31135.active-directory-large-integer-attributes.aspx
19func ParseTicks(ticks string) (time.Time, error) {
20	i, err := strconv.ParseInt(ticks, 10, 64)
21	if err != nil {
22		return time.Time{}, err
23	}
24	return TicksToTime(i), nil
25}
26
27// TicksToTime converts an ActiveDirectory time in ticks to a time.
28// This algorithm is summarized as:
29//
30// 		Many dates are saved in Active Directory as Large Integer values.
31// 		These attributes represent dates as the number of 100-nanosecond intervals since 12:00 AM January 1, 1601.
32//		100-nanosecond intervals, equal to 0.0000001 seconds, are also called ticks.
33//		Dates in Active Directory are always saved in Coordinated Universal Time, or UTC.
34//		More: https://social.technet.microsoft.com/wiki/contents/articles/31135.active-directory-large-integer-attributes.aspx
35//
36// If we directly follow the above algorithm we encounter time.Duration limits of 290 years and int overflow issues.
37// Thus below, we carefully sidestep those.
38func TicksToTime(ticks int64) time.Time {
39	origin := time.Date(1601, time.January, 1, 0, 0, 0, 0, time.UTC).Unix()
40	secondsSinceOrigin := ticks / ticksPerSecond
41	remainingNanoseconds := ticks % ticksPerSecond * 100
42	return time.Unix(origin+secondsSinceOrigin, remainingNanoseconds).UTC()
43}
44