Skip to content

Commit 06d20ac

Browse files
committed
feat: add dateTimeFromNow localization util
1 parent 26fb238 commit 06d20ac

File tree

3 files changed

+111
-1
lines changed

3 files changed

+111
-1
lines changed

docs/API-Reference/utils/LocalizationUtils.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,16 @@ Formats a given date object into a locale-aware date and time string.
3232
| [dateTimeFormat.dateStyle] | <code>string</code> | Specifies the date format style. One of: DATE_TIME_STYLE.* |
3333
| [dateTimeFormat.timeStyle] | <code>string</code> | Specifies the time format style. One of: DATE_TIME_STYLE.* |
3434

35+
<a name="dateTimeFromNow"></a>
36+
37+
## dateTimeFromNow([date], [lang]) ⇒ <code>string</code>
38+
Returns a relative time string (e.g., "2 days ago", "in 3 hours") based on the difference between the given date and now.
39+
40+
**Kind**: global function
41+
**Returns**: <code>string</code> - - A human-readable relative time string (e.g., "2 days ago", "in 3 hours").
42+
43+
| Param | Type | Description |
44+
| --- | --- | --- |
45+
| [date] | <code>Date</code> | The date to compare with the current date and time. If not given, defaults to now. |
46+
| [lang] | <code>string</code> | Optional language code to use for formatting (e.g., 'en', 'fr'). If not provided, defaults to the application locale or 'en'. |
47+

src/utils/LocalizationUtils.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,42 @@ define(function (require, exports, module) {
7171
return Intl.DateTimeFormat([lang || brackets.getLocale() || "en", "en"], dateTimeFormat).format(date);
7272
}
7373

74+
/**
75+
* Returns a relative time string (e.g., "2 days ago", "in 3 hours") based on the difference between the given date and now.
76+
*
77+
* @param {Date} [date] - The date to compare with the current date and time. If not given, defaults to now.
78+
* @param {string} [lang] - Optional language code to use for formatting (e.g., 'en', 'fr').
79+
* If not provided, defaults to the application locale or 'en'.
80+
* @returns {string} - A human-readable relative time string (e.g., "2 days ago", "in 3 hours").
81+
*/
82+
function dateTimeFromNow(date, lang) {
83+
date = date || new Date();
84+
const now = new Date();
85+
const diffInSeconds = Math.floor((date - now) / 1000);
86+
87+
const rtf = new Intl.RelativeTimeFormat([lang || brackets.getLocale() || "en", "en"],
88+
{ numeric: 'auto' });
89+
90+
if (Math.abs(diffInSeconds) < 60) {
91+
return rtf.format(diffInSeconds, 'second');
92+
} else if (Math.abs(diffInSeconds) < 3600) {
93+
return rtf.format(Math.floor(diffInSeconds / 60), 'minute');
94+
} else if (Math.abs(diffInSeconds) < 86400) {
95+
return rtf.format(Math.floor(diffInSeconds / 3600), 'hour');
96+
} else if (Math.abs(diffInSeconds) < 2592000) {
97+
return rtf.format(Math.floor(diffInSeconds / 86400), 'day');
98+
} else if (Math.abs(diffInSeconds) < 31536000) {
99+
return rtf.format(Math.floor(diffInSeconds / 2592000), 'month');
100+
} else {
101+
return rtf.format(Math.floor(diffInSeconds / 31536000), 'year');
102+
}
103+
}
104+
105+
74106
// Define public API
75107
exports.getLocalizedLabel = getLocalizedLabel;
76108
exports.getFormattedDateTime = getFormattedDateTime;
109+
exports.dateTimeFromNow = dateTimeFromNow;
77110
// public constants
78111
exports.DATE_TIME_STYLE = DATE_TIME_STYLE;
79112
});

test/spec/LocalizationUtils-test.js

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,8 @@ define(function (require, exports, module) {
114114
};
115115
const formatted = LocalizationUtils.getFormattedDateTime(testDate, "fr", customFormat);
116116
// Example format: "1 janvier 2024 à 13:30"
117-
expect(formatted).toBe("1 janvier 2024 à 13:30");
117+
expect(formatted.includes("1 janvier 2024")).toBeTrue();
118+
expect(formatted.includes("13:30")).toBeTrue();
118119
});
119120

120121
it("should default to current date with custom dateTimeFormat", function () {
@@ -129,5 +130,68 @@ define(function (require, exports, module) {
129130
expect(formattedNow).toBe(expected);
130131
});
131132
});
133+
134+
describe("dateTimeFromNow", function () {
135+
it("should return 'now' for current time", function () {
136+
const now = new Date();
137+
let result = LocalizationUtils.dateTimeFromNow(now, "en");
138+
expect(result).toBe("now");
139+
result = LocalizationUtils.dateTimeFromNow(now, "de");
140+
expect(result).toBe("jetzt");
141+
});
142+
143+
it("should handle future dates within seconds", function () {
144+
const futureDate = new Date(Date.now() + 30 * 1000); // 30 seconds in the future
145+
const result = LocalizationUtils.dateTimeFromNow(futureDate, "en");
146+
expect(result).toBe("in 30 seconds");
147+
});
148+
149+
it("should handle past dates within minutes", function () {
150+
const pastDate = new Date(Date.now() - 90 * 1000); // 90 seconds in the past
151+
const result = LocalizationUtils.dateTimeFromNow(pastDate, "en");
152+
expect(result).toBe("2 minutes ago");
153+
});
154+
155+
it("should handle future dates within hours", function () {
156+
const futureDate = new Date(Date.now() + 2 * 60 * 60 * 1000); // 2 hours in the future
157+
const result = LocalizationUtils.dateTimeFromNow(futureDate, "en");
158+
expect(result).toBe("in 2 hours");
159+
});
160+
161+
it("should handle past dates within days", function () {
162+
const pastDate = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000); // 3 days ago
163+
const result = LocalizationUtils.dateTimeFromNow(pastDate, "en");
164+
expect(result).toBe("3 days ago");
165+
});
166+
167+
it("should handle future dates within months", function () {
168+
const futureDate = new Date(Date.now() + 45 * 24 * 60 * 60 * 1000); // 45 days in the future
169+
const result = LocalizationUtils.dateTimeFromNow(futureDate, "en");
170+
expect(result).toBe("next month");
171+
});
172+
173+
it("should handle past dates within years", function () {
174+
const pastDate = new Date(Date.now() - 2 * 365 * 24 * 60 * 60 * 1000); // 2 years ago
175+
const result = LocalizationUtils.dateTimeFromNow(pastDate, "en");
176+
expect(result).toBe("2 years ago");
177+
});
178+
179+
it("should return relative time in French locale", function () {
180+
const pastDate = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000); // 3 days ago
181+
const result = LocalizationUtils.dateTimeFromNow(pastDate, "fr");
182+
expect(result).toBe("il y a 3 jours");
183+
});
184+
185+
it("should fallback to default locale if an invalid locale is specified", function () {
186+
const futureDate = new Date(Date.now() + 2 * 60 * 60 * 1000); // 2 hours in the future
187+
const result = LocalizationUtils.dateTimeFromNow(futureDate, "invalid-locale");
188+
expect(result).toBe("in 2 hours");
189+
});
190+
191+
it("should handle default date input (now) gracefully", function () {
192+
const result = LocalizationUtils.dateTimeFromNow(undefined, "en");
193+
expect(result).toBe("now");
194+
});
195+
});
132196
});
133197
});

0 commit comments

Comments
 (0)