Initial commit — Seth Calendar & Decimal Time clock site
Pages: /, /simple, /decimal, /seth, /calendar, /astro, /convert, /timegov Features: Seth Calendar (10×36 + holidays), decimal time, moon phases, astronomy (sun/moon), bidirectional time converter, Seth date display, leap day split cell in calendar grid.
@@ -0,0 +1,4 @@
|
|||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
.env
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
const zoneSelect = document.getElementById("zoneSelect");
|
||||||
|
const zoneLabel = document.getElementById("zoneLabel");
|
||||||
|
const offsetLabel = document.getElementById("offsetLabel");
|
||||||
|
const utcLabel = document.getElementById("utcLabel");
|
||||||
|
const dateLine = document.getElementById("dateLine");
|
||||||
|
const digital = document.getElementById("digital");
|
||||||
|
|
||||||
|
const zones = [
|
||||||
|
"America/New_York",
|
||||||
|
"America/Chicago",
|
||||||
|
"America/Denver",
|
||||||
|
"America/Los_Angeles",
|
||||||
|
"America/Anchorage",
|
||||||
|
"Pacific/Honolulu",
|
||||||
|
"UTC"
|
||||||
|
];
|
||||||
|
|
||||||
|
let serverOffsetMs = 0;
|
||||||
|
let selectedZone = Intl.DateTimeFormat().resolvedOptions().timeZone || "America/New_York";
|
||||||
|
|
||||||
|
if (!zones.includes(selectedZone)) {
|
||||||
|
selectedZone = "America/New_York";
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const zone of zones) {
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = zone;
|
||||||
|
option.textContent = zone;
|
||||||
|
if (zone === selectedZone) {
|
||||||
|
option.selected = true;
|
||||||
|
}
|
||||||
|
zoneSelect.appendChild(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
zoneSelect.addEventListener("change", () => {
|
||||||
|
selectedZone = zoneSelect.value;
|
||||||
|
zoneLabel.textContent = selectedZone;
|
||||||
|
});
|
||||||
|
|
||||||
|
async function syncWithServer() {
|
||||||
|
const start = Date.now();
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/time?ts=${start}`, { cache: "no-store" });
|
||||||
|
const end = Date.now();
|
||||||
|
const data = await response.json();
|
||||||
|
const rtt = end - start;
|
||||||
|
const estimatedServerAtEnd = data.epoch_ms + rtt / 2;
|
||||||
|
serverOffsetMs = estimatedServerAtEnd - end;
|
||||||
|
offsetLabel.textContent = `Synchronized (${serverOffsetMs >= 0 ? "+" : ""}${serverOffsetMs.toFixed(0)} ms, RTT ${rtt} ms)`;
|
||||||
|
} catch (_error) {
|
||||||
|
offsetLabel.textContent = "Sync failed";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatParts(date, zone) {
|
||||||
|
const formatter = new Intl.DateTimeFormat("en-US", {
|
||||||
|
timeZone: zone,
|
||||||
|
hour12: false,
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "2-digit",
|
||||||
|
weekday: "long",
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
second: "2-digit"
|
||||||
|
});
|
||||||
|
|
||||||
|
return formatter.formatToParts(date).reduce((accumulator, part) => {
|
||||||
|
if (part.type !== "literal") {
|
||||||
|
accumulator[part.type] = part.value;
|
||||||
|
}
|
||||||
|
return accumulator;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
const now = new Date(Date.now() + serverOffsetMs);
|
||||||
|
const parts = formatParts(now, selectedZone);
|
||||||
|
const centiseconds = String(Math.floor(now.getMilliseconds() / 10)).padStart(2, "0");
|
||||||
|
|
||||||
|
dateLine.textContent = `${parts.weekday}, ${parts.month} ${parts.day}, ${parts.year}`;
|
||||||
|
digital.textContent = `${parts.hour}:${parts.minute}:${parts.second}.${centiseconds}`;
|
||||||
|
zoneLabel.textContent = selectedZone;
|
||||||
|
utcLabel.textContent = now.toISOString().replace("T", " ").replace("Z", " UTC");
|
||||||
|
|
||||||
|
requestAnimationFrame(render);
|
||||||
|
}
|
||||||
|
|
||||||
|
syncWithServer();
|
||||||
|
setInterval(syncWithServer, 20000);
|
||||||
|
render();
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>SethPC Astronomy</title>
|
||||||
|
<link rel="icon" type="image/png" href="https://storage.googleapis.com/sethfreiberg.com/sethflix/favicon.png">
|
||||||
|
<link rel="stylesheet" href="/style.css">
|
||||||
|
<style>
|
||||||
|
.astro-wrap {
|
||||||
|
width: min(540px, 100%);
|
||||||
|
background: var(--panel);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: clamp(1rem, 2.5vw, 2rem);
|
||||||
|
box-shadow: 0 10px 28px rgb(0 0 0 / 42%);
|
||||||
|
}
|
||||||
|
.moon-display {
|
||||||
|
text-align: center;
|
||||||
|
margin: 1.5rem 0 1rem;
|
||||||
|
}
|
||||||
|
.moon-glyph {
|
||||||
|
font-size: 5rem;
|
||||||
|
line-height: 1;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.moon-name {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
.moon-pct {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--muted);
|
||||||
|
margin-top: 0.2rem;
|
||||||
|
}
|
||||||
|
.astro-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 0.4rem 1rem;
|
||||||
|
font-size: 0.92rem;
|
||||||
|
color: var(--muted);
|
||||||
|
margin: 1.2rem 0;
|
||||||
|
}
|
||||||
|
.astro-grid p { margin: 0; }
|
||||||
|
.astro-grid strong { color: var(--text); }
|
||||||
|
.date-nav {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-bottom: 1.2rem;
|
||||||
|
}
|
||||||
|
.date-nav h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: clamp(1rem, 2.5vw, 1.3rem);
|
||||||
|
text-align: center;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.nav-btn {
|
||||||
|
background: #2a2a2a;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
color: var(--text);
|
||||||
|
padding: 0.4rem 0.8rem;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1;
|
||||||
|
transition: border-color 0.15s;
|
||||||
|
}
|
||||||
|
.nav-btn:hover { border-color: var(--accent); color: var(--accent); }
|
||||||
|
.section-head {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--accent);
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin: 1.2rem 0 0.4rem;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
padding-bottom: 0.2rem;
|
||||||
|
}
|
||||||
|
.event-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
.event-list li {
|
||||||
|
padding: 0.2rem 0;
|
||||||
|
border-bottom: 1px solid #2a2a2a;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
.event-list li:last-child { border-bottom: none; }
|
||||||
|
.event-name { color: var(--text); }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class="astro-wrap">
|
||||||
|
<div class="brand-row">
|
||||||
|
<img class="logo-img" src="https://storage.googleapis.com/sethfreiberg.com/sethflix/favicon.png" alt="SethPC logo">
|
||||||
|
<span class="brand-text">SethPC Astronomy</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="date-nav">
|
||||||
|
<button class="nav-btn" id="prevDay">‹</button>
|
||||||
|
<h2 id="dateTitle">—</h2>
|
||||||
|
<button class="nav-btn" id="nextDay">›</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="moon-display">
|
||||||
|
<span class="moon-glyph" id="moonGlyph">🌑</span>
|
||||||
|
<div class="moon-name" id="moonName">—</div>
|
||||||
|
<div class="moon-pct" id="moonPct">—</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-head">Sun</div>
|
||||||
|
<div class="astro-grid">
|
||||||
|
<p><strong>Sunrise:</strong> <span id="sunrise">—</span></p>
|
||||||
|
<p><strong>Sunset:</strong> <span id="sunset">—</span></p>
|
||||||
|
<p><strong>Day length:</strong> <span id="dayLen">—</span></p>
|
||||||
|
<p><strong>Solar noon:</strong> <span id="solarNoon">—</span></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-head">Moon</div>
|
||||||
|
<div class="astro-grid">
|
||||||
|
<p><strong>Age:</strong> <span id="moonAge">—</span></p>
|
||||||
|
<p><strong>Next new moon:</strong> <span id="nextNew">—</span></p>
|
||||||
|
<p><strong>Next full moon:</strong> <span id="nextFull">—</span></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="event-list" id="yearEvents" style="margin-top:1rem;"></ul>
|
||||||
|
</main>
|
||||||
|
<script src="/astro.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,309 @@
|
|||||||
|
// Astronomical calculations for SethPC Astronomy page
|
||||||
|
// Moon phase: Jean Meeus "Astronomical Algorithms" simplified
|
||||||
|
// Sun rise/set: NOAA solar calculator algorithm
|
||||||
|
// Solstice/equinox: Meeus Table 27.a
|
||||||
|
|
||||||
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const DEG = Math.PI / 180;
|
||||||
|
const RAD = 180 / Math.PI;
|
||||||
|
|
||||||
|
function frac(x) { return x - Math.floor(x); }
|
||||||
|
function mod360(x) { return ((x % 360) + 360) % 360; }
|
||||||
|
|
||||||
|
// Julian Day Number from calendar date (noon UT)
|
||||||
|
function julianDay(year, month, day) {
|
||||||
|
if (month <= 2) { year--; month += 12; }
|
||||||
|
const A = Math.floor(year / 100);
|
||||||
|
const B = 2 - A + Math.floor(A / 4);
|
||||||
|
return Math.floor(365.25 * (year + 4716)) + Math.floor(30.6001 * (month + 1)) + day + B - 1524.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
function jdFromDate(date) {
|
||||||
|
return julianDay(date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate()) + date.getUTCHours() / 24 + date.getUTCMinutes() / 1440;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dateFromJD(jd) {
|
||||||
|
const z = Math.floor(jd + 0.5);
|
||||||
|
const f = jd + 0.5 - z;
|
||||||
|
let A = z;
|
||||||
|
if (z >= 2299161) {
|
||||||
|
const alpha = Math.floor((z - 1867216.25) / 36524.25);
|
||||||
|
A = z + 1 + alpha - Math.floor(alpha / 4);
|
||||||
|
}
|
||||||
|
const B = A + 1524;
|
||||||
|
const C = Math.floor((B - 122.1) / 365.25);
|
||||||
|
const D = Math.floor(365.25 * C);
|
||||||
|
const E = Math.floor((B - D) / 30.6001);
|
||||||
|
const day = B - D - Math.floor(30.6001 * E) + f;
|
||||||
|
const month = E < 14 ? E - 1 : E - 13;
|
||||||
|
const year = month > 2 ? C - 4716 : C - 4715;
|
||||||
|
return new Date(Date.UTC(year, month - 1, Math.floor(day)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Moon phase ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
// Returns moon age in days (0 = new moon, ~14.77 = full moon, ~29.53 = back to new)
|
||||||
|
// and illumination fraction (0–1)
|
||||||
|
function moonPhase(date) {
|
||||||
|
// Reference new moon: Jan 6, 2000 18:14 UTC (JD 2451549.756)
|
||||||
|
const KNOWN_NEW_MOON_JD = 2451549.756;
|
||||||
|
const SYNODIC_MONTH = 29.53058867;
|
||||||
|
|
||||||
|
const jd = jdFromDate(date) + 0.5; // use noon of that day
|
||||||
|
const daysSinceNew = jd - KNOWN_NEW_MOON_JD;
|
||||||
|
const cycles = daysSinceNew / SYNODIC_MONTH;
|
||||||
|
const age = frac(cycles) * SYNODIC_MONTH; // 0..29.53
|
||||||
|
|
||||||
|
// Illumination: cos curve, 0 at new, 1 at full
|
||||||
|
const illumination = (1 - Math.cos(2 * Math.PI * age / SYNODIC_MONTH)) / 2;
|
||||||
|
|
||||||
|
return { age, illumination, cycles };
|
||||||
|
}
|
||||||
|
|
||||||
|
function moonPhaseName(age) {
|
||||||
|
const s = age / 29.53058867;
|
||||||
|
if (s < 0.025 || s >= 0.975) return "New Moon";
|
||||||
|
if (s < 0.25) return "Waxing Crescent";
|
||||||
|
if (s < 0.275) return "First Quarter";
|
||||||
|
if (s < 0.475) return "Waxing Gibbous";
|
||||||
|
if (s < 0.525) return "Full Moon";
|
||||||
|
if (s < 0.725) return "Waning Gibbous";
|
||||||
|
if (s < 0.75) return "Last Quarter";
|
||||||
|
return "Waning Crescent";
|
||||||
|
}
|
||||||
|
|
||||||
|
function moonPhaseGlyph(age) {
|
||||||
|
const s = age / 29.53058867;
|
||||||
|
if (s < 0.025 || s >= 0.975) return "🌑";
|
||||||
|
if (s < 0.25) return "🌒";
|
||||||
|
if (s < 0.275) return "🌓";
|
||||||
|
if (s < 0.475) return "🌔";
|
||||||
|
if (s < 0.525) return "🌕";
|
||||||
|
if (s < 0.725) return "🌖";
|
||||||
|
if (s < 0.75) return "🌗";
|
||||||
|
return "🌘";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find JD of next phase after given JD: phase 0=new,1=first,2=full,3=last
|
||||||
|
function nextMoonPhaseJD(jd, phase) {
|
||||||
|
const SYNODIC_MONTH = 29.53058867;
|
||||||
|
const KNOWN_NEW_MOON_JD = 2451549.756;
|
||||||
|
const daysSince = jd - KNOWN_NEW_MOON_JD;
|
||||||
|
const cycles = daysSince / SYNODIC_MONTH;
|
||||||
|
const phaseFrac = phase / 4;
|
||||||
|
let n = Math.floor(cycles - phaseFrac) + phaseFrac;
|
||||||
|
let targetJD = KNOWN_NEW_MOON_JD + n * SYNODIC_MONTH;
|
||||||
|
if (targetJD <= jd) targetJD += SYNODIC_MONTH;
|
||||||
|
return targetJD;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Sun rise/set (NOAA algorithm) ────────────────────────────────────────────
|
||||||
|
|
||||||
|
function sunriseSunset(date, lat, lon) {
|
||||||
|
const jd = julianDay(date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate());
|
||||||
|
const n = jd - 2451545.0 + 0.0008;
|
||||||
|
const Js = n - lon / 360;
|
||||||
|
const M = mod360(357.5291 + 0.98560028 * Js);
|
||||||
|
const C = 1.9148 * Math.sin(M * DEG) + 0.0200 * Math.sin(2 * M * DEG) + 0.0003 * Math.sin(3 * M * DEG);
|
||||||
|
const lam = mod360(M + C + 180 + 102.9372);
|
||||||
|
const Jt = 2451545.0 + Js + 0.0053 * Math.sin(M * DEG) - 0.0069 * Math.sin(2 * lam * DEG);
|
||||||
|
const sinDec = Math.sin(lam * DEG) * Math.sin(23.4397 * DEG);
|
||||||
|
const cosHa = (Math.sin(-0.833 * DEG) - Math.sin(lat * DEG) * sinDec) / (Math.cos(lat * DEG) * Math.cos(Math.asin(sinDec)));
|
||||||
|
|
||||||
|
if (cosHa < -1) return { rise: null, set: null, noon: jdToTime(Jt), alwaysUp: true };
|
||||||
|
if (cosHa > 1) return { rise: null, set: null, noon: jdToTime(Jt), alwaysDown: true };
|
||||||
|
|
||||||
|
const Ha = Math.acos(cosHa) * RAD / 360;
|
||||||
|
const Jrise = Jt - Ha;
|
||||||
|
const Jset = Jt + Ha;
|
||||||
|
|
||||||
|
return {
|
||||||
|
rise: jdToTime(Jrise),
|
||||||
|
set: jdToTime(Jset),
|
||||||
|
noon: jdToTime(Jt),
|
||||||
|
riseJD: Jrise,
|
||||||
|
setJD: Jset,
|
||||||
|
noonJD: Jt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert fractional JD to UTC HH:MM string
|
||||||
|
function jdToTime(jd) {
|
||||||
|
const totalMin = Math.round(((jd + 0.5) % 1) * 1440);
|
||||||
|
const h = Math.floor(totalMin / 60) % 24;
|
||||||
|
const m = totalMin % 60;
|
||||||
|
return `${String(h).padStart(2,"0")}:${String(m).padStart(2,"0")} UTC`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dayLengthStr(riseJD, setJD) {
|
||||||
|
const mins = Math.round((setJD - riseJD) * 1440);
|
||||||
|
const h = Math.floor(mins / 60);
|
||||||
|
const m = mins % 60;
|
||||||
|
return `${h}h ${String(m).padStart(2,"0")}m`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Solstice / Equinox (Meeus Table 27.a + correction) ───────────────────────
|
||||||
|
// Returns approximate JD for each event in a given year
|
||||||
|
// season: 0=March equinox, 1=June solstice, 2=September equinox, 3=December solstice
|
||||||
|
|
||||||
|
function seasonJD(year, season) {
|
||||||
|
const Y = (year - 2000) / 1000;
|
||||||
|
const JDE0s = [
|
||||||
|
// March equinox
|
||||||
|
2451623.80984 + 365242.37404*Y + 0.05169*Y*Y - 0.00411*Y*Y*Y - 0.00057*Y*Y*Y*Y,
|
||||||
|
// June solstice
|
||||||
|
2451716.56767 + 365241.62603*Y + 0.00325*Y*Y + 0.00888*Y*Y*Y - 0.00030*Y*Y*Y*Y,
|
||||||
|
// September equinox
|
||||||
|
2451810.05917 + 365242.01767*Y - 0.11575*Y*Y + 0.00337*Y*Y*Y + 0.00078*Y*Y*Y*Y,
|
||||||
|
// December solstice
|
||||||
|
2451900.05952 + 365242.74049*Y - 0.06223*Y*Y - 0.00823*Y*Y*Y + 0.00032*Y*Y*Y*Y,
|
||||||
|
];
|
||||||
|
return JDE0s[season];
|
||||||
|
}
|
||||||
|
|
||||||
|
function seasonEvents(year) {
|
||||||
|
const names = ["March Equinox", "June Solstice", "September Equinox", "December Solstice"];
|
||||||
|
const emojis = ["🌱", "☀️", "🍂", "❄️"];
|
||||||
|
return names.map((name, i) => {
|
||||||
|
const jd = seasonJD(year, i);
|
||||||
|
const d = dateFromJD(jd);
|
||||||
|
return { name, emoji: emojis[i], date: d, month: d.getUTCMonth() + 1, day: d.getUTCDate() };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Moonrise / Moonset (simplified — horizon crossing estimate) ───────────────
|
||||||
|
// Uses a simple approximation: moon moves ~13.18°/day, rises ~50min later each day.
|
||||||
|
// Good enough for a display page; full computation needs perturbation terms.
|
||||||
|
function moonriseApprox(date, lat, lon) {
|
||||||
|
// Approximate: moon rises ~50 min later each day than previous
|
||||||
|
// Anchor: use sun rise/set as rough guide, offset by moon's daily retardation
|
||||||
|
// For a simple display we just note this is approximate
|
||||||
|
return null; // placeholder — show "~" note instead
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── DOM ───────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
// Parse ?date=YYYY-MM-DD from URL, default to today
|
||||||
|
function parseDate() {
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const ds = params.get("date");
|
||||||
|
if (ds) {
|
||||||
|
const [y, m, d] = ds.split("-").map(Number);
|
||||||
|
if (y && m && d) return new Date(Date.UTC(y, m - 1, d));
|
||||||
|
}
|
||||||
|
const now = new Date();
|
||||||
|
return new Date(Date.UTC(now.getFullYear(), now.getMonth(), now.getDate()));
|
||||||
|
}
|
||||||
|
|
||||||
|
function dateToParam(date) {
|
||||||
|
const y = date.getUTCFullYear();
|
||||||
|
const m = String(date.getUTCMonth() + 1).padStart(2, "0");
|
||||||
|
const d = String(date.getUTCDate()).padStart(2, "0");
|
||||||
|
return `${y}-${m}-${d}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stepDate(date, days) {
|
||||||
|
const d = new Date(date);
|
||||||
|
d.setUTCDate(d.getUTCDate() + days);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDate(date) {
|
||||||
|
return date.toLocaleDateString("en-US", { weekday: "long", year: "numeric", month: "long", day: "numeric", timeZone: "UTC" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default lat/lon: roughly central US (no geolocation, just reasonable default)
|
||||||
|
// Will use user's timezone offset to pick a reasonable longitude
|
||||||
|
function guessLon() {
|
||||||
|
const offsetHours = -new Date().getTimezoneOffset() / 60;
|
||||||
|
return offsetHours * 15; // rough: UTC offset * 15° per hour
|
||||||
|
}
|
||||||
|
|
||||||
|
const LAT = 40.0; // roughly mid-US latitude
|
||||||
|
const LON = guessLon();
|
||||||
|
|
||||||
|
let currentDate = parseDate();
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
const date = currentDate;
|
||||||
|
|
||||||
|
// Update URL without reload
|
||||||
|
const newUrl = `${window.location.pathname}?date=${dateToParam(date)}`;
|
||||||
|
history.replaceState(null, "", newUrl);
|
||||||
|
|
||||||
|
document.getElementById("dateTitle").textContent = formatDate(date);
|
||||||
|
|
||||||
|
// Moon
|
||||||
|
const { age, illumination } = moonPhase(date);
|
||||||
|
document.getElementById("moonGlyph").textContent = moonPhaseGlyph(age);
|
||||||
|
document.getElementById("moonName").textContent = moonPhaseName(age);
|
||||||
|
document.getElementById("moonPct").textContent =
|
||||||
|
`${Math.round(illumination * 100)}% illuminated · ${age.toFixed(1)} days old`;
|
||||||
|
document.getElementById("moonAge").textContent = `${age.toFixed(1)} days`;
|
||||||
|
|
||||||
|
// Next new & full
|
||||||
|
const jd = jdFromDate(date);
|
||||||
|
const nextNewJD = nextMoonPhaseJD(jd, 0);
|
||||||
|
const nextFullJD = nextMoonPhaseJD(jd, 2);
|
||||||
|
document.getElementById("nextNew").textContent = dateFromJD(nextNewJD).toLocaleDateString("en-US", { month: "short", day: "numeric", timeZone: "UTC" });
|
||||||
|
document.getElementById("nextFull").textContent = dateFromJD(nextFullJD).toLocaleDateString("en-US", { month: "short", day: "numeric", timeZone: "UTC" });
|
||||||
|
|
||||||
|
// Sun
|
||||||
|
const sun = sunriseSunset(date, LAT, LON);
|
||||||
|
document.getElementById("sunrise").textContent = sun.rise || (sun.alwaysUp ? "Midnight sun" : "Below horizon");
|
||||||
|
document.getElementById("sunset").textContent = sun.set || (sun.alwaysUp ? "Midnight sun" : "Below horizon");
|
||||||
|
document.getElementById("solarNoon").textContent = sun.noon;
|
||||||
|
document.getElementById("dayLen").textContent = sun.rise && sun.set ? dayLengthStr(sun.riseJD, sun.setJD) : "—";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Year events
|
||||||
|
const year = date.getUTCFullYear();
|
||||||
|
const events = seasonEvents(year);
|
||||||
|
const SYNODIC = 29.53058867;
|
||||||
|
const KNOWN_NEW_MOON_JD = 2451549.756;
|
||||||
|
|
||||||
|
// Add major moon phases for the year
|
||||||
|
const moonEvents = [];
|
||||||
|
let testJD = julianDay(year, 1, 1);
|
||||||
|
const endJD = julianDay(year, 12, 31);
|
||||||
|
for (let phase = 0; phase < 4; phase++) {
|
||||||
|
let pJD = nextMoonPhaseJD(testJD - SYNODIC, phase);
|
||||||
|
while (pJD <= endJD) {
|
||||||
|
const d = dateFromJD(pJD);
|
||||||
|
if (d.getUTCFullYear() === year) {
|
||||||
|
const phaseNames = ["🌑 New Moon", "🌓 First Quarter", "🌕 Full Moon", "🌗 Last Quarter"];
|
||||||
|
moonEvents.push({ name: phaseNames[phase], date: d, jd: pJD });
|
||||||
|
}
|
||||||
|
pJD += SYNODIC;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
moonEvents.sort((a, b) => a.jd - b.jd);
|
||||||
|
|
||||||
|
const allEvents = [
|
||||||
|
...events.map(e => ({ name: `${e.emoji} ${e.name}`, date: e.date, jd: jdFromDate(e.date) })),
|
||||||
|
...moonEvents,
|
||||||
|
].sort((a, b) => a.jd - b.jd);
|
||||||
|
|
||||||
|
const ul = document.getElementById("yearEvents");
|
||||||
|
ul.innerHTML = "";
|
||||||
|
allEvents.forEach(ev => {
|
||||||
|
const li = document.createElement("li");
|
||||||
|
const ds = ev.date.toLocaleDateString("en-US", { month: "short", day: "numeric", timeZone: "UTC" });
|
||||||
|
const isToday = dateToParam(ev.date) === dateToParam(date);
|
||||||
|
li.innerHTML = `<span class="event-name"${isToday ? ' style="color:var(--accent)"' : ""}>${ev.name}</span><span>${ds}</span>`;
|
||||||
|
ul.appendChild(li);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById("prevDay").addEventListener("click", () => {
|
||||||
|
currentDate = stepDate(currentDate, -1);
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
document.getElementById("nextDay").addEventListener("click", () => {
|
||||||
|
currentDate = stepDate(currentDate, +1);
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
|
||||||
|
render();
|
||||||
@@ -0,0 +1,290 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>SethPC Calendar</title>
|
||||||
|
<link rel="icon" type="image/png" href="https://storage.googleapis.com/sethfreiberg.com/sethflix/favicon.png">
|
||||||
|
<link rel="stylesheet" href="/style.css">
|
||||||
|
<style>
|
||||||
|
.cal-wrap {
|
||||||
|
width: min(860px, 100%);
|
||||||
|
background: var(--panel);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: clamp(1rem, 2.5vw, 2rem);
|
||||||
|
box-shadow: 0 10px 28px rgb(0 0 0 / 42%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Nav row */
|
||||||
|
.cal-nav {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 1.2rem;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
.cal-nav h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: clamp(1rem, 2.5vw, 1.4rem);
|
||||||
|
text-align: center;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.cal-nav .sub {
|
||||||
|
font-size: 0.78rem;
|
||||||
|
color: var(--muted);
|
||||||
|
font-weight: 400;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.nav-btn {
|
||||||
|
background: #2a2a2a;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
color: var(--text);
|
||||||
|
padding: 0.4rem 0.8rem;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1;
|
||||||
|
transition: border-color 0.15s;
|
||||||
|
}
|
||||||
|
.nav-btn:hover { border-color: var(--accent); color: var(--accent); }
|
||||||
|
|
||||||
|
/* Month tabs */
|
||||||
|
.month-tabs {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.3rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
.month-tab {
|
||||||
|
background: #2a2a2a;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: var(--muted);
|
||||||
|
padding: 0.25rem 0.6rem;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
transition: all 0.15s;
|
||||||
|
}
|
||||||
|
.month-tab:hover { border-color: var(--accent); color: var(--accent); }
|
||||||
|
.month-tab.active {
|
||||||
|
background: var(--accent);
|
||||||
|
border-color: var(--accent);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.month-tab.holiday-tab {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Grid */
|
||||||
|
.cal-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(6, 1fr);
|
||||||
|
gap: 4px;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
.cal-grid.holiday-grid {
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
}
|
||||||
|
.col-header {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
color: var(--accent);
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
padding: 0.2rem 0;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.cal-cell {
|
||||||
|
background: #2a2a2a;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 0.35rem 0.4rem;
|
||||||
|
min-height: 56px;
|
||||||
|
cursor: default;
|
||||||
|
transition: border-color 0.15s;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.cal-cell:hover { border-color: #555; }
|
||||||
|
.cal-cell.today {
|
||||||
|
border-color: var(--accent);
|
||||||
|
background: #2e1f15;
|
||||||
|
}
|
||||||
|
.cal-cell.holiday-cell {
|
||||||
|
grid-column: span 1;
|
||||||
|
background: #1e1e2e;
|
||||||
|
border-color: #444;
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 70px;
|
||||||
|
}
|
||||||
|
.seth-day {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text);
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.greg-date {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: var(--muted);
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
.holiday-name {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--muted);
|
||||||
|
margin-top: 0.2rem;
|
||||||
|
}
|
||||||
|
.moon-link {
|
||||||
|
position: absolute;
|
||||||
|
top: 3px;
|
||||||
|
right: 4px;
|
||||||
|
opacity: 0.5;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: opacity 0.15s;
|
||||||
|
display: block;
|
||||||
|
line-height: 0;
|
||||||
|
}
|
||||||
|
.moon-link:hover { opacity: 1; }
|
||||||
|
.moon-link canvas { display: block; border-radius: 50%; }
|
||||||
|
.cal-cell.seth-weekend {
|
||||||
|
border-color: #1a2a4a;
|
||||||
|
background: #1c2130;
|
||||||
|
}
|
||||||
|
.cal-cell.greg-weekend {
|
||||||
|
border-color: #1a3a35;
|
||||||
|
background: #1c2a28;
|
||||||
|
}
|
||||||
|
.cal-cell.seth-weekend.greg-weekend {
|
||||||
|
border-color: #2a3a4a;
|
||||||
|
background: #1c2530;
|
||||||
|
}
|
||||||
|
.cal-cell.today.seth-weekend,
|
||||||
|
.cal-cell.today.greg-weekend {
|
||||||
|
border-color: var(--accent);
|
||||||
|
background: #2e1f15;
|
||||||
|
}
|
||||||
|
.cal-cell.has-holiday {
|
||||||
|
border-color: #5a4a2a;
|
||||||
|
}
|
||||||
|
/* The outer wrapper for the split cell: transparent, no own border/bg */
|
||||||
|
.cal-cell.has-leap-split {
|
||||||
|
padding: 0;
|
||||||
|
gap: 4px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: transparent;
|
||||||
|
border-color: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
.cal-cell.has-leap-split:hover {
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
/* Each half is its own independent mini-cell — same color as Seth weekend */
|
||||||
|
.leap-top,
|
||||||
|
.leap-bottom {
|
||||||
|
position: relative;
|
||||||
|
padding: 0.25rem 0.4rem;
|
||||||
|
flex: 1;
|
||||||
|
border: 1px solid #1a2a4a;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #1c2130;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: default;
|
||||||
|
transition: border-color 0.15s;
|
||||||
|
}
|
||||||
|
.leap-top:hover,
|
||||||
|
.leap-bottom:hover {
|
||||||
|
border-color: #555;
|
||||||
|
}
|
||||||
|
/* Today highlights on each half independently */
|
||||||
|
.leap-top.leap-top-today {
|
||||||
|
border-color: var(--accent);
|
||||||
|
background: #2e1f15;
|
||||||
|
}
|
||||||
|
.leap-bottom.leap-today {
|
||||||
|
border-color: var(--accent);
|
||||||
|
background: #1a2e40;
|
||||||
|
}
|
||||||
|
.hol-label {
|
||||||
|
font-size: 0.62rem;
|
||||||
|
color: #c8a060;
|
||||||
|
margin-top: 0.2rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
white-space: normal;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
.hol-label a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: underline;
|
||||||
|
text-underline-offset: 2px;
|
||||||
|
}
|
||||||
|
.hol-label a:hover {
|
||||||
|
color: #e0b878;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Legend */
|
||||||
|
.legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
color: var(--muted);
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.legend-dot {
|
||||||
|
width: 8px; height: 8px;
|
||||||
|
background: var(--accent);
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
.legend-box {
|
||||||
|
width: 12px; height: 12px;
|
||||||
|
background: #1e1e2e;
|
||||||
|
border: 1px solid #444;
|
||||||
|
border-radius: 3px;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class="cal-wrap">
|
||||||
|
<div class="brand-row">
|
||||||
|
<img class="logo-img" src="https://storage.googleapis.com/sethfreiberg.com/sethflix/favicon.png" alt="SethPC logo">
|
||||||
|
<span class="brand-text">SethPC Calendar</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cal-nav">
|
||||||
|
<button class="nav-btn" id="prevYear">≪</button>
|
||||||
|
<button class="nav-btn" id="prevMonth">‹</button>
|
||||||
|
<h2 id="navTitle">-<span class="sub" id="navSub"></span></h2>
|
||||||
|
<button class="nav-btn" id="nextMonth">›</button>
|
||||||
|
<button class="nav-btn" id="nextYear">≫</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="month-tabs" id="monthTabs"></div>
|
||||||
|
<div id="calGrid"></div>
|
||||||
|
|
||||||
|
<div class="legend">
|
||||||
|
<span><span class="legend-dot"></span><a id="todayLink" href="#" style="color:inherit;text-decoration:underline;text-underline-offset:2px;">Today</a></span>
|
||||||
|
<span><span class="legend-box"></span>Holiday days</span>
|
||||||
|
<span style="display:inline-flex;align-items:center;gap:4px;">
|
||||||
|
<span style="width:12px;height:12px;border-radius:3px;background:#1c2130;border:1px solid #1a2a4a;display:inline-block;"></span>Seth weekend (D4–D5)
|
||||||
|
</span>
|
||||||
|
<span style="display:inline-flex;align-items:center;gap:4px;">
|
||||||
|
<span style="width:12px;height:12px;border-radius:3px;background:#1c2a28;border:1px solid #1a3a35;display:inline-block;"></span>Gregorian weekend
|
||||||
|
</span>
|
||||||
|
<span style="display:inline-flex;align-items:center;gap:4px;">
|
||||||
|
<canvas id="legendMoon" width="12" height="12" style="border-radius:50%;vertical-align:middle;"></canvas>Moon phase (click for astronomy)
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<script src="/calendar.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,648 @@
|
|||||||
|
// Seth Calendar — calendar view
|
||||||
|
//
|
||||||
|
// 10 months × 36 days (6 weeks × 6 days), then 5 or 6 holiday days.
|
||||||
|
// Jan 1 = Month 1 Day 1. Same year numbers as Gregorian.
|
||||||
|
|
||||||
|
// ── Moon phase rendering ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const KNOWN_NEW_MOON_JD = 2451549.756; // Jan 6, 2000 18:14 UTC
|
||||||
|
const SYNODIC_MONTH = 29.53058867;
|
||||||
|
|
||||||
|
function jdFromCalDate(year, month, day) {
|
||||||
|
if (month <= 2) { year--; month += 12; }
|
||||||
|
const A = Math.floor(year / 100);
|
||||||
|
const B = 2 - A + Math.floor(A / 4);
|
||||||
|
return Math.floor(365.25 * (year + 4716)) + Math.floor(30.6001 * (month + 1)) + day + B - 1524.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns { age (0–29.53), illumination (0–1), waxing (bool) }
|
||||||
|
function moonPhaseForDate(jsDate) {
|
||||||
|
const jd = jdFromCalDate(jsDate.getFullYear(), jsDate.getMonth() + 1, jsDate.getDate()) + 0.5;
|
||||||
|
const daysSince = jd - KNOWN_NEW_MOON_JD;
|
||||||
|
const cycles = daysSince / SYNODIC_MONTH;
|
||||||
|
const age = (cycles - Math.floor(cycles)) * SYNODIC_MONTH;
|
||||||
|
const illumination = (1 - Math.cos(2 * Math.PI * age / SYNODIC_MONTH)) / 2;
|
||||||
|
const waxing = age < SYNODIC_MONTH / 2;
|
||||||
|
return { age, illumination, waxing };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw a moon phase into a canvas element (size x size pixels)
|
||||||
|
function drawMoon(canvas, illumination, waxing) {
|
||||||
|
const size = canvas.width;
|
||||||
|
const r = size / 2 - 0.5;
|
||||||
|
const cx = size / 2;
|
||||||
|
const cy = size / 2;
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
ctx.clearRect(0, 0, size, size);
|
||||||
|
|
||||||
|
// Dark side color
|
||||||
|
const dark = "#1a1a2e";
|
||||||
|
// Light side color
|
||||||
|
const light = "#e8dfc0";
|
||||||
|
|
||||||
|
// Draw full dark circle first
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(cx, cy, r, 0, 2 * Math.PI);
|
||||||
|
ctx.fillStyle = dark;
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
// The terminator: lit fraction maps to an ellipse x-scale
|
||||||
|
// illumination 0 = new (all dark), 0.5 = quarter (semi-circle), 1 = full (all light)
|
||||||
|
// Ellipse x-radius: at 0.5 it's 0, at 0 or 1 it's r
|
||||||
|
// Phase angle: 0=new, π/2=first quarter, π=full, 3π/2=last quarter
|
||||||
|
const phaseAngle = (age => 2 * Math.PI * age / SYNODIC_MONTH)(
|
||||||
|
waxing ? Math.acos(1 - 2 * illumination) * SYNODIC_MONTH / (2 * Math.PI)
|
||||||
|
: (SYNODIC_MONTH / 2) + Math.acos(2 * illumination - 1) * SYNODIC_MONTH / (2 * Math.PI)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Simpler direct approach: draw lit half-circle + terminator ellipse
|
||||||
|
// Lit side: right if waxing, left if waning
|
||||||
|
// x-scale of terminator ellipse: cos(phase_angle) where angle 0=new, π=full
|
||||||
|
const angle = 2 * Math.PI * (waxing
|
||||||
|
? illumination < 0.5 ? illumination : illumination
|
||||||
|
: illumination);
|
||||||
|
|
||||||
|
// Direct: ellipse terminator x-width = (1 - 2*illumination)*r for waxing,
|
||||||
|
// (2*illumination - 1)*r for waning
|
||||||
|
const termX = Math.abs(1 - 2 * illumination) * r;
|
||||||
|
const litOnRight = waxing;
|
||||||
|
|
||||||
|
// Draw lit semicircle on the appropriate side
|
||||||
|
ctx.save();
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(cx, cy, r, -Math.PI / 2, Math.PI / 2, !litOnRight);
|
||||||
|
ctx.fillStyle = light;
|
||||||
|
ctx.fill();
|
||||||
|
ctx.restore();
|
||||||
|
|
||||||
|
// Draw terminator ellipse to carve/extend into the lit side
|
||||||
|
ctx.save();
|
||||||
|
ctx.beginPath();
|
||||||
|
// Clip to the dark half
|
||||||
|
ctx.arc(cx, cy, r + 1, -Math.PI / 2, Math.PI / 2, litOnRight);
|
||||||
|
ctx.lineTo(cx, cy - r - 1);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.clip();
|
||||||
|
|
||||||
|
// Fill terminator ellipse (same as lit color if < half, dark if > half)
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.ellipse(cx, cy, termX, r, 0, 0, 2 * Math.PI);
|
||||||
|
ctx.fillStyle = illumination < 0.5 ? dark : light;
|
||||||
|
ctx.fill();
|
||||||
|
ctx.restore();
|
||||||
|
|
||||||
|
// Thin border
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(cx, cy, r, 0, 2 * Math.PI);
|
||||||
|
ctx.strokeStyle = "rgba(255,255,255,0.15)";
|
||||||
|
ctx.lineWidth = 0.5;
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeMoonCanvas(jsDate, size) {
|
||||||
|
const { illumination, waxing } = moonPhaseForDate(jsDate);
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
canvas.width = size;
|
||||||
|
canvas.height = size;
|
||||||
|
canvas.style.width = size + "px";
|
||||||
|
canvas.style.height = size + "px";
|
||||||
|
drawMoon(canvas, illumination, waxing);
|
||||||
|
return canvas;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GREG_MONTHS = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
|
||||||
|
const GREG_WEEKDAYS = ["Su","M","T","W","Tr","F","S"];
|
||||||
|
const WEEK_DAY_LABELS = ["D0","D1","D2","D3","D4","D5"];
|
||||||
|
|
||||||
|
// --- Holidays ---
|
||||||
|
// Fixed: { month, day, name, emoji }
|
||||||
|
// Floating: computed per year via buildFloatingHolidays(year)
|
||||||
|
|
||||||
|
const FIXED_HOLIDAYS = [
|
||||||
|
{ month: 1, day: 1, name: "New Year's Day", emoji: "🎆", url: "https://en.wikipedia.org/wiki/New_Year%27s_Day" },
|
||||||
|
{ month: 2, day: 13, name: "1 1 1 Day", emoji: "1️⃣" },
|
||||||
|
{ month: 3, day: 28, name: "2 2 2 Day", emoji: "2️⃣" },
|
||||||
|
{ month: 5, day: 10, name: "3 3 3 Day", emoji: "3️⃣" },
|
||||||
|
{ month: 6, day: 22, name: "4 4 4 Day", emoji: "4️⃣" },
|
||||||
|
{ month: 8, day: 4, name: "5 5 5 Day", emoji: "5️⃣" },
|
||||||
|
{ month: 4, day: 20, name: "420", emoji: "🌿", url: "https://en.wikipedia.org/wiki/420_(cannabis_culture)" },
|
||||||
|
{ month: 6, day: 6, name: "4 2 0 Day", emoji: "🌿" },
|
||||||
|
{ month: 7, day: 10, name: "710", emoji: "🍯", url: "https://weedmaps.com/learn/cannabis-and-its-evolution/everything-you-need-to-know-about-710" },
|
||||||
|
{ month: 9, day: 16, name: "7 1 0 Day", emoji: "🍯" },
|
||||||
|
{ month: 1, day: 2, name: "Science Fiction Day", emoji: "🚀", url: "https://en.wikipedia.org/wiki/Isaac_Asimov" },
|
||||||
|
{ month: 1, day: 3, name: "Festival of Sleep Day", emoji: "😴" },
|
||||||
|
{ month: 1, day: 16, name: "Nothing Day", emoji: "🕳️", url: "https://en.wikipedia.org/wiki/National_Nothing_Day" },
|
||||||
|
{ month: 1, day: 20, name: "Penguin Awareness Day", emoji: "🐧", url: "https://en.wikipedia.org/wiki/Penguin" },
|
||||||
|
{ month: 1, day: 21, name: "Squirrel Appreciation Day", emoji: "🐿️", url: "https://en.wikipedia.org/wiki/Squirrel" },
|
||||||
|
{ month: 1, day: 25, name: "Opposite Day", emoji: "🙃", url: "https://en.wikipedia.org/wiki/Opposite_Day" },
|
||||||
|
{ month: 1, day: 27, name: "Chocolate Cake Day", emoji: "🎂", url: "https://en.wikipedia.org/wiki/Chocolate_cake" },
|
||||||
|
{ month: 1, day: 27, name: "e-Day", emoji: "📐", url: "https://en.wikipedia.org/wiki/E_(mathematical_constant)" },
|
||||||
|
{ month: 1, day: 31, name: "Backwards Day", emoji: "🔄" },
|
||||||
|
{ month: 2, day: 2, name: "Groundhog Day", emoji: "🦔", url: "https://en.wikipedia.org/wiki/Groundhog_Day" },
|
||||||
|
{ month: 2, day: 6, name: "Work Naked Day", emoji: "😳" },
|
||||||
|
{ month: 2, day: 7, name: "Eat Ice Cream for Breakfast Day", emoji: "🍦" },
|
||||||
|
{ month: 2, day: 12, name: "Darwin Day", emoji: "🐒", url: "https://en.wikipedia.org/wiki/Darwin_Day" },
|
||||||
|
{ month: 2, day: 14, name: "Valentine's Day", emoji: "❤️", url: "https://en.wikipedia.org/wiki/Valentine%27s_Day" },
|
||||||
|
{ month: 2, day: 17, name: "Random Act of Kindness Day", emoji: "🤝", url: "https://en.wikipedia.org/wiki/Random_act_of_kindness" },
|
||||||
|
{ month: 2, day: 22, name: "Be Humble Day", emoji: "🙇" },
|
||||||
|
{ month: 2, day: 28, name: "Public Sleeping Day", emoji: "💤" },
|
||||||
|
{ month: 3, day: 4, name: "March Forth and Do Something Day", emoji: "🚶" },
|
||||||
|
{ month: 3, day: 9, name: "Napping Day", emoji: "😪", url: "https://en.wikipedia.org/wiki/Napping" },
|
||||||
|
{ month: 3, day: 10, name: "Mario Day", emoji: "🍄", url: "https://en.wikipedia.org/wiki/Mario_Day" },
|
||||||
|
{ month: 3, day: 14, name: "Pi Day", emoji: "🥧", url: "https://en.wikipedia.org/wiki/Pi_Day" },
|
||||||
|
{ month: 3, day: 15, name: "Everything You Think is Wrong Day", emoji: "🤔" },
|
||||||
|
{ month: 3, day: 17, name: "St. Patrick's Day", emoji: "🍀", url: "https://en.wikipedia.org/wiki/Saint_Patrick%27s_Day" },
|
||||||
|
{ month: 3, day: 22, name: "International Goof Off Day", emoji: "🤪" },
|
||||||
|
{ month: 3, day: 23, name: "Puppy Day", emoji: "🐶", url: "https://en.wikipedia.org/wiki/Dog" },
|
||||||
|
{ month: 3, day: 26, name: "Make Up Your Own Holiday Day", emoji: "📅" },
|
||||||
|
{ month: 4, day: 1, name: "April Fools' Day", emoji: "🃏", url: "https://en.wikipedia.org/wiki/April_Fools%27_Day" },
|
||||||
|
{ month: 4, day: 5, name: "First Contact Day", emoji: "👽", url: "https://en.wikipedia.org/wiki/Star_Trek:_First_Contact" },
|
||||||
|
{ month: 4, day: 12, name: "Grilled Cheese Day", emoji: "🧀", url: "https://en.wikipedia.org/wiki/Grilled_cheese" },
|
||||||
|
{ month: 4, day: 12, name: "Yuri's Night", emoji: "🛸", url: "https://en.wikipedia.org/wiki/Yuri%27s_Night" },
|
||||||
|
{ month: 4, day: 16, name: "Wear Pajamas to Work Day", emoji: "🛌" },
|
||||||
|
{ month: 4, day: 22, name: "Jelly Bean Day", emoji: "🫘", url: "https://en.wikipedia.org/wiki/Jelly_bean" },
|
||||||
|
{ month: 4, day: 23, name: "Impossible Astronaut Day", emoji: "🕐", url: "https://en.wikipedia.org/wiki/The_Impossible_Astronaut" },
|
||||||
|
{ month: 5, day: 1, name: "No Pants Day", emoji: "👖", url: "https://en.wikipedia.org/wiki/No_Pants_Day" },
|
||||||
|
{ month: 5, day: 4, name: "Star Wars Day", emoji: "⚔️", url: "https://en.wikipedia.org/wiki/Star_Wars_Day" },
|
||||||
|
{ month: 5, day: 5, name: "Cinco de Mayo", emoji: "🌮", url: "https://en.wikipedia.org/wiki/Cinco_de_Mayo" },
|
||||||
|
{ month: 5, day: 9, name: "Lost Sock Memorial Day", emoji: "🧦" },
|
||||||
|
{ month: 5, day: 11, name: "Eat What You Want Day", emoji: "🍕" },
|
||||||
|
{ month: 5, day: 21, name: "Talk Like Yoda Day", emoji: "🟢", url: "https://en.wikipedia.org/wiki/Yoda" },
|
||||||
|
{ month: 5, day: 25, name: "Towel Day", emoji: "🏖️", url: "https://en.wikipedia.org/wiki/Towel_Day" },
|
||||||
|
{ month: 5, day: 29, name: "Put a Pillow on Your Fridge Day", emoji: "🛋️" },
|
||||||
|
{ month: 6, day: 3, name: "Repeat Day", emoji: "🔁", url: "https://en.wikipedia.org/wiki/Repetition" },
|
||||||
|
{ month: 6, day: 3, name: "Repeat Day", emoji: "🔁", url: "https://en.wikipedia.org/wiki/Repetition" },
|
||||||
|
{ month: 6, day: 4, name: "Hug Your Cat Day", emoji: "🐱", url: "https://en.wikipedia.org/wiki/Cat" },
|
||||||
|
{ month: 6, day: 18, name: "International Panic Day", emoji: "😱" },
|
||||||
|
{ month: 6, day: 19, name: "Juneteenth", emoji: "✊", url: "https://en.wikipedia.org/wiki/Juneteenth" },
|
||||||
|
{ month: 6, day: 22, name: "Onion Ring Day", emoji: "🧅", url: "https://en.wikipedia.org/wiki/Onion_ring" },
|
||||||
|
{ month: 6, day: 26, name: "Seth's Birthday!", emoji: "🎂" },
|
||||||
|
{ month: 6, day: 28, name: "Tau Day", emoji: "📐", url: "https://en.wikipedia.org/wiki/Turn_(angle)" },
|
||||||
|
{ month: 7, day: 1, name: "International Joke Day", emoji: "😂", url: "https://en.wikipedia.org/wiki/Joke" },
|
||||||
|
{ month: 7, day: 2, name: "World UFO Day", emoji: "🛸", url: "https://en.wikipedia.org/wiki/World_UFO_Day" },
|
||||||
|
{ month: 7, day: 4, name: "Independence Day", emoji: "🎇", url: "https://en.wikipedia.org/wiki/Independence_Day_(United_States)" },
|
||||||
|
{ month: 7, day: 4, name: "Sidewalk Egg Frying Day", emoji: "🍳", url: "https://en.wikipedia.org/wiki/Frying_an_egg_on_the_sidewalk" },
|
||||||
|
{ month: 7, day: 11, name: "Cheer Up the Lonely Day", emoji: "🤗" },
|
||||||
|
{ month: 7, day: 13, name: "Embrace Your Geekness Day", emoji: "🤓" },
|
||||||
|
{ month: 7, day: 14, name: "Pandemonium Day", emoji: "🌀", url: "https://en.wikipedia.org/wiki/Pandemonium" },
|
||||||
|
{ month: 7, day: 17, name: "World Emoji Day", emoji: "😄", url: "https://en.wikipedia.org/wiki/World_Emoji_Day" },
|
||||||
|
{ month: 7, day: 19, name: "Stick Out Your Tongue Day", emoji: "👅" },
|
||||||
|
{ month: 7, day: 22, name: "Pi Approximation Day", emoji: "≈", url: "https://en.wikipedia.org/wiki/Pi_Approximation_Day" },
|
||||||
|
{ month: 7, day: 27, name: "Take Your Pants for a Walk Day", emoji: "🚶" },
|
||||||
|
{ month: 8, day: 2, name: "Ice Cream Sandwich Day", emoji: "🍨", url: "https://en.wikipedia.org/wiki/Ice_cream_sandwich" },
|
||||||
|
{ month: 8, day: 10, name: "Lazy Day", emoji: "🛋️", url: "https://en.wikipedia.org/wiki/Laziness" },
|
||||||
|
{ month: 8, day: 12, name: "Middle Child Day", emoji: "😐", url: "https://en.wikipedia.org/wiki/Middle_child_syndrome" },
|
||||||
|
{ month: 8, day: 13, name: "Left-Handers Day", emoji: "🤚", url: "https://en.wikipedia.org/wiki/Left-Handers_Day" },
|
||||||
|
{ month: 8, day: 15, name: "Relaxation Day", emoji: "🧘" },
|
||||||
|
{ month: 8, day: 16, name: "Tell a Joke Day", emoji: "🤣" },
|
||||||
|
{ month: 8, day: 24, name: "Pluto Demoted Day", emoji: "🔭", url: "https://en.wikipedia.org/wiki/IAU_definition_of_planet" },
|
||||||
|
{ month: 8, day: 24, name: "National Waffle Day", emoji: "🧇", url: "https://en.wikipedia.org/wiki/Waffle" },
|
||||||
|
{ month: 8, day: 30, name: "Frankenstein Day", emoji: "⚡", url: "https://en.wikipedia.org/wiki/Frankenstein" },
|
||||||
|
{ month: 9, day: 5, name: "Be Late for Something Day", emoji: "⏰" },
|
||||||
|
{ month: 9, day: 6, name: "Fight Procrastination Day", emoji: "✅", url: "https://en.wikipedia.org/wiki/Procrastination" },
|
||||||
|
{ month: 9, day: 13, name: "Blame Someone Else Day", emoji: "👉" },
|
||||||
|
{ month: 9, day: 13, name: "Fortune Cookie Day", emoji: "🥠", url: "https://en.wikipedia.org/wiki/Fortune_cookie" },
|
||||||
|
{ month: 9, day: 19, name: "Talk Like a Pirate Day", emoji: "🏴☠️", url: "https://en.wikipedia.org/wiki/International_Talk_Like_a_Pirate_Day" },
|
||||||
|
{ month: 9, day: 22, name: "Elephant Appreciation Day", emoji: "🐘", url: "https://en.wikipedia.org/wiki/Elephant" },
|
||||||
|
{ month: 9, day: 28, name: "Ask a Stupid Question Day", emoji: "❓" },
|
||||||
|
{ month: 10, day: 4, name: "National Taco Day", emoji: "🌮", url: "https://en.wikipedia.org/wiki/Taco" },
|
||||||
|
{ month: 10, day: 12, name: "Moment of Frustration Day", emoji: "😤" },
|
||||||
|
{ month: 10, day: 14, name: "Dessert Day", emoji: "🍮", url: "https://en.wikipedia.org/wiki/Dessert" },
|
||||||
|
{ month: 10, day: 21, name: "Back to the Future Day", emoji: "⏱️", url: "https://en.wikipedia.org/wiki/Back_to_the_Future_Day" },
|
||||||
|
{ month: 10, day: 23, name: "Mole Day", emoji: "🦡", url: "https://en.wikipedia.org/wiki/Mole_Day" },
|
||||||
|
{ month: 10, day: 31, name: "Halloween", emoji: "🎃", url: "https://en.wikipedia.org/wiki/Halloween" },
|
||||||
|
{ month: 11, day: 8, name: "Cook Something Bold Day", emoji: "🔥" },
|
||||||
|
{ month: 11, day: 11, name: "Veterans Day", emoji: "🎖️", url: "https://en.wikipedia.org/wiki/Veterans_Day" },
|
||||||
|
{ month: 11, day: 13, name: "World Kindness Day", emoji: "💛", url: "https://en.wikipedia.org/wiki/World_Kindness_Day" },
|
||||||
|
{ month: 11, day: 17, name: "Take a Hike Day", emoji: "🥾" },
|
||||||
|
{ month: 11, day: 19, name: "Have a Bad Day Day", emoji: "😞" },
|
||||||
|
{ month: 12, day: 12, name: "Gingerbread House Day", emoji: "🏠", url: "https://en.wikipedia.org/wiki/Gingerbread_house" },
|
||||||
|
{ month: 12, day: 21, name: "Crossword Puzzle Day", emoji: "📝", url: "https://en.wikipedia.org/wiki/Crossword" },
|
||||||
|
{ month: 12, day: 25, name: "Christmas", emoji: "🎄", url: "https://en.wikipedia.org/wiki/Christmas" },
|
||||||
|
{ month: 12, day: 30, name: "Bacon Day", emoji: "🥓", url: "https://en.wikipedia.org/wiki/Bacon" },
|
||||||
|
{ month: 12, day: 31, name: "New Year's Eve", emoji: "🥂", url: "https://en.wikipedia.org/wiki/New_Year%27s_Eve" },
|
||||||
|
];
|
||||||
|
|
||||||
|
function nthWeekday(year, month, n, weekday) {
|
||||||
|
// n: 1=first, -1=last. weekday: 0=Mon..6=Sun
|
||||||
|
const base = new Date(year, month - 1, 1);
|
||||||
|
if (n > 0) {
|
||||||
|
const diff = (weekday - base.getDay() + 7) % 7;
|
||||||
|
base.setDate(1 + diff + (n - 1) * 7);
|
||||||
|
} else {
|
||||||
|
const lastDay = new Date(year, month, 0).getDate();
|
||||||
|
base.setDate(lastDay);
|
||||||
|
const diff = (base.getDay() - weekday + 7) % 7;
|
||||||
|
base.setDate(lastDay - diff);
|
||||||
|
}
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
|
function easterDate(year) {
|
||||||
|
// Anonymous Gregorian algorithm
|
||||||
|
const a = year % 19, b = Math.floor(year / 100), c = year % 100;
|
||||||
|
const d = Math.floor(b / 4), e = b % 4, f = Math.floor((b + 8) / 25);
|
||||||
|
const g = Math.floor((b - f + 1) / 3), h = (19*a + b - d - g + 15) % 30;
|
||||||
|
const i = Math.floor(c / 4), k = c % 4;
|
||||||
|
const l = (32 + 2*e + 2*i - h - k) % 7;
|
||||||
|
const m = Math.floor((a + 11*h + 22*l) / 451);
|
||||||
|
const month = Math.floor((h + l - 7*m + 114) / 31);
|
||||||
|
const day = ((h + l - 7*m + 114) % 31) + 1;
|
||||||
|
return new Date(year, month - 1, day);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Approximate JD of solstice/equinox (Meeus Table 27.a)
|
||||||
|
// season: 0=March equinox, 1=June solstice, 2=September equinox, 3=December solstice
|
||||||
|
function seasonJD(year, season) {
|
||||||
|
const Y = (year - 2000) / 1000;
|
||||||
|
const jde = [
|
||||||
|
2451623.80984 + 365242.37404*Y + 0.05169*Y*Y - 0.00411*Y*Y*Y - 0.00057*Y*Y*Y*Y,
|
||||||
|
2451716.56767 + 365241.62603*Y + 0.00325*Y*Y + 0.00888*Y*Y*Y - 0.00030*Y*Y*Y*Y,
|
||||||
|
2451810.05917 + 365242.01767*Y - 0.11575*Y*Y + 0.00337*Y*Y*Y + 0.00078*Y*Y*Y*Y,
|
||||||
|
2451900.05952 + 365242.74049*Y - 0.06223*Y*Y - 0.00823*Y*Y*Y + 0.00032*Y*Y*Y*Y,
|
||||||
|
][season];
|
||||||
|
// Convert JD to JS Date (JD 2440587.5 = Unix epoch)
|
||||||
|
const ms = (jde - 2440587.5) * 86400000;
|
||||||
|
return new Date(ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildFloatingHolidays(year) {
|
||||||
|
const results = [];
|
||||||
|
const add = (date, name, emoji, url) => {
|
||||||
|
results.push({ month: date.getMonth() + 1, day: date.getDate(), name, emoji, url });
|
||||||
|
};
|
||||||
|
add(nthWeekday(year, 1, 3, 1), "MLK Day", "✊", "https://en.wikipedia.org/wiki/Martin_Luther_King_Jr._Day");
|
||||||
|
add(nthWeekday(year, 2, 3, 1), "Presidents Day", "🏛️", "https://en.wikipedia.org/wiki/Presidents%27_Day");
|
||||||
|
add(nthWeekday(year, 5, -1, 1), "Memorial Day", "🎖️", "https://en.wikipedia.org/wiki/Memorial_Day");
|
||||||
|
add(nthWeekday(year, 9, 1, 1), "Labor Day", "🔨", "https://en.wikipedia.org/wiki/Labor_Day");
|
||||||
|
add(nthWeekday(year, 10, 2, 1), "Columbus Day", "⛵", "https://en.wikipedia.org/wiki/Columbus_Day");
|
||||||
|
add(nthWeekday(year, 11, 4, 4), "Thanksgiving", "🦃", "https://en.wikipedia.org/wiki/Thanksgiving_(United_States)");
|
||||||
|
add(easterDate(year), "Easter", "🐣", "https://en.wikipedia.org/wiki/Easter");
|
||||||
|
const shrove = new Date(easterDate(year));
|
||||||
|
shrove.setDate(shrove.getDate() - 47);
|
||||||
|
add(shrove, "Pancake Day", "🥞", "https://en.wikipedia.org/wiki/Shrove_Tuesday");
|
||||||
|
// Solstices & equinoxes
|
||||||
|
add(seasonJD(year, 0), "March Equinox", "🌱", "https://en.wikipedia.org/wiki/March_equinox");
|
||||||
|
add(seasonJD(year, 1), "June Solstice", "☀️", "https://en.wikipedia.org/wiki/June_solstice");
|
||||||
|
add(seasonJD(year, 2), "September Equinox", "🍂", "https://en.wikipedia.org/wiki/September_equinox");
|
||||||
|
add(seasonJD(year, 3), "December Solstice", "❄️", "https://en.wikipedia.org/wiki/December_solstice");
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build a DOY-keyed map of holidays for a given year
|
||||||
|
function buildHolidayMap(year) {
|
||||||
|
const map = {};
|
||||||
|
const cum = [0,31,59,90,120,151,181,212,243,273,304,334];
|
||||||
|
const leap = isLeapYear(year);
|
||||||
|
const addHol = ({ month, day, name, emoji, url }) => {
|
||||||
|
if (month === 2 && day === 29 && !leap) return;
|
||||||
|
let doy = cum[month - 1] + day;
|
||||||
|
if (leap && month > 2) doy++;
|
||||||
|
if (!map[doy]) map[doy] = [];
|
||||||
|
map[doy].push({ name, emoji, url });
|
||||||
|
};
|
||||||
|
FIXED_HOLIDAYS.forEach(addHol);
|
||||||
|
buildFloatingHolidays(year).forEach(addHol);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Seth calendar helpers ---
|
||||||
|
|
||||||
|
function isLeapYear(y) {
|
||||||
|
return (y % 4 === 0 && y % 100 !== 0) || y % 400 === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns { year, doy } for a given JS Date (local time)
|
||||||
|
function getDayOfYear(date) {
|
||||||
|
const y = date.getFullYear();
|
||||||
|
const m = date.getMonth() + 1;
|
||||||
|
const d = date.getDate();
|
||||||
|
const cum = [0,31,59,90,120,151,181,212,243,273,304,334];
|
||||||
|
let doy = cum[m - 1] + d;
|
||||||
|
if (isLeapYear(y) && m > 2) doy++;
|
||||||
|
return { year: y, doy };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a JS Date for day-of-year `doy` in `year`
|
||||||
|
function dateFromDoy(year, doy) {
|
||||||
|
return new Date(year, 0, doy); // Jan 0 + doy = doy-th day
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gregorian month/day string for a doy
|
||||||
|
function gregLabel(year, doy) {
|
||||||
|
const d = dateFromDoy(year, doy);
|
||||||
|
return `${GREG_MONTHS[d.getMonth()]} ${d.getDate()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Holiday names (0-indexed: n = 0..4 or 0..5)
|
||||||
|
function holidayName(n, leap) {
|
||||||
|
const lastIdx = leap ? 5 : 4;
|
||||||
|
const names = [
|
||||||
|
leap ? "Holiday 0 — Boxing Day" : "Holiday 0",
|
||||||
|
"Holiday 1",
|
||||||
|
"Holiday 2",
|
||||||
|
"Holiday 3",
|
||||||
|
leap ? "Holiday 4" : "Holiday 4 — New Year's Eve",
|
||||||
|
"Holiday 5 — New Year's Eve", // leap only
|
||||||
|
];
|
||||||
|
return names[n] || `Holiday ${n}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- State ---
|
||||||
|
|
||||||
|
const today = new Date();
|
||||||
|
const todayDoy = getDayOfYear(today);
|
||||||
|
|
||||||
|
let viewYear = todayDoy.year;
|
||||||
|
let holidayMap = buildHolidayMap(viewYear);
|
||||||
|
let viewMonth = (() => {
|
||||||
|
// Start on today's Seth month (or holiday block)
|
||||||
|
const doy = todayDoy.doy;
|
||||||
|
return doy <= 360 ? Math.floor((doy - 1) / 36) : 10; // 10 = holidays
|
||||||
|
})();
|
||||||
|
|
||||||
|
// --- DOM ---
|
||||||
|
|
||||||
|
const navTitle = document.getElementById("navTitle");
|
||||||
|
const navSub = document.getElementById("navSub");
|
||||||
|
const calGrid = document.getElementById("calGrid");
|
||||||
|
const monthTabs = document.getElementById("monthTabs");
|
||||||
|
|
||||||
|
document.getElementById("prevYear").addEventListener("click", () => { viewYear--; holidayMap = buildHolidayMap(viewYear); render(); });
|
||||||
|
document.getElementById("nextYear").addEventListener("click", () => { viewYear++; holidayMap = buildHolidayMap(viewYear); render(); });
|
||||||
|
document.getElementById("prevMonth").addEventListener("click", () => { stepMonth(-1); render(); });
|
||||||
|
document.getElementById("nextMonth").addEventListener("click", () => { stepMonth(+1); render(); });
|
||||||
|
|
||||||
|
function stepMonth(dir) {
|
||||||
|
viewMonth += dir;
|
||||||
|
if (viewMonth < 0) { viewMonth = 10; viewYear--; holidayMap = buildHolidayMap(viewYear); }
|
||||||
|
if (viewMonth > 10) { viewMonth = 0; viewYear++; holidayMap = buildHolidayMap(viewYear); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Render ---
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
renderTabs();
|
||||||
|
if (viewMonth <= 9) {
|
||||||
|
renderMonth(viewYear, viewMonth);
|
||||||
|
} else {
|
||||||
|
renderHolidays(viewYear);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderTabs() {
|
||||||
|
monthTabs.innerHTML = "";
|
||||||
|
for (let m = 0; m <= 9; m++) {
|
||||||
|
const tab = document.createElement("button");
|
||||||
|
tab.className = "month-tab" + (m === viewMonth ? " active" : "");
|
||||||
|
tab.textContent = `Month ${m}`;
|
||||||
|
tab.addEventListener("click", () => { viewMonth = m; render(); });
|
||||||
|
monthTabs.appendChild(tab);
|
||||||
|
}
|
||||||
|
const htab = document.createElement("button");
|
||||||
|
htab.className = "month-tab holiday-tab" + (viewMonth === 10 ? " active" : "");
|
||||||
|
htab.textContent = "Holidays";
|
||||||
|
htab.addEventListener("click", () => { viewMonth = 10; render(); });
|
||||||
|
monthTabs.appendChild(htab);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderMonth(year, month) {
|
||||||
|
const leap = isLeapYear(year);
|
||||||
|
const monthStart = month * 36 + 1; // doy of Month N Day 0 (N=0..9)
|
||||||
|
|
||||||
|
// Approx gregorian range for subtitle
|
||||||
|
const gStart = gregLabel(year, monthStart);
|
||||||
|
const gEnd = gregLabel(year, monthStart + 35);
|
||||||
|
|
||||||
|
navTitle.childNodes[0].textContent = `${year} · Month ${month}`;
|
||||||
|
navSub.textContent = `${gStart} – ${gEnd}`;
|
||||||
|
|
||||||
|
// Build grid
|
||||||
|
const grid = document.createElement("div");
|
||||||
|
grid.className = "cal-grid";
|
||||||
|
|
||||||
|
// Column headers: D0 D1 D2 D3 D4 D5
|
||||||
|
for (const lbl of WEEK_DAY_LABELS) {
|
||||||
|
const h = document.createElement("div");
|
||||||
|
h.className = "col-header";
|
||||||
|
h.textContent = lbl;
|
||||||
|
grid.appendChild(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
// In a leap year, M1 W3 D4 is split: top=Feb28(DOY59), bottom=LeapDay(DOY60)
|
||||||
|
// All cells from dayInMonth>=23 (W3 D5 onward) get doy+1 to skip over leap day
|
||||||
|
const isLeapM1 = leap && month === 1;
|
||||||
|
|
||||||
|
for (let week = 0; week <= 5; week++) {
|
||||||
|
for (let wd = 0; wd <= 5; wd++) {
|
||||||
|
const dayInMonth = week * 6 + wd; // 0..35
|
||||||
|
// In leap M1, dayInMonth 22 = D4 W3 = Feb28/LeapDay split cell
|
||||||
|
// dayInMonth 23+ shift by +1 to account for leap day
|
||||||
|
const doy = monthStart + dayInMonth + (isLeapM1 && dayInMonth >= 23 ? 1 : 0);
|
||||||
|
const isLeapSplit = isLeapM1 && dayInMonth === 22; // the split cell
|
||||||
|
|
||||||
|
const cell = document.createElement("div");
|
||||||
|
cell.className = "cal-cell";
|
||||||
|
let topHalf = null; // only set for split cells; used by holiday marker logic below
|
||||||
|
|
||||||
|
// Weekend coloring
|
||||||
|
if (wd === 4 || wd === 5) cell.classList.add("seth-weekend");
|
||||||
|
|
||||||
|
// For the split cell, gregDate is Feb 28 (the top half)
|
||||||
|
const gregDate = dateFromDoy(year, doy);
|
||||||
|
const gregWd = GREG_WEEKDAYS[gregDate.getDay()];
|
||||||
|
const gregDay = gregDate.getDay(); // 0=Sun, 6=Sat
|
||||||
|
if (gregDay === 0 || gregDay === 6) cell.classList.add("greg-weekend");
|
||||||
|
|
||||||
|
// Highlight today (today could be Feb 28 or Feb 29 in this cell)
|
||||||
|
const leapDoyTop = doy; // Feb 28
|
||||||
|
const leapDoyBot = doy + 1; // Feb 29 (leap day)
|
||||||
|
const isToday = year === todayDoy.year &&
|
||||||
|
(todayDoy.doy === doy || (isLeapSplit && todayDoy.doy === leapDoyBot));
|
||||||
|
if (isToday) cell.classList.add("today");
|
||||||
|
|
||||||
|
const astroParam = `${gregDate.getFullYear()}-${String(gregDate.getMonth()+1).padStart(2,"0")}-${String(gregDate.getDate()).padStart(2,"0")}`;
|
||||||
|
|
||||||
|
if (isLeapSplit) {
|
||||||
|
// Split cell: transparent outer wrapper containing two independent mini-cells
|
||||||
|
cell.classList.add("has-leap-split");
|
||||||
|
// Remove border/bg classes — the outer cell is invisible; halves handle their own styling
|
||||||
|
cell.classList.remove("seth-weekend", "greg-weekend", "today");
|
||||||
|
|
||||||
|
// ── Top half: normal D4 (Feb 28), Seth weekend ──
|
||||||
|
topHalf = document.createElement("div");
|
||||||
|
topHalf.className = "leap-top";
|
||||||
|
const isTopToday = year === todayDoy.year && todayDoy.doy === doy;
|
||||||
|
if (isTopToday) topHalf.classList.add("leap-top-today");
|
||||||
|
|
||||||
|
const topMoonLink = document.createElement("a");
|
||||||
|
topMoonLink.className = "moon-link";
|
||||||
|
topMoonLink.href = `/astro?date=${astroParam}`;
|
||||||
|
topMoonLink.title = `Astronomy for ${gregLabel(year, doy)}`;
|
||||||
|
topMoonLink.appendChild(makeMoonCanvas(gregDate, 14));
|
||||||
|
topHalf.appendChild(topMoonLink);
|
||||||
|
|
||||||
|
const topNum = document.createElement("div");
|
||||||
|
topNum.className = "seth-day";
|
||||||
|
topNum.textContent = dayInMonth;
|
||||||
|
const topGreg = document.createElement("div");
|
||||||
|
topGreg.className = "greg-date";
|
||||||
|
topGreg.textContent = `${gregWd} ${gregLabel(year, doy)}`;
|
||||||
|
const topWd = document.createElement("div");
|
||||||
|
topWd.className = "greg-date";
|
||||||
|
topWd.style.color = "#555";
|
||||||
|
topWd.textContent = `W${week} D${wd}`;
|
||||||
|
topHalf.appendChild(topNum);
|
||||||
|
topHalf.appendChild(topGreg);
|
||||||
|
topHalf.appendChild(topWd);
|
||||||
|
cell.appendChild(topHalf);
|
||||||
|
|
||||||
|
// ── Bottom half: Leap Day (Feb 29), its own independent mini-cell ──
|
||||||
|
const botHalf = document.createElement("div");
|
||||||
|
botHalf.className = "leap-bottom";
|
||||||
|
const isLeapToday = year === todayDoy.year && todayDoy.doy === 60;
|
||||||
|
if (isLeapToday) botHalf.classList.add("leap-today");
|
||||||
|
|
||||||
|
const leapDate = new Date(year, 1, 29); // Feb 29
|
||||||
|
const leapAstroParam = `${year}-02-29`;
|
||||||
|
const botMoonLink = document.createElement("a");
|
||||||
|
botMoonLink.className = "moon-link";
|
||||||
|
botMoonLink.href = `/astro?date=${leapAstroParam}`;
|
||||||
|
botMoonLink.title = "Astronomy for Feb 29";
|
||||||
|
botMoonLink.appendChild(makeMoonCanvas(leapDate, 14));
|
||||||
|
botHalf.appendChild(botMoonLink);
|
||||||
|
|
||||||
|
const botNum = document.createElement("div");
|
||||||
|
botNum.className = "seth-day";
|
||||||
|
botNum.textContent = "Leap Day";
|
||||||
|
const botGreg = document.createElement("div");
|
||||||
|
botGreg.className = "greg-date";
|
||||||
|
const leapGregWd = GREG_WEEKDAYS[leapDate.getDay()];
|
||||||
|
botGreg.textContent = `${leapGregWd} Feb 29`;
|
||||||
|
botHalf.appendChild(botNum);
|
||||||
|
botHalf.appendChild(botGreg);
|
||||||
|
cell.appendChild(botHalf);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Normal cell
|
||||||
|
const moonLink = document.createElement("a");
|
||||||
|
moonLink.className = "moon-link";
|
||||||
|
moonLink.href = `/astro?date=${astroParam}`;
|
||||||
|
moonLink.title = `Astronomy for ${gregLabel(year, doy)}`;
|
||||||
|
moonLink.appendChild(makeMoonCanvas(gregDate, 14));
|
||||||
|
cell.appendChild(moonLink);
|
||||||
|
|
||||||
|
const dayNum = document.createElement("div");
|
||||||
|
dayNum.className = "seth-day";
|
||||||
|
dayNum.textContent = dayInMonth;
|
||||||
|
|
||||||
|
const greg = document.createElement("div");
|
||||||
|
greg.className = "greg-date";
|
||||||
|
greg.textContent = `${gregWd} ${gregLabel(year, doy)}`;
|
||||||
|
|
||||||
|
const wdLabel = document.createElement("div");
|
||||||
|
wdLabel.className = "greg-date";
|
||||||
|
wdLabel.style.color = "#555";
|
||||||
|
wdLabel.textContent = `W${week} D${wd}`;
|
||||||
|
|
||||||
|
cell.appendChild(dayNum);
|
||||||
|
cell.appendChild(greg);
|
||||||
|
cell.appendChild(wdLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Holiday markers — for split cells, attach to topHalf (Feb 28); otherwise to cell
|
||||||
|
const holTarget = isLeapSplit ? topHalf : cell;
|
||||||
|
const hols = holidayMap[doy];
|
||||||
|
if (hols) {
|
||||||
|
holTarget.classList.add("has-holiday");
|
||||||
|
hols.forEach(({ name, emoji, url }) => {
|
||||||
|
const hl = document.createElement("div");
|
||||||
|
hl.className = "hol-label";
|
||||||
|
if (url) {
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.href = url;
|
||||||
|
a.target = "_blank";
|
||||||
|
a.rel = "noopener noreferrer";
|
||||||
|
a.textContent = `${emoji} ${name}`;
|
||||||
|
hl.appendChild(a);
|
||||||
|
} else {
|
||||||
|
hl.textContent = `${emoji} ${name}`;
|
||||||
|
}
|
||||||
|
holTarget.appendChild(hl);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
grid.appendChild(cell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
calGrid.innerHTML = "";
|
||||||
|
calGrid.appendChild(grid);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderHolidays(year) {
|
||||||
|
const leap = isLeapYear(year);
|
||||||
|
const count = leap ? 6 : 5;
|
||||||
|
const doyBase = 361; // H0 = doy 361
|
||||||
|
|
||||||
|
navTitle.childNodes[0].textContent = `${year} · Holidays`;
|
||||||
|
navSub.textContent = `${gregLabel(year, doyBase)} – ${gregLabel(year, doyBase + count - 1)}`;
|
||||||
|
|
||||||
|
const grid = document.createElement("div");
|
||||||
|
grid.className = "cal-grid holiday-grid";
|
||||||
|
|
||||||
|
for (let h = 0; h < count; h++) {
|
||||||
|
const doy = doyBase + h;
|
||||||
|
const isToday = year === todayDoy.year && doy === todayDoy.doy;
|
||||||
|
|
||||||
|
const cell = document.createElement("div");
|
||||||
|
cell.className = "cal-cell holiday-cell";
|
||||||
|
if (isToday) cell.classList.add("today");
|
||||||
|
|
||||||
|
const gregDate = dateFromDoy(year, doy);
|
||||||
|
const gregWd = GREG_WEEKDAYS[gregDate.getDay()];
|
||||||
|
const astroParam = `${gregDate.getFullYear()}-${String(gregDate.getMonth()+1).padStart(2,"0")}-${String(gregDate.getDate()).padStart(2,"0")}`;
|
||||||
|
|
||||||
|
const moonLink = document.createElement("a");
|
||||||
|
moonLink.className = "moon-link";
|
||||||
|
moonLink.href = `/astro?date=${astroParam}`;
|
||||||
|
moonLink.title = `Astronomy for ${gregLabel(year, doy)}`;
|
||||||
|
moonLink.appendChild(makeMoonCanvas(gregDate, 14));
|
||||||
|
cell.appendChild(moonLink);
|
||||||
|
|
||||||
|
const hNum = document.createElement("div");
|
||||||
|
hNum.className = "seth-day";
|
||||||
|
hNum.textContent = `H${h}`;
|
||||||
|
|
||||||
|
const greg = document.createElement("div");
|
||||||
|
greg.className = "greg-date";
|
||||||
|
greg.textContent = `${gregWd} ${gregLabel(year, doy)}`;
|
||||||
|
|
||||||
|
const name = document.createElement("div");
|
||||||
|
name.className = "holiday-name";
|
||||||
|
name.textContent = holidayName(h, leap);
|
||||||
|
|
||||||
|
cell.appendChild(hNum);
|
||||||
|
cell.appendChild(greg);
|
||||||
|
cell.appendChild(name);
|
||||||
|
grid.appendChild(cell);
|
||||||
|
}
|
||||||
|
|
||||||
|
calGrid.innerHTML = "";
|
||||||
|
calGrid.appendChild(grid);
|
||||||
|
}
|
||||||
|
|
||||||
|
render();
|
||||||
|
|
||||||
|
// "Today" legend link — jumps back to today's month/year
|
||||||
|
document.getElementById("todayLink").addEventListener("click", e => {
|
||||||
|
e.preventDefault();
|
||||||
|
viewYear = todayDoy.year;
|
||||||
|
viewMonth = todayDoy.doy <= 360 ? Math.floor((todayDoy.doy - 1) / 36) : 10;
|
||||||
|
holidayMap = buildHolidayMap(viewYear);
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Draw sample moon in legend (waxing gibbous ~0.7 illumination)
|
||||||
|
const legendMoon = document.getElementById("legendMoon");
|
||||||
|
if (legendMoon) drawMoon(legendMoon, 0.7, true);
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Clock site web service
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
WorkingDirectory=/opt/clock-site
|
||||||
|
ExecStart=/usr/bin/python3 /opt/clock-site/server.py
|
||||||
|
Restart=always
|
||||||
|
RestartSec=2
|
||||||
|
User=root
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>SethPC Time Converter</title>
|
||||||
|
<link rel="icon" type="image/png" href="https://storage.googleapis.com/sethfreiberg.com/sethflix/favicon.png">
|
||||||
|
<link rel="stylesheet" href="/style.css">
|
||||||
|
<style>
|
||||||
|
.convert-wrap {
|
||||||
|
width: min(520px, 100%);
|
||||||
|
background: var(--panel);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: clamp(1rem, 2.5vw, 2rem);
|
||||||
|
box-shadow: 0 10px 28px rgb(0 0 0 / 42%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.converter {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.conv-panel {
|
||||||
|
background: #222;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1rem 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.conv-label {
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--accent);
|
||||||
|
margin-bottom: 0.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-inputs {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.3rem;
|
||||||
|
font-family: "JetBrains Mono", monospace;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-inputs input {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 2px solid var(--border);
|
||||||
|
color: var(--text);
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
text-align: center;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.15s;
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
}
|
||||||
|
.time-inputs input::-webkit-outer-spin-button,
|
||||||
|
.time-inputs input::-webkit-inner-spin-button { -webkit-appearance: none; }
|
||||||
|
.time-inputs input:focus { border-color: var(--accent); }
|
||||||
|
.time-inputs input.w2 { width: 2.2ch; }
|
||||||
|
.time-inputs input.w3 { width: 2.6ch; }
|
||||||
|
.time-inputs .sep { color: var(--muted); user-select: none; }
|
||||||
|
|
||||||
|
.conv-sub {
|
||||||
|
font-size: 0.78rem;
|
||||||
|
color: var(--muted);
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
font-family: "JetBrains Mono", monospace;
|
||||||
|
min-height: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.now-btn {
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: #2a2a2a;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: var(--muted);
|
||||||
|
padding: 0.3rem 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
transition: all 0.15s;
|
||||||
|
}
|
||||||
|
.now-btn:hover { border-color: var(--accent); color: var(--accent); }
|
||||||
|
|
||||||
|
.ratio-note {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--muted);
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 1rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class="convert-wrap">
|
||||||
|
<div class="brand-row">
|
||||||
|
<img class="logo-img" src="https://storage.googleapis.com/sethfreiberg.com/sethflix/favicon.png" alt="SethPC logo">
|
||||||
|
<span class="brand-text">Time Converter</span>
|
||||||
|
</div>
|
||||||
|
<p class="kicker">Decimal ↔ Gregorian · <a href="/seth" style="color:var(--accent);text-decoration:none;font-size:0.85rem;">Seth Date →</a></p>
|
||||||
|
|
||||||
|
<button class="now-btn" id="nowBtn">Use current time</button>
|
||||||
|
|
||||||
|
<div class="converter">
|
||||||
|
<div class="conv-panel" id="gregPanel">
|
||||||
|
<div class="conv-label">Gregorian (24h)</div>
|
||||||
|
<div class="time-inputs">
|
||||||
|
<input id="gH" class="w2" type="number" min="0" max="23" placeholder="HH">
|
||||||
|
<span class="sep">:</span>
|
||||||
|
<input id="gM" class="w2" type="number" min="0" max="59" placeholder="MM">
|
||||||
|
<span class="sep">:</span>
|
||||||
|
<input id="gS" class="w2" type="number" min="0" max="59" placeholder="SS">
|
||||||
|
<span class="sep">.</span>
|
||||||
|
<input id="gCs" class="w2" type="number" min="0" max="99" placeholder="cs">
|
||||||
|
</div>
|
||||||
|
<div class="conv-sub" id="gregFrac"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="arrow">⇅</div>
|
||||||
|
|
||||||
|
<div class="conv-panel" id="decPanel">
|
||||||
|
<div class="conv-label">Decimal (Seth)</div>
|
||||||
|
<div class="time-inputs">
|
||||||
|
<input id="dH" class="w2" type="number" min="0" max="9" placeholder="H">
|
||||||
|
<span class="sep">:</span>
|
||||||
|
<input id="dM" class="w2" type="number" min="0" max="99" placeholder="MM">
|
||||||
|
<span class="sep">:</span>
|
||||||
|
<input id="dS" class="w2" type="number" min="0" max="99" placeholder="SS">
|
||||||
|
<span class="sep">.</span>
|
||||||
|
<input id="dCs" class="w3" type="number" min="0" max="99" placeholder="cs">
|
||||||
|
</div>
|
||||||
|
<div class="conv-sub" id="decFrac"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="ratio-note">
|
||||||
|
1 decimal hour = 2h 24m Gregorian ·
|
||||||
|
1 decimal second = 0.864 SI seconds
|
||||||
|
</p>
|
||||||
|
</main>
|
||||||
|
<script src="/convert.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
// Seth Time Converter
|
||||||
|
// Decimal time: 10 hours/day, 100 minutes/hour, 100 seconds/minute, 100 centiseconds/second
|
||||||
|
// Gregorian: 24 hours/day, 60 minutes/hour, 60 seconds/minute, 100 centiseconds/second
|
||||||
|
//
|
||||||
|
// Conversion: day fraction is the common unit
|
||||||
|
// Gregorian -> fraction: (H*3600 + M*60 + S + cs/100) / 86400
|
||||||
|
// Decimal -> fraction: (H*10000 + M*100 + S + cs/100) / 100000
|
||||||
|
|
||||||
|
const MS_PER_DAY = 86400000;
|
||||||
|
|
||||||
|
function gregToFraction(h, m, s, cs) {
|
||||||
|
return (h * 3600 + m * 60 + s + cs / 100) / 86400;
|
||||||
|
}
|
||||||
|
|
||||||
|
function decToFraction(h, m, s, cs) {
|
||||||
|
return (h * 10000 + m * 100 + s + cs / 100) / 100000;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fractionToGreg(f) {
|
||||||
|
f = ((f % 1) + 1) % 1; // clamp 0..1
|
||||||
|
const totalCs = Math.round(f * 86400 * 100);
|
||||||
|
const cs = totalCs % 100;
|
||||||
|
const totalS = Math.floor(totalCs / 100);
|
||||||
|
const s = totalS % 60;
|
||||||
|
const totalM = Math.floor(totalS / 60);
|
||||||
|
const m = totalM % 60;
|
||||||
|
const h = Math.floor(totalM / 60) % 24;
|
||||||
|
return { h, m, s, cs };
|
||||||
|
}
|
||||||
|
|
||||||
|
function fractionToDec(f) {
|
||||||
|
f = ((f % 1) + 1) % 1;
|
||||||
|
const totalCs = Math.round(f * 100000 * 100);
|
||||||
|
const cs = totalCs % 100;
|
||||||
|
const totalS = Math.floor(totalCs / 100);
|
||||||
|
const s = totalS % 100;
|
||||||
|
const totalM = Math.floor(totalS / 100);
|
||||||
|
const m = totalM % 100;
|
||||||
|
const h = Math.floor(totalM / 100) % 10;
|
||||||
|
return { h, m, s, cs };
|
||||||
|
}
|
||||||
|
|
||||||
|
function pad(n, w) { return String(n).padStart(w, "0"); }
|
||||||
|
|
||||||
|
// DOM refs
|
||||||
|
const gH = document.getElementById("gH");
|
||||||
|
const gM = document.getElementById("gM");
|
||||||
|
const gS = document.getElementById("gS");
|
||||||
|
const gCs = document.getElementById("gCs");
|
||||||
|
const dH = document.getElementById("dH");
|
||||||
|
const dM = document.getElementById("dM");
|
||||||
|
const dS = document.getElementById("dS");
|
||||||
|
const dCs = document.getElementById("dCs");
|
||||||
|
const gregFrac = document.getElementById("gregFrac");
|
||||||
|
const decFrac = document.getElementById("decFrac");
|
||||||
|
|
||||||
|
let updating = false; // prevent feedback loops
|
||||||
|
|
||||||
|
function setGreg(h, m, s, cs) {
|
||||||
|
gH.value = pad(h, 2);
|
||||||
|
gM.value = pad(m, 2);
|
||||||
|
gS.value = pad(s, 2);
|
||||||
|
gCs.value = pad(cs, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setDec(h, m, s, cs) {
|
||||||
|
dH.value = h;
|
||||||
|
dM.value = pad(m, 2);
|
||||||
|
dS.value = pad(s, 2);
|
||||||
|
dCs.value = pad(cs, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFromGreg() {
|
||||||
|
if (updating) return;
|
||||||
|
updating = true;
|
||||||
|
const h = parseInt(gH.value) || 0;
|
||||||
|
const m = parseInt(gM.value) || 0;
|
||||||
|
const s = parseInt(gS.value) || 0;
|
||||||
|
const cs = parseInt(gCs.value) || 0;
|
||||||
|
const f = gregToFraction(h, m, s, cs);
|
||||||
|
const dec = fractionToDec(f);
|
||||||
|
setDec(dec.h, dec.m, dec.s, dec.cs);
|
||||||
|
gregFrac.textContent = `day fraction: ${f.toFixed(8)}`;
|
||||||
|
decFrac.textContent = `day fraction: ${f.toFixed(8)}`;
|
||||||
|
updating = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFromDec() {
|
||||||
|
if (updating) return;
|
||||||
|
updating = true;
|
||||||
|
const h = parseInt(dH.value) || 0;
|
||||||
|
const m = parseInt(dM.value) || 0;
|
||||||
|
const s = parseInt(dS.value) || 0;
|
||||||
|
const cs = parseInt(dCs.value) || 0;
|
||||||
|
const f = decToFraction(h, m, s, cs);
|
||||||
|
const greg = fractionToGreg(f);
|
||||||
|
setGreg(greg.h, greg.m, greg.s, greg.cs);
|
||||||
|
gregFrac.textContent = `day fraction: ${f.toFixed(8)}`;
|
||||||
|
decFrac.textContent = `day fraction: ${f.toFixed(8)}`;
|
||||||
|
updating = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clamp inputs on change
|
||||||
|
function clampInput(el, min, max) {
|
||||||
|
el.addEventListener("change", () => {
|
||||||
|
let v = parseInt(el.value);
|
||||||
|
if (isNaN(v)) v = min;
|
||||||
|
el.value = Math.max(min, Math.min(max, v));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
clampInput(gH, 0, 23); clampInput(gM, 0, 59);
|
||||||
|
clampInput(gS, 0, 59); clampInput(gCs, 0, 99);
|
||||||
|
clampInput(dH, 0, 9); clampInput(dM, 0, 99);
|
||||||
|
clampInput(dS, 0, 99); clampInput(dCs, 0, 99);
|
||||||
|
|
||||||
|
// Tab through fields on Enter or arrow keys within a panel
|
||||||
|
function setupNavigation(inputs) {
|
||||||
|
inputs.forEach((el, i) => {
|
||||||
|
el.addEventListener("keydown", e => {
|
||||||
|
if (e.key === "ArrowRight" || e.key === "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
|
inputs[(i + 1) % inputs.length].focus();
|
||||||
|
inputs[(i + 1) % inputs.length].select();
|
||||||
|
}
|
||||||
|
if (e.key === "ArrowLeft") {
|
||||||
|
e.preventDefault();
|
||||||
|
inputs[(i - 1 + inputs.length) % inputs.length].focus();
|
||||||
|
inputs[(i - 1 + inputs.length) % inputs.length].select();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setupNavigation([gH, gM, gS, gCs]);
|
||||||
|
setupNavigation([dH, dM, dS, dCs]);
|
||||||
|
|
||||||
|
[gH, gM, gS, gCs].forEach(el => el.addEventListener("input", updateFromGreg));
|
||||||
|
[dH, dM, dS, dCs].forEach(el => el.addEventListener("input", updateFromDec));
|
||||||
|
|
||||||
|
// Now button — fill with current time and convert
|
||||||
|
document.getElementById("nowBtn").addEventListener("click", () => {
|
||||||
|
const now = new Date();
|
||||||
|
setGreg(now.getHours(), now.getMinutes(), now.getSeconds(), Math.floor(now.getMilliseconds() / 10));
|
||||||
|
updateFromGreg();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Init with current time
|
||||||
|
const now = new Date();
|
||||||
|
setGreg(now.getHours(), now.getMinutes(), now.getSeconds(), Math.floor(now.getMilliseconds() / 10));
|
||||||
|
updateFromGreg();
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>SethPC Decimal Time</title>
|
||||||
|
<link rel="icon" type="image/png" href="https://storage.googleapis.com/sethfreiberg.com/sethflix/favicon.png">
|
||||||
|
<link rel="stylesheet" href="/style.css">
|
||||||
|
<style>
|
||||||
|
.decimal-note {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--muted);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
.decimal-note a {
|
||||||
|
color: var(--accent);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.decimal-note a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
.meta-grid .label-col {
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
.ref-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
margin: 0.4rem 0 0.9rem;
|
||||||
|
}
|
||||||
|
.ref-table th {
|
||||||
|
text-align: left;
|
||||||
|
color: var(--accent);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
padding: 0.2rem 0.5rem 0.2rem 0;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.ref-table td {
|
||||||
|
padding: 0.15rem 0.5rem 0.15rem 0;
|
||||||
|
color: var(--muted);
|
||||||
|
border-bottom: 1px solid #2a2a2a;
|
||||||
|
}
|
||||||
|
.ref-table td:first-child {
|
||||||
|
color: var(--text);
|
||||||
|
width: 2rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class="wrap">
|
||||||
|
<div class="brand-row">
|
||||||
|
<img class="logo-img" src="https://storage.googleapis.com/sethfreiberg.com/sethflix/favicon.png" alt="SethPC logo">
|
||||||
|
<span class="brand-text">SethPC Decimal Time</span>
|
||||||
|
</div>
|
||||||
|
<p class="kicker">French Republican / Decimal Time & Date</p>
|
||||||
|
<h1 id="dateLine">Loading date...</h1>
|
||||||
|
<p id="dateTrans" class="decimal-note" style="margin:0 0 0.6rem; font-size:0.9rem;"></p>
|
||||||
|
<p id="digital" class="digital">-:--.--</p>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
<label class="zone-picker" for="zoneSelect">Display Time Zone</label>
|
||||||
|
<select id="zoneSelect"></select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="decimal-note">
|
||||||
|
<p>
|
||||||
|
<strong>Decimal time</strong> divides the day into 10 hours, each hour into 100 minutes,
|
||||||
|
and each minute into 100 seconds. The time shown counts from local midnight in the selected zone.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The date uses the <strong>French Republican Calendar</strong>, introduced in 1793.
|
||||||
|
The year begins at the autumnal equinox (~22 September). Each year has 12 months of exactly
|
||||||
|
30 days, plus 5 complementary days at year end (6 in a leap year). Years are numbered in
|
||||||
|
Roman numerals from An I (1792).
|
||||||
|
</p>
|
||||||
|
<p><strong>Reading the date:</strong> dates are written as
|
||||||
|
<em>Weekday, Day Month An Year</em> — e.g. <em>Sextidi, 6 Ventôse An CCXXXIII</em>
|
||||||
|
means the 6th day of the month, in the 6th day of the décade (10-day week), in the month of Ventôse, year 233.
|
||||||
|
</p>
|
||||||
|
<p><strong>Months</strong> (each 30 days, grouped by season):</p>
|
||||||
|
<table class="ref-table">
|
||||||
|
<thead><tr><th>#</th><th>French</th><th>English</th><th>Approx. Gregorian</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>1</td><td>Vendémiaire</td><td>Vintage</td><td>Sep 22 – Oct 21</td></tr>
|
||||||
|
<tr><td>2</td><td>Brumaire</td><td>Mist</td><td>Oct 22 – Nov 20</td></tr>
|
||||||
|
<tr><td>3</td><td>Frimaire</td><td>Frost</td><td>Nov 21 – Dec 20</td></tr>
|
||||||
|
<tr><td>4</td><td>Nivôse</td><td>Snowy</td><td>Dec 21 – Jan 19</td></tr>
|
||||||
|
<tr><td>5</td><td>Pluviôse</td><td>Rainy</td><td>Jan 20 – Feb 18</td></tr>
|
||||||
|
<tr><td>6</td><td>Ventôse</td><td>Windy</td><td>Feb 19 – Mar 20</td></tr>
|
||||||
|
<tr><td>7</td><td>Germinal</td><td>Budding</td><td>Mar 21 – Apr 19</td></tr>
|
||||||
|
<tr><td>8</td><td>Floréal</td><td>Flowery</td><td>Apr 20 – May 19</td></tr>
|
||||||
|
<tr><td>9</td><td>Prairial</td><td>Meadow</td><td>May 20 – Jun 18</td></tr>
|
||||||
|
<tr><td>10</td><td>Messidor</td><td>Harvest</td><td>Jun 19 – Jul 18</td></tr>
|
||||||
|
<tr><td>11</td><td>Thermidor</td><td>Heat</td><td>Jul 19 – Aug 17</td></tr>
|
||||||
|
<tr><td>12</td><td>Fructidor</td><td>Fruit</td><td>Aug 18 – Sep 16</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p><strong>Weekdays</strong> (the 10-day décade):</p>
|
||||||
|
<table class="ref-table">
|
||||||
|
<thead><tr><th>Day</th><th>French</th><th>English</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>1</td><td>Primidi</td><td>First day</td></tr>
|
||||||
|
<tr><td>2</td><td>Duodi</td><td>Second day</td></tr>
|
||||||
|
<tr><td>3</td><td>Tridi</td><td>Third day</td></tr>
|
||||||
|
<tr><td>4</td><td>Quartidi</td><td>Fourth day</td></tr>
|
||||||
|
<tr><td>5</td><td>Quintidi</td><td>Fifth day</td></tr>
|
||||||
|
<tr><td>6</td><td>Sextidi</td><td>Sixth day</td></tr>
|
||||||
|
<tr><td>7</td><td>Septidi</td><td>Seventh day</td></tr>
|
||||||
|
<tr><td>8</td><td>Octidi</td><td>Eighth day</td></tr>
|
||||||
|
<tr><td>9</td><td>Nonidi</td><td>Ninth day</td></tr>
|
||||||
|
<tr><td>10</td><td>Décadi</td><td>Tenth day (rest day)</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p><strong>Complementary days</strong> (jours complémentaires, after Fructidor):</p>
|
||||||
|
<table class="ref-table">
|
||||||
|
<thead><tr><th>Day</th><th>French</th><th>English</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>1</td><td>Jour de la Vertu</td><td>Day of Virtue</td></tr>
|
||||||
|
<tr><td>2</td><td>Jour du Génie</td><td>Day of Genius</td></tr>
|
||||||
|
<tr><td>3</td><td>Jour du Travail</td><td>Day of Labour</td></tr>
|
||||||
|
<tr><td>4</td><td>Jour de l'Opinion</td><td>Day of Opinion</td></tr>
|
||||||
|
<tr><td>5</td><td>Jour des Récompenses</td><td>Day of Rewards</td></tr>
|
||||||
|
<tr><td>6*</td><td>Jour de la Révolution</td><td>Day of the Revolution</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p style="font-size:0.8rem;">* Leap years only.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="meta-grid">
|
||||||
|
<p><strong>Decimal Time:</strong> <span id="decimalUtc">-</span></p>
|
||||||
|
<p><strong>Day of Year:</strong> <span id="dayOfYear">-</span></p>
|
||||||
|
<p><strong>Republican Year:</strong> <span id="repYear">-</span></p>
|
||||||
|
<p><strong>Time Zone:</strong> <span id="zoneLabel">-</span></p>
|
||||||
|
<p><strong>Status:</strong> <span id="offsetLabel">calibrating...</span></p>
|
||||||
|
<p><strong>Gregorian UTC:</strong> <span id="utcLabel">-</span></p>
|
||||||
|
<p><strong>Source:</strong> Hosted by SethPC</p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<script src="/decimal.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,280 @@
|
|||||||
|
// Decimal time & French Republican Calendar
|
||||||
|
//
|
||||||
|
// Decimal time: 1 day = 10 decimal hours
|
||||||
|
// 1 decimal hour = 100 decimal minutes
|
||||||
|
// 1 decimal minute = 100 decimal seconds
|
||||||
|
//
|
||||||
|
// French Republican Calendar epoch: 22 September 1792 (Gregorian) = 1 Vendémiaire An I
|
||||||
|
// Each year has 12 months of 30 days + 5 (or 6 in sextile years) complementary days.
|
||||||
|
// Sextile (leap) years: the Republican leap rule approximates the Gregorian one —
|
||||||
|
// years 3, 7, 11, 15, 20 of each 20-year cycle were designated sextile in the original
|
||||||
|
// Romme proposal. For simplicity we use the widely accepted rule:
|
||||||
|
// A Republican year is sextile if the *following* Gregorian year is a Gregorian leap year.
|
||||||
|
|
||||||
|
const REPUBLICAN_EPOCH_JD = 2375840; // Julian Day Number of 1 Vendémiaire An I (22 Sep 1792)
|
||||||
|
|
||||||
|
const MONTH_NAMES = [
|
||||||
|
"Vendémiaire", "Brumaire", "Frimaire",
|
||||||
|
"Nivôse", "Pluviôse", "Ventôse",
|
||||||
|
"Germinal", "Floréal", "Prairial",
|
||||||
|
"Messidor", "Thermidor", "Fructidor"
|
||||||
|
];
|
||||||
|
|
||||||
|
const MONTH_NAMES_EN = [
|
||||||
|
"Vintage", "Mist", "Frost",
|
||||||
|
"Snowy", "Rainy", "Windy",
|
||||||
|
"Budding", "Flowery", "Meadow",
|
||||||
|
"Harvest", "Heat", "Fruit"
|
||||||
|
];
|
||||||
|
|
||||||
|
const COMPLEMENTARY_NAMES = [
|
||||||
|
"Jour de la Vertu", // 1
|
||||||
|
"Jour du Génie", // 2
|
||||||
|
"Jour du Travail", // 3
|
||||||
|
"Jour de l'Opinion", // 4
|
||||||
|
"Jour des Récompenses", // 5
|
||||||
|
"Jour de la Révolution", // 6 (sextile only)
|
||||||
|
];
|
||||||
|
|
||||||
|
const COMPLEMENTARY_NAMES_EN = [
|
||||||
|
"Day of Virtue",
|
||||||
|
"Day of Genius",
|
||||||
|
"Day of Labour",
|
||||||
|
"Day of Opinion",
|
||||||
|
"Day of Rewards",
|
||||||
|
"Day of the Revolution",
|
||||||
|
];
|
||||||
|
|
||||||
|
const WEEKDAY_NAMES = [
|
||||||
|
"Primidi", "Duodi", "Tridi", "Quartidi", "Quintidi",
|
||||||
|
"Sextidi", "Septidi", "Octidi", "Nonidi", "Décadi"
|
||||||
|
];
|
||||||
|
|
||||||
|
const WEEKDAY_NAMES_EN = [
|
||||||
|
"First day", "Second day", "Third day", "Fourth day", "Fifth day",
|
||||||
|
"Sixth day", "Seventh day", "Eighth day", "Ninth day", "Tenth day"
|
||||||
|
];
|
||||||
|
|
||||||
|
// --- Julian Day Number helpers ---
|
||||||
|
|
||||||
|
function gregorianToJD(year, month, day) {
|
||||||
|
// Algorithm from Jean Meeus, "Astronomical Algorithms"
|
||||||
|
if (month <= 2) { year -= 1; month += 12; }
|
||||||
|
const A = Math.floor(year / 100);
|
||||||
|
const B = 2 - A + Math.floor(A / 4);
|
||||||
|
return Math.floor(365.25 * (year + 4716)) +
|
||||||
|
Math.floor(30.6001 * (month + 1)) +
|
||||||
|
day + B - 1524.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isGregorianLeap(year) {
|
||||||
|
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSextile(republicanYear) {
|
||||||
|
// The year following An N is Gregorian year 1792 + N
|
||||||
|
return isGregorianLeap(1792 + republicanYear);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toRepublicanDate(utcDate) {
|
||||||
|
// JD at midnight UTC for the given date
|
||||||
|
const jd = Math.floor(gregorianToJD(
|
||||||
|
utcDate.getUTCFullYear(),
|
||||||
|
utcDate.getUTCMonth() + 1,
|
||||||
|
utcDate.getUTCDate()
|
||||||
|
) + 0.5); // +0.5 because JD starts at noon; floor gives previous midnight
|
||||||
|
|
||||||
|
const daysSinceEpoch = jd - REPUBLICAN_EPOCH_JD;
|
||||||
|
|
||||||
|
if (daysSinceEpoch < 0) {
|
||||||
|
return null; // Before the Republic
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk years: each year is 365 or 366 days
|
||||||
|
let year = 1;
|
||||||
|
let remaining = daysSinceEpoch;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const yearLen = isSextile(year) ? 366 : 365;
|
||||||
|
if (remaining < yearLen) break;
|
||||||
|
remaining -= yearLen;
|
||||||
|
year++;
|
||||||
|
}
|
||||||
|
|
||||||
|
let month, day, weekday, complementary;
|
||||||
|
|
||||||
|
if (remaining < 360) {
|
||||||
|
// Normal months (0-indexed day within year)
|
||||||
|
month = Math.floor(remaining / 30) + 1; // 1..12
|
||||||
|
day = (remaining % 30) + 1; // 1..30
|
||||||
|
weekday = ((remaining % 10)); // 0..9 → Primidi..Décadi
|
||||||
|
complementary = null;
|
||||||
|
} else {
|
||||||
|
// Complementary days (jours complémentaires / sans-culottides)
|
||||||
|
const compIdx = remaining - 360; // 0..4 (or 0..5 sextile)
|
||||||
|
month = null;
|
||||||
|
day = compIdx + 1;
|
||||||
|
weekday = null;
|
||||||
|
complementary = COMPLEMENTARY_NAMES[compIdx] || "Jour inconnu";
|
||||||
|
}
|
||||||
|
|
||||||
|
return { year, month, day, weekday, complementary };
|
||||||
|
}
|
||||||
|
|
||||||
|
function toRomanNumeral(n) {
|
||||||
|
const vals = [1000,900,500,400,100,90,50,40,10,9,5,4,1];
|
||||||
|
const syms = ["M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"];
|
||||||
|
let result = "";
|
||||||
|
for (let i = 0; i < vals.length; i++) {
|
||||||
|
while (n >= vals[i]) { result += syms[i]; n -= vals[i]; }
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatRepublicanDate(r) {
|
||||||
|
if (!r) return { full: "Before the Republic", translation: "", dayOfYear: "-", year: "-" };
|
||||||
|
|
||||||
|
let full, translation;
|
||||||
|
if (r.complementary) {
|
||||||
|
const compIdx = r.day - 1;
|
||||||
|
full = `${r.complementary}, An ${toRomanNumeral(r.year)}`;
|
||||||
|
translation = `${COMPLEMENTARY_NAMES_EN[compIdx]}, Year ${r.year}`;
|
||||||
|
} else {
|
||||||
|
const weekdayName = WEEKDAY_NAMES[r.weekday];
|
||||||
|
const weekdayNameEn = WEEKDAY_NAMES_EN[r.weekday];
|
||||||
|
const monthName = MONTH_NAMES[r.month - 1];
|
||||||
|
const monthNameEn = MONTH_NAMES_EN[r.month - 1];
|
||||||
|
full = `${weekdayName}, ${r.day} ${monthName} An ${toRomanNumeral(r.year)}`;
|
||||||
|
translation = `${weekdayNameEn}, ${r.day} ${monthNameEn}, Year ${r.year}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dayOfYear = r.complementary
|
||||||
|
? `360 + ${r.day} (Jour complémentaire)`
|
||||||
|
: `${(r.month - 1) * 30 + r.day}`;
|
||||||
|
|
||||||
|
return { full, translation, dayOfYear, year: `An ${toRomanNumeral(r.year)}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Decimal time ---
|
||||||
|
// Decimal time counts from local midnight in the selected time zone.
|
||||||
|
|
||||||
|
function getLocalParts(date, zone) {
|
||||||
|
const fmt = new Intl.DateTimeFormat("en-US", {
|
||||||
|
timeZone: zone,
|
||||||
|
hour12: false,
|
||||||
|
year: "numeric", month: "2-digit", day: "2-digit",
|
||||||
|
hour: "2-digit", minute: "2-digit", second: "2-digit"
|
||||||
|
});
|
||||||
|
const parts = fmt.formatToParts(date).reduce((acc, p) => {
|
||||||
|
if (p.type !== "literal") acc[p.type] = parseInt(p.value, 10);
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
return parts; // { year, month, day, hour, minute, second }
|
||||||
|
}
|
||||||
|
|
||||||
|
function toDecimalTime(date, zone) {
|
||||||
|
const p = getLocalParts(date, zone);
|
||||||
|
// ms since local midnight (ignoring DST transitions within the day — good enough)
|
||||||
|
const msIntoDay =
|
||||||
|
p.hour * 3_600_000 +
|
||||||
|
p.minute * 60_000 +
|
||||||
|
p.second * 1_000 +
|
||||||
|
date.getMilliseconds();
|
||||||
|
|
||||||
|
const dayFraction = msIntoDay / 86_400_000;
|
||||||
|
const totalDecimalSeconds = dayFraction * 100_000; // 10h * 100m * 100s
|
||||||
|
|
||||||
|
const dHour = Math.floor(totalDecimalSeconds / 10_000);
|
||||||
|
const rem1 = totalDecimalSeconds % 10_000;
|
||||||
|
const dMin = Math.floor(rem1 / 100);
|
||||||
|
const dSec = rem1 % 100;
|
||||||
|
const dCenti = Math.floor((dSec % 1) * 100);
|
||||||
|
const dSecInt = Math.floor(dSec);
|
||||||
|
|
||||||
|
return {
|
||||||
|
str: `${dHour}:${String(dMin).padStart(2,"0")}:${String(dSecInt).padStart(2,"0")}.${String(dCenti).padStart(2,"0")}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function toRepublicanDateLocal(date, zone) {
|
||||||
|
const p = getLocalParts(date, zone);
|
||||||
|
return toRepublicanDate(new Date(Date.UTC(p.year, p.month - 1, p.day)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- DOM & sync ---
|
||||||
|
|
||||||
|
const zoneSelect = document.getElementById("zoneSelect");
|
||||||
|
const zoneLabel = document.getElementById("zoneLabel");
|
||||||
|
const dateTrans = document.getElementById("dateTrans");
|
||||||
|
const offsetLabel = document.getElementById("offsetLabel");
|
||||||
|
const decimalUtc = document.getElementById("decimalUtc");
|
||||||
|
const dayOfYear = document.getElementById("dayOfYear");
|
||||||
|
const repYear = document.getElementById("repYear");
|
||||||
|
const utcLabel = document.getElementById("utcLabel");
|
||||||
|
const dateLine = document.getElementById("dateLine");
|
||||||
|
const digital = document.getElementById("digital");
|
||||||
|
|
||||||
|
const zones = [
|
||||||
|
"America/New_York",
|
||||||
|
"America/Chicago",
|
||||||
|
"America/Denver",
|
||||||
|
"America/Los_Angeles",
|
||||||
|
"America/Anchorage",
|
||||||
|
"Pacific/Honolulu",
|
||||||
|
"UTC"
|
||||||
|
];
|
||||||
|
|
||||||
|
let selectedZone = Intl.DateTimeFormat().resolvedOptions().timeZone || "America/New_York";
|
||||||
|
if (!zones.includes(selectedZone)) selectedZone = "America/New_York";
|
||||||
|
|
||||||
|
for (const zone of zones) {
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = zone;
|
||||||
|
option.textContent = zone;
|
||||||
|
if (zone === selectedZone) option.selected = true;
|
||||||
|
zoneSelect.appendChild(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
zoneSelect.addEventListener("change", () => {
|
||||||
|
selectedZone = zoneSelect.value;
|
||||||
|
zoneLabel.textContent = selectedZone;
|
||||||
|
});
|
||||||
|
|
||||||
|
let serverOffsetMs = 0;
|
||||||
|
|
||||||
|
async function syncWithServer() {
|
||||||
|
const start = Date.now();
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/time?ts=${start}`, { cache: "no-store" });
|
||||||
|
const end = Date.now();
|
||||||
|
const data = await response.json();
|
||||||
|
const rtt = end - start;
|
||||||
|
serverOffsetMs = (data.epoch_ms + rtt / 2) - end;
|
||||||
|
offsetLabel.textContent = `Synchronized (${serverOffsetMs >= 0 ? "+" : ""}${serverOffsetMs.toFixed(0)} ms, RTT ${rtt} ms)`;
|
||||||
|
} catch (_) {
|
||||||
|
offsetLabel.textContent = "Sync failed";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
const now = new Date(Date.now() + serverOffsetMs);
|
||||||
|
|
||||||
|
const dt = toDecimalTime(now, selectedZone);
|
||||||
|
const rep = toRepublicanDateLocal(now, selectedZone);
|
||||||
|
const fmt = formatRepublicanDate(rep);
|
||||||
|
|
||||||
|
digital.textContent = dt.str;
|
||||||
|
dateLine.textContent = fmt.full;
|
||||||
|
dateTrans.textContent = fmt.translation;
|
||||||
|
decimalUtc.textContent = dt.str;
|
||||||
|
dayOfYear.textContent = fmt.dayOfYear;
|
||||||
|
repYear.textContent = fmt.year;
|
||||||
|
zoneLabel.textContent = selectedZone;
|
||||||
|
utcLabel.textContent = now.toISOString().replace("T", " ").replace("Z", " UTC");
|
||||||
|
|
||||||
|
requestAnimationFrame(render);
|
||||||
|
}
|
||||||
|
|
||||||
|
syncWithServer();
|
||||||
|
setInterval(syncWithServer, 20_000);
|
||||||
|
render();
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Sethflix Official Time</title>
|
||||||
|
<link rel="icon" type="image/png" href="https://storage.googleapis.com/sethfreiberg.com/sethflix/favicon.png">
|
||||||
|
<link rel="stylesheet" href="/style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class="wrap">
|
||||||
|
<div class="brand-row">
|
||||||
|
<img class="logo-img" src="https://storage.googleapis.com/sethfreiberg.com/sethflix/favicon.png" alt="Sethflix logo">
|
||||||
|
<span class="brand-text">SethPC Time</span>
|
||||||
|
</div>
|
||||||
|
<p class="kicker">Official U.S. Time</p>
|
||||||
|
<h1 id="dateLine">Loading date...</h1>
|
||||||
|
<p id="digital" class="digital">--:--:--.--</p>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
<label class="zone-picker" for="zoneSelect">Display Time Zone</label>
|
||||||
|
<select id="zoneSelect"></select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="meta-grid">
|
||||||
|
<p><strong>Time Zone:</strong> <span id="zoneLabel">-</span></p>
|
||||||
|
<p><strong>Status:</strong> <span id="offsetLabel">calibrating...</span></p>
|
||||||
|
<p><strong>UTC:</strong> <span id="utcLabel">-</span></p>
|
||||||
|
<p><strong>Source:</strong> Hosted by SethPC</p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<script src="/app.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
from datetime import date, datetime, timezone
|
||||||
|
from http.server import ThreadingHTTPServer, SimpleHTTPRequestHandler
|
||||||
|
from pathlib import Path
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent
|
||||||
|
CLOCK_CGI_PATH = "/zzz__2fbc6c3300df7e4483acd44c5044098a9fcc61d6.cgi"
|
||||||
|
|
||||||
|
|
||||||
|
def us_dst_dates_for_year(year: int) -> tuple[int, int]:
|
||||||
|
march1 = date(year, 3, 1)
|
||||||
|
first_sunday_march = 1 + ((6 - march1.weekday()) % 7)
|
||||||
|
second_sunday_march = first_sunday_march + 7
|
||||||
|
|
||||||
|
nov1 = date(year, 11, 1)
|
||||||
|
first_sunday_november = 1 + ((6 - nov1.weekday()) % 7)
|
||||||
|
|
||||||
|
return second_sunday_march, first_sunday_november
|
||||||
|
|
||||||
|
|
||||||
|
class Handler(SimpleHTTPRequestHandler):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, directory=str(ROOT), **kwargs)
|
||||||
|
|
||||||
|
def do_GET(self):
|
||||||
|
parsed = urlparse(self.path)
|
||||||
|
|
||||||
|
if parsed.path == "/":
|
||||||
|
self.send_response(302)
|
||||||
|
self.send_header("Location", "/timegov/")
|
||||||
|
self.end_headers()
|
||||||
|
return
|
||||||
|
|
||||||
|
if parsed.path in {"/simple", "/simple/"}:
|
||||||
|
self.path = "/index.html"
|
||||||
|
return super().do_GET()
|
||||||
|
|
||||||
|
if parsed.path == "/api/time":
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header("Content-Type", "application/json")
|
||||||
|
self.send_header("Cache-Control", "no-store")
|
||||||
|
self.end_headers()
|
||||||
|
payload = {
|
||||||
|
"epoch_ms": time.time_ns() // 1_000_000,
|
||||||
|
"iso_utc": datetime.now(timezone.utc).isoformat(),
|
||||||
|
}
|
||||||
|
self.wfile.write(json.dumps(payload).encode("utf-8"))
|
||||||
|
return
|
||||||
|
|
||||||
|
if parsed.path == "/api/timegov/auxdata.xml":
|
||||||
|
now = datetime.now(timezone.utc)
|
||||||
|
dst_start_day, dst_end_day = us_dst_dates_for_year(now.year)
|
||||||
|
xml = (
|
||||||
|
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
|
||||||
|
"<timeData>"
|
||||||
|
f"<currYear>{now.year}</currYear>"
|
||||||
|
f"<DstDates>3 {dst_start_day} 11 {dst_end_day}</DstDates>"
|
||||||
|
"<LeapDate>2099 12 31</LeapDate>"
|
||||||
|
"</timeData>"
|
||||||
|
)
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header("Content-Type", "application/xml")
|
||||||
|
self.send_header("Cache-Control", "no-store")
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(xml.encode("utf-8"))
|
||||||
|
return
|
||||||
|
|
||||||
|
if parsed.path == CLOCK_CGI_PATH:
|
||||||
|
t2 = time.time_ns() // 1_000
|
||||||
|
t3 = time.time_ns() // 1_000
|
||||||
|
xml = f"<timestamp time2=\"{t2}\" time3=\"{t3}\"/>"
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header("Content-Type", "application/xml")
|
||||||
|
self.send_header("Cache-Control", "no-store")
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(xml.encode("utf-8"))
|
||||||
|
return
|
||||||
|
|
||||||
|
return super().do_GET()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
server = ThreadingHTTPServer(("0.0.0.0", 8092), Handler)
|
||||||
|
server.serve_forever()
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
from datetime import date, datetime, timezone
|
||||||
|
from http.server import ThreadingHTTPServer, SimpleHTTPRequestHandler
|
||||||
|
from pathlib import Path
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent
|
||||||
|
CLOCK_CGI_PATH = "/zzz__2fbc6c3300df7e4483acd44c5044098a9fcc61d6.cgi"
|
||||||
|
|
||||||
|
|
||||||
|
def us_dst_dates_for_year(year: int) -> tuple[int, int]:
|
||||||
|
march1 = date(year, 3, 1)
|
||||||
|
first_sunday_march = 1 + ((6 - march1.weekday()) % 7)
|
||||||
|
second_sunday_march = first_sunday_march + 7
|
||||||
|
|
||||||
|
nov1 = date(year, 11, 1)
|
||||||
|
first_sunday_november = 1 + ((6 - nov1.weekday()) % 7)
|
||||||
|
|
||||||
|
return second_sunday_march, first_sunday_november
|
||||||
|
|
||||||
|
|
||||||
|
class Handler(SimpleHTTPRequestHandler):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, directory=str(ROOT), **kwargs)
|
||||||
|
|
||||||
|
def do_GET(self):
|
||||||
|
parsed = urlparse(self.path)
|
||||||
|
|
||||||
|
if parsed.path == "/":
|
||||||
|
self.send_response(302)
|
||||||
|
self.send_header("Location", "/timegov/")
|
||||||
|
self.end_headers()
|
||||||
|
return
|
||||||
|
|
||||||
|
if parsed.path in {"/simple", "/simple/"}:
|
||||||
|
self.path = "/index.html"
|
||||||
|
return super().do_GET()
|
||||||
|
|
||||||
|
if parsed.path in {"/decimal", "/decimal/"}:
|
||||||
|
self.path = "/decimal.html"
|
||||||
|
return super().do_GET()
|
||||||
|
|
||||||
|
if parsed.path in {"/seth", "/seth/"}:
|
||||||
|
self.path = "/seth.html"
|
||||||
|
return super().do_GET()
|
||||||
|
|
||||||
|
if parsed.path in {"/calendar", "/calendar/"}:
|
||||||
|
self.path = "/calendar.html"
|
||||||
|
return super().do_GET()
|
||||||
|
|
||||||
|
if parsed.path in {"/astro", "/astro/"}:
|
||||||
|
self.path = "/astro.html"
|
||||||
|
return super().do_GET()
|
||||||
|
|
||||||
|
if parsed.path in {"/convert", "/convert/"}:
|
||||||
|
self.path = "/convert.html"
|
||||||
|
return super().do_GET()
|
||||||
|
|
||||||
|
if parsed.path == "/api/time":
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header("Content-Type", "application/json")
|
||||||
|
self.send_header("Cache-Control", "no-store")
|
||||||
|
self.end_headers()
|
||||||
|
payload = {
|
||||||
|
"epoch_ms": time.time_ns() // 1_000_000,
|
||||||
|
"iso_utc": datetime.now(timezone.utc).isoformat(),
|
||||||
|
}
|
||||||
|
self.wfile.write(json.dumps(payload).encode("utf-8"))
|
||||||
|
return
|
||||||
|
|
||||||
|
if parsed.path == "/api/timegov/auxdata.xml":
|
||||||
|
now = datetime.now(timezone.utc)
|
||||||
|
dst_start_day, dst_end_day = us_dst_dates_for_year(now.year)
|
||||||
|
xml = (
|
||||||
|
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
|
||||||
|
"<timeData>"
|
||||||
|
f"<currYear>{now.year}</currYear>"
|
||||||
|
f"<DstDates>3 {dst_start_day} 11 {dst_end_day}</DstDates>"
|
||||||
|
"<LeapDate>2099 12 31</LeapDate>"
|
||||||
|
"</timeData>"
|
||||||
|
)
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header("Content-Type", "application/xml")
|
||||||
|
self.send_header("Cache-Control", "no-store")
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(xml.encode("utf-8"))
|
||||||
|
return
|
||||||
|
|
||||||
|
if parsed.path == CLOCK_CGI_PATH:
|
||||||
|
t2 = time.time_ns() // 1_000
|
||||||
|
t3 = time.time_ns() // 1_000
|
||||||
|
xml = f"<timestamp time2=\"{t2}\" time3=\"{t3}\"/>"
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header("Content-Type", "application/xml")
|
||||||
|
self.send_header("Cache-Control", "no-store")
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(xml.encode("utf-8"))
|
||||||
|
return
|
||||||
|
|
||||||
|
return super().do_GET()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
server = ThreadingHTTPServer(("0.0.0.0", 8092), Handler)
|
||||||
|
server.serve_forever()
|
||||||
@@ -0,0 +1,267 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>SethPC Seth Date</title>
|
||||||
|
<link rel="icon" type="image/png" href="https://storage.googleapis.com/sethfreiberg.com/sethflix/favicon.png">
|
||||||
|
<link rel="stylesheet" href="/style.css">
|
||||||
|
<style>
|
||||||
|
.seth-note {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--muted);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
.ref-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
margin: 0.4rem 0 0.9rem;
|
||||||
|
}
|
||||||
|
.ref-table th {
|
||||||
|
text-align: left;
|
||||||
|
color: var(--accent);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
padding: 0.2rem 0.5rem 0.2rem 0;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.ref-table td {
|
||||||
|
padding: 0.15rem 0.5rem 0.15rem 0;
|
||||||
|
color: var(--muted);
|
||||||
|
border-bottom: 1px solid #2a2a2a;
|
||||||
|
}
|
||||||
|
.ref-table td:first-child {
|
||||||
|
color: var(--text);
|
||||||
|
width: 2rem;
|
||||||
|
}
|
||||||
|
.time-compare {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.2rem;
|
||||||
|
}
|
||||||
|
.time-compare p {
|
||||||
|
margin: 0;
|
||||||
|
font-family: "JetBrains Mono", monospace;
|
||||||
|
font-size: 1rem;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
.seth-details {
|
||||||
|
margin: 0.75rem 0 0;
|
||||||
|
}
|
||||||
|
.seth-details summary {
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--accent);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
user-select: none;
|
||||||
|
padding: 0.3rem 0;
|
||||||
|
}
|
||||||
|
.seth-details summary:hover {
|
||||||
|
color: var(--accent-hover);
|
||||||
|
}
|
||||||
|
.seth-details .seth-note {
|
||||||
|
margin-top: 0.6rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class="wrap">
|
||||||
|
<div class="brand-row">
|
||||||
|
<img class="logo-img" src="https://storage.googleapis.com/sethfreiberg.com/sethflix/favicon.png" alt="SethPC logo">
|
||||||
|
<span class="brand-text">SethPC Seth Date</span>
|
||||||
|
</div>
|
||||||
|
<p class="kicker">Seth Calendar & Decimal Time ·
|
||||||
|
<a href="/calendar/" style="color:var(--accent);text-decoration:none;font-size:0.85rem;">Calendar</a> ·
|
||||||
|
<a href="/convert" style="color:var(--accent);text-decoration:none;font-size:0.85rem;">Converter</a> ·
|
||||||
|
<a href="/decimal" style="color:var(--accent);text-decoration:none;font-size:0.85rem;">Decimal Time</a> ·
|
||||||
|
<a href="/astro" style="color:var(--accent);text-decoration:none;font-size:0.85rem;">Astronomy</a>
|
||||||
|
</p>
|
||||||
|
<h1 id="dateLine">Loading date...</h1>
|
||||||
|
<p id="digital" class="digital">-:--.--</p>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
<label class="zone-picker" for="zoneSelect">Display Time Zone</label>
|
||||||
|
<select id="zoneSelect"></select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="meta-grid">
|
||||||
|
<div class="time-compare" style="grid-column: span 2;">
|
||||||
|
<p class="mono">DECI: <span id="decimalTime">-</span></p>
|
||||||
|
<p class="mono">GREG: <span id="gregorianTime">-</span></p>
|
||||||
|
</div>
|
||||||
|
<p><strong>Time Zone:</strong> <span id="zoneLabel">-</span></p>
|
||||||
|
<p><strong>Day of Year:</strong> <span id="dayOfYear">-</span></p>
|
||||||
|
<p><strong>Status:</strong> <span id="offsetLabel">calibrating...</span></p>
|
||||||
|
<p><strong>Week of Year:</strong> <span id="weekOfYear">-</span></p>
|
||||||
|
<p><strong>Gregorian UTC:</strong> <span id="utcLabel">-</span></p>
|
||||||
|
<p><strong>Source:</strong> Hosted by SethPC</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<details class="seth-details">
|
||||||
|
<summary>History</summary>
|
||||||
|
<div class="seth-note">
|
||||||
|
<p>
|
||||||
|
The Seth Calendar was invented by <strong>Seth Freiberg</strong> in 2026 as a personal design project,
|
||||||
|
born out of frustration with the irregularity of the Gregorian calendar — unequal month lengths,
|
||||||
|
weeks that don't divide months evenly, and no clean mapping between days and dates.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The goal was a calendar with a simple, regular structure: 10 months of exactly 36 days,
|
||||||
|
each month divided into 6 weeks of 6 days, with a small block of holiday days at the year end
|
||||||
|
to absorb the remainder. Year numbers and the January 1 new year are kept from the Gregorian
|
||||||
|
calendar to stay compatible with the existing world.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Decimal time was paired with it for the same reason: the standard 24-hour clock is an arbitrary
|
||||||
|
Babylonian inheritance. Dividing the day into 10 hours of 100 minutes of 100 seconds gives a
|
||||||
|
fully base-10 time system that is easier to reason about and calculate with.
|
||||||
|
All units in the Seth system — months, weeks, days, hours, minutes, seconds — are zero-indexed.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Credit is due to the <strong>French Republican Calendar</strong> (1793), which pioneered both ideas:
|
||||||
|
a reformed calendar with equal 30-day months and 5–6 complementary days at year end,
|
||||||
|
and a decimal clock dividing the day into 10 hours of 100 minutes of 100 seconds.
|
||||||
|
The French system was officially used from 1793 to 1805 before Napoleon abolished it.
|
||||||
|
The Seth Calendar was designed independently in the same spirit, with a different month structure
|
||||||
|
and the addition of zero-indexing throughout.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="seth-details">
|
||||||
|
<summary>About Seth Date</summary>
|
||||||
|
<div class="seth-note">
|
||||||
|
<p>
|
||||||
|
<strong>Seth Date</strong> uses the same year numbers and January 1 new year as the Gregorian
|
||||||
|
calendar. The year is divided into <strong>10 months of 36 days</strong> (6 weeks of 6 days each),
|
||||||
|
followed by <strong>5 holiday days</strong> at year end (6 on leap years).
|
||||||
|
All units are <strong>zero-indexed</strong>: months 0–9, days 0–35, weeks 0–5, weekdays 0–5.
|
||||||
|
Time uses the same decimal system: 10 hours, 100 minutes, 100 seconds per day.
|
||||||
|
The Seth second is derived from the Unix second (which equals the SI second, leap seconds aside)
|
||||||
|
at a fixed ratio: <strong>1 Seth second = 0.864 SI seconds</strong> (86,400 SI seconds per day ÷ 100,000 Seth seconds per day).
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Leap Day</strong> (Gregorian Feb 29) is a special intercalary day that exists
|
||||||
|
<em>outside</em> the normal month and week structure. It is inserted between Month 1, Week 3, Day 4
|
||||||
|
and Month 1, Week 3, Day 5 — occupying DOY 60 in its own slot. After Leap Day the calendar
|
||||||
|
resumes at D5 unchanged, which is why every Seth date from March 1 onward falls on the same
|
||||||
|
Gregorian date every year (e.g. Christmas is always Month 9, Day 34). On the calendar it appears
|
||||||
|
as a split cell sharing the D4 column: the top half is the normal D4 day, the bottom half is Leap Day.
|
||||||
|
</p>
|
||||||
|
<p><strong>Reading the date:</strong> dates are written as
|
||||||
|
<em>Year M W D</em> —
|
||||||
|
e.g. <em>Month 3, Week 2, Day 4</em> means the 3rd month (0-indexed), week 2, day 4 of that week,
|
||||||
|
which is day 16 of the month (2×6 + 4).
|
||||||
|
Holiday days are written as <em>Holiday N</em> (N = 0–4, or 0–5 on leap years) and fall outside any month or week.
|
||||||
|
</p>
|
||||||
|
<p><strong>Months</strong> (months 0–9, days 0–35, approx. Gregorian ranges):</p>
|
||||||
|
<table class="ref-table">
|
||||||
|
<thead><tr><th>#</th><th>Days</th><th>Approx. Gregorian</th><th>Notes</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>0</td><td>0–35</td><td>Jan 1 – Feb 5</td><td></td></tr>
|
||||||
|
<tr><td>1</td><td>0–35</td><td>Feb 6 – Mar 13</td><td></td></tr>
|
||||||
|
<tr><td>2</td><td>0–35</td><td>Mar 14 – Apr 18</td><td></td></tr>
|
||||||
|
<tr><td>3</td><td>0–35</td><td>Apr 19 – May 24</td><td></td></tr>
|
||||||
|
<tr><td>4</td><td>0–35</td><td>May 25 – Jun 29</td><td></td></tr>
|
||||||
|
<tr><td>5</td><td>0–35</td><td>Jun 30 – Aug 4</td><td></td></tr>
|
||||||
|
<tr><td>6</td><td>0–35</td><td>Aug 5 – Sep 9</td><td></td></tr>
|
||||||
|
<tr><td>7</td><td>0–35</td><td>Sep 10 – Oct 15</td><td></td></tr>
|
||||||
|
<tr><td>8</td><td>0–35</td><td>Oct 16 – Nov 20</td><td></td></tr>
|
||||||
|
<tr><td>9</td><td>0–35</td><td>Nov 21 – Dec 26</td><td>Ends Dec 25 on leap years</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p><strong>Weeks</strong> (weeks 0–5 within each month, days 0–5 within each week):</p>
|
||||||
|
<table class="ref-table">
|
||||||
|
<thead><tr><th>Week</th><th>Days</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>0</td><td>0–5</td></tr>
|
||||||
|
<tr><td>1</td><td>6–11</td></tr>
|
||||||
|
<tr><td>2</td><td>12–17</td></tr>
|
||||||
|
<tr><td>3</td><td>18–23</td></tr>
|
||||||
|
<tr><td>4</td><td>24–29</td></tr>
|
||||||
|
<tr><td>5</td><td>30–35</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p><strong>Holiday days</strong> (after Month 9, Day 35):</p>
|
||||||
|
<table class="ref-table">
|
||||||
|
<thead><tr><th>Holiday</th><th>Normal year</th><th>Leap year</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>H0</td><td>Dec 27</td><td>Dec 26 — Boxing Day</td></tr>
|
||||||
|
<tr><td>H1</td><td>Dec 28</td><td>Dec 27</td></tr>
|
||||||
|
<tr><td>H2</td><td>Dec 29</td><td>Dec 28</td></tr>
|
||||||
|
<tr><td>H3</td><td>Dec 30</td><td>Dec 29</td></tr>
|
||||||
|
<tr><td>H4</td><td>Dec 31 — New Year's Eve</td><td>Dec 30</td></tr>
|
||||||
|
<tr><td>H5*</td><td>—</td><td>Dec 31 — New Year's Eve</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p style="font-size:0.8rem;">
|
||||||
|
* Leap years only.<br>
|
||||||
|
Christmas (Dec 25) falls on Month 9, Day 34 in normal years, and Month 9, Day 35 in leap years.<br>
|
||||||
|
Dec 26 (Boxing Day) is Month 9, Day 35 in normal years, and Holiday 0 in leap years.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details class="seth-details">
|
||||||
|
<summary>Time Conversions</summary>
|
||||||
|
<div class="seth-note">
|
||||||
|
<p>Each decimal hour = 2 hours 24 minutes Gregorian. Each Gregorian hour = ~0:41:67 decimal.</p>
|
||||||
|
<div style="display:grid; grid-template-columns: 1fr 1fr; gap: 0 2rem;">
|
||||||
|
<div>
|
||||||
|
<p><strong>Decimal → Gregorian</strong></p>
|
||||||
|
<table class="ref-table">
|
||||||
|
<thead><tr><th>Decimal hour</th><th>Gregorian (24h)</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>0:00</td><td>00:00</td></tr>
|
||||||
|
<tr><td>1:00</td><td>02:24</td></tr>
|
||||||
|
<tr><td>2:00</td><td>04:48</td></tr>
|
||||||
|
<tr><td>3:00</td><td>07:12</td></tr>
|
||||||
|
<tr><td>4:00</td><td>09:36</td></tr>
|
||||||
|
<tr><td>5:00</td><td>12:00</td></tr>
|
||||||
|
<tr><td>6:00</td><td>14:24</td></tr>
|
||||||
|
<tr><td>7:00</td><td>16:48</td></tr>
|
||||||
|
<tr><td>8:00</td><td>19:12</td></tr>
|
||||||
|
<tr><td>9:00</td><td>21:36</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p><strong>Gregorian → Decimal</strong></p>
|
||||||
|
<table class="ref-table">
|
||||||
|
<thead><tr><th>Gregorian (24h)</th><th>Decimal hour</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>00:00</td><td>0:00</td></tr>
|
||||||
|
<tr><td>01:00</td><td>0:41:67</td></tr>
|
||||||
|
<tr><td>02:00</td><td>0:83:33</td></tr>
|
||||||
|
<tr><td>03:00</td><td>1:25:00</td></tr>
|
||||||
|
<tr><td>04:00</td><td>1:66:67</td></tr>
|
||||||
|
<tr><td>05:00</td><td>2:08:33</td></tr>
|
||||||
|
<tr><td>06:00</td><td>2:50:00</td></tr>
|
||||||
|
<tr><td>07:00</td><td>2:91:67</td></tr>
|
||||||
|
<tr><td>08:00</td><td>3:33:33</td></tr>
|
||||||
|
<tr><td>09:00</td><td>3:75:00</td></tr>
|
||||||
|
<tr><td>10:00</td><td>4:16:67</td></tr>
|
||||||
|
<tr><td>11:00</td><td>4:58:33</td></tr>
|
||||||
|
<tr><td>12:00</td><td>5:00:00</td></tr>
|
||||||
|
<tr><td>13:00</td><td>5:41:67</td></tr>
|
||||||
|
<tr><td>14:00</td><td>5:83:33</td></tr>
|
||||||
|
<tr><td>15:00</td><td>6:25:00</td></tr>
|
||||||
|
<tr><td>16:00</td><td>6:66:67</td></tr>
|
||||||
|
<tr><td>17:00</td><td>7:08:33</td></tr>
|
||||||
|
<tr><td>18:00</td><td>7:50:00</td></tr>
|
||||||
|
<tr><td>19:00</td><td>7:91:67</td></tr>
|
||||||
|
<tr><td>20:00</td><td>8:33:33</td></tr>
|
||||||
|
<tr><td>21:00</td><td>8:75:00</td></tr>
|
||||||
|
<tr><td>22:00</td><td>9:16:67</td></tr>
|
||||||
|
<tr><td>23:00</td><td>9:58:33</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
</main>
|
||||||
|
<script src="/seth.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,218 @@
|
|||||||
|
// Seth Calendar
|
||||||
|
//
|
||||||
|
// Structure:
|
||||||
|
// - Same year numbers and Jan 1 epoch as Gregorian
|
||||||
|
// - 10 months of 36 days (6 weeks of 6 days each)
|
||||||
|
// - 5 holiday days at year end (6 on leap years)
|
||||||
|
// - Total: 365 or 366 days
|
||||||
|
//
|
||||||
|
// Gregorian alignment (non-leap year):
|
||||||
|
// Month 1 Day 1 = Jan 1
|
||||||
|
// Month 10 Day 35 = Dec 25 (Christmas)
|
||||||
|
// Month 10 Day 36 = Dec 26 (Boxing Day)
|
||||||
|
// Holiday 1 = Dec 27
|
||||||
|
// Holiday 5 = Dec 31 (New Year's Eve)
|
||||||
|
//
|
||||||
|
// Gregorian alignment (leap year):
|
||||||
|
// Month 10 Day 35 = Dec 24 (Christmas Eve)
|
||||||
|
// Month 10 Day 36 = Dec 25 (Christmas)
|
||||||
|
// Holiday 1 = Dec 26 (Boxing Day)
|
||||||
|
// Holiday 6 = Dec 31 (New Year's Eve)
|
||||||
|
|
||||||
|
function isLeapYear(year) {
|
||||||
|
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toSethDate(year, dayOfYear) {
|
||||||
|
// dayOfYear: 1-indexed (Jan 1 = 1)
|
||||||
|
const leap = isLeapYear(year);
|
||||||
|
|
||||||
|
// Leap Day: DOY 60 in a leap year — sits between M1 W3 D4 and M1 W3 D5
|
||||||
|
if (leap && dayOfYear === 60) {
|
||||||
|
return { type: "leapday", year, dayOfYear };
|
||||||
|
}
|
||||||
|
|
||||||
|
// After leap day, subtract 1 so the rest of the calendar stays pegged
|
||||||
|
const idx = (leap && dayOfYear > 60 ? dayOfYear - 1 : dayOfYear) - 1;
|
||||||
|
|
||||||
|
if (idx < 360) {
|
||||||
|
const month = Math.floor(idx / 36); // 0..9
|
||||||
|
const dayInM = idx % 36; // 0..35
|
||||||
|
const week = Math.floor(dayInM / 6); // 0..5
|
||||||
|
const dayInW = dayInM % 6; // 0..5
|
||||||
|
return { type: "month", year, month, day: dayInM, week, weekDay: dayInW, dayOfYear };
|
||||||
|
} else {
|
||||||
|
const holiday = idx - 360; // 0..4 (or 0..5 leap)
|
||||||
|
return { type: "holiday", year, holiday, leap, dayOfYear };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDayOfYear(date, zone) {
|
||||||
|
const fmt = new Intl.DateTimeFormat("en-US", {
|
||||||
|
timeZone: zone,
|
||||||
|
year: "numeric", month: "numeric", day: "numeric"
|
||||||
|
});
|
||||||
|
const parts = fmt.formatToParts(date).reduce((acc, p) => {
|
||||||
|
if (p.type !== "literal") acc[p.type] = parseInt(p.value, 10);
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const { year, month, day } = parts;
|
||||||
|
|
||||||
|
// Day of year calculation
|
||||||
|
const cumDays = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
|
||||||
|
const leap = isLeapYear(year);
|
||||||
|
let doy = cumDays[month - 1] + day;
|
||||||
|
if (leap && month > 2) doy++;
|
||||||
|
|
||||||
|
return { year, doy };
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatSethDate(sd) {
|
||||||
|
if (sd.type === "leapday") return "Leap Day";
|
||||||
|
if (sd.type === "holiday") {
|
||||||
|
const total = sd.leap ? 6 : 5;
|
||||||
|
return `Holiday ${sd.holiday} of ${total}`;
|
||||||
|
}
|
||||||
|
return `Month ${sd.month}, Week ${sd.week}, Day ${sd.weekDay}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatSethLong(sd) {
|
||||||
|
if (sd.type === "leapday") return `${sd.year} Leap Day — Feb 29`;
|
||||||
|
if (sd.type === "holiday") {
|
||||||
|
const lastHoliday = sd.leap ? 5 : 4;
|
||||||
|
const isNYE = sd.holiday === lastHoliday;
|
||||||
|
const isBoxingDay = sd.leap && sd.holiday === 0;
|
||||||
|
let note = "";
|
||||||
|
if (isNYE) note = " — New Year's Eve";
|
||||||
|
else if (isBoxingDay) note = " — Boxing Day";
|
||||||
|
return `${sd.year} Holiday ${sd.holiday}${note}`;
|
||||||
|
}
|
||||||
|
return `${sd.year} M${sd.month} W${sd.week} D${sd.weekDay} (Month ${sd.month}, Day ${sd.day})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Decimal time (same as decimal page) ---
|
||||||
|
|
||||||
|
function getLocalParts(date, zone) {
|
||||||
|
const fmt = new Intl.DateTimeFormat("en-US", {
|
||||||
|
timeZone: zone,
|
||||||
|
hour12: false,
|
||||||
|
year: "numeric", month: "2-digit", day: "2-digit",
|
||||||
|
hour: "2-digit", minute: "2-digit", second: "2-digit"
|
||||||
|
});
|
||||||
|
return fmt.formatToParts(date).reduce((acc, p) => {
|
||||||
|
if (p.type !== "literal") acc[p.type] = parseInt(p.value, 10);
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
function toDecimalTime(date, zone) {
|
||||||
|
const p = getLocalParts(date, zone);
|
||||||
|
const msIntoDay =
|
||||||
|
p.hour * 3_600_000 +
|
||||||
|
p.minute * 60_000 +
|
||||||
|
p.second * 1_000 +
|
||||||
|
date.getMilliseconds();
|
||||||
|
|
||||||
|
const dayFraction = msIntoDay / 86_400_000;
|
||||||
|
const totalDecimalSeconds = dayFraction * 100_000;
|
||||||
|
|
||||||
|
const dHour = Math.floor(totalDecimalSeconds / 10_000);
|
||||||
|
const rem1 = totalDecimalSeconds % 10_000;
|
||||||
|
const dMin = Math.floor(rem1 / 100);
|
||||||
|
const dSec = rem1 % 100;
|
||||||
|
const dCenti = Math.floor((dSec % 1) * 100);
|
||||||
|
const dSecInt = Math.floor(dSec);
|
||||||
|
|
||||||
|
return `\u00a0${dHour}:${String(dMin).padStart(2,"0")}:${String(dSecInt).padStart(2,"0")}.${String(dCenti).padStart(2,"0")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- DOM & sync ---
|
||||||
|
|
||||||
|
const zoneSelect = document.getElementById("zoneSelect");
|
||||||
|
const zoneLabel = document.getElementById("zoneLabel");
|
||||||
|
const offsetLabel = document.getElementById("offsetLabel");
|
||||||
|
const decimalTime = document.getElementById("decimalTime");
|
||||||
|
const gregorianTime = document.getElementById("gregorianTime");
|
||||||
|
const dayOfYear = document.getElementById("dayOfYear");
|
||||||
|
const weekOfYear = document.getElementById("weekOfYear");
|
||||||
|
const utcLabel = document.getElementById("utcLabel");
|
||||||
|
const dateLine = document.getElementById("dateLine");
|
||||||
|
const digital = document.getElementById("digital");
|
||||||
|
|
||||||
|
const zones = [
|
||||||
|
"America/New_York",
|
||||||
|
"America/Chicago",
|
||||||
|
"America/Denver",
|
||||||
|
"America/Los_Angeles",
|
||||||
|
"America/Anchorage",
|
||||||
|
"Pacific/Honolulu",
|
||||||
|
"UTC"
|
||||||
|
];
|
||||||
|
|
||||||
|
let selectedZone = Intl.DateTimeFormat().resolvedOptions().timeZone || "America/New_York";
|
||||||
|
if (!zones.includes(selectedZone)) selectedZone = "America/New_York";
|
||||||
|
|
||||||
|
for (const zone of zones) {
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = zone;
|
||||||
|
option.textContent = zone;
|
||||||
|
if (zone === selectedZone) option.selected = true;
|
||||||
|
zoneSelect.appendChild(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
zoneSelect.addEventListener("change", () => {
|
||||||
|
selectedZone = zoneSelect.value;
|
||||||
|
zoneLabel.textContent = selectedZone;
|
||||||
|
});
|
||||||
|
|
||||||
|
let serverOffsetMs = 0;
|
||||||
|
|
||||||
|
async function syncWithServer() {
|
||||||
|
const start = Date.now();
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/time?ts=${start}`, { cache: "no-store" });
|
||||||
|
const end = Date.now();
|
||||||
|
const data = await response.json();
|
||||||
|
const rtt = end - start;
|
||||||
|
serverOffsetMs = (data.epoch_ms + rtt / 2) - end;
|
||||||
|
offsetLabel.textContent = `Synchronized (${serverOffsetMs >= 0 ? "+" : ""}${serverOffsetMs.toFixed(0)} ms, RTT ${rtt} ms)`;
|
||||||
|
} catch (_) {
|
||||||
|
offsetLabel.textContent = "Sync failed";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toGregorianTime(date, zone) {
|
||||||
|
const p = getLocalParts(date, zone);
|
||||||
|
// Format as HH:MM:SS.cc to match decimal time width
|
||||||
|
const cs = String(Math.floor(date.getMilliseconds() / 10)).padStart(2, "0");
|
||||||
|
return `${String(p.hour).padStart(2,"0")}:${String(p.minute).padStart(2,"0")}:${String(p.second).padStart(2,"0")}.${cs}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
const now = new Date(Date.now() + serverOffsetMs);
|
||||||
|
const { year, doy } = getDayOfYear(now, selectedZone);
|
||||||
|
const sd = toSethDate(year, doy);
|
||||||
|
const dt = toDecimalTime(now, selectedZone);
|
||||||
|
const gt = toGregorianTime(now, selectedZone);
|
||||||
|
|
||||||
|
dateLine.textContent = formatSethLong(sd);
|
||||||
|
digital.textContent = dt;
|
||||||
|
decimalTime.textContent = dt;
|
||||||
|
gregorianTime.textContent = gt;
|
||||||
|
dayOfYear.textContent = `${doy} of ${isLeapYear(year) ? 366 : 365}`;
|
||||||
|
const totalWeeks = 60; // 10 months × 6 weeks
|
||||||
|
const woy = sd.type === "month" ? sd.month * 6 + sd.week : "—";
|
||||||
|
const woyTotal = sd.type === "month" ? `${woy} of ${totalWeeks}`
|
||||||
|
: sd.type === "leapday" ? "— (leap day)"
|
||||||
|
: `— (holiday days)`;
|
||||||
|
weekOfYear.textContent = woyTotal;
|
||||||
|
zoneLabel.textContent = selectedZone;
|
||||||
|
utcLabel.textContent = now.toISOString().replace("T", " ").replace("Z", " UTC");
|
||||||
|
|
||||||
|
requestAnimationFrame(render);
|
||||||
|
}
|
||||||
|
|
||||||
|
syncWithServer();
|
||||||
|
setInterval(syncWithServer, 20_000);
|
||||||
|
render();
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
:root {
|
||||||
|
--bg-top: #000000;
|
||||||
|
--bg-bottom: #1a1a1a;
|
||||||
|
--panel: #252525;
|
||||||
|
--text: #e0e0e0;
|
||||||
|
--muted: #cccccc;
|
||||||
|
--accent: #d35400;
|
||||||
|
--accent-hover: #e65c00;
|
||||||
|
--border: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
min-height: 100vh;
|
||||||
|
font-family: "Source Sans 3", "Segoe UI", sans-serif;
|
||||||
|
color: var(--text);
|
||||||
|
background: radial-gradient(circle at 20% -10%, #2c2c2c 0%, var(--bg-bottom) 38%, var(--bg-top) 100%);
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
padding: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrap {
|
||||||
|
width: min(760px, 100%);
|
||||||
|
background: var(--panel);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: clamp(1rem, 2.5vw, 2rem);
|
||||||
|
box-shadow: 0 10px 28px rgb(0 0 0 / 42%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.55rem;
|
||||||
|
margin-bottom: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-img {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-text {
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.kicker {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--muted);
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 0.35rem 0 0.8rem;
|
||||||
|
font-size: clamp(1.2rem, 3vw, 1.7rem);
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.digital {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
font-family: "JetBrains Mono", monospace;
|
||||||
|
font-size: clamp(2rem, 8vw, 4rem);
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(180px, 1fr));
|
||||||
|
gap: 0.45rem 0.9rem;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.98rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-grid p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zone-picker {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.35rem;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
width: min(500px, 100%);
|
||||||
|
background: #2a2a2a;
|
||||||
|
color: #ffffff;
|
||||||
|
border: 1px solid #444444;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 0.55rem 0.75rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--accent);
|
||||||
|
box-shadow: 0 0 0 3px rgb(211 84 0 / 25%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 760px) {
|
||||||
|
.meta-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Header and Footer styles
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Header Styles */
|
||||||
|
.nist-header {
|
||||||
|
background: #000;
|
||||||
|
font-family: Helvetica, Arial, sans-serif;
|
||||||
|
padding: 10px 16px 0;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nist-header__logo-link {
|
||||||
|
display: inline-block;
|
||||||
|
height: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nist-header__logo-icon {
|
||||||
|
fill: #fff;
|
||||||
|
display: inline-block;
|
||||||
|
height: 16px;
|
||||||
|
position: relative;
|
||||||
|
top: -2px;
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nist-header__logo-image {
|
||||||
|
fill: #fff;
|
||||||
|
display: inline-block;
|
||||||
|
height: 24px;
|
||||||
|
width: 90px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Limit main content area width */
|
||||||
|
.nist-main {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
max-width: 1200px;
|
||||||
|
padding: 0 16px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make sure body has no margin or padding when using only this header component */
|
||||||
|
body {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer styles */
|
||||||
|
|
||||||
|
.nist-footer {
|
||||||
|
background: #333333;
|
||||||
|
position: relative;
|
||||||
|
z-index: 200;
|
||||||
|
font-family: Helvetica, Arial, sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nist-footer__inner {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
max-width: 1200px;
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nist-footer__inner:after {
|
||||||
|
content: "";
|
||||||
|
display: table;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nist-footer {
|
||||||
|
background: #333333;
|
||||||
|
color: white;
|
||||||
|
padding: 10px 0px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nist-footer a {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nist-footer .nist-footer__logo img {
|
||||||
|
/* width: 30%; 300px;*/
|
||||||
|
height: 100px;
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 5%; /*margin-left: 10%;*/
|
||||||
|
margin-right: 5%; /*margin-right: 10%auto;*/
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nist-footer__menu {
|
||||||
|
clear: both;
|
||||||
|
margin-bottom: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nist-footer__menu.first {
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nist-footer__menu ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nist-footer__menu-item {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 0;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nist-footer__menu-item:after {
|
||||||
|
content: '|';
|
||||||
|
margin-right: 1.6px;
|
||||||
|
display: inherit;
|
||||||
|
position: static;
|
||||||
|
font: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nist-footer__menu-item:last-child:after {
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nist-footer__menu-item a {
|
||||||
|
font-weight: normal;
|
||||||
|
margin-right: 6px;
|
||||||
|
white-space: nowrap;
|
||||||
|
padding: 0.5em 0;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stick footer to bottom of page
|
||||||
|
* Source: https://css-tricks.com/couple-takes-sticky-footer/
|
||||||
|
*/
|
||||||
|
|
||||||
|
html.nist-footer-bottom,
|
||||||
|
html.nist-footer-bottom body {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
html.nist-footer-bottom body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
html.nist-footer-bottom #main {
|
||||||
|
flex: 1 0 auto;
|
||||||
|
}
|
||||||
|
html.nist-footer-bottom .nist-footer {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
////////////////////// ANALOG CLOCK FUNCTIONS ///////////////////////////
|
||||||
|
///////////////// Clock code used from W3Schools.com ////////////////////
|
||||||
|
//////// Refer to their site about applicable use of the code ///////////
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
var canvas = document.getElementById("analog-clock");
|
||||||
|
var ctx = canvas.getContext("2d");
|
||||||
|
var radius = canvas.height / 2;
|
||||||
|
ctx.translate(radius, radius);
|
||||||
|
radius = radius * 0.90
|
||||||
|
setInterval(drawClock, 1000);
|
||||||
|
|
||||||
|
// add 0 to single digit time min, sec
|
||||||
|
function checkTime(i) {
|
||||||
|
if (i < 10) {i = "0" + i}; // add zero in front of numbers < 10
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawClock() {
|
||||||
|
drawFace(ctx, radius);
|
||||||
|
drawNumbers(ctx, radius);
|
||||||
|
drawTime(ctx, radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawFace(ctx, radius) {
|
||||||
|
var grad;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(0, 0, radius, 0, 2*Math.PI);
|
||||||
|
ctx.fillStyle = 'white';
|
||||||
|
ctx.fill();
|
||||||
|
grad = ctx.createRadialGradient(0,0,radius*0.95, 0,0,radius*1.05);
|
||||||
|
grad.addColorStop(0, '#333');
|
||||||
|
grad.addColorStop(0.5, 'white');
|
||||||
|
grad.addColorStop(1, '#333');
|
||||||
|
ctx.strokeStyle = grad;
|
||||||
|
ctx.lineWidth = radius*0.1;
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(0, 0, radius*0.1, 0, 2*Math.PI);
|
||||||
|
ctx.fillStyle = '#333';
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawNumbers(ctx, radius) {
|
||||||
|
var ang;
|
||||||
|
var num;
|
||||||
|
ctx.font = radius*0.15 + "px arial";
|
||||||
|
ctx.textBaseline="middle";
|
||||||
|
ctx.textAlign="center";
|
||||||
|
for(num = 1; num < 13; num++){
|
||||||
|
ang = num * Math.PI / 6;
|
||||||
|
ctx.rotate(ang);
|
||||||
|
ctx.translate(0, -radius*0.85);
|
||||||
|
ctx.rotate(-ang);
|
||||||
|
ctx.fillText(num.toString(), 0, 0);
|
||||||
|
ctx.rotate(ang);
|
||||||
|
ctx.translate(0, radius*0.85);
|
||||||
|
ctx.rotate(-ang);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawTime(ctx, radius){
|
||||||
|
|
||||||
|
var deviceClock = new Date();
|
||||||
|
var hour = deviceClock.getHours();
|
||||||
|
var minute = deviceClock.getMinutes();
|
||||||
|
var second = deviceClock.getSeconds();
|
||||||
|
|
||||||
|
var ampm = hour >= 12 ? 'P.M.' : 'A.M.';
|
||||||
|
hour = hour % 12;
|
||||||
|
hour = hour ? hour : 12; // the hour '0' should be '12'
|
||||||
|
//minute = minute < 10 ? '0'+minute : minute;
|
||||||
|
|
||||||
|
// CHECKTIME FUNC ADDS LEADING '0' if <10
|
||||||
|
hour = checkTime(hour);
|
||||||
|
minute = checkTime(minute);
|
||||||
|
second = checkTime(second);
|
||||||
|
|
||||||
|
//digital time display
|
||||||
|
var myDigTime = hour + ":" + minute +":" + second;
|
||||||
|
document.getElementById('myTime').innerHTML = myDigTime + ' ' + ampm;
|
||||||
|
|
||||||
|
var dd = deviceClock.getDate();
|
||||||
|
var mm = deviceClock.getMonth();
|
||||||
|
mm++; //Increment because Jan is month 0
|
||||||
|
dd = checkTime(dd);
|
||||||
|
mm = checkTime(mm);
|
||||||
|
var yyyy = deviceClock.getFullYear();
|
||||||
|
|
||||||
|
var todaysDate = mm + '/' + dd + '/' + yyyy;
|
||||||
|
document.getElementById("myDate").innerHTML = "Today: " + todaysDate;
|
||||||
|
|
||||||
|
// OFFSET
|
||||||
|
var utcOffset = deviceClock.getTimezoneOffset()/60;
|
||||||
|
if (utcOffset > 0) {
|
||||||
|
utcOffset = "(UTC-" + utcOffset + ")";
|
||||||
|
} else {
|
||||||
|
utcOffset = "(UTC+" + Math.abs(utcOffset) + ")";
|
||||||
|
};
|
||||||
|
document.getElementById("myTimeTitle").innerHTML = utcOffset;
|
||||||
|
|
||||||
|
//hour
|
||||||
|
hour=hour%12;
|
||||||
|
hour=(hour*Math.PI/6)+
|
||||||
|
(minute*Math.PI/(6*60))+
|
||||||
|
(second*Math.PI/(360*60));
|
||||||
|
drawHand(ctx, hour, radius*0.5, radius*0.07);
|
||||||
|
//minute
|
||||||
|
minute=(minute*Math.PI/30)+(second*Math.PI/(30*60));
|
||||||
|
drawHand(ctx, minute, radius*0.8, radius*0.07);
|
||||||
|
// second
|
||||||
|
second=(second*Math.PI/30);
|
||||||
|
drawHand(ctx, second, radius*0.9, radius*0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawHand(ctx, pos, length, width) {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.lineWidth = width;
|
||||||
|
ctx.lineCap = "round";
|
||||||
|
ctx.moveTo(0,0);
|
||||||
|
ctx.rotate(pos);
|
||||||
|
ctx.lineTo(0, -length);
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.rotate(-pos);
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
// ONLOAD OPERATIONS FOR THE SITE
|
||||||
|
window.onload = function() {
|
||||||
|
|
||||||
|
/* PARSE THE URL FOR 12/24 VARIABLE*/
|
||||||
|
var getT = location.search;
|
||||||
|
var tArr = getT.split("=");
|
||||||
|
var t = tArr[1];
|
||||||
|
|
||||||
|
// CREATE A VAR FOR THE CHECKBOX
|
||||||
|
var twentyFour = document.getElementById("twenty-four");
|
||||||
|
|
||||||
|
// CHECK VALUE OF 12/24 URL VAR "t" AND SET CHECKBOX ACCORDINGLY
|
||||||
|
if (t === "24") {
|
||||||
|
twentyFour.checked = true;
|
||||||
|
} else {
|
||||||
|
// DEFAULT TO 12HR DISPLAY
|
||||||
|
twentyFour.checked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var noMoreAlerts = false;
|
||||||
|
// NOTIFICATION BOX FOR BOOKMARKING 24-HOUR SETTINGS PAGE
|
||||||
|
var twentyFour = document.getElementById("twenty-four");
|
||||||
|
twentyFour.addEventListener("click", function(event) {
|
||||||
|
var hourLabelDiv = document.getElementsByClassName("am-pm")[0];
|
||||||
|
var url = window.location.toString();
|
||||||
|
if(timeDotGov.data.twentyFour()) {
|
||||||
|
window.history.replaceState(url, "", "/timegov/?t=24");
|
||||||
|
if (!noMoreAlerts) {
|
||||||
|
alert("Bookmarking this page will save your preference for 24-hour time display.");
|
||||||
|
}
|
||||||
|
noMoreAlerts = true;
|
||||||
|
} else {
|
||||||
|
window.history.replaceState(url, "", "/timegov/");
|
||||||
|
}
|
||||||
|
timeDotGov.clockController.handleonrefresh(new Date());
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
//? timeZoneChange = function(event) {
|
||||||
|
//? timeDotGov.clockController.getnewOffset(event.target.value);
|
||||||
|
//? }
|
||||||
|
|
||||||
|
// LOAD DST DATES AND LEAP DATE
|
||||||
|
var xmlHttp = new XMLHttpRequest();
|
||||||
|
xmlHttp.open("GET", "/api/timegov/auxdata.xml", false); // false for synchronous request
|
||||||
|
xmlHttp.send(null);
|
||||||
|
timeDotGov.auxdata = xmlHttp.responseText;
|
||||||
|
|
||||||
|
timeDotGov.clockController.auxdata();
|
||||||
|
|
||||||
|
timeDotGov.clockController.checkservertime();
|
||||||
|
document.getElementById('responseTime').innerHTML = timeDotGov.data.zoneOffset;
|
||||||
|
|
||||||
|
// SET REFRESH RATE TO CHECK FOR TOP OF NEW SECOND, SO THE DISPLAY DOES NOT HAVE TO BE REFRESHED MORE THAN NECESSARY
|
||||||
|
setInterval(function() {
|
||||||
|
if(timeDotGov.data.currentTime) {
|
||||||
|
timeDotGov.clockController.runningclocks();
|
||||||
|
}
|
||||||
|
}, 20); // 20 milliseconds
|
||||||
|
|
||||||
|
// FUNCTION REFRESHES PAGE EVERY 10 MIN
|
||||||
|
setInterval(function() {
|
||||||
|
location.reload();
|
||||||
|
}, 600000);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
@@ -0,0 +1,363 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="title" content="Atermiter-X79 Official Time" />
|
||||||
|
<meta property="og:site_name" content="Atermiter-X79" />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:url" content="https://clock.atermiter-x79.xyz/timegov/" />
|
||||||
|
<meta property="og:title" content="Official U.S. Time | Atermiter-X79" />
|
||||||
|
<meta property="og:description" content="Self-hosted official U.S. time style display with U.S. map and synchronized clocks." />
|
||||||
|
<meta property="og:image" content="/timegov/img/map-elements/united-states-map.svg" />
|
||||||
|
<meta name="dcterms.title" content="Atermiter-X79 Official Time" />
|
||||||
|
<meta name="description" content="Self-hosted official U.S. time display with synchronized clock data." />
|
||||||
|
<meta name="dcterms.subject" content="Working with industry and science to advance innovation and improve quality of life." />
|
||||||
|
<meta name="google-site-verification" content="" />
|
||||||
|
<meta name="dcterms.type" content="text" />
|
||||||
|
<meta name="dcterms.format" content="text/html" />
|
||||||
|
<meta name="dcterms.identifier" content="https://clock.atermiter-x79.xyz/timegov/" />
|
||||||
|
<meta name="twitter:title" content="Official U.S. Time | Atermiter-X79" />
|
||||||
|
<meta name="twitter:description" content="Self-hosted official U.S. time style display with synchronized clocks." />
|
||||||
|
<meta name="twitter:image" content="/img/map-elements/og_map.png" />
|
||||||
|
|
||||||
|
<title>Atermiter-X79 Official Time</title>
|
||||||
|
<link rel="shortcut icon" href="https://storage.googleapis.com/sethfreiberg.com/sethflix/favicon.png" />
|
||||||
|
<link rel="stylesheet" href="css/main.css" />
|
||||||
|
<link rel="stylesheet" href="css/NIST-styles.css" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- FUNC FOR TOP BAR INFO BTN -->
|
||||||
|
<script type="text/javascript">
|
||||||
|
function info_exp(id, id2) {
|
||||||
|
var n = document.getElementById(id);
|
||||||
|
if (n.style.display != 'none') {
|
||||||
|
n.style.display = 'none';
|
||||||
|
document.getElementById(id2).setAttribute('aria-expanded', 'false');
|
||||||
|
} else {
|
||||||
|
n.style.display = 'block';
|
||||||
|
document.getElementById(id2).setAttribute('aria-expanded', 'true');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
|
||||||
|
<!-- TOP GREY BAR -->
|
||||||
|
<div id="top-grey">
|
||||||
|
<div class="inner">
|
||||||
|
<img src="img/us_flag_small.png" class="left gap" alt="U.S. Flag"><span class="left gap">Official self-hosted time service</span> <div id="info-btn"><a href="index.html#" onclick="info_exp('top-grey-exp', 'arrow');">Service details <span id="arrow" aria-expanded="false"></span></a></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- EXPANDED TOP GREY BAR -->
|
||||||
|
<div id="top-grey-exp" style="display:none;">
|
||||||
|
<div class="inner">
|
||||||
|
<div class="col-50">
|
||||||
|
<div class="pad-4">
|
||||||
|
<img src="img/icon-dot-gov.svg" alt="icon dot gov" class="img10"><br>
|
||||||
|
<strong>Self-hosted service notice.</strong>
|
||||||
|
<p class="no-marg">This is a privately hosted clock service designed to mirror the time.gov experience in a homelab environment.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-50">
|
||||||
|
<div class="pad-4">
|
||||||
|
<img src="img/icon-https.svg" alt="icon https" class="img10"><br>
|
||||||
|
<strong>Encrypted transport.</strong>
|
||||||
|
<p class="no-marg">The <strong>https://</strong> indicates encrypted traffic between your browser and this host.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="clear"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- BLACK TITLE BAR AREA -->
|
||||||
|
<div id="title-area">
|
||||||
|
<div class="inner">
|
||||||
|
<center>
|
||||||
|
<span class="title"><img src="https://storage.googleapis.com/sethfreiberg.com/sethflix/favicon.png" id="logo" alt="Atermiter-X79"/>OFFICIAL U.S. TIME</span>
|
||||||
|
</center>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ANNOUNCEMENT GREY BAR -->
|
||||||
|
<div id="callout-bar"><p></p></div> <!-- Blank between <p> </p> unless there is an upcoming announcement -->
|
||||||
|
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- MAIN CONTENT AREA -->
|
||||||
|
<div id="content">
|
||||||
|
|
||||||
|
<!-- LEFT COLUMN -->
|
||||||
|
<div id="col-left">
|
||||||
|
<div class="pad-2">
|
||||||
|
|
||||||
|
<div id="noncontTitle">Non-Contiguous U.S. and Territories</div>
|
||||||
|
|
||||||
|
<div id="ak-col">
|
||||||
|
<!-- ALASKA TIME -->
|
||||||
|
<div class="clock-box blue dst-clock">
|
||||||
|
<div class="title">Alaska <span class="DSTterm">Standard</span> Time</div>
|
||||||
|
<div class="sub-title">AK<span class="DSTletter">S</span>T (UTC-<span class="DSTnum">9</span>)</div>
|
||||||
|
<div class="time-display">
|
||||||
|
<div class="color-area"></div>
|
||||||
|
<div class="clock time-text"><time zoneOffset=9></time></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ALASKA MAP GRAPHIC -->
|
||||||
|
<img src="img/map-elements/alaska.svg" class="side-img" alt="Alaska Map">
|
||||||
|
|
||||||
|
<!-- ALEUTIAN TIME -->
|
||||||
|
<div class="clock-box blue dst-clock">
|
||||||
|
<div class="title">Aleutian <span class="DSTterm">Standard</span> Time</div>
|
||||||
|
<div class="sub-title">HA<span class="DSTletter">S</span>T (UTC-<span class="DSTnum">10</span>)</div>
|
||||||
|
<div class="time-display">
|
||||||
|
<div class="color-area"></div>
|
||||||
|
<div class="clock time-text"><time zoneOffset=10></time></div>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="null"><span class="DSTterm"></span><span class="DSTletter"></span><span class="DSTnum"></span></div> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="hi-col">
|
||||||
|
<!-- HAWAIIAN TIME -->
|
||||||
|
<div class="clock-box purple">
|
||||||
|
<div class="title">Hawaii Standard Time</div>
|
||||||
|
<div class="sub-title">HST (UTC-10)</div>
|
||||||
|
<div class="time-display">
|
||||||
|
<div class="color-area no-saving"></div>
|
||||||
|
<div class="clock time-text"><time zoneOffset=10></time></div>
|
||||||
|
</div>
|
||||||
|
<div class="null"><span class="DSTterm"></span><span class="DSTletter"></span><span class="DSTnum"></span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- HAWAII MAP GRAPHIC -->
|
||||||
|
<img src="img/map-elements/hawaii.svg" class="side-img" alt="Hawaii Map">
|
||||||
|
|
||||||
|
<!-- SAMOA TIME -->
|
||||||
|
<div class="clock-box dk-blue">
|
||||||
|
<div class="title">Samoa Standard Time</div>
|
||||||
|
<div class="sub-title">SST (UTC-11)</div>
|
||||||
|
<div class="time-display">
|
||||||
|
<div class="color-area no-saving"></div>
|
||||||
|
<div class="clock time-text"><time zoneOffset=11></time></div>
|
||||||
|
</div>
|
||||||
|
<div class="null"><span class="DSTterm"></span><span class="DSTletter"></span><span class="DSTnum"></span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- CHAMORRO TIME -->
|
||||||
|
<div class="clock-box dk-blue">
|
||||||
|
<div class="title">Chamorro Standard Time</div>
|
||||||
|
<div class="sub-title">CHST (UTC+10)</div>
|
||||||
|
<div class="time-display">
|
||||||
|
<div class="color-area no-saving"></div>
|
||||||
|
<div class="clock time-text"><time zoneOffset=-10></time></div>
|
||||||
|
</div>
|
||||||
|
<div class="null"><span class="DSTterm"></span><span class="DSTletter"></span><span class="DSTnum"></span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- PUERTO RICO LT COL-->
|
||||||
|
<div id="pr-lt" class="pad-2 margTop15">
|
||||||
|
<div class="clock-box dk-blue">
|
||||||
|
<div class="title">Atlantic Standard Time</div>
|
||||||
|
<div class="sub-title"><b>Puerto Rico / US Virgin Islands</b></div>
|
||||||
|
<div class="sub-title">AST (UTC-4)</div>
|
||||||
|
<div class="time-display">
|
||||||
|
<div class="color-area no-saving"></div>
|
||||||
|
<div class="clock time-text"><time zoneOffset=4></time></div>
|
||||||
|
</div>
|
||||||
|
<div class="null"><span class="DSTterm"></span><span class="DSTletter"></span><span class="DSTnum"></span></div>
|
||||||
|
</div> </div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- MAIN COLUMN -->
|
||||||
|
<div id="col-main">
|
||||||
|
<div class="pad-2">
|
||||||
|
<div style="height: 0px;"> </div>
|
||||||
|
|
||||||
|
<!-- ROW FOR MAINLAND US CLOCK AREA -->
|
||||||
|
<div class="main-top">
|
||||||
|
|
||||||
|
<!-- PACIFIC TIME -->
|
||||||
|
<div class="col-25">
|
||||||
|
<div class="pad-2">
|
||||||
|
<div class="clock-box orange dst-clock">
|
||||||
|
<div class="title">Pacific <span class="DSTterm">Standard</span> Time</div>
|
||||||
|
<div class="sub-title">P<span class="DSTletter">S</span>T (UTC-<span class="DSTnum">8</span>)</div>
|
||||||
|
<div class="time-display">
|
||||||
|
<div class="color-area"></div>
|
||||||
|
<div class="clock time-text"><time zoneOffset=8 />SYNCHRONIZING</time></div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- MOUNTAIN TIME -->
|
||||||
|
<div class="col-25">
|
||||||
|
<div class="pad-2">
|
||||||
|
<div class="clock-box green dst-clock">
|
||||||
|
<div class="title">Mountain <span class="DSTterm">Standard</span> Time</div>
|
||||||
|
<div class="sub-title">M<span class="DSTletter">S</span>T (UTC-<span class="DSTnum">7</span>)</div>
|
||||||
|
<div class="time-display">
|
||||||
|
<div class="color-area"></div>
|
||||||
|
<div class="clock time-text"><time zoneOffset=7>SYNCHRONIZING</time></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- CENTRAL TIME -->
|
||||||
|
<div class="col-25">
|
||||||
|
<div class="pad-2">
|
||||||
|
<div class="clock-box yellow dst-clock">
|
||||||
|
<div class="title">Central <span class="DSTterm">Standard</span> Time</div>
|
||||||
|
<div class="sub-title">C<span class="DSTletter">S</span>T (UTC-<span class="DSTnum">6</span>)</div>
|
||||||
|
<div class="time-display">
|
||||||
|
<div class="color-area"></div>
|
||||||
|
<div class="clock time-text"><time zoneOffset=6>SYNCHRONIZING</time></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- EASTERN TIME -->
|
||||||
|
<div class="col-25">
|
||||||
|
<div class="pad-2">
|
||||||
|
<div class="clock-box red dst-clock">
|
||||||
|
<div class="title">Eastern <span class="DSTterm">Standard</span> Time</div>
|
||||||
|
<div class="sub-title">E<span class="DSTletter">S</span>T (UTC-<span class="DSTnum">5</span>)</div>
|
||||||
|
<div class="time-display">
|
||||||
|
<div class="color-area"></div>
|
||||||
|
<div class="clock time-text"><time zoneOffset=5>SYNCHRONIZING</time></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="clear"></div>
|
||||||
|
</div>
|
||||||
|
<!-- END TOP ROW -->
|
||||||
|
|
||||||
|
|
||||||
|
<div id="mainland">
|
||||||
|
<!-- ARIZONA/MOUNTAIN STANDARD TIME -->
|
||||||
|
<div id="az-clock" class="col-25">
|
||||||
|
<div class="pad-2">
|
||||||
|
<div class="clock-box green">
|
||||||
|
<div class="title">Arizona Mountain<br>Standard Time</div>
|
||||||
|
<div class="sub-title">MST (UTC-7)</div>
|
||||||
|
<div class="time-display">
|
||||||
|
<div class="color-area no-saving"></div>
|
||||||
|
<div class="clock time-text"><time zoneOffset=7></time></div>
|
||||||
|
</div>
|
||||||
|
<div class="null"><span class="DSTterm"></span><span class="DSTletter"></span><span class="DSTnum"></span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- BEGIN MAIN MAP AREA -->
|
||||||
|
<img src="img/map-elements/united-states-map.svg" class="full-img" alt="United States Time Zone Map">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- RIGHT COLUMN -->
|
||||||
|
<div id="col-right">
|
||||||
|
<div class="pad-4">
|
||||||
|
|
||||||
|
<!-- 24HR CHECKBOX -->
|
||||||
|
<div class='hours-wrapper'>
|
||||||
|
<div class='am-pm'></div>
|
||||||
|
<input id='twenty-four' type='checkbox' class="css-checkbox">
|
||||||
|
<label class='css-label' for='twenty-four'>24-Hour Clock Display</label>
|
||||||
|
<div class="clear"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="clock-utc">
|
||||||
|
<p class="text-xs">Coordinated Universal Time (UTC)</p>
|
||||||
|
<div class="clock text-lg"><time id="timeUTC"></time></div>
|
||||||
|
<div class="null"><span class="DSTterm"></span><span class="DSTletter"></span><span class="DSTnum"></span></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<p class="sub-title ital">UTC is always displayed as a 24-hour clock.</p>
|
||||||
|
|
||||||
|
<!-- MAIN CLOCK AREA -->
|
||||||
|
<div id="main-time-area">
|
||||||
|
<p>Your Device's Clock <span id="myTimeTitle">UTC-0</span> <br>
|
||||||
|
<div id='myDate'></div>
|
||||||
|
<canvas id="analog-clock" width=200 height=200></canvas>
|
||||||
|
<br>
|
||||||
|
<span id="myTime"></span><br><br>
|
||||||
|
Your clock is off by: <br><span id="realTimeDif"></span> s</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- PUERTO RICO RT COL-->
|
||||||
|
<div id="pr-rt" class="pad-2 margTop15">
|
||||||
|
<div class="clock-box dk-blue">
|
||||||
|
<div class="title">Atlantic Standard Time</div>
|
||||||
|
<div class="sub-title"><b>Puerto Rico / US Virgin Islands</b></div>
|
||||||
|
<div class="sub-title">AST (UTC-4)</div>
|
||||||
|
<div class="time-display">
|
||||||
|
<div class="color-area no-saving"></div>
|
||||||
|
<div class="clock time-text"><time zoneOffset=4></time></div>
|
||||||
|
</div>
|
||||||
|
<div class="null"><span class="DSTterm"></span><span class="DSTletter"></span><span class="DSTnum"></span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="rightColBot">
|
||||||
|
<a href="/simple/">Simple View</a>
|
||||||
|
<a href="/api/time">Time API</a>
|
||||||
|
<div class="imgSmText">
|
||||||
|
<img src="img/stripes.svg" class="striped-box" alt="Hashed image for Daylight Saving Time not observed"> = DAYLIGHT SAVING TIME NOT OBSERVED
|
||||||
|
<p>Clocks are corrected for network delay</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="clear"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="nist-footer">
|
||||||
|
<div class="nist-footer__inner">
|
||||||
|
<div class="nist-footer__menu" role="navigation">
|
||||||
|
<ul>
|
||||||
|
<li class="nist-footer__menu-item"><a href="/timegov/">Map View</a></li>
|
||||||
|
<li class="nist-footer__menu-item"><a href="/simple/">Simple View</a></li>
|
||||||
|
<li class="nist-footer__menu-item"><a href="/api/time">JSON Time API</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="nist-footer__logo">
|
||||||
|
<center><img src="https://storage.googleapis.com/sethfreiberg.com/sethflix/favicon.png" alt="Atermiter-X79 logo" /><p>Atermiter-X79 Official Time</p></center>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<div class='time-zone-container' style='display:none'>
|
||||||
|
<div class='dropdown-container'>
|
||||||
|
<select id='dst-time-zone'>
|
||||||
|
</select>
|
||||||
|
<select id='no-dst-time-zone'>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="responseTime"></div>
|
||||||
|
<script src="scripts/jquery-3.7.1.min.js" type="text/javascript"></script>
|
||||||
|
<script type="text/javascript" src="scripts/analogClock.js"></script>
|
||||||
|
<script type="text/javascript" src="scripts/application.js"></script>
|
||||||
|
<script type="text/javascript" src="scripts/zzz__bd08fcbbc8bd600a5f9e994be2de69f7cb26b9f4.js"></script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,550 @@
|
|||||||
|
@charset "utf-8";
|
||||||
|
/* CSS Document */
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1em;
|
||||||
|
font-family: Source Sans Pro Web,Helvetica Neue,Helvetica,Roboto,Arial,sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inner {
|
||||||
|
padding: 0 5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gap {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#top-grey {
|
||||||
|
padding-bottom: .25rem;
|
||||||
|
padding-top: .25rem;
|
||||||
|
min-height: 0;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#arrow {
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#arrow[aria-expanded=false] {
|
||||||
|
background-image: url(../img/arrow-down.svg);
|
||||||
|
}
|
||||||
|
#arrow[aria-expanded=true] {
|
||||||
|
background-image: url(../img/arrow-up.svg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#top-grey-exp {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-50 {
|
||||||
|
width: 50%;
|
||||||
|
float: left;
|
||||||
|
line-height: 1.5em;
|
||||||
|
}
|
||||||
|
.no-marg {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.pad-4 {
|
||||||
|
padding: 4%;
|
||||||
|
}
|
||||||
|
.pad-2 {
|
||||||
|
padding: 2%;
|
||||||
|
}
|
||||||
|
#col-main .pad-2 {
|
||||||
|
padding: 2% 2% 0px 2%;
|
||||||
|
}
|
||||||
|
.clear {
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
.img10 {
|
||||||
|
width: 10%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
#title-area {
|
||||||
|
background-color: #000;
|
||||||
|
color: #fff;
|
||||||
|
padding: 4px 0;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
font-size: 1.55em;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
#logo {
|
||||||
|
width: 1.25em;
|
||||||
|
height: 1.25em;
|
||||||
|
margin-right: 12px;
|
||||||
|
vertical-align: -0.12em;
|
||||||
|
}
|
||||||
|
#logo2 { /* added for USNO logo and increased margins */
|
||||||
|
width: 44px;
|
||||||
|
height: auto;
|
||||||
|
margin-left: 12px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
#clock-utc {
|
||||||
|
border: solid 1px #000;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.ital {
|
||||||
|
font-style: italic;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 15px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#myDate {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
#myTime {
|
||||||
|
font-size: 1.4em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.imgSmText {
|
||||||
|
font-size: 0.7em;
|
||||||
|
line-height: 1.5em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
#rightColBot a {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
#rightColBot {
|
||||||
|
display: block;
|
||||||
|
margin-top: 15px;
|
||||||
|
padding-top: 15px;
|
||||||
|
border-top: 1px solid #000;
|
||||||
|
}
|
||||||
|
.text-xs {
|
||||||
|
font-size: 0.8em;
|
||||||
|
margin: 5px 10px 0;
|
||||||
|
}
|
||||||
|
.text-lg {
|
||||||
|
font-size: 1.8em;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
#callout-bar {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #999;
|
||||||
|
padding: .25rem 0;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
#callout-bar p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content{
|
||||||
|
display: table;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
#col-left {
|
||||||
|
width: 15%;
|
||||||
|
background-color: #d9e8f6;
|
||||||
|
display: table-cell;
|
||||||
|
}
|
||||||
|
#col-main {
|
||||||
|
width: 70%;
|
||||||
|
display: table-cell;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#col-right {
|
||||||
|
width: 15%;
|
||||||
|
background-color: #e2e2e2;
|
||||||
|
display: table-cell;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.col-25 {
|
||||||
|
width: 25%;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#noncontTitle,
|
||||||
|
#contTitle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pr-lt {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
STYLES FOR CLOCK AREAS
|
||||||
|
*/
|
||||||
|
.clock-box {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
#col-main .clock-box {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.clock-box .title {
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.9em;
|
||||||
|
font-weight: bold;
|
||||||
|
/*color: #000000 !important;*/
|
||||||
|
}
|
||||||
|
.sub-title {
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.7em;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.time-display {
|
||||||
|
display: table;
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
.color-area {
|
||||||
|
width: 5%;
|
||||||
|
display: table-cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-text {
|
||||||
|
background-color: #000;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 1.4em;
|
||||||
|
padding: 5px;
|
||||||
|
display: table-cell;
|
||||||
|
}
|
||||||
|
.side-img {
|
||||||
|
max-width: 90%;
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto 15px;
|
||||||
|
}
|
||||||
|
.margTop15 {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
#twenty-hour {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
#main-time-area {
|
||||||
|
background-color: #000;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
color: #fff;
|
||||||
|
padding: 15px 0;
|
||||||
|
}
|
||||||
|
.full-img {
|
||||||
|
width: 94.5%;
|
||||||
|
display: block;
|
||||||
|
margin: 0 2.3%;
|
||||||
|
}
|
||||||
|
#mainland {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
#az-clock {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 100;
|
||||||
|
bottom: 0;
|
||||||
|
left: 5%;
|
||||||
|
}
|
||||||
|
.hours-wrapper {
|
||||||
|
padding: 5px;
|
||||||
|
background: #eee;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
.striped-box {
|
||||||
|
max-height: .75rem;
|
||||||
|
max-width: .75rem;
|
||||||
|
}
|
||||||
|
#analog-clock {
|
||||||
|
max-width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#responseTime {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CUSTOM CHECKBOX STYLES */
|
||||||
|
input[type=checkbox].css-checkbox {
|
||||||
|
position:absolute; z-index:-1000; left:-1000px; overflow: hidden; clip: rect(0 0 0 0); height:1px; width:1px; margin:-1px; padding:0; border:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=checkbox].css-checkbox + label.css-label {
|
||||||
|
padding-left:20px;
|
||||||
|
height:15px;
|
||||||
|
display:inline-block;
|
||||||
|
line-height:15px;
|
||||||
|
background-repeat:no-repeat;
|
||||||
|
background-position: 0 0;
|
||||||
|
vertical-align:middle;
|
||||||
|
cursor:pointer;
|
||||||
|
font-size: .7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=checkbox].css-checkbox:checked + label.css-label {
|
||||||
|
background-position: 0 -15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label.css-label {
|
||||||
|
background-image:url(../img/csscheckbox.png);
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-khtml-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.no-saving {
|
||||||
|
background-image: url(../img/diagonal-white.png);
|
||||||
|
}
|
||||||
|
.blue .color-area {
|
||||||
|
background-color: #0060a8;
|
||||||
|
}
|
||||||
|
.blue .title {
|
||||||
|
color: #0060a8;
|
||||||
|
}
|
||||||
|
.purple .color-area {
|
||||||
|
background-color: #aa378d;
|
||||||
|
}
|
||||||
|
.purple .title {
|
||||||
|
color: #aa378d;
|
||||||
|
}
|
||||||
|
.dk-blue .color-area {
|
||||||
|
background-color: #1b0459;
|
||||||
|
}
|
||||||
|
.dk-blue .title {
|
||||||
|
color: #1b0459;
|
||||||
|
}
|
||||||
|
.orange .color-area {
|
||||||
|
background-color: #d66733;
|
||||||
|
}
|
||||||
|
.orange .title {
|
||||||
|
color: #d66733;
|
||||||
|
}
|
||||||
|
.green .color-area {
|
||||||
|
background-color: #63a952;
|
||||||
|
}
|
||||||
|
.green .title {
|
||||||
|
color: #63a952;
|
||||||
|
}
|
||||||
|
.yellow .color-area {
|
||||||
|
background-color: #e6a730;
|
||||||
|
}
|
||||||
|
.yellow .title {
|
||||||
|
color: #e6a730;
|
||||||
|
}
|
||||||
|
.red .color-area {
|
||||||
|
background-color: #cc2131;
|
||||||
|
}
|
||||||
|
.red .title {
|
||||||
|
color: #cc2131;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* ///////////////////////////////////////////////////////////
|
||||||
|
////////////////////// RESPONSIVE STYLES //////////////////
|
||||||
|
///////////////////////////////////////////////////////////
|
||||||
|
*/
|
||||||
|
|
||||||
|
@media only screen and (min-width : 300px) and (max-width : 1260px) {
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 2.0em; /* was 1.8 */
|
||||||
|
}
|
||||||
|
#content {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: wrap;
|
||||||
|
display: -webkit-flex; /* Safari */
|
||||||
|
-webkit-flex-flow: wrap; /* Safari 6.1+ */
|
||||||
|
}
|
||||||
|
|
||||||
|
#col-main {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
order: 1;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
#col-left {
|
||||||
|
width: 70%;
|
||||||
|
display: block;
|
||||||
|
order: 2;
|
||||||
|
}
|
||||||
|
#noncontTitle, #contTitle {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1.5em;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
#col-right {
|
||||||
|
width: 30%;
|
||||||
|
display: block;
|
||||||
|
order: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
#main-time-area {
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 15px 0;
|
||||||
|
}
|
||||||
|
#ak-col, #hi-col {
|
||||||
|
width: 50%;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pr-lt {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pr-rt {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#rightColBot {
|
||||||
|
border-top: 1px solid #000;
|
||||||
|
margin-top: 30px;
|
||||||
|
position: relative;
|
||||||
|
padding-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clock-box .title {
|
||||||
|
font-size: 1em;
|
||||||
|
line-height: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-title {
|
||||||
|
font-size: .7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-text {
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-xs {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=checkbox].css-checkbox + label.css-label {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.imgSmText {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@media only screen and (min-device-width : 320px) and (max-device-width : 740px) {
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 2.1em; /* was 2.3 */
|
||||||
|
}
|
||||||
|
#content {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: wrap;
|
||||||
|
display: -webkit-flex; /* Safari */
|
||||||
|
-webkit-flex-flow: wrap; /* Safari 6.1+ */
|
||||||
|
}
|
||||||
|
|
||||||
|
#col-main {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
order: 1;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
#col-left {
|
||||||
|
width: 70%;
|
||||||
|
display: block;
|
||||||
|
order: 2;
|
||||||
|
}
|
||||||
|
#noncontTitle, #contTitle {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1.5em;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
#col-right {
|
||||||
|
width: 30%;
|
||||||
|
display: block;
|
||||||
|
order: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
#main-time-area {
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 15px 0;
|
||||||
|
}
|
||||||
|
#ak-col, #hi-col {
|
||||||
|
width: 50%;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pr-lt {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pr-rt {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#rightColBot {
|
||||||
|
border-top: 1px solid #000;
|
||||||
|
margin-top: 30px;
|
||||||
|
position: relative;
|
||||||
|
padding-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clock-box .title {
|
||||||
|
font-size: 1.5em;
|
||||||
|
line-height: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-title {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-text {
|
||||||
|
font-size: 1.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-xs {
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=checkbox].css-checkbox + label.css-label {
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.imgSmText {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* ///////////////////////////////////////////////////////////
|
||||||
|
/////// STYLES AND DESIGN USED WITH PERMISSION /////////
|
||||||
|
///////////////// MORGANFEBREY.COM ////////////////////////
|
||||||
|
///////////////////////////////////////////////////////////
|
||||||
|
*/
|
||||||
|
|
||||||
@@ -0,0 +1,370 @@
|
|||||||
|
|
||||||
|
timeDotGov = {};
|
||||||
|
timeDotGov.clock = {};
|
||||||
|
timeDotGov.clockController = {};
|
||||||
|
timeDotGov.clockController.dsClock = {}
|
||||||
|
|
||||||
|
timeDotGov.data = {
|
||||||
|
"then": null,
|
||||||
|
"serverTime": null,
|
||||||
|
"responseTime": null,
|
||||||
|
"RThalf": null,
|
||||||
|
"realTimeDif": null,
|
||||||
|
"requestTime": null,
|
||||||
|
"leapsecond": null,
|
||||||
|
"dststart": null,
|
||||||
|
"dstend": null,
|
||||||
|
"currYear": null,
|
||||||
|
"dstDates": null,
|
||||||
|
"leapDate": null,
|
||||||
|
"clockinstances": [],
|
||||||
|
"leapFlag": null,
|
||||||
|
"leapsec60": null,
|
||||||
|
"myhour0": "0",
|
||||||
|
"myhour1": "0",
|
||||||
|
"mysec0": "0",
|
||||||
|
"mysec1": "0",
|
||||||
|
"currentTime": null,
|
||||||
|
"twentyFour": function() {
|
||||||
|
twentyFour = document.getElementById("twenty-four");
|
||||||
|
return twentyFour.checked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var offsetCheck = true;
|
||||||
|
var daylightTitles = false;
|
||||||
|
|
||||||
|
// CREATE ARRAY THAT WILL STORE WHICH CLOCK NUMS HAVE BEEN UPDATED
|
||||||
|
var dstClocksUpdated = [];
|
||||||
|
timeDotGov.clockController.getParameterByName = function(name) {
|
||||||
|
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
|
||||||
|
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
|
||||||
|
results = regex.exec(location.search);
|
||||||
|
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
|
||||||
|
}
|
||||||
|
|
||||||
|
timeDotGov.clockController.getleapdate = function() {
|
||||||
|
var leap = new Date();
|
||||||
|
var temp1 = timeDotGov.data.leapdate.split(" ");
|
||||||
|
var leapdateYear = temp1[0];
|
||||||
|
var leapdateMonth = temp1[1];
|
||||||
|
|
||||||
|
leapdateMonth = leapdateMonth - 1; // month convention = 0-11 (Jan = 0)
|
||||||
|
|
||||||
|
var leapdateDay = temp1[2];
|
||||||
|
|
||||||
|
leap.setUTCFullYear(leapdateYear, leapdateMonth, leapdateDay);
|
||||||
|
leap.setUTCHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
timeDotGov.data.leapsecond = leap.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET TIME FROM SERVER
|
||||||
|
timeDotGov.clockController.dsClock.doRequest = function() {
|
||||||
|
d = new Date();
|
||||||
|
var xmlHttp = new XMLHttpRequest();
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
////// USE OF THIS .CGI BY OUTSIDE SITES OR APPLICATIONS IS STRICTLY PROHIBITED ////
|
||||||
|
// OR USING THE TIME FROM THIS SITE IN ANY WAY FOR OTHER SITES IS ALSO PROHIBITED //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
xmlHttp.open("GET", "/zzz__2fbc6c3300df7e4483acd44c5044098a9fcc61d6.cgi?disablecache=" + d.getTime(), false);
|
||||||
|
xmlHttp.send(null);
|
||||||
|
return xmlHttp.responseText;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET t1, t2, t3, t4 in doRequest AND CALCULATE DELAYS IN doData
|
||||||
|
timeDotGov.clockController.checkservertime = function() {
|
||||||
|
var o = new Object();
|
||||||
|
var d = new Date();
|
||||||
|
var currentTimeObj = new Date();
|
||||||
|
timeDotGov.data.requestTime = currentTimeObj.getTime();
|
||||||
|
timeDotGov.data.currentTime = timeDotGov.clockController.dsClock.doRequest(); // GET NIST TIME FROM SERVER
|
||||||
|
timeDotGov.clockController.doData();
|
||||||
|
}
|
||||||
|
// PARSE DATA FROM AUXDATA.XML
|
||||||
|
timeDotGov.clockController.doAuxData = function() {
|
||||||
|
parser = new DOMParser();
|
||||||
|
xmlDoc = parser.parseFromString(timeDotGov.auxdata, "text/xml");
|
||||||
|
timeDotGov.data.curryear = xmlDoc.getElementsByTagName("currYear")[0].childNodes[0].nodeValue;
|
||||||
|
timeDotGov.data.dstdates = xmlDoc.getElementsByTagName("DstDates")[0].childNodes[0].nodeValue;
|
||||||
|
timeDotGov.data.leapdate = xmlDoc.getElementsByTagName("LeapDate")[0].childNodes[0].nodeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
timeDotGov.clockController.getDSTdates = function() {
|
||||||
|
var dstStartDate = new Date();
|
||||||
|
var dstEndDate = new Date();
|
||||||
|
var temp=timeDotGov.data.dstdates.split(" "); // makes an array of the string elements
|
||||||
|
var startmonth = temp[0];
|
||||||
|
|
||||||
|
startmonth = startmonth - 1; // to follow js convention of month = 0-11 (Jan = 0)
|
||||||
|
|
||||||
|
var startday = temp[1];
|
||||||
|
var endmonth = temp[2];
|
||||||
|
|
||||||
|
endmonth = endmonth - 1;
|
||||||
|
|
||||||
|
var endday = temp[3];
|
||||||
|
|
||||||
|
dstStartDate.setUTCFullYear(timeDotGov.data.curryear, startmonth, startday);
|
||||||
|
dstStartDate.setUTCHours(2, 0, 0, 0);
|
||||||
|
dstEndDate.setUTCFullYear(timeDotGov.data.curryear, endmonth, endday);
|
||||||
|
dstEndDate.setUTCHours(1, 0, 0, 0);
|
||||||
|
timeDotGov.data.dstStart = dstStartDate.getTime();
|
||||||
|
timeDotGov.data.dstEnd = dstEndDate.getTime();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
timeDotGov.clockController.auxdata = function() {
|
||||||
|
this.doAuxData();
|
||||||
|
this.getDSTdates();
|
||||||
|
this.getleapdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
timeDotGov.clockController.doData = function() {
|
||||||
|
|
||||||
|
var parser = new DOMParser();
|
||||||
|
var xmlDoc = parser.parseFromString(timeDotGov.data.currentTime, "text/xml");
|
||||||
|
|
||||||
|
var t2 = xmlDoc.getElementsByTagName("timestamp")[0].getAttribute("time2");
|
||||||
|
var t3 = xmlDoc.getElementsByTagName("timestamp")[0].getAttribute("time3");
|
||||||
|
var serverDelay = Math.round((t3 - t2) / 1000); // server delay in milliseconds
|
||||||
|
timeDotGov.data.serverTime = Math.round(t3/1000); // Server time in milliseconds
|
||||||
|
|
||||||
|
var currentTimeObj2 = new Date();
|
||||||
|
timeDotGov.data.responseTime = currentTimeObj2.getTime(); // t4
|
||||||
|
timeDotGov.data.RThalf = ((timeDotGov.data.responseTime - timeDotGov.data.requestTime) - serverDelay) / 2; // (t4 - t1) - (t3 -t2) NTP EQ
|
||||||
|
timeDotGov.data.realTimeDif = timeDotGov.data.serverTime - timeDotGov.data.responseTime; // t3-t4 used for correction to local clock for official time
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
// USE OFFSET CHECK VAR TO ONLY CHECK CLIENT CLOCK ONCE ///
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
if (offsetCheck) {
|
||||||
|
// ROUND TO MILLISECONDS - USE (* -1) to invert timediff pos/neg display value
|
||||||
|
var diff = (timeDotGov.data.realTimeDif/1000) * -1;
|
||||||
|
var diffDisplay = diff.toFixed(3);
|
||||||
|
if (diffDisplay > 0 ) {
|
||||||
|
diffDisplay = "+" + diffDisplay;
|
||||||
|
}
|
||||||
|
document.getElementById("realTimeDif").innerHTML = diffDisplay;
|
||||||
|
offsetCheck = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
timeDotGov.clockController.runningclocks();
|
||||||
|
timeDotGov.data.leapFlag = "false";
|
||||||
|
} // END OF doData FUNCTION
|
||||||
|
|
||||||
|
// REFRESHES CLOCKS AT THE TOP OF EACH *ACTUAL* SECOND
|
||||||
|
timeDotGov.clockController.runningclocks = function() {
|
||||||
|
var deviceClock = new Date();
|
||||||
|
var fractionalZone =(Math.abs((deviceClock.getTimezoneOffset()/60))) % 1; // IF CLIENT IS IN A ZONE WITH A FRACTIONAL HOUR, MAKE ADJUSTMENT (MODULO 1 TO GET THE FRACTIONAL HOUR)
|
||||||
|
if (fractionalZone != 0) {
|
||||||
|
fractionalZone = (1 - fractionalZone); // SUBTRACT FROM 1 TO GET VALID CORRECTION FOR 30 AND 45 MIN ZONES
|
||||||
|
}
|
||||||
|
var fractionalZoneMilli = fractionalZone * 3600000; // CONVERT PARTIAL HOUR TO MILLISECONDS, TO BE SUBTRACTED FROM ADJUSTED TIME
|
||||||
|
var s = new Date(); // convert PC time and delay back to seconds (this is UTC)
|
||||||
|
|
||||||
|
s.setTime(s.getTime() + timeDotGov.data.realTimeDif + timeDotGov.data.RThalf - fractionalZoneMilli); // CORRECT FOR PC CLOCK ERROR, HALF NETWORK DELAY AND CLIENT IN PARTIAL TIME ZONE
|
||||||
|
|
||||||
|
var sec = s.getSeconds();
|
||||||
|
if (sec != timeDotGov.data.previousSec) { // call handleonrefresh as soon as you see a new second
|
||||||
|
timeDotGov.data.previousSec = sec;
|
||||||
|
timeDotGov.clockController.handleonrefresh(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK IF USER CHANGED CLOCK or IF NEW DST STATE or LEAP SECOND, THEN REFRESH ALL CLOCKS
|
||||||
|
timeDotGov.clockController.handleonrefresh = function(s) {
|
||||||
|
//var mins = s.getMinutes();
|
||||||
|
var now = s.getTime(); // convert time to ms since epoch (UTC)
|
||||||
|
//if (0 <= mins < 30) {
|
||||||
|
// now = now + fractionalZoneMilli;
|
||||||
|
//} else {
|
||||||
|
// now = now - fractionalZoneMilli;
|
||||||
|
//}
|
||||||
|
|
||||||
|
if (timeDotGov.data.then == null) {
|
||||||
|
timeDotGov.data.then = now; // if it's the first round, don't let it fail
|
||||||
|
}
|
||||||
|
|
||||||
|
var DidUserChangeClock = Math.abs(now - timeDotGov.data.then);
|
||||||
|
if (DidUserChangeClock >= 2000) { // if pc clock changed then reset (|now-then| should only be 1 s)
|
||||||
|
location.reload();
|
||||||
|
timeDotGov.data.then = now;
|
||||||
|
} else {
|
||||||
|
timeDotGov.data.then = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeDotGov.data.leapsecond / 1000 == Math.floor(now / 1000)) {
|
||||||
|
timeDotGov.data.leapFlag = "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
var clocks = document.getElementsByClassName("clock");
|
||||||
|
|
||||||
|
var clockNum; // SET TO VALUE OF 'i' IN THE FOLLOWING LOOP TO REPRESENT EACH CLOCK INSTANCE
|
||||||
|
|
||||||
|
// SET ALL OF THE CLOCKS BY CALLING setCurrentTime FOR EACH CLOCK INSTANCE
|
||||||
|
for(var i =0; i < clocks.length; i++ ){
|
||||||
|
|
||||||
|
var clock = clocks[i].getElementsByTagName("time")[0];
|
||||||
|
var zoneOffset = clocks[i].getElementsByTagName("time")[0].getAttribute("zoneOffset") || 0;
|
||||||
|
// GIVE VAR CLOCKNUM VALUE OF i
|
||||||
|
clockNum = i;
|
||||||
|
clock.innerHTML = timeDotGov.clock.setCurrentTime(timeDotGov.data.clockinstances[i], now, timeDotGov.data.twentyFour(), timeDotGov.data.dstStart, timeDotGov.data.dstEnd, zoneOffset, timeDotGov.data.leapFlag, timeDotGov.data.leapsec60, timeDotGov.data.RThalf, clockNum);
|
||||||
|
document.getElementById('timeUTC').innerHTML = timeDotGov.clock.setCurrentTime(timeDotGov.data.clockinstances[i], now, true, timeDotGov.data.dstStart, timeDotGov.data.dstEnd, 0, timeDotGov.data.leapFlag, timeDotGov.data.leapsec60, timeDotGov.data.RThalf, 999); // LAST VAR IS CLOCKNUM, PASSING AS '999' TO AVOID DUPLICATE 'i' VARIABLE VALUE SEND TO SETCURRENTTIME FUNCTION
|
||||||
|
}
|
||||||
|
|
||||||
|
} // END OF handleonrefresh FUNCTION
|
||||||
|
|
||||||
|
// CREATES CLOCK DIGITS AND DST/ST LABELS FOR EACH CLOCK INSTANCE
|
||||||
|
timeDotGov.clock.setCurrentTime = function(clock, now, twentyFour, dstStart, dstEnd, zoneOffset, leapFlag, leapsec60, RThalf, clockNum) {
|
||||||
|
|
||||||
|
var displayTime = new Date();
|
||||||
|
now = (now - (zoneOffset * 3600000));
|
||||||
|
|
||||||
|
if (leapFlag == "true"){
|
||||||
|
now = now - 1000; // if leap has occurred, show (seconds - 1) until next sync
|
||||||
|
var leapsec60 = "true"; // var to show a 60 instead of 59
|
||||||
|
}
|
||||||
|
|
||||||
|
displayTime.setTime(now);
|
||||||
|
|
||||||
|
var year = displayTime.getUTCFullYear();
|
||||||
|
var hourNum = displayTime.getUTCHours();
|
||||||
|
var minNum = displayTime.getMinutes();
|
||||||
|
var secNum = displayTime.getSeconds();
|
||||||
|
|
||||||
|
// CREATE ARRAY OF THE CLOCK NUMS WHO FOLLOW DST
|
||||||
|
// ALSO CREATE ARRAY OF DST LABEL CLASSES ////////////////////////
|
||||||
|
var DSTclocksArray = [ 0, 1, 6, 7, 8, 9 ]; // THESE ARE THE clockNums FOR THE ZONES THAT FOLLOW DST
|
||||||
|
var dstLabels = document.getElementsByClassName("DSTterm"); // CREATE ARRAY FOR DST LABELS
|
||||||
|
var dstLetters = document.getElementsByClassName("DSTletter"); // CREATE ARRAY FOR DST ABBREVIATION
|
||||||
|
var dstNums = document.getElementsByClassName("DSTnum"); // CREATE ARRAY FOR DST OFFSET NUM
|
||||||
|
|
||||||
|
if ( now >= dstStart && now <= dstEnd ) { // CHECK IF THIS SECOND IS DST OR NOT
|
||||||
|
|
||||||
|
// CHECK IF CLOCK NUM IS IN DST ARRAY, IF SO, ADD THE DST HOUR AND LABELS
|
||||||
|
// if ( DSTclocksArray.includes(clockNum) ) {
|
||||||
|
if ( DSTclocksArray.indexOf(clockNum) > -1 ) { // indexOf fixes a chance in ie
|
||||||
|
hourNum = hourNum + 1;
|
||||||
|
// AT END OF FUNC ADD/PUSH CLOCKNUM TO dstClocksUpdated ARRAY, IF CLOCKNUM IS IN THERE, DONT UPDATE AGAIN
|
||||||
|
// if ( !dstClocksUpdated.includes( clockNum ) ) {
|
||||||
|
if ( dstClocksUpdated.indexOf( clockNum ) == -1 ) { // indexOf fixes a chance in ie
|
||||||
|
dstLabels[clockNum].innerHTML = "DAYLIGHT";
|
||||||
|
dstLetters[clockNum].innerHTML = "D";
|
||||||
|
var standardNum = Number(dstNums[clockNum].innerHTML);
|
||||||
|
dstNums[clockNum].innerHTML = standardNum - 1;
|
||||||
|
dstClocksUpdated.push(clockNum); // ADD CLOCK NUM TO ARRAY OF CLOCKS UPDATED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// IF DST OR INCREMENTED HOUR GOES INTO NEXT DAY, RESET TO HOUR ZERO AND ADD A DAY
|
||||||
|
if (hourNum > 23) {
|
||||||
|
hourNum = 0;
|
||||||
|
now = (now + 3600000) // advance now by one hour
|
||||||
|
displayTime.setTime(now); // reset displaytime so day/date/month are correct with new day
|
||||||
|
}
|
||||||
|
// CHECK FOR AND IMPLEMENT LEAP SECOND
|
||||||
|
if (leapFlag == "true") {
|
||||||
|
if (leapsec60 == "true") {
|
||||||
|
if (minNum == "59") {
|
||||||
|
if (secNum == "59") { // if leapsec, and 59:59 then show 60 and reset
|
||||||
|
secNum = "60";
|
||||||
|
leapsec60 = "false";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var hourLabel = "";
|
||||||
|
|
||||||
|
// 12-HOUR OR 24-HOUR DISPLAY
|
||||||
|
|
||||||
|
// BOX NOT CHECKED SO 12-HOUR DISPLAY
|
||||||
|
if (!(twentyFour)) {
|
||||||
|
|
||||||
|
if (hourNum > 11) {
|
||||||
|
hourNum -= 12;
|
||||||
|
am_pm = "P.M.";
|
||||||
|
} else {
|
||||||
|
am_pm = "A.M.";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
am_pm = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
var newVal;
|
||||||
|
// SET HOURS
|
||||||
|
if(hourNum > 9) {
|
||||||
|
newVal = Number(String(hourNum).charAt(0));
|
||||||
|
if(timeDotGov.data.myhour0 != newVal) {
|
||||||
|
timeDotGov.data.myhour0 = newVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(Number(timeDotGov.data.myhour0) != 0) {
|
||||||
|
timeDotGov.data.myhour0 = "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Number(hourNum) > 9){
|
||||||
|
newVal=Number(String(hourNum).charAt(1));
|
||||||
|
if(timeDotGov.data.myhour1 != newVal) {
|
||||||
|
timeDotGov.data.myhour1 = newVal;
|
||||||
|
}
|
||||||
|
} else if (Number(timeDotGov.data.myhour1) != Number(hourNum)) {
|
||||||
|
timeDotGov.data.myhour1 = Number(hourNum);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((hourNum < 1) && (!(twentyFour))) { // if not 24hour time force the 12 so it's not 00
|
||||||
|
timeDotGov.data.myhour0 = 1;
|
||||||
|
timeDotGov.data.myhour1 = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SET MINUTES
|
||||||
|
if(minNum > 9) {
|
||||||
|
newVal = Number(String(minNum).charAt(0));
|
||||||
|
if (timeDotGov.data.mymin0 != newVal) {
|
||||||
|
timeDotGov.data.mymin0 = newVal;
|
||||||
|
}
|
||||||
|
} else if(Number(timeDotGov.data.mymin0) != 0) {
|
||||||
|
timeDotGov.data.mymin0 = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Number(minNum) > 9) {
|
||||||
|
newVal = Number(String(minNum).charAt(1));
|
||||||
|
if(timeDotGov.data.mymin1 != newVal) {
|
||||||
|
timeDotGov.data.mymin1 = newVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(Number(timeDotGov.data.mymin1) != Number(minNum)) {
|
||||||
|
timeDotGov.data.mymin1 = Number(minNum);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SET SECONDS
|
||||||
|
if(secNum > 9) {
|
||||||
|
newVal = Number(String(secNum).charAt(0));
|
||||||
|
if(timeDotGov.data.mysec0 != newVal) {
|
||||||
|
timeDotGov.data.mysec0 = newVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(Number(timeDotGov.data.mysec0) != 0) {
|
||||||
|
timeDotGov.data.mysec0 = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Number(secNum) > 9) {
|
||||||
|
newVal=Number(String(secNum).charAt(1));
|
||||||
|
if(timeDotGov.data.mysec1 != newVal) {
|
||||||
|
timeDotGov.data.mysec1 = newVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(Number(timeDotGov.data.mysec1) != Number(secNum)) {
|
||||||
|
timeDotGov.data.mysec1 = Number(secNum);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CREATE CLOCK DIGITS STRING TO DISPLAY
|
||||||
|
var clockdigits = (timeDotGov.data.myhour0 + "" + timeDotGov.data.myhour1 + ":" + timeDotGov.data.mymin0 + timeDotGov.data.mymin1 + ":" + timeDotGov.data.mysec0 + timeDotGov.data.mysec1);
|
||||||
|
clock = clockdigits + " " + am_pm;
|
||||||
|
return clock;
|
||||||
|
|
||||||
|
} // END OF setCurrentTime FUNCTION
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Header and Footer styles
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Header Styles */
|
||||||
|
.nist-header {
|
||||||
|
background: #000;
|
||||||
|
font-family: Helvetica, Arial, sans-serif;
|
||||||
|
padding: 10px 16px 0;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nist-header__logo-link {
|
||||||
|
display: inline-block;
|
||||||
|
height: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nist-header__logo-icon {
|
||||||
|
fill: #fff;
|
||||||
|
display: inline-block;
|
||||||
|
height: 16px;
|
||||||
|
position: relative;
|
||||||
|
top: -2px;
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nist-header__logo-image {
|
||||||
|
fill: #fff;
|
||||||
|
display: inline-block;
|
||||||
|
height: 24px;
|
||||||
|
width: 90px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Limit main content area width */
|
||||||
|
.nist-main {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
max-width: 1200px;
|
||||||
|
padding: 0 16px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make sure body has no margin or padding when using only this header component */
|
||||||
|
body {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer styles */
|
||||||
|
|
||||||
|
.nist-footer {
|
||||||
|
background: #333333;
|
||||||
|
position: relative;
|
||||||
|
z-index: 200;
|
||||||
|
font-family: Helvetica, Arial, sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nist-footer__inner {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
max-width: 1200px;
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nist-footer__inner:after {
|
||||||
|
content: "";
|
||||||
|
display: table;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nist-footer {
|
||||||
|
background: #333333;
|
||||||
|
color: white;
|
||||||
|
padding: 10px 0px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nist-footer a {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nist-footer .nist-footer__logo img {
|
||||||
|
/* width: 30%; 300px;*/
|
||||||
|
height: 100px;
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 5%; /*margin-left: 10%;*/
|
||||||
|
margin-right: 5%; /*margin-right: 10%auto;*/
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nist-footer__menu {
|
||||||
|
clear: both;
|
||||||
|
margin-bottom: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nist-footer__menu.first {
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nist-footer__menu ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nist-footer__menu-item {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 0;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nist-footer__menu-item:after {
|
||||||
|
content: '|';
|
||||||
|
margin-right: 1.6px;
|
||||||
|
display: inherit;
|
||||||
|
position: static;
|
||||||
|
font: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nist-footer__menu-item:last-child:after {
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nist-footer__menu-item a {
|
||||||
|
font-weight: normal;
|
||||||
|
margin-right: 6px;
|
||||||
|
white-space: nowrap;
|
||||||
|
padding: 0.5em 0;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stick footer to bottom of page
|
||||||
|
* Source: https://css-tricks.com/couple-takes-sticky-footer/
|
||||||
|
*/
|
||||||
|
|
||||||
|
html.nist-footer-bottom,
|
||||||
|
html.nist-footer-bottom body {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
html.nist-footer-bottom body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
html.nist-footer-bottom #main {
|
||||||
|
flex: 1 0 auto;
|
||||||
|
}
|
||||||
|
html.nist-footer-bottom .nist-footer {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,550 @@
|
|||||||
|
@charset "utf-8";
|
||||||
|
/* CSS Document */
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1em;
|
||||||
|
font-family: Source Sans Pro Web,Helvetica Neue,Helvetica,Roboto,Arial,sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inner {
|
||||||
|
padding: 0 5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gap {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#top-grey {
|
||||||
|
padding-bottom: .25rem;
|
||||||
|
padding-top: .25rem;
|
||||||
|
min-height: 0;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#arrow {
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#arrow[aria-expanded=false] {
|
||||||
|
background-image: url(../img/arrow-down.svg);
|
||||||
|
}
|
||||||
|
#arrow[aria-expanded=true] {
|
||||||
|
background-image: url(../img/arrow-up.svg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#top-grey-exp {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-50 {
|
||||||
|
width: 50%;
|
||||||
|
float: left;
|
||||||
|
line-height: 1.5em;
|
||||||
|
}
|
||||||
|
.no-marg {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.pad-4 {
|
||||||
|
padding: 4%;
|
||||||
|
}
|
||||||
|
.pad-2 {
|
||||||
|
padding: 2%;
|
||||||
|
}
|
||||||
|
#col-main .pad-2 {
|
||||||
|
padding: 2% 2% 0px 2%;
|
||||||
|
}
|
||||||
|
.clear {
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
.img10 {
|
||||||
|
width: 10%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
#title-area {
|
||||||
|
background-color: #000;
|
||||||
|
color: #fff;
|
||||||
|
padding: 4px 0;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
font-size: 1.55em;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
#logo {
|
||||||
|
width: 1.25em;
|
||||||
|
height: 1.25em;
|
||||||
|
margin-right: 12px;
|
||||||
|
vertical-align: -0.12em;
|
||||||
|
}
|
||||||
|
#logo2 { /* added for USNO logo and increased margins */
|
||||||
|
width: 44px;
|
||||||
|
height: auto;
|
||||||
|
margin-left: 12px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
#clock-utc {
|
||||||
|
border: solid 1px #000;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.ital {
|
||||||
|
font-style: italic;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 15px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#myDate {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
#myTime {
|
||||||
|
font-size: 1.4em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.imgSmText {
|
||||||
|
font-size: 0.7em;
|
||||||
|
line-height: 1.5em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
#rightColBot a {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
#rightColBot {
|
||||||
|
display: block;
|
||||||
|
margin-top: 15px;
|
||||||
|
padding-top: 15px;
|
||||||
|
border-top: 1px solid #000;
|
||||||
|
}
|
||||||
|
.text-xs {
|
||||||
|
font-size: 0.8em;
|
||||||
|
margin: 5px 10px 0;
|
||||||
|
}
|
||||||
|
.text-lg {
|
||||||
|
font-size: 1.8em;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
#callout-bar {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #999;
|
||||||
|
padding: .25rem 0;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
#callout-bar p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content{
|
||||||
|
display: table;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
#col-left {
|
||||||
|
width: 15%;
|
||||||
|
background-color: #d9e8f6;
|
||||||
|
display: table-cell;
|
||||||
|
}
|
||||||
|
#col-main {
|
||||||
|
width: 70%;
|
||||||
|
display: table-cell;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#col-right {
|
||||||
|
width: 15%;
|
||||||
|
background-color: #e2e2e2;
|
||||||
|
display: table-cell;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.col-25 {
|
||||||
|
width: 25%;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#noncontTitle,
|
||||||
|
#contTitle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pr-lt {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
STYLES FOR CLOCK AREAS
|
||||||
|
*/
|
||||||
|
.clock-box {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
#col-main .clock-box {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.clock-box .title {
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.9em;
|
||||||
|
font-weight: bold;
|
||||||
|
/*color: #000000 !important;*/
|
||||||
|
}
|
||||||
|
.sub-title {
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.7em;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.time-display {
|
||||||
|
display: table;
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
.color-area {
|
||||||
|
width: 5%;
|
||||||
|
display: table-cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-text {
|
||||||
|
background-color: #000;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 1.4em;
|
||||||
|
padding: 5px;
|
||||||
|
display: table-cell;
|
||||||
|
}
|
||||||
|
.side-img {
|
||||||
|
max-width: 90%;
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto 15px;
|
||||||
|
}
|
||||||
|
.margTop15 {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
#twenty-hour {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
#main-time-area {
|
||||||
|
background-color: #000;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
color: #fff;
|
||||||
|
padding: 15px 0;
|
||||||
|
}
|
||||||
|
.full-img {
|
||||||
|
width: 94.5%;
|
||||||
|
display: block;
|
||||||
|
margin: 0 2.3%;
|
||||||
|
}
|
||||||
|
#mainland {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
#az-clock {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 100;
|
||||||
|
bottom: 0;
|
||||||
|
left: 5%;
|
||||||
|
}
|
||||||
|
.hours-wrapper {
|
||||||
|
padding: 5px;
|
||||||
|
background: #eee;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
.striped-box {
|
||||||
|
max-height: .75rem;
|
||||||
|
max-width: .75rem;
|
||||||
|
}
|
||||||
|
#analog-clock {
|
||||||
|
max-width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#responseTime {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CUSTOM CHECKBOX STYLES */
|
||||||
|
input[type=checkbox].css-checkbox {
|
||||||
|
position:absolute; z-index:-1000; left:-1000px; overflow: hidden; clip: rect(0 0 0 0); height:1px; width:1px; margin:-1px; padding:0; border:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=checkbox].css-checkbox + label.css-label {
|
||||||
|
padding-left:20px;
|
||||||
|
height:15px;
|
||||||
|
display:inline-block;
|
||||||
|
line-height:15px;
|
||||||
|
background-repeat:no-repeat;
|
||||||
|
background-position: 0 0;
|
||||||
|
vertical-align:middle;
|
||||||
|
cursor:pointer;
|
||||||
|
font-size: .7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=checkbox].css-checkbox:checked + label.css-label {
|
||||||
|
background-position: 0 -15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label.css-label {
|
||||||
|
background-image:url(../img/csscheckbox.png);
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-khtml-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.no-saving {
|
||||||
|
background-image: url(../img/diagonal-white.png);
|
||||||
|
}
|
||||||
|
.blue .color-area {
|
||||||
|
background-color: #0060a8;
|
||||||
|
}
|
||||||
|
.blue .title {
|
||||||
|
color: #0060a8;
|
||||||
|
}
|
||||||
|
.purple .color-area {
|
||||||
|
background-color: #aa378d;
|
||||||
|
}
|
||||||
|
.purple .title {
|
||||||
|
color: #aa378d;
|
||||||
|
}
|
||||||
|
.dk-blue .color-area {
|
||||||
|
background-color: #1b0459;
|
||||||
|
}
|
||||||
|
.dk-blue .title {
|
||||||
|
color: #1b0459;
|
||||||
|
}
|
||||||
|
.orange .color-area {
|
||||||
|
background-color: #d66733;
|
||||||
|
}
|
||||||
|
.orange .title {
|
||||||
|
color: #d66733;
|
||||||
|
}
|
||||||
|
.green .color-area {
|
||||||
|
background-color: #63a952;
|
||||||
|
}
|
||||||
|
.green .title {
|
||||||
|
color: #63a952;
|
||||||
|
}
|
||||||
|
.yellow .color-area {
|
||||||
|
background-color: #e6a730;
|
||||||
|
}
|
||||||
|
.yellow .title {
|
||||||
|
color: #e6a730;
|
||||||
|
}
|
||||||
|
.red .color-area {
|
||||||
|
background-color: #cc2131;
|
||||||
|
}
|
||||||
|
.red .title {
|
||||||
|
color: #cc2131;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* ///////////////////////////////////////////////////////////
|
||||||
|
////////////////////// RESPONSIVE STYLES //////////////////
|
||||||
|
///////////////////////////////////////////////////////////
|
||||||
|
*/
|
||||||
|
|
||||||
|
@media only screen and (min-width : 300px) and (max-width : 1260px) {
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 2.0em; /* was 1.8 */
|
||||||
|
}
|
||||||
|
#content {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: wrap;
|
||||||
|
display: -webkit-flex; /* Safari */
|
||||||
|
-webkit-flex-flow: wrap; /* Safari 6.1+ */
|
||||||
|
}
|
||||||
|
|
||||||
|
#col-main {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
order: 1;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
#col-left {
|
||||||
|
width: 70%;
|
||||||
|
display: block;
|
||||||
|
order: 2;
|
||||||
|
}
|
||||||
|
#noncontTitle, #contTitle {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1.5em;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
#col-right {
|
||||||
|
width: 30%;
|
||||||
|
display: block;
|
||||||
|
order: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
#main-time-area {
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 15px 0;
|
||||||
|
}
|
||||||
|
#ak-col, #hi-col {
|
||||||
|
width: 50%;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pr-lt {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pr-rt {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#rightColBot {
|
||||||
|
border-top: 1px solid #000;
|
||||||
|
margin-top: 30px;
|
||||||
|
position: relative;
|
||||||
|
padding-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clock-box .title {
|
||||||
|
font-size: 1em;
|
||||||
|
line-height: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-title {
|
||||||
|
font-size: .7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-text {
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-xs {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=checkbox].css-checkbox + label.css-label {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.imgSmText {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@media only screen and (min-device-width : 320px) and (max-device-width : 740px) {
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 2.1em; /* was 2.3 */
|
||||||
|
}
|
||||||
|
#content {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: wrap;
|
||||||
|
display: -webkit-flex; /* Safari */
|
||||||
|
-webkit-flex-flow: wrap; /* Safari 6.1+ */
|
||||||
|
}
|
||||||
|
|
||||||
|
#col-main {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
order: 1;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
#col-left {
|
||||||
|
width: 70%;
|
||||||
|
display: block;
|
||||||
|
order: 2;
|
||||||
|
}
|
||||||
|
#noncontTitle, #contTitle {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1.5em;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
#col-right {
|
||||||
|
width: 30%;
|
||||||
|
display: block;
|
||||||
|
order: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
#main-time-area {
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 15px 0;
|
||||||
|
}
|
||||||
|
#ak-col, #hi-col {
|
||||||
|
width: 50%;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pr-lt {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pr-rt {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#rightColBot {
|
||||||
|
border-top: 1px solid #000;
|
||||||
|
margin-top: 30px;
|
||||||
|
position: relative;
|
||||||
|
padding-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clock-box .title {
|
||||||
|
font-size: 1.5em;
|
||||||
|
line-height: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-title {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-text {
|
||||||
|
font-size: 1.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-xs {
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=checkbox].css-checkbox + label.css-label {
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.imgSmText {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* ///////////////////////////////////////////////////////////
|
||||||
|
/////// STYLES AND DESIGN USED WITH PERMISSION /////////
|
||||||
|
///////////////// MORGANFEBREY.COM ////////////////////////
|
||||||
|
///////////////////////////////////////////////////////////
|
||||||
|
*/
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 203 KiB |
|
After Width: | Height: | Size: 90 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 284.929 284.929"><path d="M282.082 76.51L267.808 62.24c-1.902-1.906-4.093-2.856-6.57-2.856-2.47 0-4.66.95-6.563 2.856L142.465 174.44 30.263 62.24c-1.903-1.905-4.093-2.855-6.567-2.855-2.475 0-4.665.95-6.567 2.856L2.856 76.516C.95 78.417 0 80.607 0 83.082c0 2.473.953 4.663 2.856 6.565L135.9 222.693c1.9 1.903 4.092 2.854 6.566 2.854s4.66-.95 6.562-2.854L282.082 89.647c1.902-1.903 2.847-4.093 2.847-6.565 0-2.475-.946-4.665-2.848-6.57z" fill="#005ea2"/></svg>
|
||||||
|
After Width: | Height: | Size: 536 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 284.929 284.929"><path d="M282.082 195.285L149.028 62.24c-1.9-1.903-4.088-2.856-6.562-2.856s-4.665.953-6.567 2.856L2.855 195.285C.95 197.19 0 199.378 0 201.853c0 2.474.953 4.664 2.856 6.566l14.272 14.27c1.903 1.903 4.093 2.854 6.567 2.854s4.664-.95 6.567-2.854l112.204-112.202 112.208 112.21c1.902 1.902 4.093 2.847 6.563 2.847 2.478 0 4.668-.95 6.57-2.848l14.274-14.277c1.903-1.902 2.848-4.093 2.848-6.566 0-2.476-.944-4.666-2.846-6.57z" fill="#1a4480"/></svg>
|
||||||
|
After Width: | Height: | Size: 539 B |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 54 54"><defs><style>.cls-1{fill:#2378c3;}.cls-2{fill:none;stroke:#005ea2;stroke-miterlimit:10;}</style></defs><title>dot gov icon</title><path class="cls-1" d="M36.5,20.91v1.36H35.15a0.71,0.71,0,0,1-.73.68H18.23a0.71,0.71,0,0,1-.73-0.68H16.14V20.91l10.18-4.07Zm0,13.57v1.36H16.14V34.48a0.71,0.71,0,0,1,.73-0.68h18.9A0.71,0.71,0,0,1,36.5,34.48ZM21.57,23.62v8.14h1.36V23.62h2.71v8.14H27V23.62h2.71v8.14h1.36V23.62h2.71v8.14h0.63a0.71,0.71,0,0,1,.73.68v0.68H17.5V32.45a0.71,0.71,0,0,1,.73-0.68h0.63V23.62h2.71Z"/><circle class="cls-2" cx="27" cy="27.12" r="26"/></svg>
|
||||||
|
After Width: | Height: | Size: 651 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 54 54"><defs><style>.cls-1{fill:#719f2a;}.cls-2{fill:none;stroke:#538200;stroke-miterlimit:10;}</style></defs><title>https icon</title><path class="cls-1" d="M34.72,34.84a1.29,1.29,0,0,1-1.29,1.29H20.57a1.29,1.29,0,0,1-1.29-1.29V27.12a1.29,1.29,0,0,1,1.29-1.29H21V23.26a6,6,0,0,1,12,0v2.57h0.43a1.29,1.29,0,0,1,1.29,1.29v7.72Zm-4.29-9V23.26a3.43,3.43,0,0,0-6.86,0v2.57h6.86Z"/><circle class="cls-2" cx="27" cy="27.12" r="26"/></svg>
|
||||||
|
After Width: | Height: | Size: 518 B |
|
After Width: | Height: | Size: 36 KiB |
@@ -0,0 +1,161 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 272 112" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-miterlimit:3.84999;" shape-rendering="geometricPrecision">
|
||||||
|
<g transform="matrix(1,0,0,1,0,-2129.92)">
|
||||||
|
<g id="Lines">
|
||||||
|
</g>
|
||||||
|
<g id="Hawaii---New-Colors" serif:id="Hawaii - New Colors" transform="matrix(1.86867,0,0,1.75,-964.299,2625.17)">
|
||||||
|
<rect x="516.034" y="-283" width="145.558" height="64" style="fill:none;"/>
|
||||||
|
<g transform="matrix(0.374018,0,0,0.399381,595.786,-396.165)">
|
||||||
|
<g transform="matrix(22.8926,0,0,22.8926,63.0186,371.75)">
|
||||||
|
<path d="M0.543,-0.311L0.543,-0L0.689,-0L0.689,-0.723L0.543,-0.723L0.543,-0.433L0.227,-0.433L0.227,-0.723L0.081,-0.723L0.081,-0L0.227,-0L0.227,-0.311L0.543,-0.311Z" style="fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(22.8926,0,0,22.8926,80.6459,371.75)">
|
||||||
|
<rect x="0.081" y="-0.723" width="0.146" height="0.723" style="fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.93185,0,0,1.12321,36.9225,37.8028)">
|
||||||
|
<g transform="matrix(0.10475,0,0,0.0927968,548.768,-284.626)">
|
||||||
|
<path d="M608.98,451.46L616.4,446.42L621.6,441.8L626,441.25L631.76,437.13L636.13,430.51L642.37,425.4L646.45,423.64L648.18,417.03L644.89,410.92L639.62,400.74L638.61,392.2L641.81,380.23L647.91,378.31L656.11,381.93L665.25,387.83L669.84,393.21L679.65,400.33L685.35,405.41L691.57,404.95L700.1,408.57L717.33,417.74L734.85,427.79L749.89,441.35L755.84,448.32L761.2,455.44L759.22,467.66L759.12,475.25L765.86,474.06L771.97,474.74L775.2,482.69L775.4,491.74L779.25,496.57L793.27,506.66L799,510.21L798.31,514.76L787.76,524.33L776.35,531.29L763.55,537.97L748.3,542.85L740.65,545.58L733.24,546.02L724.18,543.02L716.13,545.9L708.01,551.63L703.15,552.35L692.45,558.98L684.06,560.32L675.05,570.14L668.47,579.13L664.19,584.52L659.92,588.57L654.16,591.77L646.45,582.82L634.53,574.58L622.98,569.79L618.86,559.55L620.53,547.18L623.35,535.04L625.87,528.96L627.02,521.37L624.93,510.55L623.11,504.32L619.45,498.78L617.8,487.71L617.29,479.45L614.8,473.74L610.02,472.07L609.66,469.02L605.83,458.93L608.98,451.46ZM471.91,257.03L479.82,255.39L490.95,257.42L500.35,263.37L505.36,268.88L509,276.13L503.36,284.3L494.94,286.43L491.93,286.92L482.73,286.54L479.96,279.75L480.72,271.82L478.28,267.12L471.3,261.51L471.91,257.03ZM527.63,262.44L531.64,253.47L541.98,247.14L550.56,249.96L556.28,260.34L561.1,269.82L573.45,268.43L584.9,264.87L598.72,267.77L605.34,275.75L614.43,281.59L624.86,290.76L633.33,294.05L635.27,302.36L631.94,309.3L624.87,313.77L610.86,318.14L598.39,316.61L586.32,320.33L571.52,321.53L563.44,317.51L562.62,308.28L561.75,295.43L561.47,292.67L560.36,286.73L555.49,284.8L550.2,287.52L547.72,286.03L536.39,279.17L528.96,267.33L527.63,262.44ZM525.69,319.24L535.58,315.03L542.55,314.07L545.85,318.36L545.45,326.6L537.92,328.26L524.96,327.87L520.26,322.95L525.69,319.24ZM442.84,214.72L447.58,211.18L446.67,204.53L455.57,206.62L466.63,210.78L480.04,214.06L484.19,213L489.07,211.76L492.33,212.16L497.98,219.65L498.48,219.71L510.23,221.27L525.71,221.76L530.51,225.1L524.98,231.95L514.47,237.73L502.56,239.15L491.02,235.13L480.28,230.23L472.11,227.2L457.85,227.27L445.3,226.23L436.58,222.96L442.84,214.72ZM304.39,129.64L320.8,131.33L328.65,125.78L334,119.63L343.7,115.04L347.42,114.77L352.93,125.26L358.65,138.05L363.94,145.93L361.37,155.64L365.7,160.39L373.69,158.07L380.12,159.22L378.73,167.92L379.96,175.19L388.13,183.3L387.54,183.94L384.27,187.52L379.71,190.15L371.19,186.85L367.44,188.4L362.72,188.99L356.88,183.99L351.32,179.79L342.09,177.65L337.43,176.51L330.68,176.68L320.91,177.04L320.65,177.05L318.33,176.89L315.27,169.04L314.19,164.29L308.83,158.75L308.83,154.65L302.28,144.65L303.31,139.03L297.31,129.5L304.39,129.64ZM1,49.47L5.92,38.42L12.05,34.05L21.34,31.28L23.93,29.4L26.15,24.57L32.53,24.25L34.43,25.64L35.2,28.21L31.87,30.43L28.65,35.79L28.93,40.82L21.58,42.78L16.52,43.42L13.17,46.26L10.3,49.73L6.82,56.05L3.32,53.89L1,49.47ZM77.87,21.23L84.49,17.01L87.71,10.71L91.96,8.24L107.2,3.77L112.36,1L117.96,2.11L122.59,5.68L126.65,2.12L134.99,4.85L139.72,3.53L145.53,7.32L152.31,13.24L153.9,23.06L148.67,32.13L145.76,34.86L145.1,41.52L143.24,48.93L135.11,53.93L125.02,59.46L112.85,55.26L105.79,52.8L101.39,52.25L95.26,44.71L87.04,39.13L80.26,35.49L76.35,28.36L77.87,21.23Z" style="fill:rgb(211,111,175);"/>
|
||||||
|
</g>
|
||||||
|
<g id="No-Daylight-Savings" serif:id="No Daylight Savings" transform="matrix(0.689936,0,0,0.611207,461.087,-486.077)">
|
||||||
|
<clipPath id="_clip1">
|
||||||
|
<path d="M219.545,398.139L220.671,397.373L221.461,396.672L222.129,396.588L223.003,395.963L223.667,394.958L224.614,394.182L225.234,393.915L225.496,392.911L224.997,391.984L224.197,390.438L224.043,389.141L224.529,387.324L225.455,387.032L226.7,387.582L228.088,388.478L228.785,389.295L230.274,390.376L231.14,391.147L232.084,391.077L233.379,391.627L235.995,393.019L238.655,394.545L240.939,396.604L241.842,397.662L242.656,398.743L242.355,400.598L242.34,401.75L243.363,401.57L244.291,401.673L244.781,402.88L244.812,404.254L245.396,404.987L247.525,406.519L248.395,407.058L248.29,407.749L246.688,409.202L244.956,410.259L243.013,411.273L240.697,412.014L239.536,412.428L238.411,412.495L237.035,412.04L235.813,412.477L234.58,413.347L233.842,413.456L232.218,414.463L230.944,414.666L229.576,416.157L228.577,417.522L227.927,418.34L227.279,418.955L226.404,419.441L225.234,418.082L223.424,416.831L221.67,416.104L221.045,414.549L221.298,412.671L221.727,410.828L222.109,409.905L222.284,408.753L221.966,407.11L221.69,406.164L221.134,405.323L220.884,403.642L220.807,402.388L220.429,401.521L219.703,401.268L219.648,400.805L219.067,399.273L219.545,398.139ZM198.734,368.619L199.935,368.37L201.625,368.678L203.052,369.582L203.813,370.418L204.365,371.519L203.509,372.759L202.231,373.083L201.774,373.157L200.377,373.099L199.956,372.069L200.072,370.865L199.701,370.151L198.642,369.299L198.734,368.619ZM207.194,369.44L207.803,368.079L209.373,367.118L210.675,367.546L211.544,369.122L212.275,370.561L214.151,370.35L215.889,369.809L217.987,370.25L218.992,371.461L220.372,372.348L221.956,373.74L223.242,374.24L223.536,375.501L223.031,376.555L221.957,377.234L219.83,377.897L217.937,377.665L216.105,378.23L213.857,378.412L212.631,377.801L212.506,376.4L212.374,374.449L212.332,374.03L212.163,373.128L211.424,372.835L210.621,373.248L210.244,373.022L208.524,371.98L207.396,370.183L207.194,369.44ZM206.899,378.064L208.401,377.425L209.459,377.279L209.96,377.931L209.899,379.182L208.756,379.434L206.788,379.374L206.075,378.627L206.899,378.064ZM194.321,362.195L195.04,361.658L194.902,360.648L196.253,360.966L197.932,361.597L199.968,362.095L200.599,361.934L201.339,361.746L201.834,361.807L202.692,362.944L202.768,362.953L204.552,363.19L206.902,363.264L207.631,363.771L206.792,364.811L205.196,365.689L203.388,365.904L201.636,365.294L200.005,364.55L198.764,364.09L196.599,364.101L194.694,363.943L193.37,363.446L194.321,362.195ZM173.3,349.278L175.792,349.535L176.984,348.692L177.796,347.758L179.269,347.061L179.833,347.02L180.67,348.613L181.538,350.555L182.342,351.751L181.951,353.225L182.609,353.947L183.822,353.594L184.798,353.769L184.587,355.09L184.774,356.194L186.014,357.425L185.925,357.522L185.428,358.066L184.736,358.465L183.442,357.964L182.873,358.199L182.156,358.289L181.27,357.53L180.425,356.892L179.024,356.567L178.317,356.394L177.292,356.42L175.808,356.475L175.769,356.476L175.417,356.452L174.952,355.26L174.788,354.539L173.974,353.698L173.974,353.075L172.98,351.557L173.136,350.704L172.225,349.257L173.3,349.278ZM127.238,337.106L127.985,335.428L128.916,334.765L130.326,334.344L130.719,334.059L131.056,333.326L132.025,333.277L132.313,333.488L132.43,333.878L131.925,334.215L131.436,335.029L131.478,335.793L130.362,336.09L129.594,336.188L129.086,336.619L128.65,337.146L128.122,338.105L127.59,337.777L127.238,337.106ZM138.909,332.819L139.914,332.178L140.403,331.221L141.048,330.846L143.362,330.168L144.145,329.747L144.995,329.916L145.698,330.458L146.315,329.917L147.581,330.332L148.299,330.131L149.181,330.707L150.211,331.605L150.452,333.096L149.658,334.473L149.216,334.888L149.116,335.899L148.834,337.024L147.599,337.783L146.067,338.623L144.22,337.985L143.148,337.612L142.48,337.528L141.549,336.383L140.301,335.536L139.272,334.984L138.678,333.901L138.909,332.819Z"/>
|
||||||
|
</clipPath>
|
||||||
|
<g clip-path="url(#_clip1)">
|
||||||
|
<g id="Lines1" serif:id="Lines" transform="matrix(1,0,0,1,2,-7)">
|
||||||
|
<g id="No-Daylight-Savings1" serif:id="No Daylight Savings">
|
||||||
|
<g id="Lines2" serif:id="Lines">
|
||||||
|
<g transform="matrix(0.978609,-0.96912,-0.978603,-0.969126,176.003,588.009)">
|
||||||
|
<path d="M71.318,155.942L172.818,155.942" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.978616,-0.969113,-0.978596,-0.969133,174.834,590.801)">
|
||||||
|
<path d="M73.355,153.905L174.855,153.905" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.978602,-0.969127,-0.97861,-0.969119,173.67,593.592)">
|
||||||
|
<path d="M75.392,151.868L176.892,151.868" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.978606,-0.969124,-0.978606,-0.969123,172.5,596.385)">
|
||||||
|
<path d="M77.43,149.83L178.93,149.83" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.978603,-0.969126,-0.978609,-0.96912,171.333,599.177)">
|
||||||
|
<path d="M79.467,147.793L180.967,147.793" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.978582,-0.969147,-0.97863,-0.9691,170.172,601.968)">
|
||||||
|
<path d="M81.503,145.757L183.003,145.757" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.978606,-0.969123,-0.978606,-0.969123,168.998,604.759)">
|
||||||
|
<path d="M83.54,143.721L185.039,143.721" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.978606,-0.969123,-0.978606,-0.969123,167.831,607.549)">
|
||||||
|
<path d="M85.575,141.685L187.075,141.685" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.978609,-0.96912,-0.978603,-0.969127,166.663,610.339)">
|
||||||
|
<path d="M87.611,139.649L189.111,139.649" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.978606,-0.969123,-0.978606,-0.969123,165.497,613.129)">
|
||||||
|
<path d="M89.646,137.613L191.147,137.613" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.978609,-0.96912,-0.978603,-0.969126,164.329,615.919)">
|
||||||
|
<path d="M91.682,135.578L193.182,135.578" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.978606,-0.969124,-0.978606,-0.969123,163.163,618.709)">
|
||||||
|
<path d="M93.718,133.542L195.217,133.542" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.978609,-0.96912,-0.978603,-0.969127,161.996,621.499)">
|
||||||
|
<path d="M95.754,131.507L197.253,131.507" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.978606,-0.969123,-0.978606,-0.969123,160.829,624.289)">
|
||||||
|
<path d="M97.789,129.471L199.289,129.471" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.978603,-0.969126,-0.978609,-0.969121,159.663,627.079)">
|
||||||
|
<path d="M99.825,127.435L201.325,127.435" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.978602,-0.969127,-0.97861,-0.96912,158.497,629.869)">
|
||||||
|
<path d="M101.861,125.399L203.361,125.399" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1.38396,-8.39218e-17,8.4743e-17,1.37055,-26.0821,190.525)">
|
||||||
|
<path d="M118.761,161.9L190.532,90.129L193.183,87.478" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1.38396,-8.39218e-17,8.4743e-17,1.37055,-26.0821,194.05)">
|
||||||
|
<path d="M120.796,161.364L192.568,89.592L196.719,85.441" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1.38396,-8.39218e-17,8.4743e-17,1.37055,-26.0821,197.915)">
|
||||||
|
<path d="M122.832,160.579L194.603,88.808L200.004,83.406" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1.38396,-8.39218e-17,8.4743e-17,1.37055,-26.0821,201.782)">
|
||||||
|
<path d="M124.868,159.793L196.639,88.021L203.29,81.371" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1.38396,-8.39218e-17,8.4743e-17,1.37055,-26.0821,205.308)">
|
||||||
|
<path d="M126.903,159.257L198.675,87.485L206.826,79.335" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1.38396,-8.39218e-17,8.4743e-17,1.37055,-26.0821,218.253)">
|
||||||
|
<path d="M128.939,151.847L200.71,80.076L203.486,77.3" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.978606,-0.969123,-0.978606,-0.969123,150.327,649.399)">
|
||||||
|
<path d="M116.11,111.149L217.61,111.149" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.978606,-0.969124,-0.978606,-0.969123,149.159,652.189)">
|
||||||
|
<path d="M118.146,109.113L219.646,109.113" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.978602,-0.969127,-0.97861,-0.96912,147.994,654.979)">
|
||||||
|
<path d="M120.182,107.078L221.682,107.078" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.978603,-0.969126,-0.978609,-0.96912,146.826,657.769)">
|
||||||
|
<path d="M122.218,105.042L223.717,105.042" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.978599,-0.96913,-0.978613,-0.969117,145.66,660.559)">
|
||||||
|
<path d="M124.252,103.006L225.753,103.006" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.978606,-0.969123,-0.978606,-0.969123,144.492,663.349)">
|
||||||
|
<path d="M126.289,100.971L227.789,100.971" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.978606,-0.969123,-0.978606,-0.969123,143.325,666.139)">
|
||||||
|
<path d="M128.325,98.936L229.825,98.936" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.978606,-0.969123,-0.978606,-0.969123,142.158,668.929)">
|
||||||
|
<path d="M130.36,96.899L231.86,96.899" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.978612,-0.969117,-0.9786,-0.969129,140.989,671.719)">
|
||||||
|
<path d="M132.396,94.864L233.896,94.864" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.978606,-0.969123,-0.978606,-0.969123,139.824,674.509)">
|
||||||
|
<path d="M134.432,92.828L235.932,92.828" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.978613,-0.969116,-0.978599,-0.96913,138.655,677.298)">
|
||||||
|
<path d="M136.467,90.792L237.967,90.792" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.978606,-0.969123,-0.978606,-0.969123,137.489,680.089)">
|
||||||
|
<path d="M138.504,88.756L240.002,88.756" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.978603,-0.969126,-0.978609,-0.96912,136.323,682.88)">
|
||||||
|
<path d="M140.539,86.721L242.039,86.721" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.978603,-0.969127,-0.978609,-0.96912,135.157,685.67)">
|
||||||
|
<path d="M142.575,84.686L244.074,84.686" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.978606,-0.969123,-0.978606,-0.969123,133.989,688.46)">
|
||||||
|
<path d="M144.61,82.649L246.11,82.649" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.978605,-0.969124,-0.978607,-0.969123,132.822,691.249)">
|
||||||
|
<path d="M146.646,80.614L248.146,80.614" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.978606,-0.969123,-0.978606,-0.969123,131.655,694.039)">
|
||||||
|
<path d="M148.682,78.578L250.182,78.578" style="fill:none;fill-rule:nonzero;stroke:rgb(158,30,126);stroke-width:1.21px;"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g id="Outline" transform="matrix(0.10475,0,0,0.0927968,548.768,-284.626)">
|
||||||
|
<path d="M608.98,451.46L616.4,446.42L621.6,441.8L626,441.25L631.76,437.13L636.13,430.51L642.37,425.4L646.45,423.64L648.18,417.03L644.89,410.92L639.62,400.74L638.61,392.2L641.81,380.23L647.91,378.31L656.11,381.93L665.25,387.83L669.84,393.21L679.65,400.33L685.35,405.41L691.57,404.95L700.1,408.57L717.33,417.74L734.85,427.79L749.89,441.35L755.84,448.32L761.2,455.44L759.22,467.66L759.12,475.25L765.86,474.06L771.97,474.74L775.2,482.69L775.4,491.74L779.25,496.57L793.27,506.66L799,510.21L798.31,514.76L787.76,524.33L776.35,531.29L763.55,537.97L748.3,542.85L740.65,545.58L733.24,546.02L724.18,543.02L716.13,545.9L708.01,551.63L703.15,552.35L692.45,558.98L684.06,560.32L675.05,570.14L668.47,579.13L664.19,584.52L659.92,588.57L654.16,591.77L646.45,582.82L634.53,574.58L622.98,569.79L618.86,559.55L620.53,547.18L623.35,535.04L625.87,528.96L627.02,521.37L624.93,510.55L623.11,504.32L619.45,498.78L617.8,487.71L617.29,479.45L614.8,473.74L610.02,472.07L609.66,469.02L605.83,458.93L608.98,451.46ZM471.91,257.03L479.82,255.39L490.95,257.42L500.35,263.37L505.36,268.88L509,276.13L503.36,284.3L494.94,286.43L491.93,286.92L482.73,286.54L479.96,279.75L480.72,271.82L478.28,267.12L471.3,261.51L471.91,257.03ZM527.63,262.44L531.64,253.47L541.98,247.14L550.56,249.96L556.28,260.34L561.1,269.82L573.45,268.43L584.9,264.87L598.72,267.77L605.34,275.75L614.43,281.59L624.86,290.76L633.33,294.05L635.27,302.36L631.94,309.3L624.87,313.77L610.86,318.14L598.39,316.61L586.32,320.33L571.52,321.53L563.44,317.51L562.62,308.28L561.75,295.43L561.47,292.67L560.36,286.73L555.49,284.8L550.2,287.52L547.72,286.03L536.39,279.17L528.96,267.33L527.63,262.44ZM525.69,319.24L535.58,315.03L542.55,314.07L545.85,318.36L545.45,326.6L537.92,328.26L524.96,327.87L520.26,322.95L525.69,319.24ZM442.84,214.72L447.58,211.18L446.67,204.53L455.57,206.62L466.63,210.78L480.04,214.06L484.19,213L489.07,211.76L492.33,212.16L497.98,219.65L498.48,219.71L510.23,221.27L525.71,221.76L530.51,225.1L524.98,231.95L514.47,237.73L502.56,239.15L491.02,235.13L480.28,230.23L472.11,227.2L457.85,227.27L445.3,226.23L436.58,222.96L442.84,214.72ZM304.39,129.64L320.8,131.33L328.65,125.78L334,119.63L343.7,115.04L347.42,114.77L352.93,125.26L358.65,138.05L363.94,145.93L361.37,155.64L365.7,160.39L373.69,158.07L380.12,159.22L378.73,167.92L379.96,175.19L388.13,183.3L387.54,183.94L384.27,187.52L379.71,190.15L371.19,186.85L367.44,188.4L362.72,188.99L356.88,183.99L351.32,179.79L342.09,177.65L337.43,176.51L330.68,176.68L320.91,177.04L320.65,177.05L318.33,176.89L315.27,169.04L314.19,164.29L308.83,158.75L308.83,154.65L302.28,144.65L303.31,139.03L297.31,129.5L304.39,129.64ZM1,49.47L5.92,38.42L12.05,34.05L21.34,31.28L23.93,29.4L26.15,24.57L32.53,24.25L34.43,25.64L35.2,28.21L31.87,30.43L28.65,35.79L28.93,40.82L21.58,42.78L16.52,43.42L13.17,46.26L10.3,49.73L6.82,56.05L3.32,53.89L1,49.47ZM77.87,21.23L84.49,17.01L87.71,10.71L91.96,8.24L107.2,3.77L112.36,1L117.96,2.11L122.59,5.68L126.65,2.12L134.99,4.85L139.72,3.53L145.53,7.32L152.31,13.24L153.9,23.06L148.67,32.13L145.76,34.86L145.1,41.52L143.24,48.93L135.11,53.93L125.02,59.46L112.85,55.26L105.79,52.8L101.39,52.25L95.26,44.71L87.04,39.13L80.26,35.49L76.35,28.36L77.87,21.23Z" style="fill:none;stroke:black;stroke-width:9.59px;stroke-linejoin:round;stroke-miterlimit:2;"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g id="Time_Zones1">
|
||||||
|
</g>
|
||||||
|
<g id="States">
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 390 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 46 KiB |
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 11 11" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||||
|
<g transform="matrix(1,0,0,1,-58.7899,-489.903)">
|
||||||
|
<g id="Lines">
|
||||||
|
</g>
|
||||||
|
<g id="Artboard5" transform="matrix(0.55,0,0,0.270127,40.6399,374.754)">
|
||||||
|
<rect x="33" y="426.278" width="20" height="40.722" style="fill:none;"/>
|
||||||
|
<clipPath id="_clip1">
|
||||||
|
<rect x="33" y="426.278" width="20" height="40.722"/>
|
||||||
|
</clipPath>
|
||||||
|
<g clip-path="url(#_clip1)">
|
||||||
|
<g transform="matrix(1.81818,0,0,3.70196,-73.8908,-1387.32)">
|
||||||
|
<rect x="58.79" y="489.903" width="11" height="11" style="fill:white;"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1.98807,0,0,4.04786,-42.4869,-1558.5)">
|
||||||
|
<path d="M37.97,493.257L37.97,490.327L40.928,490.327L37.97,493.257ZM37.97,496.021L43.719,490.327L46.704,490.327L37.97,498.976L37.97,496.021ZM39.335,500.387L48.03,491.776L48.03,494.732L42.319,500.387L39.335,500.387ZM45.111,500.387L48.03,497.496L48.03,500.387L45.111,500.387Z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 176 B |
@@ -0,0 +1,369 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="title" content="SethPC Official Time" />
|
||||||
|
<meta property="og:site_name" content="SethPC" />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:url" content="https://clock.sethpc.xyz/timegov/" />
|
||||||
|
<meta property="og:title" content="Official U.S. Time | SethPC" />
|
||||||
|
<meta property="og:description" content="Self-hosted official U.S. time style display with U.S. map and synchronized clocks." />
|
||||||
|
<meta property="og:image" content="/timegov/img/map-elements/united-states-map.svg" />
|
||||||
|
<meta name="dcterms.title" content="SethPC Official Time" />
|
||||||
|
<meta name="description" content="Self-hosted official U.S. time display with synchronized clock data." />
|
||||||
|
<meta name="dcterms.subject" content="Working with industry and science to advance innovation and improve quality of life." />
|
||||||
|
<meta name="google-site-verification" content="" />
|
||||||
|
<meta name="dcterms.type" content="text" />
|
||||||
|
<meta name="dcterms.format" content="text/html" />
|
||||||
|
<meta name="dcterms.identifier" content="https://clock.sethpc.xyz/timegov/" />
|
||||||
|
<meta name="twitter:title" content="Official U.S. Time | SethPC" />
|
||||||
|
<meta name="twitter:description" content="Self-hosted official U.S. time style display with synchronized clocks." />
|
||||||
|
<meta name="twitter:image" content="/img/map-elements/og_map.png" />
|
||||||
|
|
||||||
|
<title>SethPC Official Time</title>
|
||||||
|
<link rel="shortcut icon" href="https://storage.googleapis.com/sethfreiberg.com/sethflix/favicon.png" />
|
||||||
|
<link rel="stylesheet" href="css/main.css" />
|
||||||
|
<link rel="stylesheet" href="css/NIST-styles.css" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- FUNC FOR TOP BAR INFO BTN -->
|
||||||
|
<script type="text/javascript">
|
||||||
|
function info_exp(id, id2) {
|
||||||
|
var n = document.getElementById(id);
|
||||||
|
if (n.style.display != 'none') {
|
||||||
|
n.style.display = 'none';
|
||||||
|
document.getElementById(id2).setAttribute('aria-expanded', 'false');
|
||||||
|
} else {
|
||||||
|
n.style.display = 'block';
|
||||||
|
document.getElementById(id2).setAttribute('aria-expanded', 'true');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
|
||||||
|
<!-- TOP GREY BAR -->
|
||||||
|
<div id="top-grey">
|
||||||
|
<div class="inner">
|
||||||
|
<img src="img/us_flag_small.png" class="left gap" alt="U.S. Flag"><span class="left gap">Official self-hosted time service</span> <div id="info-btn"><a href="index.html#" onclick="info_exp('top-grey-exp', 'arrow');">Service details <span id="arrow" aria-expanded="false"></span></a></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- EXPANDED TOP GREY BAR -->
|
||||||
|
<div id="top-grey-exp" style="display:none;">
|
||||||
|
<div class="inner">
|
||||||
|
<div class="col-50">
|
||||||
|
<div class="pad-4">
|
||||||
|
<img src="img/icon-dot-gov.svg" alt="icon dot gov" class="img10"><br>
|
||||||
|
<strong>Self-hosted service notice.</strong>
|
||||||
|
<p class="no-marg">This is a privately hosted clock service designed to mirror the time.gov experience in a homelab environment.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-50">
|
||||||
|
<div class="pad-4">
|
||||||
|
<img src="img/icon-https.svg" alt="icon https" class="img10"><br>
|
||||||
|
<strong>Encrypted transport.</strong>
|
||||||
|
<p class="no-marg">The <strong>https://</strong> indicates encrypted traffic between your browser and this host.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="clear"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- BLACK TITLE BAR AREA -->
|
||||||
|
<div id="title-area">
|
||||||
|
<div class="inner">
|
||||||
|
<center>
|
||||||
|
<span class="title"><img src="https://storage.googleapis.com/sethfreiberg.com/sethflix/favicon.png" id="logo" alt="SethPC"/>OFFICIAL U.S. TIME</span>
|
||||||
|
</center>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ANNOUNCEMENT GREY BAR -->
|
||||||
|
<div id="callout-bar"><p></p></div> <!-- Blank between <p> </p> unless there is an upcoming announcement -->
|
||||||
|
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- MAIN CONTENT AREA -->
|
||||||
|
<div id="content">
|
||||||
|
|
||||||
|
<!-- LEFT COLUMN -->
|
||||||
|
<div id="col-left">
|
||||||
|
<div class="pad-2">
|
||||||
|
|
||||||
|
<div id="noncontTitle">Non-Contiguous U.S. and Territories</div>
|
||||||
|
|
||||||
|
<div id="ak-col">
|
||||||
|
<!-- ALASKA TIME -->
|
||||||
|
<div class="clock-box blue dst-clock">
|
||||||
|
<div class="title">Alaska <span class="DSTterm">Standard</span> Time</div>
|
||||||
|
<div class="sub-title">AK<span class="DSTletter">S</span>T (UTC-<span class="DSTnum">9</span>)</div>
|
||||||
|
<div class="time-display">
|
||||||
|
<div class="color-area"></div>
|
||||||
|
<div class="clock time-text"><time zoneOffset=9></time></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ALASKA MAP GRAPHIC -->
|
||||||
|
<img src="img/map-elements/alaska.svg" class="side-img" alt="Alaska Map">
|
||||||
|
|
||||||
|
<!-- ALEUTIAN TIME -->
|
||||||
|
<div class="clock-box blue dst-clock">
|
||||||
|
<div class="title">Aleutian <span class="DSTterm">Standard</span> Time</div>
|
||||||
|
<div class="sub-title">HA<span class="DSTletter">S</span>T (UTC-<span class="DSTnum">10</span>)</div>
|
||||||
|
<div class="time-display">
|
||||||
|
<div class="color-area"></div>
|
||||||
|
<div class="clock time-text"><time zoneOffset=10></time></div>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="null"><span class="DSTterm"></span><span class="DSTletter"></span><span class="DSTnum"></span></div> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="hi-col">
|
||||||
|
<!-- HAWAIIAN TIME -->
|
||||||
|
<div class="clock-box purple">
|
||||||
|
<div class="title">Hawaii Standard Time</div>
|
||||||
|
<div class="sub-title">HST (UTC-10)</div>
|
||||||
|
<div class="time-display">
|
||||||
|
<div class="color-area no-saving"></div>
|
||||||
|
<div class="clock time-text"><time zoneOffset=10></time></div>
|
||||||
|
</div>
|
||||||
|
<div class="null"><span class="DSTterm"></span><span class="DSTletter"></span><span class="DSTnum"></span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- HAWAII MAP GRAPHIC -->
|
||||||
|
<img src="img/map-elements/hawaii.svg" class="side-img" alt="Hawaii Map">
|
||||||
|
|
||||||
|
<!-- SAMOA TIME -->
|
||||||
|
<div class="clock-box dk-blue">
|
||||||
|
<div class="title">Samoa Standard Time</div>
|
||||||
|
<div class="sub-title">SST (UTC-11)</div>
|
||||||
|
<div class="time-display">
|
||||||
|
<div class="color-area no-saving"></div>
|
||||||
|
<div class="clock time-text"><time zoneOffset=11></time></div>
|
||||||
|
</div>
|
||||||
|
<div class="null"><span class="DSTterm"></span><span class="DSTletter"></span><span class="DSTnum"></span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- CHAMORRO TIME -->
|
||||||
|
<div class="clock-box dk-blue">
|
||||||
|
<div class="title">Chamorro Standard Time</div>
|
||||||
|
<div class="sub-title">CHST (UTC+10)</div>
|
||||||
|
<div class="time-display">
|
||||||
|
<div class="color-area no-saving"></div>
|
||||||
|
<div class="clock time-text"><time zoneOffset=-10></time></div>
|
||||||
|
</div>
|
||||||
|
<div class="null"><span class="DSTterm"></span><span class="DSTletter"></span><span class="DSTnum"></span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- PUERTO RICO LT COL-->
|
||||||
|
<div id="pr-lt" class="pad-2 margTop15">
|
||||||
|
<div class="clock-box dk-blue">
|
||||||
|
<div class="title">Atlantic Standard Time</div>
|
||||||
|
<div class="sub-title"><b>Puerto Rico / US Virgin Islands</b></div>
|
||||||
|
<div class="sub-title">AST (UTC-4)</div>
|
||||||
|
<div class="time-display">
|
||||||
|
<div class="color-area no-saving"></div>
|
||||||
|
<div class="clock time-text"><time zoneOffset=4></time></div>
|
||||||
|
</div>
|
||||||
|
<div class="null"><span class="DSTterm"></span><span class="DSTletter"></span><span class="DSTnum"></span></div>
|
||||||
|
</div> </div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- MAIN COLUMN -->
|
||||||
|
<div id="col-main">
|
||||||
|
<div class="pad-2">
|
||||||
|
<div style="height: 0px;"> </div>
|
||||||
|
|
||||||
|
<!-- ROW FOR MAINLAND US CLOCK AREA -->
|
||||||
|
<div class="main-top">
|
||||||
|
|
||||||
|
<!-- PACIFIC TIME -->
|
||||||
|
<div class="col-25">
|
||||||
|
<div class="pad-2">
|
||||||
|
<div class="clock-box orange dst-clock">
|
||||||
|
<div class="title">Pacific <span class="DSTterm">Standard</span> Time</div>
|
||||||
|
<div class="sub-title">P<span class="DSTletter">S</span>T (UTC-<span class="DSTnum">8</span>)</div>
|
||||||
|
<div class="time-display">
|
||||||
|
<div class="color-area"></div>
|
||||||
|
<div class="clock time-text"><time zoneOffset=8 />SYNCHRONIZING</time></div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- MOUNTAIN TIME -->
|
||||||
|
<div class="col-25">
|
||||||
|
<div class="pad-2">
|
||||||
|
<div class="clock-box green dst-clock">
|
||||||
|
<div class="title">Mountain <span class="DSTterm">Standard</span> Time</div>
|
||||||
|
<div class="sub-title">M<span class="DSTletter">S</span>T (UTC-<span class="DSTnum">7</span>)</div>
|
||||||
|
<div class="time-display">
|
||||||
|
<div class="color-area"></div>
|
||||||
|
<div class="clock time-text"><time zoneOffset=7>SYNCHRONIZING</time></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- CENTRAL TIME -->
|
||||||
|
<div class="col-25">
|
||||||
|
<div class="pad-2">
|
||||||
|
<div class="clock-box yellow dst-clock">
|
||||||
|
<div class="title">Central <span class="DSTterm">Standard</span> Time</div>
|
||||||
|
<div class="sub-title">C<span class="DSTletter">S</span>T (UTC-<span class="DSTnum">6</span>)</div>
|
||||||
|
<div class="time-display">
|
||||||
|
<div class="color-area"></div>
|
||||||
|
<div class="clock time-text"><time zoneOffset=6>SYNCHRONIZING</time></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- EASTERN TIME -->
|
||||||
|
<div class="col-25">
|
||||||
|
<div class="pad-2">
|
||||||
|
<div class="clock-box red dst-clock">
|
||||||
|
<div class="title">Eastern <span class="DSTterm">Standard</span> Time</div>
|
||||||
|
<div class="sub-title">E<span class="DSTletter">S</span>T (UTC-<span class="DSTnum">5</span>)</div>
|
||||||
|
<div class="time-display">
|
||||||
|
<div class="color-area"></div>
|
||||||
|
<div class="clock time-text"><time zoneOffset=5>SYNCHRONIZING</time></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="clear"></div>
|
||||||
|
</div>
|
||||||
|
<!-- END TOP ROW -->
|
||||||
|
|
||||||
|
|
||||||
|
<div id="mainland">
|
||||||
|
<!-- ARIZONA/MOUNTAIN STANDARD TIME -->
|
||||||
|
<div id="az-clock" class="col-25">
|
||||||
|
<div class="pad-2">
|
||||||
|
<div class="clock-box green">
|
||||||
|
<div class="title">Arizona Mountain<br>Standard Time</div>
|
||||||
|
<div class="sub-title">MST (UTC-7)</div>
|
||||||
|
<div class="time-display">
|
||||||
|
<div class="color-area no-saving"></div>
|
||||||
|
<div class="clock time-text"><time zoneOffset=7></time></div>
|
||||||
|
</div>
|
||||||
|
<div class="null"><span class="DSTterm"></span><span class="DSTletter"></span><span class="DSTnum"></span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- BEGIN MAIN MAP AREA -->
|
||||||
|
<img src="img/map-elements/united-states-map.svg" class="full-img" alt="United States Time Zone Map">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- RIGHT COLUMN -->
|
||||||
|
<div id="col-right">
|
||||||
|
<div class="pad-4">
|
||||||
|
|
||||||
|
<!-- 24HR CHECKBOX -->
|
||||||
|
<div class='hours-wrapper'>
|
||||||
|
<div class='am-pm'></div>
|
||||||
|
<input id='twenty-four' type='checkbox' class="css-checkbox">
|
||||||
|
<label class='css-label' for='twenty-four'>24-Hour Clock Display</label>
|
||||||
|
<div class="clear"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="clock-utc">
|
||||||
|
<p class="text-xs">Coordinated Universal Time (UTC)</p>
|
||||||
|
<div class="clock text-lg"><time id="timeUTC"></time></div>
|
||||||
|
<div class="null"><span class="DSTterm"></span><span class="DSTletter"></span><span class="DSTnum"></span></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<p class="sub-title ital">UTC is always displayed as a 24-hour clock.</p>
|
||||||
|
|
||||||
|
<!-- MAIN CLOCK AREA -->
|
||||||
|
<div id="main-time-area">
|
||||||
|
<p>Your Device's Clock <span id="myTimeTitle">UTC-0</span> <br>
|
||||||
|
<div id='myDate'></div>
|
||||||
|
<canvas id="analog-clock" width=200 height=200></canvas>
|
||||||
|
<br>
|
||||||
|
<span id="myTime"></span><br><br>
|
||||||
|
Your clock is off by: <br><span id="realTimeDif"></span> s</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- PUERTO RICO RT COL-->
|
||||||
|
<div id="pr-rt" class="pad-2 margTop15">
|
||||||
|
<div class="clock-box dk-blue">
|
||||||
|
<div class="title">Atlantic Standard Time</div>
|
||||||
|
<div class="sub-title"><b>Puerto Rico / US Virgin Islands</b></div>
|
||||||
|
<div class="sub-title">AST (UTC-4)</div>
|
||||||
|
<div class="time-display">
|
||||||
|
<div class="color-area no-saving"></div>
|
||||||
|
<div class="clock time-text"><time zoneOffset=4></time></div>
|
||||||
|
</div>
|
||||||
|
<div class="null"><span class="DSTterm"></span><span class="DSTletter"></span><span class="DSTnum"></span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="rightColBot">
|
||||||
|
<a href="/simple/">Simple View</a>
|
||||||
|
<a href="/decimal/">Decimal Time</a>
|
||||||
|
<a href="/seth/">Seth Date</a>
|
||||||
|
<a href="/calendar/">Seth Calendar</a>
|
||||||
|
<a href="/api/time">Time API</a>
|
||||||
|
<div class="imgSmText">
|
||||||
|
<img src="img/stripes.svg" class="striped-box" alt="Hashed image for Daylight Saving Time not observed"> = DAYLIGHT SAVING TIME NOT OBSERVED
|
||||||
|
<p>Clocks are corrected for network delay</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="clear"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="nist-footer">
|
||||||
|
<div class="nist-footer__inner">
|
||||||
|
<div class="nist-footer__menu" role="navigation">
|
||||||
|
<ul>
|
||||||
|
<li class="nist-footer__menu-item"><a href="/timegov/">Map View</a></li>
|
||||||
|
<li class="nist-footer__menu-item"><a href="/simple/">Simple View</a></li>
|
||||||
|
<li class="nist-footer__menu-item"><a href="/decimal/">Decimal Time</a></li>
|
||||||
|
<li class="nist-footer__menu-item"><a href="/seth/">Seth Date</a></li>
|
||||||
|
<li class="nist-footer__menu-item"><a href="/calendar/">Seth Calendar</a></li>
|
||||||
|
<li class="nist-footer__menu-item"><a href="/api/time">JSON Time API</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="nist-footer__logo">
|
||||||
|
<center><img src="https://storage.googleapis.com/sethfreiberg.com/sethflix/favicon.png" alt="SethPC logo" /><p>SethPC Official Time</p></center>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<div class='time-zone-container' style='display:none'>
|
||||||
|
<div class='dropdown-container'>
|
||||||
|
<select id='dst-time-zone'>
|
||||||
|
</select>
|
||||||
|
<select id='no-dst-time-zone'>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="responseTime"></div>
|
||||||
|
<script src="scripts/jquery-3.7.1.min.js" type="text/javascript"></script>
|
||||||
|
<script type="text/javascript" src="scripts/analogClock.js"></script>
|
||||||
|
<script type="text/javascript" src="scripts/application.js"></script>
|
||||||
|
<script type="text/javascript" src="scripts/zzz__bd08fcbbc8bd600a5f9e994be2de69f7cb26b9f4.js"></script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
////////////////////// ANALOG CLOCK FUNCTIONS ///////////////////////////
|
||||||
|
///////////////// Clock code used from W3Schools.com ////////////////////
|
||||||
|
//////// Refer to their site about applicable use of the code ///////////
|
||||||
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
var canvas = document.getElementById("analog-clock");
|
||||||
|
var ctx = canvas.getContext("2d");
|
||||||
|
var radius = canvas.height / 2;
|
||||||
|
ctx.translate(radius, radius);
|
||||||
|
radius = radius * 0.90
|
||||||
|
setInterval(drawClock, 1000);
|
||||||
|
|
||||||
|
// add 0 to single digit time min, sec
|
||||||
|
function checkTime(i) {
|
||||||
|
if (i < 10) {i = "0" + i}; // add zero in front of numbers < 10
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawClock() {
|
||||||
|
drawFace(ctx, radius);
|
||||||
|
drawNumbers(ctx, radius);
|
||||||
|
drawTime(ctx, radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawFace(ctx, radius) {
|
||||||
|
var grad;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(0, 0, radius, 0, 2*Math.PI);
|
||||||
|
ctx.fillStyle = 'white';
|
||||||
|
ctx.fill();
|
||||||
|
grad = ctx.createRadialGradient(0,0,radius*0.95, 0,0,radius*1.05);
|
||||||
|
grad.addColorStop(0, '#333');
|
||||||
|
grad.addColorStop(0.5, 'white');
|
||||||
|
grad.addColorStop(1, '#333');
|
||||||
|
ctx.strokeStyle = grad;
|
||||||
|
ctx.lineWidth = radius*0.1;
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(0, 0, radius*0.1, 0, 2*Math.PI);
|
||||||
|
ctx.fillStyle = '#333';
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawNumbers(ctx, radius) {
|
||||||
|
var ang;
|
||||||
|
var num;
|
||||||
|
ctx.font = radius*0.15 + "px arial";
|
||||||
|
ctx.textBaseline="middle";
|
||||||
|
ctx.textAlign="center";
|
||||||
|
for(num = 1; num < 13; num++){
|
||||||
|
ang = num * Math.PI / 6;
|
||||||
|
ctx.rotate(ang);
|
||||||
|
ctx.translate(0, -radius*0.85);
|
||||||
|
ctx.rotate(-ang);
|
||||||
|
ctx.fillText(num.toString(), 0, 0);
|
||||||
|
ctx.rotate(ang);
|
||||||
|
ctx.translate(0, radius*0.85);
|
||||||
|
ctx.rotate(-ang);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawTime(ctx, radius){
|
||||||
|
|
||||||
|
var deviceClock = new Date();
|
||||||
|
var hour = deviceClock.getHours();
|
||||||
|
var minute = deviceClock.getMinutes();
|
||||||
|
var second = deviceClock.getSeconds();
|
||||||
|
|
||||||
|
var ampm = hour >= 12 ? 'P.M.' : 'A.M.';
|
||||||
|
hour = hour % 12;
|
||||||
|
hour = hour ? hour : 12; // the hour '0' should be '12'
|
||||||
|
//minute = minute < 10 ? '0'+minute : minute;
|
||||||
|
|
||||||
|
// CHECKTIME FUNC ADDS LEADING '0' if <10
|
||||||
|
hour = checkTime(hour);
|
||||||
|
minute = checkTime(minute);
|
||||||
|
second = checkTime(second);
|
||||||
|
|
||||||
|
//digital time display
|
||||||
|
var myDigTime = hour + ":" + minute +":" + second;
|
||||||
|
document.getElementById('myTime').innerHTML = myDigTime + ' ' + ampm;
|
||||||
|
|
||||||
|
var dd = deviceClock.getDate();
|
||||||
|
var mm = deviceClock.getMonth();
|
||||||
|
mm++; //Increment because Jan is month 0
|
||||||
|
dd = checkTime(dd);
|
||||||
|
mm = checkTime(mm);
|
||||||
|
var yyyy = deviceClock.getFullYear();
|
||||||
|
|
||||||
|
var todaysDate = mm + '/' + dd + '/' + yyyy;
|
||||||
|
document.getElementById("myDate").innerHTML = "Today: " + todaysDate;
|
||||||
|
|
||||||
|
// OFFSET
|
||||||
|
var utcOffset = deviceClock.getTimezoneOffset()/60;
|
||||||
|
if (utcOffset > 0) {
|
||||||
|
utcOffset = "(UTC-" + utcOffset + ")";
|
||||||
|
} else {
|
||||||
|
utcOffset = "(UTC+" + Math.abs(utcOffset) + ")";
|
||||||
|
};
|
||||||
|
document.getElementById("myTimeTitle").innerHTML = utcOffset;
|
||||||
|
|
||||||
|
//hour
|
||||||
|
hour=hour%12;
|
||||||
|
hour=(hour*Math.PI/6)+
|
||||||
|
(minute*Math.PI/(6*60))+
|
||||||
|
(second*Math.PI/(360*60));
|
||||||
|
drawHand(ctx, hour, radius*0.5, radius*0.07);
|
||||||
|
//minute
|
||||||
|
minute=(minute*Math.PI/30)+(second*Math.PI/(30*60));
|
||||||
|
drawHand(ctx, minute, radius*0.8, radius*0.07);
|
||||||
|
// second
|
||||||
|
second=(second*Math.PI/30);
|
||||||
|
drawHand(ctx, second, radius*0.9, radius*0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawHand(ctx, pos, length, width) {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.lineWidth = width;
|
||||||
|
ctx.lineCap = "round";
|
||||||
|
ctx.moveTo(0,0);
|
||||||
|
ctx.rotate(pos);
|
||||||
|
ctx.lineTo(0, -length);
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.rotate(-pos);
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
// ONLOAD OPERATIONS FOR THE SITE
|
||||||
|
window.onload = function() {
|
||||||
|
|
||||||
|
/* PARSE THE URL FOR 12/24 VARIABLE*/
|
||||||
|
var getT = location.search;
|
||||||
|
var tArr = getT.split("=");
|
||||||
|
var t = tArr[1];
|
||||||
|
|
||||||
|
// CREATE A VAR FOR THE CHECKBOX
|
||||||
|
var twentyFour = document.getElementById("twenty-four");
|
||||||
|
|
||||||
|
// CHECK VALUE OF 12/24 URL VAR "t" AND SET CHECKBOX ACCORDINGLY
|
||||||
|
if (t === "24") {
|
||||||
|
twentyFour.checked = true;
|
||||||
|
} else {
|
||||||
|
// DEFAULT TO 12HR DISPLAY
|
||||||
|
twentyFour.checked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var noMoreAlerts = false;
|
||||||
|
// NOTIFICATION BOX FOR BOOKMARKING 24-HOUR SETTINGS PAGE
|
||||||
|
var twentyFour = document.getElementById("twenty-four");
|
||||||
|
twentyFour.addEventListener("click", function(event) {
|
||||||
|
var hourLabelDiv = document.getElementsByClassName("am-pm")[0];
|
||||||
|
var url = window.location.toString();
|
||||||
|
if(timeDotGov.data.twentyFour()) {
|
||||||
|
window.history.replaceState(url, "", "/timegov/?t=24");
|
||||||
|
if (!noMoreAlerts) {
|
||||||
|
alert("Bookmarking this page will save your preference for 24-hour time display.");
|
||||||
|
}
|
||||||
|
noMoreAlerts = true;
|
||||||
|
} else {
|
||||||
|
window.history.replaceState(url, "", "/timegov/");
|
||||||
|
}
|
||||||
|
timeDotGov.clockController.handleonrefresh(new Date());
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
//? timeZoneChange = function(event) {
|
||||||
|
//? timeDotGov.clockController.getnewOffset(event.target.value);
|
||||||
|
//? }
|
||||||
|
|
||||||
|
// LOAD DST DATES AND LEAP DATE
|
||||||
|
var xmlHttp = new XMLHttpRequest();
|
||||||
|
xmlHttp.open("GET", "/api/timegov/auxdata.xml", false); // false for synchronous request
|
||||||
|
xmlHttp.send(null);
|
||||||
|
timeDotGov.auxdata = xmlHttp.responseText;
|
||||||
|
|
||||||
|
timeDotGov.clockController.auxdata();
|
||||||
|
|
||||||
|
timeDotGov.clockController.checkservertime();
|
||||||
|
document.getElementById('responseTime').innerHTML = timeDotGov.data.zoneOffset;
|
||||||
|
|
||||||
|
// SET REFRESH RATE TO CHECK FOR TOP OF NEW SECOND, SO THE DISPLAY DOES NOT HAVE TO BE REFRESHED MORE THAN NECESSARY
|
||||||
|
setInterval(function() {
|
||||||
|
if(timeDotGov.data.currentTime) {
|
||||||
|
timeDotGov.clockController.runningclocks();
|
||||||
|
}
|
||||||
|
}, 20); // 20 milliseconds
|
||||||
|
|
||||||
|
// FUNCTION REFRESHES PAGE EVERY 10 MIN
|
||||||
|
setInterval(function() {
|
||||||
|
location.reload();
|
||||||
|
}, 600000);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
@@ -0,0 +1,370 @@
|
|||||||
|
|
||||||
|
timeDotGov = {};
|
||||||
|
timeDotGov.clock = {};
|
||||||
|
timeDotGov.clockController = {};
|
||||||
|
timeDotGov.clockController.dsClock = {}
|
||||||
|
|
||||||
|
timeDotGov.data = {
|
||||||
|
"then": null,
|
||||||
|
"serverTime": null,
|
||||||
|
"responseTime": null,
|
||||||
|
"RThalf": null,
|
||||||
|
"realTimeDif": null,
|
||||||
|
"requestTime": null,
|
||||||
|
"leapsecond": null,
|
||||||
|
"dststart": null,
|
||||||
|
"dstend": null,
|
||||||
|
"currYear": null,
|
||||||
|
"dstDates": null,
|
||||||
|
"leapDate": null,
|
||||||
|
"clockinstances": [],
|
||||||
|
"leapFlag": null,
|
||||||
|
"leapsec60": null,
|
||||||
|
"myhour0": "0",
|
||||||
|
"myhour1": "0",
|
||||||
|
"mysec0": "0",
|
||||||
|
"mysec1": "0",
|
||||||
|
"currentTime": null,
|
||||||
|
"twentyFour": function() {
|
||||||
|
twentyFour = document.getElementById("twenty-four");
|
||||||
|
return twentyFour.checked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var offsetCheck = true;
|
||||||
|
var daylightTitles = false;
|
||||||
|
|
||||||
|
// CREATE ARRAY THAT WILL STORE WHICH CLOCK NUMS HAVE BEEN UPDATED
|
||||||
|
var dstClocksUpdated = [];
|
||||||
|
timeDotGov.clockController.getParameterByName = function(name) {
|
||||||
|
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
|
||||||
|
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
|
||||||
|
results = regex.exec(location.search);
|
||||||
|
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
|
||||||
|
}
|
||||||
|
|
||||||
|
timeDotGov.clockController.getleapdate = function() {
|
||||||
|
var leap = new Date();
|
||||||
|
var temp1 = timeDotGov.data.leapdate.split(" ");
|
||||||
|
var leapdateYear = temp1[0];
|
||||||
|
var leapdateMonth = temp1[1];
|
||||||
|
|
||||||
|
leapdateMonth = leapdateMonth - 1; // month convention = 0-11 (Jan = 0)
|
||||||
|
|
||||||
|
var leapdateDay = temp1[2];
|
||||||
|
|
||||||
|
leap.setUTCFullYear(leapdateYear, leapdateMonth, leapdateDay);
|
||||||
|
leap.setUTCHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
timeDotGov.data.leapsecond = leap.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET TIME FROM SERVER
|
||||||
|
timeDotGov.clockController.dsClock.doRequest = function() {
|
||||||
|
d = new Date();
|
||||||
|
var xmlHttp = new XMLHttpRequest();
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
////// USE OF THIS .CGI BY OUTSIDE SITES OR APPLICATIONS IS STRICTLY PROHIBITED ////
|
||||||
|
// OR USING THE TIME FROM THIS SITE IN ANY WAY FOR OTHER SITES IS ALSO PROHIBITED //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
xmlHttp.open("GET", "/zzz__2fbc6c3300df7e4483acd44c5044098a9fcc61d6.cgi?disablecache=" + d.getTime(), false);
|
||||||
|
xmlHttp.send(null);
|
||||||
|
return xmlHttp.responseText;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET t1, t2, t3, t4 in doRequest AND CALCULATE DELAYS IN doData
|
||||||
|
timeDotGov.clockController.checkservertime = function() {
|
||||||
|
var o = new Object();
|
||||||
|
var d = new Date();
|
||||||
|
var currentTimeObj = new Date();
|
||||||
|
timeDotGov.data.requestTime = currentTimeObj.getTime();
|
||||||
|
timeDotGov.data.currentTime = timeDotGov.clockController.dsClock.doRequest(); // GET NIST TIME FROM SERVER
|
||||||
|
timeDotGov.clockController.doData();
|
||||||
|
}
|
||||||
|
// PARSE DATA FROM AUXDATA.XML
|
||||||
|
timeDotGov.clockController.doAuxData = function() {
|
||||||
|
parser = new DOMParser();
|
||||||
|
xmlDoc = parser.parseFromString(timeDotGov.auxdata, "text/xml");
|
||||||
|
timeDotGov.data.curryear = xmlDoc.getElementsByTagName("currYear")[0].childNodes[0].nodeValue;
|
||||||
|
timeDotGov.data.dstdates = xmlDoc.getElementsByTagName("DstDates")[0].childNodes[0].nodeValue;
|
||||||
|
timeDotGov.data.leapdate = xmlDoc.getElementsByTagName("LeapDate")[0].childNodes[0].nodeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
timeDotGov.clockController.getDSTdates = function() {
|
||||||
|
var dstStartDate = new Date();
|
||||||
|
var dstEndDate = new Date();
|
||||||
|
var temp=timeDotGov.data.dstdates.split(" "); // makes an array of the string elements
|
||||||
|
var startmonth = temp[0];
|
||||||
|
|
||||||
|
startmonth = startmonth - 1; // to follow js convention of month = 0-11 (Jan = 0)
|
||||||
|
|
||||||
|
var startday = temp[1];
|
||||||
|
var endmonth = temp[2];
|
||||||
|
|
||||||
|
endmonth = endmonth - 1;
|
||||||
|
|
||||||
|
var endday = temp[3];
|
||||||
|
|
||||||
|
dstStartDate.setUTCFullYear(timeDotGov.data.curryear, startmonth, startday);
|
||||||
|
dstStartDate.setUTCHours(2, 0, 0, 0);
|
||||||
|
dstEndDate.setUTCFullYear(timeDotGov.data.curryear, endmonth, endday);
|
||||||
|
dstEndDate.setUTCHours(1, 0, 0, 0);
|
||||||
|
timeDotGov.data.dstStart = dstStartDate.getTime();
|
||||||
|
timeDotGov.data.dstEnd = dstEndDate.getTime();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
timeDotGov.clockController.auxdata = function() {
|
||||||
|
this.doAuxData();
|
||||||
|
this.getDSTdates();
|
||||||
|
this.getleapdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
timeDotGov.clockController.doData = function() {
|
||||||
|
|
||||||
|
var parser = new DOMParser();
|
||||||
|
var xmlDoc = parser.parseFromString(timeDotGov.data.currentTime, "text/xml");
|
||||||
|
|
||||||
|
var t2 = xmlDoc.getElementsByTagName("timestamp")[0].getAttribute("time2");
|
||||||
|
var t3 = xmlDoc.getElementsByTagName("timestamp")[0].getAttribute("time3");
|
||||||
|
var serverDelay = Math.round((t3 - t2) / 1000); // server delay in milliseconds
|
||||||
|
timeDotGov.data.serverTime = Math.round(t3/1000); // Server time in milliseconds
|
||||||
|
|
||||||
|
var currentTimeObj2 = new Date();
|
||||||
|
timeDotGov.data.responseTime = currentTimeObj2.getTime(); // t4
|
||||||
|
timeDotGov.data.RThalf = ((timeDotGov.data.responseTime - timeDotGov.data.requestTime) - serverDelay) / 2; // (t4 - t1) - (t3 -t2) NTP EQ
|
||||||
|
timeDotGov.data.realTimeDif = timeDotGov.data.serverTime - timeDotGov.data.responseTime; // t3-t4 used for correction to local clock for official time
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
// USE OFFSET CHECK VAR TO ONLY CHECK CLIENT CLOCK ONCE ///
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
if (offsetCheck) {
|
||||||
|
// ROUND TO MILLISECONDS - USE (* -1) to invert timediff pos/neg display value
|
||||||
|
var diff = (timeDotGov.data.realTimeDif/1000) * -1;
|
||||||
|
var diffDisplay = diff.toFixed(3);
|
||||||
|
if (diffDisplay > 0 ) {
|
||||||
|
diffDisplay = "+" + diffDisplay;
|
||||||
|
}
|
||||||
|
document.getElementById("realTimeDif").innerHTML = diffDisplay;
|
||||||
|
offsetCheck = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
timeDotGov.clockController.runningclocks();
|
||||||
|
timeDotGov.data.leapFlag = "false";
|
||||||
|
} // END OF doData FUNCTION
|
||||||
|
|
||||||
|
// REFRESHES CLOCKS AT THE TOP OF EACH *ACTUAL* SECOND
|
||||||
|
timeDotGov.clockController.runningclocks = function() {
|
||||||
|
var deviceClock = new Date();
|
||||||
|
var fractionalZone =(Math.abs((deviceClock.getTimezoneOffset()/60))) % 1; // IF CLIENT IS IN A ZONE WITH A FRACTIONAL HOUR, MAKE ADJUSTMENT (MODULO 1 TO GET THE FRACTIONAL HOUR)
|
||||||
|
if (fractionalZone != 0) {
|
||||||
|
fractionalZone = (1 - fractionalZone); // SUBTRACT FROM 1 TO GET VALID CORRECTION FOR 30 AND 45 MIN ZONES
|
||||||
|
}
|
||||||
|
var fractionalZoneMilli = fractionalZone * 3600000; // CONVERT PARTIAL HOUR TO MILLISECONDS, TO BE SUBTRACTED FROM ADJUSTED TIME
|
||||||
|
var s = new Date(); // convert PC time and delay back to seconds (this is UTC)
|
||||||
|
|
||||||
|
s.setTime(s.getTime() + timeDotGov.data.realTimeDif + timeDotGov.data.RThalf - fractionalZoneMilli); // CORRECT FOR PC CLOCK ERROR, HALF NETWORK DELAY AND CLIENT IN PARTIAL TIME ZONE
|
||||||
|
|
||||||
|
var sec = s.getSeconds();
|
||||||
|
if (sec != timeDotGov.data.previousSec) { // call handleonrefresh as soon as you see a new second
|
||||||
|
timeDotGov.data.previousSec = sec;
|
||||||
|
timeDotGov.clockController.handleonrefresh(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK IF USER CHANGED CLOCK or IF NEW DST STATE or LEAP SECOND, THEN REFRESH ALL CLOCKS
|
||||||
|
timeDotGov.clockController.handleonrefresh = function(s) {
|
||||||
|
//var mins = s.getMinutes();
|
||||||
|
var now = s.getTime(); // convert time to ms since epoch (UTC)
|
||||||
|
//if (0 <= mins < 30) {
|
||||||
|
// now = now + fractionalZoneMilli;
|
||||||
|
//} else {
|
||||||
|
// now = now - fractionalZoneMilli;
|
||||||
|
//}
|
||||||
|
|
||||||
|
if (timeDotGov.data.then == null) {
|
||||||
|
timeDotGov.data.then = now; // if it's the first round, don't let it fail
|
||||||
|
}
|
||||||
|
|
||||||
|
var DidUserChangeClock = Math.abs(now - timeDotGov.data.then);
|
||||||
|
if (DidUserChangeClock >= 2000) { // if pc clock changed then reset (|now-then| should only be 1 s)
|
||||||
|
location.reload();
|
||||||
|
timeDotGov.data.then = now;
|
||||||
|
} else {
|
||||||
|
timeDotGov.data.then = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeDotGov.data.leapsecond / 1000 == Math.floor(now / 1000)) {
|
||||||
|
timeDotGov.data.leapFlag = "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
var clocks = document.getElementsByClassName("clock");
|
||||||
|
|
||||||
|
var clockNum; // SET TO VALUE OF 'i' IN THE FOLLOWING LOOP TO REPRESENT EACH CLOCK INSTANCE
|
||||||
|
|
||||||
|
// SET ALL OF THE CLOCKS BY CALLING setCurrentTime FOR EACH CLOCK INSTANCE
|
||||||
|
for(var i =0; i < clocks.length; i++ ){
|
||||||
|
|
||||||
|
var clock = clocks[i].getElementsByTagName("time")[0];
|
||||||
|
var zoneOffset = clocks[i].getElementsByTagName("time")[0].getAttribute("zoneOffset") || 0;
|
||||||
|
// GIVE VAR CLOCKNUM VALUE OF i
|
||||||
|
clockNum = i;
|
||||||
|
clock.innerHTML = timeDotGov.clock.setCurrentTime(timeDotGov.data.clockinstances[i], now, timeDotGov.data.twentyFour(), timeDotGov.data.dstStart, timeDotGov.data.dstEnd, zoneOffset, timeDotGov.data.leapFlag, timeDotGov.data.leapsec60, timeDotGov.data.RThalf, clockNum);
|
||||||
|
document.getElementById('timeUTC').innerHTML = timeDotGov.clock.setCurrentTime(timeDotGov.data.clockinstances[i], now, true, timeDotGov.data.dstStart, timeDotGov.data.dstEnd, 0, timeDotGov.data.leapFlag, timeDotGov.data.leapsec60, timeDotGov.data.RThalf, 999); // LAST VAR IS CLOCKNUM, PASSING AS '999' TO AVOID DUPLICATE 'i' VARIABLE VALUE SEND TO SETCURRENTTIME FUNCTION
|
||||||
|
}
|
||||||
|
|
||||||
|
} // END OF handleonrefresh FUNCTION
|
||||||
|
|
||||||
|
// CREATES CLOCK DIGITS AND DST/ST LABELS FOR EACH CLOCK INSTANCE
|
||||||
|
timeDotGov.clock.setCurrentTime = function(clock, now, twentyFour, dstStart, dstEnd, zoneOffset, leapFlag, leapsec60, RThalf, clockNum) {
|
||||||
|
|
||||||
|
var displayTime = new Date();
|
||||||
|
now = (now - (zoneOffset * 3600000));
|
||||||
|
|
||||||
|
if (leapFlag == "true"){
|
||||||
|
now = now - 1000; // if leap has occurred, show (seconds - 1) until next sync
|
||||||
|
var leapsec60 = "true"; // var to show a 60 instead of 59
|
||||||
|
}
|
||||||
|
|
||||||
|
displayTime.setTime(now);
|
||||||
|
|
||||||
|
var year = displayTime.getUTCFullYear();
|
||||||
|
var hourNum = displayTime.getUTCHours();
|
||||||
|
var minNum = displayTime.getMinutes();
|
||||||
|
var secNum = displayTime.getSeconds();
|
||||||
|
|
||||||
|
// CREATE ARRAY OF THE CLOCK NUMS WHO FOLLOW DST
|
||||||
|
// ALSO CREATE ARRAY OF DST LABEL CLASSES ////////////////////////
|
||||||
|
var DSTclocksArray = [ 0, 1, 6, 7, 8, 9 ]; // THESE ARE THE clockNums FOR THE ZONES THAT FOLLOW DST
|
||||||
|
var dstLabels = document.getElementsByClassName("DSTterm"); // CREATE ARRAY FOR DST LABELS
|
||||||
|
var dstLetters = document.getElementsByClassName("DSTletter"); // CREATE ARRAY FOR DST ABBREVIATION
|
||||||
|
var dstNums = document.getElementsByClassName("DSTnum"); // CREATE ARRAY FOR DST OFFSET NUM
|
||||||
|
|
||||||
|
if ( now >= dstStart && now <= dstEnd ) { // CHECK IF THIS SECOND IS DST OR NOT
|
||||||
|
|
||||||
|
// CHECK IF CLOCK NUM IS IN DST ARRAY, IF SO, ADD THE DST HOUR AND LABELS
|
||||||
|
// if ( DSTclocksArray.includes(clockNum) ) {
|
||||||
|
if ( DSTclocksArray.indexOf(clockNum) > -1 ) { // indexOf fixes a chance in ie
|
||||||
|
hourNum = hourNum + 1;
|
||||||
|
// AT END OF FUNC ADD/PUSH CLOCKNUM TO dstClocksUpdated ARRAY, IF CLOCKNUM IS IN THERE, DONT UPDATE AGAIN
|
||||||
|
// if ( !dstClocksUpdated.includes( clockNum ) ) {
|
||||||
|
if ( dstClocksUpdated.indexOf( clockNum ) == -1 ) { // indexOf fixes a chance in ie
|
||||||
|
dstLabels[clockNum].innerHTML = "DAYLIGHT";
|
||||||
|
dstLetters[clockNum].innerHTML = "D";
|
||||||
|
var standardNum = Number(dstNums[clockNum].innerHTML);
|
||||||
|
dstNums[clockNum].innerHTML = standardNum - 1;
|
||||||
|
dstClocksUpdated.push(clockNum); // ADD CLOCK NUM TO ARRAY OF CLOCKS UPDATED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// IF DST OR INCREMENTED HOUR GOES INTO NEXT DAY, RESET TO HOUR ZERO AND ADD A DAY
|
||||||
|
if (hourNum > 23) {
|
||||||
|
hourNum = 0;
|
||||||
|
now = (now + 3600000) // advance now by one hour
|
||||||
|
displayTime.setTime(now); // reset displaytime so day/date/month are correct with new day
|
||||||
|
}
|
||||||
|
// CHECK FOR AND IMPLEMENT LEAP SECOND
|
||||||
|
if (leapFlag == "true") {
|
||||||
|
if (leapsec60 == "true") {
|
||||||
|
if (minNum == "59") {
|
||||||
|
if (secNum == "59") { // if leapsec, and 59:59 then show 60 and reset
|
||||||
|
secNum = "60";
|
||||||
|
leapsec60 = "false";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var hourLabel = "";
|
||||||
|
|
||||||
|
// 12-HOUR OR 24-HOUR DISPLAY
|
||||||
|
|
||||||
|
// BOX NOT CHECKED SO 12-HOUR DISPLAY
|
||||||
|
if (!(twentyFour)) {
|
||||||
|
|
||||||
|
if (hourNum > 11) {
|
||||||
|
hourNum -= 12;
|
||||||
|
am_pm = "P.M.";
|
||||||
|
} else {
|
||||||
|
am_pm = "A.M.";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
am_pm = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
var newVal;
|
||||||
|
// SET HOURS
|
||||||
|
if(hourNum > 9) {
|
||||||
|
newVal = Number(String(hourNum).charAt(0));
|
||||||
|
if(timeDotGov.data.myhour0 != newVal) {
|
||||||
|
timeDotGov.data.myhour0 = newVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(Number(timeDotGov.data.myhour0) != 0) {
|
||||||
|
timeDotGov.data.myhour0 = "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Number(hourNum) > 9){
|
||||||
|
newVal=Number(String(hourNum).charAt(1));
|
||||||
|
if(timeDotGov.data.myhour1 != newVal) {
|
||||||
|
timeDotGov.data.myhour1 = newVal;
|
||||||
|
}
|
||||||
|
} else if (Number(timeDotGov.data.myhour1) != Number(hourNum)) {
|
||||||
|
timeDotGov.data.myhour1 = Number(hourNum);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((hourNum < 1) && (!(twentyFour))) { // if not 24hour time force the 12 so it's not 00
|
||||||
|
timeDotGov.data.myhour0 = 1;
|
||||||
|
timeDotGov.data.myhour1 = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SET MINUTES
|
||||||
|
if(minNum > 9) {
|
||||||
|
newVal = Number(String(minNum).charAt(0));
|
||||||
|
if (timeDotGov.data.mymin0 != newVal) {
|
||||||
|
timeDotGov.data.mymin0 = newVal;
|
||||||
|
}
|
||||||
|
} else if(Number(timeDotGov.data.mymin0) != 0) {
|
||||||
|
timeDotGov.data.mymin0 = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Number(minNum) > 9) {
|
||||||
|
newVal = Number(String(minNum).charAt(1));
|
||||||
|
if(timeDotGov.data.mymin1 != newVal) {
|
||||||
|
timeDotGov.data.mymin1 = newVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(Number(timeDotGov.data.mymin1) != Number(minNum)) {
|
||||||
|
timeDotGov.data.mymin1 = Number(minNum);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SET SECONDS
|
||||||
|
if(secNum > 9) {
|
||||||
|
newVal = Number(String(secNum).charAt(0));
|
||||||
|
if(timeDotGov.data.mysec0 != newVal) {
|
||||||
|
timeDotGov.data.mysec0 = newVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(Number(timeDotGov.data.mysec0) != 0) {
|
||||||
|
timeDotGov.data.mysec0 = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Number(secNum) > 9) {
|
||||||
|
newVal=Number(String(secNum).charAt(1));
|
||||||
|
if(timeDotGov.data.mysec1 != newVal) {
|
||||||
|
timeDotGov.data.mysec1 = newVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(Number(timeDotGov.data.mysec1) != Number(secNum)) {
|
||||||
|
timeDotGov.data.mysec1 = Number(secNum);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CREATE CLOCK DIGITS STRING TO DISPLAY
|
||||||
|
var clockdigits = (timeDotGov.data.myhour0 + "" + timeDotGov.data.myhour1 + ":" + timeDotGov.data.mymin0 + timeDotGov.data.mymin1 + ":" + timeDotGov.data.mysec0 + timeDotGov.data.mysec1);
|
||||||
|
clock = clockdigits + " " + am_pm;
|
||||||
|
return clock;
|
||||||
|
|
||||||
|
} // END OF setCurrentTime FUNCTION
|
||||||
|
|
||||||
|
|
||||||