commit a6b3f039d8d6dd7e0c305564f1953be9cdf48967 Author: Seth Date: Sun Mar 8 22:32:38 2026 +0000 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. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9460c99 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +__pycache__/ +*.pyc +*.pyo +.env diff --git a/app.js b/app.js new file mode 100644 index 0000000..25d3f29 --- /dev/null +++ b/app.js @@ -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(); diff --git a/astro.html b/astro.html new file mode 100644 index 0000000..aa2fbd7 --- /dev/null +++ b/astro.html @@ -0,0 +1,139 @@ + + + + + + SethPC Astronomy + + + + + +
+
+ SethPC logo + SethPC Astronomy +
+ +
+ +

โ€”

+ +
+ +
+ ๐ŸŒ‘ +
โ€”
+
โ€”
+
+ +
Sun
+
+

Sunrise: โ€”

+

Sunset: โ€”

+

Day length: โ€”

+

Solar noon: โ€”

+
+ +
Moon
+
+

Age: โ€”

+

Next new moon: โ€”

+

Next full moon: โ€”

+
+ + +
+ + + diff --git a/astro.js b/astro.js new file mode 100644 index 0000000..8d20c36 --- /dev/null +++ b/astro.js @@ -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 = `${ev.name}${ds}`; + ul.appendChild(li); + }); +} + +document.getElementById("prevDay").addEventListener("click", () => { + currentDate = stepDate(currentDate, -1); + render(); +}); +document.getElementById("nextDay").addEventListener("click", () => { + currentDate = stepDate(currentDate, +1); + render(); +}); + +render(); diff --git a/calendar.html b/calendar.html new file mode 100644 index 0000000..5779ab0 --- /dev/null +++ b/calendar.html @@ -0,0 +1,290 @@ + + + + + + SethPC Calendar + + + + + +
+
+ SethPC logo + SethPC Calendar +
+ +
+ + + + + +
+ +
+
+ +
+ Today + Holiday days + + Seth weekend (D4โ€“D5) + + + Gregorian weekend + + + Moon phase (click for astronomy) + +
+
+ + + diff --git a/calendar.js b/calendar.js new file mode 100644 index 0000000..bc45717 --- /dev/null +++ b/calendar.js @@ -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); diff --git a/clock.service b/clock.service new file mode 100644 index 0000000..ea28f25 --- /dev/null +++ b/clock.service @@ -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 diff --git a/convert.html b/convert.html new file mode 100644 index 0000000..ede2faf --- /dev/null +++ b/convert.html @@ -0,0 +1,155 @@ + + + + + + SethPC Time Converter + + + + + +
+
+ SethPC logo + Time Converter +
+

Decimal ↔ Gregorian  ยท  Seth Date →

+ + + +
+
+
Gregorian (24h)
+
+ + : + + : + + . + +
+
+
+ +
โ‡…
+ +
+
Decimal (Seth)
+
+ + : + + : + + . + +
+
+
+
+ +

+ 1 decimal hour = 2h 24m Gregorian  ยท  + 1 decimal second = 0.864 SI seconds +

+
+ + + diff --git a/convert.js b/convert.js new file mode 100644 index 0000000..73ebf1f --- /dev/null +++ b/convert.js @@ -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(); diff --git a/decimal.html b/decimal.html new file mode 100644 index 0000000..f6ec806 --- /dev/null +++ b/decimal.html @@ -0,0 +1,142 @@ + + + + + + SethPC Decimal Time + + + + + +
+
+ SethPC logo + SethPC Decimal Time +
+

French Republican / Decimal Time & Date

+

Loading date...

+

+

-:--.--

+ +
+ + +
+ +
+

+ Decimal time 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. +

+

+ The date uses the French Republican Calendar, 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). +

+

Reading the date: dates are written as + Weekday, Day Month An Year — e.g. Sextidi, 6 Ventรดse An CCXXXIII + 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. +

+

Months (each 30 days, grouped by season):

+ + + + + + + + + + + + + + + + +
#FrenchEnglishApprox. Gregorian
1VendรฉmiaireVintageSep 22 โ€“ Oct 21
2BrumaireMistOct 22 โ€“ Nov 20
3FrimaireFrostNov 21 โ€“ Dec 20
4NivรดseSnowyDec 21 โ€“ Jan 19
5PluviรดseRainyJan 20 โ€“ Feb 18
6VentรดseWindyFeb 19 โ€“ Mar 20
7GerminalBuddingMar 21 โ€“ Apr 19
8FlorรฉalFloweryApr 20 โ€“ May 19
9PrairialMeadowMay 20 โ€“ Jun 18
10MessidorHarvestJun 19 โ€“ Jul 18
11ThermidorHeatJul 19 โ€“ Aug 17
12FructidorFruitAug 18 โ€“ Sep 16
+

Weekdays (the 10-day dรฉcade):

+ + + + + + + + + + + + + + +
DayFrenchEnglish
1PrimidiFirst day
2DuodiSecond day
3TridiThird day
4QuartidiFourth day
5QuintidiFifth day
6SextidiSixth day
7SeptidiSeventh day
8OctidiEighth day
9NonidiNinth day
10DรฉcadiTenth day (rest day)
+

Complementary days (jours complรฉmentaires, after Fructidor):

+ + + + + + + + + + +
DayFrenchEnglish
1Jour de la VertuDay of Virtue
2Jour du GรฉnieDay of Genius
3Jour du TravailDay of Labour
4Jour de l'OpinionDay of Opinion
5Jour des RรฉcompensesDay of Rewards
6*Jour de la RรฉvolutionDay of the Revolution
+

* Leap years only.

+
+ +
+

Decimal Time: -

+

Day of Year: -

+

Republican Year: -

+

