Skip to content
This repository was archived by the owner on Aug 29, 2025. It is now read-only.

Commit 11d5d3f

Browse files
committed
Replace Ruby tests with JS tests
1 parent b3d11e5 commit 11d5d3f

File tree

12 files changed

+206
-171
lines changed

12 files changed

+206
-171
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
Returns the difference between two times.
44

5+
## System requirements
6+
7+
Node.js >= 20.18.0
8+
59
## Installation
610

711
```shell

bin/cli.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
#!/usr/bin/env node
22

33
import process from "node:process";
4-
import { main } from "../src/index.js";
4+
import { parseArgs } from "node:util";
5+
import { getTimeDiff } from "../src/index.js";
56

67
try {
7-
main();
8+
const { positionals } = parseArgs({ allowPositionals: true });
9+
console.log(getTimeDiff(positionals));
810
} catch (error) {
911
process.exitCode = 1;
1012
console.error(error.message);

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
"scripts": {
1414
"format": "prettier . --write",
1515
"lint": "eslint . --fix",
16-
"test": "node --test test/"
16+
"test": "node --test test/",
17+
"test:watch": "node --test --watch test/"
1718
},
1819
"repository": {
1920
"type": "git",

src/index.js

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
1-
import { parseArgs } from "node:util";
21
import { stringToDate } from "./lib/normalize.js";
32
import { TimeDiff } from "./lib/time-diff.js";
43

5-
export function main() {
6-
const { positionals } = parseArgs({ allowPositionals: true });
7-
8-
switch (positionals.length) {
4+
/**
5+
* Returns the difference between two times.
6+
* @param {[string, string]} times The start and end times to compare.
7+
* @returns {string} The difference between the start and end times.
8+
*/
9+
export function getTimeDiff(times) {
10+
switch (times.length) {
911
case 0:
1012
throw new Error("Usage: time-diff <start_time> <end_time>");
1113
case 1:
1214
throw new Error("An end time is required.");
1315
}
1416

15-
const startTime = stringToDate(positionals[0]);
16-
const endTime = stringToDate(positionals[1]);
17-
const diff = new TimeDiff(startTime, endTime);
18-
19-
console.log(diff.toString());
17+
const [start, end] = times;
18+
return new TimeDiff(stringToDate(start), stringToDate(end)).toString();
2019
}

src/lib/normalize.js

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,24 @@
1212
//#region Constants
1313

1414
const TIME_REGEX =
15-
/^(?<hour>\d{1,2})?:?(?<min>\d{1,2})?:?(?<sec>\d{1,2})?\s?(?<ord>am|pm)?$/i;
15+
/^(?<hour>\d{1,2})?:?(?<min>\d{1,2})?:?(?<sec>\d{1,2})?\s?(?<mer>am|pm)?$/i;
1616

1717
//#endregion
1818

1919
/**
2020
* Normalizes the provirded hour, min, sec, and ordinal values.
21-
* @param {string|undefined} hour
22-
* @param {string|undefined} min
23-
* @param {string|undefined} sec
24-
* @param {string|undefined} ord
21+
* @param {string|undefined} hour The hour to normalize.
22+
* @param {string|undefined} min The minute to normalize.
23+
* @param {string|undefined} sec The second to normalize.
24+
* @param {string|undefined} mer The meridiem (AM/PM), if using 12h format.
2525
* @returns {NormalizedTime} The time normalized on a 24h clock.
2626
*/
27-
function getNormalizedTime(hour, min, sec, ord) {
27+
function getNormalizedTime(hour, min, sec, mer) {
2828
hour = hour ? parseInt(hour) : 0;
2929

30-
if (ord?.toLowerCase() == "pm" && hour < 12) {
30+
if (mer?.toLowerCase() == "am" && hour == 12) {
31+
hour -= 12;
32+
} else if (mer?.toLowerCase() == "pm" && hour < 12) {
3133
hour += 12;
3234
}
3335

@@ -43,15 +45,19 @@ function getNormalizedTime(hour, min, sec, ord) {
4345
* @returns {Date} A date parsed from the provided date-time string.
4446
*/
4547
export function stringToDate(dateTimeStr) {
48+
if (typeof dateTimeStr != "string") {
49+
dateTimeStr = String(dateTimeStr);
50+
}
51+
4652
if (dateTimeStr.toLowerCase() == "now") {
4753
return new Date();
4854
}
4955

5056
const timeMatch = dateTimeStr.match(TIME_REGEX);
5157

5258
if (timeMatch != null) {
53-
const { hour, min, sec, ord } = timeMatch.groups;
54-
const normalized = getNormalizedTime(hour, min, sec, ord);
59+
const { hour, min, sec, mer } = timeMatch.groups;
60+
const normalized = getNormalizedTime(hour, min, sec, mer);
5561

5662
const today = new Date();
5763
today.setHours(normalized.hour, normalized.min, normalized.sec);

src/lib/time-diff.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export class TimeDiff {
7070
}
7171

7272
toString() {
73-
if (this.value == 0) {
73+
if (this.seconds == 0) {
7474
return "No difference";
7575
}
7676

@@ -81,7 +81,7 @@ export class TimeDiff {
8181
}
8282

8383
/** The time difference rounded to the nearest second. */
84-
get value() {
84+
get seconds() {
8585
const diffMs = this.#endTime - this.#startTime;
8686
return Math.round(diffMs / 1000);
8787
}
@@ -118,7 +118,7 @@ export class TimeDiff {
118118
*/
119119
#timeToUnits() {
120120
const units = {};
121-
let diffSeconds = this.value;
121+
let diffSeconds = this.seconds;
122122

123123
this.#units.forEach((seconds, unit) => {
124124
units[unit] = 0;

test/index.test.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import assert from "node:assert";
2+
import { describe, it } from "node:test";
3+
import { getTimeDiff } from "../src/index.js";
4+
import { TimeDiff } from "../src/lib/time-diff.js";
5+
6+
describe("getTimeDiff", () => {
7+
it("Should throw usage instructions if no times provided", () => {
8+
assert.throws(() => {
9+
getTimeDiff([]);
10+
}, new Error("Usage: time-diff <start_time> <end_time>"));
11+
});
12+
13+
it("Should throw error if only one time provided", () => {
14+
assert.throws(() => {
15+
getTimeDiff(["8:00 am"]);
16+
}, new Error("An end time is required."));
17+
});
18+
19+
it("Should return the difference bweteen two times as a string", () => {
20+
const start = new Date(2024, 0, 1, 12, 0, 0);
21+
const end = new Date(2024, 0, 16, 14, 2, 2);
22+
const diff = new TimeDiff(start, end);
23+
const expectedString = "2 weeks, 1 day, 2 hours, 2 minutes, 2 seconds";
24+
25+
assert.equal(diff.toString(), expectedString);
26+
});
27+
});

test/main.test.js

Whitespace-only changes.

test/main_test.rb

Lines changed: 0 additions & 47 deletions
This file was deleted.

test/normalize.test.js

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import assert from "node:assert";
2+
import { afterEach, beforeEach, describe, it, mock } from "node:test";
3+
import { stringToDate } from "../src/lib/normalize.js";
4+
5+
describe("stringToDate", () => {
6+
beforeEach(() => {
7+
mock.timers.enable({
8+
apis: ["Date"],
9+
now: new Date(2024, 0, 1, 0, 0, 0, 0),
10+
});
11+
});
12+
13+
afterEach(() => {
14+
mock.timers.reset();
15+
});
16+
17+
it('should return the current date if "now" is provided', () => {
18+
assert.deepStrictEqual(stringToDate("now"), new Date());
19+
});
20+
21+
it("should return a date when given only a time", () => {
22+
const testSet = [
23+
["12:00 am", 0, 0],
24+
["12:00am", 0, 0],
25+
["12:00 AM", 0, 0],
26+
["12:00AM", 0, 0],
27+
["1:30 am", 1, 30],
28+
["1:30 AM", 1, 30],
29+
["1:30am", 1, 30],
30+
["1:30AM", 1, 30],
31+
["01:30AM", 1, 30],
32+
["01:30am", 1, 30],
33+
["01:30 am", 1, 30],
34+
["01:30 AM", 1, 30],
35+
["01:30", 1, 30],
36+
["12:00 pm", 12, 0],
37+
["12:00pm", 12, 0],
38+
["12:00 PM", 12, 0],
39+
["12:00PM", 12, 0],
40+
["1:30 pm", 13, 30],
41+
["1:30 PM", 13, 30],
42+
["1:30pm", 13, 30],
43+
["1:30PM", 13, 30],
44+
["01:30PM", 13, 30],
45+
["01:30pm", 13, 30],
46+
["01:30 pm", 13, 30],
47+
["01:30 PM", 13, 30],
48+
["13:30", 13, 30],
49+
];
50+
51+
for (const [time, hour, min] of testSet) {
52+
const expectedDate = new Date();
53+
expectedDate.setHours(hour, min);
54+
55+
assert.deepEqual(
56+
stringToDate(time),
57+
expectedDate,
58+
`${time} should yield ${expectedDate.toISOString()}.`,
59+
);
60+
}
61+
});
62+
63+
it("should return a date from the provided string", () => {
64+
const expectedDate = new Date();
65+
expectedDate.setFullYear(2024, 11, 31);
66+
expectedDate.setHours(0, 0);
67+
68+
const testSet = [
69+
"12/31/2024 12:00 am",
70+
"2024-12-31T00:00:00",
71+
"Dec 31, 2024 12:00 am",
72+
"31 Dec, 2024 12:00 am",
73+
"December 31, 2024 12:00 AM",
74+
];
75+
76+
for (const dateStr of testSet) {
77+
assert.deepEqual(
78+
stringToDate(dateStr),
79+
expectedDate,
80+
`${dateStr} should yield ${expectedDate}.`,
81+
);
82+
}
83+
});
84+
85+
it("should handle invalid inputs", () => {
86+
assert.doesNotThrow(() => stringToDate("foobar"));
87+
assert.doesNotThrow(() => stringToDate());
88+
assert.doesNotThrow(() => stringToDate(null));
89+
assert.doesNotThrow(() => stringToDate(42));
90+
assert.doesNotThrow(() => stringToDate(["foo,", "bar"]));
91+
assert.doesNotThrow(() => stringToDate({ foo: "bar" }));
92+
});
93+
});

0 commit comments

Comments
 (0)