feat(korea): add koreanMarketSession + wire KR into AppState.refresh

AppState.koreanMarketSession (nonisolated) returns '盘中' during KST 09:00-15:30
on weekdays, nil otherwise. Marked nonisolated so it can be called from sync
non-MainActor contexts (notably tests).

AppState.refresh now has a 5th parallel branch fetching Korean quotes via
DataService.fetchKoreanQuotes. KR is non-throwing (returns [:] on failure) so
it's awaited outside the try block — any KR network failure does not abort
A股/港股fetch results.
This commit is contained in:
vgearen
2026-05-14 10:05:47 +08:00
parent bddb608b76
commit 6e63064207
2 changed files with 79 additions and 2 deletions
+22 -2
View File
@@ -180,8 +180,9 @@ final class AppState: ObservableObject {
hasError = false
do {
let all = stocks.map(\.id)
let sinaCodes = all.filter { !$0.hasPrefix("hk") }
let sinaCodes = all.filter { !$0.hasPrefix("hk") && !KoreanStockID.isKorean($0) }
let hkCodes = all.filter { $0.hasPrefix("hk") }
let krIds = all.filter { KoreanStockID.isKorean($0) }
let isOvernight = Self.usMarketSession() == "夜盘"
let usCodes = isOvernight ? all.filter { $0.hasPrefix("usr_") } : []
@@ -189,10 +190,12 @@ final class AppState: ObservableObject {
// 所有数据源并行请求
async let sinaResult = DataService.fetchSinaQuotes(codes: sinaCodes)
async let hkResult = DataService.fetchTencentHKQuotes(codes: hkCodes)
async let krResult = DataService.fetchKoreanQuotes(ids: krIds)
async let ratesResult = CurrencyService.fetchRates()
async let overnightResult = PythService.fetchOvernightPrices(codes: usCodes)
let (s, h) = try await (sinaResult, hkResult)
let kr = await krResult
let rates = await ratesResult
let overnight = await overnightResult
@@ -205,7 +208,8 @@ final class AppState: ObservableObject {
}
quotes.merge(merged) { $1 }
quotes.merge(h) { $1 }
quotes.merge(h) { $1 }
quotes.merge(kr) { $1 }
exchangeRates = rates
lastUpdateTime = Date()
syncStockNamesFromQuotes()
@@ -334,6 +338,22 @@ final class AppState: ObservableObject {
return .secondary
}
// MARK: - 韩股交易时段
/// 韩股交易时段:返回 "盘中" 或 nil(非交易时段 / 周末)。
/// KST 固定 UTC+9,无夏令时。
nonisolated static func koreanMarketSession(at now: Date = Date()) -> String? {
var cal = Calendar(identifier: .gregorian)
cal.timeZone = TimeZone(identifier: "Asia/Seoul")!
let comps = cal.dateComponents([.weekday, .hour, .minute], from: now)
let wd = comps.weekday ?? 1
// Swift Calendar weekday: 1=周日, 2=周一, ..., 6=周五, 7=周六
guard wd >= 2 && wd <= 6 else { return nil }
let t = (comps.hour ?? 0) * 60 + (comps.minute ?? 0)
if t >= 9 * 60 && t <= 15 * 60 + 30 { return "盘中" }
return nil
}
// MARK: - 美股交易时段
static func usMarketSession() -> String? {