Time Zone: -

+

Status: calibrating...

+

Gregorian UTC: -

+

Source: Hosted by SethPC

+
+
+ + + diff --git a/decimal.js b/decimal.js new file mode 100644 index 0000000..8498fc3 --- /dev/null +++ b/decimal.js @@ -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(); diff --git a/index.html b/index.html new file mode 100644 index 0000000..f97cede --- /dev/null +++ b/index.html @@ -0,0 +1,34 @@ + + + + + + Sethflix Official Time + + + + +
+
+ Sethflix logo + SethPC Time +
+

Official U.S. Time

+

Loading date...

+

--:--:--.--

+ +
+ + +
+ +
+

Time Zone: -

+

Status: calibrating...

+

UTC: -

+

Source: Hosted by SethPC

+
+
+ + + diff --git a/server-live.py b/server-live.py new file mode 100644 index 0000000..b61bf2b --- /dev/null +++ b/server-live.py @@ -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 = ( + "" + "" + f"{now.year}" + f"3 {dst_start_day} 11 {dst_end_day}" + "2099 12 31" + "" + ) + 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"" + 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() diff --git a/server.py b/server.py new file mode 100755 index 0000000..887e33f --- /dev/null +++ b/server.py @@ -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 = ( + "" + "" + f"{now.year}" + f"3 {dst_start_day} 11 {dst_end_day}" + "2099 12 31" + "" + ) + 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"" + 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() diff --git a/seth.html b/seth.html new file mode 100644 index 0000000..3461398 --- /dev/null +++ b/seth.html @@ -0,0 +1,267 @@ + + + + + + SethPC Seth Date + + + + + +
+
+ SethPC logo + SethPC Seth Date +
+

Seth Calendar & Decimal Time  ยท  + Calendar  ยท  + Converter  ยท  + Decimal Time  ยท  + Astronomy +

+

Loading date...

+

-:--.--

+ +
+ + +
+ +
+
+

DECI: -

+

GREG: -

+
+

Time Zone: -

+

Day of Year: -

+

Status: calibrating...

+

Week of Year: -

+

Gregorian UTC: -

+

Source: Hosted by SethPC

+
+ +
+ History +
+

+ The Seth Calendar was invented by Seth Freiberg 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. +

+

+ 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. +

+

+ 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. +

+

+ Credit is due to the French Republican Calendar (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. +

+
+
+ +
+ About Seth Date +
+

+ Seth Date uses the same year numbers and January 1 new year as the Gregorian + calendar. The year is divided into 10 months of 36 days (6 weeks of 6 days each), + followed by 5 holiday days at year end (6 on leap years). + All units are zero-indexed: 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: 1 Seth second = 0.864 SI seconds (86,400 SI seconds per day ÷ 100,000 Seth seconds per day). +

+

+ Leap Day (Gregorian Feb 29) is a special intercalary day that exists + outside 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. +

+

Reading the date: dates are written as + Year M W D — + e.g. Month 3, Week 2, Day 4 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 Holiday N (N = 0–4, or 0–5 on leap years) and fall outside any month or week. +

+

Months (months 0–9, days 0–35, approx. Gregorian ranges):

+ + + + + + + + + + + + + + +
#DaysApprox. GregorianNotes
00–35Jan 1 – Feb 5
10–35Feb 6 – Mar 13
20–35Mar 14 – Apr 18
30–35Apr 19 – May 24
40–35May 25 – Jun 29
50–35Jun 30 – Aug 4
60–35Aug 5 – Sep 9
70–35Sep 10 – Oct 15
80–35Oct 16 – Nov 20
90–35Nov 21 – Dec 26Ends Dec 25 on leap years
+

Weeks (weeks 0–5 within each month, days 0–5 within each week):

+ + + + + + + + + + +
WeekDays
00–5
16–11
212–17
318–23
424–29
530–35
+

Holiday days (after Month 9, Day 35):

+ + + + + + + + + + +
HolidayNormal yearLeap year
H0Dec 27Dec 26 — Boxing Day
H1Dec 28Dec 27
H2Dec 29Dec 28
H3Dec 30Dec 29
H4Dec 31 — New Year's EveDec 30
H5*Dec 31 — New Year's Eve
+

+ * Leap years only.
+ Christmas (Dec 25) falls on Month 9, Day 34 in normal years, and Month 9, Day 35 in leap years.
+ Dec 26 (Boxing Day) is Month 9, Day 35 in normal years, and Holiday 0 in leap years. +

+
+
+ +
+ Time Conversions +
+

Each decimal hour = 2 hours 24 minutes Gregorian. Each Gregorian hour = ~0:41:67 decimal.

+
+
+

Decimal → Gregorian

+ + + + + + + + + + + + + + +
Decimal hourGregorian (24h)
0:0000:00
1:0002:24
2:0004:48
3:0007:12
4:0009:36
5:0012:00
6:0014:24
7:0016:48
8:0019:12
9:0021:36
+
+
+

Gregorian → Decimal

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Gregorian (24h)Decimal hour
00:000:00
01:000:41:67
02:000:83:33
03:001:25:00
04:001:66:67
05:002:08:33
06:002:50:00
07:002:91:67
08:003:33:33
09:003:75:00
10:004:16:67
11:004:58:33
12:005:00:00
13:005:41:67
14:005:83:33
15:006:25:00
16:006:66:67
17:007:08:33
18:007:50:00
19:007:91:67
20:008:33:33
21:008:75:00
22:009:16:67
23:009:58:33
+
+
+
+
+
+ + + diff --git a/seth.js b/seth.js new file mode 100644 index 0000000..bdd06f4 --- /dev/null +++ b/seth.js @@ -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(); diff --git a/style.css b/style.css new file mode 100644 index 0000000..026e850 --- /dev/null +++ b/style.css @@ -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; + } +} diff --git a/timegov b/timegov new file mode 120000 index 0000000..de3afb3 --- /dev/null +++ b/timegov @@ -0,0 +1 @@ +/opt/clock-site/timegov-clone/time.gov \ No newline at end of file diff --git a/timegov-clone/NIST-styles.css b/timegov-clone/NIST-styles.css new file mode 100644 index 0000000..a642271 --- /dev/null +++ b/timegov-clone/NIST-styles.css @@ -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; +} diff --git a/timegov-clone/analogClock.js b/timegov-clone/analogClock.js new file mode 100644 index 0000000..db99007 --- /dev/null +++ b/timegov-clone/analogClock.js @@ -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); + } \ No newline at end of file diff --git a/timegov-clone/application.js b/timegov-clone/application.js new file mode 100644 index 0000000..27509ff --- /dev/null +++ b/timegov-clone/application.js @@ -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); + + }; + diff --git a/timegov-clone/index.html b/timegov-clone/index.html new file mode 100644 index 0000000..2a2470c --- /dev/null +++ b/timegov-clone/index.html @@ -0,0 +1,363 @@ + + + + + + + + + + + + + + + + + + + + + + + Atermiter-X79 Official Time + + + + + + + + + + + + + + +
+ + +
+
+ U.S. FlagOfficial self-hosted time service +
+
+ + + + + +
+
+
+ OFFICIAL U.S. TIME +
+
+
+ + +

