@@ -46,12 +46,18 @@ enum CodexLocalProjectUsageIndexer {
4646 defer { CodexModelsTelemetry . end ( " IndexRefresh " , id: refreshSignpost) }
4747 let clampedHistoryDays = max ( 1 , min ( 365 , historyDays) )
4848 let until = now
49- let since = Calendar . current. date ( byAdding: . day, value: - ( clampedHistoryDays - 1 ) , to: now) ?? now
50- let comparisonSince = self . modelsAnalyticsScanStart ( since: since, until: until)
5149 var scannerOptions = options. scannerOptions
5250 if forceRefresh {
5351 scannerOptions. refreshMinIntervalSeconds = 0
5452 }
53+ let since = scannerOptions. calendar. date (
54+ byAdding: . day,
55+ value: - ( clampedHistoryDays - 1 ) ,
56+ to: now) ?? now
57+ let comparisonSince = self . modelsAnalyticsScanStart (
58+ since: since,
59+ until: until,
60+ calendar: scannerOptions. calendar)
5561
5662 progress ? ( CodexLocalProjectUsageIndexProgress ( phase: . scanningLogs) )
5763 _ = try CostUsageScanner . loadDailyReportCancellable (
@@ -63,7 +69,10 @@ enum CodexLocalProjectUsageIndexer {
6369 checkCancellation: checkCancellation)
6470 try checkCancellation ? ( )
6571
66- let cache = CostUsageCacheIO . load ( provider: . codex, cacheRoot: scannerOptions. cacheRoot)
72+ let cache = CostUsageCacheIO . load (
73+ provider: . codex,
74+ cacheRoot: scannerOptions. cacheRoot,
75+ calendar: scannerOptions. calendar)
6776 let catalogResult = CodexThreadCatalogReader . loadResult ( options: scannerOptions)
6877 let catalog = catalogResult. catalog
6978 let sourceStatus = CodexLocalProjectUsageSourceStatus ( catalog: catalogResult. completeness)
@@ -145,8 +154,14 @@ enum CodexLocalProjectUsageIndexer {
145154 checkCancellation: CostUsageScanner . CancellationCheck ? = nil ) throws -> CodexLocalProjectUsageSnapshot
146155 {
147156 let clampedHistoryDays = max ( 1 , min ( 365 , historyDays) )
148- let range = CostUsageScanner . CostUsageDayRange ( since: since, until: until)
149- let cache = cacheOverride ?? CostUsageCacheIO . load ( provider: . codex, cacheRoot: options. cacheRoot)
157+ let range = CostUsageScanner . CostUsageDayRange (
158+ since: since,
159+ until: until,
160+ calendar: options. calendar)
161+ let cache = cacheOverride ?? CostUsageCacheIO . load (
162+ provider: . codex,
163+ cacheRoot: options. cacheRoot,
164+ calendar: options. calendar)
150165 let catalog = catalogOverride ?? CodexThreadCatalogReader . load ( options: options)
151166 let expectedRoots = CostUsageScanner . codexRootsFingerprint ( options: options)
152167 let scopeSignature = self . stableScopeSignature ( options: options)
@@ -275,7 +290,8 @@ enum CodexLocalProjectUsageIndexer {
275290 since: since,
276291 until: until,
277292 historyDays: clampedHistoryDays,
278- generatedAt: now) ,
293+ generatedAt: now,
294+ calendar: options. calendar) ,
279295 identity: ModelsAnalyticsIdentity (
280296 scopeSignature: scopeSignature,
281297 rootsFingerprint: rootsFingerprint) ,
@@ -297,6 +313,7 @@ extension CodexLocalProjectUsageIndexer {
297313 let until : Date
298314 let historyDays : Int
299315 let generatedAt : Date
316+ let calendar : Calendar
300317 }
301318
302319 fileprivate struct ModelsAnalyticsIdentity {
@@ -653,19 +670,27 @@ extension CodexLocalProjectUsageIndexer {
653670 {
654671 let aggregationSignpost = CodexModelsTelemetry . begin ( " SnapshotAggregation " )
655672 defer { CodexModelsTelemetry . end ( " SnapshotAggregation " , id: aggregationSignpost) }
656- let periods = self . modelsAnalyticsPeriods ( since: window. since, until: window. until)
673+ let periods = self . modelsAnalyticsPeriods (
674+ since: window. since,
675+ until: window. until,
676+ calendar: window. calendar)
657677 let previousInterval = periods. previous
658678 let previousRange = CostUsageScanner . CostUsageDayRange (
659679 since: previousInterval. start,
660- until: previousInterval. end. addingTimeInterval ( - 1 ) )
680+ until: previousInterval. end. addingTimeInterval ( - 1 ) ,
681+ calendar: window. calendar)
661682 let previousBuckets = try self . sessionBuckets (
662683 from: context. cache,
663684 range: previousRange,
664685 catalog: context. catalog,
665686 progress: nil ,
666687 checkCancellation: checkCancellation) . sessionBuckets
667- let currentFragments = self . analyticsFragments ( from: context. currentBuckets. values)
668- let previousFragments = self . analyticsFragments ( from: previousBuckets. values)
688+ let currentFragments = self . analyticsFragments (
689+ from: context. currentBuckets. values,
690+ calendar: window. calendar)
691+ let previousFragments = self . analyticsFragments (
692+ from: previousBuckets. values,
693+ calendar: window. calendar)
669694 let currentBucketsByProject = Dictionary ( grouping: context. currentBuckets. values, by: \. projectId)
670695 let previousBucketsByProject = Dictionary ( grouping: previousBuckets. values, by: \. projectId)
671696 let currentFragmentsByProject = Dictionary ( grouping: currentFragments, by: \. workspaceID)
@@ -814,14 +839,16 @@ extension CodexLocalProjectUsageIndexer {
814839 }
815840
816841 fileprivate static func analyticsFragments(
817- from buckets: Dictionary < String , SessionBucket > . Values ) -> [ CodexModelsUsageFragment ]
842+ from buckets: Dictionary < String , SessionBucket > . Values ,
843+ calendar: Calendar ) -> [ CodexModelsUsageFragment ]
818844 {
819- buckets. flatMap { bucket in
845+ let calendar = CostUsageScanner . CostUsageDayRange. localGregorianCalendar ( matching: calendar)
846+ return buckets. flatMap { bucket in
820847 if bucket. hasCompleteEventRows, !bucket. usageRows. isEmpty {
821848 return bucket. usageRows. compactMap { row -> CodexModelsUsageFragment ? in
822849 guard let timestampUnixMs = row. timestampUnixMs else { return nil }
823850 let timestamp = Date ( timeIntervalSince1970: Double ( timestampUnixMs) / 1000 )
824- let day = Calendar . current . startOfDay ( for: timestamp)
851+ let day = calendar . startOfDay ( for: timestamp)
825852 let inputTokens = max ( 0 , row. input)
826853 let outputTokens = max ( 0 , row. output)
827854 let totalTokens = Int64 ( inputTokens + outputTokens)
@@ -841,7 +868,8 @@ extension CodexLocalProjectUsageIndexer {
841868 }
842869 }
843870 return bucket. modelDailyTotals. flatMap { day, models -> [ CodexModelsUsageFragment ] in
844- guard let date = CostUsageDateParser . parse ( day) else { return [ ] }
871+ guard let parsedDate = CostUsageScanner . parseDayKey ( day, calendar: calendar) else { return [ ] }
872+ let date = calendar. startOfDay ( for: parsedDate)
845873 return models. map { model, totals in
846874 CodexModelsUsageFragment (
847875 workspaceID: bucket. projectId,
@@ -895,7 +923,7 @@ extension CodexLocalProjectUsageIndexer {
895923 }
896924
897925 fileprivate static func stableScopeSignature( options: CostUsageScanner . Options ) -> String {
898- CodexLocalDataScope . resolve ( options: options) . identifier
926+ " \( CodexLocalDataScope . resolve ( options: options) . identifier) |timeZone= \( options . calendar . timeZone . identifier ) "
899927 }
900928
901929 fileprivate static func rootsFingerprint( _ roots: [ String : Int64 ] ) -> [ String : Int64 ] {
0 commit comments