forked from Xenoveritas/js-timer
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathclock.js
More file actions
284 lines (276 loc) · 9.04 KB
/
clock.js
File metadata and controls
284 lines (276 loc) · 9.04 KB
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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
/**
* A module for creating clocks. The core module is simply a constructor that
* provides a method of receiving a notification roughly every second.
* @module clock
*/
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], factory);
} else if (typeof module === 'object' && module.exports) {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory();
} else {
// Browser globals (root is window)
root.Clock = factory();
}
}(this, function () {
/**
* Very basic clock object that receives tick notifications ever second.
* Override ontick to receive the notification.
* @constructor
* @alias module:clock
*/
function Clock() {
this.timeout = false;
}
Clock.prototype = {
/**
* The offset in milliseconds from real time to use to send to the clock. This
* can be used if you want to make a clock that displays ticks based on when
* a user clicked the button (by setting it to the number of milliseconds off
* the second) or to make a clock that displays a certain amount of time in
* the future or past. Changing this will take effect on the *next* tick.
*
* The offset is added to the time - positive offsets move the values given by
* {@link module:clock#ontick ontick()} into the future, negative values move
* it into the past.
*
* Changing this enough to make a tick appear in the past will also trigger an
* {@link module:clock#onbackwards onbackwards()} call, just as if the clock
* had moved backwards due to external causes.
*/
offset: 0,
/**
* Whether or not the clock is still running.
*
* If set, starts or stops the clock by using
* {@link module:clock#start start()} or {@link module:clock#stop stop()} as
* appropriate.
*/
get running() {
return this.timeout !== false;
},
set running(value) {
if (value) {
this.start();
} else {
this.stop();
}
},
/**
* Start the clock. {@link module:clock#ontick ontick()} will be invoked
* nearly immediately with the current time and then invoked every second.
*/
start: function() {
if (this.timeout === false) {
// I suppose lastTime could also be set to 0 on the assumption that no one
// is going to be running with a clock set to 1970 Jan 1, but... whatever.
// Go ahead and support weird time travelers.
var me = this, lastTime = new Date().getTime() - 1000;
me.timeout = 1;
function tick() {
var now = new Date();
// Round now up, as the timeout may be called slightly early.
var t = now.getTime() + me.offset + 500;
// And chop off the uneven milliseconds - useful for countdown
// timers.
t -= t % 1000;
now.setTime(t);
if (t < lastTime) {
// Clock has run backwards.
me.onbackwards(now);
}
lastTime = t;
me.ontick(now);
var next = 1000 - ((new Date().getTime() + me.offset) % 1000);
if (next < 50) {
// If it's really close, just jump to the next second, as we'll
// have "rounded" to this second anyway.
next += 1000;
}
// We need to check if we're still going, as stop() may have
// stopped the timeout.
if (me.timeout !== false)
me.timeout = setTimeout(tick, next);
}
tick();
}
},
/**
* Stops the clock. No further {@link module:clock#ontick ontick()}s will
* be called until the clock is restarted using
* {@linkcode module:clock#start start()}.
*/
stop: function() {
if (this.timeout !== false) {
clearTimeout(this.timeout);
this.timeout = false;
}
},
/**
* Called once a second with the current time.
*
* Note that the `Date` object given is the time that the clock is being
* "ticked" for, and is **not** going to be the exact same time that
* `new Date()` would generate. This is because JavaScript engines may call a
* callback set by `setTimeout()` or `setInterval()` slightly early or
* slightly late. This class deals with that by rounding to the nearest
* second and then passing that rounded time to this function.
*
* If {@link module:clock#offset} is non-zero, the `Date` given will be
* adjusted by that.
*
* The default implementation does nothing.
*
* @param date {Date} the current time.
*/
ontick: function(date) {
},
/**
* Generally speaking it can be assumed that ticks will only ever increase the
* current time. This does not need to be true: it is possible for the clock
* to run "backwards." The most likely cause is the clock being fast and the
* user correcting it or the clock be synced to an NTP server.
*
* Changing over to Daylight Saving Time will **not** trigger this callback,
* it's only used when an {@link module:clock#ontick ontick()} would have
* triggered for an earlier time than a previous one.
*
* Not all clocks need to handle this case, however some clocks may be written
* in such a way that elements that happened "in the past" are removed and may
* suddenly be required again.
*
* The default implementation does nothing.
*
* @param date {Date} the current time, as would have been passed to
* {@code ontick}.
*/
onbackwards: function(date) {
}
};
/**
* Utility function to 0-pad a two-digit number, since this comes up so often.
* This will only add a 0 in front of a single digit number and only works with
* positive numbers.
* @param {Number} d digit
*/
Clock.zeropad = function(d) {
return d < 10 ? '0' + d : d.toString();
}
/**
* Returns an interval between two dates.
*
* @param {Date} firstDate the first date
* @param {Date} secondDate the second date
* @return {Timer.Interval} interval between the two.
*/
Clock.difference = function(firstDate, secondDate) {
if (firstDate instanceof Date)
firstDate = firstDate.getTime();
if (secondDate instanceof Date)
secondDate = secondDate.getTime();
return new Timer.Interval(firstDate - secondDate);
}
/**
* An interval of time - sort of like a `Date()` but for different
* periods of time. Given a number of milliseconds, this calculates the number
* of weeks, days, hours, minutes, and seconds between the two. Useful for
* creating a countdown to a given time.
* @constructor
* @param {Number} interval
* the initial interval in milliseconds
* @param {Number|Boolean} periods
* number of periods to split the interval into. Defaults to 5:
* include weeks, days, hours, minutes, seconds. Milliseconds is always
* included and values less than 1 are treated as seconds. Decrease
* to reduce the number of fields in that order: 4 omits weeks,
* 3 will only return hours, minute, and seconds, etc. `false`
* can be used as a "shortcut" for 4 to treat the parameter as
* "include weeks". Note that the fields not calculated will still be
* present, they'll just be set to 0.
*/
Clock.Interval = function(interval, periods) {
if (arguments.length < 2 || periods === true) {
periods = 5;
} else if (periods === false) {
periods = 4;
}
// Step 0: Deal with negative intervals. Intervals only make sense using
// positive numbers, but include a flag if it's in the past.
/**
* Indicates that the interval occurred in the past.
*/
this.isInPast = interval < 0;
// And make it absolute.
interval = Math.abs(interval);
// Step 1: convert to seconds.
var t = Math.floor(interval / 1000); // 1000 ms = 1 seconds
this.millis = (interval - (t * 1000));
if (periods < 2) {
this.seconds = t;
return;
}
this.seconds = t % 60; // 60 seconds = 1 minute
t = Math.floor(t / 60);
if (periods < 3) {
this.minutes = t;
return;
}
this.minutes = t % 60; // 60 minutes = 1 hour
t = Math.floor(t / 60);
if (periods < 4) {
this.hours = t;
return;
}
this.hours = t % 24; // 24 hours = 1 day
t = Math.floor(t / 24);
if (periods < 5) {
this.days = t;
return;
}
this.days = t % 7; // 7 days = 1 week
this.weeks = Math.floor(t / 7); // And enough
};
Clock.Interval.prototype = {
/**
* The number of weeks in the interval. There are no larger spans of time
* calculated at present. Months make no sense at this level: how long is a
* month? 28 days? Depends on when the interval starts? Years may be added
* in the future, but years have a similar problem: How long is a year?
* 365 days? What about leap years?
*/
weeks: 0,
/**
* The number of days in the interval. For a 10 day interval, this is 3 and
* {@linkcode module:timer.Interval#weeks weeks} will be 1.
*/
days: 0,
/**
* The number of hours in the interval. For a 1.5 day interval, this is 12.
*/
hours: 0,
/**
* The number of minutes in the interval. For a 90 minute interval, this is
* 30.
*/
minutes: 0,
/**
* The number of seconds in the interval. For a 90 second interval, this is
* 30.
*/
seconds: 0,
/**
* The number of milliseconds in the interval. For a 1500ms interval, this
* is 500. This is almost never useful but is included anyway.
*/
millis: 0,
toString: function() {
return '[' + this.weeks + ' weeks ' + this.days + ' days ' + this.hours +
' hours ' + this.minutes + ' minutes ' + this.seconds + ' seconds]';
}
};
return Clock;
}));