dependencies: Replace moment.js with date-fns.

Replaced methods/functions of moment.js with date-fns library.
The motive was to replace it with a smaller frontend timezone library.

Date-fns ~ 11.51 kb
moment.js ~ 217.87 kb

Some of the format strings change because date-fns encodes them
differently from how moment did.

Fixes #16373.
This commit is contained in:
aryanshridhar 2020-09-30 01:50:46 +05:30 committed by Tim Abbott
parent 26a81ab3aa
commit f92f99d92d
19 changed files with 112 additions and 77 deletions

View File

@ -2,21 +2,18 @@
const {strict: assert} = require("assert");
const {parseISO} = require("date-fns");
const _ = require("lodash");
const moment = require("moment-timezone");
const rewiremock = require("rewiremock/node");
const MockDate = require("mockdate");
const {set_global, zrequire} = require("../zjsunit/namespace");
const {run_test} = require("../zjsunit/test");
const people = rewiremock.proxy(() => zrequire("people"), {
"moment-timezone": () => moment("20130208T080910"),
});
set_global("message_store", {});
set_global("page_params", {});
set_global("settings_data", {});
const people = zrequire("people");
const settings_config = zrequire("settings_config");
const visibility = settings_config.email_address_visibility_values;
const admins_only = visibility.admins_only.code;
@ -28,6 +25,8 @@ function set_email_visibility(code) {
set_email_visibility(admins_only);
MockDate.set(parseISO("20130208T080910").getTime());
const welcome_bot = {
email: "welcome-bot@example.com",
user_id: 4,
@ -399,7 +398,7 @@ run_test("user_timezone", () => {
page_params.twenty_four_hour_time = true;
assert.deepEqual(people.get_user_time_preferences(me.user_id), expected_pref);
expected_pref.format = "h:mm A";
expected_pref.format = "h:mm a";
page_params.twenty_four_hour_time = false;
assert.deepEqual(people.get_user_time_preferences(me.user_id), expected_pref);
@ -1111,3 +1110,6 @@ run_test("get_active_message_people", () => {
active_message_people = people.get_active_message_people();
assert.deepEqual(active_message_people, [steven, maria]);
});
// reset to native Date()
MockDate.reset();

View File

@ -2,7 +2,7 @@
const {strict: assert} = require("assert");
const moment = require("moment");
const {getTime} = require("date-fns");
const XDate = require("xdate");
const {set_global, zrequire} = require("../zjsunit/namespace");
@ -150,10 +150,10 @@ run_test("get_timestamp_for_flatpickr", () => {
Date.now = () => new Date("2020-07-07T10:00:00Z").getTime();
// Invalid timestamps should show current time.
assert.equal(func("random str").valueOf(), moment().valueOf());
assert.equal(func("random str").valueOf(), getTime(new Date()));
// Valid ISO timestamps should return Date objects.
assert.equal(func(iso_timestamp).valueOf(), moment(unix_timestamp).valueOf());
assert.equal(func(iso_timestamp).valueOf(), getTime(new Date(unix_timestamp)));
// Restore the Date object.
Date.now = date_now;

View File

@ -24,6 +24,8 @@
"core-js": "^3.6.5",
"css-loader": "^5.0.0",
"css.escape": "^1.5.1",
"date-fns": "^2.16.1",
"date-fns-tz": "^1.1.1",
"emoji-datasource-google": "^6.0.0",
"emoji-datasource-google-blob": "npm:emoji-datasource-google@^3.0.0",
"emoji-datasource-twitter": "^6.0.0",
@ -45,8 +47,6 @@
"katex": "^0.12.0",
"lodash": "^4.17.19",
"mini-css-extract-plugin": "^1.2.0",
"moment": "^2.24.0",
"moment-timezone": "^0.5.25",
"optimize-css-assets-webpack-plugin": "^5.0.3",
"plotly.js": "^1.48.1",
"postcss": "^8.0.3",
@ -98,6 +98,7 @@
"eslint-plugin-import": "^2.22.0",
"js-yaml": "^4.0.0",
"jsdom": "^16.1.0",
"mockdate": "^3.0.2",
"nyc": "^15.0.0",
"openapi-examples-validator": "^4.0.0",
"prettier": "^2.0.5",

2
static/.gitignore vendored
View File

@ -10,5 +10,7 @@
/generated/emoji-styles
# From passing pygments data to the frontend
/generated/pygments_data.json
# From passing timezones data to the frontend
/generated/timezones.json
# Legacy emoji data directory
/third/emoji-data

View File

@ -1,9 +1,9 @@
"use strict";
const autosize = require("autosize");
const {formatISO} = require("date-fns");
const ConfirmDatePlugin = require("flatpickr/dist/plugins/confirmDate/confirmDate");
const _ = require("lodash");
const moment = require("moment");
const pygments_data = require("../generated/pygments_data.json");
const emoji = require("../shared/js/emoji");
@ -754,10 +754,7 @@ const show_flatpickr = (element, callback, default_timestamp) => {
plugins: [new ConfirmDatePlugin({})],
positionElement: element,
dateFormat: "Z",
formatDate: (date) => {
const dt = moment(date);
return dt.local().format();
},
formatDate: (date) => formatISO(date),
});
const container = $($(instance.innerContainer).parent());
container.on("click", ".flatpickr-calendar", (e) => {

View File

@ -1,8 +1,8 @@
"use strict";
const {isValid} = require("date-fns");
const katex = require("katex");
const _ = require("lodash");
const moment = require("moment");
const emoji = require("../shared/js/emoji");
const fenced_code = require("../shared/js/fenced_code");
@ -294,18 +294,14 @@ function handleEmoji(emoji_name) {
function handleTimestamp(time) {
let timeobject;
if (Number.isNaN(Number(time))) {
// Moment throws a large deprecation warning when it has to fallback
// to the Date() constructor. We needn't worry here and can let backend
// Markdown handle any dates that moment misses.
moment.suppressDeprecationWarnings = true;
timeobject = moment(time); // not a Unix timestamp
timeobject = new Date(time); // not a Unix timestamp
} else {
// JavaScript dates are in milliseconds, Unix timestamps are in seconds
timeobject = moment(time * 1000);
timeobject = new Date(time * 1000);
}
const escaped_time = _.escape(time);
if (timeobject === null || !timeobject.isValid()) {
if (timeobject === null || !isValid(timeobject)) {
// Unsupported time format: rerender accordingly.
// We do not show an error on these formats in local echo because

View File

@ -1,6 +1,6 @@
import md5 from "blueimp-md5";
import {format, utcToZonedTime} from "date-fns-tz";
import _ from "lodash";
import moment from "moment-timezone";
import * as typeahead from "../shared/js/typeahead";
@ -252,7 +252,8 @@ export function get_user_time_preferences(user_id) {
export function get_user_time(user_id) {
const user_pref = get_user_time_preferences(user_id);
if (user_pref) {
return moment().tz(user_pref.timezone).format(user_pref.format);
const current_date = utcToZonedTime(new Date(), user_pref.timezone);
return format(current_date, user_pref.format, {timeZone: user_pref.timezone});
}
return undefined;
}

View File

@ -1,8 +1,8 @@
"use strict";
const ClipboardJS = require("clipboard");
const {parseISO, formatISO, add, set} = require("date-fns");
const ConfirmDatePlugin = require("flatpickr/dist/plugins/confirmDate/confirmDate");
const moment = require("moment");
const render_actions_popover_content = require("../templates/actions_popover_content.hbs");
const render_mobile_message_buttons_popover = require("../templates/mobile_message_buttons_popover.hbs");
@ -143,7 +143,7 @@ function get_custom_profile_field_data(user, field, field_types, dateFormat) {
switch (field_type) {
case field_types.DATE.id:
profile_field.value = moment(field_value.value).format(dateFormat);
profile_field.value = dateFormat.format(parseISO(field_value.value));
break;
case field_types.USER.id:
profile_field.id = field.id;
@ -328,7 +328,7 @@ exports.hide_user_profile = function () {
exports.show_user_profile = function (user) {
exports.hide_all();
const dateFormat = moment.localeData().longDateFormat("LL");
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))
@ -340,7 +340,7 @@ exports.show_user_profile = function (user) {
profile_data,
user_avatar: "avatar/" + user.email + "/medium",
is_me: people.is_current_user(user.email),
date_joined: moment(user.date_joined).format(dateFormat),
date_joined: dateFormat.format(parseISO(user.date_joined)),
last_seen: buddy_data.user_last_seen_time_status(user.user_id),
show_email: settings_data.show_email(),
user_time: people.get_user_time(user.user_id),
@ -584,7 +584,7 @@ exports.render_actions_remind_popover = function (element, id) {
).flatpickr({
enableTime: true,
clickOpens: false,
defaultDate: moment().format(),
defaultDate: "today",
minDate: "today",
plugins: [new ConfirmDatePlugin({})],
});
@ -1106,27 +1106,31 @@ exports.register_click_handlers = function () {
}
$("body").on("click", ".remind.in_20m", (e) => {
const datestr = moment().add(20, "m").format();
const datestr = formatISO(add(new Date(), {minutes: 20}));
reminder_click_handler(datestr, e);
});
$("body").on("click", ".remind.in_1h", (e) => {
const datestr = moment().add(1, "h").format();
const datestr = formatISO(add(new Date(), {hours: 1}));
reminder_click_handler(datestr, e);
});
$("body").on("click", ".remind.in_3h", (e) => {
const datestr = moment().add(3, "h").format();
const datestr = formatISO(add(new Date(), {hours: 3}));
reminder_click_handler(datestr, e);
});
$("body").on("click", ".remind.tomo", (e) => {
const datestr = moment().add(1, "d").hour(9).minute(0).seconds(0).format();
const datestr = formatISO(
set(add(new Date(), {days: 1}), {hours: 9, minutes: 0, seconds: 0}),
);
reminder_click_handler(datestr, e);
});
$("body").on("click", ".remind.nxtw", (e) => {
const datestr = moment().add(1, "w").day("monday").hour(9).minute(0).seconds(0).format();
const datestr = formatISO(
set(add(new Date(), {weeks: 1}), {hours: 9, minutes: 0, seconds: 0}),
);
reminder_click_handler(datestr, e);
});

View File

@ -1,7 +1,5 @@
"use strict";
const moment = require("moment-timezone");
$(() => {
// NB: this file is included on multiple pages. In each context,
// some of the jQuery selectors below will return empty lists.
@ -82,7 +80,7 @@ $(() => {
$(".team_subdomain_error_server").css("display", "none");
}
$("#timezone").val(moment.tz.guess());
$("#timezone").val(new Intl.DateTimeFormat().resolvedOptions().timeZone);
}
// Code in this block will be executed when the /accounts/send_confirm

View File

@ -1,7 +1,5 @@
"use strict";
const moment = require("moment-timezone");
const people = require("./people");
const util = require("./util");
@ -37,7 +35,7 @@ function patch_request_for_scheduling(request, message_content, deliver_at, deli
new_request.content = message_content;
new_request.deliver_at = deliver_at;
new_request.delivery_type = delivery_type;
new_request.tz_guess = moment.tz.guess();
new_request.tz_guess = new Intl.DateTimeFormat().resolvedOptions().timeZone;
return new_request;
}

View File

@ -1,7 +1,7 @@
"use strict";
const ClipboardJS = require("clipboard");
const moment = require("moment");
const {parseISO, isValid} = require("date-fns");
const copy_code_button = require("../templates/copy_code_button.hbs");
const view_code_in_playground = require("../templates/view_code_in_playground.hbs");
@ -155,20 +155,15 @@ exports.update_elements = (content) => {
return;
}
// Moment throws a large deprecation warning when it has to
// fallback to the Date() constructor. This isn't really a
// problem for us except in local echo, as the backend always
// uses a format that ensures that is unnecessary.
moment.suppressDeprecationWarnings = true;
const timestamp = moment(time_str);
if (timestamp.isValid()) {
const timestamp = parseISO(time_str);
if (isValid(timestamp)) {
const text = $(this).text();
const rendered_time = timerender.render_markdown_timestamp(timestamp, text);
$(this).text(rendered_time.text);
$(this).attr("title", rendered_time.title);
} else {
// This shouldn't happen. If it does, we're very interested in debugging it.
blueslip.error(`Moment could not parse datetime supplied by backend: ${time_str}`);
blueslip.error(`Could not parse datetime supplied by backend: ${time_str}`);
}
});

View File

@ -1,7 +1,6 @@
"use strict";
const moment = require("moment-timezone");
const timezones = require("../generated/timezones.json");
const render_settings_tab = require("../templates/settings_tab.hbs");
const people = require("./people");
@ -71,7 +70,7 @@ exports.build_page = function () {
page_params.enable_sounds || page_params.enable_stream_audible_notifications,
zuliprc: "zuliprc",
botserverrc: "botserverrc",
timezones: moment.tz.names(),
timezones: timezones.timezones,
can_create_new_bots: settings_bots.can_create_new_bots(),
settings_label: exports.settings_label,
demote_inactive_streams_values: settings_config.demote_inactive_streams_values,

View File

@ -54,7 +54,7 @@ exports.get_time_preferences = function (user_timezone) {
}
return {
timezone: user_timezone,
format: "h:mm A",
format: "h:mm a",
};
};

View File

@ -1,6 +1,6 @@
"use strict";
const moment = require("moment");
const {format, parseISO, isValid} = require("date-fns");
const XDate = require("xdate");
let next_timerender_id = 0;
@ -174,8 +174,8 @@ exports.render_date = function (time, time_above, today) {
// Renders the timestamp returned by the <time:> Markdown syntax.
exports.render_markdown_timestamp = function (time, text) {
const hourformat = page_params.twenty_four_hour_time ? "HH:mm" : "h:mm A";
const timestring = time.format("ddd, MMM D YYYY, " + hourformat);
const hourformat = page_params.twenty_four_hour_time ? "HH:mm" : "h:mm a";
const timestring = format(time, "E, MMM d yyyy, " + hourformat);
const titlestring = "This time is in your timezone. Original text was '" + text + "'.";
return {
text: timestring,
@ -232,19 +232,17 @@ exports.get_full_time = function (timestamp) {
exports.get_timestamp_for_flatpickr = (timestring) => {
let timestamp;
moment.suppressDeprecationWarnings = true;
try {
// If there's already a valid time in the compose box,
// we use it to initialize the flatpickr instance.
timestamp = moment(timestring);
timestamp = parseISO(timestring);
} finally {
// Otherwise, default to showing the current time.
if (!timestamp || !timestamp.isValid()) {
timestamp = moment();
if (!timestamp || !isValid(timestamp)) {
timestamp = new Date();
}
}
moment.suppressDeprecationWarnings = false;
return timestamp.toDate();
return timestamp;
};
exports.stringify_time = function (time) {

View File

@ -10,6 +10,7 @@ ZULIP_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__f
sys.path.append(ZULIP_PATH)
from pygments import __version__ as pygments_version
from pytz import VERSION as timezones_version
from scripts.lib.zulip_tools import (
ENDC,
@ -47,6 +48,12 @@ def build_pygments_data_paths() -> List[str]:
]
return paths
def build_timezones_data_paths() -> List[str]:
paths = [
"tools/setup/build_timezone_values",
]
return paths
def compilemessages_paths() -> List[str]:
paths = ['zerver/management/commands/compilemessages.py']
paths += glob.glob('locale/*/LC_MESSAGES/*.po')
@ -135,6 +142,16 @@ def need_to_run_build_pygments_data() -> bool:
[pygments_version],
)
def need_to_run_build_timezone_data() -> bool:
if not os.path.exists("static/generated/timezones.json"):
return True
return is_digest_obsolete(
"build_timezones_data_hash",
build_timezones_data_paths(),
[timezones_version],
)
def need_to_run_compilemessages() -> bool:
if not os.path.exists('locale/language_name_map.json'):
# User may have cleaned their git checkout.
@ -213,6 +230,16 @@ def main(options: argparse.Namespace) -> int:
else:
print("No need to run `tools/setup/build_pygments_data`.")
if options.is_force or need_to_run_build_timezone_data():
run(["tools/setup/build_timezone_values"])
write_new_digest(
"build_timezones_data_hash",
build_timezones_data_paths(),
[timezones_version],
)
else:
print("No need to run `tools/setup/build_timezone_values`.")
if options.is_force or need_to_run_inline_email_css():
run(["scripts/setup/inline_email_css.py"])
write_new_digest(

View File

@ -0,0 +1,11 @@
#!/usr/bin/env python3
import json
import os
import pytz
ZULIP_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../')
OUT_PATH = os.path.join(ZULIP_PATH, 'static', 'generated', 'timezones.json')
with open(OUT_PATH, 'w') as f:
json.dump({"timezones": pytz.all_timezones}, f)

View File

@ -44,6 +44,9 @@ run(['./tools/setup/generate_zulip_bots_static_files.py'])
# Build pygment data
run(['./tools/setup/build_pygments_data'])
# Build timezones data
run(['./tools/setup/build_timezone_values'])
# Create webpack bundle
run(['./tools/webpack', '--quiet'])

View File

@ -43,4 +43,4 @@ API_FEATURE_LEVEL = 38
# historical commits sharing the same major version, in which case a
# minor version bump suffices.
PROVISION_VERSION = '124.2'
PROVISION_VERSION = '125.0'

View File

@ -3865,6 +3865,16 @@ data-urls@^2.0.0:
whatwg-mimetype "^2.3.0"
whatwg-url "^8.0.0"
date-fns-tz@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/date-fns-tz/-/date-fns-tz-1.1.1.tgz#2e0dfcc62cc5b7b5fa7ea620f11a5e7f63a7ed75"
integrity sha512-5PR604TlyvpiNXtvn+PZCcCazsI8fI1am3/aimNFN8CMqHQ0KRl+6hB46y4mDbB7bk3+caEx3qHhS7Ewac/FIg==
date-fns@^2.16.1:
version "2.16.1"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.16.1.tgz#05775792c3f3331da812af253e1a935851d3834b"
integrity sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ==
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@ -8191,17 +8201,10 @@ mkdirp@^1.0.3, mkdirp@^1.0.4:
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
moment-timezone@^0.5.25:
version "0.5.32"
resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.32.tgz#db7677cc3cc680fd30303ebd90b0da1ca0dfecc2"
integrity sha512-Z8QNyuQHQAmWucp8Knmgei8YNo28aLjJq6Ma+jy1ZSpSk5nyfRT8xgUbSQvD2+2UajISfenndwvFuH3NGS+nvA==
dependencies:
moment ">= 2.9.0"
"moment@>= 2.9.0", moment@^2.24.0:
version "2.29.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
mockdate@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/mockdate/-/mockdate-3.0.2.tgz#a5a7bb5820da617747af424d7a4dcb22c6c03d79"
integrity sha512-ldfYSUW1ocqSHGTK6rrODUiqAFPGAg0xaHqYJ5tvj1hQyFsjuHpuWgWFTZWwDVlzougN/s2/mhDr8r5nY5xDpA==
monotone-convex-hull-2d@^1.0.1:
version "1.0.1"