environment: Remove toLocaleFormat and add dateUtils for date formatting

`toLocaleFormat` is now `formatDateWithCFormatString` and formatTime and
formatTimeSpan are moved into dateUtils.

Instead of overriding system.clearDateCaches, add a helper in dateUtils.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2827>
This commit is contained in:
Evan Welsh 2023-07-07 17:58:11 -07:00
parent 88eb04a42c
commit 8d48dc8c6f
8 changed files with 222 additions and 192 deletions

View File

@ -14,6 +14,7 @@
<file>misc/config.js</file>
<file>misc/extensionUtils.js</file>
<file>misc/fileUtils.js</file>
<file>misc/dateUtils.js</file>
<file>misc/dbusUtils.js</file>
<file>misc/gnomeSession.js</file>
<file>misc/history.js</file>

191
js/misc/dateUtils.js Normal file
View File

@ -0,0 +1,191 @@
/* exported formatDateWithCFormatString, formatTime, formatTimeSpan, clearCachedLocalTimeZone */
const System = imports.system;
const Gettext = imports.gettext;
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const Shell = imports.gi.Shell;
const Params = imports.misc.params;
let _desktopSettings = null;
let _localTimeZone = null;
function formatDateWithCFormatString(date, format) {
if (_localTimeZone === null)
_localTimeZone = GLib.TimeZone.new_local();
let dt = GLib.DateTime.new(_localTimeZone,
date.getFullYear(),
date.getMonth() + 1,
date.getDate(),
date.getHours(),
date.getMinutes(),
date.getSeconds());
return dt?.format(format) ?? '';
}
function formatTimeSpan(date) {
let now = GLib.DateTime.new_now_local();
const timespan = now.difference(date);
const minutesAgo = timespan / GLib.TIME_SPAN_MINUTE;
const hoursAgo = timespan / GLib.TIME_SPAN_HOUR;
const daysAgo = timespan / GLib.TIME_SPAN_DAY;
const weeksAgo = daysAgo / 7;
const monthsAgo = daysAgo / 30;
const yearsAgo = weeksAgo / 52;
if (minutesAgo < 5)
return _('Just now');
if (hoursAgo < 1) {
return Gettext.ngettext(
'%d minute ago',
'%d minutes ago',
minutesAgo
).format(minutesAgo);
}
if (daysAgo < 1) {
return Gettext.ngettext(
'%d hour ago',
'%d hours ago',
hoursAgo
).format(hoursAgo);
}
if (daysAgo < 2)
return _('Yesterday');
if (daysAgo < 15) {
return Gettext.ngettext(
'%d day ago',
'%d days ago',
daysAgo
).format(daysAgo);
}
if (weeksAgo < 8) {
return Gettext.ngettext(
'%d week ago',
'%d weeks ago',
weeksAgo
).format(weeksAgo);
}
if (yearsAgo < 1) {
return Gettext.ngettext(
'%d month ago',
'%d months ago',
monthsAgo
).format(monthsAgo);
}
return Gettext.ngettext(
'%d year ago',
'%d years ago',
yearsAgo
).format(yearsAgo);
}
function formatTime(time, params) {
let date;
// HACK: The built-in Date type sucks at timezones, which we need for the
// world clock; it's often more convenient though, so allow either
// Date or GLib.DateTime as parameter
if (time instanceof Date)
date = GLib.DateTime.new_from_unix_local(time.getTime() / 1000);
else
date = time;
const now = GLib.DateTime.new_now_local();
const daysAgo = now.difference(date) / (24 * 60 * 60 * 1000 * 1000);
let format;
if (_desktopSettings == null)
_desktopSettings = new Gio.Settings({schema_id: 'org.gnome.desktop.interface'});
const clockFormat = _desktopSettings.get_string('clock-format');
params = Params.parse(params, {
timeOnly: false,
ampm: true,
});
if (clockFormat === '24h') {
// Show only the time if date is on today
if (daysAgo < 1 || params.timeOnly)
/* Translators: Time in 24h format */
format = N_('%H\u2236%M');
// Show the word "Yesterday" and time if date is on yesterday
else if (daysAgo < 2)
/* Translators: this is the word "Yesterday" followed by a
time string in 24h format. i.e. "Yesterday, 14:30" */
// xgettext:no-c-format
format = N_('Yesterday, %H\u2236%M');
// Show a week day and time if date is in the last week
else if (daysAgo < 7)
/* Translators: this is the week day name followed by a time
string in 24h format. i.e. "Monday, 14:30" */
// xgettext:no-c-format
format = N_('%A, %H\u2236%M');
else if (date.get_year() === now.get_year())
/* Translators: this is the month name and day number
followed by a time string in 24h format.
i.e. "May 25, 14:30" */
// xgettext:no-c-format
format = N_('%B %-d, %H\u2236%M');
else
/* Translators: this is the month name, day number, year
number followed by a time string in 24h format.
i.e. "May 25 2012, 14:30" */
// xgettext:no-c-format
format = N_('%B %-d %Y, %H\u2236%M');
} else {
// Show only the time if date is on today
if (daysAgo < 1 || params.timeOnly) // eslint-disable-line no-lonely-if
/* Translators: Time in 12h format */
format = N_('%l\u2236%M %p');
// Show the word "Yesterday" and time if date is on yesterday
else if (daysAgo < 2)
/* Translators: this is the word "Yesterday" followed by a
time string in 12h format. i.e. "Yesterday, 2:30 pm" */
// xgettext:no-c-format
format = N_('Yesterday, %l\u2236%M %p');
// Show a week day and time if date is in the last week
else if (daysAgo < 7)
/* Translators: this is the week day name followed by a time
string in 12h format. i.e. "Monday, 2:30 pm" */
// xgettext:no-c-format
format = N_('%A, %l\u2236%M %p');
else if (date.get_year() === now.get_year())
/* Translators: this is the month name and day number
followed by a time string in 12h format.
i.e. "May 25, 2:30 pm" */
// xgettext:no-c-format
format = N_('%B %-d, %l\u2236%M %p');
else
/* Translators: this is the month name, day number, year
number followed by a time string in 12h format.
i.e. "May 25 2012, 2:30 pm"*/
// xgettext:no-c-format
format = N_('%B %-d %Y, %l\u2236%M %p');
}
// Time in short 12h format, without the equivalent of "AM" or "PM"; used
// when it is clear from the context
if (!params.ampm)
format = format.replace(/\s*%p/g, '');
let formattedTime = date.format(Shell.util_translate_time_string(format));
// prepend LTR-mark to colon/ratio to force a text direction on times
return formattedTime.replace(/([:\u2236])/g, '\u200e$1');
}
/**
* Update the timezone used by JavaScript Date objects and other
* date utilities
*/
function clearCachedLocalTimeZone() {
// SpiderMonkey caches the time zone so we must explicitly clear it
// before we can update the calendar, see
// https://bugzilla.gnome.org/show_bug.cgi?id=678507
System.clearDateCaches();
_localTimeZone = GLib.TimeZone.new_local();
}

