810 lines
30 KiB
HTML
810 lines
30 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Zeitzonen-Rechner | PonyWave Tools</title>
|
|
<meta name="description" content="Berechne Zeiten in verschiedenen Zeitzonen, mit automatischer Erkennung deiner Zeitzone.">
|
|
<meta property="og:title" content="Zeitzonen-Rechner | PonyWave Tools">
|
|
<meta property="og:description" content="Berechne Zeiten in verschiedenen Zeitzonen, mit automatischer Erkennung deiner Zeitzone.">
|
|
<meta property="og:url" content="https://tools.ponywave.de/zeitzonen">
|
|
<meta property="og:type" content="website">
|
|
<link rel="icon" href="../favicon.png" type="image/png">
|
|
<script defer src="https://stats.ponywave.de/script" data-website-id="9ef713d2-adb9-4906-9df5-708d8a8b9131" data-tag="zeitzonen"></script>
|
|
<style>
|
|
:root {
|
|
--bg-color: #f5f5f5;
|
|
--text-color: #333;
|
|
--primary-color: #7a42f4;
|
|
--secondary-color: #f45e42;
|
|
--card-bg: #ffffff;
|
|
--border-color: #e0e0e0;
|
|
--input-bg: #ffffff;
|
|
--shadow-color: rgba(0, 0, 0, 0.1);
|
|
--accent-color: #42b8f4;
|
|
}
|
|
|
|
[data-theme="dark"] {
|
|
--bg-color: #121212;
|
|
--text-color: #e0e0e0;
|
|
--primary-color: #9966ff;
|
|
--secondary-color: #ff7766;
|
|
--card-bg: #1e1e1e;
|
|
--border-color: #3a3a3a;
|
|
--input-bg: #2d2d2d;
|
|
--shadow-color: rgba(0, 0, 0, 0.3);
|
|
--accent-color: #66ccff;
|
|
}
|
|
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
transition: background-color 0.3s, color 0.3s;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
background-color: var(--bg-color);
|
|
color: var(--text-color);
|
|
line-height: 1.6;
|
|
padding: 20px;
|
|
min-height: 100vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
header {
|
|
margin-bottom: 30px;
|
|
text-align: center;
|
|
}
|
|
|
|
h1 {
|
|
font-size: 2.5rem;
|
|
margin-bottom: 10px;
|
|
color: var(--primary-color);
|
|
}
|
|
|
|
h2 {
|
|
font-size: 1.5rem;
|
|
color: var(--secondary-color);
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
main {
|
|
flex: 1;
|
|
max-width: 900px;
|
|
margin: 0 auto;
|
|
width: 100%;
|
|
}
|
|
|
|
.theme-toggle {
|
|
position: fixed;
|
|
top: 20px;
|
|
right: 20px;
|
|
background: var(--card-bg);
|
|
border: 2px solid var(--border-color);
|
|
border-radius: 25px;
|
|
padding: 10px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
box-shadow: 0 4px 6px var(--shadow-color);
|
|
z-index: 100;
|
|
}
|
|
|
|
.theme-toggle span {
|
|
font-size: 1.3rem;
|
|
}
|
|
|
|
.current-time {
|
|
background-color: var(--card-bg);
|
|
border-radius: 10px;
|
|
padding: 20px;
|
|
margin-bottom: 30px;
|
|
box-shadow: 0 6px 12px var(--shadow-color);
|
|
border: 1px solid var(--border-color);
|
|
text-align: center;
|
|
}
|
|
|
|
.current-time h2 {
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.current-time p {
|
|
font-size: 2rem;
|
|
font-weight: bold;
|
|
color: var(--primary-color);
|
|
}
|
|
|
|
.timezone-name {
|
|
font-size: 1.2rem;
|
|
color: var(--secondary-color);
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.timezone-input {
|
|
background-color: var(--card-bg);
|
|
border-radius: 10px;
|
|
padding: 20px;
|
|
margin-bottom: 30px;
|
|
box-shadow: 0 6px 12px var(--shadow-color);
|
|
border: 1px solid var(--border-color);
|
|
}
|
|
|
|
.input-fields {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 15px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.input-group {
|
|
flex: 1;
|
|
min-width: 200px;
|
|
}
|
|
|
|
.time-input-container {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background-color: var(--input-bg);
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 5px;
|
|
padding: 5px;
|
|
}
|
|
|
|
.time-spinner {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
margin: 0 5px;
|
|
}
|
|
|
|
.time-spinner-value {
|
|
font-size: 1.8rem;
|
|
font-weight: bold;
|
|
color: var(--primary-color);
|
|
user-select: none;
|
|
width: 40px;
|
|
text-align: center;
|
|
}
|
|
|
|
.time-spinner-arrow {
|
|
cursor: pointer;
|
|
font-size: 1.2rem;
|
|
color: var(--secondary-color);
|
|
transition: transform 0.2s, color 0.2s;
|
|
}
|
|
|
|
.time-spinner-arrow:hover {
|
|
color: var(--primary-color);
|
|
transform: scale(1.2);
|
|
}
|
|
|
|
.time-divider {
|
|
font-size: 1.8rem;
|
|
font-weight: bold;
|
|
color: var(--text-color);
|
|
margin: 0 5px;
|
|
}
|
|
|
|
label {
|
|
display: block;
|
|
margin-bottom: 8px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
input, select {
|
|
width: 100%;
|
|
padding: 12px;
|
|
border-radius: 5px;
|
|
border: 1px solid var(--border-color);
|
|
background-color: var(--input-bg);
|
|
color: var(--text-color);
|
|
font-size: 1rem;
|
|
}
|
|
|
|
button {
|
|
background-color: var(--primary-color);
|
|
color: white;
|
|
border: none;
|
|
padding: 12px 20px;
|
|
border-radius: 5px;
|
|
font-size: 1rem;
|
|
cursor: pointer;
|
|
transition: background-color 0.3s;
|
|
}
|
|
|
|
button:hover {
|
|
background-color: var(--accent-color);
|
|
}
|
|
|
|
.timezone-list {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
gap: 20px;
|
|
margin-top: 30px;
|
|
}
|
|
|
|
#timezone-result {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
gap: 20px;
|
|
margin-top: 30px;
|
|
}
|
|
|
|
.arrow-container {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
background: transparent;
|
|
box-shadow: none;
|
|
border: none;
|
|
min-width: 80px;
|
|
}
|
|
|
|
.conversion-arrow {
|
|
font-size: 2.5rem;
|
|
color: var(--secondary-color);
|
|
animation: pulse 1.5s infinite;
|
|
}
|
|
|
|
.timezone-card {
|
|
background-color: var(--card-bg);
|
|
border-radius: 10px;
|
|
padding: 20px;
|
|
box-shadow: 0 6px 12px var(--shadow-color);
|
|
border: 1px solid var(--border-color);
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
}
|
|
|
|
.timezone-card h3 {
|
|
color: var(--secondary-color);
|
|
margin-bottom: 10px;
|
|
text-align: center;
|
|
}
|
|
|
|
.time {
|
|
font-size: 1.8rem;
|
|
font-weight: bold;
|
|
color: var(--primary-color);
|
|
margin: 10px 0;
|
|
}
|
|
|
|
.date {
|
|
color: var(--text-color);
|
|
font-size: 1rem;
|
|
}
|
|
|
|
.heart {
|
|
color: #ff4d4d;
|
|
animation: pulse 1.5s infinite;
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0% { transform: scale(1); }
|
|
50% { transform: scale(1.2); }
|
|
100% { transform: scale(1); }
|
|
}
|
|
|
|
footer {
|
|
margin-top: 50px;
|
|
text-align: center;
|
|
padding: 20px;
|
|
border-top: 1px solid var(--border-color);
|
|
}
|
|
|
|
footer a {
|
|
color: var(--primary-color);
|
|
text-decoration: none;
|
|
}
|
|
|
|
footer a:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
h1 {
|
|
font-size: 2rem;
|
|
}
|
|
|
|
.current-time p {
|
|
font-size: 1.6rem;
|
|
}
|
|
|
|
.input-group {
|
|
min-width: 100%;
|
|
}
|
|
|
|
#timezone-result {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.arrow-container {
|
|
transform: rotate(90deg);
|
|
margin: 10px 0;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="theme-toggle" id="theme-toggle" title="Farbmodus wechseln">
|
|
<span>🌓</span>
|
|
</div>
|
|
|
|
<header>
|
|
<h1>Zeitzonen-Rechner</h1>
|
|
<h2>Vergleiche Zeiten weltweit</h2>
|
|
</header>
|
|
|
|
<main>
|
|
<div class="current-time">
|
|
<h2>Aktuelle Zeit</h2>
|
|
<p id="local-time">--:--:--</p>
|
|
<div class="timezone-name" id="local-timezone">Deine Zeitzone wird erkannt...</div>
|
|
</div>
|
|
|
|
<div class="timezone-input">
|
|
<h2>Zeitzone berechnen</h2>
|
|
<div class="input-fields">
|
|
<div class="input-group">
|
|
<label for="custom-date">Datum</label>
|
|
<input type="date" id="custom-date">
|
|
</div>
|
|
<div class="input-group">
|
|
<label>Zeit</label>
|
|
<div class="time-input-container">
|
|
<!-- Hours -->
|
|
<div class="time-spinner">
|
|
<div class="time-spinner-arrow" id="hour-up">▲</div>
|
|
<div class="time-spinner-value" id="hour-value">12</div>
|
|
<div class="time-spinner-arrow" id="hour-down">▼</div>
|
|
</div>
|
|
|
|
<div class="time-divider">:</div>
|
|
|
|
<!-- Minutes -->
|
|
<div class="time-spinner">
|
|
<div class="time-spinner-arrow" id="minute-up">▲</div>
|
|
<div class="time-spinner-value" id="minute-value">00</div>
|
|
<div class="time-spinner-arrow" id="minute-down">▼</div>
|
|
</div>
|
|
</div>
|
|
<input type="hidden" id="custom-time">
|
|
</div>
|
|
<div class="input-group">
|
|
<label for="source-timezone">Quell-Zeitzone</label>
|
|
<select id="source-timezone"></select>
|
|
</div>
|
|
<div class="input-group">
|
|
<label for="target-timezone">Ziel-Zeitzone</label>
|
|
<select id="target-timezone"></select>
|
|
</div>
|
|
</div>
|
|
<button id="calculate-btn">Berechnen</button>
|
|
</div>
|
|
|
|
<h2>Ergebnis der Umrechnung</h2>
|
|
<div class="timezone-list" id="timezone-result">
|
|
<!-- Result cards will be generated here -->
|
|
</div>
|
|
|
|
<h2>Weltzeiten</h2>
|
|
<div class="timezone-list" id="timezone-list">
|
|
<!-- Timezone cards will be generated here -->
|
|
</div>
|
|
</main>
|
|
|
|
<footer>
|
|
<p><a href="https://tools.ponywave.de/">Zurück zur Startseite</a> | © <span id="current-year"></span> Akamaru | Made with <span class="heart">❤️</span> by Claude</p>
|
|
</footer>
|
|
|
|
<script>
|
|
// Theme handling
|
|
const themeToggle = document.getElementById('theme-toggle');
|
|
const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)');
|
|
|
|
// Check for saved theme or use system preference
|
|
let currentTheme = localStorage.getItem('theme');
|
|
if (!currentTheme) {
|
|
currentTheme = prefersDarkScheme.matches ? 'dark' : 'light';
|
|
}
|
|
|
|
// Apply the current theme
|
|
document.body.setAttribute('data-theme', currentTheme);
|
|
|
|
// Toggle theme when button is clicked
|
|
themeToggle.addEventListener('click', () => {
|
|
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
|
|
document.body.setAttribute('data-theme', newTheme);
|
|
localStorage.setItem('theme', newTheme);
|
|
currentTheme = newTheme;
|
|
});
|
|
|
|
// Set current year in footer
|
|
document.getElementById('current-year').textContent = new Date().getFullYear();
|
|
|
|
// Timezone data
|
|
const majorTimezones = [
|
|
{ label: "Pacific (Los Angeles)", id: "America/Los_Angeles" },
|
|
{ label: "Mountain (Denver)", id: "America/Denver" },
|
|
{ label: "Central (Chicago)", id: "America/Chicago" },
|
|
{ label: "Eastern (New York)", id: "America/New_York" },
|
|
{ label: "Brasilien (São Paulo)", id: "America/Sao_Paulo" },
|
|
{ label: "Großbritannien (London)", id: "Europe/London" },
|
|
{ label: "Westeuropa (Paris, Berlin)", id: "Europe/Berlin" },
|
|
{ label: "Osteuropa (Athen)", id: "Europe/Athens" },
|
|
{ label: "Moskau", id: "Europe/Moscow" },
|
|
{ label: "Dubai", id: "Asia/Dubai" },
|
|
{ label: "Indien (Mumbai)", id: "Asia/Kolkata" },
|
|
{ label: "China (Peking)", id: "Asia/Shanghai" },
|
|
{ label: "Japan (Tokio)", id: "Asia/Tokyo" },
|
|
{ label: "Australien (Sydney)", id: "Australia/Sydney" },
|
|
{ label: "Neuseeland (Auckland)", id: "Pacific/Auckland" }
|
|
];
|
|
|
|
// Populate timezone dropdowns
|
|
const sourceTimezoneSelect = document.getElementById('source-timezone');
|
|
const targetTimezoneSelect = document.getElementById('target-timezone');
|
|
|
|
majorTimezones.forEach(tz => {
|
|
// Für Quell-Zeitzone
|
|
const sourceOption = document.createElement('option');
|
|
sourceOption.value = tz.id;
|
|
sourceOption.textContent = tz.label;
|
|
sourceTimezoneSelect.appendChild(sourceOption);
|
|
|
|
// Für Ziel-Zeitzone
|
|
const targetOption = document.createElement('option');
|
|
targetOption.value = tz.id;
|
|
targetOption.textContent = tz.label;
|
|
targetTimezoneSelect.appendChild(targetOption);
|
|
});
|
|
|
|
// Set default values for date and time inputs
|
|
const now = new Date();
|
|
const dateInput = document.getElementById('custom-date');
|
|
const timeInput = document.getElementById('custom-time');
|
|
const hourValue = document.getElementById('hour-value');
|
|
const minuteValue = document.getElementById('minute-value');
|
|
|
|
dateInput.value = now.toISOString().split('T')[0];
|
|
|
|
// Initialize time spinners
|
|
const hours = now.getHours();
|
|
const minutes = now.getMinutes();
|
|
|
|
hourValue.textContent = hours.toString().padStart(2, '0');
|
|
minuteValue.textContent = minutes.toString().padStart(2, '0');
|
|
timeInput.value = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
|
|
|
|
// Time spinner event listeners
|
|
document.getElementById('hour-up').addEventListener('click', () => {
|
|
let hour = parseInt(hourValue.textContent);
|
|
hour = (hour + 1) % 24;
|
|
hourValue.textContent = hour.toString().padStart(2, '0');
|
|
updateHiddenTimeInput();
|
|
});
|
|
|
|
document.getElementById('hour-down').addEventListener('click', () => {
|
|
let hour = parseInt(hourValue.textContent);
|
|
hour = (hour - 1 + 24) % 24;
|
|
hourValue.textContent = hour.toString().padStart(2, '0');
|
|
updateHiddenTimeInput();
|
|
});
|
|
|
|
document.getElementById('minute-up').addEventListener('click', () => {
|
|
let minute = parseInt(minuteValue.textContent);
|
|
minute = (minute + 1) % 60;
|
|
minuteValue.textContent = minute.toString().padStart(2, '0');
|
|
updateHiddenTimeInput();
|
|
});
|
|
|
|
document.getElementById('minute-down').addEventListener('click', () => {
|
|
let minute = parseInt(minuteValue.textContent);
|
|
minute = (minute - 1 + 60) % 60;
|
|
minuteValue.textContent = minute.toString().padStart(2, '0');
|
|
updateHiddenTimeInput();
|
|
});
|
|
|
|
// Support for dragging the time values up/down
|
|
let isDragging = false;
|
|
let startY = 0;
|
|
let currentElement = null;
|
|
|
|
function handleDragStart(e, element) {
|
|
isDragging = true;
|
|
startY = e.clientY || e.touches[0].clientY;
|
|
currentElement = element;
|
|
}
|
|
|
|
function handleDragMove(e) {
|
|
if (!isDragging || !currentElement) return;
|
|
|
|
const currentY = e.clientY || e.touches[0].clientY;
|
|
const deltaY = startY - currentY;
|
|
|
|
// Threshold to trigger a change (10px)
|
|
if (Math.abs(deltaY) > 10) {
|
|
if (currentElement === hourValue) {
|
|
if (deltaY > 0) {
|
|
document.getElementById('hour-up').click();
|
|
} else {
|
|
document.getElementById('hour-down').click();
|
|
}
|
|
startY = currentY;
|
|
} else if (currentElement === minuteValue) {
|
|
if (deltaY > 0) {
|
|
document.getElementById('minute-up').click();
|
|
} else {
|
|
document.getElementById('minute-down').click();
|
|
}
|
|
startY = currentY;
|
|
}
|
|
}
|
|
}
|
|
|
|
function handleDragEnd() {
|
|
isDragging = false;
|
|
currentElement = null;
|
|
}
|
|
|
|
// Setup drag events for hours
|
|
hourValue.addEventListener('mousedown', (e) => handleDragStart(e, hourValue));
|
|
hourValue.addEventListener('touchstart', (e) => handleDragStart(e, hourValue));
|
|
|
|
// Setup drag events for minutes
|
|
minuteValue.addEventListener('mousedown', (e) => handleDragStart(e, minuteValue));
|
|
minuteValue.addEventListener('touchstart', (e) => handleDragStart(e, minuteValue));
|
|
|
|
// Global events for move and end
|
|
window.addEventListener('mousemove', handleDragMove);
|
|
window.addEventListener('touchmove', handleDragMove);
|
|
window.addEventListener('mouseup', handleDragEnd);
|
|
window.addEventListener('touchend', handleDragEnd);
|
|
|
|
function updateHiddenTimeInput() {
|
|
timeInput.value = `${hourValue.textContent}:${minuteValue.textContent}`;
|
|
}
|
|
|
|
// Detect user's timezone
|
|
const localTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
document.getElementById('local-timezone').textContent =
|
|
`Deine Zeitzone: ${localTimezone} (${getTimezoneOffsetString(now, localTimezone)})`;
|
|
|
|
// Set initial timezone selection to match user's timezone, if possible
|
|
for (let i = 0; i < sourceTimezoneSelect.options.length; i++) {
|
|
if (sourceTimezoneSelect.options[i].value === localTimezone) {
|
|
sourceTimezoneSelect.selectedIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Set target timezone initially to different timezone (e.g., New York)
|
|
for (let i = 0; i < targetTimezoneSelect.options.length; i++) {
|
|
if (targetTimezoneSelect.options[i].value === "America/New_York") {
|
|
targetTimezoneSelect.selectedIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Update local time every second
|
|
function updateLocalTime() {
|
|
const now = new Date();
|
|
document.getElementById('local-time').textContent = now.toLocaleTimeString();
|
|
|
|
// Auto update world times every minute
|
|
if (now.getSeconds() === 0) {
|
|
generateTimezoneCards();
|
|
}
|
|
|
|
setTimeout(updateLocalTime, 1000);
|
|
}
|
|
updateLocalTime();
|
|
|
|
// Update world times every 30 seconds
|
|
setInterval(() => {
|
|
generateTimezoneCards();
|
|
}, 30000);
|
|
|
|
// Create timezone cards
|
|
function generateTimezoneCards(referenceDate = new Date()) {
|
|
const container = document.getElementById('timezone-list');
|
|
container.innerHTML = '';
|
|
|
|
majorTimezones.forEach(tz => {
|
|
const card = document.createElement('div');
|
|
card.className = 'timezone-card';
|
|
|
|
// Format time and date using Intl.DateTimeFormat for the specific timezone
|
|
const timeFormatter = new Intl.DateTimeFormat('de-DE', { timeZone: tz.id, hour: '2-digit', minute: '2-digit' });
|
|
const dateFormatter = new Intl.DateTimeFormat('de-DE', { timeZone: tz.id, year: 'numeric', month: '2-digit', day: '2-digit' });
|
|
|
|
const timeStr = timeFormatter.format(referenceDate);
|
|
const dateStr = dateFormatter.format(referenceDate);
|
|
const offsetStr = getTimezoneOffsetString(referenceDate, tz.id);
|
|
|
|
card.innerHTML = `
|
|
<h3>${tz.label}</h3>
|
|
<div class="time">${timeStr}</div>
|
|
<div class="date">${dateStr}</div>
|
|
<div class="timezone-offset">${offsetStr}</div>
|
|
`;
|
|
|
|
container.appendChild(card);
|
|
});
|
|
}
|
|
|
|
// Calculate and show timezone results
|
|
document.getElementById('calculate-btn').addEventListener('click', () => {
|
|
const dateStr = dateInput.value; // "YYYY-MM-DD"
|
|
const timeStr = timeInput.value; // "HH:mm"
|
|
const sourceTimezoneId = sourceTimezoneSelect.value;
|
|
const targetTimezoneId = targetTimezoneSelect.value;
|
|
|
|
if (!dateStr || !timeStr || !sourceTimezoneId || !targetTimezoneId) {
|
|
alert('Bitte gib ein Datum, eine Zeit und beide Zeitzonen an.');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// --- Korrekte Logik zur Erstellung des Referenz-Zeitpunkts ---
|
|
|
|
// 1. Parse input date/time components
|
|
const [year, month, day] = dateStr.split('-').map(Number);
|
|
const [hours, minutes] = timeStr.split(':').map(Number);
|
|
|
|
// 2. Helper function to get offset in hours for a given date and timezone
|
|
function getOffsetHours(date, timezone) {
|
|
try {
|
|
const formatter = new Intl.DateTimeFormat('en-US', {
|
|
timeZone: timezone,
|
|
timeZoneName: 'longOffset' // e.g., GMT-07:00 or GMT+02:00
|
|
});
|
|
const parts = formatter.formatToParts(date);
|
|
const offsetPart = parts.find(part => part.type === 'timeZoneName');
|
|
if (offsetPart) {
|
|
// Extract hours and minutes from GMT string (e.g., "GMT-7", "GMT+5:30")
|
|
const match = offsetPart.value.match(/GMT([-+])(\d{1,2})(?::(\d{2}))?/);
|
|
if (match) {
|
|
const sign = match[1] === '-' ? -1 : 1;
|
|
const hourOffset = parseInt(match[2], 10);
|
|
const minuteOffset = match[3] ? parseInt(match[3], 10) : 0;
|
|
return sign * (hourOffset + minuteOffset / 60);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.warn(`Could not determine offset for timezone ${timezone}`, e);
|
|
}
|
|
// Fallback: Browser's local offset for the given date (less accurate for the *target* zone)
|
|
console.warn(`Falling back to local offset for timezone ${timezone}`);
|
|
return -date.getTimezoneOffset() / 60;
|
|
}
|
|
|
|
// 3. Estimate the UTC time by initially assuming the input time *was* UTC.
|
|
// We need a Date object to pass to getOffsetHours.
|
|
const guessedUtcMillis = Date.UTC(year, month - 1, day, hours, minutes);
|
|
const tempDate = new Date(guessedUtcMillis);
|
|
|
|
// 4. Get the actual offset of the source timezone *at that time*.
|
|
const sourceOffsetHours = getOffsetHours(tempDate, sourceTimezoneId);
|
|
|
|
// 5. Calculate the correct UTC milliseconds.
|
|
// The input time (e.g., 20:00 Berlin) corresponds to a UTC time that is
|
|
// 'sourceOffsetHours' earlier than 20:00 UTC.
|
|
// So, we subtract the offset from the initial UTC guess.
|
|
const correctUtcMillis = guessedUtcMillis - (sourceOffsetHours * 3600 * 1000);
|
|
|
|
// 6. Create the final Date object representing the correct moment in time.
|
|
const referenceDate = new Date(correctUtcMillis);
|
|
|
|
// 7. Display the results using this correct referenceDate.
|
|
displayConversionResult(referenceDate, sourceTimezoneId, targetTimezoneId);
|
|
|
|
} catch (error) {
|
|
console.error("Fehler bei der Zeitzonenberechnung:", error);
|
|
// Add more specific error message if Intl fails (e.g., invalid timezone ID)
|
|
if (error instanceof RangeError) {
|
|
alert(`Fehler: Eine der angegebenen Zeitzonen ('${sourceTimezoneId}' oder '${targetTimezoneId}') ist ungültig oder wird vom Browser nicht unterstützt.`);
|
|
} else {
|
|
alert("Ein unerwarteter Fehler bei der Berechnung der Zeit ist aufgetreten. Bitte überprüfe die Eingabe.");
|
|
}
|
|
}
|
|
});
|
|
|
|
// Display conversion result between source and target timezone
|
|
function displayConversionResult(referenceDate, sourceTimezoneId, targetTimezoneId) {
|
|
const container = document.getElementById('timezone-result');
|
|
container.innerHTML = '';
|
|
|
|
const sourceTzData = majorTimezones.find(tz => tz.id === sourceTimezoneId);
|
|
const targetTzData = majorTimezones.find(tz => tz.id === targetTimezoneId);
|
|
|
|
if (!sourceTzData || !targetTzData) {
|
|
console.error("Source or target timezone data not found in majorTimezones list.");
|
|
return;
|
|
}
|
|
|
|
// --- Funktion zum Erstellen einer Karte ---
|
|
function createResultCard(timezoneData, dateToFormat) {
|
|
const card = document.createElement('div');
|
|
card.className = 'timezone-card';
|
|
|
|
const timeFormatter = new Intl.DateTimeFormat('de-DE', { timeZone: timezoneData.id, hour: '2-digit', minute: '2-digit' });
|
|
const dateFormatter = new Intl.DateTimeFormat('de-DE', { timeZone: timezoneData.id, year: 'numeric', month: '2-digit', day: '2-digit' });
|
|
|
|
const timeStr = timeFormatter.format(dateToFormat);
|
|
const dateStr = dateFormatter.format(dateToFormat);
|
|
const offsetStr = getTimezoneOffsetString(dateToFormat, timezoneData.id);
|
|
|
|
card.innerHTML = `
|
|
<h3>${timezoneData.label}</h3>
|
|
<div class="time">${timeStr}</div>
|
|
<div class="date">${dateStr}</div>
|
|
<div class="timezone-offset">${offsetStr}</div>
|
|
`;
|
|
return card;
|
|
}
|
|
|
|
// Erstelle Quell- und Ziel-Karten
|
|
const sourceCard = createResultCard(sourceTzData, referenceDate);
|
|
const targetCard = createResultCard(targetTzData, referenceDate);
|
|
|
|
container.appendChild(sourceCard);
|
|
|
|
// Pfeil zwischen den Karten
|
|
const arrowContainer = document.createElement('div');
|
|
arrowContainer.className = 'timezone-card arrow-container';
|
|
arrowContainer.innerHTML = `<div class="conversion-arrow">→</div>`;
|
|
container.appendChild(arrowContainer);
|
|
|
|
container.appendChild(targetCard);
|
|
}
|
|
|
|
// Helper function to get timezone offset string (e.g., GMT+1)
|
|
function getTimezoneOffsetString(date, timezone) {
|
|
try {
|
|
// Nutze Intl.DateTimeFormat, um den Offset für das spezifische Datum und die Zeitzone zu erhalten
|
|
const formatter = new Intl.DateTimeFormat('en-US', {
|
|
timeZone: timezone,
|
|
timeZoneName: 'longOffset' // Gibt z.B. "GMT+01:00" zurück
|
|
});
|
|
|
|
// Formatiere das Datum, um den Offset-String zu extrahieren
|
|
const formattedParts = formatter.formatToParts(date);
|
|
const offsetPart = formattedParts.find(part => part.type === 'timeZoneName');
|
|
|
|
if (offsetPart) {
|
|
// Extrahiere den numerischen Teil (z.B. +1, -8)
|
|
const match = offsetPart.value.match(/GMT([-+]\d{1,2}(?::\d{2})?)/);
|
|
if (match && match[1]) {
|
|
// Vereinfache zu GMT+X oder GMT-X
|
|
const numericOffset = parseInt(match[1].split(':')[0], 10);
|
|
const sign = numericOffset >= 0 ? '+' : '-';
|
|
return `GMT${sign}${Math.abs(numericOffset)}`;
|
|
}
|
|
return offsetPart.value; // Fallback auf den vollen String
|
|
}
|
|
} catch (error) {
|
|
console.warn(`Konnte Offset für Zeitzone ${timezone} nicht ermitteln:`, error);
|
|
// Fallback: Verwende den Offset der lokalen Zeitzone des Browsers für dieses Datum
|
|
const localOffsetMinutes = date.getTimezoneOffset();
|
|
const offsetHours = -localOffsetMinutes / 60;
|
|
const sign = offsetHours >= 0 ? '+' : '-';
|
|
return `GMT${sign}${Math.abs(offsetHours)} (Lokal)`;
|
|
}
|
|
|
|
// Sicherer Fallback, falls alles fehlschlägt
|
|
return 'GMT?';
|
|
}
|
|
|
|
// Generate initial timezone cards
|
|
generateTimezoneCards();
|
|
</script>
|
|
</body>
|
|
</html> |