From 642f4f7d05f20adfefbd9bc437df767294d1feb0 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Fri, 18 Aug 2023 11:39:09 -0700 Subject: [PATCH] consistent intervals with interval.every --- README.md | 10 ++++----- src/day.js | 31 +++++++------------------- src/epoch.js | 1 + src/hour.js | 30 ++++++++++--------------- src/index.js | 4 ++-- src/interval.js | 8 +++---- src/minute.js | 30 ++++++++++--------------- src/month.js | 32 ++++++++++----------------- src/second.js | 14 +++++------- src/ticks.js | 4 ++-- src/week.js | 28 ++++++++++------------- src/year.js | 54 +++++++++++++++++---------------------------- test/day-test.js | 8 +++---- test/ticks-test.js | 17 ++++++++++---- test/utcDay-test.js | 8 +++---- 15 files changed, 115 insertions(+), 164 deletions(-) create mode 100644 src/epoch.js diff --git a/README.md b/README.md index a7b4eaf..273aa40 100644 --- a/README.md +++ b/README.md @@ -129,7 +129,7 @@ The returned filtered interval does not support [*interval*.count](#interval_cou # interval.every(step) · [Source](https://github.com/d3/d3-time/blob/main/src/interval.js) -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, etc. 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. +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, etc. ~~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. 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: @@ -169,7 +169,7 @@ The *offset* function takes a date and an integer step as arguments and advances 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. -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). +~~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. ### Intervals @@ -197,9 +197,8 @@ Hours (e.g., 01:00 AM); 60 minutes. Note that advancing time by one hour in loca # d3.timeDay · [Source](https://github.com/d3/d3-time/blob/main/src/day.js "Source")
# d3.utcDay · [Source](https://github.com/d3/d3-time/blob/main/src/day.js) -
# d3.unixDay · [Source](https://github.com/d3/d3-time/blob/main/src/day.js) -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. +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.timeWeek · [Source](https://github.com/d3/d3-time/blob/main/src/week.js "Source")
# d3.utcWeek · [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](# # d3.timeDays(start, stop[, step]) · [Source](https://github.com/d3/d3-time/blob/main/src/day.js)
# d3.utcDays(start, stop[, step]) · [Source](https://github.com/d3/d3-time/blob/main/src/day.js) -
# d3.unixDays(start, stop[, step]) · [Source](https://github.com/d3/d3-time/blob/main/src/day.js) -Aliases for [d3.timeDay](#timeDay).[range](#interval_range), [d3.utcDay](#timeDay).[range](#interval_range), and [d3.unixDay](#timeDay).[range](#interval_range). +Aliases for [d3.timeDay](#timeDay).[range](#interval_range) and [d3.utcDay](#timeDay).[range](#interval_range). # d3.timeWeeks(start, stop[, step])
# d3.utcWeeks(start, stop[, step]) diff --git a/src/day.js b/src/day.js index 2b5d4b6..2574f8a 100644 --- a/src/day.js +++ b/src/day.js @@ -1,35 +1,20 @@ import {timeInterval} from "./interval.js"; import {durationDay, durationMinute} from "./duration.js"; +import {timeEpoch} from "./epoch.js"; export const timeDay = timeInterval( - date => date.setHours(0, 0, 0, 0), + (date) => date.setHours(0, 0, 0, 0), (date, step) => date.setDate(date.getDate() + step), (start, end) => (end - start - (end.getTimezoneOffset() - start.getTimezoneOffset()) * durationMinute) / durationDay, - date => date.getDate() - 1 + timeEpoch ); export const timeDays = timeDay.range; -export const utcDay = timeInterval((date) => { - date.setUTCHours(0, 0, 0, 0); -}, (date, step) => { - date.setUTCDate(date.getUTCDate() + step); -}, (start, end) => { - return (end - start) / durationDay; -}, (date) => { - return date.getUTCDate() - 1; -}); +export const utcDay = timeInterval( + (date) => date.setUTCHours(0, 0, 0, 0), + (date, step) => date.setUTCDate(date.getUTCDate() + step), + (start, end) => (end - start) / durationDay +); export const utcDays = utcDay.range; - -export const unixDay = timeInterval((date) => { - date.setUTCHours(0, 0, 0, 0); -}, (date, step) => { - date.setUTCDate(date.getUTCDate() + step); -}, (start, end) => { - return (end - start) / durationDay; -}, (date) => { - return Math.floor(date / durationDay); -}); - -export const unixDays = unixDay.range; diff --git a/src/epoch.js b/src/epoch.js new file mode 100644 index 0000000..fc6fa0a --- /dev/null +++ b/src/epoch.js @@ -0,0 +1 @@ +export const timeEpoch = new Date(1970, 0, 1); diff --git a/src/hour.js b/src/hour.js index 77b77a3..f7f8c38 100644 --- a/src/hour.js +++ b/src/hour.js @@ -1,26 +1,20 @@ import {timeInterval} from "./interval.js"; import {durationHour, durationMinute, durationSecond} from "./duration.js"; +import {timeEpoch} from "./epoch.js"; -export const timeHour = timeInterval((date) => { - date.setTime(date - date.getMilliseconds() - date.getSeconds() * durationSecond - date.getMinutes() * durationMinute); -}, (date, step) => { - date.setTime(+date + step * durationHour); -}, (start, end) => { - return (end - start) / durationHour; -}, (date) => { - return date.getHours(); -}); +export const timeHour = timeInterval( + (date) => date.setTime(date - date.getMilliseconds() - date.getSeconds() * durationSecond - date.getMinutes() * durationMinute), + (date, step) => date.setTime(+date + step * durationHour), + (start, end) => (end - start) / durationHour, + timeEpoch +); export const timeHours = timeHour.range; -export const utcHour = timeInterval((date) => { - date.setUTCMinutes(0, 0, 0); -}, (date, step) => { - date.setTime(+date + step * durationHour); -}, (start, end) => { - return (end - start) / durationHour; -}, (date) => { - return date.getUTCHours(); -}); +export const utcHour = timeInterval( + (date) => date.setUTCMinutes(0, 0, 0), + (date, step) => date.setTime(+date + step * durationHour), + (start, end) => (end - start) / durationHour +); export const utcHours = utcHour.range; diff --git a/src/index.js b/src/index.js index d6dfd90..cc277b5 100644 --- a/src/index.js +++ b/src/index.js @@ -35,8 +35,8 @@ export { timeDays, utcDay, utcDays, - unixDay, - unixDays + utcDay as unixDay, // deprecated! use utcDay + utcDays as unixDays // deprecated! use utcDays } from "./day.js"; export { diff --git a/src/interval.js b/src/interval.js index 492ae90..2783c92 100644 --- a/src/interval.js +++ b/src/interval.js @@ -1,6 +1,6 @@ const t0 = new Date, t1 = new Date; -export function timeInterval(floori, offseti, count, field) { +export function timeInterval(floori, offseti, count, epoch = 0) { function interval(date) { return floori(date = arguments.length === 0 ? new Date : new Date(+date)), date; @@ -59,9 +59,9 @@ export function timeInterval(floori, offseti, count, field) { step = Math.floor(step); return !isFinite(step) || !(step > 0) ? null : !(step > 1) ? interval - : interval.filter(field - ? (d) => field(d) % step === 0 - : (d) => interval.count(0, d) % step === 0); + : interval.filter(typeof epoch === "function" // deprecated field + ? (d) => epoch(d) % step === 0 + : (d) => interval.count(epoch, d) % step === 0); }; } diff --git a/src/minute.js b/src/minute.js index ba8e4d9..b7b5297 100644 --- a/src/minute.js +++ b/src/minute.js @@ -1,26 +1,20 @@ import {timeInterval} from "./interval.js"; import {durationMinute, durationSecond} from "./duration.js"; +import {timeEpoch} from "./epoch.js"; -export const timeMinute = timeInterval((date) => { - date.setTime(date - date.getMilliseconds() - date.getSeconds() * durationSecond); -}, (date, step) => { - date.setTime(+date + step * durationMinute); -}, (start, end) => { - return (end - start) / durationMinute; -}, (date) => { - return date.getMinutes(); -}); +export const timeMinute = timeInterval( + (date) => date.setTime(date - date.getMilliseconds() - date.getSeconds() * durationSecond), + (date, step) => date.setTime(+date + step * durationMinute), + (start, end) => (end - start) / durationMinute, + timeEpoch +); export const timeMinutes = timeMinute.range; -export const utcMinute = timeInterval((date) => { - date.setUTCSeconds(0, 0); -}, (date, step) => { - date.setTime(+date + step * durationMinute); -}, (start, end) => { - return (end - start) / durationMinute; -}, (date) => { - return date.getUTCMinutes(); -}); +export const utcMinute = timeInterval( + (date) => date.setUTCSeconds(0, 0), + (date, step) => date.setTime(+date + step * durationMinute), + (start, end) => (end - start) / durationMinute +); export const utcMinutes = utcMinute.range; diff --git a/src/month.js b/src/month.js index 6dab69c..cbc37be 100644 --- a/src/month.js +++ b/src/month.js @@ -1,27 +1,19 @@ +import {timeEpoch} from "./epoch.js"; import {timeInterval} from "./interval.js"; -export const timeMonth = timeInterval((date) => { - date.setDate(1); - date.setHours(0, 0, 0, 0); -}, (date, step) => { - date.setMonth(date.getMonth() + step); -}, (start, end) => { - return end.getMonth() - start.getMonth() + (end.getFullYear() - start.getFullYear()) * 12; -}, (date) => { - return date.getMonth(); -}); +export const timeMonth = timeInterval( + (date) => (date.setDate(1), date.setHours(0, 0, 0, 0)), + (date, step) => date.setMonth(date.getMonth() + step), + (start, end) => end.getMonth() - start.getMonth() + (end.getFullYear() - start.getFullYear()) * 12, + timeEpoch +); export const timeMonths = timeMonth.range; -export const utcMonth = timeInterval((date) => { - date.setUTCDate(1); - date.setUTCHours(0, 0, 0, 0); -}, (date, step) => { - date.setUTCMonth(date.getUTCMonth() + step); -}, (start, end) => { - return end.getUTCMonth() - start.getUTCMonth() + (end.getUTCFullYear() - start.getUTCFullYear()) * 12; -}, (date) => { - return date.getUTCMonth(); -}); +export const utcMonth = timeInterval( + (date) => (date.setUTCDate(1), date.setUTCHours(0, 0, 0, 0)), + (date, step) => date.setUTCMonth(date.getUTCMonth() + step), + (start, end) => end.getUTCMonth() - start.getUTCMonth() + (end.getUTCFullYear() - start.getUTCFullYear()) * 12 +); export const utcMonths = utcMonth.range; diff --git a/src/second.js b/src/second.js index 47b2f43..a3fa04a 100644 --- a/src/second.js +++ b/src/second.js @@ -1,14 +1,10 @@ import {timeInterval} from "./interval.js"; import {durationSecond} from "./duration.js"; -export const second = timeInterval((date) => { - date.setTime(date - date.getMilliseconds()); -}, (date, step) => { - date.setTime(+date + step * durationSecond); -}, (start, end) => { - return (end - start) / durationSecond; -}, (date) => { - return date.getUTCSeconds(); -}); +export const second = timeInterval( + (date) => date.setTime(date - date.getMilliseconds()), + (date, step) => date.setTime(+date + step * durationSecond), + (start, end) => (end - start) / durationSecond +); export const seconds = second.range; diff --git a/src/ticks.js b/src/ticks.js index c314789..b18261f 100644 --- a/src/ticks.js +++ b/src/ticks.js @@ -4,7 +4,7 @@ import {millisecond} from "./millisecond.js"; import {second} from "./second.js"; import {timeMinute, utcMinute} from "./minute.js"; import {timeHour, utcHour} from "./hour.js"; -import {timeDay, unixDay} from "./day.js"; +import {timeDay, utcDay} from "./day.js"; import {timeSunday, utcSunday} from "./week.js"; import {timeMonth, utcMonth} from "./month.js"; import {timeYear, utcYear} from "./year.js"; @@ -52,7 +52,7 @@ function ticker(year, month, week, day, hour, minute) { return [ticks, tickInterval]; } -const [utcTicks, utcTickInterval] = ticker(utcYear, utcMonth, utcSunday, unixDay, utcHour, utcMinute); +const [utcTicks, utcTickInterval] = ticker(utcYear, utcMonth, utcSunday, utcDay, utcHour, utcMinute); const [timeTicks, timeTickInterval] = ticker(timeYear, timeMonth, timeSunday, timeDay, timeHour, timeMinute); export {utcTicks, utcTickInterval, timeTicks, timeTickInterval}; diff --git a/src/week.js b/src/week.js index f75ff52..b535b79 100644 --- a/src/week.js +++ b/src/week.js @@ -1,15 +1,14 @@ import {timeInterval} from "./interval.js"; import {durationMinute, durationWeek} from "./duration.js"; +import {timeEpoch} from "./epoch.js"; function timeWeekday(i) { - return timeInterval((date) => { - date.setDate(date.getDate() - (date.getDay() + 7 - i) % 7); - date.setHours(0, 0, 0, 0); - }, (date, step) => { - date.setDate(date.getDate() + step * 7); - }, (start, end) => { - return (end - start - (end.getTimezoneOffset() - start.getTimezoneOffset()) * durationMinute) / durationWeek; - }); + return timeInterval( + (date) => (date.setDate(date.getDate() - (date.getDay() + 7 - i) % 7), date.setHours(0, 0, 0, 0)), + (date, step) => date.setDate(date.getDate() + step * 7), + (start, end) => (end - start - (end.getTimezoneOffset() - start.getTimezoneOffset()) * durationMinute) / durationWeek, + timeEpoch + ); } export const timeSunday = timeWeekday(0); @@ -29,14 +28,11 @@ export const timeFridays = timeFriday.range; export const timeSaturdays = timeSaturday.range; function utcWeekday(i) { - return timeInterval((date) => { - date.setUTCDate(date.getUTCDate() - (date.getUTCDay() + 7 - i) % 7); - date.setUTCHours(0, 0, 0, 0); - }, (date, step) => { - date.setUTCDate(date.getUTCDate() + step * 7); - }, (start, end) => { - return (end - start) / durationWeek; - }); + return timeInterval( + (date) => (date.setUTCDate(date.getUTCDate() - (date.getUTCDay() + 7 - i) % 7), date.setUTCHours(0, 0, 0, 0)), + (date, step) => date.setUTCDate(date.getUTCDate() + step * 7), + (start, end) => (end - start) / durationWeek + ); } export const utcSunday = utcWeekday(0); diff --git a/src/year.js b/src/year.js index 9eadff3..2d63587 100644 --- a/src/year.js +++ b/src/year.js @@ -1,49 +1,35 @@ +import {timeEpoch} from "./epoch.js"; import {timeInterval} from "./interval.js"; -export const timeYear = timeInterval((date) => { - date.setMonth(0, 1); - date.setHours(0, 0, 0, 0); -}, (date, step) => { - date.setFullYear(date.getFullYear() + step); -}, (start, end) => { - return end.getFullYear() - start.getFullYear(); -}, (date) => { - return date.getFullYear(); -}); +export const timeYear = timeInterval( + (date) => (date.setMonth(0, 1), date.setHours(0, 0, 0, 0)), + (date, step) => date.setFullYear(date.getFullYear() + step), + (start, end) => end.getFullYear() - start.getFullYear(), + timeEpoch +); // An optimized implementation for this simple case. timeYear.every = (k) => { - return !isFinite(k = Math.floor(k)) || !(k > 0) ? null : timeInterval((date) => { - date.setFullYear(Math.floor(date.getFullYear() / k) * k); - date.setMonth(0, 1); - date.setHours(0, 0, 0, 0); - }, (date, step) => { - date.setFullYear(date.getFullYear() + step * k); - }); + return !isFinite(k = Math.floor(k)) || !(k > 0) ? null : timeInterval( + (date) => (date.setFullYear(Math.floor(date.getFullYear() / k) * k), date.setMonth(0, 1), date.setHours(0, 0, 0, 0)), + (date, step) => date.setFullYear(date.getFullYear() + step * k) + ); }; export const timeYears = timeYear.range; -export const utcYear = timeInterval((date) => { - date.setUTCMonth(0, 1); - date.setUTCHours(0, 0, 0, 0); -}, (date, step) => { - date.setUTCFullYear(date.getUTCFullYear() + step); -}, (start, end) => { - return end.getUTCFullYear() - start.getUTCFullYear(); -}, (date) => { - return date.getUTCFullYear(); -}); +export const utcYear = timeInterval( + (date) => (date.setUTCMonth(0, 1), date.setUTCHours(0, 0, 0, 0)), + (date, step) => date.setUTCFullYear(date.getUTCFullYear() + step), + (start, end) => end.getUTCFullYear() - start.getUTCFullYear() +); // An optimized implementation for this simple case. utcYear.every = (k) => { - return !isFinite(k = Math.floor(k)) || !(k > 0) ? null : timeInterval((date) => { - date.setUTCFullYear(Math.floor(date.getUTCFullYear() / k) * k); - date.setUTCMonth(0, 1); - date.setUTCHours(0, 0, 0, 0); - }, (date, step) => { - date.setUTCFullYear(date.getUTCFullYear() + step * k); - }); + return !isFinite(k = Math.floor(k)) || !(k > 0) ? null : timeInterval( + (date) => (date.setUTCFullYear(Math.floor(date.getUTCFullYear() / k) * k), date.setUTCMonth(0, 1), date.setUTCHours(0, 0, 0, 0)), + (date, step) => date.setUTCFullYear(date.getUTCFullYear() + step * k) + ); }; export const utcYears = utcYear.range; diff --git a/test/day-test.js b/test/day-test.js index 771616b..0ac7bb5 100644 --- a/test/day-test.js +++ b/test/day-test.js @@ -208,8 +208,8 @@ it("timeDay.count(start, end) returns 364 or 365 for a full year", () => { assert.strictEqual(timeDay.count(local(2011, 0, 1), local(2011, 11, 31)), 364); }); -it("timeDay.every(step) returns every stepth day, starting with the first day of the month", () => { - assert.deepStrictEqual(timeDay.every(3).range(local(2008, 11, 30, 0, 12), local(2009, 0, 5, 23, 48)), [local(2008, 11, 31), local(2009, 0, 1), local(2009, 0, 4)]); - assert.deepStrictEqual(timeDay.every(5).range(local(2008, 11, 30, 0, 12), local(2009, 0, 6, 23, 48)), [local(2008, 11, 31), local(2009, 0, 1), local(2009, 0, 6)]); - assert.deepStrictEqual(timeDay.every(7).range(local(2008, 11, 30, 0, 12), local(2009, 0, 8, 23, 48)), [local(2009, 0, 1), local(2009, 0, 8)]); +it("timeDay.every(step) returns every stepth day without resetting on the first of the month", () => { + assert.deepStrictEqual(timeDay.every(3).range(local(2008, 11, 30, 0, 12), local(2009, 0, 5, 23, 48)), [local(2008, 11, 31), local(2009, 0, 3)]); + assert.deepStrictEqual(timeDay.every(5).range(local(2008, 11, 25, 0, 12), local(2009, 0, 6, 23, 48)), [local(2008, 11, 27), local(2009, 0, 1), local(2009, 0, 6)]); + assert.deepStrictEqual(timeDay.every(7).range(local(2008, 11, 23, 0, 12), local(2009, 0, 8, 23, 48)), [local(2008, 11, 25), local(2009, 0, 1), local(2009, 0, 8)]); }); diff --git a/test/ticks-test.js b/test/ticks-test.js index 36c0318..0c03aa8 100644 --- a/test/ticks-test.js +++ b/test/ticks-test.js @@ -136,6 +136,16 @@ it("timeTicks(start, stop, count) can generate 6-hour ticks", () => { ]); }); +// Note: not aligned at midnight because of daylight savings time! +it("timeTicks(start, stop, count) can generate 6-hour ticks across daylight savings time", () => { + assert.deepStrictEqual(timeTicks(local(2011, 3, 1, 16, 28, 27), local(2011, 3, 2, 14, 34, 12), 4), [ + local(2011, 3, 1, 19, 0), + local(2011, 3, 2, 1, 0), + local(2011, 3, 2, 7, 0), + local(2011, 3, 2, 13, 0) + ]); +}); + it("timeTicks(start, stop, count) can generate 12-hour ticks", () => { assert.deepStrictEqual(timeTicks(local(2011, 0, 1, 16, 28, 27), local(2011, 0, 3, 21, 34, 12), 4), [ local(2011, 0, 2, 0, 0), @@ -156,10 +166,9 @@ it("timeTicks(start, stop, count) can generate 1-day ticks", () => { it("timeTicks(start, stop, count) can generate 2-day ticks", () => { assert.deepStrictEqual(timeTicks(local(2011, 0, 2, 16, 28, 27), local(2011, 0, 9, 21, 34, 12), 4), [ - local(2011, 0, 3, 0, 0), - local(2011, 0, 5, 0, 0), - local(2011, 0, 7, 0, 0), - local(2011, 0, 9, 0, 0) + local(2011, 0, 4, 0, 0), + local(2011, 0, 6, 0, 0), + local(2011, 0, 8, 0, 0) ]); }); diff --git a/test/utcDay-test.js b/test/utcDay-test.js index f521c91..b6be6e0 100644 --- a/test/utcDay-test.js +++ b/test/utcDay-test.js @@ -110,8 +110,8 @@ it("utcDay.count(start, end) returns 364 or 365 for a full year", () => { assert.strictEqual(utcDay.count(utc(2011, 0, 1), utc(2011, 11, 31)), 364); }); -it("utcDay.every(step) returns every stepth day, starting with the first day of the month", () => { - assert.deepStrictEqual(utcDay.every(3).range(utc(2008, 11, 30, 0, 12), utc(2009, 0, 5, 23, 48)), [utc(2008, 11, 31), utc(2009, 0, 1), utc(2009, 0, 4)]); - assert.deepStrictEqual(utcDay.every(5).range(utc(2008, 11, 30, 0, 12), utc(2009, 0, 6, 23, 48)), [utc(2008, 11, 31), utc(2009, 0, 1), utc(2009, 0, 6)]); - assert.deepStrictEqual(utcDay.every(7).range(utc(2008, 11, 30, 0, 12), utc(2009, 0, 8, 23, 48)), [utc(2009, 0, 1), utc(2009, 0, 8)]); +it("utcDay.every(step) returns every stepth day without resetting on the first of the month", () => { + assert.deepStrictEqual(utcDay.every(3).range(utc(2008, 11, 30, 0, 12), utc(2009, 0, 5, 23, 48)), [utc(2008, 11, 31), utc(2009, 0, 3)]); + assert.deepStrictEqual(utcDay.every(5).range(utc(2008, 11, 25, 0, 12), utc(2009, 0, 6, 23, 48)), [utc(2008, 11, 27), utc(2009, 0, 1), utc(2009, 0, 6)]); + assert.deepStrictEqual(utcDay.every(7).range(utc(2008, 11, 23, 0, 12), utc(2009, 0, 8, 23, 48)), [utc(2008, 11, 25), utc(2009, 0, 1), utc(2009, 0, 8)]); });