Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 11 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
- **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
- **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
- **Fuzzy city search** — scored ranking so the best match always comes first; accents and diacritics normalised (type "Medellin" to find "Medellín")
- **City aliases** — historical and colloquial names supported: Bombay → Mumbai, Peking → Beijing, Saigon → Ho Chi Minh City, NYC, HK, KL, BKK, and more
- **44 city aliases** — historical and colloquial names: 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
- **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
- **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
- **Accessible** — full ARIA attributes, keyboard navigation in dropdowns, screen reader announcements on result updates

---
Expand All @@ -35,7 +35,6 @@ 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

---

Expand All @@ -44,13 +43,12 @@ Enter a date, time, and origin city — instantly see the equivalent local time
| Detail | Value |
|---|---|
| Architecture | Single self-contained HTML file |
| Dependencies | None (zero npm, zero CDN at runtime) |
| Dependencies | No npm · Google Fonts loaded from CDN at runtime |
| Fonts | Google Fonts (Playfair Display + DM Mono) |
| Data | 1,055 cities, ~50KB inline |
| Cities | 1,055 entries · 1,039 unique names · 44 aliases |
| 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 |

---

Expand All @@ -64,7 +62,7 @@ start index.html # Windows
xdg-open index.html # Linux
```

Or serve it with any static server:
Or serve with any static server:

```bash
npx serve .
Expand All @@ -81,20 +79,6 @@ 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.
26 changes: 23 additions & 3 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="google" content="notranslate">
<meta http-equiv="Content-Language" content="en">
<title>Time Converter</title>
Expand Down Expand Up @@ -119,6 +120,7 @@
}
/* 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;}
Expand Down Expand Up @@ -1914,9 +1916,27 @@ <h1>Time Converter</h1>
INIT
═══════════════════════════════════════════════ */
(function(){
try{ localStorage.removeItem(STORAGE); }catch(e){}
createCard(null, true);
syncEmpty(); syncDateDisplay(); syncTimeDisplay();
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();
}
})();
</script>
</body>
Expand Down