1
0

789 lines
28 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> | &copy; <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", offset: -8 },
{ label: "Mountain (Denver)", id: "America/Denver", offset: -7 },
{ label: "Central (Chicago)", id: "America/Chicago", offset: -6 },
{ label: "Eastern (New York)", id: "America/New_York", offset: -5 },
{ label: "Brasilien (São Paulo)", id: "America/Sao_Paulo", offset: -3 },
{ label: "Großbritannien (London)", id: "Europe/London", offset: 0 },
{ label: "Westeuropa (Paris, Berlin)", id: "Europe/Berlin", offset: 1 },
{ label: "Osteuropa (Athen)", id: "Europe/Athens", offset: 2 },
{ label: "Moskau", id: "Europe/Moscow", offset: 3 },
{ label: "Dubai", id: "Asia/Dubai", offset: 4 },
{ label: "Indien (Mumbai)", id: "Asia/Kolkata", offset: 5.5 },
{ label: "China (Peking)", id: "Asia/Shanghai", offset: 8 },
{ label: "Japan (Tokio)", id: "Asia/Tokyo", offset: 9 },
{ label: "Australien (Sydney)", id: "Australia/Sydney", offset: 10 },
{ label: "Neuseeland (Auckland)", id: "Pacific/Auckland", offset: 12 }
];
// 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 => {
// Create a timezone card
const card = document.createElement('div');
card.className = 'timezone-card';
const dateInTimezone = getDateInTimezone(referenceDate, tz.id);
card.innerHTML = `
<h3>${tz.label}</h3>
<div class="time">${dateInTimezone.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}</div>
<div class="date">${dateInTimezone.toLocaleDateString()}</div>
<div class="timezone-offset">${getTimezoneOffsetString(dateInTimezone, tz.id)}</div>
`;
container.appendChild(card);
});
}
// Calculate and show timezone results
document.getElementById('calculate-btn').addEventListener('click', () => {
const dateStr = dateInput.value;
const timeStr = timeInput.value;
const sourceTimezone = sourceTimezoneSelect.value;
const targetTimezone = targetTimezoneSelect.value;
if (!dateStr || !timeStr) {
alert('Bitte gib ein Datum und eine Zeit ein');
return;
}
// Create date object from inputs
const [year, month, day] = dateStr.split('-').map(Number);
const [hours, minutes] = timeStr.split(':').map(Number);
// Erstelle ein Datum im lokalen Format
const inputDate = new Date(year, month - 1, day, hours, minutes);
// Wir erstellen eine Referenzzeit, die der Eingabe in der Quell-Zeitzone entspricht
const utcMilliseconds = Date.UTC(year, month - 1, day, hours, minutes);
const sourceOffset = getTimezoneOffsetHours(sourceTimezone) * 60 * 60 * 1000;
const referenceDate = new Date(utcMilliseconds - sourceOffset);
// Zeige nur die Quell- und Zielzeitzone an
displayConversionResult(referenceDate, sourceTimezone, targetTimezone);
});
// Display conversion result between source and target timezone
function displayConversionResult(referenceDate, sourceTimezone, targetTimezone) {
const container = document.getElementById('timezone-result');
container.innerHTML = '';
const sourceTz = majorTimezones.find(tz => tz.id === sourceTimezone);
const targetTz = majorTimezones.find(tz => tz.id === targetTimezone);
if (!sourceTz || !targetTz) return;
// Quell-Zeitzone Karte
const sourceCard = document.createElement('div');
sourceCard.className = 'timezone-card';
const dateInSourceTimezone = getDateInTimezone(referenceDate, sourceTimezone);
sourceCard.innerHTML = `
<h3>${sourceTz.label}</h3>
<div class="time">${dateInSourceTimezone.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}</div>
<div class="date">${dateInSourceTimezone.toLocaleDateString()}</div>
<div class="timezone-offset">${getTimezoneOffsetString(dateInSourceTimezone, sourceTimezone)}</div>
`;
// Ziel-Zeitzone Karte
const targetCard = document.createElement('div');
targetCard.className = 'timezone-card';
const dateInTargetTimezone = getDateInTimezone(referenceDate, targetTimezone);
targetCard.innerHTML = `
<h3>${targetTz.label}</h3>
<div class="time">${dateInTargetTimezone.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}</div>
<div class="date">${dateInTargetTimezone.toLocaleDateString()}</div>
<div class="timezone-offset">${getTimezoneOffsetString(dateInTargetTimezone, targetTimezone)}</div>
`;
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 a date object in a specific timezone
function getDateInTimezone(date, timezone) {
// Nutze die eingebaute Zeitzonenunterstützung von JavaScript
const options = { timeZone: timezone };
const formatter = new Intl.DateTimeFormat('en-US', {
...options,
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
hour12: false
});
const parts = formatter.formatToParts(date);
const dateObj = {};
parts.forEach(part => {
if (part.type !== 'literal') {
dateObj[part.type] = part.value;
}
});
return new Date(
dateObj.year,
parseInt(dateObj.month, 10) - 1,
dateObj.day,
dateObj.hour,
dateObj.minute,
dateObj.second
);
}
// Helper function to get timezone offset string (e.g., GMT+1)
function getTimezoneOffsetString(date, timezone) {
if (timezone) {
// Ermittle den tatsächlichen Offset für das Datum in der Zeitzone
const testDate = new Date(date); // Wir erstellen eine Kopie
// Formatiere das Datum in der angegebenen Zeitzone und hole den Offset
const formatter = new Intl.DateTimeFormat('en-US', {
timeZone: timezone,
timeZoneName: 'longOffset'
});
// Extrahiere den GMT-Offset-String aus der formatierten Ausgabe
const formattedDate = formatter.format(testDate);
const offsetMatch = formattedDate.match(/GMT([-+]\d+)/);
if (offsetMatch) {
return `GMT${offsetMatch[1]}`;
}
// Fallback: Verwende den statischen Offset aus majorTimezones
const tz = majorTimezones.find(t => t.id === timezone);
if (tz) {
const sign = tz.offset >= 0 ? '+' : '-';
return `GMT${sign}${Math.abs(tz.offset)}`;
}
}
// Wenn keine Zeitzone angegeben oder gefunden wurde, verwende den lokalen Offset
const offset = date.getTimezoneOffset() * -1 / 60;
const sign = offset >= 0 ? '+' : '-';
return `GMT${sign}${Math.abs(offset)}`;
}
// Helper function to get timezone offset in hours
function getTimezoneOffsetHours(timezone) {
const tz = majorTimezones.find(t => t.id === timezone);
return tz ? tz.offset : 0;
}
// Generate initial timezone cards
generateTimezoneCards();
</script>
</body>
</html>