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

feature: diff & add functions #69

Open
wants to merge 18 commits 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions src/__tests__/add.spec.ts
Original file line number Diff line number Diff line change
@@ -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"))
})
})
49 changes: 49 additions & 0 deletions src/__tests__/diff.spec.ts
Original file line number Diff line number Diff line change
@@ -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,
})
})
})
51 changes: 51 additions & 0 deletions src/add.ts
Original file line number Diff line number Diff line change
@@ -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<DurationObj, "microseconds" | "nanoseconds">,
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
}
130 changes: 130 additions & 0 deletions src/diff.ts
Original file line number Diff line number Diff line change
@@ -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<DurationObj, "microseconds" | "nanoseconds">

// 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<DurationKeys>
}

/**
* 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
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
13 changes: 13 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}