-
Notifications
You must be signed in to change notification settings - Fork 17
Portal Dashboard Showed The Wrong Time
On the self-service portal, the dashboard and the ticket list disagreed about the same ticket. One of them was wrong β sometimes by an hour, sometimes by several, and sometimes by nothing at all, depending on the time of year and where the person was sitting.
Found while building the date and time formats work in issue #105.
Fixed in d44735e8, released as update #1195.
This is a separate fault from the one that was reported. #105 was about how dates were written; this one was about which moment they referred to.
Open the portal dashboard and note the time against a ticket. Click through to My tickets and look at the same ticket. The two could show different times.
Whether they disagreed, and by how much, depended on two things nobody would think to connect:
| Where the person was | What the dashboard did |
|---|---|
| UK in winter (GMT) | Correct, by coincidence |
| UK in summer (BST) | One hour early |
| New York in August | Four hours late |
| Sydney | Ten hours early |
A bug that is invisible for five months of the year in the country the software was written in, and then quietly appears at the end of March, is a bug that survives a long time.
The tell is that the dashboard and the ticket list format exactly the same expression:
// self-service/index.php β dashboard
const date = formatDate(t.updated_datetime || t.created_datetime);
// self-service/tickets.php β ticket list
shortDate(t.updated_datetime || t.created_datetime)Same field, same fallback, two different answers. That rules out the data and points squarely at the two formatters.
FreeITSM stores every datetime in UTC. A value arrives at the browser as a plain string with no zone marker on it:
2026-08-05 13:07:00
That string does not say what it is. It is the reader who has to know it means UTC.
The ticket list knew:
// self-service/tickets.php β correct
const d = new Date(String(s).replace(' ', 'T') + 'Z');The dashboard did not:
// self-service/index.php β the bug
const d = new Date(dateStr);Without the Z, a browser treats the string as local time. So 13:07 UTC was read as 13:07 in whatever zone the reader happened to be in, and then displayed as 13:07. The number never changed, which is precisely why it looks fine: nothing is obviously mangled, the time is simply the wrong one.
In London in August that is one hour early. In New York it is four hours late β a ticket updated at nine in the morning appears to have been updated at one in the afternoon.
Two coincidences protected it.
UTC and UK winter time are the same thing. From late October to late March, 13:07 local and 13:07 UTC really are the same moment, so the bug produces the right answer for nearly half the year.
The number always looks plausible. A wrong-instant bug does not produce Invalid Date or a nonsensical year. It produces a real time, correctly formatted, that happens to be the wrong one. There is nothing for the eye to catch.
new Date('2026-08-05 13:07:00') β with a space rather than a T β is outside what the JavaScript specification requires browsers to parse. Engines accept it by convention and are free to disagree about it. So the line was relying on unspecified behaviour as well as getting the zone wrong.
Fixing the missing Z would have addressed the symptom and left the cause.
The internal application has shared date helpers: assets/js/tz.js, and a Tz::scriptTag() that publishes the analyst's timezone to the browser. Every analyst page uses them, which is why no analyst page had this problem.
The portal loaded neither. It had no timezone plumbing whatsoever. Each portal page had rolled its own date handling inline, and two of the three happened to get it right. That is not a codebase where the third one being wrong is surprising β it is a codebase where it was only a matter of which one.
So the fix was not to add a Z. It was to give the portal the same helpers everything else uses.
The portal has a single shared header, so it took one wiring point to cover every portal page:
// self-service/includes/header.php
require_once __DIR__ . '/../../includes/timezone.php';
Tz::init(); <?php echo Tz::scriptTag(); ?>
<script src="../assets/js/tz.js?v=4"></script>All three portal date formatters then became calls to the shared family, which marks the value as UTC on the way in:
// self-service/index.php β after
function formatDate(dateStr) {
if (!dateStr) return '';
try {
return fmtDateTime(dateStr);
} catch (e) {
return dateStr;
}
}One more thing was corrected while it was open. The ticket list decided whether to show a time or a date by asking whether the value fell on today:
// before β the BROWSER's idea of today
const sameDay = d.toDateString() === today.toDateString();
// after β the same day the time is being SHOWN in
const sameDay = ymdInZone(d) === ymdInZone(today);Bucketing on one day while displaying a time from another is how a timestamp ends up labelled "yesterday" beside a time from today.
| File | What changed |
|---|---|
self-service/includes/header.php |
Loads includes/timezone.php, calls Tz::init(), emits Tz::scriptTag() and tz.js. One place, every portal page. |
self-service/index.php |
formatDate β the actual wrong-instant bug. |
self-service/tickets.php |
shortDate / fullDate onto the shared helpers; Today bucketing moved onto the display zone. |
self-service/help-centre.php |
formatDate onto the shared helpers. |
| File | Why it was fine |
|---|---|
self-service/tickets.php, help-centre.php
|
Both appended Z, so both showed the right instant. They were wrong only about the format, which is #105. |
All three portal pages were loaded for real with a portal session and checked to be publishing a timezone and a format β the plumbing being present is what makes the fix hold rather than the individual lines being right today.
Portal users are not analysts and have no personal preferences, so they follow the install-wide default. That was proved rather than assumed: the system setting was changed to DD.MM.YYYY with a 12-hour clock, the portal was reloaded and confirmed to have followed it, then it was set back.
If you use the self-service portal, times on the dashboard are now correct, and they agree with the ticket list. If you are in the UK you may notice nothing changed over the winter, because over the winter nothing was wrong.
Portal dates also now follow whatever is chosen under System β Date and time formats.
- Date and Time Formats β the reported issue this was found inside
-
Date and Time Formats β developer guide β the helpers, and why a raw
new Date(dbString)is always wrong - Timezones and Time Handling
- Bugs resolved
FreeITSM β an open-source IT Service Management platform Β· github.com/edmozley/freeitsm Β· MIT licence
- Installation
- β° Scheduled tasks (cron jobs)
- Architecture
- AI Providers
- Internationalisation (i18n)
- Timezones & Time Handling
- π Date & Time Formats
- Theming & Dark Mode
- β¨οΈ Command palette (βK)
- π Searching inside tickets
- π Attached documents
-
MobileβFriendly
- β³ π« Mobile: Tickets
- β³ π» Mobile: Assets
- β³ π Mobile: Calendar
- β³ π Mobile: Knowledge
- β³ π¦ Mobile: Service Status
- β³ πΌ Mobile: Watchtower
- β³ π§© Mobile: Problem Management
- β³ π Mobile: Change Management
- β³ πΏ Mobile: Software
- β³ β Mobile: Tasks
- β³ π§° Mobile: Techniques & Tricks
-
Security
- Layer 1 β which modules you can enter
- β³ π§© Module Access Control
- β³ π οΈ Module Access β Developer Guide
- Layer 2 β what you can administer
- β³ π Roles & Permissions
- β³ π οΈ Roles β Developer Guide
- β³ π€ Why capabilities are constants
- Layer 3 β the System module
- β³ π Admin Access Control
- Hardening
- β³ π Security review response 2026-08
- β³ π‘οΈ Security hardening 2026-08
- β³ π οΈ Security hardening 2026-08 β Developer Guide
- β³ π‘οΈ Round three β plain English
- β³ π οΈ Round three β Developer Guide
- Single Sign-On (SSO)
- ποΈ LDAP & Active Directory
- Browser Extension
- API Reference
-
π REST API β how it works
- β³ π« REST API: Tickets
- β³ π» REST API: Assets
- β³ π΄ REST API: Problems
- β³ π REST API: Changes
- β³ π REST API: Knowledge
- β³ β REST API: Tasks
- β³ ποΈ REST API: CMDB
- β³ π REST API: Contracts
- β³ ποΈ REST API: Calendar
- β³ πΏ REST API: Software
- β³ π¦ REST API: Service Status
- β³ βοΈ REST API: Morning Checks
- β³ π REST API: Forms
- β³ βοΈ REST API: Workflow
- β³ πΊοΈ REST API: Network Mapper
- β³ π§ Using the API docs page
- β³ π OpenAPI specification
- β³ β OpenAPI: kept correct
- β³ π οΈ Maintaining the catalogue
- Watchtower
-
Tickets
- β³ Mailbox Authentication
- β³ π€ Email send log
- β³ Basic IMAP mailboxes
- β³ Email rendering & images
- β³ SLA Management
- β³ WhatsApp channel
- β³ π¬ Web chat channel
- β³ π£ Slack channel
- β³ π Linking tickets
- β³ π Ticket notes: internal or shared
- β³ ποΈ Canned responses
- β³ βοΈ Limiting replies to particular senders
- β³ βοΈ Email signatures
- β³ π The public web address
- β³ π’ Ticket numbering
- β³ π Raising a ticket for someone else
- β³ π Merging tickets
- β³ β Splitting tickets
- β³ β Selecting several tickets
- β³ ποΈ The folder pane
- β³ π οΈ Snoozing tickets β Developer Guide
- β³ π₯ Collision detection
- β³ β±οΈ Time tracking
- β³ π Scheduled work in your own calendar
- Problem Management
- Tasks
- Assets
- Knowledge
- Change Management
- Calendar
- Morning Checks
- Reporting
- Software
- Forms
- Contracts
- Service Status
- π Notifications
- π¨ War Room
- Self-Service Portal
- LMS
- Process Mapper
- CMDB
- Network Mapper
- Workflows
- Issue trackers (Jira, Azure DevOps)
- System
-
Overview
- β³ π Progress tracker
- β³ Concepts & vocabulary
- β³ Email routing & mailboxes
- β³ Settings: global vs per-company
- β³ Users & self-service
- β³ Staff cross-company access
- β³ Worked examples
- β³ Pitfalls & gotchas
- β³ Scope: what it's for
- β³ π οΈ Developer Guide (make a module multi-company)
- β³ ποΈ Case study: CMDB (a linked graph)
- β³ π§ͺ Test harness (prove it's isolated)