Skip to content

Commit 642f4f7

Browse files
committed
consistent intervals with interval.every
1 parent 9e8dc94 commit 642f4f7

15 files changed

Lines changed: 115 additions & 164 deletions

README.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ The returned filtered interval does not support [*interval*.count](#interval_cou
129129

130130
<a name="interval_every" href="#interval_every">#</a> <i>interval</i>.<b>every</b>(<i>step</i>) · [Source](https://github.com/d3/d3-time/blob/main/src/interval.js)
131131

132-
Returns a [filtered](#interval_filter) view of this interval representing every *step*th date. The meaning of *step* is dependent on this interval’s parent interval as defined by the field function. For example, [d3.timeMinute](#timeMinute).every(15) returns an interval representing every fifteen minutes, starting on the hour: :00, :15, :30, :45, <i>etc.</i> Note that for some intervals, the resulting dates may not be uniformly-spaced; [d3.timeDay](#timeDay)’s parent interval is [d3.timeMonth](#timeMonth), and thus the interval number resets at the start of each month. If *step* is not valid, returns null. If *step* is one, returns this interval.
132+
Returns a [filtered](#interval_filter) view of this interval representing every *step*th date. ~~The meaning of *step* is dependent on this interval’s parent interval as defined by the field function.~~ For example, [d3.timeMinute](#timeMinute).every(15) returns an interval representing every fifteen minutes, starting on the hour: :00, :15, :30, :45, <i>etc.</i> ~~Note that for some intervals, the resulting dates may not be uniformly-spaced; [d3.timeDay](#timeDay)’s parent interval is [d3.timeMonth](#timeMonth), and thus the interval number resets at the start of each month.~~ TODO They are now uniformly spaced, but they may not be aligned with the parent interval; for example, [d3.timeHour](#timeHour).every(12) may not return 12AM or 12PM due to daylight savings time. If *step* is not valid, returns null. If *step* is one, returns this interval.
133133

134134
This method can be used in conjunction with [*interval*.range](#interval_range) to ensure that two overlapping ranges are consistent. For example, this range contains odd days:
135135

@@ -169,7 +169,7 @@ The *offset* function takes a date and an integer step as arguments and advances
169169

170170
The optional *count* function takes a start date and an end date, already floored to the current interval, and returns the number of boundaries between the start (exclusive) and end (inclusive). If a *count* function is not specified, the returned interval does not expose [*interval*.count](#interval_count) or [*interval*.every](#interval_every) methods. Note: due to an internal optimization, the specified *count* function must not invoke *interval*.count on other time intervals.
171171

172-
The optional *field* function takes a date, already floored to the current interval, and returns the field value of the specified date, corresponding to the number of boundaries between this date (exclusive) and the latest previous parent boundary. For example, for the [d3.timeDay](#timeDay) interval, this returns the number of days since the start of the month. If a *field* function is not specified, it defaults to counting the number of interval boundaries since the UNIX epoch of January 1, 1970 UTC. The *field* function defines the behavior of [*interval*.every](#interval_every).
172+
~~The optional *field* function takes a date, already floored to the current interval, and returns the field value of the specified date, corresponding to the number of boundaries between this date (exclusive) and the latest previous parent boundary. For example, for the [d3.timeDay](#timeDay) interval, this returns the number of days since the start of the month. If a *field* function is not specified, it defaults to counting the number of interval boundaries since the UNIX epoch of January 1, 1970 UTC. The *field* function defines the behavior of [*interval*.every](#interval_every).~~ TODO This is now an *epoch* argument.
173173

174174
### Intervals
175175

@@ -197,9 +197,8 @@ Hours (e.g., 01:00 AM); 60 minutes. Note that advancing time by one hour in loca
197197

198198
<a name="timeDay" href="#timeDay">#</a> d3.<b>timeDay</b> · [Source](https://github.com/d3/d3-time/blob/main/src/day.js "Source")
199199
<br><a href="#timeDay">#</a> d3.<b>utcDay</b> · [Source](https://github.com/d3/d3-time/blob/main/src/day.js)
200-
<br><a href="#timeDay">#</a> d3.<b>unixDay</b> · [Source](https://github.com/d3/d3-time/blob/main/src/day.js)
201200

202-
Days (e.g., February 7, 2012 at 12:00 AM); typically 24 hours. Days in local time may range from 23 to 25 hours due to daylight saving. d3.unixDay is like [d3.utcDay](#timeDay), except it counts days since the UNIX epoch (January 1, 1970) such that *interval*.every returns uniformly-spaced dates rather than varying based on day-of-month.
201+
Days (e.g., February 7, 2012 at 12:00 AM); typically 24 hours. Days in local time may range from 23 to 25 hours due to daylight saving.
203202

204203
<a name="timeWeek" href="#timeWeek">#</a> d3.<b>timeWeek</b> · [Source](https://github.com/d3/d3-time/blob/main/src/week.js "Source")
205204
<br><a href="#timeWeek">#</a> d3.<b>utcWeek</b> · [Source](https://github.com/d3/d3-time/blob/main/src/utcWeek.js "Source")
@@ -277,9 +276,8 @@ Aliases for [d3.timeHour](#timeHour).[range](#interval_range) and [d3.utcHour](#
277276

278277
<a name="timeDays" href="#timeDays">#</a> d3.<b>timeDays</b>(<i>start</i>, <i>stop</i>[, <i>step</i>]) · [Source](https://github.com/d3/d3-time/blob/main/src/day.js)
279278
<br><a href="#timeDays">#</a> d3.<b>utcDays</b>(<i>start</i>, <i>stop</i>[, <i>step</i>]) · [Source](https://github.com/d3/d3-time/blob/main/src/day.js)
280-
<br><a href="#timeDays">#</a> d3.<b>unixDays</b>(<i>start</i>, <i>stop</i>[, <i>step</i>]) · [Source](https://github.com/d3/d3-time/blob/main/src/day.js)
281279

282-
Aliases for [d3.timeDay](#timeDay).[range](#interval_range), [d3.utcDay](#timeDay).[range](#interval_range), and [d3.unixDay](#timeDay).[range](#interval_range).
280+
Aliases for [d3.timeDay](#timeDay).[range](#interval_range) and [d3.utcDay](#timeDay).[range](#interval_range).
283281

284282
<a name="timeWeeks" href="#timeWeeks">#</a> d3.<b>timeWeeks</b>(<i>start</i>, <i>stop</i>[, <i>step</i>])
285283
<br><a href="#timeWeeks">#</a> d3.<b>utcWeeks</b>(<i>start</i>, <i>stop</i>[, <i>step</i>])

src/day.js

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,20 @@
11
import {timeInterval} from "./interval.js";
22
import {durationDay, durationMinute} from "./duration.js";
3+
import {timeEpoch} from "./epoch.js";
34

45
export const timeDay = timeInterval(
5-
date => date.setHours(0, 0, 0, 0),
6+
(date) => date.setHours(0, 0, 0, 0),
67
(date, step) => date.setDate(date.getDate() + step),
78
(start, end) => (end - start - (end.getTimezoneOffset() - start.getTimezoneOffset()) * durationMinute) / durationDay,
8-
date => date.getDate() - 1
9+
timeEpoch
910
);
1011

1112
export const timeDays = timeDay.range;
1213

13-
export const utcDay = timeInterval((date) => {
14-
date.setUTCHours(0, 0, 0, 0);
15-
}, (date, step) => {
16-
date.setUTCDate(date.getUTCDate() + step);
17-
}, (start, end) => {
18-
return (end - start) / durationDay;
19-
}, (date) => {
20-
return date.getUTCDate() - 1;
21-
});
14+
export const utcDay = timeInterval(
15+
(date) => date.setUTCHours(0, 0, 0, 0),
16+
(date, step) => date.setUTCDate(date.getUTCDate() + step),
17+
(start, end) => (end - start) / durationDay
18+
);
2219

2320
export const utcDays = utcDay.range;
24-
25-
export const unixDay = timeInterval((date) => {
26-
date.setUTCHours(0, 0, 0, 0);
27-
}, (date, step) => {
28-
date.setUTCDate(date.getUTCDate() + step);
29-
}, (start, end) => {
30-
return (end - start) / durationDay;
31-
}, (date) => {
32-
return Math.floor(date / durationDay);
33-
});
34-
35-
export const unixDays = unixDay.range;

src/epoch.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const timeEpoch = new Date(1970, 0, 1);

src/hour.js

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,20 @@
11
import {timeInterval} from "./interval.js";
22
import {durationHour, durationMinute, durationSecond} from "./duration.js";
3+
import {timeEpoch} from "./epoch.js";
34

4-
export const timeHour = timeInterval((date) => {
5-
date.setTime(date - date.getMilliseconds() - date.getSeconds() * durationSecond - date.getMinutes() * durationMinute);
6-
}, (date, step) => {
7-
date.setTime(+date + step * durationHour);
8-
}, (start, end) => {
9-
return (end - start) / durationHour;
10-
}, (date) => {
11-
return date.getHours();
12-
});
5+
export const timeHour = timeInterval(
6+
(date) => date.setTime(date - date.getMilliseconds() - date.getSeconds() * durationSecond - date.getMinutes() * durationMinute),
7+
(date, step) => date.setTime(+date + step * durationHour),
8+
(start, end) => (end - start) / durationHour,
9+
timeEpoch
10+
);
1311

1412
export const timeHours = timeHour.range;
1513

16-
export const utcHour = timeInterval((date) => {
17-
date.setUTCMinutes(0, 0, 0);
18-
}, (date, step) => {
19-
date.setTime(+date + step * durationHour);
20-
}, (start, end) => {
21-
return (end - start) / durationHour;
22-
}, (date) => {
23-
return date.getUTCHours();
24-
});
14+
export const utcHour = timeInterval(
15+
(date) => date.setUTCMinutes(0, 0, 0),
16+
(date, step) => date.setTime(+date + step * durationHour),
17+
(start, end) => (end - start) / durationHour
18+
);
2519

2620
export const utcHours = utcHour.range;

src/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ export {
3535
timeDays,
3636
utcDay,
3737
utcDays,
38-
unixDay,
39-
unixDays
38+
utcDay as unixDay, // deprecated! use utcDay
39+
utcDays as unixDays // deprecated! use utcDays
4040
} from "./day.js";
4141

4242
export {

src/interval.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const t0 = new Date, t1 = new Date;
22

3-
export function timeInterval(floori, offseti, count, field) {
3+
export function timeInterval(floori, offseti, count, epoch = 0) {
44

55
function interval(date) {
66
return floori(date = arguments.length === 0 ? new Date : new Date(+date)), date;
@@ -59,9 +59,9 @@ export function timeInterval(floori, offseti, count, field) {
5959
step = Math.floor(step);
6060
return !isFinite(step) || !(step > 0) ? null
6161
: !(step > 1) ? interval
62-
: interval.filter(field
63-
? (d) => field(d) % step === 0
64-
: (d) => interval.count(0, d) % step === 0);
62+
: interval.filter(typeof epoch === "function" // deprecated field
63+
? (d) => epoch(d) % step === 0
64+
: (d) => interval.count(epoch, d) % step === 0);
6565
};
6666
}
6767

src/minute.js

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,20 @@
11
import {timeInterval} from "./interval.js";
22
import {durationMinute, durationSecond} from "./duration.js";
3+
import {timeEpoch} from "./epoch.js";
34

4-
export const timeMinute = timeInterval((date) => {
5-
date.setTime(date - date.getMilliseconds() - date.getSeconds() * durationSecond);
6-
}, (date, step) => {
7-
date.setTime(+date + step * durationMinute);
8-
}, (start, end) => {
9-
return (end - start) / durationMinute;
10-
}, (date) => {
11-
return date.getMinutes();
12-
});
5+
export const timeMinute = timeInterval(
6+
(date) => date.setTime(date - date.getMilliseconds() - date.getSeconds() * durationSecond),
7+
(date, step) => date.setTime(+date + step * durationMinute),
8+
(start, end) => (end - start) / durationMinute,
9+
timeEpoch
10+
);
1311

1412
export const timeMinutes = timeMinute.range;
1513

16-
export const utcMinute = timeInterval((date) => {
17-
date.setUTCSeconds(0, 0);
18-
}, (date, step) => {
19-
date.setTime(+date + step * durationMinute);
20-
}, (start, end) => {
21-
return (end - start) / durationMinute;
22-
}, (date) => {
23-
return date.getUTCMinutes();
24-
});
14+
export const utcMinute = timeInterval(
15+
(date) => date.setUTCSeconds(0, 0),
16+
(date, step) => date.setTime(+date + step * durationMinute),
17+
(start, end) => (end - start) / durationMinute
18+
);
2519

2620
export const utcMinutes = utcMinute.range;

src/month.js

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,19 @@
1+
import {timeEpoch} from "./epoch.js";
12
import {timeInterval} from "./interval.js";
23

3-
export const timeMonth = timeInterval((date) => {
4-
date.setDate(1);
5-
date.setHours(0, 0, 0, 0);
6-
}, (date, step) => {
7-
date.setMonth(date.getMonth() + step);
8-
}, (start, end) => {
9-
return end.getMonth() - start.getMonth() + (end.getFullYear() - start.getFullYear()) * 12;
10-
}, (date) => {
11-
return date.getMonth();
12-
});
4+
export const timeMonth = timeInterval(
5+
(date) => (date.setDate(1), date.setHours(0, 0, 0, 0)),
6+
(date, step) => date.setMonth(date.getMonth() + step),
7+
(start, end) => end.getMonth() - start.getMonth() + (end.getFullYear() - start.getFullYear()) * 12,
8+
timeEpoch
9+
);
1310

1411
export const timeMonths = timeMonth.range;
1512

16-
export const utcMonth = timeInterval((date) => {
17-
date.setUTCDate(1);
18-
date.setUTCHours(0, 0, 0, 0);
19-
}, (date, step) => {
20-
date.setUTCMonth(date.getUTCMonth() + step);
21-
}, (start, end) => {
22-
return end.getUTCMonth() - start.getUTCMonth() + (end.getUTCFullYear() - start.getUTCFullYear()) * 12;
23-
}, (date) => {
24-
return date.getUTCMonth();
25-
});
13+
export const utcMonth = timeInterval(
14+
(date) => (date.setUTCDate(1), date.setUTCHours(0, 0, 0, 0)),
15+
(date, step) => date.setUTCMonth(date.getUTCMonth() + step),
16+
(start, end) => end.getUTCMonth() - start.getUTCMonth() + (end.getUTCFullYear() - start.getUTCFullYear()) * 12
17+
);
2618

2719
export const utcMonths = utcMonth.range;

src/second.js

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
import {timeInterval} from "./interval.js";
22
import {durationSecond} from "./duration.js";
33

4-
export const second = timeInterval((date) => {
5-
date.setTime(date - date.getMilliseconds());
6-
}, (date, step) => {
7-
date.setTime(+date + step * durationSecond);
8-
}, (start, end) => {
9-
return (end - start) / durationSecond;
10-
}, (date) => {
11-
return date.getUTCSeconds();
12-
});
4+
export const second = timeInterval(
5+
(date) => date.setTime(date - date.getMilliseconds()),
6+
(date, step) => date.setTime(+date + step * durationSecond),
7+
(start, end) => (end - start) / durationSecond
8+
);
139

1410
export const seconds = second.range;

src/ticks.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {millisecond} from "./millisecond.js";
44
import {second} from "./second.js";
55
import {timeMinute, utcMinute} from "./minute.js";
66
import {timeHour, utcHour} from "./hour.js";
7-
import {timeDay, unixDay} from "./day.js";
7+
import {timeDay, utcDay} from "./day.js";
88
import {timeSunday, utcSunday} from "./week.js";
99
import {timeMonth, utcMonth} from "./month.js";
1010
import {timeYear, utcYear} from "./year.js";
@@ -52,7 +52,7 @@ function ticker(year, month, week, day, hour, minute) {
5252
return [ticks, tickInterval];
5353
}
5454

55-
const [utcTicks, utcTickInterval] = ticker(utcYear, utcMonth, utcSunday, unixDay, utcHour, utcMinute);
55+
const [utcTicks, utcTickInterval] = ticker(utcYear, utcMonth, utcSunday, utcDay, utcHour, utcMinute);
5656
const [timeTicks, timeTickInterval] = ticker(timeYear, timeMonth, timeSunday, timeDay, timeHour, timeMinute);
5757

5858
export {utcTicks, utcTickInterval, timeTicks, timeTickInterval};

0 commit comments

Comments
 (0)