@opendata-ai/openchart-core 6.2.0 → 6.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opendata-ai/openchart-core",
3
- "version": "6.2.0",
3
+ "version": "6.2.1",
4
4
  "description": "Types, theme, colors, accessibility, and utilities for openchart",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Riley Hilliard",
@@ -136,26 +136,32 @@ const GRANULARITY_FORMATS: Record<DateGranularity, string> = {
136
136
  * @param value - Date object, ISO string, or timestamp number.
137
137
  * @param locale - Locale string (currently unused, reserved for i18n).
138
138
  * @param granularity - Time granularity for format selection.
139
+ * @param useUtc - Whether to infer granularity and format using UTC methods.
140
+ * Pass `false` when formatting ticks from a local-time scale (d3 scaleTime),
141
+ * so that e.g. midnight local isn't misread as an intra-day UTC time.
142
+ * Defaults to `true` for backward compatibility.
139
143
  */
140
144
  export function formatDate(
141
145
  value: Date | string | number,
142
146
  _locale?: string,
143
147
  granularity?: DateGranularity,
148
+ useUtc: boolean = true,
144
149
  ): string {
145
150
  const date = value instanceof Date ? value : new Date(value);
146
151
  if (Number.isNaN(date.getTime())) return String(value);
147
152
 
148
- const gran = granularity ?? inferGranularity(date);
153
+ const gran = granularity ?? inferGranularity(date, useUtc);
149
154
 
150
155
  // Special handling for quarter (not a d3 format token)
151
156
  if (gran === 'quarter') {
152
- const q = Math.ceil((date.getMonth() + 1) / 3);
157
+ const q = useUtc
158
+ ? Math.ceil((date.getUTCMonth() + 1) / 3)
159
+ : Math.ceil((date.getMonth() + 1) / 3);
153
160
  return `Q${q} ${date.getFullYear()}`;
154
161
  }
155
162
 
156
163
  const formatStr = GRANULARITY_FORMATS[gran];
157
- // Use UTC format for year/month/day to avoid timezone shifts
158
- if (['year', 'month', 'day'].includes(gran)) {
164
+ if (useUtc) {
159
165
  return utcFormat(formatStr)(date);
160
166
  }
161
167
  return timeFormat(formatStr)(date);
@@ -181,12 +187,20 @@ export function buildTemporalFormatter(
181
187
  /**
182
188
  * Infer the appropriate granularity from a date value.
183
189
  * If time components are all zero, assume day or higher.
190
+ *
191
+ * @param useUtc - When true, inspect UTC fields. When false, inspect local-time
192
+ * fields. This must match the D3 scale type: scaleUtc -> true, scaleTime -> false.
184
193
  */
185
- function inferGranularity(date: Date): DateGranularity {
186
- if (date.getUTCHours() !== 0 || date.getUTCMinutes() !== 0) {
187
- return date.getUTCMinutes() !== 0 ? 'minute' : 'hour';
194
+ function inferGranularity(date: Date, useUtc: boolean = true): DateGranularity {
195
+ const hours = useUtc ? date.getUTCHours() : date.getHours();
196
+ const minutes = useUtc ? date.getUTCMinutes() : date.getMinutes();
197
+ const day = useUtc ? date.getUTCDate() : date.getDate();
198
+ const month = useUtc ? date.getUTCMonth() : date.getMonth();
199
+
200
+ if (hours !== 0 || minutes !== 0) {
201
+ return minutes !== 0 ? 'minute' : 'hour';
188
202
  }
189
- if (date.getUTCDate() !== 1) return 'day';
190
- if (date.getUTCMonth() !== 0) return 'month';
203
+ if (day !== 1) return 'day';
204
+ if (month !== 0) return 'month';
191
205
  return 'year';
192
206
  }