Minghsin University of Science and Technology
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try a Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

1

Local AI

--

Uedu Code

--

明新科大 AQI 70 24°C PM2.5 19
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 10

日期和時間

時間戳記、date 的格式、時區,以及 DateTime 的加減與日期差
Interaction實作 25 minute(s)

Learning Objectives

  • 說明 Unix 時間戳記是什麼,用 time()、mktime() 取得時間戳記,再用 date() 排成想要的格式
  • 說明同一個時間戳記在不同時區會顯示成不同的當地時間,並用 date_default_timezone_set() 設定時區
  • 用 strtotime() 解讀「+1 day」「next monday」這類描述,並檢查回傳 false 的情況
  • 分辨 DateTime 與 DateTimeImmutable:modify 會改到原物件,還是回傳新物件
  • 用 DateInterval 加減時間、用 diff() 算年齡與倒數天數,並避開「月底加一個月」的陷阱

網站到處都是時間

留言的發表時間、訂單的建立時間、報名截止前還剩幾天、會員今年幾歲……幾乎每個網站都要處理日期和時間。 日期看起來簡單,其實有不少陷阱:每個月的天數不同、有閏年、世界各地有時差,有些地方還有日光節約時間。 PHP 內建的日期函式與 DateTime 類別已經幫你處理好這些規則,你要學的是怎麼表示時間、怎麼換算、怎麼顯示。

電腦裡最常用的時間表示法是 Unix 時間戳記(Unix timestamp):從 1970-01-01 00:00:00 UTC 起算經過了幾秒,是一個整數。

時間(UTC) 時間戳記
1970-01-01 00:00:00 0
1970-01-02 00:00:00 86400(一天 = 24 × 60 × 60 秒)
2026-09-01 09:00:00 1788253200

時間戳記不受時區影響:地球上同一個瞬間,不論在臺北還是紐約,時間戳記都相同;只有「顯示成當地時間」時才需要時區。

CodeLab 的設定:真正的 PHP 每次執行,time() 都會拿到當下的時間。為了讓每個人看到的結果相同, CodeLab 的「現在」固定是 2026-09-01 09:00:00 UTC(星期二),預設時區是 UTC。在你自己的電腦上執行,結果會隨當下的時間改變。

時間戳記與 date()

date(格式, 時間戳記) 把時間戳記排成字串;第二個參數省略時用現在的時間。時間戳記是整數,所以「明天」可以直接加上一天的秒數(第 9 行)。mktime(時, 分, 秒, 月, 日, 年) 反過來,把年月日時分秒換成時間戳記,注意參數的順序是時分秒在前、月日年在後。

date() 的格式字元

date() 的格式字串裡,有特殊意義的英文字母會換成對應的值,其他字元(-、/、:、空白、中文)原樣保留。 常用的格式字元(以 2026-09-01 09:00 星期二為例):

字元 意思 範例
Y 四位數年份 2026
m 月,補零 09
n 月,不補零 9
d 日,補零 01
j 日,不補零 1
H 時,24 小時制,補零 09
i 分,補零 00
s 秒,補零 00
D 星期,三個字母 Tue
l 星期,英文全名(小寫的 L) Tuesday
N 星期,數字,星期一 = 1 … 星期日 = 7 2
w 星期,數字,星期日 = 0 … 星期六 = 6 2
t 這個月有幾天 30
L 是否為閏年,是 = 1、否 = 0 0
a 小寫 am/pm am
A 大寫 AM/PM AM

常見的組合:資料庫的 DATETIME 欄位用 'Y-m-d H:i:s';臺灣習慣的顯示方式可以寫 'Y/m/d H:i'。 要在格式裡放一般的英文字母,在前面加反斜線跳脫,例如 date('\T\o\d\a\y: Y-m-d')(用單引號字串)。

常見的錯誤是把月和分搞混:m 是月、i 是分(minute 的 m 已經被月份用掉了)。

時區:同一個瞬間,各地的時鐘不同

date() 會把時間戳記換成「某個時區的當地時間」,用的是預設時區。CodeLab 與很多伺服器的預設時區是 UTC(世界協調時間); 臺灣比 UTC 快 8 小時(UTC+8),所以 UTC 的 09:00 就是臺北的 17:00。

date_default_timezone_set('Asia/Taipei');   // 之後的 date()、mktime() 都用臺北時間
echo date_default_timezone_get();           // Asia/Taipei

