#PHP#Laravel#Carbon#createFromTimestamp

最近碰到 Laravel 11 的專案,突然發現 createFromTimestamp toDateTimeString 的結果變為 UTC+0。

<?php
use Carbon\Carbon;

$now = Carbon::now();

// 1745305106
$timestamp = $now->timestamp;

// 2025-04-22 14:58:26
$now->toDateTimeString();

// 2025-04-22 06:58:26
Carbon::createFromTimestamp($timestamp)->toDateTimeString();

立刻去檢查了 config/app.phptimezoneenv,發現確實有設定 APP_TIMEZONE=Asia/Taipei, 往下挖掘了新舊專案的底層 Carbon Source Code 後發現原來是 Laravel 11 已經將 Carbon 從 2 升級到 3 (https://laravel.com/docs/11.x/upgrade#carbon-3),而 Carbon 3 的 createFromTimestamp 如果不帶入 Timezone 將會預設使用 UTC+0,與 Carbon 2 如果不帶入 Timezone 將會抓取 date_default_timezone_get() 的方式大不相同。

查了一下 Github 上也有 Issues 討論: https://github.com/briannesbitt/Carbon/issues/2948

可以看到在這個 commit 中將如果為 null 預設抓取 date_default_timezone_get 的程式碼移除了。

- 	protected static function getDateTimeZoneNameFromMixed($timezone): string
+ 	protected static function getDateTimeZoneNameFromMixed(string|int|float $timezone): string
  	{
-		if ($timezone === null) {
-			return date_default_timezone_get();
-		}

所以未來要使用 createFromTimestamp 時就必須帶入 timezone 參數。

<?php
use Carbon\Carbon;

Carbon::createFromTimestamp($timestamp, config('app.timezone'));

即便官方文件後來有補上此說明,但因為 $timezone 參數可為 null,所以對 Unit Test 沒完整判斷 assertEquals的系統將會沒有症狀的出現 Bug,設為必填至少會有 ArgumentCountError ,個人覺得第二參數設為必填較佳。

<?php
/**
 * Create a Carbon instance from a timestamp and set the timezone (UTC by default).
 *
 * Timestamp input can be given as int, float or a string containing one or more numbers.
 */
public static function createFromTimestamp(
    float|int|string $timestamp,
    DateTimeZone|string|int|null $timezone = null,
): static {
    $date = static::createFromTimestampUTC($timestamp);

    return $timezone === null ? $date : $date->setTimezone($timezone);
}