Skip to content

Commit 3e8cfca

Browse files
authored
chore: set user timezone in chart (#29654)
## **Description** The advanced chart time axis was always displaying in UTC regardless of the user's locale. **Root cause:** The [`timezone`](https://www.tradingview.com/charting-library-docs/latest/api/interfaces/Charting_Library.ChartingLibraryWidgetOptions#timezone) property was missing from the `TradingView.widget` constructor. When omitted, falls back to the symbol's [`resolveSymbol` timezone](https://www.tradingview.com/charting-library-docs/latest/connecting_data/Symbology#time-zone) — hardcoded to `Etc/UTC` for our crypto data. **Fix:** Detect the user's timezone via `Intl.DateTimeFormat().resolvedOptions().timeZone` and pass it to the widget constructor. The value is validated against [TradingView's supported timezone list](https://www.tradingview.com/charting-library-docs/latest/ui_elements/timezones/#supported-time-zones) with a fallback to `Etc/UTC`. Note: the `timezone: 'Etc/UTC'` in `resolveSymbol` is unchanged, it declares data alignment, which is separate from the display timezone. ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: Fixed advanced chart displaying timestamps in UTC instead of the user's local timezone ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/ASSETS-3141 ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** <!-- Every checklist item must be consciously assessed before marking this PR as "Ready for review". A checked box means you deliberately considered that responsibility, not that you literally performed every action listed. Unchecked boxes are ambiguous: they are not an implicit "N/A" and they are not a silent "skip". See `docs/readme/ready-for-review.md` for the full checklist semantics. --> - [ ] 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). - [ ] I've completed the PR template to the best of my ability - [ ] I've included tests if applicable - [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] 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. #### Performance checks (if applicable) - [ ] I've tested on Android - Ideally on a mid-range device; emulator is acceptable - [ ] I've tested with a power user scenario - Use these [power-user SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93) to import wallets with many accounts and tokens - [ ] I've instrumented key operations with Sentry traces for production performance metrics - See [`trace()`](/app/util/trace.ts) for usage and [`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274) for an example For performance guidelines and tooling, see the [Performance Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers). ## **Pre-merge reviewer checklist** <!-- Reviewer checklist items follow the same semantics as the author checklist: an unchecked box is ambiguous, a checked box means the reviewer consciously assessed that responsibility. See `docs/readme/ready-for-review.md`. --> - [ ] 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] > **Low Risk** > Low risk: only affects AdvancedChart display settings by passing a validated `timezone` into the TradingView widget, with a safe fallback to `Etc/UTC` if detection/mapping fails. > > **Overview** > Fixes AdvancedChart timestamps always rendering in UTC by **detecting the user’s device timezone** (`Intl.DateTimeFormat().resolvedOptions().timeZone`) and passing it to `TradingView.widget` via the `timezone` option. > > Adds a whitelist of TradingView-supported IANA timezone IDs plus a small canonical-to-TradingView alias map, and **falls back to `Etc/UTC`** when the device timezone is unsupported or detection throws (applied in both `chartLogic.js` and the injected `chartLogicString.ts`). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 5496398. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 0a39d7f commit 3e8cfca

2 files changed

Lines changed: 240 additions & 0 deletions

File tree

app/components/UI/Charts/AdvancedChart/webview/chartLogic.js

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3579,6 +3579,125 @@ function initChart() {
35793579
}
35803580
: undefined;
35813581

3582+
// TradingView only supports a fixed set of IANA timezone IDs.
3583+
// If the device returns an unsupported ID we fall back to Etc/UTC.
3584+
// List of supported timezones: https://www.tradingview.com/charting-library-docs/latest/ui_elements/timezones#supported-time-zones
3585+
var TV_SUPPORTED_TIMEZONES = [
3586+
'Etc/UTC',
3587+
'Africa/Cairo',
3588+
'Africa/Casablanca',
3589+
'Africa/Johannesburg',
3590+
'Africa/Lagos',
3591+
'Africa/Nairobi',
3592+
'Africa/Tunis',
3593+
'America/Anchorage',
3594+
'America/Argentina/Buenos_Aires',
3595+
'America/Bogota',
3596+
'America/Caracas',
3597+
'America/Chicago',
3598+
'America/El_Salvador',
3599+
'America/Halifax',
3600+
'America/Juneau',
3601+
'America/Lima',
3602+
'America/Los_Angeles',
3603+
'America/Mexico_City',
3604+
'America/New_York',
3605+
'America/Phoenix',
3606+
'America/Santiago',
3607+
'America/Sao_Paulo',
3608+
'America/Toronto',
3609+
'America/Vancouver',
3610+
'Asia/Astana',
3611+
'Asia/Ashkhabad',
3612+
'Asia/Bahrain',
3613+
'Asia/Bangkok',
3614+
'Asia/Chongqing',
3615+
'Asia/Colombo',
3616+
'Asia/Dhaka',
3617+
'Asia/Dubai',
3618+
'Asia/Ho_Chi_Minh',
3619+
'Asia/Hong_Kong',
3620+
'Asia/Jakarta',
3621+
'Asia/Jerusalem',
3622+
'Asia/Karachi',
3623+
'Asia/Kabul',
3624+
'Asia/Kathmandu',
3625+
'Asia/Kolkata',
3626+
'Asia/Kuala_Lumpur',
3627+
'Asia/Kuwait',
3628+
'Asia/Manila',
3629+
'Asia/Muscat',
3630+
'Asia/Nicosia',
3631+
'Asia/Qatar',
3632+
'Asia/Riyadh',
3633+
'Asia/Seoul',
3634+
'Asia/Shanghai',
3635+
'Asia/Singapore',
3636+
'Asia/Taipei',
3637+
'Asia/Tehran',
3638+
'Asia/Tokyo',
3639+
'Asia/Yangon',
3640+
'Atlantic/Azores',
3641+
'Atlantic/Reykjavik',
3642+
'Australia/Adelaide',
3643+
'Australia/Brisbane',
3644+
'Australia/Perth',
3645+
'Australia/Sydney',
3646+
'Europe/Amsterdam',
3647+
'Europe/Athens',
3648+
'Europe/Belgrade',
3649+
'Europe/Berlin',
3650+
'Europe/Bratislava',
3651+
'Europe/Brussels',
3652+
'Europe/Bucharest',
3653+
'Europe/Budapest',
3654+
'Europe/Copenhagen',
3655+
'Europe/Dublin',
3656+
'Europe/Helsinki',
3657+
'Europe/Istanbul',
3658+
'Europe/Lisbon',
3659+
'Europe/London',
3660+
'Europe/Luxembourg',
3661+
'Europe/Madrid',
3662+
'Europe/Malta',
3663+
'Europe/Moscow',
3664+
'Europe/Oslo',
3665+
'Europe/Paris',
3666+
'Europe/Prague',
3667+
'Europe/Riga',
3668+
'Europe/Rome',
3669+
'Europe/Stockholm',
3670+
'Europe/Tallinn',
3671+
'Europe/Vienna',
3672+
'Europe/Vilnius',
3673+
'Europe/Warsaw',
3674+
'Europe/Zurich',
3675+
'Pacific/Auckland',
3676+
'Pacific/Chatham',
3677+
'Pacific/Fakaofo',
3678+
'Pacific/Honolulu',
3679+
'Pacific/Norfolk',
3680+
'US/Mountain',
3681+
];
3682+
3683+
// Intl returns canonical IANA names, but TradingView uses some legacy aliases.
3684+
var CANONICAL_TO_TV = {
3685+
'America/Denver': 'US/Mountain',
3686+
'Asia/Ashgabat': 'Asia/Ashkhabad',
3687+
'Asia/Almaty': 'Asia/Astana',
3688+
};
3689+
3690+
var userTimezone = (function () {
3691+
try {
3692+
var tz = Intl.DateTimeFormat().resolvedOptions().timeZone || 'Etc/UTC';
3693+
var mapped = CANONICAL_TO_TV[tz] || tz;
3694+
return TV_SUPPORTED_TIMEZONES.indexOf(mapped) !== -1
3695+
? mapped
3696+
: 'Etc/UTC';
3697+
} catch (_e) {
3698+
return 'Etc/UTC';
3699+
}
3700+
})();
35823701
window.chartWidget = new TradingView.widget({
35833702
symbol: window.currentSymbol,
35843703
interval: window.currentResolution || '5',
@@ -3587,6 +3706,7 @@ function initChart() {
35873706
datafeed: customDatafeed,
35883707
library_path: window.CONFIG.libraryUrl,
35893708
locale: 'en',
3709+
timezone: userTimezone,
35903710
fullscreen: false,
35913711
autosize: true,
35923712
theme: 'Dark',

app/components/UI/Charts/AdvancedChart/webview/chartLogicString.ts

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3588,6 +3588,125 @@ function initChart() {
35883588
}
35893589
: undefined;
35903590
3591+
// TradingView only supports a fixed set of IANA timezone IDs.
3592+
// If the device returns an unsupported ID we fall back to Etc/UTC.
3593+
// List of supported timezones: https://www.tradingview.com/charting-library-docs/latest/ui_elements/timezones#supported-time-zones
3594+
var TV_SUPPORTED_TIMEZONES = [
3595+
'Etc/UTC',
3596+
'Africa/Cairo',
3597+
'Africa/Casablanca',
3598+
'Africa/Johannesburg',
3599+
'Africa/Lagos',
3600+
'Africa/Nairobi',
3601+
'Africa/Tunis',
3602+
'America/Anchorage',
3603+
'America/Argentina/Buenos_Aires',
3604+
'America/Bogota',
3605+
'America/Caracas',
3606+
'America/Chicago',
3607+
'America/El_Salvador',
3608+
'America/Halifax',
3609+
'America/Juneau',
3610+
'America/Lima',
3611+
'America/Los_Angeles',
3612+
'America/Mexico_City',
3613+
'America/New_York',
3614+
'America/Phoenix',
3615+
'America/Santiago',
3616+
'America/Sao_Paulo',
3617+
'America/Toronto',
3618+
'America/Vancouver',
3619+
'Asia/Astana',
3620+
'Asia/Ashkhabad',
3621+
'Asia/Bahrain',
3622+
'Asia/Bangkok',
3623+
'Asia/Chongqing',
3624+
'Asia/Colombo',
3625+
'Asia/Dhaka',
3626+
'Asia/Dubai',
3627+
'Asia/Ho_Chi_Minh',
3628+
'Asia/Hong_Kong',
3629+
'Asia/Jakarta',
3630+
'Asia/Jerusalem',
3631+
'Asia/Karachi',
3632+
'Asia/Kabul',
3633+
'Asia/Kathmandu',
3634+
'Asia/Kolkata',
3635+
'Asia/Kuala_Lumpur',
3636+
'Asia/Kuwait',
3637+
'Asia/Manila',
3638+
'Asia/Muscat',
3639+
'Asia/Nicosia',
3640+
'Asia/Qatar',
3641+
'Asia/Riyadh',
3642+
'Asia/Seoul',
3643+
'Asia/Shanghai',
3644+
'Asia/Singapore',
3645+
'Asia/Taipei',
3646+
'Asia/Tehran',
3647+
'Asia/Tokyo',
3648+
'Asia/Yangon',
3649+
'Atlantic/Azores',
3650+
'Atlantic/Reykjavik',
3651+
'Australia/Adelaide',
3652+
'Australia/Brisbane',
3653+
'Australia/Perth',
3654+
'Australia/Sydney',
3655+
'Europe/Amsterdam',
3656+
'Europe/Athens',
3657+
'Europe/Belgrade',
3658+
'Europe/Berlin',
3659+
'Europe/Bratislava',
3660+
'Europe/Brussels',
3661+
'Europe/Bucharest',
3662+
'Europe/Budapest',
3663+
'Europe/Copenhagen',
3664+
'Europe/Dublin',
3665+
'Europe/Helsinki',
3666+
'Europe/Istanbul',
3667+
'Europe/Lisbon',
3668+
'Europe/London',
3669+
'Europe/Luxembourg',
3670+
'Europe/Madrid',
3671+
'Europe/Malta',
3672+
'Europe/Moscow',
3673+
'Europe/Oslo',
3674+
'Europe/Paris',
3675+
'Europe/Prague',
3676+
'Europe/Riga',
3677+
'Europe/Rome',
3678+
'Europe/Stockholm',
3679+
'Europe/Tallinn',
3680+
'Europe/Vienna',
3681+
'Europe/Vilnius',
3682+
'Europe/Warsaw',
3683+
'Europe/Zurich',
3684+
'Pacific/Auckland',
3685+
'Pacific/Chatham',
3686+
'Pacific/Fakaofo',
3687+
'Pacific/Honolulu',
3688+
'Pacific/Norfolk',
3689+
'US/Mountain',
3690+
];
3691+
3692+
// Intl returns canonical IANA names, but TradingView uses some legacy aliases.
3693+
var CANONICAL_TO_TV = {
3694+
'America/Denver': 'US/Mountain',
3695+
'Asia/Ashgabat': 'Asia/Ashkhabad',
3696+
'Asia/Almaty': 'Asia/Astana',
3697+
};
3698+
3699+
var userTimezone = (function () {
3700+
try {
3701+
var tz = Intl.DateTimeFormat().resolvedOptions().timeZone || 'Etc/UTC';
3702+
var mapped = CANONICAL_TO_TV[tz] || tz;
3703+
return TV_SUPPORTED_TIMEZONES.indexOf(mapped) !== -1
3704+
? mapped
3705+
: 'Etc/UTC';
3706+
} catch (_e) {
3707+
return 'Etc/UTC';
3708+
}
3709+
})();
35913710
window.chartWidget = new TradingView.widget({
35923711
symbol: window.currentSymbol,
35933712
interval: window.currentResolution || '5',
@@ -3596,6 +3715,7 @@ function initChart() {
35963715
datafeed: customDatafeed,
35973716
library_path: window.CONFIG.libraryUrl,
35983717
locale: 'en',
3718+
timezone: userTimezone,
35993719
fullscreen: false,
36003720
autosize: true,
36013721
theme: 'Dark',

0 commit comments

Comments
 (0)