Skip to content

Commit

Permalink
fix: account for DST changes while adjusting offsets (#6034)
Browse files Browse the repository at this point in the history
  • Loading branch information
djbarnwal authored and ericpgreen2 committed Nov 5, 2024
1 parent ec9447c commit 56e8fb2
Show file tree
Hide file tree
Showing 10 changed files with 117 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,22 @@
datePortion,
timePortion,
} from "@rilldata/web-common/lib/formatters";
import { timeGrainToDuration } from "@rilldata/web-common/lib/time/grains";
import { removeLocalTimezoneOffset } from "@rilldata/web-common/lib/time/timezone";
import type { V1TimeGrain } from "@rilldata/web-common/runtime-client";
export let value: Date;
export let grain: V1TimeGrain;
export let label = "value";
export let align: "left" | "right" = "left";
let valueWithoutOffset: Date | undefined;
$: if (value instanceof Date)
valueWithoutOffset = removeLocalTimezoneOffset(value);
valueWithoutOffset = removeLocalTimezoneOffset(
value,
timeGrainToDuration(grain),
);
</script>

<Tooltip
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@
* a smoothed series (showing the trend) if the time series merits it.
*/
import Tooltip from "@rilldata/web-common/components/tooltip/Tooltip.svelte";
import { copyToClipboard } from "@rilldata/web-common/lib/actions/copy-to-clipboard";
import { modified } from "@rilldata/web-common/lib/actions/modified-click";
import { guidGenerator } from "@rilldata/web-common/lib/guid";
import { timeGrainToDuration } from "@rilldata/web-common/lib/time/grains";
import { removeLocalTimezoneOffset } from "@rilldata/web-common/lib/time/timezone";
import type { V1TimeGrain } from "@rilldata/web-common/runtime-client";
import { bisector, extent, max, min } from "d3-array";
Expand All @@ -38,7 +40,6 @@
import TimestampProfileSummary from "./TimestampProfileSummary.svelte";
import TimestampTooltipContent from "./TimestampTooltipContent.svelte";
import ZoomWindow from "./ZoomWindow.svelte";
import { copyToClipboard } from "@rilldata/web-common/lib/actions/copy-to-clipboard";
const id = guidGenerator();
Expand Down Expand Up @@ -291,6 +292,7 @@
function shiftClick() {
const exportedValue = `TIMESTAMP '${removeLocalTimezoneOffset(
nearestPoint[xAccessor],
timeGrainToDuration(rollupTimeGrain),
).toISOString()}'`;
copyToClipboard(exportedValue);
}
Expand Down Expand Up @@ -422,6 +424,7 @@
{#if $coordinates.x}
<TimestampMouseoverAnnotation
point={nearestPoint}
grain={rollupTimeGrain}
{xAccessor}
{yAccessor}
/>
Expand Down Expand Up @@ -496,8 +499,18 @@

<!-- Bottom time horizon labels -->
<div class="select-none grid grid-cols-2 space-between">
<TimestampBound align="left" value={zoomMinBound} label="Min" />
<TimestampBound align="right" value={zoomMaxBound} label="Max" />
<TimestampBound
grain={rollupTimeGrain}
align="left"
value={zoomMinBound}
label="Min"
/>
<TimestampBound
grain={rollupTimeGrain}
align="right"
value={zoomMaxBound}
label="Max"
/>
</div>
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
formatInteger,
timePortion,
} from "@rilldata/web-common/lib/formatters";
import type { ScaleLinear } from "d3-scale";
import { timeGrainToDuration } from "@rilldata/web-common/lib/time/grains";
import { removeLocalTimezoneOffset } from "@rilldata/web-common/lib/time/timezone";
import type { V1TimeGrain } from "@rilldata/web-common/runtime-client";
import type { ScaleLinear } from "d3-scale";
import { getContext } from "svelte";
import type { Writable } from "svelte/store";
import { fly } from "svelte/transition";
Expand All @@ -25,7 +27,11 @@
export let point;
export let xAccessor: string;
export let yAccessor: string;
$: xLabel = removeLocalTimezoneOffset(point[xAccessor]);
export let grain: V1TimeGrain;
$: xLabel = removeLocalTimezoneOffset(
point[xAccessor],
timeGrainToDuration(grain),
);
</script>

