TWSE MIS 即時報價的三種時間欄位:d / t / tlong 並存結構與 epoch 毫秒陷阱
TWSE MIS(Market Information System)的即時報價端點 getStockInfo 在單一筆 quote response 物件內同時回傳三種性質不同的時間表示:交易日期字串(d)、本地時鐘字串(t)、以及 epoch 毫秒整數字串(tlong)。三個欄位各自只攜帶部分時間資訊,且 tlong 的單位為毫秒(13 碼),不是秒(10 碼)——這是 parser 最常踩到的靜默錯誤來源。
本文聚焦於同一筆 payload 內的時間維度並存問題。TWSE OpenAPI 跨多端點的日期編碼差異(ROC 7 碼 / 斜線 / 5 碼 / AD 8 碼等五種格式)屬於另一個獨立問題,請參閱姊妹篇 TWSE OpenAPI 的日期格式陷阱。
① 端點位置與 response 結構概覽
TWSE MIS getStockInfo 端點通常以下列形式呼叫(非官方 OpenAPI · 無正式文件):
https://mis.twse.com.tw/stock/api/getStockInfo.jsp?ex_ch=tse_{stockCode}.tw&json=1&delay=0
Response 為 JSON,主要內容在 msgArray 陣列,每個元素代表一筆即時報價。以下以一筆真實回傳物件說明三個時間欄位的並存結構(來源:mis.twse.com.tw getStockInfo 端點實測回傳 · 快照日期 2026-05-25):
{
"d": "20260525",
"t": "13:30:00",
"tlong": "1779690600000",
"%": "14:30:00",
"ot": "14:30:00"
}
三個時間相關主欄位:
| 欄位 | 實際範例值 | 格式說明 |
|---|---|---|
d | "20260525" | 交易日期,AD YYYYMMDD 8 碼字串,無時間部分 |
t | "13:30:00" | 最後成交時刻,HH:MM:SS 本地時鐘字串,無日期、無時區 |
tlong | "1779690600000" | Unix epoch 毫秒(13 碼字串),同時含日期與時間 |
另有 %(開盤 / 資料更新時刻)與 ot 在部分場景出現,格式同為 HH:MM:SS 本地時鐘字串,限制與 t 相同。
② 三欄並存結構:各自攜帶什麼、缺少什麼
三個欄位的資訊邊界如下:
d(交易日期)
- 提供:年月日(8 碼 YYYYMMDD)
- 缺少:時、分、秒,無法產出精確 datetime
- 用途:判斷這筆報價屬於哪個交易日;在不需要精確時刻的場景(例如日線對齊)可直接使用
t(本地時鐘字串)
- 提供:時分秒(HH:MM:SS,24 小時制)
- 缺少:日期、時區標示
- 限制:純本地時鐘,不含時區資訊;若需要跨日對齊或與其他系統的 UTC timestamp 比對,單靠
t無法進行,需搭配d拼合,且仍需外掛台灣時區(Asia/Taipei · UTC+8)
tlong(epoch 毫秒)
- 提供:完整精確時戳(同時含日期與時間),且單位為毫秒可區分同秒內的先後
- 缺少:需要知道單位是毫秒才能正確 parse
- 唯一同時帶日期 + 時間 + 時區基準的欄位:epoch 秒 / 毫秒是 UTC 對齊的絕對時戳,無需另外外掛時區假設
結論:在需要精確 timezone-aware datetime 的 pipeline 中,tlong 是三個欄位中唯一可以單獨使用的時戳。d 與 t 的組合(d + T + t + 台灣時區)可作為替代方案,但必須外掛時區且仍不具備毫秒精度。
③ tlong 是毫秒,不是秒(13 碼 vs 10 碼)
tlong 的值為 13 碼字串 "1779690600000",代表 Unix epoch 毫秒(ms)。標準 Unix timestamp(秒)為 10 碼(例如 1779690600)。
誤當秒處理的後果:
若直接把 tlong 的字串值 1779690600000 當作秒數傳入 datetime.fromtimestamp()(Python)或 new Date(1779690600000) 但對「new Date 接受毫秒」這點認知不清楚,會得到預期之外的結果:
- Python
datetime.fromtimestamp(1779690600000)在多數平台上會raise OSError(超過可表示的最大時間)或靜默回傳錯誤年份 - 若語言或函式庫接受超大秒數而不報錯,時間會落在數萬年後(約五萬多年後)的荒謬年份——因為把毫秒當秒等於把數值放大了 1000 倍
驗證方式:tlong 值 1779690600000 除以 1000 得 1779690600,換算為 2026-05-25 14:30:00 CST(UTC+8),落在欄位 d = "20260525" 的交易日內。需注意本快照為收盤後抓取(response 頂層 queryTime.sysTime 顯示 21:30),此時 t = "13:30:00"(停在收盤時刻)與 tlong / % = "14:30:00"(盤後最後一次資料更新時刻)並不相同。兩欄各自對應哪個確切時間語義屬觀察推論(MIS 無官方文件),但可確認的事實是:同一筆 payload 內 t 與 tlong 的時刻未必相等,跨欄拼時間前需留意。
MIS 端點無任何官方欄位文件,tlong 的單位(毫秒)是透過觀察回傳值位數(13 碼)與已知台股收盤時間比對交叉驗證所得。
④ Silent failure 模式:為何容易靜默出錯
tlong 為毫秒的問題屬於典型靜默失敗:
tlong在 JSON 中是字串型別("1779690600000"),int()轉型不報錯datetime.fromtimestamp(int("1779690600000"))的行為依 Python 版本與作業系統而異:部分平台 raiseOSError(有錯誤可查),部分平台靜默回傳錯誤年份(無錯誤,資料看起來有值)- JavaScript
new Date(1779690600000)接受毫秒,行為正確。但若開發者誤套用「先除以 1000」的秒制前提(如 Python 路徑),new Date(1779690600)會把該值當毫秒解讀,落在 1970-01-21 附近——與 Python 誤當秒的方向相反,但同樣是靜默落入 1970 的錯誤
這三種情形下,pipeline 不一定會報錯,datetime 看起來有值,只有在和 d / t 欄位交叉比對時才發現對不起來。
另一個常見陷阱:t 欄位的 HH:MM:SS 是本地台灣時間,沒有 UTC offset 標示。若 pipeline 在 UTC 環境(例如 GCP / AWS 預設)執行,未外掛 Asia/Taipei 時區就拼合 d + t,產出的 datetime 會比正確時間早 8 小時,且不報任何錯誤。
⑤ 正確 parse 方式(Python 範例)
方案 A:直接使用 tlong(建議)
from datetime import datetime, timezone, timedelta
TZ_TAIPEI = timezone(timedelta(hours=8)) # Asia/Taipei · UTC+8
def parse_tlong(tlong_str: str) -> datetime:
"""
tlong 為 epoch 毫秒(13 碼字串),先除以 1000 轉秒再建 datetime。
範例值對應 2026-05-25 端點實測快照
"""
epoch_ms = int(tlong_str) # "1779690600000" → 1779690600000
epoch_s = epoch_ms / 1000 # 毫秒 → 秒(關鍵轉換)
return datetime.fromtimestamp(epoch_s, tz=TZ_TAIPEI)
# 以 2026-05-25 快照值驗證
dt = parse_tlong("1779690600000")
# → datetime(2026, 5, 25, 14, 30, 0, tzinfo=UTC+8)
方案 B:組合 d + t(需外掛時區)
from datetime import datetime, timezone, timedelta
TZ_TAIPEI = timezone(timedelta(hours=8))
def parse_d_and_t(d_str: str, t_str: str) -> datetime:
"""
d: YYYYMMDD 8 碼 AD 日期字串(如 "20260525")
t: HH:MM:SS 本地時鐘字串(如 "13:30:00"),無時區標示
外掛 Asia/Taipei(UTC+8)組成 timezone-aware datetime。
注意: t 為 HH:MM:SS 時鐘字串,精度為秒(不含毫秒);盤後可能為凍結的收盤時刻。
"""
naive_dt = datetime.strptime(f"{d_str} {t_str}", "%Y%m%d %H:%M:%S")
return naive_dt.replace(tzinfo=TZ_TAIPEI)
# 以 2026-05-25 快照值驗證
dt = parse_d_and_t("20260525", "13:30:00")
# → datetime(2026, 5, 25, 13, 30, 0, tzinfo=UTC+8)
兩個方案的結果不同(方案 A 得 14:30,方案 B 得 13:30),因為在本(收盤後)快照中 t 停在收盤 13:30,而 tlong / % / ot 是盤後 14:30 的最後更新——同一筆 payload 內這兩組欄位的時刻本來就不一定相等。這不是 parse 錯誤;至於各欄確切語義(成交時刻 vs 資料更新時刻)屬觀察推論,MIS 無官方文件,不應寫死假設。實務取捨:需要該筆報價對應的絕對市場時刻,優先用 tlong(絕對時戳、無時區歧義、適合 cache / 去重);只需判斷交易日用 d;用 t 前先確認它在你的時段下是否為有效成交時刻(盤後 / 盤前可能是凍結或空值)。
⑥ 欄位語義彙整
| 欄位 | 語義 | 時區 | 精度 | 單獨可用? |
|---|---|---|---|---|
d | 交易日期 | 無(日期只) | 日 | 否(無時間) |
t | 最後成交時刻 | 隱含 UTC+8 · 需外掛 | 秒 | 否(無日期) |
tlong | 資料更新時戳 | UTC epoch(絕對) | 毫秒 | 是 |
% | 資料更新時刻(另一表示) | 隱含 UTC+8 · 需外掛 | 秒 | 否(無日期) |
ot | 開盤或前次更新時刻 | 隱含 UTC+8 · 需外掛 | 秒 | 否(無日期) |
MIS 端點無官方文件,t / tlong / % / ot 各欄位語義(最後成交時刻 vs 資料更新時戳等)均為觀察推論,實際欄位定義以 TWSE 官方為準。
⑦ 與其他 TWSE 端點的時間問題差異
本篇描述的是單一 MIS payload 內三種時間維度並存的 parse 問題。這與 TWSE OpenAPI 跨多端點的「五種日期編碼格式(ROC 7 碼 / 斜線 / 5 碼 / 純年 / AD 8 碼)並存」問題是不同層次的問題——前者是「同一個 JSON 物件內的時間維度」,後者是「不同端點各自採用不同日期字串格式」。
詳見姊妹篇:TWSE OpenAPI 的日期格式陷阱:同一個台股 API 有五種不同格式。
⑧ Edge case 已知
收盤後 API 仍可呼叫:MIS 端點在收盤後仍可呼叫,回傳的 tlong 為最後一次更新的快照值,此時 tlong 與 d / t 的值皆為該次更新的殘留,不代表當下時刻。Response 頂層另有 cachedAlive 欄位(單位與語義無官方文件、為觀察值),疑似與快取狀態相關,但在缺乏官方定義下不應作為「資料是否即時」的唯一判據。
盤前 / 盤中 / 盤後的 t 欄位:t 欄位在盤前開盤前回傳的值可能為昨日收盤時刻或空字串,不應假設 t 在任何時段都有效。tlong 同樣有此問題,盤前 tlong 對應的可能是前一交易日的時戳。(本快照為收盤後單次抓取,盤前行為未直接觀測)
跨日零時附近:d 與 t 的組合在跨日情境(例如夜盤市場或後台批次跨日更新)可能出現 d 為前日日期、t 顯示 00:00:XX 的組合,單靠字串拼合無法偵測跨日。tlong epoch 值在此情境下行為明確,不存在日期拼合的模糊地帶。
tlong 為字串型別:JSON 回傳中 tlong 是字串(帶引號)而非數字,int() 轉型前需確認輸入不為空字串,避免在行情中斷或端點異常時 raise ValueError。
⑨ See also
- TWSE OpenAPI 的日期格式陷阱:同一個台股 API 有五種不同格式 — 跨多端點的五種日期編碼差異(本文姊妹篇 · 不同問題層次)
- TWSE T86 欄位命名實況 — MIS / T86 欄位名稱縮寫與觀察方法
- TWSE MIS 預算好的漲跌停欄位 — MIS
u/w欄位的 silent failure 風險 - floviq 台股 n8n 自動化工具 — 整合 MIS 即時報價與時間欄位 parse 的 n8n workflow 模板,包含 tlong 毫秒轉換與 timezone-aware datetime 建構的實作參考
建構即時報價 pipeline 時,若需要跨 session 的時戳去重(避免快取重複處理)或與其他 UTC 時戳對齊,floviq 盤前工具 對 MIS 的三欄時間結構設計了獨立 parse 層,可作為實作參考。
※ 本文描述 TWSE MIS getStockInfo 端點即時報價 payload 內 d / t / tlong 三種時間欄位的結構差異與 parse 機制 · 不構成投資建議,投資請自行評估風險。
資料來源:TWSE MIS getStockInfo 端點(mis.twse.com.tw/stock/api/getStockInfo.jsp)· 快照日期 2026-05-25 · 欄位觀察無官方文件
如果這篇對你有用: