Resolve relative report dates in the site's timezone - #25256
Draft
mneudert wants to merge 3 commits into
Draft
Conversation
mneudert
force-pushed
the
relative-date-site-timezone
branch
from
September 9, 2026 17:41
99747f2 to
f675f0a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Relative dates such as
yesterday,previous7and2026-09-01,todayresolved partly in UTC rather than in the site's timezone, so a report could be labelled with a day it did not cover, and a relative range could be counted over one. Three separate things caused it, and a site whose local day differs from UTC's is exposed to all three.ProcessedReport::getProcessedReport()built the period behindprettyDatewithPeriod\Factory::build($period, $date)and no timezone, while the rows that label describes come from the archive, which resolves the date in the site's timezone. On a UTC+12 site,date=yesterdaycounted the site's yesterday and named UTC's for half of every day.Period\Factory::makePeriodFromQueryParams()passedDate::factory('today', $timezone)as the range's default end date, andDate::factory()shifts the timestamp but hands back a UTC-labelledDate, soprevious7andlast7measured back from UTC's today. It now usesDate::factoryInTimezone(), the way the non-range branch of the same method already did.Range::generate()andRange::getRelativeToEndDate()treated an endpoint as relative only when it contained no hyphen, solast-weekread as an absolute date and resolved a day away from whatlast weekresolved to. Both now match the same keyword pattern their callers use, and a relative range start resolves in the timezone too.Range::getRelativeToEndDate()takes its endpoint straight from the request, so it normalises before matching.Date::factoryInTimezone()now trims, url-decodes and lowercases its argument, which lets it accept every spelling the case-insensitive patterns that dispatch to it accept. PreviouslyTODAY,last%20weekand a paddedtodaymissed the check and fell through toDate::factory(), resolving silently in UTC and a day off.prettyDateis now read from the period metadata of the table the report actually returned, and built from the request parameters only when the table carries none.idSitedoes not decide the archive's scope for every module:MultiSites.getAllselects over every site the user can view, and the archive applies a site timezone only when the request resolves to a single site, so the label follows the period that was resolved instead of the one that was requested. ADataTable\Mapspanning several periods is labelled with a range from the earliest start to the latest end.Where this shows up
UsersManager\Controller::getDefaultDates()maps all four ontoperiod=range. Such a user loads every page asperiod=range&date=last30, which is exactly the request the anchoring fix changes.ScheduledReports\API::generateReport()takesprettyDatestraight fromAPI.getProcessedReportand uses it for the "Date range" line on the front page of the HTML and PDF renderers and for the download filename. The scheduled cron run passes an absolute date and is unaffected; the magic keywordsgenerateReportdocuments (today,yesterday,lastWeek, ...) are the affected input.ScheduledReportsrequestsMultiSites.getAllfor it, which is the module that selects its own site set rather than the requested one, so it is the practical case for reading the label off the resolved period.JqplotGraph\Evolution,Sparklines\Config,ImageGraphandController::getGraphParamsModified()all resolve their window throughRange::getRelativeToEndDate(), so they are what the keyword-versus-hyphen fix reaches.last-week,last-monthandlast-yearare the spellings that were wrong;todayandyesterdayalready worked, since they carry no hyphen for the old check to trip over.Notes
ArchiveQueryFactory::getPeriodInfoFromQueryParam()resolves the periods it queries the archive for through the samemakePeriodFromQueryParams(), andPeriod::isMultiplePeriod()returns false whenever the period isrange, soperiod=range&date=previous7takes the branch that changed. On a site that is not on UTC's day it now covers a different window and reads a different archive. The new integration test asserts the visit count as well as the label for that request, so a window anchored a day early fails on the count.Date::factoryInTimezone()widens what it accepts rather than narrowing it, and the keywords it now takes are the ones its callers already matched before dispatching.getProcessedReport()keys its result on the period label, so in a multi-site request every site's table collides on one key and all but the last are dropped. That is pre-existing and out of scope here; the new test asserts only that the surviving table holds data, with a note on what to assert once the keying is fixed.Checklist
Review