WebUI: Support modifying default date format

This commit is contained in:
Thomas Piccirello 2025-08-15 19:15:28 -07:00
commit 2b6f858307
No known key found for this signature in database
8 changed files with 223 additions and 19 deletions

View file

@ -275,7 +275,7 @@ window.qBittorrent.AddTorrent ??= (() => {
if (metadata.info?.length !== undefined)
document.getElementById("size").textContent = window.qBittorrent.Misc.friendlyUnit(metadata.info.length, false);
if ((metadata.creation_date !== undefined) && (metadata.creation_date > 1))
document.getElementById("createdDate").textContent = new Date(metadata.creation_date * 1000).toLocaleString();
document.getElementById("createdDate").textContent = window.qBittorrent.Misc.formatDate(new Date(metadata.creation_date * 1000));
if (metadata.comment !== undefined)
document.getElementById("comment").textContent = metadata.comment;

View file

@ -69,6 +69,7 @@ window.qBittorrent.Client ??= (() => {
"show_status_bar",
"show_filters_sidebar",
"hide_zero_status_filters",
"date_format",
"color_scheme",
"full_url_tracker_column",
"use_alt_row_colors",

View file

@ -1426,7 +1426,7 @@ window.qBittorrent.DynamicTable ??= (() => {
// added on
this.columns["added_on"].updateTd = function(td, row) {
const date = new Date(this.getRowValue(row) * 1000).toLocaleString();
const date = window.qBittorrent.Misc.formatDate(new Date(this.getRowValue(row) * 1000));
td.textContent = date;
td.title = date;
};
@ -1439,7 +1439,7 @@ window.qBittorrent.DynamicTable ??= (() => {
td.title = "";
}
else {
const date = new Date(this.getRowValue(row) * 1000).toLocaleString();
const date = window.qBittorrent.Misc.formatDate(new Date(this.getRowValue(row) * 1000));
td.textContent = date;
td.title = date;
}
@ -1964,7 +1964,7 @@ window.qBittorrent.DynamicTable ??= (() => {
};
const displayDate = function(td, row) {
const value = this.getRowValue(row) * 1000;
const formattedValue = (Number.isNaN(value) || (value <= 0)) ? "" : (new Date(value).toLocaleString());
const formattedValue = (Number.isNaN(value) || (value <= 0)) ? "" : window.qBittorrent.Misc.formatDate(new Date(value));
td.textContent = formattedValue;
td.title = formattedValue;
};
@ -3734,7 +3734,7 @@ window.qBittorrent.DynamicTable ??= (() => {
initColumnsFunctions() {
this.columns["timestamp"].updateTd = function(td, row) {
const date = new Date(this.getRowValue(row) * 1000).toLocaleString();
const date = window.qBittorrent.Misc.formatDate(new Date(this.getRowValue(row) * 1000));
td.textContent = date;
td.title = date;
};
@ -3812,7 +3812,7 @@ window.qBittorrent.DynamicTable ??= (() => {
this.newColumn("reason", "", "QBT_TR(Reason)QBT_TR[CONTEXT=ExecutionLogWidget]", 150, true);
this.columns["timestamp"].updateTd = function(td, row) {
const date = new Date(this.getRowValue(row) * 1000).toLocaleString();
const date = window.qBittorrent.Misc.formatDate(new Date(this.getRowValue(row) * 1000));
td.textContent = date;
td.title = date;
};
@ -4036,7 +4036,7 @@ window.qBittorrent.DynamicTable ??= (() => {
td.title = "";
}
else {
const date = new Date(val).toLocaleString();
const date = window.qBittorrent.Misc.formatDate(new Date(val));
td.textContent = date;
td.title = date;
}

View file

@ -28,6 +28,108 @@
"use strict";
/**
* @type Record<string, {locale: string, options: {}}>
*/
const DateFormatOptions = {
"MM/dd/yyyy, h:mm:ss AM/PM": {
locale: "en-US",
options: {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "numeric",
minute: "2-digit",
second: "2-digit",
hour12: true
}
},
"MM/dd/yyyy, HH:mm:ss": {
locale: "en-US",
options: {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false
}
},
"dd/MM/yyyy, HH:mm:ss": {
locale: "en-GB",
options: {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false
}
},
"yyyy-MM-dd HH:mm:ss": {
locale: "sv-SE",
options: {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false
}
},
"yyyy/MM/dd HH:mm:ss": {
locale: "ja-JP",
options: {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false
}
},
"dd.MM.yyyy, HH:mm:ss": {
locale: "en-GB",
options: {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false
}
},
"MMM dd, yyyy, h:mm:ss AM/PM": {
locale: "en-US",
options: {
year: "numeric",
month: "short",
day: "2-digit",
hour: "numeric",
minute: "2-digit",
second: "2-digit",
hour12: true
}
},
"dd MMM yyyy, HH:mm:ss": {
locale: "en-GB",
options: {
year: "numeric",
month: "short",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
hour12: false
}
},
};
window.qBittorrent ??= {};
window.qBittorrent.Misc ??= (() => {
const exports = () => {
@ -46,6 +148,7 @@ window.qBittorrent.Misc ??= (() => {
containsAllTerms: containsAllTerms,
sleep: sleep,
downloadFile: downloadFile,
formatDate: formatDate,
// variables
FILTER_INPUT_DELAY: 400,
MAX_ETA: 8640000
@ -302,6 +405,21 @@ window.qBittorrent.Misc ??= (() => {
}
};
/**
* @param {Date} date
* @param {string} format
* @returns {string}
*/
const formatDate = (date, format = window.parent.qBittorrent.ClientData.getCached("date_format")) => {
if ((format === "default") || !Object.keys(DateFormatOptions).includes(format))
return date.toLocaleString();
const { locale, options } = DateFormatOptions[format];
const formatter = new Intl.DateTimeFormat(locale, options);
const formatted = formatter.format(date).replace(" at ", ", ");
return format.includes(".") ? formatted.replaceAll("/", ".") : formatted;
};
return exports();
})();
Object.freeze(window.qBittorrent.Misc);

View file

@ -177,7 +177,7 @@ window.qBittorrent.PropGeneral ??= (() => {
document.getElementById("reannounce").textContent = window.qBittorrent.Misc.friendlyDuration(data.reannounce);
const lastSeen = (data.last_seen >= 0)
? new Date(data.last_seen * 1000).toLocaleString()
? window.qBittorrent.Misc.formatDate(new Date(data.last_seen * 1000))
: "QBT_TR(Never)QBT_TR[CONTEXT=PropertiesWidget]";
document.getElementById("last_seen").textContent = lastSeen;
@ -195,17 +195,17 @@ window.qBittorrent.PropGeneral ??= (() => {
document.getElementById("created_by").textContent = data.created_by;
const additionDate = (data.addition_date >= 0)
? new Date(data.addition_date * 1000).toLocaleString()
? window.qBittorrent.Misc.formatDate(new Date(data.addition_date * 1000))
: "QBT_TR(Unknown)QBT_TR[CONTEXT=HttpServer]";
document.getElementById("addition_date").textContent = additionDate;
const completionDate = (data.completion_date >= 0)
? new Date(data.completion_date * 1000).toLocaleString()
? window.qBittorrent.Misc.formatDate(new Date(data.completion_date * 1000))
: "";
document.getElementById("completion_date").textContent = completionDate;
const creationDate = (data.creation_date >= 0)
? new Date(data.creation_date * 1000).toLocaleString()
? window.qBittorrent.Misc.formatDate(new Date(data.creation_date * 1000))
: "";
document.getElementById("creation_date").textContent = creationDate;

View file

@ -1,10 +1,34 @@
<div id="BehaviorTab" class="PrefTab">
<fieldset class="settings">
<legend>QBT_TR(Language)QBT_TR[CONTEXT=OptionsDialog]</legend>
<label for="locale_select">QBT_TR(User interface language:)QBT_TR[CONTEXT=OptionsDialog]</label>
<select id="locale_select">
${LANGUAGE_OPTIONS}
</select>
<legend>QBT_TR(Localization)QBT_TR[CONTEXT=OptionsDialog]</legend>
<table>
<tbody>
<tr>
<td><label for="locale_select">QBT_TR(User interface language:)QBT_TR[CONTEXT=OptionsDialog]</label></td>
<td>
<select id="locale_select">
${LANGUAGE_OPTIONS}
</select>
</td>
</tr>
<tr>
<td><label for="dateFormatSelect">QBT_TR(Date format:)QBT_TR[CONTEXT=OptionsDialog]</label></td>
<td>
<select id="dateFormatSelect">
<option value="default">QBT_TR(Browser default)QBT_TR[CONTEXT=OptionsDialog]</option>
<option value="MM/dd/yyyy, h:mm:ss AM/PM">QBT_TR(08/23/2025, 10:32:46 PM)QBT_TR[CONTEXT=OptionsDialog]</option>
<option value="MM/dd/yyyy, HH:mm:ss">QBT_TR(08/23/2025, 22:32:46)QBT_TR[CONTEXT=OptionsDialog]</option>
<option value="dd/MM/yyyy, HH:mm:ss">QBT_TR(23/08/2025, 22:32:46)QBT_TR[CONTEXT=OptionsDialog]</option>
<option value="yyyy-MM-dd HH:mm:ss">QBT_TR(2025-08-23 22:32:46)QBT_TR[CONTEXT=OptionsDialog]</option>
<option value="yyyy/MM/dd HH:mm:ss">QBT_TR(2025/08/23 22:32:46)QBT_TR[CONTEXT=OptionsDialog]</option>
<option value="dd.MM.yyyy, HH:mm:ss">QBT_TR(23.08.2025, 22:32:46)QBT_TR[CONTEXT=OptionsDialog]</option>
<option value="MMM dd, yyyy, h:mm:ss AM/PM">QBT_TR(Aug 23, 2025, 10:32:46 PM)QBT_TR[CONTEXT=OptionsDialog]</option>
<option value="dd MMM yyyy, HH:mm:ss">QBT_TR(23 Aug 2025, 22:32:46)QBT_TR[CONTEXT=OptionsDialog]</option>
</select>
</td>
</tr>
</tbody>
</table>
</fieldset>
<fieldset class="settings">
@ -2229,6 +2253,7 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD
window.parent.qBittorrent.Cache.preferences.init()
.then((pref) => {
const clientData = window.parent.qBittorrent.ClientData;
const dateFormat = clientData.getCached("date_format");
const colorScheme = clientData.getCached("color_scheme");
const fullUrlTrackerColumn = clientData.getCached("full_url_tracker_column");
const useVirtualList = clientData.getCached("use_virtual_list");
@ -2239,8 +2264,13 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD
const useAltRowColors = clientData.getCached("use_alt_row_colors");
// Behavior tab
// Language
// Localization
updateWebuiLocaleSelect(pref.locale);
const dateFormatSelect = document.getElementById("dateFormatSelect");
if ((dateFormat?.length > 0) && ([...dateFormatSelect.options].find(o => o.value === dateFormat) !== undefined))
dateFormatSelect.value = dateFormat;
// Interface
updateColoSchemeSelect(colorScheme);
document.getElementById("statusBarExternalIP").checked = pref.status_bar_external_ip;
@ -2673,6 +2703,7 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD
// Behavior tab
// Language
settings["locale"] = document.getElementById("locale_select").value;
clientData.date_format = document.getElementById("dateFormatSelect").value;
const colorScheme = Number(document.getElementById("colorSchemeSelect").value);
if (colorScheme === 0)
clientData.color_scheme = null;

View file

@ -456,7 +456,7 @@
torrentDate.append(torrentDateDesc);
const torrentDateData = document.createElement("span");
torrentDateData.textContent = new Date(articleDate).toLocaleString();
torrentDateData.textContent = window.qBittorrent.Misc.formatDate(new Date(articleDate));
torrentDate.append(torrentDateData);
detailsView.append(torrentDate);

View file

@ -26,7 +26,7 @@
* exception statement from your version.
*/
import { expect, test } from "vitest";
import { vi, expect, test } from "vitest";
import "../../private/scripts/misc.js";
test("Test toFixedPointString()", () => {
@ -76,3 +76,57 @@ test("Test toFixedPointString()", () => {
expect(toFixedPointString(-100.00, 1)).toBe("-100.0");
expect(toFixedPointString(-100.00, 2)).toBe("-100.00");
});
test("Test formatDate() - Format Coverage", () => {
const formatDate = window.qBittorrent.Misc.formatDate;
const testDate = new Date(2025, 7, 23, 22, 32, 46); // Aug 23, 2025 10:32:46 PM
expect(formatDate(testDate, "MM/dd/yyyy, h:mm:ss AM/PM")).toBe("08/23/2025, 10:32:46 PM");
expect(formatDate(testDate, "MM/dd/yyyy, HH:mm:ss")).toBe("08/23/2025, 22:32:46");
expect(formatDate(testDate, "dd/MM/yyyy, HH:mm:ss")).toBe("23/08/2025, 22:32:46");
expect(formatDate(testDate, "yyyy-MM-dd HH:mm:ss")).toBe("2025-08-23 22:32:46");
expect(formatDate(testDate, "yyyy/MM/dd HH:mm:ss")).toBe("2025/08/23 22:32:46");
expect(formatDate(testDate, "dd.MM.yyyy, HH:mm:ss")).toBe("23.08.2025, 22:32:46");
expect(formatDate(testDate, "MMM dd, yyyy, h:mm:ss AM/PM")).toBe("Aug 23, 2025, 10:32:46 PM");
expect(formatDate(testDate, "dd MMM yyyy, HH:mm:ss")).toBe("23 Aug 2025, 22:32:46");
});
test("Test formatDate() - Fallback Behavior", () => {
// Mock ClientData.getCached
const mockGetCached = vi.fn().mockReturnValue("default");
const originalParent = window.parent;
window.parent = {
qBittorrent: {
ClientData: {
getCached: mockGetCached
}
}
};
const formatDate = window.qBittorrent.Misc.formatDate;
const testDate = new Date(2025, 7, 23, 22, 32, 46); // Aug 23, 2025 10:32:46 PM
const expectedDefault = testDate.toLocaleString();
// Test that "default" format uses toLocaleString()
expect(formatDate(testDate, "default")).toBe(expectedDefault);
// Test default behavior when no format argument is provided
expect(mockGetCached).toHaveBeenCalledTimes(0);
expect(formatDate(testDate)).toBe(expectedDefault);
expect(mockGetCached).toHaveBeenCalledWith("date_format");
expect(mockGetCached).toHaveBeenCalledTimes(1);
// Test with unknown/invalid format strings
expect(formatDate(testDate, "invalid-format")).toBe(expectedDefault);
expect(formatDate(testDate, "")).toBe(expectedDefault);
expect(formatDate(testDate, null)).toBe(expectedDefault);
expect(mockGetCached).toHaveBeenCalledTimes(1);
expect(formatDate(testDate, undefined)).toBe(expectedDefault);
expect(mockGetCached).toHaveBeenCalledWith("date_format");
expect(mockGetCached).toHaveBeenCalledTimes(2);
// Restore original window.parent
window.parent = originalParent;
});