diff --git a/src/__tests__/add.spec.ts b/src/__tests__/add.spec.ts new file mode 100644 index 0000000..359eba8 --- /dev/null +++ b/src/__tests__/add.spec.ts @@ -0,0 +1,40 @@ +import { describe, expect, it } from "vitest" +import { add } from "../add" +import { date } from "../date" + +describe("add", () => { + it("should add to result into 2025-04-16 14:08:40", () => { + const a = "2024-01-01 12:00:00" + + expect( + add(a, { + years: 1, + months: 3, + days: 15, + hours: 2, + minutes: 8, + seconds: 40, + }) + ).toEqual(date("2025-04-16 14:08:40")) + }) + + it("should overflow", () => { + expect( + add( + "2024-01-30", + { + months: 1, + }, + true + ) + ).toEqual(date("2024-03-01")) + }) + + it("should remove 5 weeks", () => { + expect( + add("2024-02-05", { + weeks: -5, + }) + ).toEqual(date("2024-01-01")) + }) +}) diff --git a/src/__tests__/diff.spec.ts b/src/__tests__/diff.spec.ts new file mode 100644 index 0000000..74169d9 --- /dev/null +++ b/src/__tests__/diff.spec.ts @@ -0,0 +1,49 @@ +import { describe, expect, it } from "vitest" +import { diff } from "../diff" +import { date } from "../date" +import { addSecond } from "../addSecond" +import { addDay } from "../addDay" + +describe("diff", () => { + it("should give 1 year, 3 months & 50 seconds", () => { + const a = "2025-04-01 12:00:50" + const b = "2024-01-01 12:00:00" + expect(diff(a, b)).toEqual({ years: 1, months: 3, seconds: 50 }) + }) + + it("should give -3 weeks, -6 days, -4 hours & -5 minutes", () => { + const a = "2024-01-28 12:00:00" + const b = "2024-01-01 07:55:00" + + expect(diff(b, a)).toEqual({ weeks: -3, days: -6, hours: -4, minutes: -5 }) + }) + + it("should give abs 5 days & 5070 milliseconds from current time", () => { + const a = date() + const b = addDay(addSecond(a, 5), 5) + + expect(diff(null, b, { skip: ["seconds"], abs: true })).toEqual({ + days: 5, + milliseconds: 5000, + }) + }) + + it("should give 18 months & 60 seconds while skipping years & minutes", () => { + const a = "2025-07-01 12:01:00" + const b = "2024-01-01 12:00:00" + expect(diff(a, b, { skip: ["years", "minutes"] })).toEqual({ + months: 18, + seconds: 60, + }) + }) + + it("should give 27 days and 245 minutes while using abs and skipping weeks and hours", () => { + const a = "2024-01-28 12:00:00" + const b = "2024-01-01 07:55:00" + + expect(diff(b, a, { abs: true, skip: ["weeks", "hours"] })).toEqual({ + days: 27, + minutes: 245, + }) + }) +}) diff --git a/src/add.ts b/src/add.ts new file mode 100644 index 0000000..2bd9a03 --- /dev/null +++ b/src/add.ts @@ -0,0 +1,51 @@ +import { addDay } from "./addDay" +import { addHour } from "./addHour" +import { addMinute } from "./addMinute" +import { addMonth } from "./addMonth" +import { addSecond } from "./addSecond" +import { addYear } from "./addYear" +import { date } from "./date" +import type { DurationObj, MaybeDateInput } from "./types" + +/** + * returns a new date object with the added amount of time after the original date. + * @param [inputDate] - A date to increment or null to increment from the current time. + * @param add - An object with values for the amount of time to add to the original date. + * @param [dateOverflow] - Whether or not to allow the date to overflow to another month if the inputDate’s month is out of range of the new month + */ +export function add( + inputDate: MaybeDateInput, + add: Omit, + dateOverflow = false +) { + let d = date(inputDate) + if (add.weeks) { + d = addDay(d, add.weeks * 7) + } + if (add.days) { + d = addDay(d, add.days) + } + if (add.hours) { + d = addHour(d, add.hours) + } + if (add.minutes) { + d = addMinute(d, add.minutes) + } + if (add.seconds) { + d = addSecond(d, add.seconds) + } + + if (add.milliseconds) { + d.setMilliseconds(d.getMilliseconds() + add.milliseconds) + } + + // doing years & months due to the dateOverflow option, it might be that the other units already resolved the overflow + if (add.months) { + d = addMonth(d, add.months, dateOverflow) + } + if (add.years) { + d = addYear(d, add.years, dateOverflow) + } + + return d +} diff --git a/src/diff.ts b/src/diff.ts new file mode 100644 index 0000000..9e8c29a --- /dev/null +++ b/src/diff.ts @@ -0,0 +1,130 @@ +import { addDay } from "./addDay" +import { addHour } from "./addHour" +import { addMinute } from "./addMinute" +import { addMonth } from "./addMonth" +import { addSecond } from "./addSecond" +import { addYear } from "./addYear" +import { date } from "./date" +import { diffDays } from "./diffDays" +import { diffHours } from "./diffHours" +import { diffMilliseconds } from "./diffMilliseconds" +import { diffMinutes } from "./diffMinutes" +import { diffMonths } from "./diffMonths" +import { diffSeconds } from "./diffSeconds" +import { diffWeeks } from "./diffWeeks" +import { diffYears } from "./diffYears" +import type { DateInput, DurationObj, MaybeDateInput } from "./types" + +type DurationKeys = keyof Omit + +// DiffFnOptions is called with `Fn` to prevent confusion with the other diff* function + +/** + * Options for `diff` function + */ +export interface DiffFnOptions { + /** + * whether the difference should be absolute (not negative) + */ + abs?: boolean + /** + * units you want to skip, for example weeks + */ + skip?: DurationKeys[] | Set +} + +/** + * Returns the difference between 2 dates in an object + * @param dateA - A date to compare with the right date + * @param [dateB] - A date to compare with the left date or nothing to compare with the current time + * @param [options] additional options + * @param [options.skip] units you want skip + * @param [options.abs] whether the difference should be absolute + * @returns an object which could be used with `Intl.DurationFormat.format'` + */ +export function diff( + dateA: DateInput, + dateB?: MaybeDateInput, + options?: DiffFnOptions +): DurationObj + +/** + * Returns the difference between 2 dates in an object + * @param [dateA] - A date to compare with the right date or null to compare with the current time + * @param dateB - A date to compare with the left date + * @param [options] additional options + * @param [options.skip] units you want skip + * @param [options.abs] whether the difference should be absolute + * @returns an object which could be used with `Intl.DurationFormat.format'` + */ +export function diff( + dateA: MaybeDateInput, + dateB: DateInput, + options?: DiffFnOptions +): DurationObj + +export function diff( + dateA: MaybeDateInput, + dateB?: MaybeDateInput, + options?: DiffFnOptions +): DurationObj { + let a = date(dateA) + let b = date(dateB) + + if (options?.abs && a < b) { + const d = diff(b, a, options) + return d + } + + const skip = new Set(options?.skip) + const duration: DurationObj = {} + + if (!skip.has("years")) { + const years = diffYears(a, b) + a = addYear(a, -years) + if (years) duration.years = years + } + + if (!skip.has("months")) { + const months = diffMonths(a, b) + a = addMonth(a, -months) + if (months) duration.months = months + } + + if (!skip.has("weeks")) { + const weeks = diffWeeks(a, b) + a = addDay(a, -(weeks * 7)) + if (weeks) duration.weeks = weeks + } + + if (!skip.has("days")) { + const days = diffDays(a, b) + a = addDay(a, -days) + if (days) duration.days = days + } + + if (!skip.has("hours")) { + const hours = diffHours(a, b) + a = addHour(a, -hours) + if (hours) duration.hours = hours + } + + if (!skip.has("minutes")) { + const minutes = diffMinutes(a, b) + a = addMinute(a, -minutes) + if (minutes) duration.minutes = minutes + } + + if (!skip.has("seconds")) { + const seconds = diffSeconds(a, b) + a = addSecond(a, -seconds) + if (seconds) duration.seconds = seconds + } + + if (!skip.has("milliseconds")) { + const ms = diffMilliseconds(a, b) + // removing ms isn't needed as it's the last + if (ms) duration.milliseconds = ms + } + return duration +} diff --git a/src/index.ts b/src/index.ts index 3854023..d6ddb03 100644 --- a/src/index.ts +++ b/src/index.ts @@ -53,3 +53,4 @@ export { diffDays } from "./diffDays" export { diffWeeks } from "./diffWeeks" export { diffMonths } from "./diffMonths" export { diffYears } from "./diffYears" +export { diff, type DiffFnOptions as DiffOptions } from "./diff" diff --git a/src/types.ts b/src/types.ts index 293b674..3a87756 100644 --- a/src/types.ts +++ b/src/types.ts @@ -172,3 +172,16 @@ export interface FormatOptions { */ partFilter?: (part: Part) => boolean } + +export interface DurationObj { + years?: number + months?: number + weeks?: number + days?: number + hours?: number + minutes?: number + seconds?: number + milliseconds?: number + microseconds?: number + nanoseconds?: number +}