Time Zones Are Hard - https://tz.rita.moe
1<?php 2function getOffset($tz) { 3 $tz = new DateTime('now', new DateTimeZone($tz)); 4 $offset = $tz->format('T'); 5 return str_starts_with($offset, '-') || str_starts_with($offset, '+') ? 'UTC' . $offset : $offset; 6} 7 8function toTZ($dt, $tz, $format='Y-m-d H:i') { 9 $tz = new DateTimeZone($tz); 10 return $dt->setTimezone($tz)->format($format); 11} 12 13$popTZ = [ 14 'America/Los_Angeles', 15 'America/New_York', 16 'Europe/London', 17 'Europe/Paris', 18 'Asia/Dubai', 19 'Asia/Bangkok', 20 'Asia/Tokyo', 21 'Australia/Sydney', 22 'UTC' 23]; 24 25// User sets timezone cookie 26if (isset($_POST['user-tz'])) { 27 // Just check it's valid 28 if (in_array($_POST['user-tz'], timezone_identifiers_list(DateTimeZone::ALL))) { 29 setcookie('user-tz', $_POST['user-tz'], time() + 60 * 60 * 24 * 365); 30 $_COOKIE['user-tz'] = $_POST['user-tz']; 31 } 32} 33 34// User sets 24 hours 35if (isset($_POST['user-24'])) { 36 setcookie('user-24', '1', time() + 60 * 60 * 24); 37 $_COOKIE['user-24'] = '1'; 38} 39 40// Micro-routing 41if (isset($_POST['datetime']) && isset($_POST['timezone'])) { 42 // -- Redirect to submitted date 43 $postTZ = $_POST['timezone']; 44 45 // Handle if timezone is an offset 46 if (is_numeric($_POST['timezone'])) { 47 $isNeg = str_starts_with($postTZ, '-'); 48 $h = str_pad(abs(intdiv($postTZ, 60)), 2, '0', STR_PAD_LEFT); 49 $m = str_pad($postTZ % 60, 2, '0', STR_PAD_LEFT); 50 $postTZ = ($isNeg ? '-' : '+') . $h . $m; 51 } 52 53 // Make our date object 54 $dateObj = new DateTime($_POST['datetime'], new DateTimeZone($postTZ)); 55 56 // Redirect, with the "+" replaced by "_" 57 header('Location: /' . str_replace('+', '_', $dateObj->format('c'))); 58 exit; 59} else if ($_SERVER['REQUEST_URI'] !== '/') { 60 // Remove leading "/" 61 $req = substr($_SERVER['REQUEST_URI'], 1); 62 63 // -- Show date infos 64 // First check if date is following the format 65 $re = '/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[\_|-]\d{2}:\d{2}$/'; 66 if (!preg_match($re, $req)) { 67 http_response_code(404); 68 $error = 'Date is not valid, wrong format, or file not found.'; 69 } else { 70 // Make our date object, convert back "_" to "+" 71 $dt = new DateTime(str_replace('_', '+', $req)); 72 } 73} 74?> 75<!DOCTYPE html> 76<!-- 77 Source code: https://git.rita.moe/kody/tz 78 License: MIT 79--> 80<html lang="en"> 81<head> 82 <meta charset="UTF-8"> 83 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 84 <title>Time Zones Are Hard<?= isset($req) ? ' - ' . str_replace('_', '+', $req) : '' ?></title> 85 <link rel="stylesheet" href="/css/styles.css"> 86<?php if (isset($dt)) { ?> 87 <meta name="robots" content="noindex,noarchive"> 88 <script src="/js/dayjs.min.js"></script> 89 <script src="/js/relativeTime.js"></script> 90 <script> 91 dayjs.extend(window.dayjs_plugin_relativeTime) 92 </script> 93<?php } ?> 94 <link rel="apple-touch-icon" sizes="180x180" href="/img/apple-touch-icon.png"> 95 <link rel="icon" type="image/png" sizes="32x32" href="/img/favicon-32x32.png"> 96 <link rel="icon" type="image/png" sizes="16x16" href="/img/favicon-16x16.png"> 97 <link rel="manifest" href="/site.webmanifest"> 98 <link rel="mask-icon" href="/img/safari-pinned-tab.svg" color="#111111"> 99 <meta name="msapplication-TileColor" content="#111111"> 100 <meta name="theme-color" content="#111111"> 101</head> 102<body> 103 <header> 104 <h1><a href="/"><?= file_get_contents('./img/clock.svg'); ?> Time Zones Are Hard</a></h1> 105 </header> 106 107 <main> 108<?php if (isset($error)) { ?> 109 <h2 class="error">Error: <?= $error ?></h2> 110 <form action="/" method="post"> 111 <button type="submit">Go back home</button> 112 </form> 113<?php } else if (isset($dt)) { ?> 114 <h2 class="local"> 115 <noscript> 116<?php 117 if (isset($_COOKIE['user-tz'])) { 118 $hm = $_COOKIE['user-24'] === '1' ? 'H:i' : 'h:i a'; 119 echo toTZ($dt, $_COOKIE['user-tz'], 'l \t\h\e jS \o\f F, Y \a\t ' . $hm); 120 } else { 121?> 122 Sorry, we use JavaScript to detect your local time.<br/> 123 Enable it or select your time zone:<br/> 124 <form method="post"> 125 <select required id="user-tz" name="user-tz"> 126 <option selected disabled>(pick one)</option> 127 <optgroup label="Popular"> 128<?php foreach($popTZ as $tz) { ?> 129 <option value="<?= $tz ?>"> 130 <?= str_replace('_', ' ', $tz) ?> (<?= getOffset($tz) ?>) 131 </option> 132<?php } ?> 133 </optgroup> 134 <optgroup label="All"> 135<?php foreach(timezone_identifiers_list(DateTimeZone::ALL) as $tz) { ?> 136 <option value="<?= $tz ?>"> 137 <?= str_replace('_', ' ', $tz) ?> (<?= getOffset($tz) ?>) 138 </option> 139<?php } ?> 140 </optgroup> 141 </select> 142 <button type="submit">Submit</button><br/> 143 <input type="checkbox" id="user-24" name="user-24"/> 144 <label for="user-24"><span>24h format</span></label> 145 </form> 146<?php 147 } 148?> 149 </noscript> 150 </h2> 151 152 <br/> 153 154 <h3>In popular time zones</h3> 155 <div class="tz-table"> 156<?php foreach($popTZ as $tz) { ?> 157 <div class="tz-table-element"> 158 <strong> 159 <?= $tz === 'UTC' ? 'UTC' : str_replace('_', ' ', explode('/', $tz, 2)[1]) . ' (' . getOffset($tz) . ')' ?> 160 </strong> 161 <br/> 162 <span><?= toTZ($dt, $tz) ?></span> 163 </div> 164<?php } ?> 165 </div> 166 167 <div class="share"> 168 <button type="button" onclick="shareURL()">Share</button> 169 <input type="hidden" id="url" value="https://tz.kdy.ch<?= $_SERVER['REQUEST_URI'] ?>"/> 170 </div> 171 172 <script> 173 // Make our day.js object 174 const djs = dayjs('<?= $dt->format('c') ?>') 175 176 // Use the browser's formating to local time 177 const localDT = djs.toDate().toLocaleString(undefined, { 178 dateStyle: 'full', 179 timeStyle: 'short' 180 }) 181 182 // Get the relative time 183 const relative = djs.fromNow() 184 185 // Display 186 document.querySelector('.local').innerHTML = localDT + '<br/><span>(' + relative + ')</span>' 187 188 // Enable share button 189 document.querySelector('.share').style.display = 'block' 190 191 // Our share URL function 192 function shareURL () { 193 if (navigator.canShare) { 194 // Use native navigator share 195 navigator.share({ 196 url: 'https://tz.kdy.ch<?= $_SERVER['REQUEST_URI'] ?>' 197 }) 198 } else if (navigator.clipboard) { 199 // Use Clipboard API 200 navigator.clipboard.writeText('https://tz.kdy.ch<?= $_SERVER['REQUEST_URI'] ?>') 201 .then(() => { 202 alert('URL copied to clipboard!') 203 }) 204 .catch((err) => { 205 console.log('Clipboard API failed, fallback.', err) 206 // Use old input copy trick 207 commandCopy() 208 }) 209 } else { 210 // Use old input copy trick 211 commandCopy() 212 } 213 } 214 215 // Copy content from the hidden input 216 function commandCopy () { 217 shareTextInput = document.querySelector('#url') 218 shareTextInput.focus() 219 shareTextInput.select() 220 221 if (document.execCommand('copy')) { 222 alert('URL copied to clipboard!') 223 } else { 224 // Fallback to prompt with URL 225 prompt('Share this URL:', document.querySelector('#url').value) 226 } 227 } 228 </script> 229<?php } else { ?> 230 <p> 231 Fill out this form to make a page that shows a set date and time in the visitor's time zone.<br/> 232 It will also show the time in other popular time zones. 233 </p> 234 235 <form action="/" method="post"> 236 <label for="datetime">Date and Time:</label>&nbsp; 237 <input required type="datetime-local" id="datetime" name="datetime"/> 238 239 <br/> 240 241 <label for="timezone">Time Zone:</label>&nbsp; 242 <select required id="timezone" name="timezone"> 243 <option selected disabled>(pick one)</option> 244 <optgroup label="Popular"> 245<?php foreach($popTZ as $tz) { ?> 246 <option value="<?= $tz ?>"> 247 <?= str_replace('_', ' ', $tz) ?> (<?= getOffset($tz) ?>) 248 </option> 249<?php } ?> 250 </optgroup> 251 <optgroup label="All"> 252<?php foreach(timezone_identifiers_list(DateTimeZone::ALL) as $tz) { ?> 253 <option value="<?= $tz ?>"> 254 <?= str_replace('_', ' ', $tz) ?> (<?= getOffset($tz) ?>) 255 </option> 256<?php } ?> 257 </optgroup> 258 </select> 259 260 <br/> 261 262 <button type="submit">Submit</button> 263 </form> 264 265 <script> 266 // Get a date object 267 const dt = new Date() 268 269 // Set the user's offset and enable option 270 const userEl = document.querySelector('option[disabled]') 271 userEl.value = dt.getTimezoneOffset() * -1 272 userEl.innerHTML = '(use my timezone)' 273 userEl.disabled = false 274 userEl.selected = true 275 </script> 276<?php } ?> 277 </main> 278 279 <footer> 280 &copy; <?= date('Y') ?> Kody - Made with 🍮<br/> 281 <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) 282 </footer> 283</body> 284</html>