Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

consistent intervals with interval.every #66

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
31 changes: 8 additions & 23 deletions 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;
1 change: 1 addition & 0 deletions src/epoch.js
@@ -0,0 +1 @@
export const timeEpoch = new Date(1970, 0, 1);
30 changes: 12 additions & 18 deletions 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;
4 changes: 2 additions & 2 deletions src/index.js
Expand Up @@ -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 {
Expand Down
8 changes: 4 additions & 4 deletions 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;
Expand Down Expand Up @@ -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);
};
}

Expand Down
30 changes: 12 additions & 18 deletions 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;
32 changes: 12 additions & 20 deletions 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;
14 changes: 5 additions & 9 deletions 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;
4 changes: 2 additions & 2 deletions src/ticks.js
Expand Up @@ -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";
Expand Down Expand Up @@ -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};
28 changes: 12 additions & 16 deletions 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);
Expand All @@ -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);
Expand Down
54 changes: 20 additions & 34 deletions 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;
8 changes: 4 additions & 4 deletions test/day-test.js
Expand Up @@ -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)]);
});
17 changes: 13 additions & 4 deletions test/ticks-test.js
Expand Up @@ -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),
Expand All @@ -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)
]);
});

Expand Down