/
TimeStamp.cs
131 lines (111 loc) · 4.13 KB
/
TimeStamp.cs
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
using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace LiveSplit.Model
{
public sealed class TimeStamp
{
public static double PersistentDrift { get; set; }
public static double NewDrift { get; set; }
private static Stopwatch qpc;
private static TimeSpan firstQPCTime;
private static DateTime firstNTPTime;
private static TimeSpan lastQPCTime;
private static DateTime lastNTPTime;
private readonly TimeSpan value;
private TimeStamp(TimeSpan value)
{
this.value = value;
}
static TimeStamp()
{
PersistentDrift = 1.0;
NewDrift = 1.0;
firstQPCTime = lastQPCTime = TimeSpan.Zero;
firstNTPTime = lastNTPTime = DateTime.MinValue;
qpc = new Stopwatch();
qpc.Start();
Task.Factory.StartNew(() => RefreshDrift());
}
public static TimeStamp Now
=> new TimeStamp(TimeSpan.FromTicks((long)(qpc.Elapsed.Ticks / PersistentDrift)));
public static bool IsSyncedWithAtomicClock
=> lastQPCTime != TimeSpan.Zero;
public static AtomicDateTime CurrentDateTime
{
get
{
if (IsSyncedWithAtomicClock)
{
return new AtomicDateTime(lastNTPTime.Add(Now - new TimeStamp(lastQPCTime)), true);
}
return new AtomicDateTime(DateTime.UtcNow, false);
}
}
private static void RefreshDrift()
{
while (true)
{
var times = new List<long>();
DateTime ntpTime;
TimeSpan qpcTime = TimeSpan.Zero;
for (var count = 1; count <= 10; count++)
{
try
{
ntpTime = NTP.Now;
qpcTime = qpc.Elapsed;
times.Add(ntpTime.Ticks - qpcTime.Ticks);
}
catch { }
if (count < 10)
Wait(TimeSpan.FromSeconds(5));
}
if (times.Count >= 5)
{
var averageDifference = times.Average();
lastQPCTime = qpcTime;
lastNTPTime = new DateTime(qpcTime.Ticks + (long)averageDifference, DateTimeKind.Utc);
if (firstQPCTime != TimeSpan.Zero)
{
var qpcDelta = lastQPCTime - firstQPCTime;
var ntpDelta = lastNTPTime - firstNTPTime;
var newDrift = qpcDelta.TotalMilliseconds / ntpDelta.TotalMilliseconds;
// Ignore any drift that is too far from 1
if (Math.Abs(newDrift - 1) < 0.01)
{
var weight = Math.Pow(0.95, ntpDelta.TotalHours);
NewDrift = Math.Pow(newDrift, 1 - weight) * Math.Pow(PersistentDrift, weight);
}
Wait(TimeSpan.FromHours(0.5));
}
else
{
firstQPCTime = lastQPCTime;
firstNTPTime = lastNTPTime;
Wait(TimeSpan.FromHours(1));
}
}
else Wait(TimeSpan.FromHours(0.5));
}
}
private static void Wait(TimeSpan waitTime)
{
var before = Now;
Thread.Sleep(waitTime);
var elapsed = Now - before;
if (elapsed.TotalMinutes > waitTime.TotalMinutes + 2)
{
firstQPCTime = TimeSpan.Zero;
firstNTPTime = DateTime.MinValue;
}
}
public static TimeSpan operator -(TimeStamp a, TimeStamp b)
=> a.value - b.value;
public static TimeStamp operator -(TimeStamp a, TimeSpan b)
=> new TimeStamp(a.value - b);
}
}