mirror of
https://github.com/Shadowss/TravianZ.git
synced 2026-06-27 16:14:30 +00:00
feat(preferences): apply time preference (timezone + date format) [#198]
Implement the "Time Preference" section of the player preferences (issue #198), which until now was stored in DB but never applied in-game. procMtime() — the central date/time formatter used across reports, messages, notices and troop movements — now honours the per-user settings: - timezone: the stored value is mapped to a DateTimeZone, either a named DST-aware region (Europe, UK, Turkey, Kolkata, Bangkok, New York, Chicago, New Zealand) or a fixed UTC offset (general zones); timestamps are converted before formatting. - tformat: drives the date layout and 12h/24h clock (0=EU dd.mm.yy 24h, 1=US mm/dd/yy 12h, 2=UK dd/mm/yy 12h, 3=ISO yy/mm/dd 24h). tformat 0 keeps the previous EU output unchanged. The today/yesterday sentinels are preserved (callers compare them) and are computed in the player's timezone. The time-only path (mode 9) stays 24h H:i:s because it feeds the live JS clock counters (unx.js), which parse "H:i:s" by splitting on ":". A few alliance/report/farm-list rows that combined procMtime's day part with a raw date('H:i') now take the time from the same procMtime result, to avoid mixing timezones. A second "local time" clock is shown next to the server-time one: a small vanilla-JS snippet emitted once from menu.tpl finds the visible #tp1 clock and appends a row ticking in the player's timezone (Date.now() + UTC offset), independent of the browser timezone and of the unx.js counters. It aligns the value under the server time, is skipped when the player's timezone matches the server's, and lets the clock box grow so the extra row fits the frame. Adds the LOCAL_TIME string (EN/FR/RO). When no player session is available (admin / pre-login) procMtime falls back to the server timezone and EU layout, i.e. the previous behaviour. Removes the last "not coded yet" tag from the preferences form. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
committed by
Catalin Novgorodschi
parent
af1b8c7ce7
commit
63c94fa961
+130
-26
@@ -136,45 +136,149 @@ class MyGenerator
|
||||
}
|
||||
|
||||
/**
|
||||
* Format timestamp into readable date/time
|
||||
* Resolve a player's "timezone" preference (issue #198) to a DateTimeZone.
|
||||
*
|
||||
* The preference form exposes two kinds of values:
|
||||
* - 8 named, DST-aware regions (Europe, UK, Turkey, Kolkata, Bangkok,
|
||||
* New York, Chicago, New Zealand) stored as their legacy magic ids;
|
||||
* - fixed UTC offsets (general zones): 0..11 => UTC+1..UTC+12,
|
||||
* 12..22 => UTC-11..UTC-1, 23 => UTC.
|
||||
* Falls back to the server's default timezone when the value is unknown.
|
||||
*
|
||||
* @param int|null $tz Stored timezone preference value (null = server time).
|
||||
* @return DateTimeZone
|
||||
*/
|
||||
private function resolveUserTimeZone($tz)
|
||||
{
|
||||
if ($tz === null) {
|
||||
return new DateTimeZone(date_default_timezone_get());
|
||||
}
|
||||
|
||||
$tz = (int) $tz;
|
||||
|
||||
$named = [
|
||||
495 => 'Europe/Berlin',
|
||||
99 => 'Europe/London',
|
||||
492 => 'Europe/Istanbul',
|
||||
328 => 'Asia/Kolkata',
|
||||
345 => 'Asia/Bangkok',
|
||||
257 => 'America/New_York',
|
||||
189 => 'America/Chicago',
|
||||
474 => 'Pacific/Auckland',
|
||||
];
|
||||
|
||||
try {
|
||||
if (isset($named[$tz])) {
|
||||
return new DateTimeZone($named[$tz]);
|
||||
}
|
||||
|
||||
if ($tz === 23) {
|
||||
$offset = 0;
|
||||
} elseif ($tz >= 0 && $tz <= 11) {
|
||||
$offset = $tz + 1;
|
||||
} elseif ($tz >= 12 && $tz <= 22) {
|
||||
$offset = $tz - 23;
|
||||
} else {
|
||||
$offset = 0;
|
||||
}
|
||||
|
||||
$sign = $offset >= 0 ? '+' : '-';
|
||||
return new DateTimeZone(sprintf('%s%02d:00', $sign, abs($offset)));
|
||||
} catch (\Exception $e) {
|
||||
return new DateTimeZone(date_default_timezone_get());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format an absolute timestamp into a readable date/time, honouring the
|
||||
* current player's time preference (issue #198): timezone conversion +
|
||||
* date layout / 12h-24h clock derived from the "tformat" setting.
|
||||
*
|
||||
* tformat 0 => EU dd.mm.yy 24h (legacy default, unchanged)
|
||||
* tformat 1 => US mm/dd/yy 12h
|
||||
* tformat 2 => UK dd/mm/yy 12h
|
||||
* tformat 3 => ISO yy/mm/dd 24h
|
||||
*
|
||||
* When no player session is available (e.g. admin / pre-login) it falls
|
||||
* back to the server timezone and the EU layout, i.e. the previous output.
|
||||
* The "today"/"yesterday" sentinels are kept verbatim (callers compare them).
|
||||
*
|
||||
* @param int $time Unix timestamp.
|
||||
* @param int $pref Pass 9 to get the time-of-day string only.
|
||||
* @return array|string [$day, $time] normally, or the time string when $pref == 9.
|
||||
*/
|
||||
public function procMtime($time, $pref = 3)
|
||||
{
|
||||
$time = (int) $time;
|
||||
$time += 0; // placeholder for timezone adjustments
|
||||
global $session;
|
||||
|
||||
$today = date('d', time()) - 1;
|
||||
$time = (int) $time;
|
||||
|
||||
if (date('Ymd', time()) == date('Ymd', $time)) {
|
||||
$day = "today";
|
||||
} elseif ($today == date('d', $time)) {
|
||||
$day = "yesterday";
|
||||
} else {
|
||||
switch ($pref) {
|
||||
case 1:
|
||||
$day = date("m/j/y", $time);
|
||||
break;
|
||||
case 2:
|
||||
$day = date("j/m/y", $time);
|
||||
break;
|
||||
case 3:
|
||||
$day = date("j.m.y", $time);
|
||||
break;
|
||||
default:
|
||||
$day = date("y/m/j", $time);
|
||||
break;
|
||||
}
|
||||
$tzPref = (isset($session) && isset($session->userinfo['timezone']))
|
||||
? $session->userinfo['timezone'] : null;
|
||||
$tformat = (isset($session) && isset($session->userinfo['tformat']))
|
||||
? (int) $session->userinfo['tformat'] : 0;
|
||||
|
||||
$zone = $this->resolveUserTimeZone($tzPref);
|
||||
|
||||
$dt = new DateTime('@' . $time);
|
||||
$dt->setTimezone($zone);
|
||||
$now = new DateTime('now', $zone);
|
||||
|
||||
// date layout + clock (12h/24h) per the tformat preference
|
||||
switch ($tformat) {
|
||||
case 1: $dateFmt = "m/d/y"; $timeFmt = "h:i:s A"; break;
|
||||
case 2: $dateFmt = "d/m/y"; $timeFmt = "h:i:s A"; break;
|
||||
case 3: $dateFmt = "y/m/d"; $timeFmt = "H:i:s"; break;
|
||||
default: $dateFmt = "j.m.y"; $timeFmt = "H:i:s"; break; // legacy EU output, unchanged
|
||||
}
|
||||
|
||||
$new = date("H:i:s", $time);
|
||||
|
||||
// Time-only mode (9) feeds the live JS clock counters (unx.js ob()/rb()),
|
||||
// which parse "H:i:s" by splitting on ":" — keep 24h here regardless of
|
||||
// the 12h/24h tformat, while still honouring the timezone conversion.
|
||||
if ($pref == 9) {
|
||||
return $new;
|
||||
return $dt->format("H:i:s");
|
||||
}
|
||||
|
||||
$new = $dt->format($timeFmt);
|
||||
|
||||
$yesterday = (clone $now)->modify('-1 day');
|
||||
|
||||
if ($dt->format('Ymd') == $now->format('Ymd')) {
|
||||
$day = "today";
|
||||
} elseif ($dt->format('Ymd') == $yesterday->format('Ymd')) {
|
||||
$day = "yesterday";
|
||||
} else {
|
||||
$day = $dt->format($dateFmt);
|
||||
}
|
||||
|
||||
return [$day, $new];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the current player's timezone preference (issue #198), shared by
|
||||
* the "local time" header clock helpers below.
|
||||
*/
|
||||
private function currentPlayerZone()
|
||||
{
|
||||
global $session;
|
||||
|
||||
$tzPref = (isset($session) && isset($session->userinfo['timezone']))
|
||||
? $session->userinfo['timezone'] : null;
|
||||
|
||||
return $this->resolveUserTimeZone($tzPref);
|
||||
}
|
||||
|
||||
/**
|
||||
* Current UTC offset (in seconds, DST-aware) of the player's timezone.
|
||||
* Feeds the live "local time" clock in the page header (issue #198).
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function userTimeZoneOffset()
|
||||
{
|
||||
return (new DateTime('now', $this->currentPlayerZone()))->getOffset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert map coordinates to base ID
|
||||
*/
|
||||
|
||||
@@ -152,6 +152,7 @@ tz_def('HI', 'HI');
|
||||
tz_def('P_IN', 'in');
|
||||
tz_def('MS', 'ms');
|
||||
tz_def('SERVER_TIME', 'Server time:');
|
||||
tz_def('LOCAL_TIME', 'Local time:');
|
||||
tz_def('REMAINING_GOLD', 'Remaining gold');
|
||||
|
||||
// HEADER && MENU && Messages && Reports
|
||||
|
||||
@@ -149,6 +149,7 @@ define('HI', 'Bonjour');
|
||||
define('P_IN', 'dans');
|
||||
define('MS', 'ms');
|
||||
define('SERVER_TIME', 'Heure du serveur :');
|
||||
define('LOCAL_TIME', 'Heure locale :');
|
||||
define('REMAINING_GOLD', 'Or restant');
|
||||
|
||||
// HEADER && MENU && Messages && Reports
|
||||
|
||||
@@ -149,6 +149,7 @@ define('HI', 'Salut');
|
||||
define('P_IN', 'în');
|
||||
define('MS', 'ms');
|
||||
define('SERVER_TIME', 'Ora serverului:');
|
||||
define('LOCAL_TIME', 'Ora locală:');
|
||||
define('REMAINING_GOLD', 'Aur rămas');
|
||||
|
||||
// HEADER && MENU && Messages && Reports
|
||||
|
||||
@@ -140,7 +140,7 @@ if (!$sql || mysqli_num_rows($sql) == 0) {
|
||||
$outputList .= "<td class=\"al\">" . $allyLink . "</td>";
|
||||
|
||||
// date column
|
||||
$outputList .= "<td class=\"dat\">" . $date[0] . " " . date('H:i', $time) . "</td>";
|
||||
$outputList .= "<td class=\"dat\">" . $date[0] . " " . substr($date[1], 0, 5) . "</td>";
|
||||
|
||||
$outputList .= "</tr>";
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ if ($f === 31 || $f === 32) {
|
||||
$outputList .= "</a></div></td>";
|
||||
|
||||
$outputList .= "<td class=\"al\">" . $allyName . "</td>";
|
||||
$outputList .= "<td class=\"dat\">" . $date[0] . " " . date('H:i', $time) . "</td>";
|
||||
$outputList .= "<td class=\"dat\">" . $date[0] . " " . substr($date[1], 0, 5) . "</td>";
|
||||
|
||||
$outputList .= "</tr>";
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ function renderReports($database,$generator,$session,$d,$limit,$typeMap=null){
|
||||
$icon = '<img src="img/x.gif" class="iReport iReport'.$row['ntype'].'" title="'.$row['topic'].'">';
|
||||
}
|
||||
$date = $generator->procMtime($row['time']);
|
||||
echo '<tr><td>'.$icon.' <a href="berichte.php?id='.$row['id'].'">'.$date[0].' '.date('H:i',$row['time']).'</a></td></tr>';
|
||||
echo '<tr><td>'.$icon.' <a href="berichte.php?id='.$row['id'].'">'.$date[0].' '.substr($date[1],0,5).'</a></td></tr>';
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -435,9 +435,6 @@ if(isset($_POST['lang']))
|
||||
<tr>
|
||||
<th colspan="2">
|
||||
<?php echo TZ_TIME_PREFERENCE; ?>
|
||||
<span style="color:#999; font-weight:400; font-size:0.9em; font-style:italic; opacity:0.7;">
|
||||
<?php echo TZ_NOT_CODED_YET; ?>
|
||||
</span>
|
||||
</th>
|
||||
</tr>
|
||||
<tr><td colspan="2"><?php echo TZ_HERE_YOU_CAN_CHANGE_TRAVIAN_S_DISP; ?></td></tr>
|
||||
|
||||
@@ -231,7 +231,7 @@ if (mysqli_num_rows($getnotice) > 0) {
|
||||
|
||||
$date = $generator->procMtime($row2['time']);
|
||||
echo '<a href="berichte.php?id='.$row2['id'].'">'
|
||||
.$date[0]." ".date('H:i', $row2['time']).'</a>';
|
||||
.$date[0]." ".substr($date[1],0,5).'</a>';
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -242,6 +242,68 @@ $idUser = isset($_SESSION['id_user']) ? (int)$_SESSION['id_user'] : 0;
|
||||
|
||||
</div>
|
||||
|
||||
<?php
|
||||
/**
|
||||
* Live "local time" clock (issue #198): show a second clock next to the
|
||||
* server-time one, ticking in the player's chosen timezone. The server-time
|
||||
* block lives in each page's own footer (rendered after this menu), so we wait
|
||||
* for the DOM and target the last #tp1 (the visible one). Vanilla JS, driven by
|
||||
* Date.now() + the player's UTC offset, so it is independent of the browser
|
||||
* timezone and does not touch the unx.js tp+i counters (arrival timers).
|
||||
*
|
||||
* Skipped entirely when the player's timezone matches the server's, so no
|
||||
* redundant line is shown.
|
||||
*/
|
||||
$localOffset = (int) $generator->userTimeZoneOffset();
|
||||
$serverOffset = (int) date('Z');
|
||||
if ($localOffset !== $serverOffset):
|
||||
?>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var anchors = document.querySelectorAll('#tp1');
|
||||
if (!anchors.length) return;
|
||||
var tp = anchors[anchors.length - 1];
|
||||
var off = <?php echo $localOffset; ?> * 1000;
|
||||
var label = <?php echo json_encode(LOCAL_TIME); ?>;
|
||||
|
||||
var br = document.createElement('br');
|
||||
var lbl = document.createElement('span');
|
||||
lbl.appendChild(document.createTextNode(label + ' '));
|
||||
var val = document.createElement('span');
|
||||
val.className = 'b';
|
||||
|
||||
var parent = tp.parentNode, next = tp.nextSibling;
|
||||
parent.insertBefore(br, next);
|
||||
parent.insertBefore(lbl, next);
|
||||
parent.insertBefore(val, next);
|
||||
|
||||
// align the local-time value vertically under the server-time value
|
||||
var delta = tp.offsetLeft - val.offsetLeft;
|
||||
if (delta > 0) {
|
||||
lbl.style.display = 'inline-block';
|
||||
lbl.style.width = (lbl.offsetWidth + delta) + 'px';
|
||||
}
|
||||
|
||||
// make room for the extra line and lift the block so it stays in the frame
|
||||
var box = tp;
|
||||
while (box && box.id !== 'ltime') box = box.parentNode;
|
||||
if (box) {
|
||||
box.style.height = 'auto';
|
||||
var top = parseInt(window.getComputedStyle(box).top, 10);
|
||||
if (!isNaN(top)) box.style.top = (top - 8) + 'px';
|
||||
}
|
||||
|
||||
function p(n) { return n < 10 ? '0' + n : n; }
|
||||
function tick() {
|
||||
var d = new Date(Date.now() + off);
|
||||
val.innerHTML = p(d.getUTCHours()) + ':' + p(d.getUTCMinutes()) + ':' + p(d.getUTCSeconds());
|
||||
}
|
||||
tick();
|
||||
setInterval(tick, 1000);
|
||||
});
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
/**
|
||||
* Announcement screen
|
||||
|
||||
Reference in New Issue
Block a user