Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,26 @@ class User extends Authenticatable implements Commenter, HasName, HasAvatar
}
```

### Adjusting the display timezone

Comment timestamps are stored in the app default timezone, but can be displayed in a different one. Set the `timezone` config key for a fixed override, or wire a closure for per-request resolution (e.g. the authenticated user's timezone):

```php
// config/commentions.php
return [
// ...
'timezone' => 'America/Chicago',
];
```

```php
use Kirschbaum\Commentions\Config;

Config::resolveTimezoneUsing(fn () => auth()->user()?->timezone);
```

The closure result takes precedence; when it returns `null`, the `timezone` config value is used as a fallback. When neither yields a value, dates render in the storage timezone.

### Customizing TipTap Editor Styles

You can customize the TipTap editor CSS classes used using the `Config` class.
Expand Down
13 changes: 13 additions & 0 deletions config/commentions.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,19 @@
'allowed' => ['👍', '❤️', '😂', '😮', '😢', '🤔'],
],

/*
|--------------------------------------------------------------------------
| Display timezone
|--------------------------------------------------------------------------
|
| Timezone applied when rendering comment created/updated timestamps.
| When null, dates are rendered in the storage timezone (typically the
| app default). Set to an IANA name like "America/Chicago", or wire a
| per-user value via Config::resolveTimezoneUsing(fn () => auth()->user()?->timezone).
|
*/
'timezone' => null,

/*
|--------------------------------------------------------------------------
| Subscriptions
Expand Down
4 changes: 2 additions & 2 deletions src/Comment.php
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,12 @@ public function getParsedBody(): string

public function getCreatedAt(): DateTime|CarbonInterface
{
return $this->created_at;
return Config::applyTimezone($this->created_at);
}

public function getUpdatedAt(): DateTime|CarbonInterface
{
return $this->updated_at;
return Config::applyTimezone($this->updated_at);
}

public function reactions(): HasMany
Expand Down
37 changes: 37 additions & 0 deletions src/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

namespace Kirschbaum\Commentions;

use Carbon\CarbonInterface;
use Closure;
use Composer\InstalledVersions;
use DateTime;
use DateTimeZone;
use InvalidArgumentException;
use Kirschbaum\Commentions\Contracts\Commenter;

Expand All @@ -17,6 +20,8 @@ class Config

protected static ?Closure $resolveTipTapCssClasses = null;

protected static ?Closure $resolveTimezone = null;

public static function resolveAuthenticatedUserUsing(Closure $callback): void
{
static::$resolveAuthenticatedUser = $callback;
Expand Down Expand Up @@ -101,6 +106,38 @@ public static function getComponentPrefix(): string
return static::isLivewireV4() ? 'commentions.' : 'commentions::';
}

public static function resolveTimezoneUsing(?Closure $callback = null): void
{
static::$resolveTimezone = $callback;
}

public static function getTimezone(): ?string
{
if (static::$resolveTimezone instanceof Closure) {
return call_user_func(static::$resolveTimezone) ?? config('commentions.timezone');
}

return config('commentions.timezone');
}

public static function applyTimezone(DateTime|CarbonInterface $dt): DateTime|CarbonInterface
{
$tz = static::getTimezone();

if (blank($tz)) {
return $dt;
}

if ($dt instanceof CarbonInterface) {
return $dt->copy()->setTimezone($tz);
}

$cloned = clone $dt;
$cloned->setTimezone(new DateTimeZone($tz));

return $cloned;
}

public static function isLivewireV4(): bool
{
return version_compare(
Expand Down
4 changes: 2 additions & 2 deletions src/RenderableComment.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,12 @@ public function getParsedBody(): string

public function getCreatedAt(): DateTime|CarbonInterface
{
return $this->createdAt;
return Config::applyTimezone($this->createdAt);
}

public function getUpdatedAt(): DateTime|CarbonInterface
{
return $this->updatedAt;
return Config::applyTimezone($this->updatedAt);
}

public function getLabel(): ?string
Expand Down
106 changes: 106 additions & 0 deletions tests/TimezoneTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<?php

use Carbon\Carbon;
use Kirschbaum\Commentions\Comment as CommentModel;
use Kirschbaum\Commentions\Config;
use Kirschbaum\Commentions\RenderableComment;
use Tests\Models\Post;
use Tests\Models\User;

afterEach(function () {
config()->set('commentions.timezone', null);
Config::resolveTimezoneUsing(null);
});

test('comment dates are returned unmodified when no timezone is configured', function () {
config()->set('commentions.timezone', null);

$user = User::factory()->create();
$post = Post::factory()->create();
$comment = CommentModel::factory()->author($user)->commentable($post)->create([
'created_at' => Carbon::parse('2026-01-15 12:00:00', 'UTC'),
'updated_at' => Carbon::parse('2026-01-15 12:00:00', 'UTC'),
]);

expect($comment->getCreatedAt()->format('Y-m-d H:i:s'))
->toBe('2026-01-15 12:00:00');
});

test('comment dates are converted when a timezone is configured', function () {
config()->set('commentions.timezone', 'America/Chicago');

$user = User::factory()->create();
$post = Post::factory()->create();
$comment = CommentModel::factory()->author($user)->commentable($post)->create([
'created_at' => Carbon::parse('2026-01-15 12:00:00', 'UTC'),
'updated_at' => Carbon::parse('2026-01-15 12:00:00', 'UTC'),
]);

expect($comment->getCreatedAt()->format('Y-m-d H:i'))
->toBe('2026-01-15 06:00');
});

test('RenderableComment dates respect the timezone config', function () {
config()->set('commentions.timezone', 'Asia/Tokyo');

$renderable = new RenderableComment(
id: 1,
authorName: 'System',
body: 'Test',
createdAt: Carbon::parse('2026-01-15 12:00:00', 'UTC'),
updatedAt: Carbon::parse('2026-01-15 12:00:00', 'UTC'),
);

expect($renderable->getCreatedAt()->format('Y-m-d H:i'))
->toBe('2026-01-15 21:00');
});

test('resolveTimezoneUsing closure takes precedence over config', function () {
config()->set('commentions.timezone', 'UTC');
Config::resolveTimezoneUsing(fn () => 'Europe/London');

$user = User::factory()->create();
$post = Post::factory()->create();
$comment = CommentModel::factory()->author($user)->commentable($post)->create([
'created_at' => Carbon::parse('2026-06-15 12:00:00', 'UTC'),
'updated_at' => Carbon::parse('2026-06-15 12:00:00', 'UTC'),
]);

// London is BST (UTC+1) in June
expect($comment->getCreatedAt()->format('Y-m-d H:i'))
->toBe('2026-06-15 13:00');
});

test('applyTimezone does not mutate the source instance', function () {
config()->set('commentions.timezone', 'America/New_York');

$source = Carbon::parse('2026-01-15 12:00:00', 'UTC');
$converted = Config::applyTimezone($source);

expect($source->format('Y-m-d H:i'))->toBe('2026-01-15 12:00')
->and($converted->format('Y-m-d H:i'))->toBe('2026-01-15 07:00');
});

test('an empty-string timezone is treated as no timezone', function () {
Config::resolveTimezoneUsing(fn () => '');

$source = Carbon::parse('2026-01-15 12:00:00', 'UTC');

expect(Config::applyTimezone($source)->format('Y-m-d H:i'))
->toBe('2026-01-15 12:00');
});

test('resolveTimezoneUsing falls back to the config value when the closure returns null', function () {
config()->set('commentions.timezone', 'America/Chicago');
Config::resolveTimezoneUsing(fn () => null);

$user = User::factory()->create();
$post = Post::factory()->create();
$comment = CommentModel::factory()->author($user)->commentable($post)->create([
'created_at' => Carbon::parse('2026-01-15 12:00:00', 'UTC'),
'updated_at' => Carbon::parse('2026-01-15 12:00:00', 'UTC'),
]);

expect($comment->getCreatedAt()->format('Y-m-d H:i'))
->toBe('2026-01-15 06:00');
});
Loading