diff --git a/packages/cubejs-client-core/src/time.ts b/packages/cubejs-client-core/src/time.ts index dcba68a6d70dd..72d5a8bb3b1a5 100644 --- a/packages/cubejs-client-core/src/time.ts +++ b/packages/cubejs-client-core/src/time.ts @@ -8,6 +8,15 @@ dayjs.extend(quarterOfYear); dayjs.extend(duration); dayjs.extend(isoWeek); +// A custom locale for internal use that doesn't affect the global dayjs instance +const cubeInternalLocale = 'cube-internal-en'; +const customLocale = { + ...en, + name: cubeInternalLocale, + weekStart: 1 +}; +(dayjs as any).Ls[cubeInternalLocale] = customLocale; + export type SqlInterval = string; // TODO: Define a better type as unitOfTime.DurationConstructor in moment.js @@ -59,7 +68,7 @@ export const DEFAULT_GRANULARITY = 'day'; // When granularity is week, weekStart Value must be 1. However, since the client can change it globally // (https://day.js.org/docs/en/i18n/changing-locale) So the function below has been added. -export const internalDayjs = (...args: any[]): dayjs.Dayjs => dayjs(...args).locale({ ...en, weekStart: 1 }); +export const internalDayjs = (...args: any[]): dayjs.Dayjs => dayjs(...args).locale(cubeInternalLocale); export const TIME_SERIES: Record string[]> = { day: (range) => range.by('d').map(d => d.format('YYYY-MM-DDT00:00:00.000')), diff --git a/packages/cubejs-client-core/test/dayjs-isolation.test.ts b/packages/cubejs-client-core/test/dayjs-isolation.test.ts new file mode 100644 index 0000000000000..3c61e200cc53e --- /dev/null +++ b/packages/cubejs-client-core/test/dayjs-isolation.test.ts @@ -0,0 +1,54 @@ +/* globals describe,test,expect */ + +import 'jest'; +import dayjs from 'dayjs'; +import { internalDayjs } from '../src/time'; + +describe('Dayjs Instance Isolation', () => { + test('internalDayjs should not affect global dayjs instance week start', () => { + const initialWeekStart = dayjs().startOf('week').format('dddd'); + + const cubeDayjs = internalDayjs(); + expect(cubeDayjs.startOf('week').format('dddd')).toBe('Monday'); + + const afterWeekStart = dayjs().startOf('week').format('dddd'); + expect(afterWeekStart).toBe(initialWeekStart); + }); + + test('internalDayjs week calculation should use Monday as week start', () => { + const testDate = '2024-01-10'; + + const globalWeekStartBefore = dayjs(testDate).startOf('week'); + const internalWeekStart = internalDayjs(testDate).startOf('week'); + expect(internalWeekStart.format('YYYY-MM-DD')).toBe('2024-01-08'); + expect(internalWeekStart.format('dddd')).toBe('Monday'); + + const globalWeekStartAfter = dayjs(testDate).startOf('week'); + expect(globalWeekStartAfter.format('YYYY-MM-DD')).toBe(globalWeekStartBefore.format('YYYY-MM-DD')); + expect(globalWeekStartAfter.format('dddd')).toBe(globalWeekStartBefore.format('dddd')); + }); + + test('multiple calls to internalDayjs should not affect global instance', () => { + const initialWeekStart = dayjs().startOf('week').format('dddd'); + + internalDayjs('2024-01-01'); + internalDayjs('2024-02-01'); + internalDayjs('2024-03-01'); + + expect(dayjs().startOf('week').format('dddd')).toBe(initialWeekStart); + }); + + test('internalDayjs should consistently use weekStart: 1', () => { + const dates = [ + '2024-01-10', // Wednesday + '2024-02-15', // Thursday + '2024-03-20', // Wednesday + '2024-04-25', // Thursday + ]; + + dates.forEach((date) => { + const weekStart = internalDayjs(date).startOf('week'); + expect(weekStart.format('dddd')).toBe('Monday'); + }); + }); +});