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>
130 <input required type="datetime-local" id="datetime" name="datetime" />
131
132 <br/>
133
134 <label for="timezone">Time Zone:</label>
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 © <?= date('Y') ?> Kody - Made with 🍮
174 </footer>
175</body>
176</html>