時區用 IANA 時區名稱(也叫 tz database 名稱)表示,格式是「洲/城市」:Asia/Taipei、Asia/Tokyo、America/New_York、Europe/London。 不要用 +08:00 這種固定的時差代替時區名稱:美國、歐洲有日光節約時間,夏天和冬天的時差不同,時區名稱會自動套用正確的規則。

為什麼伺服器常設成 UTC? 網站的使用者可能在不同國家,伺服器也可能搬到別的地區。實務上常見的作法是: 資料庫一律存 UTC 時間(或時間戳記),只在顯示給使用者看的時候才換成使用者的時區。這樣比較時間先後、計算時間差都不會被時差干擾。

也可以在 php.ini 設定 date.timezone = Asia/Taipei,就不用每支程式都呼叫 date_default_timezone_set()。

切換時區

注意 $ts 從頭到尾都沒有改變,改變的只有「顯示成哪個時區的時間」。第 11~14 行用同一組「9 月 1 日 08:00」呼叫 mktime() 兩次:時區不同,得到的時間戳記也不同。

想一想:時間戳記、格式與時區

1.

下面的程式會印出什麼?

<?php
$t = mktime(0, 0, 0, 3, 5, 2026);
echo date('Y-n-j', $t);
2.

下面的程式會印出什麼?

<?php
date_default_timezone_set('Asia/Taipei');
echo date('Y-m-d H:i', 0);
第 1 次作答

strtotime:用英文描述日期

strtotime(描述, 基準時間戳記) 把一段英文的日期描述轉成時間戳記。第二個參數省略時,以「現在」為基準。

描述 意思
'2026-12-25 18:30' 指定的日期時間(用目前的時區解讀)
'+1 day'、'-2 weeks'、'+1 week 2 hours' 從基準時間往後或往前
'tomorrow'、'yesterday'、'today' 明天、昨天、今天的 00:00
'next monday'、'last friday' 下一個星期一、上一個星期五(00:00)
'first day of next month'、'last day of next month' 下個月的第一天、最後一天
'first friday of october 2026' 2026 年 10 月的第一個星期五

兩件要注意的事:

  1. 看不懂的描述回傳 false,例如中文「明天」、拼錯的 next fryday。拿使用者輸入的字串呼叫 strtotime 時,一定要用 === false 檢查; 直接把 false 交給 date(),會被當成 0,顯示成 1970 年。
  2. 不存在的日期不一定回傳 false:'2026-02-30' 會「溢位」成 3 月 2 日。要確認使用者輸入的年月日真的存在,用後面介紹的 checkdate()。

strtotime 的各種寫法

每一行都以 $base(臺北時間 2026-09-01 10:30,星期二)為基準。比較第 7 行的 +1 day(保留 10:30)和第 9 行的 tomorrow(變成 00:00)。

DateTime 與 DateTimeImmutable

時間戳記加減秒數很方便,但遇到「加一個月」「兩個日期差幾年幾個月」就麻煩了。PHP 內建兩個類別來表示一個日期時間: DateTime 與 DateTimeImmutable(PHP 5.5 加入)。自己寫類別是第 14 課的內容,這一課先把它們當成「內建的物件」使用,只需要兩個寫法:

  • new 類別名稱(參數):建立一個物件,例如 $d = new DateTime('2026-09-01');(不給參數就是現在)
  • $物件->方法(參數):呼叫這個物件的方法,例如 $d->format('Y-m-d')
方法 作用
format('Y-m-d') 排成字串,格式字元和 date() 相同
modify('+1 day') 用 strtotime 的寫法調整時間
add(new DateInterval('P1D'))、sub(…) 加上、減去一段時間
diff($另一個) 算兩個時間的差,回傳 DateInterval
setTimezone(new DateTimeZone('Asia/Taipei')) 換成另一個時區顯示
getTimestamp() 取得時間戳記

兩個類別的方法幾乎一樣,差別在 modify、add、sub、setTimezone 會不會改到原本的物件:

DateTime(可變) DateTimeImmutable(不可變)
$b = $a->modify('+1 day') 改到 $a,再回傳 $a 自己 $a 不變,回傳一個新的物件
適合 很少用到 建議預設使用

物件和陣列不同:$b = $a 不會複製物件,兩個變數指向同一個物件(第 14 課詳細說明)。 用 DateTime 時,一個不小心就會改到別處也在用的日期;DateTimeImmutable 不會有這個問題。

可變與不可變

看右邊的物件:第 4 行執行完,$start 和 $end 的箭頭指向同一個 DateTime;第 9 行執行完,$e2 指向另一個新的 DateTimeImmutable。

