I decided to mimic the macroable trait and create something similar
namespace App\Concerns\Models;
use Carbon\Exceptions\InvalidFormatException;
use Illuminate\Support\Carbon;
use Str;
trait HasDateFunctions
{
protected $dateMacros = [];
public function initializeHasDateFunctions()
{
$dateAttributes = $this->getDates();
foreach ($this->casts as $attribute => $type) {
if (Str::contains($type, ['date', 'datetime', 'timestamp'])) {
$dateAttributes[] = $attribute;
}
}
foreach ($dateAttributes as $dateAttribute) {
$this->dateMacros[Str::camel($dateAttribute)] = function($timezone = null) use ($dateAttribute) {
if (auth()->check()) {
$timezone ??= auth()->user()->timezone;
}
else {
$timezone ??= config('app.timezone');
}
try {
return Carbon::parse($this->{$dateAttribute})->timezone($timezone);
}
catch (InvalidFormatException $e) {
return $this->{$dateAttribute};
}
};
}
}
public function __call($method, $parameters)
{
if (array_key_exists($method, $this->dateMacros)) {
return $this->dateMacros[$method](...$parameters);
}
return parent::__call($method, $parameters);
}
}
So now I can use
$user = User::find(1);
$user->valid_from->format('Y-m-d H:i:s');//this is in UTC from the app config
$user->validFrom('Europe/Athens')->format('Y-m-d H:i:s');//this is in Athens timezone