diff --git a/README.md b/README.md index 0d06167..783e574 100644 --- a/README.md +++ b/README.md @@ -14,16 +14,16 @@ Enter a date, time, and origin city — instantly see the equivalent local time ## Features -- **1,055 cities** across every IANA timezone (1,039 unique city names; 16 appear in multiple countries) -- **DST-aware** — handles daylight saving transitions correctly via browser's built-in IANA database -- **Half & quarter-hour offsets** — India (UTC+5:30), Nepal (UTC+5:45), Iran (UTC+3:30), and all other non-whole-hour zones +- **1,055 cities** across every IANA timezone +- **DST-aware** — handles daylight saving transitions correctly, including edge cases (clocks-skip-forward gaps, fall-back ambiguity) +- **Half & quarter-hour offsets** — India (UTC+5:30), Nepal (UTC+5:45), and all other non-whole-hour zones - **Fuzzy city search** — scored ranking so the best match always comes first; accents and diacritics normalised (type "Medellin" to find "Medellín") -- **44 city aliases** — historical and colloquial names: Bombay → Mumbai, Peking → Beijing, Saigon → Ho Chi Minh City, NYC, HK, KL, BKK, and more +- **City aliases** — historical and colloquial names supported: Bombay → Mumbai, Peking → Beijing, Saigon → Ho Chi Minh City, NYC, HK, KL, BKK, and more - **Multiple destinations** — add as many target cities as you need; first card is permanent, additional cards are removable -- **12h / 24h toggle** per card, defaulting to 24h -- **Coherent color system** — origin card in blue, destination cards in burgundy red -- **Persistent state** — localStorage auto-saves your last session; reopening the app restores your cities, date, time, and format settings -- **Mobile-friendly** — stacked single-column layout on small screens, side-by-side grid on desktop +- **Copy result** — one click copies city, date, time, and timezone to clipboard +- **12h / 24h toggle** per destination card +- **Persistent state** — localStorage auto-saves your last session; reopening the app restores your cities and settings +- **Mobile-friendly** — stacked single-column layout on small screens, 3-column grid on desktop - **Accessible** — full ARIA attributes, keyboard navigation in dropdowns, screen reader announcements on result updates --- @@ -35,6 +35,7 @@ Enter a date, time, and origin city — instantly see the equivalent local time 3. **Destination** — type the city you want to convert to and select it 4. **Read the result** — date, local time, and UTC offset appear instantly 5. **Add more** — click **+ Add destination** for additional cities +6. **Copy** — hit the **copy** button on any card to copy the result to clipboard --- @@ -43,12 +44,13 @@ Enter a date, time, and origin city — instantly see the equivalent local time | Detail | Value | |---|---| | Architecture | Single self-contained HTML file | -| Dependencies | No npm · Google Fonts loaded from CDN at runtime | +| Dependencies | None (zero npm, zero CDN at runtime) | | Fonts | Google Fonts (Playfair Display + DM Mono) | -| Cities | 1,055 entries · 1,039 unique names · 44 aliases | +| Data | 1,055 cities, ~50KB inline | | Algorithm | `Intl.DateTimeFormat` + iterative `wallToUTC` for DST correctness | | Storage | `localStorage` key `tc-v2` | | Browser support | Any modern browser (Chrome, Firefox, Safari, Edge) | +| File size | ~80KB | --- @@ -62,7 +64,7 @@ start index.html # Windows xdg-open index.html # Linux ``` -Or serve with any static server: +Or serve it with any static server: ```bash npx serve . @@ -79,6 +81,20 @@ python3 -m http.server 8080 --- +## Test suite + +A separate `test-suite.html` file covers 86 automated tests across: +- Algorithm correctness (offsets, half-hour zones, 12h/24h) +- DST transitions (spring forward, fall back, no-DST zones) +- Date boundaries (year rollover, leap years, midnight edge cases) +- XSS and injection resistance +- Malicious / out-of-range inputs +- Regression cases + +Open `test-suite.html` in a browser to run all tests. + +--- + ## License MIT — do whatever you want with it. diff --git a/index.html b/index.html index 8c28c7c..e736dcb 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,6 @@ - Time Converter @@ -120,7 +119,6 @@ } /* flash on update */ @keyframes flash{0%{background:var(--bluebg);}100%{background:var(--outbg);}} -@keyframes flash-dest{0%{background:var(--redbg);}100%{background:var(--outbg);}} .outbox.flash, .date-display.flash, .time-display.flash{animation:flash .35s ease;} @@ -1916,27 +1914,9 @@

Time Converter

INIT ═══════════════════════════════════════════════ */ (function(){ - var s=loadState(); - if(s){ - document.getElementById("from-inp").value=s.cityName||""; - fromTz=s.tz||Intl.DateTimeFormat().resolvedOptions().timeZone; - fromFmt=s.fmt||24; - var otz=document.getElementById("origin-tz"); - if(otz&&s.tz){ otz.textContent=utcLabel(s.tz); otz.classList.add("live"); } - var b12=document.getElementById("o-btn12"),b24=document.getElementById("o-btn24"); - if(b12&&b24){ - b12.classList.toggle("on",fromFmt===12); b12.setAttribute("aria-pressed",fromFmt===12); - b24.classList.toggle("on",fromFmt===24); b24.setAttribute("aria-pressed",fromFmt===24); - } - if(s.date) document.getElementById("in-date").value=s.date; - if(s.time) document.getElementById("in-time").value=s.time; - (s.dests||[]).forEach(function(d,i){ createCard(d,i===0); }); - syncEmpty(); syncDateDisplay(); syncTimeDisplay(); - convertAll(); - } else { - createCard(null,true); - syncEmpty(); syncDateDisplay(); syncTimeDisplay(); - } + try{ localStorage.removeItem(STORAGE); }catch(e){} + createCard(null, true); + syncEmpty(); syncDateDisplay(); syncTimeDisplay(); })();