DateInterval:一段時間

DateInterval 表示「一段時間」,建立時用 ISO 8601 的期間寫法:以 P(period)開頭,日期部分接著寫數字與單位; 有時、分、秒時,先寫 T(time)再接時間部分。

寫法 意思
P1D 1 天
P2W 2 週
P1M2D 1 個月又 2 天
P1Y 1 年
PT36H 36 小時
PT1H30M 1 小時 30 分(T 後面的 M 是分)
P1DT12H 1 天又 12 小時

$a->diff($b) 回傳從 $a 到 $b 的 DateInterval,常用的屬性:

屬性 意思
->y、->m、->d 相差幾年、幾個月、幾天(例如 20 年 9 個月 17 天,用來算年齡)
->h、->i、->s 不滿一天的時、分、秒
->days 相差的總天數(倒數幾天用這個)
->invert $b 比 $a 早時是 1,否則是 0(->days 一律是正數)

format() 也可以排成字串:%y 年、%m 月、%d 日、%a 總天數、%R 正負號。注意這裡的格式字元前面要加 %,和 date() 不同。

驗證使用者輸入的日期是否存在,用 checkdate(月, 日, 年):checkdate(2, 29, 2026) 是 false(2026 年不是閏年)。

加減時間、算年齡與倒數

$d 是 DateTimeImmutable,第 4~6 行的 add、sub 都回傳新物件,$d 一直是 9 月 1 日。第 18 行是本課最重要的陷阱:1 月 31 日加一個月。

月底加一個月的陷阱

PHP 計算「+1 month」的方式是月份加 1、日期不變,如果那個月沒有這一天,就把多出來的天數往後溢位:

起點 +1 month 原因
2026-01-31 2026-03-03 2 月 31 日不存在,2 月只有 28 天,多 3 天
2026-03-31 2026-05-01 4 月只有 30 天,多 1 天
2026-01-15 2026-02-15 沒有問題

月費、每月固定日扣款、「一個月後到期」這類功能都會踩到。常見的處理方式:

  • 要「下個月的最後一天」:寫 'last day of next month'
  • 要「下個月的同一天,沒有就用月底」:先算 'first day of next month',再用 min(原本的日, 那個月的天數 t) 決定日期
  • 訂單到期日改用固定天數(P30D)計算

想一想:DateTime 的加減

1.

下面的程式會印出什麼?

<?php
$a = new DateTime('2026-09-01');
$b = $a;
$b->modify('+1 day');
echo $a->format('d');
2.

下面的程式會印出什麼?

<?php
$d = new DateTimeImmutable('2026-03-31');
echo $d->modify('+1 month')->format('m-d');
3.

下面的程式會印出什麼?

<?php
$a = new DateTimeImmutable('2026-12-25');
$b = new DateTimeImmutable('2026-12-01');
$i = $a->diff($b);
echo $i->days, ' ', $i->invert;
第 1 次作答

動手做:活動倒數

社團有四個活動。以今天(CodeLab 的今天是 2026-09-01)為準,每個活動印一行:已經過了印「活動名稱:已結束」;就是今天印「活動名稱:就是今天」;還沒到印「活動名稱:還有 N 天(星期縮寫)」,星期縮寫用格式字元 D。

你的輸出
預期輸出

重點回顧

  • 時間戳記是從 1970-01-01 00:00:00 UTC 起算的秒數,和時區無關;time() 取得現在,mktime(時, 分, 秒, 月, 日, 年) 指定一個時間
  • date(格式, 時間戳記) 依預設時區排成字串:Y-m-d H:i:s 最常用;m 是月、i 是分,n、j 不補零,D/l 是星期,N/w 是星期數字,t 是當月天數,L 是閏年
  • 時區用 IANA 名稱(Asia/Taipei),date_default_timezone_set() 設定;伺服器與資料庫常用 UTC,顯示時才換成使用者的時區
  • strtotime() 看得懂 +1 day、next monday、last day of next month;看不懂回傳 false,要用 === false 檢查
  • DateTime 的 modify/add/sub 會改到原物件,DateTimeImmutable 回傳新物件;建議預設用 Immutable,並接住回傳值
  • DateInterval('P1M2D')、('PT36H') 表示一段時間;diff() 的 ->y 算年齡、->days 算倒數天數
  • checkdate(月, 日, 年) 驗證日期是否存在;1/31 加一個月是 3/3,要月底請寫 last day of next month
這一課完成了!
Next lesson:表單處理
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。