Skip to content

Commit 7e438cd

Browse files
committed
chore: remove legacy Date VO
1 parent ce51734 commit 7e438cd

12 files changed

Lines changed: 204 additions & 236 deletions

File tree

src/features/carts/infrastructure/useAddToCart.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useMutation } from "@tanstack/react-query";
22

33
import { useAuthStore } from "@/features/auth/application/authStore";
4-
import { dateVO } from "@/lib/format/Date";
4+
import { DateVO } from "@/lib/date/Date";
55
import { httpService } from "@/lib/http";
66
import { Logger } from "@/lib/logger";
77

@@ -27,7 +27,7 @@ export const useAddToCart = () => {
2727
>((body) =>
2828
httpService.put<void, IAddToCartDto>(`carts/${cartId}`, {
2929
userId,
30-
date: dateVO.now(),
30+
date: DateVO.now(),
3131
products: [{ productId: body.productId, quantity: body.quantity ?? 1 }],
3232
})
3333
);

src/features/demo/presentation/Demo.tsx

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import { VStack, Text, Heading, Center, Button } from "@chakra-ui/react";
22

33
import { useCounter } from "@/features/demo/application/useCounter";
4-
import { dateVO } from "@/lib/format/Date";
4+
import { useFormatDate } from "@/lib/date/useFormatDate";
5+
import { useFormatDateTime } from "@/lib/date/useFormatDateTime";
56
import { moneyVO } from "@/lib/format/Money";
67
import { numberVO } from "@/lib/format/Number";
78

89
const Demo = () => {
10+
const formatDate = useFormatDate();
11+
const formatDateTime = useFormatDateTime();
12+
913
const { count, increment } = useCounter();
1014

1115
return (
@@ -19,15 +23,10 @@ const Demo = () => {
1923
</Text>
2024
</VStack>
2125
<Text fontWeight="400">
22-
{`Vite had ${numberVO.format("2696684.12")} weekly downloads on NPM in ${dateVO.formatDate(new window.Date(2023, 1, 17, 10, 44, 0))}`}
23-
</Text>
24-
<Text fontWeight="400">
25-
{`${numberVO.format(1000)} bitcoins were worth ${moneyVO.format(23753382.63, "USD")} on ${dateVO.formatDateTime(new Date(2023, 1, 17, 12, 44, 0))}`}
26+
{`Vite had ${numberVO.format("2696684.12")} weekly downloads on NPM in ${formatDate(new window.Date(2023, 1, 17, 10, 44, 0), { format: "long" })}`}
2627
</Text>
2728
<Text fontWeight="400">
28-
{`Storybook conference: ${dateVO.formatRelativeTime(
29-
dateVO.given(new Date(2023, 1, 17, 12, 44, 0))
30-
)}`}
29+
{`${numberVO.format(1000)} bitcoins were worth ${moneyVO.format(23753382.63, "USD")} on ${formatDateTime(new Date(2023, 1, 17, 12, 44, 0))}`}
3130
</Text>
3231
</VStack>
3332
</Center>

src/lib/date/Date.ts

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1+
type DateBase = string | Date;
2+
13
export class DateVO {
24
static isValidDateFormat = (date: unknown) =>
35
// eslint-disable-next-line @typescript-eslint/no-base-to-string
46
!!date && !isNaN(Date.parse(date.toString?.()));
57

6-
static isDateBefore = (
7-
dateToCheck: Date | string,
8-
referenceDate: Date | string
9-
) => {
8+
static isDateBefore = (dateToCheck: DateBase, referenceDate: DateBase) => {
109
const parsedDateToCheck =
1110
typeof dateToCheck === "string" ? new Date(dateToCheck) : dateToCheck;
1211
const parsedReferenceDate =
@@ -17,10 +16,7 @@ export class DateVO {
1716
return parsedDateToCheck < parsedReferenceDate;
1817
};
1918

20-
static isDateAfter = (
21-
dateToCheck: Date | string,
22-
referenceDate: Date | string
23-
) => {
19+
static isDateAfter = (dateToCheck: DateBase, referenceDate: DateBase) => {
2420
const parsedDateToCheck =
2521
typeof dateToCheck === "string" ? new Date(dateToCheck) : dateToCheck;
2622
const parsedReferenceDate =
@@ -30,4 +26,37 @@ export class DateVO {
3026

3127
return parsedDateToCheck > parsedReferenceDate;
3228
};
29+
30+
static now = () => new Date().toISOString();
31+
32+
static given = (date: DateBase) => new Date(date).toISOString();
33+
34+
static past = (min?: DateBase) => {
35+
const today = new Date();
36+
37+
return DateVO.range(
38+
min ?? new Date(today.getFullYear() - 1, today.getMonth()),
39+
today
40+
).toISOString();
41+
};
42+
43+
static future = (max?: DateBase) => {
44+
const today = new Date();
45+
46+
return DateVO.range(
47+
today,
48+
max ?? new Date(today.getFullYear() + 1, today.getMonth())
49+
).toISOString();
50+
};
51+
52+
static range = (min: DateBase, max: DateBase) => {
53+
const minValue = new Date(min).getTime();
54+
const maxValue = new Date(max).getTime();
55+
56+
const timestamp = Math.floor(
57+
Math.random() * (maxValue - minValue + 1) + minValue
58+
);
59+
60+
return new Date(timestamp);
61+
};
3362
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { renderHook } from "@testing-library/react";
2+
import { describe, expect, it, vi } from "vitest";
3+
4+
import { useRelativeTime } from "./useRelativeTime";
5+
6+
describe("useRelativeTime", () => {
7+
// Mock current date to ensure consistent test results
8+
const NOW = new Date("2025-07-18T12:00:00.000Z");
9+
10+
beforeEach(() => {
11+
vi.useFakeTimers();
12+
vi.setSystemTime(NOW);
13+
});
14+
15+
afterEach(() => {
16+
vi.useRealTimers();
17+
});
18+
19+
it("should format seconds ago", () => {
20+
const date = new Date("2025-07-18T11:59:30.000Z"); // 30 seconds ago
21+
22+
const { result } = renderHook(() => useRelativeTime());
23+
const format = result.current;
24+
25+
expect(format(date)).toBe("30 seconds ago");
26+
});
27+
28+
it("should format minutes ago", () => {
29+
const date = new Date("2025-07-18T11:55:00.000Z"); // 5 minutes ago
30+
31+
const { result } = renderHook(() => useRelativeTime());
32+
const format = result.current;
33+
34+
expect(format(date)).toBe("5 minutes ago");
35+
});
36+
37+
it("should format hours ago", () => {
38+
const date = new Date("2025-07-18T09:00:00.000Z"); // 3 hours ago
39+
40+
const { result } = renderHook(() => useRelativeTime());
41+
const format = result.current;
42+
43+
expect(format(date)).toBe("3 hours ago");
44+
});
45+
46+
it("should format days ago", () => {
47+
const date = new Date("2025-07-16T12:00:00.000Z"); // 2 days ago
48+
49+
const { result } = renderHook(() => useRelativeTime());
50+
const format = result.current;
51+
52+
expect(format(date)).toBe("2 days ago");
53+
});
54+
55+
it("should format weeks ago", () => {
56+
const date = new Date("2025-07-04T12:00:00.000Z"); // 2 weeks ago
57+
58+
const { result } = renderHook(() => useRelativeTime());
59+
const format = result.current;
60+
61+
expect(format(date)).toBe("2 weeks ago");
62+
});
63+
64+
it("should format months ago", () => {
65+
const date = new Date("2025-05-20T12:00:00.000Z"); // 2 months ago
66+
67+
const { result } = renderHook(() => useRelativeTime());
68+
const format = result.current;
69+
70+
expect(format(date)).toBe("2 months ago");
71+
});
72+
73+
it("should format years ago", () => {
74+
const date = new Date("2024-07-18T12:00:00.000Z"); // 1 year ago
75+
76+
const { result } = renderHook(() => useRelativeTime());
77+
const format = result.current;
78+
79+
expect(format(date)).toBe("last year");
80+
});
81+
82+
it("should format future dates", () => {
83+
const date = new Date("2025-07-19T12:00:00.000Z"); // 1 day in future
84+
85+
const { result } = renderHook(() => useRelativeTime());
86+
const format = result.current;
87+
88+
expect(format(date)).toBe("tomorrow");
89+
});
90+
91+
it("should handle string date input", () => {
92+
const date = "2025-07-17T12:00:00.000Z"; // 1 day ago
93+
94+
const { result } = renderHook(() => useRelativeTime());
95+
const format = result.current;
96+
97+
expect(format(date)).toBe("yesterday");
98+
});
99+
});

src/lib/date/useRelativeTime.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { useCallback } from "react";
2+
import { useTranslation } from "react-i18next";
3+
4+
export const useRelativeTime = () => {
5+
const { i18n } = useTranslation();
6+
7+
return useCallback(
8+
(date: Date | string) => {
9+
const timeMs =
10+
date instanceof Date ? date.getTime() : new Date(date).getTime();
11+
const deltaSeconds = Math.round((timeMs - Date.now()) / 1000);
12+
const cutoffs = [
13+
60,
14+
3600,
15+
86400,
16+
86400 * 7,
17+
86400 * 30,
18+
86400 * 365,
19+
Infinity,
20+
];
21+
const units = [
22+
"second",
23+
"minute",
24+
"hour",
25+
"day",
26+
"week",
27+
"month",
28+
"year",
29+
];
30+
const unitIndex = cutoffs.findIndex(
31+
(cutoff) => cutoff > Math.abs(deltaSeconds)
32+
);
33+
const divisor = unitIndex ? cutoffs[unitIndex - 1] : 1;
34+
35+
const rtf = new Intl.RelativeTimeFormat(i18n.resolvedLanguage, {
36+
numeric: "auto",
37+
});
38+
39+
return rtf.format(
40+
Math.floor(deltaSeconds / divisor),
41+
units[unitIndex] as Intl.RelativeTimeFormatUnit
42+
);
43+
},
44+
[i18n.resolvedLanguage]
45+
);
46+
};

0 commit comments

Comments
 (0)