@@ -128,6 +128,88 @@ final class StatsManagerTests: XCTestCase {
128128 XCTAssertEqual ( statsManager. stats. currentStreak, 1 , " Streak should reset after a gap " )
129129 }
130130
131+ @MainActor
132+ func testDailyBucketsUseInjectedCalendarTimeZone( ) {
133+ var calendar = Calendar ( identifier: . gregorian)
134+ calendar. timeZone = TimeZone ( secondsFromGMT: 2 * 60 * 60 ) !
135+ statsManager = StatsManager ( statsFileURL: tempFileURL, calendar: calendar)
136+
137+ let nearMidnightUTC = Date ( timeIntervalSince1970: 1_704_060_000 ) // 2023-12-31 22:00:00 UTC, 2024-01-01 in GMT+2
138+ let transcription = VocaTranscription (
139+ text: " local day " ,
140+ duration: 1.0 ,
141+ detectedLanguage: " en " ,
142+ audioLengthSeconds: 1.0 ,
143+ modelUsed: . tiny,
144+ timestamp: nearMidnightUTC
145+ )
146+
147+ statsManager. recordTranscription ( transcription)
148+
149+ XCTAssertEqual ( statsManager. stats. dailyWordCounts [ " 2024-01-01 " ] , 2 )
150+ XCTAssertNil ( statsManager. stats. dailyWordCounts [ " 2023-12-31 " ] )
151+ }
152+
153+ @MainActor
154+ func testStreakUsesTranscriptionDateRatherThanCurrentDate( ) {
155+ var calendar = Calendar ( identifier: . gregorian)
156+ calendar. timeZone = TimeZone ( secondsFromGMT: 0 ) !
157+ statsManager = StatsManager ( statsFileURL: tempFileURL, calendar: calendar)
158+
159+ let firstDay = Date ( timeIntervalSince1970: 946_684_800 ) // 2000-01-01 00:00:00 UTC
160+ let secondDay = Date ( timeIntervalSince1970: 946_771_200 ) // 2000-01-02 00:00:00 UTC
161+
162+ statsManager. recordTranscription ( VocaTranscription (
163+ text: " first day " ,
164+ duration: 1.0 ,
165+ detectedLanguage: " en " ,
166+ audioLengthSeconds: 1.0 ,
167+ modelUsed: . tiny,
168+ timestamp: firstDay
169+ ) )
170+ statsManager. recordTranscription ( VocaTranscription (
171+ text: " second day " ,
172+ duration: 1.0 ,
173+ detectedLanguage: " en " ,
174+ audioLengthSeconds: 1.0 ,
175+ modelUsed: . tiny,
176+ timestamp: secondDay
177+ ) )
178+
179+ XCTAssertEqual ( statsManager. stats. currentStreak, 2 )
180+ XCTAssertEqual ( statsManager. stats. bestStreak, 2 )
181+ }
182+
183+ @MainActor
184+ func testSameDayTranscriptionsDoNotIncrementStreak( ) {
185+ var calendar = Calendar ( identifier: . gregorian)
186+ calendar. timeZone = TimeZone ( secondsFromGMT: 0 ) !
187+ statsManager = StatsManager ( statsFileURL: tempFileURL, calendar: calendar)
188+
189+ let first = Date ( timeIntervalSince1970: 946_684_800 ) // 2000-01-01 00:00:00 UTC
190+ let second = Date ( timeIntervalSince1970: 946_728_000 ) // 2000-01-01 12:00:00 UTC
191+
192+ statsManager. recordTranscription ( VocaTranscription (
193+ text: " morning words " ,
194+ duration: 1.0 ,
195+ detectedLanguage: " en " ,
196+ audioLengthSeconds: 1.0 ,
197+ modelUsed: . tiny,
198+ timestamp: first
199+ ) )
200+ statsManager. recordTranscription ( VocaTranscription (
201+ text: " afternoon words " ,
202+ duration: 1.0 ,
203+ detectedLanguage: " en " ,
204+ audioLengthSeconds: 1.0 ,
205+ modelUsed: . tiny,
206+ timestamp: second
207+ ) )
208+
209+ XCTAssertEqual ( statsManager. stats. currentStreak, 1 )
210+ XCTAssertEqual ( statsManager. stats. bestStreak, 1 )
211+ }
212+
131213 @MainActor
132214 func testResetStats( ) {
133215 let transcription = VocaTranscription ( text: " Test " , duration: 1.0 , detectedLanguage: " en " , audioLengthSeconds: 1.0 , modelUsed: . tiny)
0 commit comments