Skip to content

Commit bdb9490

Browse files
authored
#5800 - Date formatting (#5808)
* Work In Progress for DateTimeUtil Signed-off-by: StanZGenchev <stan.genchev@codbex.com> * Fixed #5800 Signed-off-by: StanZGenchev <stan.genchev@codbex.com> --------- Signed-off-by: StanZGenchev <stan.genchev@codbex.com>
1 parent 41cb9de commit bdb9490

17 files changed

Lines changed: 322 additions & 123 deletions

File tree

components/engine/engine-web/src/main/resources/platform-links.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,5 +254,11 @@
254254
"type": "SCRIPT",
255255
"path": "/webjars/angular-file-upload/dist/angular-file-upload.min.js"
256256
}
257+
],
258+
"date-time-util": [
259+
{
260+
"type": "SCRIPT",
261+
"path": "/services/web/platform-core/utilities/datetime.js"
262+
}
257263
]
258264
}

components/resources/platform-core/src/main/resources/META-INF/dirigible/platform-core/ui/platform/dialogs.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ angular.module('platformDialogs', ['blimpKit', 'platformView', 'platformLocale']
1818
Extensions.getWindows().then((response) => {
1919
cachedWindows = response.data;
2020
}, (error) => {
21-
console.log(error);
21+
console.error(error);
2222
});
2323
// @ts-ignore
2424
const dialogHub = new DialogHub();
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/*
2+
* Copyright (c) 2026 Eclipse Dirigible contributors
3+
*
4+
* All rights reserved. This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License v2.0
6+
* which accompanies this distribution, and is available at
7+
* http://www.eclipse.org/legal/epl-v20.html
8+
*
9+
* SPDX-FileCopyrightText: Eclipse Dirigible contributors
10+
* SPDX-License-Identifier: EPL-2.0
11+
*/
12+
class DateTimeUtil {
13+
/**
14+
* DateTime utility for formatting and getting relative time.
15+
* Uses native Intl APIs for localization.
16+
* @param {string} [locale] - Optional locale (e.g. "en", "bg", "de"). Defaults to runtime locale.
17+
*/
18+
constructor(locale = undefined) {
19+
this.locale = locale;
20+
21+
// Cache Intl formatters for performance
22+
this._dtf = {
23+
default: new Intl.DateTimeFormat(this.locale),
24+
monthLong: new Intl.DateTimeFormat(this.locale, { month: "long" }),
25+
monthShort: new Intl.DateTimeFormat(this.locale, { month: "short" }),
26+
weekdayLong: new Intl.DateTimeFormat(this.locale, { weekday: "long" }),
27+
weekdayShort: new Intl.DateTimeFormat(this.locale, { weekday: "short" }),
28+
dayPeriod: new Intl.DateTimeFormat(this.locale, { hour: "numeric", hour12: true })
29+
};
30+
31+
this._rtf = new Intl.RelativeTimeFormat(this.locale, { numeric: "auto" });
32+
}
33+
34+
/**
35+
* Convert input into a valid Date object.
36+
* @private
37+
* @param {string|Date|number} input - ISO string, Date object, or timestamp.
38+
* @returns {Date}
39+
* @throws {Error} If the date is invalid.
40+
*/
41+
toDate(input) {
42+
const date = input instanceof Date ? input : new Date(input);
43+
if (isNaN(date)) throw new Error("Invalid date");
44+
return date;
45+
}
46+
47+
/**
48+
* Pad a number with leading zeros.
49+
* @private
50+
* @param {number} num
51+
* @returns {string}
52+
*/
53+
pad(num) {
54+
return String(num).padStart(2, "0");
55+
}
56+
57+
/**
58+
* Format a date using either:
59+
* 1) Custom tokens
60+
* 2) Intl.DateTimeFormat options
61+
* 3) Locale default formatting (if no format is provided)
62+
*
63+
* ---
64+
* Supported tokens:
65+
* YYYY, YY - Year (2026, 26)
66+
* MMMM, MMM, MM, M - Month (January, Jan, 01, 1), localized
67+
* DD, D, dddd, ddd - Day (01, 1, Thursday, Thu), localized
68+
* HH, H, hh, h - Hour (09, 9, 09, 9), lowercase is for the 12 hour format
69+
* mm, m - Minute (07, 7)
70+
* ss, s - Second (01, 1)
71+
* A (AM/PM, localized)
72+
*
73+
* ---
74+
* Behavior:
75+
* - If `formatStr` is omitted → uses locale default format
76+
* - If `formatStr` is an object → uses Intl.DateTimeFormat options
77+
* - If `formatStr` is a string → uses token-based formatting
78+
*
79+
* @param {string|Date|number} input - Date input (ISO string, Date object, or timestamp)
80+
* @param {string|Object} [formatStr] - Format string OR Intl options
81+
* @returns {string}
82+
*
83+
* @example
84+
* du.format("2026-03-26", "YYYY-MM-DD") // "2026-03-26"
85+
*
86+
* @example
87+
* du.format("2026-03-26", "D MMMM YYYY") // "26 March 2026"
88+
*
89+
* @example
90+
* du.format("2026-03-26") // locale default (e.g. "3/26/2026")
91+
*
92+
* @example
93+
* du.format("2026-03-26", { dateStyle: "long" }) // "March 26, 2026"
94+
*/
95+
format(input, formatStr) {
96+
const date = this.toDate(input);
97+
98+
// 👉 Default locale format
99+
if (!formatStr) {
100+
return this._dtf.default.format(date);
101+
}
102+
103+
// 👉 Intl options
104+
if (typeof formatStr === "object") {
105+
return new Intl.DateTimeFormat(this.locale, formatStr).format(date);
106+
}
107+
108+
const hours24 = date.getHours();
109+
const hours12 = hours24 % 12 || 12;
110+
111+
const map = {
112+
YYYY: date.getFullYear(),
113+
YY: String(date.getFullYear()).slice(-2),
114+
115+
MMMM: this._dtf.monthLong.format(date),
116+
MMM: this._dtf.monthShort.format(date),
117+
MM: this.pad(date.getMonth() + 1),
118+
M: date.getMonth() + 1,
119+
120+
DD: this.pad(date.getDate()),
121+
D: date.getDate(),
122+
123+
dddd: this._dtf.weekdayLong.format(date),
124+
ddd: this._dtf.weekdayShort.format(date),
125+
126+
HH: this.pad(hours24),
127+
H: hours24,
128+
129+
hh: this.pad(hours12),
130+
h: hours12,
131+
132+
mm: this.pad(date.getMinutes()),
133+
m: date.getMinutes(),
134+
135+
ss: this.pad(date.getSeconds()),
136+
s: date.getSeconds(),
137+
138+
A: this._dtf.dayPeriod
139+
.formatToParts(date)
140+
.find(p => p.type === "dayPeriod")?.value || ""
141+
};
142+
143+
return formatStr.replace(
144+
/YYYY|YY|MMMM|MMM|MM|M|DD|D|dddd|ddd|HH|H|hh|h|mm|m|ss|s|A/g,
145+
token => map[token]
146+
);
147+
}
148+
149+
/**
150+
* Format a date relative to another date (e.g. "2 hours ago", "in 3 days").
151+
*
152+
* @param {string|Date|number} input - Target date
153+
* @param {string|Date|number} [base=new Date()] - Base date to compare against
154+
* @returns {string}
155+
*
156+
* @example
157+
* du.relative(Date.now() - 60000) // "1 minute ago"
158+
* du.relative(Date.now() + 86400000) // "in 1 day"
159+
*/
160+
relative(input, base = new Date()) {
161+
const date = this.toDate(input);
162+
const now = this.toDate(base);
163+
164+
const diffSec = Math.round((date - now) / 1000);
165+
166+
const units = [
167+
{ limit: 60, unit: "second", value: diffSec },
168+
{ limit: 60, unit: "minute", value: diffSec / 60 },
169+
{ limit: 24, unit: "hour", value: diffSec / 3600 },
170+
{ limit: 7, unit: "day", value: diffSec / 86400 },
171+
{ limit: 4.345, unit: "week", value: diffSec / 604800 },
172+
{ limit: 12, unit: "month", value: diffSec / 2629800 },
173+
{ limit: Infinity, unit: "year", value: diffSec / 31557600 }
174+
];
175+
176+
for (const u of units) {
177+
if (Math.abs(u.value) < u.limit) {
178+
return this._rtf.format(Math.round(u.value), u.unit);
179+
}
180+
}
181+
}
182+
}

0 commit comments

Comments
 (0)