Add timespan class

This commit is contained in:
Rory& 2025-09-20 15:47:47 +02:00
parent c6ed8ecfcd
commit f9d6e977e9
2 changed files with 210 additions and 0 deletions

129
timespan.js Normal file
View file

@ -0,0 +1,129 @@
/**
* Represents a timespan with a start and end time.
*/
export class Timespan {
// constructors
constructor(start = Date.now(), end = Date.now()) {
if (start > end) {
throw new Error("Start time must be less than or equal to end time.");
}
this.start = start;
this.end = end;
}
static fromDates(startDate, endDate) {
return new Timespan(startDate, endDate);
}
static fromMillis(millis) {
return new Timespan(0, millis);
}
static fromSeconds(seconds) {
return Timespan.fromMillis(seconds * 1000);
}
// methods
get totalMillis() {
return this.end - this.start;
}
get millis() {
return Math.floor((this.totalMillis) % 1000);
}
get totalSeconds() {
return Math.floor(this.totalMillis / 1000);
}
get seconds() {
return Math.floor((this.totalMillis) / 1000 % 60);
}
get totalMinutes() {
return Math.floor(this.totalMillis / 1000 / 60);
}
get minutes() {
return Math.floor(this.totalMillis / 1000 / 60 % 60);
}
get totalHours() {
return Math.floor(this.totalMillis / 1000 / 60 / 60);
}
get hours() {
return Math.floor(this.totalMillis / 1000 / 60 / 60 % 24);
}
get totalDays() {
return Math.floor(this.totalMillis / 1000 / 60 / 60 / 24);
}
get days() {
return Math.floor(this.totalMillis / 1000 / 60 / 60 / 24 % 30.44); // Average days in a month
}
get weekDays() {
return Math.floor(this.totalMillis / 1000 / 60 / 60 / 24 % 7);
}
get totalWeeks() {
return Math.floor(this.totalMillis / 1000 / 60 / 60 / 24 / 7);
}
get weeks() {
return Math.floor(this.totalMillis / 1000 / 60 / 60 / 24 / 7 % 4.345); // Average weeks in a month
}
get totalMonths() {
return Math.floor(this.totalMillis / 1000 / 60 / 60 / 24 / 30.44); // Average days in a month
}
get months() {
return Math.floor(this.totalMillis / 1000 / 60 / 60 / 24 / 30.44 % 12); // Average days in a month
}
get totalYears() {
return Math.floor(this.totalMillis / 1000 / 60 / 60 / 24 / 365.25); // Average days in a year
}
get years() {
return Math.floor(this.totalMillis / 1000 / 60 / 60 / 24 / 365.25 % 1); // Average days in a year
}
toString(includeWeeks = true, includeMillis = true) {
const parts = [];
if (this.totalYears >= 1) parts.push(`${this.totalYears} years`);
if (this.totalMonths >= 1) parts.push(`${this.months} months`);
if (includeWeeks && this.totalWeeks >= 1) parts.push(`${this.weeks} weeks`);
if (this.totalDays >= 1) parts.push(`${includeWeeks ? this.weekDays : this.days} days`);
if (this.totalHours >= 1) parts.push(`${this.hours} hours`);
if (this.totalMinutes >= 1) parts.push(`${this.minutes} minutes`);
if (this.totalSeconds >= 1) parts.push(`${this.seconds} seconds`);
if (includeMillis) parts.push(`${this.millis} milliseconds`);
return parts.join(", ");
}
toShortString(includeWeeks = true, includeMillis = true, withSpaces = false) {
const parts = [];
if (this.totalYears >= 1) parts.push(`${this.totalYears}y`);
if (this.totalMonths >= 1) parts.push(`${this.months}mo`);
if (includeWeeks && this.totalWeeks >= 1) parts.push(`${this.weeks}w`);
if (this.totalDays >= 1) parts.push(`${includeWeeks ? this.weekDays : this.days}d`);
if (this.totalHours >= 1) parts.push(`${this.hours}h`);
if (this.totalMinutes >= 1) parts.push(`${this.minutes}m`);
if (this.totalSeconds >= 1) parts.push(`${this.seconds}s`);
if (includeMillis) parts.push(`${this.millis}ms`);
return parts.join(withSpaces ? " " : "");
}
get startTime() {
return new Date(this.start);
}
get endTime() {
return new Date(this.end);
}
}

