-
Notifications
You must be signed in to change notification settings - Fork 5
/
rtime.go
180 lines (170 loc) · 3.68 KB
/
rtime.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
package rtime
import (
"errors"
"net/http"
"sort"
"sync"
"time"
)
var sites = []string{
"facebook.com", "microsoft.com", "amazon.com", "google.com",
"youtube.com", "twitter.com", "reddit.com", "netflix.com",
"bing.com", "twitch.tv", "myshopify.com", "wikipedia.org",
}
// Now returns the current remote time. If the remote time cannot be
// retrieved then the zero value for Time is returned. It's a good idea to
// test for zero after every call, such as:
//
// now := rtime.Now()
// if now.IsZero() {
// ... handle failure ...
// }
//
func Now() time.Time {
smu.Lock()
if synced {
tm := sremote.Add(time.Since(slocal))
smu.Unlock()
return tm
}
smu.Unlock()
return now()
}
var rmu sync.Mutex
var rtime time.Time
func now() time.Time {
res := make([]time.Time, 0, len(sites))
results := make(chan time.Time, len(sites))
// get as many dates as quickly as possible
client := http.Client{
Timeout: time.Duration(time.Second * 2),
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
for _, site := range sites {
go func(site string) {
resp, err := client.Head("https://" + site)
if err == nil {
tm, err := time.Parse(time.RFC1123, resp.Header.Get("Date"))
resp.Body.Close()
if err == nil {
results <- tm
}
}
}(site)
}
for {
select {
case <-time.After(2 * time.Second):
return time.Time{}
case tm := <-results:
res = append(res, tm)
if len(res) < 3 {
continue
}
// We must have a minimum of three results. Find the two of three
// that have the least difference in time and take the smaller of
// the two.
type pair struct {
tm0 time.Time
tm1 time.Time
diff time.Duration
}
var list []pair
for i := 0; i < len(res); i++ {
for j := i + 1; j < len(res); j++ {
if i != j {
tm0, tm1 := res[i], res[j]
if tm0.After(tm1) {
tm0, tm1 = tm1, tm0
}
list = append(list, pair{tm0, tm1, tm1.Sub(tm0)})
}
}
}
sort.Slice(list, func(i, j int) bool {
if list[i].diff < list[j].diff {
return true
}
if list[i].diff > list[j].diff {
return false
}
return list[i].tm0.Before(list[j].tm0)
})
res := list[0].tm0.Local()
// Ensure that the new time is after the previous time.
rmu.Lock()
defer rmu.Unlock()
if res.After(rtime) {
rtime = res
}
return rtime
}
}
}
var smu sync.Mutex
var sid int
var synced bool
var sremote time.Time
var slocal time.Time
// Sync tells the application to keep rtime in sync with internet time. This
// ensures that all following rtime.Now() calls are fast, accurate, and without
// the need to check the result.
//
// Ideally you would call this at the top of your main() or init() function.
//
// if err := rtime.Sync(); err != nil {
// ... internet offline, handle error or try again ...
// return
// }
// rtime.Now() // guaranteed to be a valid time
//
func Sync() error {
smu.Lock()
defer smu.Unlock()
if synced {
return nil
}
start := time.Now()
for {
tm := now()
if !tm.IsZero() {
sremote = tm
break
}
if time.Since(start) > time.Second*15 {
return errors.New("internet offline")
}
time.Sleep(time.Second / 15)
}
sid++
gsid := sid
synced = true
slocal = time.Now()
go func() {
for {
time.Sleep(time.Second * 15)
tm := now()
if !tm.IsZero() {
smu.Lock()
if gsid != sid {
smu.Unlock()
return
}
if tm.After(sremote) {
sremote = tm
slocal = time.Now()
}
smu.Unlock()
}
}
}()
return nil
}
// MustSync is like Sync but panics if the internet is offline.
func MustSync() {
if err := Sync(); err != nil {
panic(err)
}
}