+ +
+ + +
+ + +
+
+ +
Non-Contiguous U.S. and Territories
+ +
+ +
+
Alaska Standard Time
+
AKST (UTC-9)
+
+
+
+
+
+ + + Alaska Map + + +
+
Aleutian Standard Time
+
HAST (UTC-10)
+
+
+
+
+ +
+
+ +
+ +
+
Hawaii Standard Time
+
HST (UTC-10)
+
+
+
+
+
+
+ + + Hawaii Map + + +
+
Samoa Standard Time
+
SST (UTC-11)
+
+
+
+
+
+
+ + +
+
Chamorro Standard Time
+
CHST (UTC+10)
+
+
+
+
+
+
+ + +
+
+
Atlantic Standard Time
+
Puerto Rico / US Virgin Islands
+
AST (UTC-4)
+
+
+
+
+
+
+
+ +
+
+ + +
+
+
 
+ + +
+ + +
+
+
+
Pacific Standard Time
+
PST (UTC-8)
+
+
+
+
+
+
+
+ + +
+
+
+
Mountain Standard Time
+
MST (UTC-7)
+
+
+
+
+
+
+
+ + +
+
+
+
Central Standard Time
+
CST (UTC-6)
+
+
+
+
+
+
+
+ + +
+
+
+
Eastern Standard Time
+
EST (UTC-5)
+
+
+
+
+
+
+
+ +
+
+ + + +
+ +
+
+
+
Arizona Mountain
Standard Time
+
MST (UTC-7)
+
+
+
+
+
+
+
+
+ + + United States Time Zone Map +
+ +
+ + +
+
+ + +
+
+ + +
+
+ +
+

Coordinated Universal Time (UTC)

+
+
+ +
+

UTC is always displayed as a 24-hour clock.

+ + +
+

Your Device's Clock UTC-0
+

+ +
+

+ Your clock is off by:
s

+
+ + + +
+
+
Atlantic Standard Time
+
Puerto Rico / US Virgin Islands
+
AST (UTC-4)
+
+
+
+
+
+
+
+ +
+ Simple View + Time API +
+ Hashed image for Daylight Saving Time not observed = DAYLIGHT SAVING TIME NOT OBSERVED +

Clocks are corrected for network delay

