diff --git a/TeamspeakStatsNew/TeamspeakStatsNew/ClientApp/src/app/pipes/date-format.pipe.spec.ts b/TeamspeakStatsNew/TeamspeakStatsNew/ClientApp/src/app/pipes/date-format.pipe.spec.ts index cb09f98..6df4d0f 100644 --- a/TeamspeakStatsNew/TeamspeakStatsNew/ClientApp/src/app/pipes/date-format.pipe.spec.ts +++ b/TeamspeakStatsNew/TeamspeakStatsNew/ClientApp/src/app/pipes/date-format.pipe.spec.ts @@ -41,14 +41,14 @@ describe("DateFormatPipe", () => { expect(formattedDate).toBe("4 days and 0 hours ago"); }); - it("should transform date to 2 months and 3 days ago", () => { + it("should transform date to 2 months and 2 days ago", () => { const currentDate = moment(); const date = currentDate .clone() .subtract(2, "months") .subtract(2, "days"); // This is intended const formattedDate = pipe.transform(date.toDate()); - expect(formattedDate).toBe("2 months and 3 days ago"); + expect(formattedDate).toBe("2 months and 2 days ago"); }); it("should transform date to 4 years and 6 months ago", () => { @@ -92,12 +92,18 @@ describe("DateFormatPipe", () => { const currentDate = moment(); const date = currentDate.clone().subtract(1, "day").subtract(1, "hour"); const formattedDate = pipe.transform(date.toDate()); + expect(formattedDate).toBe("1 day and 1 hour ago"); }); it("should transform date to 1 month and 1 day ago", () => { const currentDate = moment(); - const date = currentDate.clone().subtract(31, "day"); + const date = currentDate.clone().subtract(1, "month"); + + if (date.daysInMonth() === 30) { + date.subtract(1, "day"); + } + const formattedDate = pipe.transform(date.toDate()); expect(formattedDate).toBe("1 month and 1 day ago"); }); @@ -112,6 +118,27 @@ describe("DateFormatPipe", () => { expect(formattedDate).toBe("1 year and 1 month ago"); }); + it("should return just now, if just few miliseconds passes", () => { + const currentDate = moment(); + const date = currentDate.clone().subtract(1, "millisecond"); + const formattedDate = pipe.transform(date.toDate()); + expect(formattedDate).toBe("Just now"); + }); + + it("should return just now, if time is in the future", () => { + const currentDate = moment(); + const date = currentDate.clone().add(1, "millisecond"); + const formattedDate = pipe.transform(date.toDate()); + expect(formattedDate).toBe("Just now"); + }); + + it("should return 1 years and months ago", () => { + const currentDate = moment(); + const date = currentDate.clone().subtract(1, "year"); + const formattedDate = pipe.transform(date.toDate()); + expect(formattedDate).toBe("1 year and 0 months ago"); + }); + it("should update the view every second", fakeAsync(() => { const currentDate = moment(); const date = currentDate.clone().subtract(10, "seconds"); @@ -149,6 +176,4 @@ describe("DateFormatPipe", () => { expect(spyComplete).toHaveBeenCalled(); }); - - // Add more test cases for the remaining scenarios }); diff --git a/TeamspeakStatsNew/TeamspeakStatsNew/ClientApp/src/app/pipes/date-format.pipe.ts b/TeamspeakStatsNew/TeamspeakStatsNew/ClientApp/src/app/pipes/date-format.pipe.ts index b1ddb77..4a0e7e0 100644 --- a/TeamspeakStatsNew/TeamspeakStatsNew/ClientApp/src/app/pipes/date-format.pipe.ts +++ b/TeamspeakStatsNew/TeamspeakStatsNew/ClientApp/src/app/pipes/date-format.pipe.ts @@ -15,49 +15,58 @@ export class DateFormatPipe implements PipeTransform { const date = moment.tz(dateStr, timezone); const now = moment.tz(timezone); - const diff = now.diff(date, "milliseconds", true); - - const seconds = Math.floor(diff / 1000); - const minutes = Math.floor(seconds / 60); - const hours = Math.floor(minutes / 60); - const days = Math.floor(hours / 24); - const months = Math.floor(days / 30); - const years = Math.floor(months / 12); - - if (seconds < 60) { - return `${seconds} second${seconds === 1 ? "" : "s"} ago`; - } else if (minutes < 60) { - const remainingSeconds = seconds % 60; - return `${minutes} minute${ - minutes === 1 ? "" : "s" - } and ${remainingSeconds} second${ - remainingSeconds === 1 ? "" : "s" - } ago`; - } else if (hours < 24) { - const remainingMinutes = minutes % 60; - return `${hours} hour${ - hours === 1 ? "" : "s" - } and ${remainingMinutes} minute${ - remainingMinutes === 1 ? "" : "s" - } ago`; - } else if (days < 30) { - const remainingHours = hours % 24; - return `${days} day${ - days === 1 ? "" : "s" - } and ${remainingHours} hour${remainingHours === 1 ? "" : "s"} ago`; - } else if (months < 12) { - const remainingDays = days % 30; - return `${months} month${ - months === 1 ? "" : "s" - } and ${remainingDays} day${remainingDays === 1 ? "" : "s"} ago`; - } else { - const remainingMonths = months % 12; - return `${years} year${ - years === 1 ? "" : "s" - } and ${remainingMonths} month${ - remainingMonths === 1 ? "" : "s" - } ago`; + const duration = moment.duration(now.diff(date)); + const milliseconds = duration.asMilliseconds(); + + // Handle negative durations (future dates) + if (milliseconds < 0) { + return "Just now"; + } + + const units: ( + | "years" + | "months" + | "days" + | "hours" + | "minutes" + | "seconds" + )[] = ["years", "months", "days", "hours", "minutes", "seconds"]; + + for (let i = 0; i < units.length; i++) { + const unit = units[i]; + const diff = now.diff(date, unit); + if (diff > 0) { + const diffLabel = diff === 1 ? unit.slice(0, -1) : unit; + const nextUnit = units[i + 1]; + + const durationNumber = duration.as(unit); + + if (i < 5) { + let remainingInUnitFloored: number = + duration.as(unit) - Math.floor(duration.as(unit)); + + if (remainingInUnitFloored === durationNumber) { + remainingInUnitFloored = 0; + } + + const remainingInUnit = Math.round( + moment + .duration(remainingInUnitFloored, unit) + .as(nextUnit) + ); + + const remainingLabel = + remainingInUnit === 1 + ? nextUnit.slice(0, -1) + : nextUnit; + + return `${diff} ${diffLabel} and ${remainingInUnit} ${remainingLabel} ago`; + } + + return `${diff} ${diffLabel} ago`; + } } + return `Just now`; } toggleShouldUpdateView() { diff --git a/TeamspeakStatsNew/TeamspeakStatsNew/ClientApp/src/app/services/date/date.service.spec.ts b/TeamspeakStatsNew/TeamspeakStatsNew/ClientApp/src/app/services/date/date.service.spec.ts index d194321..8b30b3f 100644 --- a/TeamspeakStatsNew/TeamspeakStatsNew/ClientApp/src/app/services/date/date.service.spec.ts +++ b/TeamspeakStatsNew/TeamspeakStatsNew/ClientApp/src/app/services/date/date.service.spec.ts @@ -2,6 +2,8 @@ import { TestBed } from "@angular/core/testing"; import { DateService } from "./date.service"; +import * as moment from "moment"; + describe("DateService", () => { let service: DateService; @@ -75,21 +77,31 @@ describe("DateService", () => { it("should return months and days ago", () => { const date = new Date(); - date.setMonth(date.getMonth() - 6); // 4 months ago + date.setMonth(date.getMonth() - 4); // 4 months ago + const formattedDate = service.format(date); - expect(formattedDate).toBe("6 months and 0 days ago"); + expect(formattedDate).toBe("4 months and 0 days ago"); }); it("should return 1 month and 1 day ago", () => { const date = new Date(); - date.setDate(date.getDate() - 31); + date.setMonth(date.getMonth() - 1); + + if (getDaysInMonth(date.getFullYear(), date.getMonth() - 1) === 31) { + date.setDate(date.getDate()); + } else { + date.setDate(date.getDate() - 1); + } + const formattedDate = service.format(date); expect(formattedDate).toBe("1 month and 1 day ago"); }); it("should return 1 years and months ago", () => { const date = new Date(); + date.setFullYear(date.getFullYear() - 1); // 1 year ago + const formattedDate = service.format(date); expect(formattedDate).toBe("1 year and 0 months ago"); }); @@ -101,4 +113,22 @@ describe("DateService", () => { const formattedDate = service.format(date); expect(formattedDate).toBe("2 years and 1 month ago"); }); + + it("should return just now, if just few miliseconds passes", () => { + const date = new Date(); + date.setMilliseconds(date.getMilliseconds() - 1); // 2 years ago + const formattedDate = service.format(date); + expect(formattedDate).toBe("Just now"); + }); + + it("should return just now, if time is in the future", () => { + const date = new Date(); + date.setSeconds(date.getSeconds() + 1); // 2 years ago + const formattedDate = service.format(date); + expect(formattedDate).toBe("Just now"); + }); + + function getDaysInMonth(year: number, month: number) { + return new Date(year, month, 0).getDate(); + } }); diff --git a/TeamspeakStatsNew/TeamspeakStatsNew/ClientApp/src/app/services/date/date.service.ts b/TeamspeakStatsNew/TeamspeakStatsNew/ClientApp/src/app/services/date/date.service.ts index 94122dd..73c220e 100644 --- a/TeamspeakStatsNew/TeamspeakStatsNew/ClientApp/src/app/services/date/date.service.ts +++ b/TeamspeakStatsNew/TeamspeakStatsNew/ClientApp/src/app/services/date/date.service.ts @@ -9,48 +9,59 @@ export class DateService { const date = moment.tz(dateStr, timezone); const now = moment.tz(timezone); - const diff = now.diff(date, "milliseconds", true); - - const seconds = Math.floor(diff / 1000); - const minutes = Math.floor(seconds / 60); - const hours = Math.floor(minutes / 60); - const days = Math.floor(hours / 24); - const months = Math.floor(days / 30); - const years = Math.floor(months / 12); - - if (seconds < 60) { - return `${seconds} second${seconds === 1 ? "" : "s"} ago`; - } else if (minutes < 60) { - const remainingSeconds = seconds % 60; - return `${minutes} minute${ - minutes === 1 ? "" : "s" - } and ${remainingSeconds} second${ - remainingSeconds === 1 ? "" : "s" - } ago`; - } else if (hours < 24) { - const remainingMinutes = minutes % 60; - return `${hours} hour${ - hours === 1 ? "" : "s" - } and ${remainingMinutes} minute${ - remainingMinutes === 1 ? "" : "s" - } ago`; - } else if (days < 30) { - const remainingHours = hours % 24; - return `${days} day${ - days === 1 ? "" : "s" - } and ${remainingHours} hour${remainingHours === 1 ? "" : "s"} ago`; - } else if (months < 12) { - const remainingDays = days % 30; - return `${months} month${ - months === 1 ? "" : "s" - } and ${remainingDays} day${remainingDays === 1 ? "" : "s"} ago`; - } else { - const remainingMonths = months % 12; - return `${years} year${ - years === 1 ? "" : "s" - } and ${remainingMonths} month${ - remainingMonths === 1 ? "" : "s" - } ago`; + const duration = moment.duration(now.diff(date)); + const milliseconds = duration.asMilliseconds(); + + // Handle negative durations (future dates) + if (milliseconds < 0) { + return "Just now"; + } + + const units: ( + | "years" + | "months" + | "days" + | "hours" + | "minutes" + | "seconds" + )[] = ["years", "months", "days", "hours", "minutes", "seconds"]; + + for (let i = 0; i < units.length; i++) { + const unit = units[i]; + const diff = now.diff(date, unit); + if (diff > 0) { + const diffLabel = diff === 1 ? unit.slice(0, -1) : unit; + const nextUnit = units[i + 1]; + + const durationNumber = duration.as(unit); + + if (i < 5) { + let remainingInUnitFloored: number = + duration.as(unit) - Math.floor(duration.as(unit)); + + if (remainingInUnitFloored === durationNumber) { + console.log("test: " + dateStr); + console.log("test: " + now.toDate()); + remainingInUnitFloored = 0; + } + + const remainingInUnit = Math.round( + moment + .duration(remainingInUnitFloored, unit) + .as(nextUnit) + ); + + const remainingLabel = + remainingInUnit === 1 + ? nextUnit.slice(0, -1) + : nextUnit; + + return `${diff} ${diffLabel} and ${remainingInUnit} ${remainingLabel} ago`; + } + + return `${diff} ${diffLabel} ago`; + } } + return `Just now`; } } diff --git a/TeamspeakStatsNew/TeamspeakStatsNew/ClientApp/src/app/services/theme/theme.service.spec.ts b/TeamspeakStatsNew/TeamspeakStatsNew/ClientApp/src/app/services/theme/theme.service.spec.ts index 6a7b7c1..d7fe0b4 100644 --- a/TeamspeakStatsNew/TeamspeakStatsNew/ClientApp/src/app/services/theme/theme.service.spec.ts +++ b/TeamspeakStatsNew/TeamspeakStatsNew/ClientApp/src/app/services/theme/theme.service.spec.ts @@ -152,4 +152,14 @@ describe("ThemeService", () => { "(prefers-color-scheme: dark)" ); })); + + it("should get theme-light, when it is set correctly", fakeAsync(() => { + service.setTheme("theme-light"); + expect(service.getTheme()).toBe("theme-light"); + })); + + it("should get theme-dark, when it is set correctly", fakeAsync(() => { + service.setTheme("theme-dark"); + expect(service.getTheme()).toBe("theme-dark"); + })); });