Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions build/pre-publish.js
Original file line number Diff line number Diff line change
Expand Up @@ -312,8 +312,8 @@ function singleTransformImport(code, replacement) {
return transformImport(
code.replace(/([\"\'])zrender\/src\//g, `$1zrender/${replacement}/`),
(moduleName) => {
// Ignore 'tslib' and 'echarts' in the extensions.
if (moduleName === 'tslib' || moduleName === 'echarts') {
// Ignore 'tslib', '@date-fns/tz' and 'echarts' in the extensions.
if (moduleName === 'tslib' || moduleName === 'echarts' || moduleName === "@date-fns/tz") {
return moduleName;
}
else if (moduleName === 'zrender/lib/export') {
Expand Down
11 changes: 11 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"lint:dist": "echo 'It might take a while. Please wait ...' && npx jshint --config .jshintrc-dist dist/echarts.js"
},
"dependencies": {
"@date-fns/tz": "^1.4.1",
"tslib": "2.3.0",
"zrender": "6.0.0"
},
Expand Down
3 changes: 2 additions & 1 deletion src/component/timeline/SliderTimelineView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -743,7 +743,8 @@ function createScaleByModel(model: SliderTimelineModel, axisType?: string): Scal
case 'time':
return new TimeScale({
locale: model.ecModel.getLocaleModel(),
useUTC: model.ecModel.get('useUTC')
useUTC: model.ecModel.get('useUTC'),
timezone: model.ecModel.get('timezone'),
});
default:
// default to be value
Expand Down
1 change: 1 addition & 0 deletions src/coord/axisHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ export function createScaleByModel(model: AxisBaseModel, axisType?: string): Sca
return new TimeScale({
locale: model.ecModel.getLocaleModel(),
useUTC: model.ecModel.get('useUTC'),
timezone: model.ecModel.get('timezone'),
});
default:
// case 'value'/'interval', 'log', or others.
Expand Down
45 changes: 27 additions & 18 deletions src/scale/Time.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ import {
JSDateSetterNames,
getUnitFromValue,
primaryTimeUnits,
roundTime
roundTime,
} from '../util/time';
import * as scaleHelper from './helper';
import IntervalScale from './Interval';
Expand All @@ -84,6 +84,7 @@ import { LocaleOption } from '../core/locale';
import Model from '../model/Model';
import { each, filter, indexOf, isNumber, map } from 'zrender/src/core/util';
import { ScaleBreakContext, getScaleBreakHelper } from './break';
import { TZDate } from '@date-fns/tz';

// FIXME 公用?
const bisect = function (
Expand All @@ -107,6 +108,7 @@ const bisect = function (
type TimeScaleSetting = {
locale: Model<LocaleOption>;
useUTC: boolean;
timezone?: string; // IANA timezone (e.g. "Europe/London")
modelAxisBreaks?: AxisBreakOption[];
};

Expand All @@ -128,13 +130,15 @@ class TimeScale extends IntervalScale<TimeScaleSetting> {
*/
getLabel(tick: TimeScaleTick): string {
const useUTC = this.getSetting('useUTC');
const timeZone = this.getSetting('timezone');
return format(
tick.value,
fullLeveledFormatter[
getDefaultFormatPrecisionOfInterval(getPrimaryTimeUnit(this._minLevelUnit))
] || fullLeveledFormatter.second,
useUTC,
this.getSetting('locale')
this.getSetting('locale'),
timeZone
);
}

Expand All @@ -145,7 +149,8 @@ class TimeScale extends IntervalScale<TimeScaleSetting> {
): string {
const isUTC = this.getSetting('useUTC');
const lang = this.getSetting('locale');
return leveledFormat(tick, idx, labelFormatter, lang, isUTC);
const timeZone = this.getSetting('timezone');
return leveledFormat(tick, idx, labelFormatter, lang, isUTC, timeZone);
}

/**
Expand All @@ -165,13 +170,14 @@ class TimeScale extends IntervalScale<TimeScaleSetting> {
}

const useUTC = this.getSetting('useUTC');
const timeZone = this.getSetting('timezone');

if (scaleBreakHelper && opt.breakTicks === 'only_break') {
getScaleBreakHelper().addBreaksToTicks(ticks, this._brkCtx!.breaks, this._extent);
return ticks;
}

const extent0Unit = getUnitFromValue(extent[1], useUTC);
const extent0Unit = getUnitFromValue(extent[1], useUTC, timeZone);
ticks.push({
value: extent[0],
time: {
Expand All @@ -187,12 +193,13 @@ class TimeScale extends IntervalScale<TimeScaleSetting> {
useUTC,
extent,
this._getExtentSpanWithBreaks(),
this._brkCtx
this._brkCtx,
timeZone
);

ticks = ticks.concat(innerTicks);

const extent1Unit = getUnitFromValue(extent[1], useUTC);
const extent1Unit = getUnitFromValue(extent[1], useUTC, timeZone);
ticks.push({
value: extent[1],
time: {
Expand Down Expand Up @@ -226,13 +233,13 @@ class TimeScale extends IntervalScale<TimeScaleSetting> {
getScaleBreakHelper().addBreaksToTicks(ticks, this._brkCtx!.breaks, this._extent, trimmedBrk => {
// @see `parseTimeAxisLabelFormatterDictionary`.
const lowerBrkUnitIndex = Math.max(
indexOf(primaryTimeUnits, getUnitFromValue(trimmedBrk.vmin, isUTC)),
indexOf(primaryTimeUnits, getUnitFromValue(trimmedBrk.vmax, isUTC)),
indexOf(primaryTimeUnits, getUnitFromValue(trimmedBrk.vmin, isUTC, timeZone)),
indexOf(primaryTimeUnits, getUnitFromValue(trimmedBrk.vmax, isUTC, timeZone)),
);
let upperBrkUnitIndex = 0;
for (let unitIdx = 0; unitIdx < primaryTimeUnits.length; unitIdx++) {
if (!isPrimaryUnitValueAndGreaterSame(
primaryTimeUnits[unitIdx], trimmedBrk.vmin, trimmedBrk.vmax, isUTC
primaryTimeUnits[unitIdx], trimmedBrk.vmin, trimmedBrk.vmax, isUTC, timeZone
)) {
upperBrkUnitIndex = unitIdx;
break;
Expand Down Expand Up @@ -351,10 +358,11 @@ function isPrimaryUnitValueAndGreaterSame(
unit: PrimaryTimeUnit,
valueA: number,
valueB: number,
isUTC: boolean
isUTC: boolean,
timeZone?: string
): boolean {
return roundTime(new Date(valueA), unit, isUTC).getTime()
=== roundTime(new Date(valueB), unit, isUTC).getTime();
return roundTime(new TZDate(valueA, timeZone), unit, isUTC).getTime()
=== roundTime(new TZDate(valueB, timeZone), unit, isUTC).getTime();
}

// function isUnitValueSame(
Expand Down Expand Up @@ -492,9 +500,9 @@ function getMillisecondsInterval(approxInterval: number) {

// e.g., if the input unit is 'day', start calculate ticks from the first day of
// that month to make ticks "nice".
function getFirstTimestampOfUnit(timestamp: number, unitName: TimeUnit, isUTC: boolean) {
function getFirstTimestampOfUnit(timestamp: number, unitName: TimeUnit, isUTC: boolean, timeZone?: string) {
const upperUnitIdx = Math.max(0, indexOf(primaryTimeUnits, unitName) - 1);
return roundTime(new Date(timestamp), primaryTimeUnits[upperUnitIdx], isUTC).getTime();
return roundTime(new TZDate(timestamp, timeZone), primaryTimeUnits[upperUnitIdx], isUTC).getTime();
}

function createEstimateNiceMultiple(
Expand Down Expand Up @@ -524,6 +532,7 @@ function getIntervalTicks(
extent: number[],
extentSpanWithBreaks: number,
brkCtx: ScaleBreakContext | NullUndefined,
timeZone?: string
): TimeScaleTick[] {
const safeLimit = 10000;
const unitNames = timeUnits;
Expand All @@ -548,7 +557,7 @@ function getIntervalTicks(
const estimateNiceMultiple = createEstimateNiceMultiple(setMethodName, interval);

let dateTime = minTimestamp;
const date = new Date(dateTime);
const date = new TZDate(dateTime, timeZone);

// if (isDate) {
// d -= 1; // Starts with 0; PENDING
Expand Down Expand Up @@ -593,13 +602,13 @@ function getIntervalTicks(
const newAddedTicks: ScaleTick[] = [];
const isFirstLevel = !lastLevelTicks.length;

if (isPrimaryUnitValueAndGreaterSame(getPrimaryTimeUnit(unitName), extent[0], extent[1], isUTC)) {
if (isPrimaryUnitValueAndGreaterSame(getPrimaryTimeUnit(unitName), extent[0], extent[1], isUTC, timeZone)) {
return;
}

if (isFirstLevel) {
lastLevelTicks = [{
value: getFirstTimestampOfUnit(extent[0], unitName, isUTC),
value: getFirstTimestampOfUnit(extent[0], unitName, isUTC, timeZone),
}, {
value: extent[1]
}];
Expand Down Expand Up @@ -742,7 +751,7 @@ function getIntervalTicks(
for (let i = 0; i < levelsTicksInExtent.length; ++i) {
const levelTicks = levelsTicksInExtent[i];
for (let k = 0; k < levelTicks.length; ++k) {
const unit = getUnitFromValue(levelTicks[k].value, isUTC);
const unit = getUnitFromValue(levelTicks[k].value, isUTC, timeZone);
ticks.push({
value: levelTicks[k].value,
time: {
Expand Down
13 changes: 7 additions & 6 deletions src/util/number.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
* </licenses/LICENSE-d3>).
*/

import { TZDate } from '@date-fns/tz';
import * as zrUtil from 'zrender/src/core/util';

const RADIAN_EPSILON = 1e-4;
Expand Down Expand Up @@ -369,7 +370,7 @@ const TIME_REG = /^(?:(\d{4})(?:[-\/](\d{1,2})(?:[-\/](\d{1,2})(?:[T ](\d{1,2})(
* + a timestamp, which represent a time in UTC.
* @return date Never be null/undefined. If invalid, return `new Date(NaN)`.
*/
export function parseDate(value: unknown): Date {
export function parseDate(value: unknown, timeZone?: string): Date {
if (value instanceof Date) {
return value;
}
Expand All @@ -390,14 +391,14 @@ export function parseDate(value: unknown): Date {
if (!match[8]) {
// match[n] can only be string or undefined.
// But take care of '12' + 1 => '121'.
return new Date(
return new TZDate(
+match[1],
+(match[2] || 1) - 1,
+match[3] || 1,
+match[4] || 0,
+(match[5] || 0),
+match[6] || 0,
match[7] ? +match[7].substring(0, 3) : 0
match[7] ? +match[7].substring(0, 3) : 0, timeZone
);
}
// Timezoneoffset of Javascript Date has considered DST (Daylight Saving Time,
Expand All @@ -412,22 +413,22 @@ export function parseDate(value: unknown): Date {
if (match[8].toUpperCase() !== 'Z') {
hour -= +match[8].slice(0, 3);
}
return new Date(Date.UTC(
return new TZDate(Date.UTC(
+match[1],
+(match[2] || 1) - 1,
+match[3] || 1,
hour,
+(match[5] || 0),
+match[6] || 0,
match[7] ? +match[7].substring(0, 3) : 0
));
), timeZone);
}
}
else if (value == null) {
return new Date(NaN);
}

return new Date(Math.round(value as number));
return new TZDate(Math.round(value as number), timeZone);
}

/**
Expand Down
17 changes: 10 additions & 7 deletions src/util/time.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {NullUndefined, ScaleTick} from './types';
import { getDefaultLocaleModel, getLocaleModel, SYSTEM_LANG, LocaleOption } from '../core/locale';
import Model from '../model/Model';
import { getScaleBreakHelper } from '../scale/break';
import { TZDate } from '@date-fns/tz';

export const ONE_SECOND = 1000;
export const ONE_MINUTE = ONE_SECOND * 60;
Expand Down Expand Up @@ -277,9 +278,9 @@ export function getDefaultFormatPrecisionOfInterval(timeUnit: PrimaryTimeUnit):
export function format(
// Note: The result based on `isUTC` are totally different, which can not be just simply
// substituted by the result without `isUTC`. So we make the param `isUTC` mandatory.
time: unknown, template: string, isUTC: boolean, lang?: string | Model<LocaleOption>
time: unknown, template: string, isUTC: boolean, lang?: string | Model<LocaleOption>, timeZone?: string
): string {
const date = numberUtil.parseDate(time);
const date = numberUtil.parseDate(time, timeZone);
const y = date[fullYearGetterName(isUTC)]();
const M = date[monthGetterName(isUTC)]() + 1;
const q = Math.floor((M - 1) / 3) + 1;
Expand Down Expand Up @@ -333,7 +334,8 @@ export function leveledFormat(
idx: number,
formatter: TimeAxisLabelFormatterParsed,
lang: string | Model<LocaleOption>,
isUTC: boolean
isUTC: boolean,
timeZone?: string
) {
let template = null;
if (zrUtil.isString(formatter)) {
Expand All @@ -359,19 +361,20 @@ export function leveledFormat(
}
else {
// tick may be from customTicks or timeline therefore no tick.time.
const unit = getUnitFromValue(tick.value, isUTC);
const unit = getUnitFromValue(tick.value, isUTC, timeZone);
template = formatter[unit][unit][0];
}
}

return format(new Date(tick.value), template, isUTC, lang);
return format(new TZDate(tick.value, timeZone), template, isUTC, lang);
}

export function getUnitFromValue(
value: number | string | Date,
isUTC: boolean
isUTC: boolean,
timeZone?: string
): PrimaryTimeUnit {
const date = numberUtil.parseDate(value);
const date = numberUtil.parseDate(value, timeZone);
const M = (date as any)[monthGetterName(isUTC)]() + 1;
const d = (date as any)[dateGetterName(isUTC)]();
const h = (date as any)[hoursGetterName(isUTC)]();
Expand Down
1 change: 1 addition & 0 deletions src/util/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,7 @@ export type ECUnitOption = {
darkMode?: boolean | 'auto'
textStyle?: GlobalTextStyleOption
useUTC?: boolean
timezone?: string;
hoverLayerThreshold?: number

legacyViewCoordSysCenterBase?: boolean
Expand Down
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"compilerOptions": {
"target": "ES3",


"skipLibCheck": true,
"noImplicitAny": true,
"noImplicitThis": true,
"strictBindCallApply": true,
Expand Down