i18n: Localize date and time displays across the app.

Implemented date localization using native Intl object.
Created special function get_localized_date_or_time_for_format
Made necessary string formatting changes in 'timerender.ts'.
Fixed tests and added some localization tests too.

Tested on my local development server, with some random languages.

Fixes #23987.
This commit is contained in:
Daniil Fadeev 2023-01-08 20:08:49 +03:00 committed by Tim Abbott
parent fe654b76b7
commit a0d15f3029
11 changed files with 321 additions and 102 deletions

View File

@ -39,6 +39,7 @@ import * as settings_bots from "./settings_bots";
import * as settings_config from "./settings_config";
import * as settings_users from "./settings_users";
import * as stream_popover from "./stream_popover";
import * as timerender from "./timerender";
import * as ui_report from "./ui_report";
import * as user_groups from "./user_groups";
import * as user_profile from "./user_profile";
@ -247,14 +248,15 @@ function render_user_info_popover(
let date_joined;
if (spectator_view) {
const dateFormat = new Intl.DateTimeFormat("default", {dateStyle: "long"});
date_joined = dateFormat.format(parseISO(user.date_joined));
date_joined = timerender.get_localized_date_or_time_for_format(
parseISO(user.date_joined),
"dayofyear_year",
);
}
// Filtering out only those profile fields that can be display in the popover and are not empty.
const dateFormat = new Intl.DateTimeFormat("default", {dateStyle: "long"});
const field_types = page_params.custom_profile_field_types;
const display_profile_fields = page_params.custom_profile_fields
.map((f) => user_profile.get_custom_profile_field_data(user, f, field_types, dateFormat))
.map((f) => user_profile.get_custom_profile_field_data(user, f, field_types))
.filter((f) => f.display_in_profile_summary && f.value !== undefined && f.value !== null);
const args = {

View File

@ -18,6 +18,7 @@ import * as settings_display from "./settings_display";
import * as settings_panel_menu from "./settings_panel_menu";
import * as settings_sections from "./settings_sections";
import * as settings_toggle from "./settings_toggle";
import * as timerender from "./timerender";
import {user_settings} from "./user_settings";
export let settings_label;
@ -65,8 +66,10 @@ function setup_settings_label() {
function get_parsed_date_of_joining() {
const user_date_joined = people.get_by_user_id(page_params.user_id, false).date_joined;
const dateFormat = new Intl.DateTimeFormat("default", {dateStyle: "long"});
return dateFormat.format(parseISO(user_date_joined));
return timerender.get_localized_date_or_time_for_format(
parseISO(user_date_joined),
"dayofyear_year",
);
}
export function build_page() {

View File

@ -24,6 +24,94 @@ export function clear_for_testing(): void {
next_timerender_id = 0;
}
type DateFormat = "weekday" | "dayofyear" | "weekday_dayofyear_year" | "dayofyear_year";
type DateWithTimeFormat = "dayofyear_time" | "dayofyear_year_time" | "weekday_dayofyear_year_time";
type TimeFormat = "time" | "time_sec";
type DateOrTimeFormat = DateFormat | TimeFormat | DateWithTimeFormat;
// Translates Zulip-specific format names, documented in the comments
// below, into the appropriate options to pass to the Intl library
// along with the user's locale to render date in that style of format.
//
// Note that because date/time formats vary with locale, the below
// examples are what a user with English as their language will see
// but users in other locales will see something different, especially
// for any formats that display the name for a month/weekday, but
// possibly in more subtle ways for languages with different
// punctuation schemes for date and times.
function get_format_options_for_type(type: DateOrTimeFormat): Intl.DateTimeFormatOptions {
const is_twenty_four_hour_time = user_settings.twenty_four_hour_time;
const time_format_options: Intl.DateTimeFormatOptions = is_twenty_four_hour_time
? {hourCycle: "h23", hour: "2-digit", minute: "2-digit"}
: {
hourCycle: "h12",
hour: "numeric",
minute: "2-digit",
};
const weekday_format_options: Intl.DateTimeFormatOptions = {weekday: "long"};
const full_format_options: Intl.DateTimeFormatOptions = {dateStyle: "full"};
const dayofyear_format_options: Intl.DateTimeFormatOptions = {day: "numeric", month: "short"};
const dayofyear_year_format_options: Intl.DateTimeFormatOptions = {
...dayofyear_format_options,
year: "numeric",
};
const long_format_options: Intl.DateTimeFormatOptions = {
...dayofyear_year_format_options,
weekday: "short",
};
switch (type) {
case "time": // 01:30 PM
return time_format_options;
case "time_sec": // 01:30:42 PM
return {...time_format_options, second: "2-digit"};
case "weekday": // Wednesday
return weekday_format_options;
case "dayofyear": // Jul 27
return dayofyear_format_options;
case "dayofyear_time": // Jul 27, 01:30 PM
return {...dayofyear_format_options, ...time_format_options};
case "dayofyear_year": // Jul 27, 2016
return dayofyear_year_format_options;
case "dayofyear_year_time": // Jul 27, 2016, 01:30 PM
return {...dayofyear_year_format_options, ...time_format_options};
case "weekday_dayofyear_year": // Wednesday, July 27, 2016
return full_format_options;
case "weekday_dayofyear_year_time": // Wed, Jul 27, 2016, 13:30
return {...long_format_options, ...time_format_options};
default:
throw new Error("Wrong format provided.");
}
}
function get_user_locale(): string {
const user_default_language = user_settings.default_language;
let locale = "";
try {
locale = Intl.DateTimeFormat.supportedLocalesOf(user_default_language)[0];
} catch {
locale = "default";
}
return locale;
}
// Common function for all date/time rendering in the project. Handles
// localization using the user's configured locale and the
// twenty_four_hour_time setting.
//
// See get_format_options_for_type for details on the supported formats.
export function get_localized_date_or_time_for_format(
date: Date | number,
format: DateOrTimeFormat,
): string {
const locale = get_user_locale();
return new Intl.DateTimeFormat(locale, get_format_options_for_type(format)).format(date);
}
// Exported for tests only.
export function get_tz_with_UTC_offset(time: number | Date): string {
const tz_offset = format(time, "xxx");
@ -66,10 +154,7 @@ export function render_now(time: Date, today = new Date()): TimeRender {
let time_str = "";
let needs_update = false;
// render formal time to be used for tippy tooltip
// "\xa0" is U+00A0 NO-BREAK SPACE.
// Can't use   as that represents the literal string " ".
const formal_time_str = format(time, "EEEE,\u00A0MMMM\u00A0d,\u00A0yyyy");
const formal_time_str = get_localized_date_or_time_for_format(time, "weekday_dayofyear_year");
// How many days old is 'time'? 0 = today, 1 = yesterday, 7 = a
// week ago, -1 = tomorrow, etc.
@ -87,12 +172,12 @@ export function render_now(time: Date, today = new Date()): TimeRender {
} else if (time.getFullYear() !== today.getFullYear()) {
// For long running servers, searching backlog can get ambiguous
// without a year stamp. Only show year if message is from an older year
time_str = format(time, "MMM\u00A0dd,\u00A0yyyy");
time_str = get_localized_date_or_time_for_format(time, "dayofyear_year");
needs_update = false;
} else {
// For now, if we get a message from tomorrow, we don't bother
// rewriting the timestamp when it gets to be tomorrow.
time_str = format(time, "MMM\u00A0dd");
time_str = get_localized_date_or_time_for_format(time, "dayofyear");
needs_update = false;
}
return {
@ -139,12 +224,22 @@ export function last_seen_status_from_date(
// Online more than 90 days ago, in the same year
return $t(
{defaultMessage: "{last_active_date}"},
{last_active_date: format(last_active_date, "MMM\u00A0dd")},
{
last_active_date: get_localized_date_or_time_for_format(
last_active_date,
"dayofyear",
),
},
);
}
return $t(
{defaultMessage: "{last_active_date}"},
{last_active_date: format(last_active_date, "MMM\u00A0dd,\u00A0yyyy")},
{
last_active_date: get_localized_date_or_time_for_format(
last_active_date,
"dayofyear_year",
),
},
);
}
@ -201,8 +296,7 @@ export function render_date(time: Date, today: Date): JQuery {
// Renders the timestamp returned by the <time:> Markdown syntax.
export function format_markdown_time(time: number | Date): string {
const hourformat = user_settings.twenty_four_hour_time ? "HH:mm" : "h:mm a";
return format(time, "E, MMM d yyyy, " + hourformat);
return get_localized_date_or_time_for_format(time, "weekday_dayofyear_year_time");
}
export function get_markdown_time_tooltip(reference: HTMLElement): DocumentFragment | string {
@ -270,10 +364,7 @@ export function get_timestamp_for_flatpickr(timestring: string): Date {
}
export function stringify_time(time: number | Date): string {
if (user_settings.twenty_four_hour_time) {
return format(time, "HH:mm");
}
return format(time, "h:mm a");
return get_localized_date_or_time_for_format(time, "time");
}
export function format_time_modern(time: number | Date, today = new Date()): String {
@ -282,18 +373,18 @@ export function format_time_modern(time: number | Date, today = new Date()): Str
if (time > today) {
/* For timestamps in the future, we always show the year*/
return format(time, "MMM\u00A0dd,\u00A0yyyy");
return get_localized_date_or_time_for_format(time, "dayofyear_year");
} else if (hours < 24) {
return stringify_time(time);
} else if (days_old === 1) {
return $t({defaultMessage: "Yesterday"});
} else if (days_old < 7) {
return format(time, "EEEE");
return get_localized_date_or_time_for_format(time, "weekday");
} else if (days_old <= 180) {
return format(time, "MMM\u00A0dd");
return get_localized_date_or_time_for_format(time, "dayofyear");
}
return format(time, "MMM\u00A0dd,\u00A0yyyy");
return get_localized_date_or_time_for_format(time, "dayofyear_year");
}
// this is for rendering absolute time based off the preferences for twenty-four
@ -301,29 +392,17 @@ export function format_time_modern(time: number | Date, today = new Date()): Str
export function absolute_time(timestamp: number, today = new Date()): string {
const date = new Date(timestamp);
const is_older_year = today.getFullYear() - date.getFullYear() > 0;
const H_24 = user_settings.twenty_four_hour_time;
return format(
return get_localized_date_or_time_for_format(
date,
is_older_year
? H_24
? "MMM d, yyyy HH:mm"
: "MMM d, yyyy hh:mm a"
: H_24
? "MMM d HH:mm"
: "MMM d hh:mm a",
is_older_year ? "dayofyear_year_time" : "dayofyear_time",
);
}
export function get_full_datetime(time: Date): string {
const time_options: Intl.DateTimeFormatOptions = {timeStyle: "medium"};
if (user_settings.twenty_four_hour_time) {
time_options.hourCycle = "h24";
}
const date_string = time.toLocaleDateString();
let time_string = time.toLocaleTimeString(undefined, time_options);
const locale = get_user_locale();
const date_string = time.toLocaleDateString(locale);
let time_string = get_localized_date_or_time_for_format(time, "time_sec");
const tz_offset_str = get_tz_with_UTC_offset(time);

View File

@ -22,6 +22,7 @@ import * as settings_profile_fields from "./settings_profile_fields";
import * as stream_data from "./stream_data";
import * as sub_store from "./sub_store";
import * as subscriber_api from "./subscriber_api";
import * as timerender from "./timerender";
import * as ui_report from "./ui_report";
import * as user_groups from "./user_groups";
import * as user_pill from "./user_pill";
@ -108,7 +109,7 @@ function render_user_group_list(groups, user) {
});
}
export function get_custom_profile_field_data(user, field, field_types, dateFormat) {
export function get_custom_profile_field_data(user, field, field_types) {
const field_value = people.get_custom_profile_data(user.user_id, field.id);
const field_type = field.type;
const profile_field = {};
@ -128,7 +129,10 @@ export function get_custom_profile_field_data(user, field, field_types, dateForm
switch (field_type) {
case field_types.DATE.id:
profile_field.value = dateFormat.format(parseISO(field_value.value));
profile_field.value = timerender.get_localized_date_or_time_for_format(
parseISO(field_value.value),
"dayofyear_year",
);
break;
case field_types.USER.id:
profile_field.id = field.id;
@ -179,10 +183,9 @@ function initialize_user_type_fields(user) {
export function show_user_profile(user, default_tab_key = "profile-tab") {
popovers.hide_all();
const dateFormat = new Intl.DateTimeFormat("default", {dateStyle: "long"});
const field_types = page_params.custom_profile_field_types;
const profile_data = page_params.custom_profile_fields
.map((f) => get_custom_profile_field_data(user, f, field_types, dateFormat))
.map((f) => get_custom_profile_field_data(user, f, field_types))
.filter((f) => f.name !== undefined);
const user_streams = stream_data.get_subscribed_streams_for_user(user.user_id);
const groups_of_user = user_groups.get_user_groups_of_user(user.user_id);
@ -194,7 +197,10 @@ export function show_user_profile(user, default_tab_key = "profile-tab") {
user_avatar: people.medium_avatar_url_for_person(user),
is_me: people.is_current_user(user.email),
is_bot: user.is_bot,
date_joined: dateFormat.format(parseISO(user.date_joined)),
date_joined: timerender.get_localized_date_or_time_for_format(
parseISO(user.date_joined),
"dayofyear_year",
),
user_circle_class: buddy_data.get_user_circle_class(user.user_id),
last_seen: buddy_data.user_last_seen_time_status(user.user_id),
user_time: people.get_user_time(user.user_id),

View File

@ -520,28 +520,28 @@ test("format_drafts", ({override_rewire, mock_template}) => {
dark_background: "",
topic: "topic",
raw_content: "Test stream message",
time_stamp: "7:55 AM",
time_stamp: "7:55AM",
},
{
draft_id: "id2",
is_stream: false,
recipients: "Aaron",
raw_content: "Test private message",
time_stamp: "Jan 30",
time_stamp: "Jan 30",
},
{
draft_id: "id5",
is_stream: false,
recipients: "Aaron",
raw_content: "Test private message 3",
time_stamp: "Jan 29",
time_stamp: "Jan 29",
},
{
draft_id: "id4",
is_stream: false,
recipients: "Aaron",
raw_content: "Test private message 2",
time_stamp: "Jan 26",
time_stamp: "Jan 26",
},
{
draft_id: "id3",
@ -551,7 +551,7 @@ test("format_drafts", ({override_rewire, mock_template}) => {
dark_background: "",
topic: "topic",
raw_content: "Test stream message 2",
time_stamp: "Jan 21",
time_stamp: "Jan 21",
},
];
@ -658,21 +658,21 @@ test("filter_drafts", ({override_rewire, mock_template}) => {
is_stream: false,
recipients: "Aaron",
raw_content: "Test private message",
time_stamp: "Jan 30",
time_stamp: "Jan 30",
},
{
draft_id: "id5",
is_stream: false,
recipients: "Aaron",
raw_content: "Test private message 3",
time_stamp: "Jan 29",
time_stamp: "Jan 29",
},
{
draft_id: "id4",
is_stream: false,
recipients: "Aaron",
raw_content: "Test private message 2",
time_stamp: "Jan 26",
time_stamp: "Jan 26",
},
];
@ -685,7 +685,7 @@ test("filter_drafts", ({override_rewire, mock_template}) => {
dark_background: "",
topic: "topic",
raw_content: "Test stream message",
time_stamp: "7:55 AM",
time_stamp: "7:55AM",
},
{
draft_id: "id3",
@ -695,7 +695,7 @@ test("filter_drafts", ({override_rewire, mock_template}) => {
dark_background: "",
topic: "topic",
raw_content: "Test stream message 2",
time_stamp: "Jan 21",
time_stamp: "Jan 21",
},
];

View File

@ -71,12 +71,12 @@ test("get_mutes", () => {
assert.deepEqual(all_muted_users, [
{
date_muted: 1577836800000,
date_muted_str: "Jan\u00A001,\u00A02020",
date_muted_str: "Jan 1, 2020",
id: 6,
},
{
date_muted: 1577836800000,
date_muted_str: "Jan\u00A001,\u00A02020",
date_muted_str: "Jan 1, 2020",
id: 4,
},
]);
@ -95,12 +95,12 @@ test("initialize", () => {
assert.deepEqual(muted_users.get_muted_users().sort(), [
{
date_muted: 1577836800000,
date_muted_str: "Jan\u00A001,\u00A02020",
date_muted_str: "Jan 1, 2020",
id: 3,
},
{
date_muted: 1577836800000,
date_muted_str: "Jan\u00A001,\u00A02020",
date_muted_str: "Jan 1, 2020",
id: 2,
},
]);

View File

@ -251,7 +251,7 @@ run_test("timestamp without time", () => {
run_test("timestamp", ({mock_template}) => {
mock_template("markdown_timestamp.hbs", true, (data, html) => {
assert.deepEqual(data, {text: "Thu, Jan 1 1970, 12:00 AM"});
assert.deepEqual(data, {text: "Thu, Jan 1, 1970, 12:00AM"});
return html;
});
@ -271,14 +271,14 @@ run_test("timestamp", ({mock_template}) => {
rm.update_elements($content);
// Final asserts
assert.equal($timestamp.html(), '<i class="fa fa-clock-o"></i>\nThu, Jan 1 1970, 12:00 AM\n');
assert.equal($timestamp.html(), '<i class="fa fa-clock-o"></i>\nThu, Jan 1, 1970, 12:00AM\n');
assert.equal($timestamp_invalid.text(), "never-been-set");
});
run_test("timestamp-twenty-four-hour-time", ({mock_template, override}) => {
mock_template("markdown_timestamp.hbs", true, (data, html) => {
// sanity check incoming data
assert.ok(data.text.startsWith("Wed, Jul 15 2020, "));
assert.ok(data.text.startsWith("Wed, Jul 15, 2020, "));
return html;
});
@ -290,11 +290,11 @@ run_test("timestamp-twenty-four-hour-time", ({mock_template, override}) => {
// We will temporarily change the 24h setting for this test.
override(user_settings, "twenty_four_hour_time", true);
rm.update_elements($content);
assert.equal($timestamp.html(), '<i class="fa fa-clock-o"></i>\nWed, Jul 15 2020, 20:40\n');
assert.equal($timestamp.html(), '<i class="fa fa-clock-o"></i>\nWed, Jul 15, 2020, 20:40\n');
override(user_settings, "twenty_four_hour_time", false);
rm.update_elements($content);
assert.equal($timestamp.html(), '<i class="fa fa-clock-o"></i>\nWed, Jul 15 2020, 8:40 PM\n');
assert.equal($timestamp.html(), '<i class="fa fa-clock-o"></i>\nWed, Jul 15, 2020, 8:40PM\n');
});
run_test("timestamp-error", () => {

View File

@ -28,7 +28,7 @@ run_test("settings", ({override}) => {
assert.deepEqual(list, [
{
date_muted: 1577836800000,
date_muted_str: "Jan\u00A001,\u00A02020",
date_muted_str: "Jan 1, 2020",
stream: frontend.name,
stream_id: frontend.stream_id,
topic: "js",

View File

@ -22,7 +22,7 @@ run_test("settings", ({override}) => {
override(list_widget, "create", ($container, list) => {
assert.deepEqual(list, [
{
date_muted_str: "Jan\u00A001,\u00A02020",
date_muted_str: "Jan 1, 2020",
user_id: 5,
user_name: "Feivel Fiverson",
},

View File

@ -35,6 +35,135 @@ const date_2021 = get_date("2021-01-27T01:53:08.000Z", "Wednesday");
const date_2025 = get_date("2025-03-03T12:10:00.000Z", "Monday");
run_test("get_localized_date_or_time_for_format returns default date with incorrect locale", () => {
const date = date_2019;
const expectedDate = "Friday, April 12, 2019";
user_settings.default_language = "invalidLanguageCode";
const actualDate = timerender.get_localized_date_or_time_for_format(
date,
"weekday_dayofyear_year",
);
assert.equal(actualDate, expectedDate);
});
run_test("get_localized_date_or_time_for_format returns correct format", () => {
const date = date_2021;
const formats = [
{
format: "time",
expected: {
date: "1:53AM",
},
},
{
format: "time_sec",
expected: {
date: "1:53:08AM",
},
},
{
format: "weekday",
expected: {
date: "Wednesday",
},
},
{
format: "dayofyear",
expected: {
date: "Jan 27",
},
},
{
format: "dayofyear_time",
expected: {
date: "Jan 27, 1:53AM",
},
},
{
format: "dayofyear_year",
expected: {
date: "Jan 27, 2021",
},
},
{
format: "dayofyear_year_time",
expected: {
date: "Jan 27, 2021, 1:53AM",
},
},
{
format: "weekday_dayofyear_year",
expected: {
date: "Wednesday, January 27, 2021",
},
},
{
format: "weekday_dayofyear_year_time",
expected: {
date: "Wed, Jan 27, 2021, 1:53AM",
},
},
];
for (const format of formats) {
const actualDate = timerender.get_localized_date_or_time_for_format(date, format.format);
assert.equal(actualDate, format.expected.date);
}
});
run_test("get_localized_date_or_time_for_format returns correct localized date", () => {
const date = add(date_2019, {years: -1});
const languages = [
{
language: "en",
expected: {
date: "Thursday, April 12, 2018",
},
},
{
language: "ru",
expected: {
date: "четверг, 12 апреля 2018г.",
},
},
{
language: "fr",
expected: {
date: "jeudi 12 avril 2018",
},
},
{
language: "de",
expected: {
date: "Donnerstag, 12. April 2018",
},
},
{
language: "su",
expected: {
date: "Kemis, 12 April 2018",
},
},
{
language: "it",
expected: {
date: "giovedì 12 aprile 2018",
},
},
];
for (const language of languages) {
user_settings.default_language = language.language;
const actualDate = timerender.get_localized_date_or_time_for_format(
date,
"weekday_dayofyear_year",
);
assert.equal(actualDate, language.expected.date);
}
});
run_test("get_tz_with_UTC_offset", () => {
let time = date_2019;
@ -60,7 +189,7 @@ run_test("render_now_returns_today", () => {
const expected = {
time_str: $t({defaultMessage: "Today"}),
formal_time_str: "Friday, April 12, 2019",
formal_time_str: "Friday, April 12, 2019",
needs_update: true,
};
const actual = timerender.render_now(today, today);
@ -75,7 +204,7 @@ run_test("render_now_returns_yesterday", () => {
const yesterday = add(today, {days: -1});
const expected = {
time_str: $t({defaultMessage: "Yesterday"}),
formal_time_str: "Thursday, April 11, 2019",
formal_time_str: "Thursday, April 11, 2019",
needs_update: true,
};
const actual = timerender.render_now(yesterday, today);
@ -89,8 +218,8 @@ run_test("render_now_returns_year", () => {
const year_ago = add(today, {years: -1});
const expected = {
time_str: "Apr 12, 2018",
formal_time_str: "Thursday, April 12, 2018",
time_str: "Apr 12, 2018",
formal_time_str: "Thursday, April 12, 2018",
needs_update: false,
};
const actual = timerender.render_now(year_ago, today);
@ -104,8 +233,8 @@ run_test("render_now_returns_month_and_day", () => {
const three_months_ago = add(today, {months: -3});
const expected = {
time_str: "Jan 12",
formal_time_str: "Saturday, January 12, 2019",
time_str: "Jan 12",
formal_time_str: "Saturday, January 12, 2019",
needs_update: false,
};
const actual = timerender.render_now(three_months_ago, today);
@ -128,9 +257,9 @@ run_test("format_time_modern", () => {
const more_than_6_months_ago = add(today, {months: -9});
const previous_year_but_less_than_6_months = add(today, {months: -1});
assert.equal(timerender.format_time_modern(few_minutes_in_future, today), "Jan 27, 2021");
assert.equal(timerender.format_time_modern(weeks_in_future, today), "Feb 16, 2021");
assert.equal(timerender.format_time_modern(less_than_24_hours_ago, today), "2:53 AM");
assert.equal(timerender.format_time_modern(few_minutes_in_future, today), "Jan 27, 2021");
assert.equal(timerender.format_time_modern(weeks_in_future, today), "Feb 16, 2021");
assert.equal(timerender.format_time_modern(less_than_24_hours_ago, today), "2:53AM");
assert.equal(
timerender.format_time_modern(twenty_four_hours_ago, today),
"translated: Yesterday",
@ -140,13 +269,13 @@ run_test("format_time_modern", () => {
"translated: Yesterday",
);
assert.equal(timerender.format_time_modern(less_than_a_week_ago, today), "Thursday");
assert.equal(timerender.format_time_modern(one_week_ago, today), "Jan 20");
assert.equal(timerender.format_time_modern(one_week_ago, today), "Jan 20");
assert.equal(
timerender.format_time_modern(previous_year_but_less_than_6_months, today),
"Dec 27",
"Dec 27",
);
assert.equal(timerender.format_time_modern(less_than_6_months_ago, today), "Oct 27");
assert.equal(timerender.format_time_modern(more_than_6_months_ago, today), "Apr 27, 2020");
assert.equal(timerender.format_time_modern(less_than_6_months_ago, today), "Oct 27");
assert.equal(timerender.format_time_modern(more_than_6_months_ago, today), "Apr 27, 2020");
});
run_test("format_time_modern_different_timezones", () => {
@ -182,7 +311,7 @@ run_test("format_time_modern_different_timezones", () => {
process.env.TZ = "America/Juneau";
expected = "translated: 5/11/2017 at 11:12:53PM AKDT (UTC-08:00)";
assert.equal(timerender.get_full_datetime(yesterday), expected);
assert.equal(timerender.format_time_modern(yesterday, today), "May 11");
assert.equal(timerender.format_time_modern(yesterday, today), "May 11");
process.env.TZ = utc_tz;
});
@ -191,8 +320,8 @@ run_test("render_now_returns_year_with_year_boundary", () => {
const six_months_ago = add(today, {months: -6});
const expected = {
time_str: "Oct 12, 2018",
formal_time_str: "Friday, October 12, 2018",
time_str: "Oct 12, 2018",
formal_time_str: "Friday, October 12, 2018",
needs_update: false,
};
const actual = timerender.render_now(six_months_ago, today);
@ -224,7 +353,7 @@ run_test("render_date_renders_time_html", () => {
const $actual = timerender.render_date(message_time, today);
assert.equal($actual.html(), expected_html);
assert.equal(attrs["data-tippy-content"], "Friday, April 12, 2019");
assert.equal(attrs["data-tippy-content"], "Friday, April 12, 2019");
assert.equal(attrs.class, "timerender0");
});
@ -257,13 +386,13 @@ run_test("absolute_time_12_hour", () => {
let timestamp = date_2019.getTime();
let today = date_2019;
let expected = "Apr 12 05:52 PM";
let expected = "Apr 12, 5:52PM";
let actual = timerender.absolute_time(timestamp, today);
assert.equal(actual, expected);
// timestamp with hour > 12, different year
let next_year = add(today, {years: 1});
expected = "Apr 12, 2019 05:52 PM";
expected = "Apr 12, 2019, 5:52PM";
actual = timerender.absolute_time(timestamp, next_year);
assert.equal(actual, expected);
@ -271,13 +400,13 @@ run_test("absolute_time_12_hour", () => {
timestamp = date_2017.getTime();
today = date_2017;
expected = "May 18 07:12 AM";
expected = "May 18, 7:12AM";
actual = timerender.absolute_time(timestamp, today);
assert.equal(actual, expected);
// timestamp with hour < 12, different year
next_year = add(today, {years: 1});
expected = "May 18, 2017 07:12 AM";
expected = "May 18, 2017, 7:12AM";
actual = timerender.absolute_time(timestamp, next_year);
assert.equal(actual, expected);
});
@ -287,26 +416,26 @@ run_test("absolute_time_24_hour", () => {
// date with hour > 12, same year
let today = date_2019;
let expected = "Apr 12 17:52";
let expected = "Apr 12, 17:52";
let actual = timerender.absolute_time(date_2019.getTime(), today);
assert.equal(actual, expected);
// date with hour > 12, different year
let next_year = add(today, {years: 1});
expected = "Apr 12, 2019 17:52";
expected = "Apr 12, 2019, 17:52";
actual = timerender.absolute_time(date_2019.getTime(), next_year);
assert.equal(actual, expected);
// timestamp with hour < 12, same year
today = date_2017;
expected = "May 18 07:12";
expected = "May 18, 07:12";
actual = timerender.absolute_time(date_2017.getTime(), today);
assert.equal(actual, expected);
// timestamp with hour < 12, different year
next_year = add(today, {years: 1});
expected = "May 18, 2017 07:12";
expected = "May 18, 2017, 07:12";
actual = timerender.absolute_time(date_2017.getTime(), next_year);
assert.equal(actual, expected);
});
@ -364,16 +493,16 @@ run_test("last_seen_status_from_date", () => {
assert_same({days: -61}, $t({defaultMessage: "61 days ago"}));
assert_same({days: -300}, $t({defaultMessage: "May 06,\u00A02015"}));
assert_same({days: -300}, $t({defaultMessage: "May 6, 2015"}));
assert_same({days: -366}, $t({defaultMessage: "Mar 01,\u00A02015"}));
assert_same({days: -366}, $t({defaultMessage: "Mar 1, 2015"}));
assert_same({years: -3}, $t({defaultMessage: "Mar 01,\u00A02013"}));
assert_same({years: -3}, $t({defaultMessage: "Mar 1, 2013"}));
// Set base_date to May 1 2016 12.30 AM (months are zero based)
base_date = new Date(2016, 4, 1, 0, 30);
assert_same({days: -91}, $t({defaultMessage: "Jan\u00A031"}));
assert_same({days: -91}, $t({defaultMessage: "Jan 31"}));
// Set base_date to May 1 2016 10.30 PM (months are zero based)
base_date = new Date(2016, 4, 2, 23, 30);
@ -397,12 +526,12 @@ run_test("set_full_datetime", () => {
user_settings.twenty_four_hour_time = false;
time_str = timerender.stringify_time(time);
expected = "5:52 PM";
expected = "5:52PM";
assert.equal(time_str, expected);
time = add(time, {hours: -7}); // time between 1 to 12 o'clock time.
user_settings.twenty_four_hour_time = false;
time_str = timerender.stringify_time(time);
expected = "10:52 AM";
expected = "10:52AM";
assert.equal(time_str, expected);
});

View File

@ -86,14 +86,14 @@ test("get_mutes", () => {
assert.deepEqual(all_muted_topics, [
{
date_muted: 1577836700000,
date_muted_str: "Dec\u00A031,\u00A02019",
date_muted_str: "Dec 31, 2019",
stream: devel.name,
stream_id: devel.stream_id,
topic: "java",
},
{
date_muted: 1577836800000,
date_muted_str: "Jan\u00A001,\u00A02020",
date_muted_str: "Jan 1, 2020",
stream: office.name,
stream_id: office.stream_id,
topic: "gossip",
@ -134,14 +134,14 @@ test("set_user_topics", () => {
assert.deepEqual(user_topics.get_muted_topics().sort(), [
{
date_muted: 1577836800000,
date_muted_str: "Jan\u00A001,\u00A02020",
date_muted_str: "Jan 1, 2020",
stream: social.name,
stream_id: social.stream_id,
topic: "breakfast",
},
{
date_muted: 1577836800000,
date_muted_str: "Jan\u00A001,\u00A02020",
date_muted_str: "Jan 1, 2020",
stream: design.name,
stream_id: design.stream_id,
topic: "typography",