Skip to content

Commit 2d39ddc

Browse files
fix(perps): Prevent duplicate day markers on TradingView chart (MetaMask#23179)
## **Description** This PR enhances the TradingView chart's timestamp display system to prevent duplicate day markers and improve date label clarity across different chart time ranges. **What is the reason for the change?** The TradingView chart was displaying duplicate or redundant date labels on the X-axis, particularly when viewing charts at different time scales (hourly, daily, weekly). This made the chart harder to read. ## **Changelog** CHANGELOG entry: Fixed duplicate day markers on TradingView chart X-axis and improved timestamp display clarity ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/TAT-1807 ## **Manual testing steps** ```gherkin Feature: TradingView Chart Day Markers Scenario: user views chart with different timeframes Given the user is on the Perps trading screen And the TradingView chart is loaded with data When user selects different timeframes (1H, 1D, 1W) Then the chart should show clear day markers without duplicates And day boundaries should display date + time (e.g., "17 Nov 00:15") And subsequent hours on the same day should show time only (e.g., "01:15") And today's date should show time only without the date label Scenario: user scrolls through chart history Given the user has a chart with day markers displayed When user scrolls or zooms the chart to view different time periods Then the displayed date tracking should reset And new day markers should appear correctly for the newly visible range And no duplicate day markers should appear ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> https://github.com/user-attachments/assets/8b2607df-38a3-4eb0-b05e-8a00a8e9fbee ### **After** <!-- [screenshots/recordings] --> https://github.com/user-attachments/assets/a9f5776c-0276-442c-946d-6e392c4bbd93 ## **Pre-merge author checklist** - [x] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Refactors chart time label formatting to show day+month on daily ticks and conditional date+time on intraday ticks, preventing duplicate day markers and improving clarity across zoom ranges. > > - **TradingView chart timestamp formatting** (`app/components/UI/Perps/components/TradingViewChart/TradingViewChartTemplate.tsx`): > - Add helpers `getDateString` and `isToday` for timezone-aware date checks. > - Update `formatTimestamp`: > - `DayOfMonth`: always `"DD Mon"` (e.g., `17 Nov`). > - `Hour`/`Minute`: show `"DD Mon HH:MM"` if not today, else `"HH:MM"`. > - `Second`: unchanged (HH:MM:SS). > - Fallback based on visible range mirrors above rules; longer ranges always `"DD Mon"`. > - Crosshair labels remain full date+time. > - Adjust final fallback to return `"DD Mon HH:MM"`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 418c660. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent b7b6b07 commit 2d39ddc

1 file changed

Lines changed: 121 additions & 35 deletions

File tree

app/components/UI/Perps/components/TradingViewChart/TradingViewChartTemplate.tsx

Lines changed: 121 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,22 @@ export const createTradingViewChartTemplate = (
5656
window.allCandleData = []; // Store all loaded data for zoom functionality
5757
window.visiblePriceRange = null; // Track visible price range for dynamic decimal precision
5858
59+
// Helper function to get date string in user's timezone (YYYY-MM-DD)
60+
window.getDateString = function(date, userTimezone) {
61+
const year = date.toLocaleString('en-US', { year: 'numeric', timeZone: userTimezone });
62+
const month = date.toLocaleString('en-US', { month: '2-digit', timeZone: userTimezone });
63+
const day = date.toLocaleString('en-US', { day: '2-digit', timeZone: userTimezone });
64+
return year + '-' + month + '-' + day;
65+
};
66+
67+
// Helper function to check if a date is today in user's timezone
68+
window.isToday = function(date, userTimezone) {
69+
const now = new Date();
70+
const todayString = window.getDateString(now, userTimezone);
71+
const dateString = window.getDateString(date, userTimezone);
72+
return todayString === dateString;
73+
};
74+
5975
// Cache for Intl.NumberFormat instances to avoid expensive recreation
6076
// Key: decimal count (e.g., "0", "2", "4"), Value: NumberFormat instance
6177
window.formatterCache = new Map();
@@ -200,25 +216,46 @@ export const createTradingViewChartTemplate = (
200216
timeZone: userTimezone
201217
});
202218
case 'DayOfMonth':
203-
return date.toLocaleString('en-US', {
204-
month: 'short',
219+
// Always show day + month for DayOfMonth tick type (e.g., 1D candles)
220+
// Format: "17 Nov" (day before month)
221+
const day = date.toLocaleString('en-US', {
205222
day: 'numeric',
206223
timeZone: userTimezone
207224
});
208-
case 'Hour':
209-
return date.toLocaleString('en-US', {
210-
hour: '2-digit',
211-
minute: '2-digit',
212-
hour12: false,
225+
const month = date.toLocaleString('en-US', {
226+
month: 'short',
213227
timeZone: userTimezone
214228
});
229+
return day + ' ' + month;
230+
case 'Hour':
215231
case 'Minute':
216-
return date.toLocaleString('en-US', {
217-
hour: '2-digit',
218-
minute: '2-digit',
219-
hour12: false,
220-
timeZone: userTimezone
221-
});
232+
// Show date + time if not today, otherwise just time
233+
if (!window.isToday(date, userTimezone)) {
234+
// Format: "17 Nov 00:15"
235+
const day = date.toLocaleString('en-US', {
236+
day: 'numeric',
237+
timeZone: userTimezone
238+
});
239+
const month = date.toLocaleString('en-US', {
240+
month: 'short',
241+
timeZone: userTimezone
242+
});
243+
const timeStr = date.toLocaleString('en-US', {
244+
hour: '2-digit',
245+
minute: '2-digit',
246+
hour12: false,
247+
timeZone: userTimezone
248+
});
249+
return day + ' ' + month + ' ' + timeStr;
250+
} else {
251+
// Show time only for today
252+
return date.toLocaleString('en-US', {
253+
hour: '2-digit',
254+
minute: '2-digit',
255+
hour12: false,
256+
timeZone: userTimezone
257+
});
258+
}
222259
case 'Second':
223260
return date.toLocaleString('en-US', {
224261
hour: '2-digit',
@@ -241,46 +278,95 @@ export const createTradingViewChartTemplate = (
241278
const timeSpanHours = (visibleRange.to - visibleRange.from) / 3600;
242279
243280
if (timeSpanHours <= 24) {
244-
// Less than 24 hours: show time only
245-
return date.toLocaleString('en-US', {
246-
hour: '2-digit',
247-
minute: '2-digit',
248-
hour12: false,
249-
timeZone: userTimezone
250-
});
281+
// Less than 24 hours: show date + time if not today, otherwise just time
282+
if (!window.isToday(date, userTimezone)) {
283+
// Format: "17 Nov 00:15"
284+
const day = date.toLocaleString('en-US', {
285+
day: 'numeric',
286+
timeZone: userTimezone
287+
});
288+
const month = date.toLocaleString('en-US', {
289+
month: 'short',
290+
timeZone: userTimezone
291+
});
292+
const timeStr = date.toLocaleString('en-US', {
293+
hour: '2-digit',
294+
minute: '2-digit',
295+
hour12: false,
296+
timeZone: userTimezone
297+
});
298+
return day + ' ' + month + ' ' + timeStr;
299+
} else {
300+
// Show time only for today
301+
return date.toLocaleString('en-US', {
302+
hour: '2-digit',
303+
minute: '2-digit',
304+
hour12: false,
305+
timeZone: userTimezone
306+
});
307+
}
251308
} else if (timeSpanHours <= 24 * 7) {
252-
// Less than a week: show date only
253-
return date.toLocaleString('en-US', {
254-
month: 'short',
255-
day: 'numeric',
256-
timeZone: userTimezone
257-
});
258-
} else if (timeSpanHours <= 24 * 30) {
259-
// Less than a month: show date only
260-
return date.toLocaleString('en-US', {
261-
month: 'short',
309+
// Less than a week: show date + time if not today, otherwise just time
310+
if (!window.isToday(date, userTimezone)) {
311+
// Format: "17 Nov 00:15"
312+
const day = date.toLocaleString('en-US', {
313+
day: 'numeric',
314+
timeZone: userTimezone
315+
});
316+
const month = date.toLocaleString('en-US', {
317+
month: 'short',
318+
timeZone: userTimezone
319+
});
320+
const timeStr = date.toLocaleString('en-US', {
321+
hour: '2-digit',
322+
minute: '2-digit',
323+
hour12: false,
324+
timeZone: userTimezone
325+
});
326+
return day + ' ' + month + ' ' + timeStr;
327+
} else {
328+
// Show time only for today
329+
return date.toLocaleString('en-US', {
330+
hour: '2-digit',
331+
minute: '2-digit',
332+
hour12: false,
333+
timeZone: userTimezone
334+
});
335+
}
336+
} else {
337+
// Longer ranges: always show day + month (e.g., "17 Nov")
338+
// This is especially important for 1D candles
339+
const day = date.toLocaleString('en-US', {
262340
day: 'numeric',
263341
timeZone: userTimezone
264342
});
265-
} else {
266-
// More than a month: show month only
267-
return date.toLocaleString('en-US', {
343+
const month = date.toLocaleString('en-US', {
268344
month: 'short',
269345
timeZone: userTimezone
270346
});
347+
return day + ' ' + month;
271348
}
272349
}
273350
}
274351
275352
// Final fallback: show date and time
276-
return date.toLocaleString('en-US', {
277-
month: 'short',
353+
// Format: "17 Nov 00:15"
354+
const day = date.toLocaleString('en-US', {
278355
day: 'numeric',
356+
timeZone: userTimezone
357+
});
358+
const month = date.toLocaleString('en-US', {
359+
month: 'short',
360+
timeZone: userTimezone
361+
});
362+
const timeStr = date.toLocaleString('en-US', {
279363
hour: '2-digit',
280364
minute: '2-digit',
281365
hour12: false,
282366
timeZone: userTimezone
283367
});
368+
369+
return day + ' ' + month + ' ' + timeStr;
284370
}
285371
};
286372

0 commit comments

Comments
 (0)