Add timespan class
This commit is contained in:
parent
c6ed8ecfcd
commit
f9d6e977e9
129
timespan.js
Normal file
129
timespan.js
Normal 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
81
timespan.test.js
Normal 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");
|
||||
});
|
Loading…
Reference in a new issue