Add global timezone/12h-24h setting and improve satellite selection

Global time preferences (Settings > Display > Time & Timezone):
- InterceptTime utility in core/utils.js with timezone + 12h/24h support
- Timezone options: UTC, Local, Eastern, Central, Mountain, Pacific
- Time format: 12-hour (AM/PM) or 24-hour toggle
- Defaults to US/Eastern + 12-hour
- Header nav clock updates to use selected timezone and format
- Weather satellite mode delegates to global InterceptTime
- Settings persist via localStorage, change listeners notify all modes

Weather satellite improvements:
- Satellite dropdown defaults to "All Meteor Satellites" showing all passes
- Can still filter to specific satellite (M2-3, M2-4, M2-4-80K)
- Capture button on pass cards auto-selects the correct satellite

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
mitchross
2026-03-25 01:24:42 -04:00
parent 1e5bc0054d
commit ebc838fa9d
6 changed files with 191 additions and 73 deletions
+19 -3
View File
@@ -77,8 +77,23 @@ function declineDisclaimer() {
function updateHeaderClock() {
const now = new Date();
const utc = now.toISOString().substring(11, 19);
document.getElementById('headerUtcTime').textContent = utc;
const el = document.getElementById('headerUtcTime');
const label = document.querySelector('.utc-label');
if (typeof InterceptTime !== 'undefined') {
if (el) el.textContent = InterceptTime.fullTime(now);
if (label) label.textContent = InterceptTime.getLabel() || 'LOCAL';
} else {
if (el) el.textContent = now.toISOString().substring(11, 19);
}
}
function initTimeSettings() {
const tzSelect = document.getElementById('globalTimezoneSelect');
const fmtSelect = document.getElementById('globalTimeFormatSelect');
if (typeof InterceptTime !== 'undefined') {
if (tzSelect) tzSelect.value = InterceptTime.getTimezone();
if (fmtSelect) fmtSelect.value = InterceptTime.getHour12() ? '12' : '24';
}
}
// ============== MODE SWITCHING ==============
@@ -447,7 +462,8 @@ function initApp() {
// Load theme
loadTheme();
// Start clock
// Start clock and init time settings
initTimeSettings();
updateHeaderClock();
setInterval(updateHeaderClock, 1000);
+108
View File
@@ -55,6 +55,114 @@ function isValidChannel(ch) {
// ============== TIME FORMATTING ==============
/**
* Global time preferences — timezone and 12h/24h format.
* Stored in localStorage, used by all modes.
*/
const InterceptTime = (function() {
const TZ_MAP = {
'UTC': 'UTC',
'local': undefined,
'US/Eastern': 'America/New_York',
'US/Central': 'America/Chicago',
'US/Mountain': 'America/Denver',
'US/Pacific': 'America/Los_Angeles',
};
const TZ_LABELS = {
'UTC': 'UTC',
'local': '',
'US/Eastern': 'ET',
'US/Central': 'CT',
'US/Mountain': 'MT',
'US/Pacific': 'PT',
};
let _timezone = localStorage.getItem('interceptTimezone') || 'US/Eastern';
let _hour12 = (localStorage.getItem('interceptHour12') || 'true') === 'true';
const _listeners = [];
function getTimezone() { return _timezone; }
function getHour12() { return _hour12; }
function getIANA() { return TZ_MAP[_timezone]; }
function getLabel() { return TZ_LABELS[_timezone] || ''; }
function setTimezone(tz) {
if (!TZ_MAP.hasOwnProperty(tz)) return;
_timezone = tz;
localStorage.setItem('interceptTimezone', tz);
// Migrate weather-sat specific key
localStorage.setItem('wxsatTimezone', tz);
_notify();
}
function setHour12(val) {
_hour12 = !!val;
localStorage.setItem('interceptHour12', _hour12 ? 'true' : 'false');
_notify();
}
function onChange(fn) { _listeners.push(fn); }
function _notify() { _listeners.forEach(fn => { try { fn(); } catch(e) { console.error(e); } }); }
/**
* Format a Date or ISO string for the global timezone.
* @param {Date|string} input - Date object or ISO string
* @param {object} [extraOpts] - Additional Intl.DateTimeFormat options
* @returns {string}
*/
function format(input, extraOpts) {
if (!input) return '--';
try {
const date = typeof input === 'string' ? new Date(input) : input;
if (isNaN(date.getTime())) return typeof input === 'string' ? input : '--';
const opts = { hour12: _hour12, ...extraOpts };
const iana = getIANA();
if (iana) opts.timeZone = iana;
return date.toLocaleString(undefined, opts);
} catch { return typeof input === 'string' ? input : '--'; }
}
/** HH:MM (or h:MM AM/PM) */
function shortTime(input) {
return format(input, { hour: '2-digit', minute: '2-digit' });
}
/** HH:MM:SS */
function fullTime(input) {
return format(input, { hour: '2-digit', minute: '2-digit', second: '2-digit' });
}
/** Mon 25, 14:30 (or 2:30 PM) */
function dateTime(input) {
return format(input, { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
}
/** Mon 25, 2026 */
function dateOnly(input) {
const iana = getIANA();
const opts = { year: 'numeric', month: 'short', day: 'numeric' };
if (iana) opts.timeZone = iana;
try {
const date = typeof input === 'string' ? new Date(input) : input;
return date.toLocaleDateString(undefined, opts);
} catch { return '--'; }
}
/** Short label like " ET" or " UTC" for appending to times */
function tzSuffix() {
const l = getLabel();
return l ? ' ' + l : '';
}
return {
getTimezone, getHour12, getIANA, getLabel,
setTimezone, setHour12, onChange,
format, shortTime, fullTime, dateTime, dateOnly, tzSuffix,
TZ_MAP, TZ_LABELS,
};
})();
/**
* Get relative time string from timestamp
* @param {string} timestamp - Time string in HH:MM:SS format
+25 -67
View File
@@ -38,74 +38,23 @@ const WeatherSat = (function() {
let lastDecodeSatellite = null;
let consoleFilter = 'all';
// Timezone support
const TZ_MAP = {
'UTC': 'UTC',
'local': null, // browser default
'US/Eastern': 'America/New_York',
'US/Central': 'America/Chicago',
'US/Mountain': 'America/Denver',
'US/Pacific': 'America/Los_Angeles',
};
let selectedTimezone = localStorage.getItem('wxsatTimezone') || 'UTC';
/**
* Format an ISO date string for the selected timezone.
* @param {string} isoString - ISO 8601 date string
* @param {object} [opts] - Additional Intl.DateTimeFormat options
* @returns {string} Formatted date/time string
*/
function formatTimeForTZ(isoString, opts = {}) {
if (!isoString) return '--';
try {
const date = new Date(isoString);
if (isNaN(date.getTime())) return isoString;
const tz = TZ_MAP[selectedTimezone];
const defaults = { hour: '2-digit', minute: '2-digit', hour12: false };
const options = { ...defaults, ...opts };
if (tz) options.timeZone = tz;
return date.toLocaleString(undefined, options);
} catch {
return isoString;
}
}
/**
* Format a short time (HH:MM) for the selected timezone.
*/
// Timezone — delegates to global InterceptTime utility
function formatShortTime(isoString) {
return formatTimeForTZ(isoString, { hour: '2-digit', minute: '2-digit', hour12: false });
return typeof InterceptTime !== 'undefined' ? InterceptTime.shortTime(isoString) : (isoString || '--');
}
/**
* Format date + time for the selected timezone.
*/
function formatDateTime(isoString) {
return formatTimeForTZ(isoString, {
month: 'short', day: 'numeric',
hour: '2-digit', minute: '2-digit', hour12: false
});
return typeof InterceptTime !== 'undefined' ? InterceptTime.dateTime(isoString) : (isoString || '--');
}
/**
* Get a short timezone label for display.
*/
function getTZLabel() {
if (selectedTimezone === 'local') return '';
if (selectedTimezone === 'UTC') return ' UTC';
const labels = { 'US/Eastern': ' ET', 'US/Central': ' CT', 'US/Mountain': ' MT', 'US/Pacific': ' PT' };
return labels[selectedTimezone] || '';
return typeof InterceptTime !== 'undefined' ? InterceptTime.tzSuffix() : '';
}
/**
* Set timezone and refresh all displays.
*/
function setTimezone(tz) {
selectedTimezone = tz;
localStorage.setItem('wxsatTimezone', tz);
if (typeof InterceptTime !== 'undefined') InterceptTime.setTimezone(tz);
const sel = document.getElementById('wxsatTimezone');
if (sel && sel.value !== tz) sel.value = tz;
// Refresh all time-dependent displays
applyPassFilter();
renderGallery();
updateTimelineLabels();
@@ -137,9 +86,9 @@ const WeatherSat = (function() {
* Initialize the Weather Satellite mode
*/
function init() {
// Restore timezone selector
// Sync timezone selector with global setting
const tzSel = document.getElementById('wxsatTimezone');
if (tzSel) tzSel.value = selectedTimezone;
if (tzSel && typeof InterceptTime !== 'undefined') tzSel.value = InterceptTime.getTimezone();
if (initialized) {
checkStatus();
@@ -154,6 +103,17 @@ const WeatherSat = (function() {
}
initialized = true;
// Listen for global timezone/format changes
if (typeof InterceptTime !== 'undefined') {
InterceptTime.onChange(() => {
const sel = document.getElementById('wxsatTimezone');
if (sel) sel.value = InterceptTime.getTimezone();
applyPassFilter();
renderGallery();
updateTimelineLabels();
});
}
checkStatus();
loadImages();
loadLocationInputs();
@@ -1870,15 +1830,14 @@ const WeatherSat = (function() {
return new Date(b.timestamp || 0) - new Date(a.timestamp || 0);
});
// Group by date (timezone-aware)
// Group by date (timezone-aware via global InterceptTime)
const groups = {};
sorted.forEach(img => {
let dateKey = 'Unknown Date';
if (img.timestamp) {
const tz = TZ_MAP[selectedTimezone];
const opts = { year: 'numeric', month: 'short', day: 'numeric' };
if (tz) opts.timeZone = tz;
dateKey = new Date(img.timestamp).toLocaleDateString(undefined, opts);
dateKey = typeof InterceptTime !== 'undefined'
? InterceptTime.dateOnly(img.timestamp)
: new Date(img.timestamp).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });
}
if (!groups[dateKey]) groups[dateKey] = [];
groups[dateKey].push(img);
@@ -2214,10 +2173,9 @@ const WeatherSat = (function() {
const type = logType || 'info';
const now = new Date();
const tz = TZ_MAP[selectedTimezone];
const tsOpts = { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false };
if (tz) tsOpts.timeZone = tz;
const ts = now.toLocaleTimeString(undefined, tsOpts);
const ts = typeof InterceptTime !== 'undefined'
? InterceptTime.fullTime(now)
: now.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit' });
const entry = document.createElement('div');
entry.className = `wxsat-console-entry wxsat-log-${type}`;
@@ -84,7 +84,8 @@
<div class="form-group">
<label>Select Satellite</label>
<select id="weatherSatSelect" class="mode-select">
<option value="METEOR-M2-3" selected>Meteor-M2-3 (137.900 MHz LRPT)</option>
<option value="" selected>All Meteor Satellites</option>
<option value="METEOR-M2-3">Meteor-M2-3 (137.900 MHz LRPT)</option>
<option value="METEOR-M2-4">Meteor-M2-4 (137.900 MHz LRPT)</option>
<option value="METEOR-M2-4-80K">Meteor-M2-4 80k baud (fallback)</option>
</select>
+7 -2
View File
@@ -540,9 +540,14 @@
window._navClockStarted = true;
function updateNavUtcClock() {
const now = new Date();
const utc = now.toISOString().slice(11, 19);
const el = document.getElementById('headerUtcTime');
if (el) el.textContent = utc;
const label = document.querySelector('.utc-label');
if (typeof InterceptTime !== 'undefined') {
if (el) el.textContent = InterceptTime.fullTime(now);
if (label) label.textContent = InterceptTime.getLabel() || 'LOCAL';
} else {
if (el) el.textContent = now.toISOString().slice(11, 19);
}
}
setInterval(updateNavUtcClock, 1000);
updateNavUtcClock();
+30
View File
@@ -216,6 +216,36 @@
</label>
</div>
</div>
<div class="settings-group">
<div class="settings-group-title">Time & Timezone</div>
<div class="settings-row">
<div class="settings-label">
<span class="settings-label-text">Timezone</span>
<span class="settings-label-desc">Applied across all modes</span>
</div>
<select id="globalTimezoneSelect" class="settings-select" onchange="InterceptTime.setTimezone(this.value)">
<option value="UTC">UTC</option>
<option value="local">Local (browser)</option>
<option value="US/Eastern">Eastern (ET)</option>
<option value="US/Central">Central (CT)</option>
<option value="US/Mountain">Mountain (MT)</option>
<option value="US/Pacific">Pacific (PT)</option>
</select>
</div>
<div class="settings-row">
<div class="settings-label">
<span class="settings-label-text">Time Format</span>
<span class="settings-label-desc">12-hour (2:30 PM) or 24-hour (14:30)</span>
</div>
<select id="globalTimeFormatSelect" class="settings-select" onchange="InterceptTime.setHour12(this.value === '12')">
<option value="12">12-hour (AM/PM)</option>
<option value="24">24-hour</option>
</select>
</div>
</div>
</div>
<!-- Updates Section -->