Time Zones Are Hard - https://tz.rita.moe

Better nojs display; Fix title; Icons; Share

Error is also a 404

+9
browserconfig.xml
···
+
<?xml version="1.0" encoding="utf-8"?>
+
<browserconfig>
+
<msapplication>
+
<tile>
+
<square150x150logo src="/img/mstile-150x150.png"/>
+
<TileColor>#111111</TileColor>
+
</tile>
+
</msapplication>
+
</browserconfig>
+1 -1
css/styles.css
···
-
body{background-color:#efefef;color:#000;font-family:"Trebuchet MS","Lucida Sans Unicode","Lucida Grande","Lucida Sans",Arial,sans-serif;margin:0 auto;max-width:900px}header{background:#fff;border-radius:10px;margin:1rem .5rem 0;padding:1rem 1.5rem}header h1{font-size:1.5rem;margin:0}@media screen and (min-width: 500px){header h1{font-size:2rem}}header a{color:inherit;text-decoration:none}main{padding:1rem 2rem}main h2{font-size:1.5rem;margin:1rem 0;text-align:center}main h2 span{font-size:1.2rem}main noscript small{font-size:small}main form{line-height:2rem}main button{cursor:pointer}main .tz-table{display:flex;flex-wrap:wrap;justify-content:center;text-align:center}main .tz-table .tz-table-element{flex:0 0 calc(100% - 1rem);margin:.5rem}@media screen and (min-width: 440px){main .tz-table .tz-table-element{flex:0 0 calc(50% - 1rem)}}@media screen and (min-width: 630px){main .tz-table .tz-table-element{flex:0 0 calc(33% - 1rem)}}@media screen and (min-width: 820px){main .tz-table .tz-table-element{flex:0 0 calc(25% - 1rem)}}footer{color:gray;font-size:.7rem;margin-top:1rem;padding:1rem;text-align:center}@media screen and (prefers-color-scheme: dark){body{background-color:#111;color:#fff}header{background:#000}}/*# sourceMappingURL=styles.css.map */
+
body{background-color:#efefef;color:#000;font-family:"Trebuchet MS","Lucida Sans Unicode","Lucida Grande","Lucida Sans",Arial,sans-serif;margin:0 auto;max-width:900px}header{background:#fff;border-radius:10px;margin:1rem .5rem 0;padding:1rem 1.5rem}header h1{font-size:1.5rem;margin:0}@media screen and (min-width: 500px){header h1{font-size:2rem}}header a{color:inherit;text-decoration:none}header svg{height:2rem;vertical-align:-0.3rem;width:2rem}main{padding:1rem 2rem}main h2{font-size:1.5rem;margin:1rem 0;text-align:center}main h2 span{font-size:1.2rem}main form{line-height:2rem}main label{cursor:pointer}main button{cursor:pointer}main .tz-table{display:flex;flex-wrap:wrap;justify-content:center;text-align:center}main .tz-table .tz-table-element{flex:0 0 calc(100% - 1rem);margin:.5rem}@media screen and (min-width: 440px){main .tz-table .tz-table-element{flex:0 0 calc(50% - 1rem)}}@media screen and (min-width: 630px){main .tz-table .tz-table-element{flex:0 0 calc(33% - 1rem)}}@media screen and (min-width: 820px){main .tz-table .tz-table-element{flex:0 0 calc(25% - 1rem)}}main .share{display:none;margin-top:2rem;text-align:center}footer{color:gray;font-size:.7rem;margin-top:1rem;padding:1rem;text-align:center}footer a{color:inherit}@media screen and (prefers-color-scheme: dark){body{background-color:#111;color:#fff}header{background:#000}}/*# sourceMappingURL=styles.css.map */
+1 -1
css/styles.css.map
···
-
{"version":3,"sourceRoot":"","sources":["styles.scss"],"names":[],"mappings":"AAAA,KACE,yBACA,WACA,gGACA,cACA,gBAGF,OACE,gBACA,mBACA,oBACA,oBAEA,UACE,iBACA,SAEA,qCAJF,UAKI,gBAIJ,SACE,cACA,qBAIJ,KACE,kBAEA,QACE,iBACA,cACA,kBAEA,aACE,iBAIJ,oBACE,gBAGF,UACE,iBAGF,YACE,eAGF,eACE,aACA,eACA,uBACA,kBAIA,iCACE,2BACA,OAJQ,MAMR,qCAJF,iCAKI,2BAGF,qCARF,iCASI,2BAGF,qCAZF,iCAaI,2BAMR,OACE,WACA,gBACA,gBACA,aACA,kBAGF,+CACE,KACE,sBACA,WAGF,OACE","file":"styles.css"}
+
{"version":3,"sourceRoot":"","sources":["styles.scss"],"names":[],"mappings":"AAAA,KACE,yBACA,WACA,gGACA,cACA,gBAGF,OACE,gBACA,mBACA,oBACA,oBAEA,UACE,iBACA,SAEA,qCAJF,UAKI,gBAIJ,SACE,cACA,qBAGF,WACE,YACA,uBACA,WAIJ,KACE,kBAEA,QACE,iBACA,cACA,kBAEA,aACE,iBAIJ,UACE,iBAGF,WACE,eAGF,YACE,eAGF,eACE,aACA,eACA,uBACA,kBAIA,iCACE,2BACA,OAJQ,MAMR,qCAJF,iCAKI,2BAGF,qCARF,iCASI,2BAGF,qCAZF,iCAaI,2BAKN,YACE,aACA,gBACA,kBAIJ,OACE,WACA,gBACA,gBACA,aACA,kBAEA,SACE,cAIJ,+CACE,KACE,sBACA,WAGF,OACE","file":"styles.css"}
+20 -4
css/styles.scss
···
color: inherit;
text-decoration: none;
}
+
+
svg {
+
height: 2rem;
+
vertical-align: -0.3rem;
+
width: 2rem;
+
}
}
main {
···
}
}
-
noscript small {
-
font-size: small;
-
}
-
form {
line-height: 2rem;
}
+
label {
+
cursor: pointer;
+
}
+
button {
cursor: pointer;
}
···
}
}
}
+
+
.share {
+
display: none;
+
margin-top: 2rem;
+
text-align: center;
+
}
}
footer {
···
margin-top: 1rem;
padding: 1rem;
text-align: center;
+
+
a {
+
color: inherit;
+
}
}
@media screen and (prefers-color-scheme: dark) {
favicon.ico

This is a binary file and will not be displayed.

img/android-chrome-192x192.png

This is a binary file and will not be displayed.

img/android-chrome-512x512.png

This is a binary file and will not be displayed.

img/apple-touch-icon.png

This is a binary file and will not be displayed.

+1
img/clock.svg
···
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" x="0px" y="0px"><title>Clock</title><g data-name="07"><path fill="#fff" d="M20.51,29h-9A8.51,8.51,0,0,1,3,20.51v-9A8.51,8.51,0,0,1,11.51,3h9a8.51,8.51,0,0,1,8.5,8.5v9A8.51,8.51,0,0,1,20.51,29Zm-9-25A7.5,7.5,0,0,0,4,11.51v9a7.5,7.5,0,0,0,7.5,7.5h9a7.5,7.5,0,0,0,7.5-7.5v-9A7.5,7.5,0,0,0,20.51,4ZM21.69,21.54a1.48,1.48,0,0,0-.11-2.11L17,15.33V9.51a1.5,1.5,0,0,0-3,0V16a1.5,1.5,0,0,0,.5,1.12l5.07,4.54a1.46,1.46,0,0,0,1,.38A1.48,1.48,0,0,0,21.69,21.54Z"/></g></svg>
img/favicon-16x16.png

This is a binary file and will not be displayed.

img/favicon-32x32.png

This is a binary file and will not be displayed.

img/maskable_icon.png

This is a binary file and will not be displayed.

img/maskable_icon_x192.png

This is a binary file and will not be displayed.

img/maskable_icon_x512.png

This is a binary file and will not be displayed.

img/mstile-144x144.png

This is a binary file and will not be displayed.

img/mstile-150x150.png

This is a binary file and will not be displayed.

img/mstile-310x150.png

This is a binary file and will not be displayed.

img/mstile-310x310.png

This is a binary file and will not be displayed.

img/mstile-70x70.png

This is a binary file and will not be displayed.

+38
img/safari-pinned-tab.svg
···
+
<?xml version="1.0" standalone="no"?>
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
+
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
+
width="700.000000pt" height="700.000000pt" viewBox="0 0 700.000000 700.000000"
+
preserveAspectRatio="xMidYMid meet">
+
<metadata>
+
Created by potrace 1.14, written by Peter Selinger 2001-2017
+
</metadata>
+
<g transform="translate(0.000000,700.000000) scale(0.100000,-0.100000)"
+
fill="#000000" stroke="none">
+
<path d="M2447 6344 c-1 -1 -44 -5 -94 -8 -280 -21 -607 -135 -853 -296 -319
+
-210 -556 -498 -713 -870 -57 -134 -111 -367 -123 -531 -10 -119 -10 -2165 -1
+
-2279 7 -81 11 -113 22 -170 3 -14 7 -36 10 -49 26 -140 95 -333 169 -476 44
+
-85 119 -208 136 -225 3 -3 25 -32 50 -65 155 -206 402 -406 655 -533 65 -32
+
246 -102 282 -108 12 -2 34 -9 47 -14 50 -19 244 -51 346 -57 141 -9 2131 -9
+
2245 0 78 6 133 13 245 32 54 9 259 74 330 104 475 203 822 560 1024 1055 47
+
114 99 340 113 487 9 98 9 2222 0 2319 -4 41 -9 86 -12 100 -3 13 -7 42 -11
+
64 -3 22 -7 44 -10 47 -2 4 -6 23 -9 43 -6 35 -47 165 -80 256 -47 127 -160
+
324 -258 450 -321 411 -769 658 -1297 716 -51 6 -2206 13 -2213 8z m2213 -228
+
c36 -5 74 -10 85 -12 11 -2 36 -7 55 -10 118 -21 298 -82 418 -143 362 -182
+
643 -490 792 -865 33 -84 74 -227 85 -296 4 -25 9 -54 11 -65 15 -76 18 -300
+
18 -1225 0 -951 -3 -1166 -19 -1240 -3 -14 -8 -43 -11 -65 -22 -147 -133 -419
+
-237 -579 -265 -406 -716 -682 -1196 -734 -85 -9 -2235 -9 -2321 0 -195 21
+
-421 91 -596 185 -481 256 -806 743 -861 1288 -9 91 -9 2187 0 2283 4 40 9 83
+
12 95 2 12 6 36 9 52 7 52 45 192 73 268 44 120 158 331 220 407 7 8 26 33 43
+
55 124 158 343 336 531 431 188 94 384 151 586 170 37 4 69 7 70 8 7 6 2183
+
-2 2233 -8z"/>
+
<path d="M3293 5231 c-98 -33 -161 -91 -206 -189 l-22 -47 0 -785 0 -785 32
+
-66 c24 -49 52 -82 109 -135 43 -38 82 -73 88 -79 6 -5 74 -66 151 -135 77
+
-69 145 -129 151 -135 6 -5 46 -41 89 -80 43 -38 83 -74 89 -80 6 -5 52 -46
+
101 -90 50 -44 115 -102 145 -130 70 -64 205 -184 250 -224 66 -58 125 -83
+
208 -88 97 -6 169 19 241 83 74 67 106 138 106 239 0 133 -30 182 -215 345
+
-46 41 -107 95 -135 120 -156 141 -592 531 -682 610 l-73 64 0 191 c0 838 -5
+
1173 -19 1188 -7 10 -11 17 -7 17 12 0 -57 100 -87 126 -85 72 -215 99 -314
+
65z"/>
+
</g>
+
</svg>
+77 -11
index.php
···
return str_starts_with($offset, '-') || str_starts_with($offset, '+') ? 'UTC' . $offset : $offset;
}
-
function toTZ($dt, $tz) {
+
function toTZ($dt, $tz, $format='Y-m-d H:i') {
$tz = new DateTimeZone($tz);
-
return $dt->setTimezone($tz)->format('Y-m-d H:i');
+
return $dt->setTimezone($tz)->format($format);
}
$popTZ = [
···
}
}
+
// User sets 24 hours
+
if (isset($_POST['user-24'])) {
+
setcookie('user-24', '1', time() + 60 * 60 * 24);
+
$_COOKIE['user-24'] = '1';
+
}
+
// Micro-routing
if (isset($_POST['datetime']) && isset($_POST['timezone'])) {
// -- Redirect to submitted date
···
// First check if date is following the format
$re = '/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[\_|-]\d{2}:\d{2}$/';
if (!preg_match($re, $req)) {
-
http_response_code(400);
-
$error = 'Date is not valid or wrong format.';
+
http_response_code(404);
+
$error = 'Date is not valid, wrong format, or file not found.';
} else {
// Make our date object, convert back "_" to "+"
$dt = new DateTime(str_replace('_', '+', $req));
···
}
?>
<!DOCTYPE html>
+
<!--
+
Source code: https://git.rita.moe/kody/tz
+
License: MIT
+
-->
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
-
<title>Time Zones are hard<?= isset($_GET['date']) ? ' - ' . $_GET['date'] : '' ?></title>
+
<title>Time Zones Are Hard<?= isset($req) ? ' - ' . str_replace('_', '+', $req) : '' ?></title>
<link rel="stylesheet" href="/css/styles.css">
<?php if (isset($dt)) { ?>
<meta name="robots" content="noindex,noarchive">
···
dayjs.extend(window.dayjs_plugin_relativeTime)
</script>
<?php } ?>
+
<link rel="apple-touch-icon" sizes="180x180" href="/img/apple-touch-icon.png">
+
<link rel="icon" type="image/png" sizes="32x32" href="/img/favicon-32x32.png">
+
<link rel="icon" type="image/png" sizes="16x16" href="/img/favicon-16x16.png">
+
<link rel="manifest" href="/site.webmanifest">
+
<link rel="mask-icon" href="/img/safari-pinned-tab.svg" color="#111111">
+
<meta name="msapplication-TileColor" content="#111111">
+
<meta name="theme-color" content="#111111">
</head>
<body>
<header>
-
<h1><a href="/">Time Zones are hard</a></h1>
+
<h1><a href="/"><?= file_get_contents('./img/clock.svg'); ?> Time Zones Are Hard</a></h1>
</header>
<main>
···
<noscript>
<?php
if (isset($_COOKIE['user-tz'])) {
-
echo toTZ($dt, $_COOKIE['user-tz']);
-
echo '<br/><small>(Enable JavaScript for better results.)</small>';
+
$hm = $_COOKIE['user-24'] === '1' ? 'H:i' : 'h:i a';
+
echo toTZ($dt, $_COOKIE['user-tz'], 'l \t\h\e jS \o\f F, Y \a\t ' . $hm);
} else {
?>
-
Sorry, we need JavaScript to detect your local time.<br/>
+
Sorry, we use JavaScript to detect your local time.<br/>
Enable it or select your time zone:<br/>
<form method="post">
<select required id="user-tz" name="user-tz">
···
<?php } ?>
</optgroup>
</select>
-
<button type="submit">Submit</button>
+
<button type="submit">Submit</button><br/>
+
<input type="checkbox" id="user-24" name="user-24"/>
+
<label for="user-24"><span>24h format</span></label>
</form>
<?php
}
···
<span><?= toTZ($dt, $tz) ?></span>
</div>
<?php } ?>
+
</div>
+
+
<div class="share">
+
<button type="button" onclick="shareURL()">Share</button>
+
<input type="hidden" id="url" value="https://tz.kdy.ch<?= $_SERVER['REQUEST_URI'] ?>"/>
</div>
<script>
···
// Display
document.querySelector('.local').innerHTML = localDT + '<br/><span>(' + relative + ')</span>'
+
+
// Enable share button
+
document.querySelector('.share').style.display = 'block'
+
+
// Our share URL function
+
function shareURL () {
+
if (navigator.canShare) {
+
// Use native navigator share
+
navigator.share({
+
url: 'https://tz.kdy.ch<?= $_SERVER['REQUEST_URI'] ?>'
+
})
+
} else if (navigator.clipboard) {
+
// Use Clipboard API
+
navigator.clipboard.writeText('https://tz.kdy.ch<?= $_SERVER['REQUEST_URI'] ?>')
+
.then(() => {
+
alert('URL copied to clipboard!')
+
})
+
.catch((err) => {
+
console.log('Clipboard API failed, fallback.', err)
+
// Use old input copy trick
+
commandCopy()
+
})
+
} else {
+
// Use old input copy trick
+
commandCopy()
+
}
+
}
+
+
// Copy content from the hidden input
+
function commandCopy () {
+
shareTextInput = document.querySelector('#url')
+
shareTextInput.focus()
+
shareTextInput.select()
+
+
if (document.execCommand('copy')) {
+
alert('URL copied to clipboard!')
+
} else {
+
// Fallback to prompt with URL
+
prompt('Share this URL:', document.querySelector('#url').value)
+
}
+
}
</script>
<?php } else { ?>
<p>
···
</main>
<footer>
-
&copy; <?= date('Y') ?> Kody - Made with 🍮
+
&copy; <?= date('Y') ?> Kody - Made with 🍮<br/>
+
<a href="https://thenounproject.com/icon/wall-clock-5456766/" target="_blank" title="Wall Clock Icon" rel="noopener">Wall Clock by Basicon</a> from Noun Project (CC BY 3.0)
</footer>
</body>
</html>
+38
site.webmanifest
···
+
{
+
"id": "/",
+
"start_url": "/",
+
"name": "Time Zones Are Hard",
+
"short_name": "TZAH",
+
"display": "standalone",
+
"orientation": "portrait-primary",
+
"categories": [
+
"utilities",
+
"productivity"
+
],
+
"theme_color": "#111111",
+
"background_color": "#111111",
+
"icons": [
+
{
+
"src": "/img/android-chrome-192x192.png",
+
"sizes": "192x192",
+
"type": "image/png"
+
},
+
{
+
"src": "/img/maskable_icon_x192.png",
+
"sizes": "192x192",
+
"type": "image/png",
+
"purpose": "maskable"
+
},
+
{
+
"src": "/img/android-chrome-512x512.png",
+
"sizes": "512x512",
+
"type": "image/png"
+
},
+
{
+
"src": "/img/maskable_icon_x512.png",
+
"sizes": "512x512",
+
"type": "image/png",
+
"purpose": "maskable"
+
}
+
]
+
}