Skip to content

Commit a142037

Browse files
authored
Timezone Meeting Planner
A better version, brand new. https://gist.github.com/simonw/3cb034fa3d327f1114da18219f2192fd
1 parent 3d76b53 commit a142037

File tree

1 file changed

+181
-180
lines changed

1 file changed

+181
-180
lines changed

timezones.html

+181-180
Original file line numberDiff line numberDiff line change
@@ -1,187 +1,188 @@
11
<!DOCTYPE html>
22
<html lang="en">
33
<head>
4-
<meta charset="UTF-8">
5-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6-
<title>Timezone Converter</title>
7-
<style>
8-
body {
9-
font-family: Arial, sans-serif;
10-
max-width: 800px;
11-
margin: 0 auto;
12-
padding: 20px;
13-
}
14-
table {
15-
width: 100%;
16-
border-collapse: collapse;
17-
margin-top: 20px;
18-
}
19-
th, td {
20-
border: 1px solid #ddd;
21-
padding: 8px;
22-
text-align: left;
23-
}
24-
th {
25-
background-color: #f2f2f2;
26-
}
27-
form {
28-
display: flex;
29-
flex-wrap: wrap;
30-
gap: 10px;
31-
align-items: center;
32-
margin-bottom: 20px;
33-
}
34-
label {
35-
margin-right: 5px;
36-
}
37-
#error {
38-
color: red;
39-
font-weight: bold;
40-
}
41-
</style>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Timezone Meeting Planner</title>
7+
<style>
8+
* {
9+
box-sizing: border-box;
10+
}
11+
12+
body {
13+
font-family: Helvetica, Arial, sans-serif;
14+
margin: 0;
15+
padding: 20px;
16+
line-height: 1.4;
17+
}
18+
19+
.timezone-selectors {
20+
display: grid;
21+
grid-template-columns: 1fr 1fr;
22+
gap: 20px;
23+
margin-bottom: 30px;
24+
}
25+
26+
.timezone-input {
27+
font-size: 16px;
28+
width: 100%;
29+
padding: 8px;
30+
border: 1px solid #ccc;
31+
border-radius: 4px;
32+
}
33+
34+
.comparison-table {
35+
width: 100%;
36+
border-collapse: collapse;
37+
}
38+
39+
.comparison-table th {
40+
text-align: left;
41+
padding: 12px;
42+
background: #f0f0f0;
43+
border: 1px solid #ddd;
44+
}
45+
46+
.comparison-table td {
47+
padding: 12px;
48+
border: 1px solid #ddd;
49+
}
50+
51+
.comparison-table tr:nth-child(even) {
52+
background: #f8f8f8;
53+
}
54+
55+
.timezone-label {
56+
color: #4a5568;
57+
font-size: 1.2em;
58+
margin-bottom: 8px;
59+
font-weight: bold;
60+
}
61+
</style>
4262
</head>
4363
<body>
44-
<h1>Timezone Converter</h1>
45-
<form id="timeForm">
46-
<label for="dateTime">Select Date and Time:</label>
47-
<input type="datetime-local" id="dateTime" required>
48-
<label for="timezone">in timezone:</label>
49-
<select id="timezone"></select>
50-
</form>
51-
<div id="error"></div>
52-
<div id="result"></div>
53-
54-
<script>
55-
const dateTimeInput = document.getElementById('dateTime');
56-
const timezoneSelect = document.getElementById('timezone');
57-
const resultDiv = document.getElementById('result');
58-
const errorDiv = document.getElementById('error');
59-
60-
const timezones = [
61-
{ name: 'UTC', zone: 'UTC' },
62-
{ name: 'New York', zone: 'America/New_York' },
63-
{ name: 'Los Angeles', zone: 'America/Los_Angeles' },
64-
{ name: 'Chicago', zone: 'America/Chicago' },
65-
{ name: 'London', zone: 'Europe/London' },
66-
{ name: 'Paris', zone: 'Europe/Paris' },
67-
{ name: 'Tokyo', zone: 'Asia/Tokyo' },
68-
{ name: 'Sydney', zone: 'Australia/Sydney' },
69-
{ name: 'Dubai', zone: 'Asia/Dubai' },
70-
{ name: 'Moscow', zone: 'Europe/Moscow' }
71-
];
72-
73-
try {
74-
const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
75-
console.log('User timezone:', userTimezone);
76-
77-
// Populate timezone select
78-
timezones.forEach(tz => {
79-
const option = document.createElement('option');
80-
option.value = tz.zone;
81-
option.textContent = `${tz.name} (${tz.zone})`;
82-
timezoneSelect.appendChild(option);
83-
});
84-
85-
// Set default timezone to user's timezone
86-
timezoneSelect.value = userTimezone;
87-
88-
function updateUrlHash(dateTime, timezone) {
89-
const timestamp = dateTime.getTime();
90-
history.replaceState(null, null, `#${timestamp},${timezone}`);
91-
}
92-
93-
function getDateTimeFromHash() {
94-
const hash = window.location.hash.slice(1);
95-
if (hash) {
96-
const [timestamp, timezone] = hash.split(',');
97-
return {
98-
dateTime: new Date(parseInt(timestamp)),
99-
timezone: timezone || userTimezone
100-
};
101-
}
102-
return null;
103-
}
104-
105-
function formatDate(date, timezone) {
106-
return date.toLocaleString('en-US', { timeZone: timezone, dateStyle: 'full', timeStyle: 'long' });
107-
}
108-
109-
function displayResults(dateTime, inputTimezone) {
110-
let html = '<table><tr><th>Timezone</th><th>Date & Time</th></tr>';
111-
timezones.forEach(tz => {
112-
html += `<tr><td>${tz.name} (${tz.zone})</td><td>${formatDate(dateTime, tz.zone)}</td></tr>`;
113-
});
114-
html += '</table>';
115-
resultDiv.innerHTML = html;
116-
}
117-
118-
function updateResults() {
119-
try {
120-
console.log('Updating results...');
121-
const inputDate = new Date(dateTimeInput.value);
122-
console.log('Input date:', inputDate);
123-
const inputTimezone = timezoneSelect.value;
124-
console.log('Input timezone:', inputTimezone);
125-
const utcDate = new Date(inputDate.toLocaleString('en-US', { timeZone: 'UTC' }));
126-
console.log('UTC date:', utcDate);
127-
updateUrlHash(utcDate, inputTimezone);
128-
displayResults(utcDate, inputTimezone);
129-
errorDiv.textContent = '';
130-
} catch (error) {
131-
console.error('Error in updateResults:', error);
132-
errorDiv.textContent = 'Error updating results: ' + error.message;
133-
}
134-
}
135-
136-
function setDateTimeInputValue(date, timezone) {
137-
const localDate = new Date(date.toLocaleString('en-US', { timeZone: timezone }));
138-
dateTimeInput.value = localDate.toISOString().slice(0, 16);
139-
}
140-
141-
// Event listeners for real-time updates
142-
dateTimeInput.addEventListener('input', updateResults);
143-
timezoneSelect.addEventListener('change', updateResults);
144-
145-
window.addEventListener('load', () => {
146-
try {
147-
console.log('Window loaded');
148-
const hashData = getDateTimeFromHash();
149-
if (hashData) {
150-
console.log('Hash data:', hashData);
151-
timezoneSelect.value = hashData.timezone;
152-
setDateTimeInputValue(hashData.dateTime, hashData.timezone);
153-
displayResults(hashData.dateTime, hashData.timezone);
154-
} else {
155-
console.log('No hash data, using current time');
156-
setDateTimeInputValue(new Date(), userTimezone);
157-
updateResults();
158-
}
159-
} catch (error) {
160-
console.error('Error in load event:', error);
161-
errorDiv.textContent = 'Error loading initial data: ' + error.message;
162-
}
163-
});
164-
165-
window.addEventListener('hashchange', () => {
166-
try {
167-
console.log('Hash changed');
168-
const hashData = getDateTimeFromHash();
169-
if (hashData) {
170-
console.log('New hash data:', hashData);
171-
timezoneSelect.value = hashData.timezone;
172-
setDateTimeInputValue(hashData.dateTime, hashData.timezone);
173-
displayResults(hashData.dateTime, hashData.timezone);
174-
}
175-
} catch (error) {
176-
console.error('Error in hashchange event:', error);
177-
errorDiv.textContent = 'Error processing URL: ' + error.message;
178-
}
179-
});
180-
181-
} catch (error) {
182-
console.error('Initialization error:', error);
183-
errorDiv.textContent = 'Initialization error: ' + error.message;
184-
}
185-
</script>
64+
<div class="timezone-selectors">
65+
<div>
66+
<div class="timezone-label">First Timezone</div>
67+
<input
68+
type="text"
69+
id="timezone1"
70+
class="timezone-input"
71+
list="timezone-list"
72+
placeholder="Select timezone...">
73+
</div>
74+
<div>
75+
<div class="timezone-label">Second Timezone</div>
76+
<input
77+
type="text"
78+
id="timezone2"
79+
class="timezone-input"
80+
list="timezone-list"
81+
placeholder="Select timezone...">
82+
</div>
83+
</div>
84+
85+
<datalist id="timezone-list"></datalist>
86+
87+
<table class="comparison-table" id="comparison-table">
88+
<thead>
89+
<tr>
90+
<th>UTC-time</th>
91+
<th id="tz1-header">First Location</th>
92+
<th id="tz2-header">Second Location</th>
93+
</tr>
94+
</thead>
95+
<tbody id="comparison-body">
96+
</tbody>
97+
</table>
98+
99+
<script type="module">
100+
const timezones = Intl.supportedValuesOf('timeZone')
101+
const tzDatalist = document.getElementById('timezone-list')
102+
const tz1Input = document.getElementById('timezone1')
103+
const tz2Input = document.getElementById('timezone2')
104+
const tz1Header = document.getElementById('tz1-header')
105+
const tz2Header = document.getElementById('tz2-header')
106+
const comparisonBody = document.getElementById('comparison-body')
107+
108+
// Populate datalist with all supported timezones
109+
timezones.forEach(tz => {
110+
const option = document.createElement('option')
111+
option.value = tz
112+
tzDatalist.appendChild(option)
113+
})
114+
115+
function updateURL(tz1, tz2) {
116+
const hash = `#${encodeURIComponent(tz1)},${encodeURIComponent(tz2)}`
117+
window.history.replaceState(null, '', hash)
118+
}
119+
120+
function loadFromURL() {
121+
const hash = window.location.hash.slice(1)
122+
if (hash) {
123+
const [tz1, tz2] = hash.split(',').map(decodeURIComponent)
124+
if (timezones.includes(tz1) && timezones.includes(tz2)) {
125+
tz1Input.value = tz1
126+
tz2Input.value = tz2
127+
updateComparison()
128+
}
129+
}
130+
}
131+
132+
function formatTime(date, timezone) {
133+
return new Intl.DateTimeFormat('en-US', {
134+
hour: 'numeric',
135+
minute: 'numeric',
136+
hour12: true,
137+
timeZone: timezone,
138+
weekday: 'short'
139+
}).format(date)
140+
}
141+
142+
function updateComparison() {
143+
const tz1 = tz1Input.value
144+
const tz2 = tz2Input.value
145+
146+
if (!timezones.includes(tz1) || !timezones.includes(tz2)) return
147+
148+
tz1Header.textContent = tz1.split('/').pop().replace(/_/g, ' ')
149+
tz2Header.textContent = tz2.split('/').pop().replace(/_/g, ' ')
150+
151+
updateURL(tz1, tz2)
152+
153+
// Clear existing rows
154+
comparisonBody.innerHTML = ''
155+
156+
// Create 24 hourly comparisons
157+
const now = new Date()
158+
now.setMinutes(0, 0, 0)
159+
160+
for (let i = 0; i < 48; i++) {
161+
const row = document.createElement('tr')
162+
163+
const utcCell = document.createElement('td')
164+
utcCell.textContent = now.toISOString().replace('T', ' at ').slice(0, -5)
165+
166+
const tz1Cell = document.createElement('td')
167+
tz1Cell.textContent = formatTime(now, tz1)
168+
169+
const tz2Cell = document.createElement('td')
170+
tz2Cell.textContent = formatTime(now, tz2)
171+
172+
row.appendChild(utcCell)
173+
row.appendChild(tz1Cell)
174+
row.appendChild(tz2Cell)
175+
comparisonBody.appendChild(row)
176+
177+
now.setHours(now.getHours() + 1)
178+
}
179+
}
180+
181+
tz1Input.addEventListener('change', updateComparison)
182+
tz2Input.addEventListener('change', updateComparison)
183+
184+
// Load initial state from URL
185+
loadFromURL()
186+
</script>
186187
</body>
187188
</html>

0 commit comments

Comments
 (0)