@@ -122,36 +122,6 @@ function mergeDailyPoints(
122122 . map ( ( [ day , downloads ] ) => ( { day, downloads } ) )
123123}
124124
125- function getIsoWeekStartDateFromWeekKey ( weekKey : string ) : Date | null {
126- const match = / ^ ( \d { 4 } ) - W ( \d { 2 } ) $ / . exec ( weekKey )
127- if ( ! match ) return null
128-
129- const year = Number ( match [ 1 ] )
130- const week = Number ( match [ 2 ] )
131-
132- const januaryFourth = new Date ( Date . UTC ( year , 0 , 4 ) )
133- const januaryFourthIsoDay = januaryFourth . getUTCDay ( ) || 7
134- const weekOneMonday = new Date ( Date . UTC ( year , 0 , 4 - ( januaryFourthIsoDay - 1 ) ) )
135-
136- const weekMonday = new Date ( weekOneMonday )
137- weekMonday . setUTCDate ( weekOneMonday . getUTCDate ( ) + ( week - 1 ) * 7 )
138- return weekMonday
139- }
140-
141- function toIsoWeekKey ( isoDay : string ) : string {
142- const date = new Date ( `${ isoDay } T00:00:00.000Z` )
143- const isoDayOfWeek = date . getUTCDay ( ) || 7
144-
145- const thursday = new Date ( date )
146- thursday . setUTCDate ( date . getUTCDate ( ) + 4 - isoDayOfWeek )
147-
148- const isoYear = thursday . getUTCFullYear ( )
149- const isoYearStart = new Date ( Date . UTC ( isoYear , 0 , 1 ) )
150- const weekNumber = Math . ceil ( ( ( + thursday - + isoYearStart ) / 86400000 + 1 ) / 7 )
151-
152- return `${ isoYear } -W${ String ( weekNumber ) . padStart ( 2 , '0' ) } `
153- }
154-
155125function buildDailyEvolutionFromDaily (
156126 daily : Array < { day : string ; downloads : number } > ,
157127) : DailyDownloadPoint [ ] {
@@ -161,30 +131,44 @@ function buildDailyEvolutionFromDaily(
161131 . map ( item => ( { day : item . day , downloads : item . downloads } ) )
162132}
163133
164- function buildWeeklyEvolutionFromDaily (
134+ function buildRollingWeeklyEvolutionFromDaily (
165135 daily : Array < { day : string ; downloads : number } > ,
136+ rangeStartIso : string ,
137+ rangeEndIso : string ,
166138) : WeeklyDownloadPoint [ ] {
167139 const sorted = daily . slice ( ) . sort ( ( a , b ) => a . day . localeCompare ( b . day ) )
168- const downloadsByWeekKey = new Map < string , number > ( )
140+ const rangeStartDate = parseIsoDateOnly ( rangeStartIso )
141+ const rangeEndDate = parseIsoDateOnly ( rangeEndIso )
142+
143+ const groupedByIndex = new Map < number , number > ( )
169144
170145 for ( const item of sorted ) {
171- const weekKey = toIsoWeekKey ( item . day )
172- downloadsByWeekKey . set ( weekKey , ( downloadsByWeekKey . get ( weekKey ) ?? 0 ) + item . downloads )
173- }
146+ const itemDate = parseIsoDateOnly ( item . day )
147+ const dayOffset = Math . floor ( ( itemDate . getTime ( ) - rangeStartDate . getTime ( ) ) / 86400000 )
148+ if ( dayOffset < 0 ) continue
174149
175- return Array . from ( downloadsByWeekKey . entries ( ) )
176- . sort ( ( [ a ] , [ b ] ) => a . localeCompare ( b ) )
177- . map ( ( [ weekKey , downloads ] ) => {
178- const weekStartDate = getIsoWeekStartDateFromWeekKey ( weekKey )
179- if ( ! weekStartDate ) return { weekKey, downloads, weekStart : '-' , weekEnd : '-' }
150+ const weekIndex = Math . floor ( dayOffset / 7 )
151+ groupedByIndex . set ( weekIndex , ( groupedByIndex . get ( weekIndex ) ?? 0 ) + item . downloads )
152+ }
180153
154+ return Array . from ( groupedByIndex . entries ( ) )
155+ . sort ( ( [ a ] , [ b ] ) => a - b )
156+ . map ( ( [ weekIndex , downloads ] ) => {
157+ const weekStartDate = addDays ( rangeStartDate , weekIndex * 7 )
181158 const weekEndDate = addDays ( weekStartDate , 6 )
182159
160+ // Clamp weekEnd to the actual data range end date
161+ const clampedWeekEndDate =
162+ weekEndDate . getTime ( ) > rangeEndDate . getTime ( ) ? rangeEndDate : weekEndDate
163+
164+ const weekStartIso = toIsoDateString ( weekStartDate )
165+ const weekEndIso = toIsoDateString ( clampedWeekEndDate )
166+
183167 return {
184- weekKey,
185168 downloads,
186- weekStart : toIsoDateString ( weekStartDate ) ,
187- weekEnd : toIsoDateString ( weekEndDate ) ,
169+ weekKey : `${ weekStartIso } _${ weekEndIso } ` ,
170+ weekStart : weekStartIso ,
171+ weekEnd : weekEndIso ,
188172 }
189173 } )
190174}
@@ -341,6 +325,9 @@ export function useCharts() {
341325 )
342326 } else if ( downloadEvolutionOptions . granularity === 'week' ) {
343327 const weekCount = downloadEvolutionOptions . weeks ?? 52
328+
329+ // Full rolling weeks ending on `end` (yesterday by default)
330+ // Range length is exactly weekCount * 7 days (inclusive)
344331 start = addDays ( end , - ( weekCount * 7 ) + 1 )
345332 } else {
346333 start = addDays ( end , - 30 + 1 )
@@ -362,14 +349,14 @@ export function useCharts() {
362349
363350 const { start, end } = resolveDateRange ( resolvedOptions , resolvedCreatedIso )
364351
365- const sortedDaily = await fetchDailyRangeChunked (
366- resolvedPackageName ,
367- toIsoDateString ( start ) ,
368- toIsoDateString ( end ) ,
369- )
352+ const startIso = toIsoDateString ( start )
353+ const endIso = toIsoDateString ( end )
354+
355+ const sortedDaily = await fetchDailyRangeChunked ( resolvedPackageName , startIso , endIso )
370356
371357 if ( resolvedOptions . granularity === 'day' ) return buildDailyEvolutionFromDaily ( sortedDaily )
372- if ( resolvedOptions . granularity === 'week' ) return buildWeeklyEvolutionFromDaily ( sortedDaily )
358+ if ( resolvedOptions . granularity === 'week' )
359+ return buildRollingWeeklyEvolutionFromDaily ( sortedDaily , startIso , endIso )
373360 if ( resolvedOptions . granularity === 'month' ) return buildMonthlyEvolutionFromDaily ( sortedDaily )
374361 return buildYearlyEvolutionFromDaily ( sortedDaily )
375362 }
0 commit comments