我是廣告,點擊一下吧!
標籤
#Flutter (15) 、 #PHP (9) 、 #Laravel (7) 、 #Dart (5) 、 #MySQL (5) 、 #Mac (4) 、 #VS Code (2) 、 #IDE (2) 、 #List (2) 、 #Android (2) 、 #Carbon (2) 、 #Linux (2) 、 #Shell Script (2) 、 #MySQL 效能 (1) 、 #Pagination (1) 、 #Cursor Pagination (1) 、 #LaTeX (1) 、 #個人空間 (1) 、 #Android Splash Screen (1) 、 #createFromTimestamp (1) 、 #資安 (1) 、 #Google Maps Static API (1) 、 #Mac M1 (1) 、 #floorMonth (1) 、 #subMonthNoOverflow (1) 、 #addMonthNoOverflow (1) 、 #subMonth (1) 、 #addMonth (1) 、 #keytool (1) 、 #Play App Signing (1)最近碰到 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.php
的 timezone
跟 env
,發現確實有設定 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);
}
Carbon 是 PHP/Laravel 非常實用的一個時間套件,但使用時有遇到神奇的坑。
當想拿到下個月的月份時會很直覺的寫成
$nextMonth = Carbon::parse('2022-01-31')->addMonth()->month;
dd($nextMonth);
但上述程式碼執行後會得到 3,WTF 一月的下個月是三月?
原因是因為他底層是使用 strtotime
,2022-01-31 加一個月就是 2022-02-31,由於二月只有 28 天所以會 Overflow,變成 02/28 加三天 03/03,同理 sub 也會出現 03/31 的上個月是 3 月的問題。
在純 PHP 使用 strtotime
的話可以使用 first day of 解決,在 Carbon 中已經幫你處理好了,使用 NoOverflow 系列 (addMonthNoOverflow
、subMonthNoOverflow
) 就可避免天數 Overflow。
dump(Carbon::parse('2022-01-31')->addMonthNoOverflow()->month); // 2
dump(Carbon::parse('2022-03-31')->subMonthNoOverflow()->month); // 2
floorMonth 理應是以每個月當區間取 floor ,不設定 precision 的話就是類似 startOfMonth
的功能 取得當月第一天的凌晨,但卻在大月最後一天時會異常,取得到的卻是下個月的第一天凌晨。
不過這 bug 在 2.58.0 版已經被修正了:https://github.com/briannesbitt/Carbon/pull/2583/commits/3ff0d04b81729647f108435cac7ce05e03b6b979
for ($i = 1; $i <= 12; $i++) {
$dt = Carbon::parse("2022-$i-01")->endOfMonth()->floorMonth();
dump($dt->toDateString());
}
output
"2022-02-01"
"2022-02-01"
"2022-04-01"
"2022-04-01"
"2022-06-01"
"2022-06-01"
"2022-08-01"
"2022-09-01"
"2022-09-01"
"2022-11-01"
"2022-11-01"
"2023-01-01"