mirror of
https://github.com/zulip/zulip.git
synced 2025-11-02 13:03:29 +00:00
timerender: Use an explicit time zone for all rendering.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
committed by
Tim Abbott
parent
33e335dbd1
commit
06c2b89525
@@ -1,4 +1,3 @@
|
|||||||
import {isSameDay} from "date-fns";
|
|
||||||
import $ from "jquery";
|
import $ from "jquery";
|
||||||
|
|
||||||
import render_message_edit_history from "../templates/message_edit_history.hbs";
|
import render_message_edit_history from "../templates/message_edit_history.hbs";
|
||||||
@@ -14,6 +13,7 @@ import * as rendered_markdown from "./rendered_markdown";
|
|||||||
import * as rows from "./rows";
|
import * as rows from "./rows";
|
||||||
import * as spectators from "./spectators";
|
import * as spectators from "./spectators";
|
||||||
import * as sub_store from "./sub_store";
|
import * as sub_store from "./sub_store";
|
||||||
|
import {is_same_day} from "./time_zone_util";
|
||||||
import * as timerender from "./timerender";
|
import * as timerender from "./timerender";
|
||||||
import * as ui_report from "./ui_report";
|
import * as ui_report from "./ui_report";
|
||||||
import {user_settings} from "./user_settings";
|
import {user_settings} from "./user_settings";
|
||||||
@@ -38,7 +38,9 @@ export function fetch_and_render_message_history(message) {
|
|||||||
const item = {
|
const item = {
|
||||||
timestamp: timerender.stringify_time(time),
|
timestamp: timerender.stringify_time(time),
|
||||||
display_date: date_time_format.format(time),
|
display_date: date_time_format.format(time),
|
||||||
show_date_row: prev_time === null || !isSameDay(time, prev_time),
|
show_date_row:
|
||||||
|
prev_time === null ||
|
||||||
|
!is_same_day(time, prev_time, timerender.display_time_zone),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!msg.user_id) {
|
if (!msg.user_id) {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import {isSameDay} from "date-fns";
|
|
||||||
import $ from "jquery";
|
import $ from "jquery";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
|
||||||
@@ -34,6 +33,7 @@ import * as stream_color from "./stream_color";
|
|||||||
import * as stream_data from "./stream_data";
|
import * as stream_data from "./stream_data";
|
||||||
import * as sub_store from "./sub_store";
|
import * as sub_store from "./sub_store";
|
||||||
import * as submessage from "./submessage";
|
import * as submessage from "./submessage";
|
||||||
|
import {is_same_day} from "./time_zone_util";
|
||||||
import * as timerender from "./timerender";
|
import * as timerender from "./timerender";
|
||||||
import * as user_topics from "./user_topics";
|
import * as user_topics from "./user_topics";
|
||||||
import * as util from "./util";
|
import * as util from "./util";
|
||||||
@@ -42,7 +42,11 @@ function same_day(earlier_msg, later_msg) {
|
|||||||
if (earlier_msg === undefined || later_msg === undefined) {
|
if (earlier_msg === undefined || later_msg === undefined) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return isSameDay(earlier_msg.msg.timestamp * 1000, later_msg.msg.timestamp * 1000);
|
return is_same_day(
|
||||||
|
earlier_msg.msg.timestamp * 1000,
|
||||||
|
later_msg.msg.timestamp * 1000,
|
||||||
|
timerender.display_time_zone,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function same_sender(a, b) {
|
function same_sender(a, b) {
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import {
|
import {
|
||||||
differenceInCalendarDays,
|
|
||||||
differenceInHours,
|
differenceInHours,
|
||||||
differenceInMinutes,
|
differenceInMinutes,
|
||||||
formatISO,
|
formatISO,
|
||||||
isEqual,
|
isEqual,
|
||||||
isValid,
|
isValid,
|
||||||
parseISO,
|
parseISO,
|
||||||
startOfToday,
|
|
||||||
} from "date-fns";
|
} from "date-fns";
|
||||||
import $ from "jquery";
|
import $ from "jquery";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
@@ -14,15 +12,23 @@ import _ from "lodash";
|
|||||||
import render_markdown_time_tooltip from "../templates/markdown_time_tooltip.hbs";
|
import render_markdown_time_tooltip from "../templates/markdown_time_tooltip.hbs";
|
||||||
|
|
||||||
import {$t} from "./i18n";
|
import {$t} from "./i18n";
|
||||||
|
import {difference_in_calendar_days, start_of_day} from "./time_zone_util";
|
||||||
import {parse_html} from "./ui_util";
|
import {parse_html} from "./ui_util";
|
||||||
import {user_settings} from "./user_settings";
|
import {user_settings} from "./user_settings";
|
||||||
|
|
||||||
let next_timerender_id = 0;
|
let next_timerender_id = 0;
|
||||||
|
|
||||||
|
export let display_time_zone = new Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||||
|
|
||||||
export function clear_for_testing(): void {
|
export function clear_for_testing(): void {
|
||||||
next_timerender_id = 0;
|
next_timerender_id = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Exported for testing only; we do not support live-updating the time zone.
|
||||||
|
export function set_display_time_zone(time_zone: string): void {
|
||||||
|
display_time_zone = time_zone;
|
||||||
|
}
|
||||||
|
|
||||||
type DateFormat = "weekday" | "dayofyear" | "weekday_dayofyear_year" | "dayofyear_year";
|
type DateFormat = "weekday" | "dayofyear" | "weekday_dayofyear_year" | "dayofyear_year";
|
||||||
type DateWithTimeFormat =
|
type DateWithTimeFormat =
|
||||||
| "dayofyear_time"
|
| "dayofyear_time"
|
||||||
@@ -107,15 +113,18 @@ export function get_localized_date_or_time_for_format(
|
|||||||
date: Date | number,
|
date: Date | number,
|
||||||
format: DateOrTimeFormat,
|
format: DateOrTimeFormat,
|
||||||
): string {
|
): string {
|
||||||
return new Intl.DateTimeFormat(
|
return new Intl.DateTimeFormat(user_settings.default_language, {
|
||||||
user_settings.default_language,
|
timeZone: display_time_zone,
|
||||||
get_format_options_for_type(format),
|
...get_format_options_for_type(format),
|
||||||
).format(date);
|
}).format(date);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exported for tests only.
|
// Exported for tests only.
|
||||||
export function get_tz_with_UTC_offset(time: number | Date): string {
|
export function get_tz_with_UTC_offset(time: number | Date): string {
|
||||||
let timezone = new Intl.DateTimeFormat(user_settings.default_language, {timeZoneName: "short"})
|
let timezone = new Intl.DateTimeFormat(user_settings.default_language, {
|
||||||
|
timeZone: display_time_zone,
|
||||||
|
timeZoneName: "short",
|
||||||
|
})
|
||||||
.formatToParts(time)
|
.formatToParts(time)
|
||||||
.find(({type}) => type === "timeZoneName")?.value;
|
.find(({type}) => type === "timeZoneName")?.value;
|
||||||
|
|
||||||
@@ -129,6 +138,7 @@ export function get_tz_with_UTC_offset(time: number | Date): string {
|
|||||||
timezone = /GMT[+-][\d:]*/.test(timezone ?? "") ? "" : timezone;
|
timezone = /GMT[+-][\d:]*/.test(timezone ?? "") ? "" : timezone;
|
||||||
|
|
||||||
const tz_offset = new Intl.DateTimeFormat(user_settings.default_language, {
|
const tz_offset = new Intl.DateTimeFormat(user_settings.default_language, {
|
||||||
|
timeZone: display_time_zone,
|
||||||
timeZoneName: "longOffset",
|
timeZoneName: "longOffset",
|
||||||
})
|
})
|
||||||
.formatToParts(time)
|
.formatToParts(time)
|
||||||
@@ -166,7 +176,7 @@ export function render_now(time: Date, today = new Date()): TimeRender {
|
|||||||
// Presumably the result of diffDays will be an integer in this
|
// Presumably the result of diffDays will be an integer in this
|
||||||
// case, but round it to be sure before comparing to integer
|
// case, but round it to be sure before comparing to integer
|
||||||
// constants.
|
// constants.
|
||||||
const days_old = differenceInCalendarDays(today, time);
|
const days_old = difference_in_calendar_days(today, time, display_time_zone);
|
||||||
|
|
||||||
if (days_old === 0) {
|
if (days_old === 0) {
|
||||||
time_str = $t({defaultMessage: "Today"});
|
time_str = $t({defaultMessage: "Today"});
|
||||||
@@ -210,7 +220,7 @@ export function relative_time_string_from_date({
|
|||||||
return $t({defaultMessage: "{minutes} minutes ago"}, {minutes});
|
return $t({defaultMessage: "{minutes} minutes ago"}, {minutes});
|
||||||
}
|
}
|
||||||
|
|
||||||
const days_old = differenceInCalendarDays(current_date, date);
|
const days_old = difference_in_calendar_days(current_date, date, display_time_zone);
|
||||||
const hours = Math.floor(minutes / 60);
|
const hours = Math.floor(minutes / 60);
|
||||||
|
|
||||||
if (hours < 24) {
|
if (hours < 24) {
|
||||||
@@ -252,7 +262,7 @@ export function last_seen_status_from_date(
|
|||||||
return $t({defaultMessage: "Active {minutes} minutes ago"}, {minutes});
|
return $t({defaultMessage: "Active {minutes} minutes ago"}, {minutes});
|
||||||
}
|
}
|
||||||
|
|
||||||
const days_old = differenceInCalendarDays(current_date, last_active_date);
|
const days_old = difference_in_calendar_days(current_date, last_active_date, display_time_zone);
|
||||||
const hours = Math.floor(minutes / 60);
|
const hours = Math.floor(minutes / 60);
|
||||||
|
|
||||||
if (hours < 24) {
|
if (hours < 24) {
|
||||||
@@ -310,7 +320,7 @@ let update_list: UpdateEntry[] = [];
|
|||||||
let last_update: Date;
|
let last_update: Date;
|
||||||
|
|
||||||
export function initialize(): void {
|
export function initialize(): void {
|
||||||
last_update = startOfToday();
|
last_update = start_of_day(new Date(), display_time_zone);
|
||||||
}
|
}
|
||||||
|
|
||||||
function maybe_add_update_list_entry(entry: UpdateEntry): void {
|
function maybe_add_update_list_entry(entry: UpdateEntry): void {
|
||||||
@@ -363,7 +373,7 @@ export function get_markdown_time_tooltip(reference: HTMLElement): DocumentFragm
|
|||||||
// This isn't expected to be called externally except manually for
|
// This isn't expected to be called externally except manually for
|
||||||
// testing purposes.
|
// testing purposes.
|
||||||
export function update_timestamps(): void {
|
export function update_timestamps(): void {
|
||||||
const today = startOfToday();
|
const today = start_of_day(new Date(), display_time_zone);
|
||||||
if (!isEqual(today, last_update)) {
|
if (!isEqual(today, last_update)) {
|
||||||
const to_process = update_list;
|
const to_process = update_list;
|
||||||
update_list = [];
|
update_list = [];
|
||||||
@@ -422,7 +432,7 @@ export function stringify_time(time: number | Date): string {
|
|||||||
|
|
||||||
export function format_time_modern(time: number | Date, today = new Date()): string {
|
export function format_time_modern(time: number | Date, today = new Date()): string {
|
||||||
const hours = differenceInHours(today, time);
|
const hours = differenceInHours(today, time);
|
||||||
const days_old = differenceInCalendarDays(today, time);
|
const days_old = difference_in_calendar_days(today, time, display_time_zone);
|
||||||
|
|
||||||
if (time > today) {
|
if (time > today) {
|
||||||
/* For timestamps in the future, we always show the year*/
|
/* For timestamps in the future, we always show the year*/
|
||||||
@@ -466,7 +476,9 @@ export function get_full_datetime_clarification(
|
|||||||
time: Date,
|
time: Date,
|
||||||
time_format: TimeFormat = "time_sec",
|
time_format: TimeFormat = "time_sec",
|
||||||
): string {
|
): string {
|
||||||
const date_string = time.toLocaleDateString(user_settings.default_language);
|
const date_string = time.toLocaleDateString(user_settings.default_language, {
|
||||||
|
timeZone: display_time_zone,
|
||||||
|
});
|
||||||
let time_string = get_localized_date_or_time_for_format(time, time_format);
|
let time_string = get_localized_date_or_time_for_format(time, time_format);
|
||||||
|
|
||||||
const tz_offset_str = get_tz_with_UTC_offset(time);
|
const tz_offset_str = get_tz_with_UTC_offset(time);
|
||||||
@@ -501,7 +513,7 @@ export function get_time_limit_setting_in_appropriate_unit(
|
|||||||
export function should_display_profile_incomplete_alert(timestamp: number): boolean {
|
export function should_display_profile_incomplete_alert(timestamp: number): boolean {
|
||||||
const today = new Date(Date.now());
|
const today = new Date(Date.now());
|
||||||
const time = new Date(timestamp);
|
const time = new Date(timestamp);
|
||||||
const days_old = differenceInCalendarDays(today, time);
|
const days_old = difference_in_calendar_days(today, time, display_time_zone);
|
||||||
|
|
||||||
if (days_old >= 15) {
|
if (days_old >= 15) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -419,6 +419,7 @@ run_test("scheduled_messages", ({override}) => {
|
|||||||
|
|
||||||
run_test("realm settings", ({override}) => {
|
run_test("realm settings", ({override}) => {
|
||||||
page_params.is_admin = true;
|
page_params.is_admin = true;
|
||||||
|
page_params.realm_date_created = new Date("2023-01-01Z");
|
||||||
|
|
||||||
override(settings_org, "sync_realm_settings", noop);
|
override(settings_org, "sync_realm_settings", noop);
|
||||||
override(settings_bots, "update_bot_permissions_ui", noop);
|
override(settings_bots, "update_bot_permissions_ui", noop);
|
||||||
|
|||||||
@@ -174,20 +174,19 @@ run_test("get_tz_with_UTC_offset", () => {
|
|||||||
let time = date_2019;
|
let time = date_2019;
|
||||||
|
|
||||||
assert.equal(timerender.get_tz_with_UTC_offset(time), "UTC");
|
assert.equal(timerender.get_tz_with_UTC_offset(time), "UTC");
|
||||||
const previous_env_tz = process.env.TZ;
|
|
||||||
|
|
||||||
// Test the GMT[+-]x:y logic.
|
// Test the GMT[+-]x:y logic.
|
||||||
process.env.TZ = "Asia/Kolkata";
|
timerender.set_display_time_zone("Asia/Kolkata");
|
||||||
assert.equal(timerender.get_tz_with_UTC_offset(time), "(UTC+05:30)");
|
assert.equal(timerender.get_tz_with_UTC_offset(time), "(UTC+05:30)");
|
||||||
|
|
||||||
process.env.TZ = "America/Los_Angeles";
|
timerender.set_display_time_zone("America/Los_Angeles");
|
||||||
assert.equal(timerender.get_tz_with_UTC_offset(time), "PDT (UTC-07:00)");
|
assert.equal(timerender.get_tz_with_UTC_offset(time), "PDT (UTC-07:00)");
|
||||||
|
|
||||||
time = date_2025;
|
time = date_2025;
|
||||||
|
|
||||||
assert.equal(timerender.get_tz_with_UTC_offset(time), "PST (UTC-08:00)");
|
assert.equal(timerender.get_tz_with_UTC_offset(time), "PST (UTC-08:00)");
|
||||||
|
|
||||||
process.env.TZ = previous_env_tz;
|
timerender.set_display_time_zone("UTC");
|
||||||
});
|
});
|
||||||
|
|
||||||
run_test("render_now_returns_today", () => {
|
run_test("render_now_returns_today", () => {
|
||||||
@@ -285,40 +284,38 @@ run_test("format_time_modern", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
run_test("format_time_modern_different_timezones", () => {
|
run_test("format_time_modern_different_timezones", () => {
|
||||||
const utc_tz = process.env.TZ;
|
|
||||||
|
|
||||||
// Day is yesterday in UTC+0 but is 2 days ago in local timezone hence DOW is returned.
|
// Day is yesterday in UTC+0 but is 2 days ago in local timezone hence DOW is returned.
|
||||||
let today = date_2017_PM;
|
let today = date_2017_PM;
|
||||||
let yesterday = add(date_2017, {days: -1});
|
let yesterday = add(date_2017, {days: -1});
|
||||||
assert.equal(timerender.format_time_modern(yesterday, today), "translated: Yesterday");
|
assert.equal(timerender.format_time_modern(yesterday, today), "translated: Yesterday");
|
||||||
|
|
||||||
process.env.TZ = "America/Juneau";
|
timerender.set_display_time_zone("America/Juneau");
|
||||||
let expected = "translated: 5/16/2017 at 11:12:53 PM AKDT (UTC-08:00)";
|
let expected = "translated: 5/16/2017 at 11:12:53 PM AKDT (UTC-08:00)";
|
||||||
assert.equal(timerender.get_full_datetime_clarification(yesterday), expected);
|
assert.equal(timerender.get_full_datetime_clarification(yesterday), expected);
|
||||||
assert.equal(timerender.format_time_modern(yesterday, today), "Tuesday");
|
assert.equal(timerender.format_time_modern(yesterday, today), "Tuesday");
|
||||||
process.env.TZ = utc_tz;
|
timerender.set_display_time_zone("UTC");
|
||||||
|
|
||||||
// Day is 2 days ago in UTC+0 but is yesterday in local timezone.
|
// Day is 2 days ago in UTC+0 but is yesterday in local timezone.
|
||||||
today = date_2017;
|
today = date_2017;
|
||||||
yesterday = add(date_2017_PM, {days: -2});
|
yesterday = add(date_2017_PM, {days: -2});
|
||||||
assert.equal(timerender.format_time_modern(yesterday, today), "Tuesday");
|
assert.equal(timerender.format_time_modern(yesterday, today), "Tuesday");
|
||||||
|
|
||||||
process.env.TZ = "Asia/Brunei";
|
timerender.set_display_time_zone("Asia/Brunei");
|
||||||
expected = "translated: 5/17/2017 at 5:12:53 AM (UTC+08:00)";
|
expected = "translated: 5/17/2017 at 5:12:53 AM (UTC+08:00)";
|
||||||
assert.equal(timerender.get_full_datetime_clarification(yesterday), expected);
|
assert.equal(timerender.get_full_datetime_clarification(yesterday), expected);
|
||||||
assert.equal(timerender.format_time_modern(yesterday, today), "translated: Yesterday");
|
assert.equal(timerender.format_time_modern(yesterday, today), "translated: Yesterday");
|
||||||
process.env.TZ = utc_tz;
|
timerender.set_display_time_zone("UTC");
|
||||||
|
|
||||||
// Day is 6 days ago in UTC+0 but a week ago in local timezone hence difference in returned strings.
|
// Day is 6 days ago in UTC+0 but a week ago in local timezone hence difference in returned strings.
|
||||||
today = date_2017_PM;
|
today = date_2017_PM;
|
||||||
yesterday = add(date_2017, {days: -6});
|
yesterday = add(date_2017, {days: -6});
|
||||||
assert.equal(timerender.format_time_modern(yesterday, today), "Friday");
|
assert.equal(timerender.format_time_modern(yesterday, today), "Friday");
|
||||||
|
|
||||||
process.env.TZ = "America/Juneau";
|
timerender.set_display_time_zone("America/Juneau");
|
||||||
expected = "translated: 5/11/2017 at 11:12:53 PM AKDT (UTC-08:00)";
|
expected = "translated: 5/11/2017 at 11:12:53 PM AKDT (UTC-08:00)";
|
||||||
assert.equal(timerender.get_full_datetime_clarification(yesterday), expected);
|
assert.equal(timerender.get_full_datetime_clarification(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;
|
timerender.set_display_time_zone("UTC");
|
||||||
});
|
});
|
||||||
|
|
||||||
run_test("render_now_returns_year_with_year_boundary", () => {
|
run_test("render_now_returns_year_with_year_boundary", () => {
|
||||||
@@ -471,13 +468,12 @@ run_test("get_full_datetime", () => {
|
|||||||
user_settings.twenty_four_hour_time = false;
|
user_settings.twenty_four_hour_time = false;
|
||||||
|
|
||||||
// Test the GMT[+-]x:y logic.
|
// Test the GMT[+-]x:y logic.
|
||||||
const previous_env_tz = process.env.TZ;
|
timerender.set_display_time_zone("Asia/Kolkata");
|
||||||
process.env.TZ = "Asia/Kolkata";
|
|
||||||
expected = "translated: 5/19/2017 at 2:42:53 AM (UTC+05:30)";
|
expected = "translated: 5/19/2017 at 2:42:53 AM (UTC+05:30)";
|
||||||
assert.equal(timerender.get_full_datetime_clarification(time), expected);
|
assert.equal(timerender.get_full_datetime_clarification(time), expected);
|
||||||
expected = "translated: May 19, 2017 at 2:42:53 AM";
|
expected = "translated: May 19, 2017 at 2:42:53 AM";
|
||||||
assert.equal(timerender.get_full_datetime(time), expected);
|
assert.equal(timerender.get_full_datetime(time), expected);
|
||||||
process.env.TZ = previous_env_tz;
|
timerender.set_display_time_zone("UTC");
|
||||||
});
|
});
|
||||||
|
|
||||||
run_test("last_seen_status_from_date", () => {
|
run_test("last_seen_status_from_date", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user