diff --git a/docs/README.md b/docs/README.md index f61c219c89..306a04d6f1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -76,3 +76,4 @@ That's it! Your first chart is ready and we can now proceed. - [Series basics](./series-basics.md) - [Customization](./customization.md) - [Time Scale](./time-scale.md) +- [Time zones support](./time-zones.md) diff --git a/docs/time-zones.md b/docs/time-zones.md new file mode 100644 index 0000000000..3cac0afa3e --- /dev/null +++ b/docs/time-zones.md @@ -0,0 +1,111 @@ +# Working with time zones + +This doc describes what do you need to do if you want to add a time zone support to your chart. + +## Background + +By default, `lightweight-charts` doesn't support time zones in any kind, just because JavaScript doesn't an API to do that. +Things that the library uses internally includes an API to: + +- Format a date +- Get a date and/or time parts of a date object (year, month, day, hours, etc) + +Out of the box we could rely on 2 APIs: + +- [Date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) +- [Intl](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) + +And even if to format a date we could (and we do) use `Date` object with its `toLocaleString` method (and we could even pass a `timeZone` field as an option), +but what's about getting a date/time fields? + +All to solve this it seems that the only solution we have is `Date`'s getters, e.g. `getHours`. Here we could use 2 APIs: + +- UTC-based methods like `getUTCHours` to get the date/time in UTC +- Client-based methods like `getHours` to get the date/time in _a local (for the client)_ time zone + +As you can see we just unable to get date/time parts in desired time zone without using custom libraries (like `date-fns`) out of the box. + +That's why we have decided to not support time zones in any kind and we do use UTC-based methods internally all the way (you can rely on this knowledge if you wish). + +But for fairness' sake, it could be done from your side! + +## How to add time zone support to your chart + +**TL;DR** - time for every bar should be "corrected" by a time zone offset. + +The only way to do this is to change a time in your data. + +As soon as the library relies on UTC-based methods, you could change a time of your data item so in UTC it could be as it is in desired time zone. + +Lets consider the example. + +Lets say you have a bar with time `2021-01-01T10:00:00.000Z` (a string representation is just for better readability). +And you want to display your chart in `Europe/Moscow` time zone. + +According to tz database, for `Europe/Moscow` time zone a time offset at this time is `UTC+03:00`, i.e. +3 hours (pay attention that you cannot use the same offset all the time, because of DST and many other things!). + +By this means, the time for `Europe/Moscow` is `2021-01-01 13:00:00.000` (so basically you want to display this time over the UTC one). + +So to display your chart in `Europe/Moscow` time zone you need to correct the time of your bar by time zone offset, i.e. by +3 hours, so it will be `2021-01-01T13:00:00.000Z` (keep in mind that the library will use it as UTC time, but it is not correct time actually). + +Note that due a time zone offset the date could be changed as well (not only time part). + +This looks tricky, but hopefully you need to implement it once and then just forget this ever happened 😀 + +### `Date` solution + +One of possible solutions (and looks like the most simplest one) is to use approach from [this answer on StackOverflow](https://stackoverflow.com/a/54127122/3893439): + +```js +// you could use this function to convert all your times to required time zone +function timeToTz(originalTime, timeZone) { + const zonedDate = new Date(new Date(originalTime * 1000).toLocaleString('en-US', { timeZone })); + return zonedDate.getTime() / 1000; +} +``` + +#### Note about converting to a "local" time zone + +If you don't need to work with time zones in general, but only needs to support a client time zone (i.e. local), you could use the following trick: + +```js +function timeToLocal(originalTime) { + const d = new Date(originalTime * 1000); + return Date.UTC(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds()) / 1000; +} +``` + +### `date-fns-tz` solution + +You could also achieve the result by using [`date-fns-tz`](https://github.com/marnusw/date-fns-tz) library in the following way: + +```js +import { utcToZonedTime } from 'date-fns-tz'; + +function timeToTz(originalTime, timeZone) { + const zonedDate = utcToZonedTime(new Date(originalTime * 1000), timeZone); + return zonedDate.getTime() / 1000; +} +``` + +### `tzdata` solution + +If you have lots of data items and the performance of other solutions doesn't fit your requirements you could try to implement more complex solution by using raw [`tzdata`](https://www.npmjs.com/package/tzdata). + +The better performance could be achieved with this approach because: + +- you don't need to parse dates every time you want to get an offset so you could use lowerbound algorithm (which is `O(log N)`) to find an offset of very first data point quickly +- after you found an offset, you go through all data items and check whether an offset should be changed or not to the next one (based on a time of the next time shift) + +## Why we didn't implement it in the library + +- `Date` solution is quite slow (in our tests it took more than 20 seconds for 100k points) +- Albeit `date-fns-tz` solution is a bit faster that the solution with `Date` but it is still very slow (~17-18 seconds for 100k points) and additionally it requires to add another set of dependencies to the package +- `tzdata` solution requires to increase the size of the library by [more than 31kB min.gz](https://bundlephobia.com/package/tzdata) (which is almost the size of the whole library!) + +Keep in mind that time zones feature is not an issue for everybody so this is up to you to decide whether you want/need to support it or not and so far we don't want to sacrifice performance/package size for everybody by this feature. + +## Note about converting business days + +If you're using a business day for your time (either [object](./time.md#business-day-object) or [string](./time.md#business-day-string) representation), for example because of DWM nature of your data, +most likely you **shouldn't** convert that time to a zoned one, because this time represents a day.