81
timespan.test.js Normal file
View file

@ -0,0 +1,81 @@
// noinspection ES6ConvertRequireIntoImport - this is CJS, not MJS
const { it } = require("node:test");
const assert = require("node:assert/strict");
const { Timespan } = require("./timespan");
it("should be able to return zero", () => {
const ts = new Timespan();
assert.equal(ts.totalMillis, 0);
});
it("should be able to return timespan from milliseconds", () => {
const ts = Timespan.fromMillis(1000);
assert.equal(ts.totalMillis, 1000);
assert.equal(ts.totalSeconds, 1);
});
it("should be able to return timespan from seconds", () => {
const ts = Timespan.fromSeconds(60);
assert.equal(ts.totalMillis, 60000);
assert.equal(ts.totalSeconds, 60);
assert.equal(ts.totalMinutes, 1);
assert.equal(ts.minutes, 1);
assert.equal(ts.hours, 0);
assert.equal(ts.days, 0);
});
it("should be pure", () => {
const count = 1000;
const timestamps = [];
for (let i = 0; i < count; i++) {
timestamps.push(Timespan.fromMillis(8972347984));
for (const ts2 of timestamps) {
assert.equal(ts2.totalMillis, 8972347984);
assert.equal(ts2.totalSeconds, 8972347);
assert.equal(ts2.totalMinutes, 149539);
assert.equal(ts2.totalHours, 2492);
assert.equal(ts2.totalDays, 103);
assert.equal(ts2.totalWeeks, 14);
assert.equal(ts2.totalMonths, 3);
assert.equal(ts2.totalYears, 0);
assert.equal(ts2.millis, 984);
assert.equal(ts2.seconds, 7);
assert.equal(ts2.minutes, 19);
assert.equal(ts2.hours, 20);
assert.equal(ts2.days, 12);
assert.equal(ts2.weekDays, 5);
assert.equal(ts2.weeks, 1);
assert.equal(ts2.months, 3);
assert.equal(ts2.years, 0);
}
}
});
it("should be able to stringify", () => {
const ts = Timespan.fromMillis(8972347984);
assert.equal(ts.toString(), "3 months, 1 weeks, 5 days, 20 hours, 19 minutes, 7 seconds, 984 milliseconds");
assert.equal(ts.toString(true), "3 months, 1 weeks, 5 days, 20 hours, 19 minutes, 7 seconds, 984 milliseconds");
assert.equal(ts.toString(true, false), "3 months, 1 weeks, 5 days, 20 hours, 19 minutes, 7 seconds");
assert.equal(ts.toString(false), "3 months, 12 days, 20 hours, 19 minutes, 7 seconds, 984 milliseconds");
assert.equal(ts.toString(false, false), "3 months, 12 days, 20 hours, 19 minutes, 7 seconds");
});
it("should be able to shortStringify", () => {
const ts = Timespan.fromMillis(8972347984);
assert.equal(ts.toShortString(), "3mo1w5d20h19m7s984ms");
assert.equal(ts.toShortString(true), "3mo1w5d20h19m7s984ms");
assert.equal(ts.toShortString(true, false), "3mo1w5d20h19m7s");
assert.equal(ts.toShortString(false), "3mo12d20h19m7s984ms");
assert.equal(ts.toShortString(false, false), "3mo12d20h19m7s");
});
it("should be able to shortStringify with spaces", () => {
const ts = Timespan.fromMillis(8972347984);
assert.equal(ts.toShortString(undefined, undefined, true), "3mo 1w 5d 20h 19m 7s 984ms");
assert.equal(ts.toShortString(true, undefined, true), "3mo 1w 5d 20h 19m 7s 984ms");
assert.equal(ts.toShortString(true, false, true), "3mo 1w 5d 20h 19m 7s");
assert.equal(ts.toShortString(false, undefined, true), "3mo 12d 20h 19m 7s 984ms");
assert.equal(ts.toShortString(false, false, true), "3mo 12d 20h 19m 7s");
});