Repository navigation
Expand file tree
/
Copy pathclock.js
More file actions
82 lines (76 loc) · 3.38 KB
/
Copy pathclock.js
File metadata and controls
82 lines (76 loc) · 3.38 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
/* Pure time calculations. Luxon uses the browser's IANA time zone database. */
'use strict';
const LinearClock = (() => {
const { DateTime } = luxon;
function createDay(date, zone) {
const start = DateTime.fromISO(date, { zone }).startOf('day');
if (!start.isValid || start.toISODate() !== date) throw new Error('Invalid date or time zone.');
const end = start.plus({ days: 1 }).startOf('day');
return { start, end, duration: end.toMillis() - start.toMillis(), zone };
}
function percentage(time, day) {
return Math.max(0, Math.min(100, (time.toMillis() - day.start.toMillis()) / day.duration * 100));
}
function hourTicks(day) {
const ticks = [];
// Walk actual instants: repeated wall-clock hours remain distinct. Include
// whole local hours even after a 30-minute DST change (e.g. Lord Howe).
for (let time = day.start; time < day.end; time = time.plus({ minutes: 1 })) {
if (time.minute === 0 || time.equals(day.start)) {
ticks.push({ time, percent: percentage(time, day), label: time.toFormat('H:mm') });
}
}
ticks.push({ time: day.end, percent: 100, label: day.end.toFormat('H:mm') });
return ticks;
}
function parseSunTime(value, day) {
if (typeof value !== 'string') return null;
const time = DateTime.fromISO(value, { zone: 'utc' }).setZone(day.zone);
// v1 uses Unix-epoch placeholders for polar events. Adjacent-day events
// are valid, but dates far outside the requested day are not.
if (!time.isValid || time < day.start.minus({ days: 1 }) || time > day.end.plus({ days: 1 })) return null;
return time;
}
function sunModel(times, day) {
const sunrise = parseSunTime(times.sunrise, day);
const sunset = parseSunTime(times.sunset, day);
const dawn = parseSunTime(times.civil_twilight_begin, day);
const dusk = parseSunTime(times.civil_twilight_end, day);
const empty = () => ({ startPercent: 0, sizePercent: 0 });
const segment = (start, end) => {
const a = percentage(start, day);
const b = percentage(end, day);
return { startPercent: a, sizePercent: Math.max(0, b - a) };
};
let kind = 'unavailable';
let segments = Array.from({ length: 5 }, empty);
if (sunrise && sunset && sunrise <= sunset) {
// Missing civil twilight at high latitudes means twilight continues
// to the edge of the day. Do not invent a night or wrap negative spans.
const begin = dawn || day.start;
const end = dusk || day.end;
if (begin <= sunrise && sunset <= end) {
kind = 'normal';
segments = [segment(day.start, begin), segment(begin, sunrise),
segment(sunrise, sunset), segment(sunset, end), segment(end, day.end)];
}
} else if (!sunrise && !sunset && times.day_length != null) {
const length = Number(times.day_length);
if (length === 86400) {
kind = 'polar-day';
segments[2] = segment(day.start, day.end);
} else if (length === 0) {
kind = 'polar-night';
if (dawn && dusk && dawn <= dusk) {
segments[0] = segment(day.start, dawn);
segments[1] = segment(dawn, dusk);
segments[4] = segment(dusk, day.end);
} else {
segments[0] = segment(day.start, day.end);
}
}
}
return { sunrise, sunset, dawn, dusk, kind, segments };
}
return { createDay, percentage, hourTicks, parseSunTime, sunModel };
})();