Skip to content

Commit

Permalink
Added Sync function
Browse files Browse the repository at this point in the history
  • Loading branch information
tidwall committed Dec 15, 2020
1 parent 3043f1e commit a5fd6a7
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 5 deletions.
26 changes: 24 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,40 @@ go get -u github.com/tidwall/rtime

## Using

The only function is `rtime.Now()`.
Get the remote time with `rtime.Now()`.

```go
tm := rtime.Now()
if tm.IsZero() {
panic("time could not be retrieved")
panic("internet offline")
}
println(tm.String())
// output: 2020-03-29 10:27:00 -0700 MST
}
```

## Keep in sync

The `rtime.Now()` will be a little slow, usually 200 ms or more, because it
must make a round trip to three or more remote servers to determine the correct
time.

You make it fast like the built-in `time.Now()` by calling `rtime.Sync()` once
at the start of your application.

```go
if err := rtime.Sync(); err != nil {
panic(err) // internet offline
}
// All following rtime.Now() calls will be quick and without the need for
// checking the result.
tm := rtime.Now()
println(tm.String())
```

It's a good idea to call `rtime.Sync()` at the top of the `main()` or `init()`
functions.

## Contact

Josh Baker [@tidwall](http://twitter.com/tidwall)
Expand Down
86 changes: 83 additions & 3 deletions rtime.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package rtime

import (
"errors"
"net/http"
"sort"
"sync"
Expand All @@ -13,9 +14,6 @@ var sites = []string{
"bing.com", "twitch.tv", "myshopify.com", "wikipedia.org",
}

var rmu sync.Mutex
var rtime time.Time

// 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:
Expand All @@ -26,6 +24,20 @@ var rtime time.Time
// }
//
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))

Expand Down Expand Up @@ -98,3 +110,71 @@ func Now() time.Time {
}
}
}

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)
}
}
38 changes: 38 additions & 0 deletions rtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,49 @@ package rtime

import (
"testing"
"time"
)

func unsync() {
smu.Lock()
synced = false
smu.Unlock()
}

func TestTime(t *testing.T) {
unsync()
start := time.Now()
tm := Now()

if tm.IsZero() {
t.Fatal("zero time")
}
println(time.Since(start).String())
}

func TestSync(t *testing.T) {
unsync()
if err := Sync(); err != nil {
t.Fatal(err)
}
if !synced {
t.Fatal("not synced")
}
tm1 := Now()
if tm1.IsZero() {
t.Fatal("zero time")
}
tm2 := Now()
if !tm2.After(tm1) {
t.Fatal("not after")
}
func() {
defer func() {
if v := recover(); v != nil {
t.Fatal(v)
}
}()
MustSync()
}()

}

0 comments on commit a5fd6a7

Please sign in to comment.