View File

@ -1,8 +1,7 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported findUrls, spawn, spawnCommandLine, spawnApp, trySpawnCommandLine,
formatTime, formatTimeSpan, createTimeLabel, insertSorted,
ensureActorVisibleInScrollView, wiggle, lerp, GNOMEversionCompare,
DBusSenderChecker, Highlighter */
createTimeLabel, insertSorted, ensureActorVisibleInScrollView,
wiggle, lerp, GNOMEversionCompare, DBusSenderChecker, Highlighter */
const Clutter = imports.gi.Clutter;
const Gio = imports.gi.Gio;
@ -10,10 +9,10 @@ const GLib = imports.gi.GLib;
const Shell = imports.gi.Shell;
const St = imports.gi.St;
const GnomeDesktop = imports.gi.GnomeDesktop;
const Gettext = imports.gettext;
const Main = imports.ui.main;
const Params = imports.misc.params;
const {formatTime} = imports.misc.dateUtils;
var SCROLL_TIME = 100;
@ -180,141 +179,6 @@ function _handleSpawnError(command, err) {
Main.notifyError(title, err.message);
}
function formatTimeSpan(date) {
let now = GLib.DateTime.new_now_local();
let timespan = now.difference(date);
let minutesAgo = timespan / GLib.TIME_SPAN_MINUTE;
let hoursAgo = timespan / GLib.TIME_SPAN_HOUR;
let daysAgo = timespan / GLib.TIME_SPAN_DAY;
let weeksAgo = daysAgo / 7;
let monthsAgo = daysAgo / 30;
let yearsAgo = weeksAgo / 52;
if (minutesAgo < 5)
return _("Just now");
if (hoursAgo < 1) {
return Gettext.ngettext("%d minute ago",
"%d minutes ago", minutesAgo).format(minutesAgo);
}
if (daysAgo < 1) {
return Gettext.ngettext("%d hour ago",
"%d hours ago", hoursAgo).format(hoursAgo);
}
if (daysAgo < 2)
return _("Yesterday");
if (daysAgo < 15) {
return Gettext.ngettext("%d day ago",
"%d days ago", daysAgo).format(daysAgo);
}
if (weeksAgo < 8) {
return Gettext.ngettext("%d week ago",
"%d weeks ago", weeksAgo).format(weeksAgo);
}
if (yearsAgo < 1) {
return Gettext.ngettext("%d month ago",
"%d months ago", monthsAgo).format(monthsAgo);
}
return Gettext.ngettext("%d year ago",
"%d years ago", yearsAgo).format(yearsAgo);
}
function formatTime(time, params) {
let date;
// HACK: The built-in Date type sucks at timezones, which we need for the
// world clock; it's often more convenient though, so allow either
// Date or GLib.DateTime as parameter
if (time instanceof Date)
date = GLib.DateTime.new_from_unix_local(time.getTime() / 1000);
else
date = time;
let now = GLib.DateTime.new_now_local();
let daysAgo = now.difference(date) / (24 * 60 * 60 * 1000 * 1000);
let format;
if (_desktopSettings == null)
_desktopSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.interface' });
let clockFormat = _desktopSettings.get_string('clock-format');
params = Params.parse(params, {
timeOnly: false,
ampm: true,
});
if (clockFormat == '24h') {
// Show only the time if date is on today
if (daysAgo < 1 || params.timeOnly)
/* Translators: Time in 24h format */
format = N_("%H\u2236%M");
// Show the word "Yesterday" and time if date is on yesterday
else if (daysAgo < 2)
/* Translators: this is the word "Yesterday" followed by a
time string in 24h format. i.e. "Yesterday, 14:30" */
// xgettext:no-c-format
format = N_("Yesterday, %H\u2236%M");
// Show a week day and time if date is in the last week
else if (daysAgo < 7)
/* Translators: this is the week day name followed by a time
string in 24h format. i.e. "Monday, 14:30" */
// xgettext:no-c-format
format = N_("%A, %H\u2236%M");
else if (date.get_year() == now.get_year())
/* Translators: this is the month name and day number
followed by a time string in 24h format.
i.e. "May 25, 14:30" */
// xgettext:no-c-format
format = N_("%B %-d, %H\u2236%M");
else
/* Translators: this is the month name, day number, year
number followed by a time string in 24h format.
i.e. "May 25 2012, 14:30" */
// xgettext:no-c-format
format = N_("%B %-d %Y, %H\u2236%M");
} else {
// Show only the time if date is on today
if (daysAgo < 1 || params.timeOnly) // eslint-disable-line no-lonely-if
/* Translators: Time in 12h format */
format = N_("%l\u2236%M %p");
// Show the word "Yesterday" and time if date is on yesterday
else if (daysAgo < 2)
/* Translators: this is the word "Yesterday" followed by a
time string in 12h format. i.e. "Yesterday, 2:30 pm" */
// xgettext:no-c-format
format = N_("Yesterday, %l\u2236%M %p");
// Show a week day and time if date is in the last week
else if (daysAgo < 7)
/* Translators: this is the week day name followed by a time
string in 12h format. i.e. "Monday, 2:30 pm" */
// xgettext:no-c-format
format = N_("%A, %l\u2236%M %p");
else if (date.get_year() == now.get_year())
/* Translators: this is the month name and day number
followed by a time string in 12h format.
i.e. "May 25, 2:30 pm" */
// xgettext:no-c-format
format = N_("%B %-d, %l\u2236%M %p");
else
/* Translators: this is the month name, day number, year
number followed by a time string in 12h format.
i.e. "May 25 2012, 2:30 pm"*/
// xgettext:no-c-format
format = N_("%B %-d %Y, %l\u2236%M %p");
}
// Time in short 12h format, without the equivalent of "AM" or "PM"; used
// when it is clear from the context
if (!params.ampm)
format = format.replace(/\s*%p/g, '');
let formattedTime = date.format(Shell.util_translate_time_string(format));
// prepend LTR-mark to colon/ratio to force a text direction on times
return formattedTime.replace(/([:\u2236])/g, '\u200e$1');
}
function createTimeLabel(date, params) {
if (_desktopSettings == null)
_desktopSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.interface' });

View File

@ -15,7 +15,8 @@ const Mpris = imports.ui.mpris;
const PopupMenu = imports.ui.popupMenu;
const Util = imports.misc.util;
const { loadInterfaceXML } = imports.misc.fileUtils;
const {formatDateWithCFormatString, formatTimeSpan} = imports.misc.dateUtils;
const {loadInterfaceXML} = imports.misc.fileUtils;
var SHOW_WEEKDATE_KEY = 'show-weekdate';
@ -520,7 +521,7 @@ var Calendar = GObject.registerClass({
iter.setSeconds(0); // Leap second protection. Hah!
iter.setHours(12);
for (let i = 0; i < 7; i++) {
// Could use iter.toLocaleFormat('%a') but that normally gives three characters
// Could use formatDateWithCFormatString(iter, '%a') but that normally gives three characters
// and we want, ideally, a single character for e.g. S M T W T F S
let customDayAbbrev = _getCalendarDayAbbreviation(iter.getDay());
let label = new St.Label({
@ -528,7 +529,7 @@ var Calendar = GObject.registerClass({
text: customDayAbbrev,
can_focus: true,
});
label.accessible_name = iter.toLocaleFormat('%A');
label.accessible_name = formatDateWithCFormatString(iter, '%A');
let col;
if (this.get_text_direction() == Clutter.TextDirection.RTL)
col = 6 - (7 + iter.getDay() - this._weekStart) % 7;
@ -656,7 +657,7 @@ var Calendar = GObject.registerClass({
while (row < nRows) {
let button = new St.Button({
// xgettext:no-javascript-format
label: iter.toLocaleFormat(C_('date day number format', '%d')),
label: formatDateWithCFormatString(iter, C_('date day number format', '%d')),
can_focus: true,
});
let rtl = button.get_text_direction() == Clutter.TextDirection.RTL;
@ -711,13 +712,13 @@ var Calendar = GObject.registerClass({
if (this._useWeekdate && iter.getDay() == 4) {
const label = new St.Label({
text: iter.toLocaleFormat('%V'),
text: formatDateWithCFormatString(iter, '%V'),
style_class: 'calendar-week-number',
can_focus: true,
});
let weekFormat = Shell.util_translate_time_string(N_("Week %V"));
label.clutter_text.y_align = Clutter.ActorAlign.CENTER;
label.accessible_name = iter.toLocaleFormat(weekFormat);
label.accessible_name = formatDateWithCFormatString(iter, weekFormat);
layout.attach(label, rtl ? 7 : 0, row, 1, 1);
}
@ -736,9 +737,9 @@ var Calendar = GObject.registerClass({
let now = new Date();
if (sameYear(this._selectedDate, now))
this._monthLabel.text = this._selectedDate.toLocaleFormat(this._headerFormatWithoutYear);
this._monthLabel.text = formatDateWithCFormatString(this._selectedDate, this._headerFormatWithoutYear);
else
this._monthLabel.text = this._selectedDate.toLocaleFormat(this._headerFormat);
this._monthLabel.text = formatDateWithCFormatString(this._selectedDate, this._headerFormat);
if (!this._calendarBegin || !sameMonth(this._selectedDate, this._calendarBegin) || !sameDay(now, this._markedAsToday))
this._rebuildCalendar();
@ -818,7 +819,7 @@ class NotificationTimeLabel extends St.Label {
}
vfunc_map() {
this.text = Util.formatTimeSpan(this._datetime);
this.text = formatTimeSpan(this._datetime);
super.vfunc_map();
}
});

View File

@ -11,13 +11,13 @@ const Pango = imports.gi.Pango;
const Shell = imports.gi.Shell;
const St = imports.gi.St;
const Util = imports.misc.util;
const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu;
const Calendar = imports.ui.calendar;
const Weather = imports.misc.weather;
const System = imports.system;
const DateUtils = imports.misc.dateUtils;
const {formatDateWithCFormatString, formatTime} = imports.misc.dateUtils;
const {loadInterfaceXML} = imports.misc.fileUtils;
const NC_ = (context, str) => `${context}\u0004${str}`;
@ -90,7 +90,7 @@ class TodayButton extends St.Button {
}
setDate(date) {
this._dayLabel.set_text(date.toLocaleFormat('%A'));
this._dayLabel.set_text(formatDateWithCFormatString(date, '%A'));
/* Translators: This is the date format to use when the calendar popup is
* shown - it is shown just below the time in the top bar (e.g.,
@ -98,14 +98,14 @@ class TodayButton extends St.Button {
* "February 17 2015".
*/
const dateFormat = Shell.util_translate_time_string(N_('%B %-d %Y'));
this._dateLabel.set_text(date.toLocaleFormat(dateFormat));
this._dateLabel.set_text(formatDateWithCFormatString(date, dateFormat));
/* Translators: This is the accessible name of the date button shown
* below the time in the shell; it should combine the weekday and the
* date, e.g. "Tuesday February 17 2015".
*/
const dateAccessibleNameFormat = Shell.util_translate_time_string(N_('%A %B %e %Y'));
this.accessible_name = date.toLocaleFormat(dateAccessibleNameFormat);
this.accessible_name = formatDateWithCFormatString(date, dateAccessibleNameFormat);
}
});
@ -185,9 +185,9 @@ class EventsSection extends St.Button {
else if (this._startDate > now && this._startDate - now <= timeSpanDay)
this._title.text = _('Tomorrow');
else if (this._startDate.getFullYear() === now.getFullYear())
this._title.text = this._startDate.toLocaleFormat(sameYearFormat);
this._title.text = formatDateWithCFormatString(this._startDate, sameYearFormat);
else
this._title.text = this._startDate.toLocaleFormat(otherYearFormat);
this._title.text = formatDateWithCFormatString(this._startDate, otherYearFormat);
}
_isAtMidnight(eventTime) {
@ -204,8 +204,8 @@ class EventsSection extends St.Button {
const startsBeforeToday = eventStart < this._startDate;
const endsAfterToday = eventEnd > this._endDate;
const startTimeOnly = Util.formatTime(eventStart, {timeOnly: true});
const endTimeOnly = Util.formatTime(eventEnd, {timeOnly: true});
const startTimeOnly = formatTime(eventStart, {timeOnly: true});
const endTimeOnly = formatTime(eventEnd, {timeOnly: true});
const rtl = Clutter.get_default_text_direction() === Clutter.TextDirection.RTL;
@ -240,8 +240,8 @@ class EventsSection extends St.Button {
else
format = '%x';
const startDateOnly = eventStart.toLocaleFormat(format);
const endDateOnly = eventEnd.toLocaleFormat(format);
const startDateOnly = formatDateWithCFormatString(eventStart, format);
const endDateOnly = formatDateWithCFormatString(eventEnd, format);
if (startsAtMidnight && endsAtMidnight)
title = `${rtl ? endDateOnly : startDateOnly} ${EN_CHAR} ${rtl ? startDateOnly : endDateOnly}`;
@ -503,7 +503,7 @@ class WorldClocksSection extends St.Button {
for (let i = 0; i < this._locations.length; i++) {
let l = this._locations[i];
const now = GLib.DateTime.new_now(l.location.get_timezone());
l.timeLabel.text = Util.formatTime(now, {timeOnly: true});
l.timeLabel.text = formatTime(now, {timeOnly: true});
}
}
@ -630,7 +630,7 @@ class WeatherSection extends St.Button {
let col = 0;
infos.forEach(fc => {
const [valid_, timestamp] = fc.get_value_update();
let timeStr = Util.formatTime(new Date(timestamp * 1000), {
let timeStr = formatTime(new Date(timestamp * 1000), {
timeOnly: true,
ampm: false,
});
@ -970,10 +970,7 @@ class DateMenuButton extends PanelMenu.Button {
}
_updateTimeZone() {
// SpiderMonkey caches the time zone so we must explicitly clear it
// before we can update the calendar, see
// https://bugzilla.gnome.org/show_bug.cgi?id=678507
System.clearDateCaches();
DateUtils.clearCachedLocalTimeZone();
this._calendar.updateTimeZone();
}

View File

@ -43,7 +43,6 @@ const Polkit = imports.gi.Polkit;
const Shell = imports.gi.Shell;
const St = imports.gi.St;
const Gettext = imports.gettext;
const System = imports.system;
const SignalTracker = imports.misc.signalTracker;
Gio._promisify(Gio.DataInputStream.prototype, 'fill_async');
@ -56,8 +55,6 @@ Gio._promisify(Gio.DBusProxy.prototype, 'call_with_unix_fd_list');
Gio._promisify(Gio.File.prototype, 'query_info_async');
Gio._promisify(Polkit.Permission, 'new');
let _localTimeZone = null;
// We can't import shell JS modules yet, because they may have
// variable initializations, etc, that depend on init() already having
// been run.
@ -373,28 +370,6 @@ function init() {
}
};
// Override to clear our own timezone cache as well
const origClearDateCaches = System.clearDateCaches;
System.clearDateCaches = function () {
_localTimeZone = null;
origClearDateCaches();
};
// Work around https://bugzilla.mozilla.org/show_bug.cgi?id=508783
Date.prototype.toLocaleFormat = function (format) {
if (_localTimeZone === null)
_localTimeZone = GLib.TimeZone.new_local();
let dt = GLib.DateTime.new(_localTimeZone,
this.getFullYear(),
this.getMonth() + 1,
this.getDate(),
this.getHours(),
this.getMinutes(),
this.getSeconds());
return dt?.format(format) ?? '';
};
let slowdownEnv = GLib.getenv('GNOME_SHELL_SLOWDOWN_FACTOR');
if (slowdownEnv) {
let factor = parseFloat(slowdownEnv);

View File

@ -17,7 +17,7 @@ const Layout = imports.ui.layout;
const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray;
const SwipeTracker = imports.ui.swipeTracker;
const {formatDateWithCFormatString} = imports.misc.dateUtils;
const AuthPrompt = imports.gdm.authPrompt;
// The timeout before going back automatically to the lock screen (in seconds)
@ -367,7 +367,7 @@ class UnlockDialogClock extends St.BoxLayout {
/* Translators: This is a time format for a date in
long format */
let dateFormat = Shell.util_translate_time_string(N_('%A %B %-d'));
this._date.text = date.toLocaleFormat(dateFormat);
this._date.text = formatDateWithCFormatString(date, dateFormat);
}
_updateHint() {

View File

@ -12,6 +12,7 @@ js/gdm/loginDialog.js
js/gdm/util.js
js/misc/systemActions.js
js/misc/util.js
js/misc/dateUtils.js
js/portalHelper/main.js
js/ui/accessDialog.js
js/ui/appDisplay.js