pond-ts 0.68.0 → 0.69.0
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/AGENTS.md +12 -3
- package/API.md +79 -75
- package/CHANGELOG.md +81 -1
- package/README.md +91 -9
- package/dist/batch/partitioned-time-series.d.ts +21 -5
- package/dist/core/calendar.d.ts +2 -5
- package/dist/core/calendar.js +4 -50
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +1 -0
- package/dist/core/time-zone.d.ts +122 -0
- package/dist/core/time-zone.js +413 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/sequence/sequence.d.ts +4 -1
- package/dist/sequence/sequence.js +15 -13
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@ import { Sequence } from '../sequence/sequence.js';
|
|
|
4
4
|
import type { DurationInput } from '../core/duration.js';
|
|
5
5
|
import type { TemporalLike } from '../core/temporal.js';
|
|
6
6
|
import type { BatchSampleStrategy } from '../sequence/sample.js';
|
|
7
|
-
import type { AggregateSchema, AlignSchema, AppendColumn, BaselineSchema, DedupeKeep, DiffSchema, EventDataForSchema, FillMapping, FillStrategy, MaterializeSchema, NumericColumnNameForSchema, RollingAlignment, RollingSchema, SeriesSchema, SmoothAppendSchema, SmoothMethod, SmoothSchema, ValidatedAggregateMap } from '../schema/index.js';
|
|
7
|
+
import type { AggregateSchema, AlignSchema, AppendColumn, BaselineSchema, DedupeKeep, DiffSchema, EventDataForSchema, ValueColumnsForSchema, FillMapping, FillStrategy, MaterializeSchema, NumericColumnNameForSchema, RollingAlignment, RollingSchema, SeriesSchema, SmoothAppendSchema, SmoothMethod, SmoothSchema, ValidatedAggregateMap } from '../schema/index.js';
|
|
8
8
|
import type { ScanStep } from './operators/scan.js';
|
|
9
9
|
type SequenceLike = Sequence | BoundedSequence;
|
|
10
10
|
type AlignMethod = 'hold' | 'linear';
|
|
@@ -24,7 +24,7 @@ type AlignSample = 'begin' | 'center' | 'end';
|
|
|
24
24
|
* the one actually passed — harmless because injected columns are already
|
|
25
25
|
* optional.
|
|
26
26
|
*/
|
|
27
|
-
export type WithPartitionColumns<Mapping, By extends string> = string extends By ? Mapping : Mapping & {
|
|
27
|
+
export type WithPartitionColumns<S extends SeriesSchema, Mapping, By extends string> = string extends By ? Mapping : string extends ValueColumnsForSchema<S>[number]['name'] ? Mapping : Mapping & {
|
|
28
28
|
readonly [C in Exclude<By, keyof Mapping>]: 'first';
|
|
29
29
|
};
|
|
30
30
|
/**
|
|
@@ -74,6 +74,22 @@ export declare class PartitionedTimeSeries<S extends SeriesSchema, K extends str
|
|
|
74
74
|
#private;
|
|
75
75
|
readonly source: TimeSeries<S>;
|
|
76
76
|
readonly by: ReadonlyArray<keyof EventDataForSchema<S> & string>;
|
|
77
|
+
/**
|
|
78
|
+
* Phantom, erased at runtime. Pins `By` **contravariantly** so a view
|
|
79
|
+
* partitioned by one column cannot be assigned where a view partitioned
|
|
80
|
+
* by another is claimed (`PartitionedTimeSeries<S, K, 'host'>` ←
|
|
81
|
+
* `partitionBy('region')` is an error), while a specialised view still
|
|
82
|
+
* assigns to the legacy `PartitionedTimeSeries<S>` / `<S, K>` shape.
|
|
83
|
+
* Without it `By` only appears inside a conditional in return positions
|
|
84
|
+
* and TypeScript treats it as freely convertible (Codex finding on #724).
|
|
85
|
+
*
|
|
86
|
+
* Spelling note: **`never` (the default) is the "any / unknown column"
|
|
87
|
+
* view**, and it is the top of this pin — every specialised view assigns
|
|
88
|
+
* to it. `string` is the *bottom* (a `string`-typed `By` assigns to any
|
|
89
|
+
* literal), so do not write `PartitionedTimeSeries<S, K, string>` to mean
|
|
90
|
+
* "any column"; the `partitionBy` overloads never produce it.
|
|
91
|
+
*/
|
|
92
|
+
readonly __partitionColumns?: (by: By) => void;
|
|
77
93
|
/**
|
|
78
94
|
* Declared partition values when `partitionBy(col, { groups })` was
|
|
79
95
|
* used. When set, `toMap` iterates in declared order (not insertion
|
|
@@ -266,13 +282,13 @@ export declare class PartitionedTimeSeries<S extends SeriesSchema, K extends str
|
|
|
266
282
|
rolling<const Mapping extends ValidatedAggregateMap<S, Mapping>>(window: DurationInput, mapping: Mapping, options?: {
|
|
267
283
|
alignment?: RollingAlignment;
|
|
268
284
|
minSamples?: number;
|
|
269
|
-
}): PartitionedTimeSeries<RollingSchema<S, WithPartitionColumns<Mapping, By>>, K, By>;
|
|
285
|
+
}): PartitionedTimeSeries<RollingSchema<S, WithPartitionColumns<S, Mapping, By>>, K, By>;
|
|
270
286
|
rolling<const Mapping extends ValidatedAggregateMap<S, Mapping>>(sequence: SequenceLike, window: DurationInput, mapping: Mapping, options?: {
|
|
271
287
|
alignment?: RollingAlignment;
|
|
272
288
|
sample?: AlignSample;
|
|
273
289
|
range?: TemporalLike;
|
|
274
290
|
minSamples?: number;
|
|
275
|
-
}): PartitionedTimeSeries<AggregateSchema<S, WithPartitionColumns<Mapping, By>>, K, By>;
|
|
291
|
+
}): PartitionedTimeSeries<AggregateSchema<S, WithPartitionColumns<S, Mapping, By>>, K, By>;
|
|
276
292
|
/** Per-partition `smooth`. See {@link TimeSeries.smooth}. */
|
|
277
293
|
smooth<const Target extends NumericColumnNameForSchema<S>, const Output extends string | undefined = undefined>(column: Target, method: SmoothMethod, options: {
|
|
278
294
|
alpha: number;
|
|
@@ -332,7 +348,7 @@ export declare class PartitionedTimeSeries<S extends SeriesSchema, K extends str
|
|
|
332
348
|
/** Per-partition `aggregate`. See {@link TimeSeries.aggregate}. */
|
|
333
349
|
aggregate<const Mapping extends ValidatedAggregateMap<S, Mapping>>(sequence: SequenceLike, mapping: Mapping, options?: {
|
|
334
350
|
range?: TemporalLike;
|
|
335
|
-
}): PartitionedTimeSeries<AggregateSchema<S, WithPartitionColumns<Mapping, By>>, K, By>;
|
|
351
|
+
}): PartitionedTimeSeries<AggregateSchema<S, WithPartitionColumns<S, Mapping, By>>, K, By>;
|
|
336
352
|
}
|
|
337
353
|
export {};
|
|
338
354
|
//# sourceMappingURL=partitioned-time-series.d.ts.map
|
package/dist/core/calendar.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
export type CalendarUnit = 'day' | 'week' | 'month';
|
|
1
|
+
/** A calendar bucket unit. Boundaries are wall-clock in an IANA zone, so a `'day'` is 23, 24 or 25 hours across DST. */
|
|
2
|
+
export type CalendarUnit = 'day' | 'week' | 'month' | 'quarter' | 'year';
|
|
3
3
|
export type WeekStartsOn = 1 | 2 | 3 | 4 | 5 | 6 | 7;
|
|
4
4
|
export type TimeZoneOptions = {
|
|
5
5
|
timeZone?: string;
|
|
@@ -10,9 +10,6 @@ export type CalendarOptions = TimeZoneOptions & {
|
|
|
10
10
|
export declare function resolveTimeZone(options?: TimeZoneOptions): string;
|
|
11
11
|
export declare function normalizeWeekStartsOn(value: number | undefined): WeekStartsOn;
|
|
12
12
|
export declare function parseTimestampString(value: string, options?: TimeZoneOptions): number;
|
|
13
|
-
export declare function toPlainDateStart(instantMs: number, timeZone: string, unit: CalendarUnit, weekStartsOn: WeekStartsOn): Temporal.PlainDate;
|
|
14
|
-
export declare function plainDateToStart(date: Temporal.PlainDate, timeZone: string): Temporal.ZonedDateTime;
|
|
15
|
-
export declare function nextCalendarStart(current: Temporal.PlainDate, unit: CalendarUnit): Temporal.PlainDate;
|
|
16
13
|
export declare function dayRangeForDate(reference: string, options?: TimeZoneOptions): {
|
|
17
14
|
start: number;
|
|
18
15
|
end: number;
|
package/dist/core/calendar.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Temporal } from '@js-temporal/polyfill';
|
|
2
|
+
import { TimeZone } from './time-zone.js';
|
|
2
3
|
const DATE_ONLY_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
3
4
|
const YEAR_MONTH_RE = /^\d{4}-\d{2}$/;
|
|
4
5
|
const DATE_TIME_LOCAL_RE = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?$/;
|
|
@@ -42,51 +43,6 @@ export function parseTimestampString(value, options = {}) {
|
|
|
42
43
|
}
|
|
43
44
|
return Temporal.Instant.from(value).epochMilliseconds;
|
|
44
45
|
}
|
|
45
|
-
export function toPlainDateStart(instantMs, timeZone, unit, weekStartsOn) {
|
|
46
|
-
// **Floor to the containing millisecond.** `Temporal.Instant` refuses a
|
|
47
|
-
// fractional epoch ms outright — `fromEpochMilliseconds(1577836800000.37)`
|
|
48
|
-
// throws `epoch milliseconds must be an integer` — and a fraction is not a
|
|
49
|
-
// caller error here. A chart's wheel-zoom derives its view range from pixel
|
|
50
|
-
// positions through `xScale.invert()`, so a perfectly ordinary gesture hands
|
|
51
|
-
// us `1.7e12 + 0.37`; realizing a calendar sequence over that range then
|
|
52
|
-
// crashed the page outright.
|
|
53
|
-
//
|
|
54
|
-
// Flooring is the only sane reading rather than a papering-over: the epoch
|
|
55
|
-
// millisecond *is* the atomic unit of this model, so an over-precise input
|
|
56
|
-
// can only mean the millisecond containing it, and calendar boundaries are
|
|
57
|
-
// themselves whole milliseconds — so the bucket containing `t` and the one
|
|
58
|
-
// containing `t + 0.37` are necessarily the same. Integer inputs are
|
|
59
|
-
// untouched.
|
|
60
|
-
//
|
|
61
|
-
// `Math.floor`, not `Math.trunc`: before 1970 they disagree, and `-5.5` lies
|
|
62
|
-
// inside the millisecond spanning `[-6, -5)`, which is `-6`.
|
|
63
|
-
const zoned = Temporal.Instant.fromEpochMilliseconds(Math.floor(instantMs)).toZonedDateTimeISO(timeZone);
|
|
64
|
-
const date = zoned.toPlainDate();
|
|
65
|
-
if (unit === 'day') {
|
|
66
|
-
return date;
|
|
67
|
-
}
|
|
68
|
-
if (unit === 'month') {
|
|
69
|
-
return Temporal.PlainDate.from({
|
|
70
|
-
year: date.year,
|
|
71
|
-
month: date.month,
|
|
72
|
-
day: 1,
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
const offset = (date.dayOfWeek - weekStartsOn + 7) % 7;
|
|
76
|
-
return date.subtract({ days: offset });
|
|
77
|
-
}
|
|
78
|
-
export function plainDateToStart(date, timeZone) {
|
|
79
|
-
return date.toZonedDateTime({ timeZone }).startOfDay();
|
|
80
|
-
}
|
|
81
|
-
export function nextCalendarStart(current, unit) {
|
|
82
|
-
if (unit === 'day') {
|
|
83
|
-
return current.add({ days: 1 });
|
|
84
|
-
}
|
|
85
|
-
if (unit === 'week') {
|
|
86
|
-
return current.add({ weeks: 1 });
|
|
87
|
-
}
|
|
88
|
-
return current.add({ months: 1 });
|
|
89
|
-
}
|
|
90
46
|
export function dayRangeForDate(reference, options = {}) {
|
|
91
47
|
const timeZone = resolveTimeZone(options);
|
|
92
48
|
const start = Temporal.PlainDate.from(reference)
|
|
@@ -102,12 +58,10 @@ export function calendarRangeForReference(unit, reference, options = {}) {
|
|
|
102
58
|
const timeZone = resolveTimeZone(options);
|
|
103
59
|
const weekStartsOn = normalizeWeekStartsOn(options.weekStartsOn);
|
|
104
60
|
const referenceMs = parseTimestampString(reference, { timeZone });
|
|
105
|
-
const
|
|
106
|
-
const start = plainDateToStart(startDate, timeZone);
|
|
107
|
-
const end = plainDateToStart(nextCalendarStart(startDate, unit), timeZone);
|
|
61
|
+
const zone = TimeZone.of(timeZone);
|
|
108
62
|
return {
|
|
109
|
-
start:
|
|
110
|
-
end:
|
|
63
|
+
start: zone.startOf(unit, referenceMs, { weekStartsOn }),
|
|
64
|
+
end: zone.next(unit, referenceMs, { weekStartsOn }),
|
|
111
65
|
};
|
|
112
66
|
}
|
|
113
67
|
//# sourceMappingURL=calendar.js.map
|
package/dist/core/index.d.ts
CHANGED
|
@@ -6,4 +6,6 @@ export type { DurationInput, DurationUnit } from './duration.js';
|
|
|
6
6
|
export { ValidationError } from './errors.js';
|
|
7
7
|
export type { EventKey, IntervalInput, IntervalValue, TemporalLike, TimeRangeInput, TimestampInput, } from './temporal.js';
|
|
8
8
|
export type { CalendarOptions, CalendarUnit, TimeZoneOptions, } from './calendar.js';
|
|
9
|
+
export { TimeZone } from './time-zone.js';
|
|
10
|
+
export type { Disambiguation, StartOfOptions, ZonedParts, ZonedPartsInput, } from './time-zone.js';
|
|
9
11
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/core/index.js
CHANGED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { CalendarUnit, WeekStartsOn } from './calendar.js';
|
|
2
|
+
/**
|
|
3
|
+
* How `TimeZone.instant()` resolves a wall-clock time that a transition
|
|
4
|
+
* makes ambiguous (a fall-back repeats an hour) or skips (a spring-forward
|
|
5
|
+
* drops one). Same vocabulary and same defaults as Temporal:
|
|
6
|
+
*
|
|
7
|
+
* - `'compatible'` (default) — the earlier instant when the time repeats,
|
|
8
|
+
* the instant *after* the gap when it is skipped (02:30 on a spring-forward
|
|
9
|
+
* night becomes 03:30). This is also what `startOf('day')` needs on the
|
|
10
|
+
* rare days that have no midnight.
|
|
11
|
+
* - `'earlier'` / `'later'` — always the earlier / later of the two readings.
|
|
12
|
+
* - `'reject'` — throw a `RangeError` for either case.
|
|
13
|
+
*/
|
|
14
|
+
export type Disambiguation = 'compatible' | 'earlier' | 'later' | 'reject';
|
|
15
|
+
/** The wall-clock reading of an instant in a zone. `weekday` is ISO: 1 = Monday … 7 = Sunday. */
|
|
16
|
+
export type ZonedParts = {
|
|
17
|
+
year: number;
|
|
18
|
+
month: number;
|
|
19
|
+
day: number;
|
|
20
|
+
hour: number;
|
|
21
|
+
minute: number;
|
|
22
|
+
second: number;
|
|
23
|
+
millisecond: number;
|
|
24
|
+
weekday: 1 | 2 | 3 | 4 | 5 | 6 | 7;
|
|
25
|
+
};
|
|
26
|
+
/** The date-and-time fields `TimeZone.instant()` accepts. Time fields default to zero. */
|
|
27
|
+
export type ZonedPartsInput = {
|
|
28
|
+
year: number;
|
|
29
|
+
month: number;
|
|
30
|
+
day: number;
|
|
31
|
+
hour?: number;
|
|
32
|
+
minute?: number;
|
|
33
|
+
second?: number;
|
|
34
|
+
millisecond?: number;
|
|
35
|
+
};
|
|
36
|
+
export type StartOfOptions = {
|
|
37
|
+
/** First day of the week for `'week'`, ISO numbering (1 = Monday, default) … 7 = Sunday. */
|
|
38
|
+
weekStartsOn?: WeekStartsOn;
|
|
39
|
+
};
|
|
40
|
+
declare function assertCalendarUnit(unit: string): asserts unit is CalendarUnit;
|
|
41
|
+
/**
|
|
42
|
+
* An IANA time zone as a calendar: the one place in pond that turns instants
|
|
43
|
+
* (UTC epoch milliseconds) into wall-clock readings and calendar boundaries,
|
|
44
|
+
* and back.
|
|
45
|
+
*
|
|
46
|
+
* Instances are interned — `TimeZone.of('Europe/Berlin')` returns the same
|
|
47
|
+
* object every time — and cache the zone's offset transitions as they are
|
|
48
|
+
* discovered, so after the first query in a stretch of history every call
|
|
49
|
+
* is integer arithmetic. `Sequence.calendar` buckets with it; the charts'
|
|
50
|
+
* time axis places and labels ticks with it; the two therefore agree on
|
|
51
|
+
* every boundary by construction.
|
|
52
|
+
*
|
|
53
|
+
* A `TimeZone` holds no instant. Values (`Time`, `TimeRange`, `Interval`)
|
|
54
|
+
* stay plain UTC milliseconds; the zone is a parameter to the code that
|
|
55
|
+
* interprets them.
|
|
56
|
+
*/
|
|
57
|
+
export declare class TimeZone {
|
|
58
|
+
#private;
|
|
59
|
+
/** The canonical IANA identifier, e.g. `'America/New_York'` or `'UTC'`. */
|
|
60
|
+
readonly id: string;
|
|
61
|
+
private constructor();
|
|
62
|
+
/**
|
|
63
|
+
* Example: `TimeZone.of('Australia/Sydney')`. Looks up a zone by IANA
|
|
64
|
+
* identifier. Throws `RangeError` for an identifier the runtime does not
|
|
65
|
+
* know. Case-insensitive on input; `id` reports the canonical spelling.
|
|
66
|
+
* A fixed-offset identifier such as `'+05:30'` is accepted too, as
|
|
67
|
+
* Temporal accepts it, and behaves as a zone with a single segment.
|
|
68
|
+
*/
|
|
69
|
+
static of(id: string): TimeZone;
|
|
70
|
+
/** Example: `TimeZone.UTC.startOf('month', t)`. The UTC zone. */
|
|
71
|
+
static get UTC(): TimeZone;
|
|
72
|
+
/**
|
|
73
|
+
* Example: `TimeZone.local()`. The runtime's own zone, as the environment
|
|
74
|
+
* reports it. Resolved on every call, so a test that changes `TZ` between
|
|
75
|
+
* calls sees the change.
|
|
76
|
+
*/
|
|
77
|
+
static local(): TimeZone;
|
|
78
|
+
/** Example: `zone.offsetAt(t)`. The zone's UTC offset at an instant, in milliseconds east of UTC. */
|
|
79
|
+
offsetAt(ms: number): number;
|
|
80
|
+
/**
|
|
81
|
+
* Example: `zone.parts(t).hour`. The wall-clock reading of an instant in
|
|
82
|
+
* this zone.
|
|
83
|
+
*/
|
|
84
|
+
parts(ms: number): ZonedParts;
|
|
85
|
+
/**
|
|
86
|
+
* Example: `zone.instant({ year: 2026, month: 3, day: 29, hour: 2, minute: 30 })`.
|
|
87
|
+
* The instant at which this zone's clocks read the given wall time. When
|
|
88
|
+
* a transition makes the reading ambiguous or nonexistent,
|
|
89
|
+
* `disambiguation` decides (default `'compatible'`, as Temporal).
|
|
90
|
+
*/
|
|
91
|
+
instant(parts: ZonedPartsInput, options?: {
|
|
92
|
+
disambiguation?: Disambiguation;
|
|
93
|
+
}): number;
|
|
94
|
+
/**
|
|
95
|
+
* Example: `zone.startOf('week', t, { weekStartsOn: 7 })`. The first
|
|
96
|
+
* instant of the calendar unit containing `ms`, in this zone. On a day
|
|
97
|
+
* with no midnight (a spring-forward at 00:00) this is the first instant
|
|
98
|
+
* that exists, as `Temporal.ZonedDateTime.startOfDay` does.
|
|
99
|
+
*/
|
|
100
|
+
startOf(unit: CalendarUnit, ms: number, options?: StartOfOptions): number;
|
|
101
|
+
/**
|
|
102
|
+
* Example: `zone.next('month', t)`. The first instant of the calendar unit
|
|
103
|
+
* *after* the one containing `ms` — i.e. the exclusive end of `startOf`'s
|
|
104
|
+
* bucket.
|
|
105
|
+
*/
|
|
106
|
+
next(unit: CalendarUnit, ms: number, options?: StartOfOptions): number;
|
|
107
|
+
/**
|
|
108
|
+
* Example: `zone.abbreviation(t)` → `'EDT'`. The zone's short name at an
|
|
109
|
+
* instant, as `Intl` reports it for `locale` (default: the runtime's
|
|
110
|
+
* locale). Which zones have a conventional abbreviation is a locale
|
|
111
|
+
* question — `en-US` knows `EST`, `en-AU` knows `AEDT`, and neither knows
|
|
112
|
+
* the other's; the fallback is a `GMT±h[:mm]` offset string. Pass the
|
|
113
|
+
* viewer's locale when rendering.
|
|
114
|
+
*/
|
|
115
|
+
abbreviation(ms: number, options?: {
|
|
116
|
+
locale?: string;
|
|
117
|
+
}): string;
|
|
118
|
+
toString(): string;
|
|
119
|
+
toJSON(): string;
|
|
120
|
+
}
|
|
121
|
+
export { assertCalendarUnit };
|
|
122
|
+
//# sourceMappingURL=time-zone.d.ts.map
|