mirror of
https://github.com/BookStackApp/BookStack.git
synced 2026-05-04 18:08:46 +03:00
Timezones: Seperated out store & display timezones to two options
This commit is contained in:
@@ -36,10 +36,14 @@ APP_LANG=en
|
||||
# APP_LANG will be used if such a header is not provided.
|
||||
APP_AUTO_LANG_PUBLIC=true
|
||||
|
||||
# Application timezone
|
||||
# Used where dates are displayed such as on exported content.
|
||||
# Application timezones
|
||||
# The first option is used to determine what timezone is used for date storage.
|
||||
# Leaving that as "UTC" is advised.
|
||||
# The second option is used to set the timezone which will be used for date
|
||||
# formatting and display. This defaults to the "APP_TIMEZONE" value.
|
||||
# Valid timezone values can be found here: https://www.php.net/manual/en/timezones.php
|
||||
APP_TIMEZONE=UTC
|
||||
APP_DISPLAY_TIMEZONE=UTC
|
||||
|
||||
# Application theme
|
||||
# Used to specific a themes/<APP_THEME> folder where BookStack UI
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace BookStack\App\Providers;
|
||||
|
||||
use BookStack\Entities\BreadcrumbsViewComposer;
|
||||
use BookStack\Util\DateFormatter;
|
||||
use Illuminate\Pagination\Paginator;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\Facades\View;
|
||||
@@ -10,6 +11,15 @@ use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class ViewTweaksServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function register()
|
||||
{
|
||||
$this->app->singleton(DateFormatter::class, function ($app) {
|
||||
return new DateFormatter(
|
||||
$app['config']->get('app.display_timezone'),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*/
|
||||
@@ -21,6 +31,9 @@ class ViewTweaksServiceProvider extends ServiceProvider
|
||||
// View Composers
|
||||
View::composer('entities.breadcrumbs', BreadcrumbsViewComposer::class);
|
||||
|
||||
// View Globals
|
||||
View::share('dates', $this->app->make(DateFormatter::class));
|
||||
|
||||
// Custom blade view directives
|
||||
Blade::directive('icon', function ($expression) {
|
||||
return "<?php echo (new \BookStack\Util\SvgIcon($expression))->toHtml(); ?>";
|
||||
|
||||
@@ -70,8 +70,8 @@ return [
|
||||
// A list of the sources/hostnames that can be reached by application SSR calls.
|
||||
// This is used wherever users can provide URLs/hosts in-platform, like for webhooks.
|
||||
// Host-specific functionality (usually controlled via other options) like auth
|
||||
// or user avatars for example, won't use this list.
|
||||
// Space seperated if multiple. Can use '*' as a wildcard.
|
||||
// or user avatars, for example, won't use this list.
|
||||
// Space separated if multiple. Can use '*' as a wildcard.
|
||||
// Values will be compared prefix-matched, case-insensitive, against called SSR urls.
|
||||
// Defaults to allow all hosts.
|
||||
'ssr_hosts' => env('ALLOWED_SSR_HOSTS', '*'),
|
||||
@@ -80,8 +80,10 @@ return [
|
||||
// Integer value between 0 (IP hidden) to 4 (Full IP usage)
|
||||
'ip_address_precision' => env('IP_ADDRESS_PRECISION', 4),
|
||||
|
||||
// Application timezone for back-end date functions.
|
||||
// Application timezone for stored date/time values.
|
||||
'timezone' => env('APP_TIMEZONE', 'UTC'),
|
||||
// Application timezone for displayed date/time values in the UI.
|
||||
'display_timezone' => env('APP_DISPLAY_TIMEZONE', env('APP_TIMEZONE', 'UTC')),
|
||||
|
||||
// Default locale to use
|
||||
// A default variant is also stored since Laravel can overwrite
|
||||
|
||||
25
app/Util/DateFormatter.php
Normal file
25
app/Util/DateFormatter.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Util;
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
||||
class DateFormatter
|
||||
{
|
||||
public function __construct(
|
||||
protected string $displayTimezone,
|
||||
) {
|
||||
}
|
||||
|
||||
public function isoWithTimezone(Carbon $date): string
|
||||
{
|
||||
$withDisplayTimezone = $date->clone()->setTimezone($this->displayTimezone);
|
||||
|
||||
return $withDisplayTimezone->format('Y-m-d H:i:s T');
|
||||
}
|
||||
|
||||
public function relative(Carbon $date): string
|
||||
{
|
||||
return $date->diffForHumans();
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@
|
||||
@icon('star')
|
||||
<div>
|
||||
{!! trans('entities.meta_created_name', [
|
||||
'timeLength' => '<span title="'.$entity->created_at->toDayDateTimeString().'">'.$entity->created_at->diffForHumans() . '</span>',
|
||||
'timeLength' => '<span title="'. $dates->isoWithTimezone($entity->created_at) .'">'. $dates->relative($entity->created_at) . '</span>',
|
||||
'user' => "<a href='{$entity->createdBy->getProfileUrl()}'>".e($entity->createdBy->name). "</a>"
|
||||
]) !!}
|
||||
</div>
|
||||
@@ -39,7 +39,7 @@
|
||||
@else
|
||||
<div class="entity-meta-item">
|
||||
@icon('star')
|
||||
<span title="{{$entity->created_at->toDayDateTimeString()}}">{{ trans('entities.meta_created', ['timeLength' => $entity->created_at->diffForHumans()]) }}</span>
|
||||
<span title="{{ $dates->isoWithTimezone($entity->created_at) }}">{{ trans('entities.meta_created', ['timeLength' => $dates->relative($entity->created_at)]) }}</span>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
@icon('edit')
|
||||
<div>
|
||||
{!! trans('entities.meta_updated_name', [
|
||||
'timeLength' => '<span title="' . $entity->updated_at->toDayDateTimeString() .'">' . $entity->updated_at->diffForHumans() .'</span>',
|
||||
'timeLength' => '<span title="' . $dates->isoWithTimezone($entity->updated_at) .'">' . $dates->relative($entity->updated_at) .'</span>',
|
||||
'user' => "<a href='{$entity->updatedBy->getProfileUrl()}'>".e($entity->updatedBy->name). "</a>"
|
||||
]) !!}
|
||||
</div>
|
||||
@@ -56,7 +56,7 @@
|
||||
@elseif (!$entity->isA('revision'))
|
||||
<div class="entity-meta-item">
|
||||
@icon('edit')
|
||||
<span title="{{ $entity->updated_at->toDayDateTimeString() }}">{{ trans('entities.meta_updated', ['timeLength' => $entity->updated_at->diffForHumans()]) }}</span>
|
||||
<span title="{{ $dates->isoWithTimezone($entity->updated_at) }}">{{ trans('entities.meta_updated', ['timeLength' => $dates->relative($entity->updated_at)]) }}</span>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
|
||||
37
tests/Util/DateFormatterTest.php
Normal file
37
tests/Util/DateFormatterTest.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Util;
|
||||
|
||||
use BookStack\Util\DateFormatter;
|
||||
use Carbon\Carbon;
|
||||
use Tests\TestCase;
|
||||
|
||||
class DateFormatterTest extends TestCase
|
||||
{
|
||||
public function test_iso_with_timezone_alters_from_stored_to_display_timezone()
|
||||
{
|
||||
$formatter = new DateFormatter('Europe/London');
|
||||
$dateTime = new Carbon('2020-06-01 12:00:00', 'UTC');
|
||||
|
||||
$result = $formatter->isoWithTimezone($dateTime);
|
||||
$this->assertEquals('2020-06-01 13:00:00 BST', $result);
|
||||
}
|
||||
|
||||
public function test_iso_with_timezone_works_from_non_utc_dates()
|
||||
{
|
||||
$formatter = new DateFormatter('Asia/Shanghai');
|
||||
$dateTime = new Carbon('2025-06-10 15:25:00', 'America/New_York');
|
||||
|
||||
$result = $formatter->isoWithTimezone($dateTime);
|
||||
$this->assertEquals('2025-06-11 03:25:00 CST', $result);
|
||||
}
|
||||
|
||||
public function test_relative()
|
||||
{
|
||||
$formatter = new DateFormatter('Europe/London');
|
||||
$dateTime = (new Carbon('now', 'UTC'))->subMinutes(50);
|
||||
|
||||
$result = $formatter->relative($dateTime);
|
||||
$this->assertEquals('50 minutes ago', $result);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user