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) { 9 $tz = new DateTimeZone($tz); 10 return $dt->setTimezone($tz)->format('Y-m-d H:i'); 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]; 23 24// Micro-routing 25if (isset($_POST['datetime']) && isset($_POST['timezone'])) { 26 // -- Redirect to submitted date 27 $postTZ = $_POST['timezone']; 28 29 // Handle if timezone is an offset 30 if (is_numeric($_POST['timezone'])) { 31 $isNeg = str_starts_with($postTZ, '-'); 32 $h = str_pad(abs(intdiv($postTZ, 60)), 2, '0', STR_PAD_LEFT); 33 $m = str_pad($postTZ % 60, 2, '0', STR_PAD_LEFT); 34 $postTZ = ($isNeg ? '-' : '+') . $h . $m; 35 } 36 37 // Make our date object 38 $dateObj = new DateTime($_POST['datetime'], new DateTimeZone($postTZ)); 39 40 // Redirect, with the "+" replaced by "_" 41 header('Location: /' . str_replace('+', '_', $dateObj->format('c'))); 42 exit; 43} else if ($_SERVER['REQUEST_URI'] !== '/') { 44 // Remove leading "/" 45 $req = substr($_SERVER['REQUEST_URI'], 1); 46 47 // -- Show date infos 48 // First check if date is following the format 49 $re = '/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[\_|-]\d{2}:\d{2}$/'; 50 if (!preg_match($re, $req)) { 51 http_response_code(400); 52 $error = 'Date is not valid or wrong format.'; 53 } else { 54 // Make our date object, convert back "_" to "+" 55 $dt = new DateTime(str_replace('_', '+', $req)); 56 } 57} 58?> 59<!DOCTYPE html> 60<html lang="en"> 61<head> 62 <meta charset="UTF-8"> 63 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 64 <title>Time Zones are hard<?= isset($_GET['date']) ? ' - ' . $_GET['date'] : '' ?></title> 65 <link rel="stylesheet" href="/css/styles.css"> 66<?php if (isset($dt)) { ?> 67 <meta name="robots" content="noindex,noarchive"> 68 <script src="/js/dayjs.min.js"></script> 69 <script src="/js/relativeTime.js"></script> 70 <script> 71 dayjs.extend(window.dayjs_plugin_relativeTime) 72 </script> 73<?php } ?> 74</head> 75<body> 76 <header> 77 <h1><a href="/">Time Zones are hard</a></h1> 78 </header> 79 80 <main> 81<?php if (isset($error)) { ?> 82 <h2 class="error">Error: <?= $error ?></h2> 83 <form action="/" method="post"> 84 <button type="submit">Go back home</button> 85 </form> 86<?php } else if (isset($dt)) { ?> 87 <h2 class="local"> 88 <noscript>[Sorry, you'll need to allow javascript so I can use your local time]</noscript> 89 </h2> 90 91 <br/> 92 93 <h3>In popular time zones</h3> 94 <div class="tz-table"> 95<?php foreach($popTZ as $tz) { ?> 96 <div> 97 <strong> 98 <?= str_replace('_', ' ', explode('/', $tz, 2)[1]) ?> (<?= getOffset($tz) ?>) 99 </strong> 100 <br/> 101 <span><?= toTZ($dt, $tz) ?></span> 102 </div> 103<?php } ?> 104 </div> 105 106 <script> 107 // Make our day.js object 108 const djs = dayjs('<?= $dt->format('c') ?>') 109 110 // Use the browser's formating to local time 111 const localDT = djs.toDate().toLocaleString(undefined, { 112 dateStyle: 'full', 113 timeStyle: 'short' 114 }) 115 116 // Get the relative time 117 const relative = djs.fromNow() 118 119 // Display 120 document.querySelector('.local').innerHTML = localDT + '<br/><span>(' + relative + ')</span>' 121 </script> 122<?php } else { ?> 123 <p> 124 Fill out this form to make a page that shows a set date and time in the visitor's time zone. 125 It will also show the time in other popular time zones. 126 </p> 127 128 <form action="/" method="post"> 129 <label for="datetime">Date and Time:</label>&nbsp; 130 <input required type="datetime-local" id="datetime" name="datetime" /> 131 132 <br/> 133 134 <label for="timezone">Time Zone:</label>&nbsp; 135 <select required id="timezone" name="timezone"> 136 <option selected disabled>(pick one)</option> 137 <optgroup label="Popular"> 138<?php foreach($popTZ as $tz) { ?> 139 <option value="<?= $tz ?>"> 140 <?= str_replace('_', ' ', $tz) ?> (<?= getOffset($tz) ?>) 141 </option> 142<?php } ?> 143 </optgroup> 144 <optgroup label="All"> 145<?php foreach(timezone_identifiers_list(DateTimeZone::ALL) as $tz) { ?> 146 <option value="<?= $tz ?>"> 147 <?= str_replace('_', ' ', $tz) ?> (<?= getOffset($tz) ?>) 148 </option> 149<?php } ?> 150 </optgroup> 151 </select> 152 153 <br/> 154 155 <button type="submit">Submit</button> 156 </form> 157 158 <script> 159 // Get a date object 160 const dt = new Date() 161 162 // Set the user's offset and enable option 163 const userEl = document.querySelector('option[disabled]') 164 userEl.value = dt.getTimezoneOffset() * -1 165 userEl.innerHTML = '(use my timezone)' 166 userEl.disabled = false 167 userEl.selected = true 168 </script> 169<?php } ?> 170 </main> 171 172 <footer> 173 &copy; <?= date('Y') ?> Kody - Made with 🍮 174 </footer> 175</body> 176</html>