<g>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import PercentageChange from "@rilldata/web-common/components/data-types/Percent
import { createMeasureValueFormatter } from "@rilldata/web-common/lib/number-formatting/format-measure-value";
import { formatMeasurePercentageDifference } from "@rilldata/web-common/lib/number-formatting/percentage-formatter";
import { TIME_GRAIN } from "@rilldata/web-common/lib/time/config";
import { timeGrainToDuration } from "@rilldata/web-common/lib/time/grains";
import {
addZoneOffset,
removeLocalTimezoneOffset,
Expand Down Expand Up @@ -91,7 +92,10 @@ function createColumnDefinitionForDimensions(
) {
const timeGrain = getTimeGrainFromDimension(dimensionNames?.[level]);
const dt = addZoneOffset(
removeLocalTimezoneOffset(new Date(value)),
removeLocalTimezoneOffset(
new Date(value),
timeGrainToDuration(timeGrain),
),
timeConfig?.timeZone,
);
const timeFormatter = timeFormat(
Expand Down Expand Up @@ -150,7 +154,10 @@ function formatRowDimensionValue(
if (value === "Total") return "Total";
const timeGrain = getTimeGrainFromDimension(dimension);
const dt = addZoneOffset(
removeLocalTimezoneOffset(new Date(value)),
removeLocalTimezoneOffset(
new Date(value),
timeGrainToDuration(timeGrain),
),
timeConfig?.timeZone,
);
const timeFormatter = timeFormat(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
<script lang="ts">
import VegaLiteRenderer from "@rilldata/web-common/features/canvas-components/render/VegaLiteRenderer.svelte";
import VegaRenderer from "@rilldata/web-common/features/canvas-components/render/VegaRenderer.svelte";
import {
resolveSignalField,
resolveSignalIntervalField,
resolveSignalTimeField,
} from "@rilldata/web-common/features/canvas-components/render/vega-signals";
import { getStateManagers } from "@rilldata/web-common/features/dashboards/state-managers/state-managers";
import { tableInteractionStore } from "@rilldata/web-common/features/dashboards/time-dimension-details/time-dimension-data-store";
import type { DimensionDataItem } from "@rilldata/web-common/features/dashboards/time-series/multiple-dimension-queries";
Expand All @@ -22,12 +28,6 @@
updateChartOnTableCellHover,
} from "./utils";
import { VegaSignalManager } from "./vega-signal-manager";
import VegaRenderer from "@rilldata/web-common/features/canvas-components/render/VegaRenderer.svelte";
import {
resolveSignalField,
resolveSignalTimeField,
resolveSignalIntervalField,
} from "@rilldata/web-common/features/canvas-components/render/vega-signals";
export let totalsData: TimeSeriesDatum[];
export let dimensionData: DimensionDataItem[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
} from "@rilldata/web-common/features/dashboards/time-series/timeseries-data-store";
import { EntityStatus } from "@rilldata/web-common/features/entity-management/types";
import { adjustOffsetForZone } from "@rilldata/web-common/lib/convertTimestampPreview";
import { timeGrainToDuration } from "@rilldata/web-common/lib/time/grains";
import { getAdjustedChartTime } from "@rilldata/web-common/lib/time/ranges";
import {
TimeRangePreset,
Expand Down Expand Up @@ -144,15 +145,17 @@
$: dimensionData = dimensionDataCopy;
// FIXME: move this logic to a function + write tests.
$: if ($timeControlsStore.ready) {
$: if ($timeControlsStore.ready && interval) {
// adjust scrub values for Javascript's timezone changes
scrubStart = adjustOffsetForZone(
$exploreStore?.selectedScrubRange?.start,
$exploreStore.selectedTimezone,
timeGrainToDuration(interval),
);
scrubEnd = adjustOffsetForZone(
$exploreStore?.selectedScrubRange?.end,
$exploreStore.selectedTimezone,
timeGrainToDuration(interval),
);
const slicedData = isAllTime
Expand Down
23 changes: 19 additions & 4 deletions web-common/src/features/dashboards/time-series/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,23 @@ export function niceMeasureExtents(
];
}

export function toComparisonKeys(d, offsetDuration: string, zone: string) {
export function toComparisonKeys(
d,
offsetDuration: string,
zone: string,
grainDuration: string,
) {
return Object.keys(d).reduce((acc, key) => {
if (key === "records") {
Object.entries(d.records).forEach(([key, value]) => {
acc[`comparison.${key}`] = value;
});
} else if (`comparison.${key}` === "comparison.ts") {
acc[`comparison.${key}`] = adjustOffsetForZone(d[key], zone);
acc[`comparison.${key}`] = adjustOffsetForZone(
d[key],
zone,
grainDuration,
);
acc["comparison.ts_position"] = getOffset(
acc["comparison.ts"],
offsetDuration,
Expand Down Expand Up @@ -110,7 +119,8 @@ export function prepareTimeSeries(
if (!originalPt?.ts) {
return emptyPt;
}
const ts = adjustOffsetForZone(originalPt.ts, zone);
const ts = adjustOffsetForZone(originalPt.ts, zone, timeGrainDuration);

if (!ts || typeof ts === "string") {
return emptyPt;
}
Expand All @@ -121,7 +131,12 @@ export function prepareTimeSeries(
ts_position,
bin: originalPt.bin,
...originalPt.records,
...toComparisonKeys(comparisonPt || {}, offsetDuration, zone),
...toComparisonKeys(
comparisonPt || {},
offsetDuration,
zone,
timeGrainDuration,
),
};
});
}
Expand Down
6 changes: 5 additions & 1 deletion web-common/src/lib/convertTimestampPreview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ export function convertTimestampPreview(d, removeLocalTimezoneOffset = false) {
export function adjustOffsetForZone(
ts: Date | string | undefined,
zone: string,
grainDuration: string,
) {
if (!ts) return ts;
return addZoneOffset(remove(new Date(ts)), zone);

const removedLocalOffsetdate = remove(new Date(ts), grainDuration);

return addZoneOffset(removedLocalOffsetdate, zone);
}
23 changes: 22 additions & 1 deletion web-common/src/lib/time/grains/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { V1TimeGrain } from "@rilldata/web-common/runtime-client";
import { Duration } from "luxon";
import { TIME_GRAIN } from "../config";
import { getTimeWidth } from "../transforms";
import type { TimeGrain } from "../types";
import type { AvailableTimeGrain, TimeGrain } from "../types";

export function unitToTimeGrain(unit: string): V1TimeGrain {
return (
Expand Down Expand Up @@ -165,3 +165,24 @@ export function mapDurationToGrain(duration: string): V1TimeGrain {
}
return V1TimeGrain.TIME_GRAIN_UNSPECIFIED;
}

export function timeGrainToDuration(timeGrain: V1TimeGrain): string {
if (isAvailableTimeGrain(timeGrain)) {
const grainConfig = TIME_GRAIN[timeGrain];
return grainConfig.duration;
} else {
console.warn("Requested duration for invalid time grain: ", timeGrain);
// Default to 1 day if the time grain is invalid to fail gracefully
return "P1D";
}
}

export function isAvailableTimeGrain(
grain: V1TimeGrain,
): grain is AvailableTimeGrain {
return (
grain !== V1TimeGrain.TIME_GRAIN_UNSPECIFIED &&
grain !== V1TimeGrain.TIME_GRAIN_MILLISECOND &&
grain !== V1TimeGrain.TIME_GRAIN_SECOND
);
}
24 changes: 21 additions & 3 deletions web-common/src/lib/time/timezone/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,26 @@
import { timeZoneNameToAbbreviationMap } from "@rilldata/web-common/lib/time/timezone/abbreviationMap";
import { getOffset } from "@rilldata/web-common/lib/time/transforms";
import { TimeOffsetType } from "@rilldata/web-common/lib/time/types";
import { DateTime } from "luxon";

export function removeLocalTimezoneOffset(dt: Date) {
return new Date(dt.getTime() + dt.getTimezoneOffset() * 60000);
function getDSToffset(start: Date, end: Date) {
const startDateTime = DateTime.fromJSDate(start);
const endDateTime = DateTime.fromMillis(end.getTime() - 1);

return startDateTime.offset - endDateTime.offset;
}

/**
* Removes the local timezone offset from a date.
*
* Some dates in Rill are used a range rather than a point of time.
* The runtime aggregates data and uses the start of a period to denote a range.
* For ex. 3 Nov 2024 with a daily grain would infer to [3 Nov 2024, 4 Nov 2024)
*/
export function removeLocalTimezoneOffset(dt: Date, grainDuration = "PT0S") {
const endTime = getOffset(dt, grainDuration, TimeOffsetType.ADD);
const dstOffset = getDSToffset(dt, endTime);
return new Date(dt.getTime() + (dt.getTimezoneOffset() + dstOffset) * 60000);
}

export function addZoneOffset(dt: Date, iana: string) {
Expand Down Expand Up @@ -31,7 +49,7 @@ export function getTimeZoneNameFromIANA(now: Date, iana: string): string {
return DateTime.fromJSDate(now).setZone(iana).toFormat("ZZZZZ");
}

export function getAbbreviationForIANA(now: Date, iana: string): string {
export function getAbbreviationForIANA(now: Date, iana: string) {
const zoneName = getTimeZoneNameFromIANA(now, iana);

if (zoneName in timeZoneNameToAbbreviationMap)
Expand Down

2 comments on commit 56e8fb2

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉 Published on https://ui.rilldata.com as production
🚀 Deployed on https://672b3bea73521d273661c949--rill-ui.netlify.app

Please sign in to comment.