+
+
+ +
+
+ +
+
+ + + + +
+ + + + + + + diff --git a/timegov-clone/jquery-3.7.1.min.js b/timegov-clone/jquery-3.7.1.min.js new file mode 100644 index 0000000..7f37b5d --- /dev/null +++ b/timegov-clone/jquery-3.7.1.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.7.1 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(ie,e){"use strict";var oe=[],r=Object.getPrototypeOf,ae=oe.slice,g=oe.flat?function(e){return oe.flat.call(e)}:function(e){return oe.concat.apply([],e)},s=oe.push,se=oe.indexOf,n={},i=n.toString,ue=n.hasOwnProperty,o=ue.toString,a=o.call(Object),le={},v=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},y=function(e){return null!=e&&e===e.window},C=ie.document,u={type:!0,src:!0,nonce:!0,noModule:!0};function m(e,t,n){var r,i,o=(n=n||C).createElement("script");if(o.text=e,t)for(r in u)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[i.call(e)]||"object":typeof e}var t="3.7.1",l=/HTML$/i,ce=function(e,t){return new ce.fn.init(e,t)};function c(e){var t=!!e&&"length"in e&&e.length,n=x(e);return!v(e)&&!y(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+ge+")"+ge+"*"),x=new RegExp(ge+"|>"),j=new RegExp(g),A=new RegExp("^"+t+"$"),D={ID:new RegExp("^#("+t+")"),CLASS:new RegExp("^\\.("+t+")"),TAG:new RegExp("^("+t+"|[*])"),ATTR:new RegExp("^"+p),PSEUDO:new RegExp("^"+g),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+ge+"*(even|odd|(([+-]|)(\\d*)n|)"+ge+"*(?:([+-]|)"+ge+"*(\\d+)|))"+ge+"*\\)|)","i"),bool:new RegExp("^(?:"+f+")$","i"),needsContext:new RegExp("^"+ge+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ge+"*((?:-\\d)?\\d*)"+ge+"*\\)|)(?=[^-]|$)","i")},N=/^(?:input|select|textarea|button)$/i,q=/^h\d$/i,L=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,H=/[+~]/,O=new RegExp("\\\\[\\da-fA-F]{1,6}"+ge+"?|\\\\([^\\r\\n\\f])","g"),P=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},M=function(){V()},R=J(function(e){return!0===e.disabled&&fe(e,"fieldset")},{dir:"parentNode",next:"legend"});try{k.apply(oe=ae.call(ye.childNodes),ye.childNodes),oe[ye.childNodes.length].nodeType}catch(e){k={apply:function(e,t){me.apply(e,ae.call(t))},call:function(e){me.apply(e,ae.call(arguments,1))}}}function I(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(V(e),e=e||T,C)){if(11!==p&&(u=L.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return k.call(n,a),n}else if(f&&(a=f.getElementById(i))&&I.contains(e,a)&&a.id===i)return k.call(n,a),n}else{if(u[2])return k.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&e.getElementsByClassName)return k.apply(n,e.getElementsByClassName(i)),n}if(!(h[t+" "]||d&&d.test(t))){if(c=t,f=e,1===p&&(x.test(t)||m.test(t))){(f=H.test(t)&&U(e.parentNode)||e)==e&&le.scope||((s=e.getAttribute("id"))?s=ce.escapeSelector(s):e.setAttribute("id",s=S)),o=(l=Y(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+Q(l[o]);c=l.join(",")}try{return k.apply(n,f.querySelectorAll(c)),n}catch(e){h(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return re(t.replace(ve,"$1"),e,n,r)}function W(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function F(e){return e[S]=!0,e}function $(e){var t=T.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function B(t){return function(e){return fe(e,"input")&&e.type===t}}function _(t){return function(e){return(fe(e,"input")||fe(e,"button"))&&e.type===t}}function z(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&R(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function X(a){return F(function(o){return o=+o,F(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function U(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}function V(e){var t,n=e?e.ownerDocument||e:ye;return n!=T&&9===n.nodeType&&n.documentElement&&(r=(T=n).documentElement,C=!ce.isXMLDoc(T),i=r.matches||r.webkitMatchesSelector||r.msMatchesSelector,r.msMatchesSelector&&ye!=T&&(t=T.defaultView)&&t.top!==t&&t.addEventListener("unload",M),le.getById=$(function(e){return r.appendChild(e).id=ce.expando,!T.getElementsByName||!T.getElementsByName(ce.expando).length}),le.disconnectedMatch=$(function(e){return i.call(e,"*")}),le.scope=$(function(){return T.querySelectorAll(":scope")}),le.cssHas=$(function(){try{return T.querySelector(":has(*,:jqfake)"),!1}catch(e){return!0}}),le.getById?(b.filter.ID=function(e){var t=e.replace(O,P);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(O,P);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):t.querySelectorAll(e)},b.find.CLASS=function(e,t){if("undefined"!=typeof t.getElementsByClassName&&C)return t.getElementsByClassName(e)},d=[],$(function(e){var t;r.appendChild(e).innerHTML="",e.querySelectorAll("[selected]").length||d.push("\\["+ge+"*(?:value|"+f+")"),e.querySelectorAll("[id~="+S+"-]").length||d.push("~="),e.querySelectorAll("a#"+S+"+*").length||d.push(".#.+[+~]"),e.querySelectorAll(":checked").length||d.push(":checked"),(t=T.createElement("input")).setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),r.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&d.push(":enabled",":disabled"),(t=T.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||d.push("\\["+ge+"*name"+ge+"*="+ge+"*(?:''|\"\")")}),le.cssHas||d.push(":has"),d=d.length&&new RegExp(d.join("|")),l=function(e,t){if(e===t)return a=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!le.sortDetached&&t.compareDocumentPosition(e)===n?e===T||e.ownerDocument==ye&&I.contains(ye,e)?-1:t===T||t.ownerDocument==ye&&I.contains(ye,t)?1:o?se.call(o,e)-se.call(o,t):0:4&n?-1:1)}),T}for(e in I.matches=function(e,t){return I(e,null,null,t)},I.matchesSelector=function(e,t){if(V(e),C&&!h[t+" "]&&(!d||!d.test(t)))try{var n=i.call(e,t);if(n||le.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){h(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(O,P),e[3]=(e[3]||e[4]||e[5]||"").replace(O,P),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||I.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&I.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return D.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&j.test(n)&&(t=Y(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(O,P).toLowerCase();return"*"===e?function(){return!0}:function(e){return fe(e,t)}},CLASS:function(e){var t=s[e+" "];return t||(t=new RegExp("(^|"+ge+")"+e+"("+ge+"|$)"))&&s(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=I.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function T(e,n,r){return v(n)?ce.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?ce.grep(e,function(e){return e===n!==r}):"string"!=typeof n?ce.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(ce.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||k,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:S.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof ce?t[0]:t,ce.merge(this,ce.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:C,!0)),w.test(r[1])&&ce.isPlainObject(t))for(r in t)v(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=C.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):v(e)?void 0!==n.ready?n.ready(e):e(ce):ce.makeArray(e,this)}).prototype=ce.fn,k=ce(C);var E=/^(?:parents|prev(?:Until|All))/,j={children:!0,contents:!0,next:!0,prev:!0};function A(e,t){while((e=e[t])&&1!==e.nodeType);return e}ce.fn.extend({has:function(e){var t=ce(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,Ce=/^$|^module$|\/(?:java|ecma)script/i;xe=C.createDocumentFragment().appendChild(C.createElement("div")),(be=C.createElement("input")).setAttribute("type","radio"),be.setAttribute("checked","checked"),be.setAttribute("name","t"),xe.appendChild(be),le.checkClone=xe.cloneNode(!0).cloneNode(!0).lastChild.checked,xe.innerHTML="",le.noCloneChecked=!!xe.cloneNode(!0).lastChild.defaultValue,xe.innerHTML="",le.option=!!xe.lastChild;var ke={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function Se(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&fe(e,t)?ce.merge([e],n):n}function Ee(e,t){for(var n=0,r=e.length;n",""]);var je=/<|&#?\w+;/;function Ae(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function Re(e,t){return fe(e,"table")&&fe(11!==t.nodeType?t:t.firstChild,"tr")&&ce(e).children("tbody")[0]||e}function Ie(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function We(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Fe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(_.hasData(e)&&(s=_.get(e).events))for(i in _.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),C.head.appendChild(r[0])},abort:function(){i&&i()}}});var Jt,Kt=[],Zt=/(=)\?(?=&|$)|\?\?/;ce.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Kt.pop()||ce.expando+"_"+jt.guid++;return this[e]=!0,e}}),ce.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Zt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Zt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=v(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Zt,"$1"+r):!1!==e.jsonp&&(e.url+=(At.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||ce.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=ie[r],ie[r]=function(){o=arguments},n.always(function(){void 0===i?ce(ie).removeProp(r):ie[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Kt.push(r)),o&&v(i)&&i(o[0]),o=i=void 0}),"script"}),le.createHTMLDocument=((Jt=C.implementation.createHTMLDocument("").body).innerHTML="
",2===Jt.childNodes.length),ce.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(le.createHTMLDocument?((r=(t=C.implementation.createHTMLDocument("")).createElement("base")).href=C.location.href,t.head.appendChild(r)):t=C),o=!n&&[],(i=w.exec(e))?[t.createElement(i[1])]:(i=Ae([e],t,o),o&&o.length&&ce(o).remove(),ce.merge([],i.childNodes)));var r,i,o},ce.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(ce.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},ce.expr.pseudos.animated=function(t){return ce.grep(ce.timers,function(e){return t===e.elem}).length},ce.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=ce.css(e,"position"),c=ce(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=ce.css(e,"top"),u=ce.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),v(t)&&(t=t.call(e,n,ce.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},ce.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){ce.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===ce.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===ce.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=ce(e).offset()).top+=ce.css(e,"borderTopWidth",!0),i.left+=ce.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-ce.css(r,"marginTop",!0),left:t.left-i.left-ce.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===ce.css(e,"position"))e=e.offsetParent;return e||J})}}),ce.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;ce.fn[t]=function(e){return M(this,function(e,t,n){var r;if(y(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),ce.each(["top","left"],function(e,n){ce.cssHooks[n]=Ye(le.pixelPosition,function(e,t){if(t)return t=Ge(e,n),_e.test(t)?ce(e).position()[n]+"px":t})}),ce.each({Height:"height",Width:"width"},function(a,s){ce.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){ce.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return M(this,function(e,t,n){var r;return y(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?ce.css(e,t,i):ce.style(e,t,n,i)},s,n?e:void 0,n)}})}),ce.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){ce.fn[t]=function(e){return this.on(t,e)}}),ce.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.on("mouseenter",e).on("mouseleave",t||e)}}),ce.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){ce.fn[n]=function(e,t){return 0 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 + + diff --git a/timegov-clone/time.gov/css/NIST-styles.css b/timegov-clone/time.gov/css/NIST-styles.css new file mode 100644 index 0000000..a642271 --- /dev/null +++ b/timegov-clone/time.gov/css/NIST-styles.css @@ -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; +} diff --git a/timegov-clone/time.gov/css/main.css b/timegov-clone/time.gov/css/main.css new file mode 100644 index 0000000..3fefe17 --- /dev/null +++ b/timegov-clone/time.gov/css/main.css @@ -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 //////////////////////// + /////////////////////////////////////////////////////////// +*/ + diff --git a/timegov-clone/time.gov/favicon.ico b/timegov-clone/time.gov/favicon.ico new file mode 100644 index 0000000..67faa7e Binary files /dev/null and b/timegov-clone/time.gov/favicon.ico differ diff --git a/timegov-clone/time.gov/img/USNO_logo_large.png b/timegov-clone/time.gov/img/USNO_logo_large.png new file mode 100644 index 0000000..0c9b1b9 Binary files /dev/null and b/timegov-clone/time.gov/img/USNO_logo_large.png differ diff --git a/timegov-clone/time.gov/img/USNO_logo_sh2.png b/timegov-clone/time.gov/img/USNO_logo_sh2.png new file mode 100644 index 0000000..888a197 Binary files /dev/null and b/timegov-clone/time.gov/img/USNO_logo_sh2.png differ diff --git a/timegov-clone/time.gov/img/arrow-down.svg b/timegov-clone/time.gov/img/arrow-down.svg new file mode 100644 index 0000000..fe3ae3b --- /dev/null +++ b/timegov-clone/time.gov/img/arrow-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/timegov-clone/time.gov/img/arrow-up.svg b/timegov-clone/time.gov/img/arrow-up.svg new file mode 100644 index 0000000..d2849ea --- /dev/null +++ b/timegov-clone/time.gov/img/arrow-up.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/timegov-clone/time.gov/img/csscheckbox.png b/timegov-clone/time.gov/img/csscheckbox.png new file mode 100644 index 0000000..f134340 Binary files /dev/null and b/timegov-clone/time.gov/img/csscheckbox.png differ diff --git a/timegov-clone/time.gov/img/diagonal-white.png b/timegov-clone/time.gov/img/diagonal-white.png new file mode 100644 index 0000000..6401235 Binary files /dev/null and b/timegov-clone/time.gov/img/diagonal-white.png differ diff --git a/timegov-clone/time.gov/img/icon-dot-gov.svg b/timegov-clone/time.gov/img/icon-dot-gov.svg new file mode 100644 index 0000000..27182dc --- /dev/null +++ b/timegov-clone/time.gov/img/icon-dot-gov.svg @@ -0,0 +1 @@ +dot gov icon \ No newline at end of file diff --git a/timegov-clone/time.gov/img/icon-https.svg b/timegov-clone/time.gov/img/icon-https.svg new file mode 100644 index 0000000..3f98a93 --- /dev/null +++ b/timegov-clone/time.gov/img/icon-https.svg @@ -0,0 +1 @@ +https icon \ No newline at end of file diff --git a/timegov-clone/time.gov/img/map-elements/alaska.svg b/timegov-clone/time.gov/img/map-elements/alaska.svg new file mode 100644 index 0000000..02f3ec1 --- /dev/null +++ b/timegov-clone/time.gov/img/map-elements/alaska.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/timegov-clone/time.gov/img/map-elements/hawaii.svg b/timegov-clone/time.gov/img/map-elements/hawaii.svg new file mode 100644 index 0000000..966035c --- /dev/null +++ b/timegov-clone/time.gov/img/map-elements/hawaii.svg @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/timegov-clone/time.gov/img/map-elements/united-states-map.svg b/timegov-clone/time.gov/img/map-elements/united-states-map.svg new file mode 100644 index 0000000..c84b67c --- /dev/null +++ b/timegov-clone/time.gov/img/map-elements/united-states-map.svg @@ -0,0 +1,777 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/timegov-clone/time.gov/img/nist_logo_sidestack_large.png b/timegov-clone/time.gov/img/nist_logo_sidestack_large.png new file mode 100644 index 0000000..febc955 Binary files /dev/null and b/timegov-clone/time.gov/img/nist_logo_sidestack_large.png differ diff --git a/timegov-clone/time.gov/img/nist_logo_sidestack_rev.png b/timegov-clone/time.gov/img/nist_logo_sidestack_rev.png new file mode 100644 index 0000000..5890be2 Binary files /dev/null and b/timegov-clone/time.gov/img/nist_logo_sidestack_rev.png differ diff --git a/timegov-clone/time.gov/img/stripes.svg b/timegov-clone/time.gov/img/stripes.svg new file mode 100644 index 0000000..17ae014 --- /dev/null +++ b/timegov-clone/time.gov/img/stripes.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/timegov-clone/time.gov/img/us_flag_small.png b/timegov-clone/time.gov/img/us_flag_small.png new file mode 100644 index 0000000..34b927b Binary files /dev/null and b/timegov-clone/time.gov/img/us_flag_small.png differ diff --git a/timegov-clone/time.gov/index.html b/timegov-clone/time.gov/index.html new file mode 100644 index 0000000..6dcdb2e --- /dev/null +++ b/timegov-clone/time.gov/index.html @@ -0,0 +1,369 @@ + + + + + + + + + + + + + + + + + + + + + + + SethPC Official Time + + + + + + + + + + + + + + +
+ + +
+
+ U.S. FlagOfficial self-hosted time service +
+
+ + + + + +
+
+
+ OFFICIAL U.S. TIME +
+
+
+ + +

+ +
+ + +
+ + +
+
+ +
Non-Contiguous U.S. and Territories
+ +
+ +
+
Alaska Standard Time
+
AKST (UTC-9)
+
+
+
+
+
+ + + Alaska Map + + +
+
Aleutian Standard Time
+
HAST (UTC-10)
+
+
+
+
+ +
+
+ +
+ +
+
Hawaii Standard Time
+
HST (UTC-10)
+
+
+
+
+
+
+ + + Hawaii Map + + +
+
Samoa Standard Time
+
SST (UTC-11)
+
+
+
+
+
+
+ + +
+
Chamorro Standard Time
+
CHST (UTC+10)
+
+
+
+
+
+
+ + +
+
+
Atlantic Standard Time
+
Puerto Rico / US Virgin Islands
+
AST (UTC-4)
+
+
+
+
+
+
+
+ +
+
+ + +
+
+
 
+ + +
+ + +
+
+
+
Pacific Standard Time
+
PST (UTC-8)
+
+
+
+
+
+
+
+ + +
+
+
+
Mountain Standard Time
+
MST (UTC-7)
+
+
+
+
+
+
+
+ + +
+
+
+
Central Standard Time
+
CST (UTC-6)
+
+
+
+
+
+
+
+ + +
+
+
+
Eastern Standard Time
+
EST (UTC-5)
+
+
+
+
+
+
+
+ +
+
+ + + +
+ +
+
+
+
Arizona Mountain
Standard Time
+
MST (UTC-7)
+
+
+
+
+
+
+
+
+ + + United States Time Zone Map +
+ +
+ + +
+
+ + +
+
+ + +
+
+ +
+

Coordinated Universal Time (UTC)

+
+
+ +
+

UTC is always displayed as a 24-hour clock.

+ + +
+

Your Device's Clock UTC-0
+

+ +
+

+ Your clock is off by:
s

+
+ + + +
+
+
Atlantic Standard Time
+
Puerto Rico / US Virgin Islands
+
AST (UTC-4)
+
+
+
+
+
+
+
+ +
+ Simple View + Decimal Time + Seth Date + Seth Calendar + Time API +
+ Hashed image for Daylight Saving Time not observed = DAYLIGHT SAVING TIME NOT OBSERVED +

Clocks are corrected for network delay

+
+
+ +
+
+ +
+
+ + + + +
+ + + + + + + diff --git a/timegov-clone/time.gov/scripts/analogClock.js b/timegov-clone/time.gov/scripts/analogClock.js new file mode 100644 index 0000000..db99007 --- /dev/null +++ b/timegov-clone/time.gov/scripts/analogClock.js @@ -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); + } \ No newline at end of file diff --git a/timegov-clone/time.gov/scripts/application.js b/timegov-clone/time.gov/scripts/application.js new file mode 100644 index 0000000..27509ff --- /dev/null +++ b/timegov-clone/time.gov/scripts/application.js @@ -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); + + }; + diff --git a/timegov-clone/time.gov/scripts/jquery-3.7.1.min.js b/timegov-clone/time.gov/scripts/jquery-3.7.1.min.js new file mode 100644 index 0000000..7f37b5d --- /dev/null +++ b/timegov-clone/time.gov/scripts/jquery-3.7.1.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.7.1 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(ie,e){"use strict";var oe=[],r=Object.getPrototypeOf,ae=oe.slice,g=oe.flat?function(e){return oe.flat.call(e)}:function(e){return oe.concat.apply([],e)},s=oe.push,se=oe.indexOf,n={},i=n.toString,ue=n.hasOwnProperty,o=ue.toString,a=o.call(Object),le={},v=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},y=function(e){return null!=e&&e===e.window},C=ie.document,u={type:!0,src:!0,nonce:!0,noModule:!0};function m(e,t,n){var r,i,o=(n=n||C).createElement("script");if(o.text=e,t)for(r in u)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[i.call(e)]||"object":typeof e}var t="3.7.1",l=/HTML$/i,ce=function(e,t){return new ce.fn.init(e,t)};function c(e){var t=!!e&&"length"in e&&e.length,n=x(e);return!v(e)&&!y(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+ge+")"+ge+"*"),x=new RegExp(ge+"|>"),j=new RegExp(g),A=new RegExp("^"+t+"$"),D={ID:new RegExp("^#("+t+")"),CLASS:new RegExp("^\\.("+t+")"),TAG:new RegExp("^("+t+"|[*])"),ATTR:new RegExp("^"+p),PSEUDO:new RegExp("^"+g),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+ge+"*(even|odd|(([+-]|)(\\d*)n|)"+ge+"*(?:([+-]|)"+ge+"*(\\d+)|))"+ge+"*\\)|)","i"),bool:new RegExp("^(?:"+f+")$","i"),needsContext:new RegExp("^"+ge+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ge+"*((?:-\\d)?\\d*)"+ge+"*\\)|)(?=[^-]|$)","i")},N=/^(?:input|select|textarea|button)$/i,q=/^h\d$/i,L=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,H=/[+~]/,O=new RegExp("\\\\[\\da-fA-F]{1,6}"+ge+"?|\\\\([^\\r\\n\\f])","g"),P=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},M=function(){V()},R=J(function(e){return!0===e.disabled&&fe(e,"fieldset")},{dir:"parentNode",next:"legend"});try{k.apply(oe=ae.call(ye.childNodes),ye.childNodes),oe[ye.childNodes.length].nodeType}catch(e){k={apply:function(e,t){me.apply(e,ae.call(t))},call:function(e){me.apply(e,ae.call(arguments,1))}}}function I(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(V(e),e=e||T,C)){if(11!==p&&(u=L.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return k.call(n,a),n}else if(f&&(a=f.getElementById(i))&&I.contains(e,a)&&a.id===i)return k.call(n,a),n}else{if(u[2])return k.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&e.getElementsByClassName)return k.apply(n,e.getElementsByClassName(i)),n}if(!(h[t+" "]||d&&d.test(t))){if(c=t,f=e,1===p&&(x.test(t)||m.test(t))){(f=H.test(t)&&U(e.parentNode)||e)==e&&le.scope||((s=e.getAttribute("id"))?s=ce.escapeSelector(s):e.setAttribute("id",s=S)),o=(l=Y(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+Q(l[o]);c=l.join(",")}try{return k.apply(n,f.querySelectorAll(c)),n}catch(e){h(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return re(t.replace(ve,"$1"),e,n,r)}function W(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function F(e){return e[S]=!0,e}function $(e){var t=T.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function B(t){return function(e){return fe(e,"input")&&e.type===t}}function _(t){return function(e){return(fe(e,"input")||fe(e,"button"))&&e.type===t}}function z(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&R(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function X(a){return F(function(o){return o=+o,F(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function U(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}function V(e){var t,n=e?e.ownerDocument||e:ye;return n!=T&&9===n.nodeType&&n.documentElement&&(r=(T=n).documentElement,C=!ce.isXMLDoc(T),i=r.matches||r.webkitMatchesSelector||r.msMatchesSelector,r.msMatchesSelector&&ye!=T&&(t=T.defaultView)&&t.top!==t&&t.addEventListener("unload",M),le.getById=$(function(e){return r.appendChild(e).id=ce.expando,!T.getElementsByName||!T.getElementsByName(ce.expando).length}),le.disconnectedMatch=$(function(e){return i.call(e,"*")}),le.scope=$(function(){return T.querySelectorAll(":scope")}),le.cssHas=$(function(){try{return T.querySelector(":has(*,:jqfake)"),!1}catch(e){return!0}}),le.getById?(b.filter.ID=function(e){var t=e.replace(O,P);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(O,P);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):t.querySelectorAll(e)},b.find.CLASS=function(e,t){if("undefined"!=typeof t.getElementsByClassName&&C)return t.getElementsByClassName(e)},d=[],$(function(e){var t;r.appendChild(e).innerHTML="",e.querySelectorAll("[selected]").length||d.push("\\["+ge+"*(?:value|"+f+")"),e.querySelectorAll("[id~="+S+"-]").length||d.push("~="),e.querySelectorAll("a#"+S+"+*").length||d.push(".#.+[+~]"),e.querySelectorAll(":checked").length||d.push(":checked"),(t=T.createElement("input")).setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),r.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&d.push(":enabled",":disabled"),(t=T.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||d.push("\\["+ge+"*name"+ge+"*="+ge+"*(?:''|\"\")")}),le.cssHas||d.push(":has"),d=d.length&&new RegExp(d.join("|")),l=function(e,t){if(e===t)return a=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!le.sortDetached&&t.compareDocumentPosition(e)===n?e===T||e.ownerDocument==ye&&I.contains(ye,e)?-1:t===T||t.ownerDocument==ye&&I.contains(ye,t)?1:o?se.call(o,e)-se.call(o,t):0:4&n?-1:1)}),T}for(e in I.matches=function(e,t){return I(e,null,null,t)},I.matchesSelector=function(e,t){if(V(e),C&&!h[t+" "]&&(!d||!d.test(t)))try{var n=i.call(e,t);if(n||le.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){h(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(O,P),e[3]=(e[3]||e[4]||e[5]||"").replace(O,P),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||I.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&I.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return D.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&j.test(n)&&(t=Y(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(O,P).toLowerCase();return"*"===e?function(){return!0}:function(e){return fe(e,t)}},CLASS:function(e){var t=s[e+" "];return t||(t=new RegExp("(^|"+ge+")"+e+"("+ge+"|$)"))&&s(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=I.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function T(e,n,r){return v(n)?ce.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?ce.grep(e,function(e){return e===n!==r}):"string"!=typeof n?ce.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(ce.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||k,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:S.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof ce?t[0]:t,ce.merge(this,ce.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:C,!0)),w.test(r[1])&&ce.isPlainObject(t))for(r in t)v(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=C.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):v(e)?void 0!==n.ready?n.ready(e):e(ce):ce.makeArray(e,this)}).prototype=ce.fn,k=ce(C);var E=/^(?:parents|prev(?:Until|All))/,j={children:!0,contents:!0,next:!0,prev:!0};function A(e,t){while((e=e[t])&&1!==e.nodeType);return e}ce.fn.extend({has:function(e){var t=ce(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,Ce=/^$|^module$|\/(?:java|ecma)script/i;xe=C.createDocumentFragment().appendChild(C.createElement("div")),(be=C.createElement("input")).setAttribute("type","radio"),be.setAttribute("checked","checked"),be.setAttribute("name","t"),xe.appendChild(be),le.checkClone=xe.cloneNode(!0).cloneNode(!0).lastChild.checked,xe.innerHTML="",le.noCloneChecked=!!xe.cloneNode(!0).lastChild.defaultValue,xe.innerHTML="",le.option=!!xe.lastChild;var ke={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function Se(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&fe(e,t)?ce.merge([e],n):n}function Ee(e,t){for(var n=0,r=e.length;n",""]);var je=/<|&#?\w+;/;function Ae(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function Re(e,t){return fe(e,"table")&&fe(11!==t.nodeType?t:t.firstChild,"tr")&&ce(e).children("tbody")[0]||e}function Ie(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function We(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Fe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(_.hasData(e)&&(s=_.get(e).events))for(i in _.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),C.head.appendChild(r[0])},abort:function(){i&&i()}}});var Jt,Kt=[],Zt=/(=)\?(?=&|$)|\?\?/;ce.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Kt.pop()||ce.expando+"_"+jt.guid++;return this[e]=!0,e}}),ce.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Zt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Zt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=v(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Zt,"$1"+r):!1!==e.jsonp&&(e.url+=(At.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||ce.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=ie[r],ie[r]=function(){o=arguments},n.always(function(){void 0===i?ce(ie).removeProp(r):ie[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Kt.push(r)),o&&v(i)&&i(o[0]),o=i=void 0}),"script"}),le.createHTMLDocument=((Jt=C.implementation.createHTMLDocument("").body).innerHTML="
",2===Jt.childNodes.length),ce.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(le.createHTMLDocument?((r=(t=C.implementation.createHTMLDocument("")).createElement("base")).href=C.location.href,t.head.appendChild(r)):t=C),o=!n&&[],(i=w.exec(e))?[t.createElement(i[1])]:(i=Ae([e],t,o),o&&o.length&&ce(o).remove(),ce.merge([],i.childNodes)));var r,i,o},ce.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(ce.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},ce.expr.pseudos.animated=function(t){return ce.grep(ce.timers,function(e){return t===e.elem}).length},ce.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=ce.css(e,"position"),c=ce(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=ce.css(e,"top"),u=ce.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),v(t)&&(t=t.call(e,n,ce.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},ce.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){ce.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===ce.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===ce.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=ce(e).offset()).top+=ce.css(e,"borderTopWidth",!0),i.left+=ce.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-ce.css(r,"marginTop",!0),left:t.left-i.left-ce.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===ce.css(e,"position"))e=e.offsetParent;return e||J})}}),ce.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;ce.fn[t]=function(e){return M(this,function(e,t,n){var r;if(y(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),ce.each(["top","left"],function(e,n){ce.cssHooks[n]=Ye(le.pixelPosition,function(e,t){if(t)return t=Ge(e,n),_e.test(t)?ce(e).position()[n]+"px":t})}),ce.each({Height:"height",Width:"width"},function(a,s){ce.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){ce.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return M(this,function(e,t,n){var r;return y(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?ce.css(e,t,i):ce.style(e,t,n,i)},s,n?e:void 0,n)}})}),ce.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){ce.fn[t]=function(e){return this.on(t,e)}}),ce.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.on("mouseenter",e).on("mouseleave",t||e)}}),ce.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){ce.fn[n]=function(e,t){return 0 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 + +