diff --git a/apps/web/app/(use-page-wrapper)/insights/page.tsx b/apps/web/app/(use-page-wrapper)/insights/page.tsx
index f5180928dc..53f930568d 100644
--- a/apps/web/app/(use-page-wrapper)/insights/page.tsx
+++ b/apps/web/app/(use-page-wrapper)/insights/page.tsx
@@ -1,4 +1,10 @@
import { _generateMetadata } from "app/_utils";
+import { cookies, headers } from "next/headers";
+
+import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
+import prisma from "@calcom/prisma";
+
+import { buildLegacyRequest } from "@lib/buildLegacyCtx";
import InsightsPage from "~/insights/insights-view";
@@ -11,6 +17,17 @@ export const generateMetadata = async () =>
"/insights"
);
-export default async function Page() {
- return ;
-}
+const ServerPage = async () => {
+ const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
+
+ const { timeZone } = await prisma.user.findUniqueOrThrow({
+ where: { id: session?.user.id ?? -1 },
+ select: {
+ timeZone: true,
+ },
+ });
+
+ return ;
+};
+
+export default ServerPage;
diff --git a/apps/web/modules/insights/insights-view.tsx b/apps/web/modules/insights/insights-view.tsx
index 1c6870906a..c2f4405fa2 100644
--- a/apps/web/modules/insights/insights-view.tsx
+++ b/apps/web/modules/insights/insights-view.tsx
@@ -28,9 +28,9 @@ import { useInsightsBookings } from "@calcom/features/insights/hooks/useInsights
import { useInsightsOrgTeams } from "@calcom/features/insights/hooks/useInsightsOrgTeams";
import { useLocale } from "@calcom/lib/hooks/useLocale";
-export default function InsightsPage() {
+export default function InsightsPage({ timeZone }: { timeZone: string }) {
return (
-
+
diff --git a/packages/features/data-table/DataTableProvider.tsx b/packages/features/data-table/DataTableProvider.tsx
index 34f1528c5f..ef4c0f3b06 100644
--- a/packages/features/data-table/DataTableProvider.tsx
+++ b/packages/features/data-table/DataTableProvider.tsx
@@ -64,6 +64,8 @@ export type DataTableContextType = {
searchTerm: string;
setSearchTerm: (searchTerm: string | null) => void;
+
+ timeZone?: string;
};
export const DataTableContext = createContext(null);
@@ -75,6 +77,7 @@ interface DataTableProviderProps {
ctaContainerClassName?: string;
defaultPageSize?: number;
segments?: FilterSegmentOutput[];
+ timeZone?: string;
preferredSegmentId?: number | null;
}
@@ -85,6 +88,7 @@ export function DataTableProvider({
defaultPageSize = DEFAULT_PAGE_SIZE,
ctaContainerClassName = CTA_CONTAINER_CLASS_NAME,
segments: providedSegments,
+ timeZone,
preferredSegmentId,
}: DataTableProviderProps) {
const filterToOpen = useRef(undefined);
@@ -245,6 +249,7 @@ export function DataTableProvider({
isSegmentEnabled,
searchTerm,
setSearchTerm: setDebouncedSearchTerm,
+ timeZone,
}}>
{children}
diff --git a/packages/features/data-table/hooks/useChangeTimeZoneWithPreservedLocalTime.ts b/packages/features/data-table/hooks/useChangeTimeZoneWithPreservedLocalTime.ts
new file mode 100644
index 0000000000..549e6192e7
--- /dev/null
+++ b/packages/features/data-table/hooks/useChangeTimeZoneWithPreservedLocalTime.ts
@@ -0,0 +1,28 @@
+import { useMemo } from "react";
+
+import dayjs from "@calcom/dayjs";
+
+import { preserveLocalTime } from "../lib/preserveLocalTime";
+import { useDataTable } from "./useDataTable";
+
+/**
+ * Converts a timestamp to maintain the same local time in a different timezone.
+ *
+ * For example, if it's midnight (00:00) in Paris time:
+ * - Input : "2025-05-22T22:00:00.000Z" (Midnight/00:00 in Paris)
+ * - Output: "2025-05-22T15:00:00.000Z" (Midnight/00:00 in Seoul)
+ *
+ * This ensures that times like midnight (00:00) or end of day (23:59)
+ * remain at those exact local times when converting between timezones.
+ * The output timestamp is based on the timezone in the user's profile settings.
+ */
+export function useChangeTimeZoneWithPreservedLocalTime(isoString: string) {
+ const { timeZone: profileTimeZone } = useDataTable();
+ return useMemo(() => {
+ const currentTimeZone = dayjs.tz.guess();
+ if (!profileTimeZone || currentTimeZone === profileTimeZone) {
+ return isoString;
+ }
+ return preserveLocalTime(isoString, currentTimeZone, profileTimeZone);
+ }, [isoString, profileTimeZone]);
+}
diff --git a/packages/trpc/react/hooks/__tests__/preserveLocalTime.test.ts b/packages/features/data-table/lib/__tests__/preserveLocalTime.test.ts
similarity index 98%
rename from packages/trpc/react/hooks/__tests__/preserveLocalTime.test.ts
rename to packages/features/data-table/lib/__tests__/preserveLocalTime.test.ts
index e9728396d8..a23273cd09 100644
--- a/packages/trpc/react/hooks/__tests__/preserveLocalTime.test.ts
+++ b/packages/features/data-table/lib/__tests__/preserveLocalTime.test.ts
@@ -2,7 +2,7 @@ import { describe, it, expect } from "vitest";
import dayjs from "@calcom/dayjs";
-import { preserveLocalTime } from "../useUserTimePreferences";
+import { preserveLocalTime } from "../preserveLocalTime";
describe("preserveLocalTime", () => {
it("should preserve midnight (00:00) when converting from Paris to Seoul", () => {
diff --git a/packages/features/data-table/lib/preserveLocalTime.ts b/packages/features/data-table/lib/preserveLocalTime.ts
new file mode 100644
index 0000000000..783e24ac82
--- /dev/null
+++ b/packages/features/data-table/lib/preserveLocalTime.ts
@@ -0,0 +1,31 @@
+import dayjs from "@calcom/dayjs";
+
+/**
+ * Converts a timestamp to maintain the same local time in a different timezone.
+ *
+ * For example, if it's midnight (00:00) in Paris time:
+ * - Input : "2025-05-22T22:00:00.000Z" (Midnight/00:00 in Paris)
+ * - Output: "2025-05-22T15:00:00.000Z" (Midnight/00:00 in Seoul)
+ *
+ * This ensures that times like midnight (00:00) or end of day (23:59)
+ * remain at those exact local times when converting between timezones.
+ * The output timestamp is based on the timezone in the user's profile settings.
+ */
+export const preserveLocalTime = (isoString: string, originalTimeZone: string, targetTimeZone: string) => {
+ // Parse the input time
+ const time = dayjs(isoString).tz(originalTimeZone);
+ // Get the wall clock time components
+ const hours = time.hour();
+ const minutes = time.minute();
+ const seconds = time.second();
+ const milliseconds = time.millisecond();
+
+ // Create a new date in target timezone with same wall clock time
+ return dayjs
+ .tz(time.format("YYYY-MM-DD"), targetTimeZone)
+ .hour(hours)
+ .minute(minutes)
+ .second(seconds)
+ .millisecond(milliseconds)
+ .toISOString();
+};
diff --git a/packages/features/insights/hooks/useInsightsParameters.ts b/packages/features/insights/hooks/useInsightsParameters.ts
index b287cfe7db..f82f168dfe 100644
--- a/packages/features/insights/hooks/useInsightsParameters.ts
+++ b/packages/features/insights/hooks/useInsightsParameters.ts
@@ -8,19 +8,18 @@ import {
ZSingleSelectFilterValue,
ZDateRangeFilterValue,
} from "@calcom/features/data-table";
+import { useChangeTimeZoneWithPreservedLocalTime } from "@calcom/features/data-table/hooks/useChangeTimeZoneWithPreservedLocalTime";
import {
getDefaultStartDate,
getDefaultEndDate,
CUSTOM_PRESET_VALUE,
type PresetOptionValue,
} from "@calcom/features/data-table/lib/dateRange";
-import { useUserTimePreferences } from "@calcom/trpc/react/hooks/useUserTimePreferences";
import { useInsightsOrgTeams } from "./useInsightsOrgTeams";
export function useInsightsParameters() {
const { isAll, teamId, userId } = useInsightsOrgTeams();
- const { preserveLocalTime } = useUserTimePreferences();
const memberUserIds = useFilterValue("bookingUserId", ZMultiSelectFilterValue)?.data as
| number[]
@@ -32,18 +31,20 @@ export function useInsightsParameters() {
// TODO for future: this preserving local time & startOf & endOf should be handled
// from DateRangeFilter out of the box.
// When we do it, we also need to remove those timezone handling logic from the backend side at the same time.
- const startDate = useMemo(() => {
- const timestamp = dayjs(createdAtRange?.startDate ?? getDefaultStartDate().toISOString())
- .startOf("day")
- .toISOString();
- return preserveLocalTime(timestamp);
- }, [createdAtRange?.startDate, preserveLocalTime]);
- const endDate = useMemo(() => {
- const timestamp = dayjs(createdAtRange?.endDate ?? getDefaultEndDate().toISOString())
- .endOf("day")
- .toISOString();
- return preserveLocalTime(timestamp);
- }, [createdAtRange?.endDate, preserveLocalTime]);
+ const startDate = useChangeTimeZoneWithPreservedLocalTime(
+ useMemo(() => {
+ return dayjs(createdAtRange?.startDate ?? getDefaultStartDate().toISOString())
+ .startOf("day")
+ .toISOString();
+ }, [createdAtRange?.startDate])
+ );
+ const endDate = useChangeTimeZoneWithPreservedLocalTime(
+ useMemo(() => {
+ return dayjs(createdAtRange?.endDate ?? getDefaultEndDate().toISOString())
+ .endOf("day")
+ .toISOString();
+ }, [createdAtRange?.endDate])
+ );
const dateRangePreset = useMemo(() => {
return (createdAtRange?.preset as PresetOptionValue) ?? CUSTOM_PRESET_VALUE;
diff --git a/packages/trpc/react/hooks/useUserTimePreferences.ts b/packages/trpc/react/hooks/useUserTimePreferences.ts
deleted file mode 100644
index 80090b630d..0000000000
--- a/packages/trpc/react/hooks/useUserTimePreferences.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import { useMemo, useCallback } from "react";
-
-import dayjs from "@calcom/dayjs";
-
-import { trpc } from "../trpc";
-
-export const preserveLocalTime = (isoString: string, originalTimeZone: string, targetTimeZone: string) => {
- // Parse the input time
- const time = dayjs(isoString).tz(originalTimeZone);
- // Get the wall clock time components
- const hours = time.hour();
- const minutes = time.minute();
- const seconds = time.second();
- const milliseconds = time.millisecond();
-
- // Create a new date in target timezone with same wall clock time
- return dayjs
- .tz(time.format("YYYY-MM-DD"), targetTimeZone)
- .hour(hours)
- .minute(minutes)
- .second(seconds)
- .millisecond(milliseconds)
- .toISOString();
-};
-
-export function useUserTimePreferences() {
- const { data } = trpc.viewer.me.getUserTimePreferences.useQuery(undefined, {
- staleTime: 5 * 60 * 1000,
- });
-
- const timeFormat = useMemo(() => data?.timeFormat ?? 12, [data?.timeFormat]);
- const timeZone = useMemo(() => data?.timeZone ?? dayjs.tz.guess(), [data?.timeZone]);
-
- /**
- * Converts a timestamp to maintain the same local time in a different timezone.
- *
- * For example, if it's midnight (00:00) in Paris time:
- * - Input : "2025-05-22T22:00:00.000Z" (Midnight/00:00 in Paris)
- * - Output: "2025-05-22T15:00:00.000Z" (Midnight/00:00 in Seoul)
- *
- * This ensures that times like midnight (00:00) or end of day (23:59)
- * remain at those exact local times when converting between timezones.
- * The output timestamp is based on the timezone in the user's profile settings.
- */
- const _preserveLocalTime = useCallback(
- (timestamp: string) => preserveLocalTime(timestamp, dayjs.tz.guess(), timeZone),
- [timeZone]
- );
-
- return {
- timeFormat,
- timeZone,
- preserveLocalTime: _preserveLocalTime,
- };
-}
diff --git a/packages/trpc/server/routers/viewer/me/_router.tsx b/packages/trpc/server/routers/viewer/me/_router.tsx
index 0d0e2f6ebb..15e3cb9678 100644
--- a/packages/trpc/server/routers/viewer/me/_router.tsx
+++ b/packages/trpc/server/routers/viewer/me/_router.tsx
@@ -18,10 +18,6 @@ export const meRouter = router({
return handler({ ctx });
}),
get,
- getUserTimePreferences: authedProcedure.query(async ({ ctx }) => {
- const handler = (await import("./getUserTimePreferences.handler")).getUserTimePreferencesHandler;
- return handler({ ctx });
- }),
getUserTopBanners: authedProcedure.query(async ({ ctx }) => {
const handler = (await import("./getUserTopBanners.handler")).getUserTopBannersHandler;
return handler({ ctx });
diff --git a/packages/trpc/server/routers/viewer/me/getUserTimePreferences.handler.ts b/packages/trpc/server/routers/viewer/me/getUserTimePreferences.handler.ts
deleted file mode 100644
index 0dd4f0f8a3..0000000000
--- a/packages/trpc/server/routers/viewer/me/getUserTimePreferences.handler.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import type { Session } from "next-auth";
-
-import type { TrpcSessionUser } from "@calcom/trpc/server/types";
-
-type GetUserTimePreferencesOptions = {
- ctx: {
- user: NonNullable;
- session: Session;
- };
-};
-
-export const getUserTimePreferencesHandler = async ({ ctx }: GetUserTimePreferencesOptions) => {
- const { user } = ctx;
-
- return {
- timeFormat: user.timeFormat ?? 12,
- timeZone: user.timeZone,
- };
-};