Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Sources/APIExplorer/AskVideoAudit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,10 @@ private func askParityContext(
"osVersion": "10_15_7",
"platform": "DESKTOP",
"userAgent": askParityUserAgent,
// Minutes from UTC, UTC-behind negative — the same value YouTube's web client
// sends (its JS `-Date.getTimezoneOffset()`). Mirrors
// `InnerTubeSupport.utcOffsetMinutes(for:)`, inlined because APIExplorer cannot
// import the Kaset executable target.
"utcOffsetMinutes": TimeZone.current.secondsFromGMT() / 60,
]
if profile.usesVisitorData {
Expand Down
9 changes: 9 additions & 0 deletions Sources/Kaset/Services/API/InnerTubeSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,13 @@ enum InnerTubeSupport {
.joined()
return "\(timestamp)_\(hash)"
}

/// The `client.utcOffsetMinutes` value for an InnerTube request: minutes offset
/// from UTC, UTC-behind zones NEGATIVE (Los Angeles is `-420`). This matches what
/// YouTube's own web client sends — its JS computes `-Date.getTimezoneOffset()`,
/// and `TimeZone.secondsFromGMT()` already carries that opposite sign, so no
/// negation is needed here.
static func utcOffsetMinutes(for timeZone: TimeZone) -> Int {
timeZone.secondsFromGMT() / 60
}
}
2 changes: 1 addition & 1 deletion Sources/Kaset/Services/API/YTMusicClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1959,7 +1959,7 @@ final class YTMusicClient: YTMusicClientProtocol {
"osVersion": "10_15_7",
"platform": "DESKTOP",
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15",
"utcOffsetMinutes": -TimeZone.current.secondsFromGMT() / 60,
"utcOffsetMinutes": InnerTubeSupport.utcOffsetMinutes(for: .current),
],
"user": userDict,
]
Expand Down
2 changes: 1 addition & 1 deletion Sources/Kaset/Services/API/YouTubeClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -689,7 +689,7 @@ final class YouTubeClient: YouTubeClientProtocol { // swiftlint:disable:this typ
"osVersion": "10_15_7",
"platform": "DESKTOP",
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15",
"utcOffsetMinutes": TimeZone.current.secondsFromGMT() / 60,
"utcOffsetMinutes": InnerTubeSupport.utcOffsetMinutes(for: .current),
],
"user": userDict,
]
Expand Down
38 changes: 38 additions & 0 deletions Tests/KasetTests/InnerTubeSupportTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,42 @@ struct InnerTubeSupportTests {
)
#expect(hash.hasPrefix("42_"))
}

@Test("utcOffsetMinutes is negative for a UTC-behind zone (matches YouTube web)")
func utcOffsetMinutesBehindUTC() throws {
// Fixed UTC-5 offset (DST-independent) so the assertion is stable on any
// run date; secondsFromGMT() == -18_000 → -18_000 / 60 == -300.
let tz = try #require(TimeZone(secondsFromGMT: -18000))
#expect(InnerTubeSupport.utcOffsetMinutes(for: tz) == -300)
}

@Test("utcOffsetMinutes is positive for a UTC-ahead zone (matches YouTube web)")
func utcOffsetMinutesAheadOfUTC() throws {
// Fixed UTC+1 offset (DST-independent); secondsFromGMT() == 3600 → 3600 / 60 == 60.
let tz = try #require(TimeZone(secondsFromGMT: 3600))
#expect(InnerTubeSupport.utcOffsetMinutes(for: tz) == 60)
}

@Test("utcOffsetMinutes is zero for UTC")
func utcOffsetMinutesUTC() throws {
let tz = try #require(TimeZone(identifier: "UTC"))
#expect(InnerTubeSupport.utcOffsetMinutes(for: tz) == 0)
}

@Test("utcOffsetMinutes carries the fractional half-hour sign for India")
func utcOffsetMinutesHalfHour() throws {
let tz = try #require(TimeZone(identifier: "Asia/Kolkata")) // IST UTC+5:30 (no DST) → +19_800s
#expect(InnerTubeSupport.utcOffsetMinutes(for: tz) == 330)
}

@Test("utcOffsetMinutes pins the Los Angeles datum YouTube's web client sends")
func utcOffsetMinutesLosAngelesReference() throws {
// YouTube's web client sends `-Date.getTimezoneOffset()` (YouTube.js,
// Session.ts), which equals `secondsFromGMT() / 60` in Foundation's opposite
// sign convention. Los Angeles on daylight time is UTC-7: -25_200s → -420,
// the datum in the review discussion. A fixed offset is used rather than the
// named zone so DST cannot move the assertion.
let tz = try #require(TimeZone(secondsFromGMT: -25200))
#expect(InnerTubeSupport.utcOffsetMinutes(for: tz) == -420)
}
}
Loading