Remove moment.js
Moment.js has a large bundle size and it causes a performance overhead. Change-Id: Ida5c2642006d5018300527cb2b5eaab3c61772d2
This commit is contained in:
@@ -26,7 +26,7 @@ import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mix
|
||||
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
|
||||
import {htmlTemplate} from './gr-messages-list-experimental_html.js';
|
||||
import {KeyboardShortcutBehavior} from '../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.js';
|
||||
import {util} from '../../../scripts/util.js';
|
||||
import {parseDate} from '../../../utils/date-util.js';
|
||||
import {MessageTag} from '../../../constants/constants.js';
|
||||
import {appContext} from '../../../services/app-context.js';
|
||||
|
||||
@@ -219,8 +219,8 @@ class GrMessagesListExperimental extends mixinBehaviors( [
|
||||
combinedMessages = combinedMessages.concat(messages.slice(mi));
|
||||
break;
|
||||
}
|
||||
mDate = mDate || util.parseDate(messages[mi].date);
|
||||
rDate = rDate || util.parseDate(reviewerUpdates[ri].date);
|
||||
mDate = mDate || parseDate(messages[mi].date);
|
||||
rDate = rDate || parseDate(reviewerUpdates[ri].date);
|
||||
if (rDate < mDate) {
|
||||
combinedMessages.push(reviewerUpdates[ri++]);
|
||||
rDate = null;
|
||||
|
@@ -25,7 +25,7 @@ import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mix
|
||||
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
|
||||
import {htmlTemplate} from './gr-messages-list_html.js';
|
||||
import {KeyboardShortcutBehavior} from '../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.js';
|
||||
import {util} from '../../../scripts/util.js';
|
||||
import {parseDate} from '../../../utils/date-util.js';
|
||||
import {appContext} from '../../../services/app-context.js';
|
||||
|
||||
const MAX_INITIAL_SHOWN_MESSAGES = 20;
|
||||
@@ -191,8 +191,8 @@ class GrMessagesList extends mixinBehaviors( [
|
||||
result = result.concat(messages.slice(mi));
|
||||
break;
|
||||
}
|
||||
mDate = mDate || util.parseDate(messages[mi].date);
|
||||
rDate = rDate || util.parseDate(reviewerUpdates[ri].date);
|
||||
mDate = mDate || parseDate(messages[mi].date);
|
||||
rDate = rDate || parseDate(reviewerUpdates[ri].date);
|
||||
if (rDate < mDate) {
|
||||
result.push(reviewerUpdates[ri++]);
|
||||
rDate = null;
|
||||
@@ -303,14 +303,14 @@ class GrMessagesList extends mixinBehaviors( [
|
||||
const messages = this.messages || [];
|
||||
const index = message._index;
|
||||
const authorId = message.author && message.author._account_id;
|
||||
const mDate = util.parseDate(message.date).getTime();
|
||||
const mDate = parseDate(message.date).getTime();
|
||||
// NB: Messages array has oldest messages first.
|
||||
let nextMDate;
|
||||
if (index > 0) {
|
||||
for (let i = index - 1; i >= 0; i--) {
|
||||
if (messages[i] && messages[i].author &&
|
||||
messages[i].author._account_id === authorId) {
|
||||
nextMDate = util.parseDate(messages[i].date).getTime();
|
||||
nextMDate = parseDate(messages[i].date).getTime();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -324,7 +324,7 @@ class GrMessagesList extends mixinBehaviors( [
|
||||
fileComments[i].author._account_id !== authorId) {
|
||||
continue;
|
||||
}
|
||||
const cDate = util.parseDate(fileComments[i].updated).getTime();
|
||||
const cDate = parseDate(fileComments[i].updated).getTime();
|
||||
if (cDate <= mDate) {
|
||||
if (nextMDate && cDate <= nextMDate) {
|
||||
continue;
|
||||
|
@@ -22,7 +22,7 @@ import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-l
|
||||
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
|
||||
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
|
||||
import {htmlTemplate} from './gr-thread-list_html.js';
|
||||
import {util} from '../../../scripts/util.js';
|
||||
import {parseDate} from '../../../utils/date-util.js';
|
||||
|
||||
import {NO_THREADS_MSG} from '../../../constants/messages.js';
|
||||
|
||||
@@ -163,8 +163,8 @@ class GrThreadList extends GestureEventListeners(
|
||||
return c1.thread.line - c2.thread.line;
|
||||
}
|
||||
|
||||
const c1Date = c1.__date || util.parseDate(c1.updated);
|
||||
const c2Date = c2.__date || util.parseDate(c2.updated);
|
||||
const c1Date = c1.__date || parseDate(c1.updated);
|
||||
const c2Date = c2.__date || parseDate(c2.updated);
|
||||
const dateCompare = c2Date - c1Date;
|
||||
if (dateCompare === 0 && (!c1.id || !c1.id.localeCompare)) {
|
||||
return 0;
|
||||
|
@@ -21,7 +21,7 @@ import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mix
|
||||
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
|
||||
import {htmlTemplate} from './gr-comment-api_html.js';
|
||||
import {PatchSetBehavior} from '../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.js';
|
||||
import {util} from '../../../scripts/util.js';
|
||||
import {parseDate} from '../../../utils/date-util.js';
|
||||
|
||||
const PARENT = 'PARENT';
|
||||
|
||||
@@ -470,7 +470,7 @@ class ChangeComments {
|
||||
.sort(
|
||||
(c1, c2) => {
|
||||
const dateDiff =
|
||||
util.parseDate(c1.updated) - util.parseDate(c2.updated);
|
||||
parseDate(c1.updated) - parseDate(c2.updated);
|
||||
if (dateDiff) {
|
||||
return dateDiff;
|
||||
}
|
||||
|
@@ -27,7 +27,7 @@ import {PolymerElement} from '@polymer/polymer/polymer-element.js';
|
||||
import {htmlTemplate} from './gr-diff-host_html.js';
|
||||
import {PatchSetBehavior} from '../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.js';
|
||||
import {GrDiffBuilder} from '../gr-diff-builder/gr-diff-builder.js';
|
||||
import {util} from '../../../scripts/util.js';
|
||||
import {parseDate} from '../../../utils/date-util.js';
|
||||
import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
|
||||
import {DiffSide, rangesEqual} from '../gr-diff/gr-diff-utils.js';
|
||||
import {appContext} from '../../../services/app-context.js';
|
||||
@@ -667,7 +667,7 @@ class GrDiffHost extends mixinBehaviors( [
|
||||
return comments.slice(0).sort((a, b) => {
|
||||
if (b.__draft && !a.__draft ) { return -1; }
|
||||
if (a.__draft && !b.__draft ) { return 1; }
|
||||
return util.parseDate(a.updated) - util.parseDate(b.updated);
|
||||
return parseDate(a.updated) - parseDate(b.updated);
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -48,7 +48,6 @@ import {GrRangeNormalizer} from './diff/gr-diff-highlight/gr-range-normalizer.js
|
||||
import {GrCountStringFormatter} from './shared/gr-count-string-formatter/gr-count-string-formatter.js';
|
||||
import {GrReviewerSuggestionsProvider, SUGGESTIONS_PROVIDERS_USERS_TYPES} from '../scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider.js';
|
||||
import {util} from '../scripts/util.js';
|
||||
import moment from 'moment/src/moment.js';
|
||||
import page from 'page/page.mjs';
|
||||
import {Auth} from './shared/gr-rest-api-interface/gr-auth.js';
|
||||
import {EventEmitter} from './shared/gr-event-interface/gr-event-interface.js';
|
||||
@@ -103,7 +102,6 @@ export function initGlobalVariables() {
|
||||
window.GrCountStringFormatter = GrCountStringFormatter;
|
||||
window.GrReviewerSuggestionsProvider = GrReviewerSuggestionsProvider;
|
||||
window.util = util;
|
||||
window.moment = moment;
|
||||
window.page = page;
|
||||
window.Auth = Auth;
|
||||
window.EventEmitter = EventEmitter;
|
||||
|
@@ -26,7 +26,7 @@ import {PolymerElement} from '@polymer/polymer/polymer-element.js';
|
||||
import {htmlTemplate} from './gr-comment-thread_html.js';
|
||||
import {PathListBehavior} from '../../../behaviors/gr-path-list-behavior/gr-path-list-behavior.js';
|
||||
import {KeyboardShortcutBehavior} from '../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.js';
|
||||
import {util} from '../../../scripts/util.js';
|
||||
import {parseDate} from '../../../utils/date-util.js';
|
||||
import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
|
||||
import {appContext} from '../../../services/app-context.js';
|
||||
|
||||
@@ -307,8 +307,8 @@ class GrCommentThread extends mixinBehaviors( [
|
||||
|
||||
_sortedComments(comments) {
|
||||
return comments.slice().sort((c1, c2) => {
|
||||
const c1Date = c1.__date || util.parseDate(c1.updated);
|
||||
const c2Date = c2.__date || util.parseDate(c2.updated);
|
||||
const c1Date = c1.__date || parseDate(c1.updated);
|
||||
const c2Date = c2.__date || parseDate(c2.updated);
|
||||
const dateCompare = c1Date - c2Date;
|
||||
// Ensure drafts are at the end. There should only be one but in edge
|
||||
// cases could be more. In the unlikely event two drafts are being
|
||||
|
@@ -22,13 +22,7 @@ import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mix
|
||||
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
|
||||
import {htmlTemplate} from './gr-date-formatter_html.js';
|
||||
import {TooltipBehavior} from '../../../behaviors/gr-tooltip-behavior/gr-tooltip-behavior.js';
|
||||
import {util} from '../../../scripts/util.js';
|
||||
import moment from 'moment/src/moment.js';
|
||||
|
||||
const Duration = {
|
||||
HOUR: 1000 * 60 * 60,
|
||||
DAY: 1000 * 60 * 60 * 24,
|
||||
};
|
||||
import {parseDate, fromNow, isValidDate, isWithinDay, isWithinHalfYear, formatDate, utcOffsetString} from '../../../utils/date-util.js';
|
||||
|
||||
const TimeFormats = {
|
||||
TIME_12: 'h:mm A', // 2:14 PM
|
||||
@@ -106,6 +100,10 @@ class GrDateFormatter extends mixinBehaviors( [
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/** @override */
|
||||
attached() {
|
||||
super.attached();
|
||||
@@ -113,7 +111,7 @@ class GrDateFormatter extends mixinBehaviors( [
|
||||
}
|
||||
|
||||
_getUtcOffsetString() {
|
||||
return ' UTC' + moment().format('Z');
|
||||
return utcOffsetString();
|
||||
}
|
||||
|
||||
_loadPreferences() {
|
||||
@@ -190,50 +188,28 @@ class GrDateFormatter extends mixinBehaviors( [
|
||||
return this.$.restAPI.getPreferences();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if date is within 24 hours and on the same day.
|
||||
*/
|
||||
_isWithinDay(now, date) {
|
||||
const diff = -date.diff(now);
|
||||
return diff < Duration.DAY && date.day() === now.getDay();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if date is from one to six months.
|
||||
*/
|
||||
_isWithinHalfYear(now, date) {
|
||||
const diff = -date.diff(now);
|
||||
return (date.day() !== now.getDay() || diff >= Duration.DAY) &&
|
||||
diff < 180 * Duration.DAY;
|
||||
}
|
||||
|
||||
_computeDateStr(
|
||||
dateStr, timeFormat, dateFormat, relative, showDateAndTime
|
||||
) {
|
||||
if (!dateStr || !timeFormat || !dateFormat) { return ''; }
|
||||
const date = moment(util.parseDate(dateStr));
|
||||
if (!date.isValid()) { return ''; }
|
||||
const date = parseDate(dateStr);
|
||||
if (!isValidDate(date)) { return ''; }
|
||||
if (relative) {
|
||||
const dateFromNow = date.fromNow();
|
||||
if (dateFromNow === 'a few seconds ago') {
|
||||
return 'just now';
|
||||
} else {
|
||||
return dateFromNow;
|
||||
}
|
||||
return fromNow(date);
|
||||
}
|
||||
const now = new Date();
|
||||
let format = dateFormat.full;
|
||||
if (this._isWithinDay(now, date)) {
|
||||
if (isWithinDay(now, date)) {
|
||||
format = timeFormat;
|
||||
} else {
|
||||
if (this._isWithinHalfYear(now, date)) {
|
||||
if (isWithinHalfYear(now, date)) {
|
||||
format = dateFormat.short;
|
||||
}
|
||||
if (this.showDateAndTime) {
|
||||
format = `${format} ${timeFormat}`;
|
||||
}
|
||||
}
|
||||
return date.format(format);
|
||||
return formatDate(date, format);
|
||||
}
|
||||
|
||||
_timeToSecondsFormat(timeFormat) {
|
||||
@@ -253,11 +229,11 @@ class GrDateFormatter extends mixinBehaviors( [
|
||||
}
|
||||
|
||||
if (!dateStr) { return ''; }
|
||||
const date = moment(util.parseDate(dateStr));
|
||||
if (!date.isValid()) { return ''; }
|
||||
const date = parseDate(dateStr);
|
||||
if (!isValidDate(date)) { return ''; }
|
||||
let format = dateFormat.full + ', ';
|
||||
format += this._timeToSecondsFormat(timeFormat);
|
||||
return date.format(format) + this._getUtcOffsetString();
|
||||
return formatDate(date, format) + this._getUtcOffsetString();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -34,7 +34,7 @@ limitations under the License.
|
||||
<script type="module">
|
||||
import '../../../test/common-test-setup.js';
|
||||
import './gr-date-formatter.js';
|
||||
import {util} from '../../../scripts/util.js';
|
||||
import {parseDate} from '../../../utils/date-util.js';
|
||||
suite('gr-date-formatter tests', () => {
|
||||
let element;
|
||||
let sandbox;
|
||||
@@ -51,7 +51,7 @@ suite('gr-date-formatter tests', () => {
|
||||
* Parse server-formatter date and normalize into current timezone.
|
||||
*/
|
||||
function normalizedDate(dateStr) {
|
||||
const d = util.parseDate(dateStr);
|
||||
const d = parseDate(dateStr);
|
||||
d.setMinutes(d.getMinutes() + d.getTimezoneOffset());
|
||||
return d;
|
||||
}
|
||||
|
@@ -29,7 +29,7 @@ import {RESTClientBehavior} from '../../../behaviors/rest-client-behavior/rest-c
|
||||
import {GrEtagDecorator} from './gr-etag-decorator.js';
|
||||
import {SiteBasedCache, FetchPromisesCache, GrRestApiHelper} from './gr-rest-apis/gr-rest-api-helper.js';
|
||||
import {GrReviewerUpdatesParser} from './gr-reviewer-updates-parser.js';
|
||||
import {util} from '../../../scripts/util.js';
|
||||
import {parseDate} from '../../../utils/date-util.js';
|
||||
import {authService} from './gr-auth.js';
|
||||
|
||||
const DiffViewMode = {
|
||||
@@ -2075,7 +2075,7 @@ class GrRestApiInterface extends mixinBehaviors( [
|
||||
_setRanges(comments) {
|
||||
comments = comments || [];
|
||||
comments.sort(
|
||||
(a, b) => util.parseDate(a.updated) - util.parseDate(b.updated)
|
||||
(a, b) => parseDate(a.updated) - parseDate(b.updated)
|
||||
);
|
||||
for (const comment of comments) {
|
||||
this._setRange(comments, comment);
|
||||
|
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {util} from '../../../scripts/util.js';
|
||||
import {parseDate} from '../../../utils/date-util.js';
|
||||
import {MessageTag} from '../../../constants/constants.js';
|
||||
|
||||
/** @constructor */
|
||||
@@ -106,8 +106,8 @@ GrReviewerUpdatesParser.prototype._groupUpdates = function() {
|
||||
if (!this._batch) {
|
||||
this._batch = this._startBatch(update);
|
||||
}
|
||||
const updateDate = util.parseDate(update.updated).getTime();
|
||||
const batchUpdateDate = util.parseDate(this._batch.date).getTime();
|
||||
const updateDate = parseDate(update.updated).getTime();
|
||||
const batchUpdateDate = parseDate(this._batch.date).getTime();
|
||||
const reviewerId = update.reviewer._account_id.toString();
|
||||
if (updateDate - batchUpdateDate >
|
||||
GrReviewerUpdatesParser.REVIEWER_UPDATE_THRESHOLD_MILLIS ||
|
||||
@@ -206,14 +206,14 @@ GrReviewerUpdatesParser.prototype._advanceUpdates = function() {
|
||||
const updates = this.result.reviewer_updates;
|
||||
const messages = this.result.messages;
|
||||
messages.forEach((message, index) => {
|
||||
const messageDate = util.parseDate(message.date).getTime();
|
||||
const messageDate = parseDate(message.date).getTime();
|
||||
const nextMessageDate = index === messages.length - 1 ? null :
|
||||
util.parseDate(messages[index + 1].date).getTime();
|
||||
parseDate(messages[index + 1].date).getTime();
|
||||
for (const update of updates) {
|
||||
const date = util.parseDate(update.date).getTime();
|
||||
const date = parseDate(update.date).getTime();
|
||||
if (date >= messageDate &&
|
||||
(!nextMessageDate || date < nextMessageDate)) {
|
||||
const timestamp = util.parseDate(update.date).getTime() -
|
||||
const timestamp = parseDate(update.date).getTime() -
|
||||
GrReviewerUpdatesParser.MESSAGE_REVIEWERS_THRESHOLD_MILLIS;
|
||||
update.date = new Date(timestamp)
|
||||
.toISOString()
|
||||
|
@@ -27,7 +27,7 @@ limitations under the License.
|
||||
<script type="module">
|
||||
import '../../../test/common-test-setup.js';
|
||||
import {GrReviewerUpdatesParser} from './gr-reviewer-updates-parser.js';
|
||||
import {util} from '../../../scripts/util.js';
|
||||
import {parseDate} from '../../../utils/date-util.js';
|
||||
|
||||
suite('gr-reviewer-updates-parser tests', () => {
|
||||
let sandbox;
|
||||
@@ -253,7 +253,7 @@ suite('gr-reviewer-updates-parser tests', () => {
|
||||
});
|
||||
|
||||
test('_advanceUpdates', () => {
|
||||
const T0 = util.parseDate('2017-02-17 19:04:18.000000000').getTime();
|
||||
const T0 = parseDate('2017-02-17 19:04:18.000000000').getTime();
|
||||
const tplus = delta => new Date(T0 + delta)
|
||||
.toISOString()
|
||||
.replace('T', ' ')
|
||||
@@ -297,8 +297,8 @@ suite('gr-reviewer-updates-parser tests', () => {
|
||||
instance = new GrReviewerUpdatesParser(change);
|
||||
instance._advanceUpdates();
|
||||
const updates = instance.result.reviewer_updates;
|
||||
assert.isBelow(util.parseDate(updates[0].date).getTime(), T0);
|
||||
assert.isBelow(util.parseDate(updates[1].date).getTime(), T0);
|
||||
assert.isBelow(parseDate(updates[0].date).getTime(), T0);
|
||||
assert.isBelow(parseDate(updates[1].date).getTime(), T0);
|
||||
assert.equal(updates[2].date, tplus(100));
|
||||
assert.equal(updates[3].date, tplus(500));
|
||||
});
|
||||
|
@@ -272,14 +272,6 @@ const packages: PackageInfo[] = [
|
||||
name: "isarray",
|
||||
license: SharedLicenses.IsArray
|
||||
},
|
||||
{
|
||||
name: "moment",
|
||||
license: {
|
||||
name: "moment",
|
||||
type: LicenseTypes.Mit,
|
||||
packageLicenseFile: "LICENSE"
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "page",
|
||||
license: SharedLicenses.Page
|
||||
|
@@ -26,7 +26,6 @@
|
||||
"@webcomponents/shadycss": "^1.9.2",
|
||||
"@webcomponents/webcomponentsjs": "^1.3.3",
|
||||
"es6-promise": "^3.3.1",
|
||||
"moment": "^2.24.0",
|
||||
"page": "^1.11.5",
|
||||
"polymer-bridges": "file:../../polymer-bridges/",
|
||||
"ba-linkify": "file:../../lib/ba-linkify/src/",
|
||||
|
@@ -29,14 +29,6 @@ function getPathFromNode(el) {
|
||||
// TODO (dmfilippov): Each function must be exported separately. According to
|
||||
// the code style guide, a namespacing is not allowed.
|
||||
export const util = {
|
||||
parseDate(dateStr) {
|
||||
// Timestamps are given in UTC and have the format
|
||||
// "'yyyy-mm-dd hh:mm:ss.fffffffff'" where "'ffffffffff'" represents
|
||||
// nanoseconds.
|
||||
// Munge the date into an ISO 8061 format and parse that.
|
||||
return new Date(dateStr.replace(' ', 'T') + 'Z');
|
||||
},
|
||||
|
||||
getCookie(name) {
|
||||
const key = name + '=';
|
||||
const cookies = document.cookie.split(';');
|
||||
|
@@ -58,6 +58,5 @@ var FetchPromisesCache;
|
||||
var GrRestApiHelper;
|
||||
var GrDisplayNameUtils;
|
||||
var GrReviewerSuggestionsProvider;
|
||||
var moment;
|
||||
var page;
|
||||
var util;
|
172
polygerrit-ui/app/utils/date-util.js
Normal file
172
polygerrit-ui/app/utils/date-util.js
Normal file
@@ -0,0 +1,172 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const Duration = {
|
||||
HOUR: 1000 * 60 * 60,
|
||||
DAY: 1000 * 60 * 60 * 24,
|
||||
};
|
||||
|
||||
export function parseDate(dateStr) {
|
||||
// Timestamps are given in UTC and have the format
|
||||
// "'yyyy-mm-dd hh:mm:ss.fffffffff'" where "'ffffffffff'" represents
|
||||
// nanoseconds.
|
||||
// Munge the date into an ISO 8061 format and parse that.
|
||||
return new Date(dateStr.replace(' ', 'T') + 'Z');
|
||||
}
|
||||
|
||||
export function isValidDate(date) {
|
||||
return date instanceof Date && !isNaN(date);
|
||||
}
|
||||
|
||||
// similar to fromNow from moment.js
|
||||
export function fromNow(date) {
|
||||
const now = new Date();
|
||||
const secondsAgo = Math.round((now - date) / 1000);
|
||||
if (secondsAgo <= 44) return 'just now';
|
||||
if (secondsAgo <= 89) return 'a minute ago';
|
||||
const minutesAgo = Math.round(secondsAgo / 60);
|
||||
if (minutesAgo <= 44) return `${minutesAgo} minutes ago`;
|
||||
if (minutesAgo <= 89) return 'an hour ago';
|
||||
const hoursAgo = Math.round(minutesAgo / 60);
|
||||
if (hoursAgo <= 21) return `${hoursAgo} hours ago`;
|
||||
if (hoursAgo <= 35) return 'a day ago';
|
||||
const daysAgo = Math.round(hoursAgo / 24);
|
||||
if (daysAgo <= 25) return `${daysAgo} days ago`;
|
||||
if (daysAgo <= 45) return `a month ago`;
|
||||
const monthsAgo = Math.round(daysAgo / 30);
|
||||
if (daysAgo <= 319) return `${monthsAgo} months ago`;
|
||||
if (daysAgo <= 547) return `a year ago`;
|
||||
const yearsAgo = Math.round(daysAgo / 365);
|
||||
return `${yearsAgo} years ago`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if date is within 24 hours and on the same day.
|
||||
*/
|
||||
export function isWithinDay(now, date) {
|
||||
const diff = now - date;
|
||||
return diff < Duration.DAY && date.getDay() == now.getDay();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if date is from one to six months.
|
||||
*/
|
||||
export function isWithinHalfYear(now, date) {
|
||||
const diff = now - date;
|
||||
return diff < 180 * Duration.DAY;
|
||||
}
|
||||
|
||||
export function formatDate(date, format) {
|
||||
const options = {};
|
||||
if (format.includes('MM')) {
|
||||
if (format.includes('MMM')) {
|
||||
options.month = 'short';
|
||||
} else {
|
||||
options.month = '2-digit';
|
||||
}
|
||||
}
|
||||
if (format.includes('YY')) {
|
||||
if (format.includes('YYYY')) {
|
||||
options.year = 'numeric';
|
||||
} else {
|
||||
options.year = '2-digit';
|
||||
}
|
||||
}
|
||||
|
||||
if (format.includes('DD')) {
|
||||
options.day = '2-digit';
|
||||
}
|
||||
|
||||
if (format.includes('HH')) {
|
||||
options.hour = '2-digit';
|
||||
options.hour12 = false;
|
||||
}
|
||||
|
||||
if (format.includes('h')) {
|
||||
options.hour = 'numeric';
|
||||
options.hour12 = true;
|
||||
}
|
||||
|
||||
if (format.includes('mm')) {
|
||||
options.minute = '2-digit';
|
||||
}
|
||||
|
||||
if (format.includes('ss')) {
|
||||
options.second = '2-digit';
|
||||
}
|
||||
// en-GB is using h23 on Chrome 80, en-US is using h24 (midnight is 24:00)
|
||||
const dtf = new Intl.DateTimeFormat('en-GB', options);
|
||||
const parts = dtf.formatToParts(date).filter(o => o.type != 'literal')
|
||||
.reduce((acc, o) => {
|
||||
acc[o.type] = o.value;
|
||||
return acc;
|
||||
}, {});
|
||||
if (format.includes('YY')) {
|
||||
if (format.includes('YYYY')) {
|
||||
format = format.replace('YYYY', parts.year);
|
||||
} else {
|
||||
format = format.replace('YY', parts.year);
|
||||
}
|
||||
}
|
||||
|
||||
if (format.includes('DD')) {
|
||||
format = format.replace('DD', parts.day);
|
||||
}
|
||||
|
||||
if (format.includes('HH')) {
|
||||
format = format.replace('HH', parts.hour);
|
||||
}
|
||||
|
||||
if (format.includes('h')) {
|
||||
format = format.replace('h', parts.hour);
|
||||
}
|
||||
|
||||
if (format.includes('mm')) {
|
||||
format = format.replace('mm', parts.minute);
|
||||
}
|
||||
|
||||
if (format.includes('ss')) {
|
||||
format = format.replace('ss', parts.second);
|
||||
}
|
||||
|
||||
if (format.includes('A')) {
|
||||
if (parts.dayperiod) {
|
||||
// Workaround for chrome 70 and below
|
||||
format = format.replace('A', parts.dayperiod.toUpperCase());
|
||||
} else {
|
||||
format = format.replace('A', parts.dayPeriod.toUpperCase());
|
||||
}
|
||||
}
|
||||
if (format.includes('MM')) {
|
||||
if (format.includes('MMM')) {
|
||||
format = format.replace('MMM', parts.month);
|
||||
} else {
|
||||
format = format.replace('MM', parts.month);
|
||||
}
|
||||
}
|
||||
return format;
|
||||
}
|
||||
|
||||
export function utcOffsetString() {
|
||||
const now = new Date();
|
||||
const tzo = -now.getTimezoneOffset();
|
||||
const pad = num => {
|
||||
const norm = Math.floor(Math.abs(num));
|
||||
return (norm < 10 ? '0' : '') + norm;
|
||||
};
|
||||
return ` UTC${tzo >= 0 ? '+' : '-'}${pad(tzo / 60)}:${pad(tzo%60)}`;
|
||||
}
|
114
polygerrit-ui/app/utils/date-util_test.js
Normal file
114
polygerrit-ui/app/utils/date-util_test.js
Normal file
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import '../test/common-test-setup-karma.js';
|
||||
import {isValidDate, parseDate, fromNow, isWithinDay, isWithinHalfYear, formatDate} from './date-util.js';
|
||||
|
||||
suite('date-util tests', () => {
|
||||
suite('parseDate', () => {
|
||||
test('parseDate server date', () => {
|
||||
const parsed = parseDate('2015-09-15 20:34:00.000000000');
|
||||
assert.equal('2015-09-15T20:34:00.000Z', parsed.toISOString());
|
||||
});
|
||||
});
|
||||
|
||||
suite('isValidDate', () => {
|
||||
test('date is valid', () => {
|
||||
assert.isTrue(isValidDate(new Date()));
|
||||
});
|
||||
test('broken date is invalid', () => {
|
||||
assert.isFalse(isValidDate(new Date('xxx')));
|
||||
});
|
||||
});
|
||||
|
||||
suite('fromNow', () => {
|
||||
test('test all variants', () => {
|
||||
const fakeNow = new Date('May 08 2020 12:00:00');
|
||||
sinon.useFakeTimers(fakeNow.getTime());
|
||||
assert.equal('just now', fromNow(new Date('May 08 2020 11:59:30')));
|
||||
assert.equal('a minute ago', fromNow(new Date('May 08 2020 11:59:00')));
|
||||
assert.equal('5 minutes ago', fromNow(new Date('May 08 2020 11:55:00')));
|
||||
assert.equal('an hour ago', fromNow(new Date('May 08 2020 11:00:00')));
|
||||
assert.equal('3 hours ago', fromNow(new Date('May 08 2020 9:00:00')));
|
||||
assert.equal('a day ago', fromNow(new Date('May 07 2020 12:00:00')));
|
||||
assert.equal('3 days ago', fromNow(new Date('May 05 2020 12:00:00')));
|
||||
assert.equal('a month ago', fromNow(new Date('Apr 05 2020 12:00:00')));
|
||||
assert.equal('2 months ago', fromNow(new Date('Mar 05 2020 12:00:00')));
|
||||
assert.equal('a year ago', fromNow(new Date('May 05 2019 12:00:00')));
|
||||
assert.equal('10 years ago', fromNow(new Date('May 05 2010 12:00:00')));
|
||||
});
|
||||
});
|
||||
|
||||
suite('isWithinDay', () => {
|
||||
test('basics works', () => {
|
||||
assert.isTrue(isWithinDay(new Date('May 08 2020 12:00:00'),
|
||||
new Date('May 08 2020 02:00:00')));
|
||||
assert.isFalse(isWithinDay(new Date('May 08 2020 12:00:00'),
|
||||
new Date('May 07 2020 12:00:00')));
|
||||
});
|
||||
});
|
||||
|
||||
suite('isWithinHalfYear', () => {
|
||||
test('basics works', () => {
|
||||
assert.isTrue(isWithinHalfYear(new Date('May 08 2020 12:00:00'),
|
||||
new Date('Feb 08 2020 12:00:00')));
|
||||
assert.isFalse(isWithinHalfYear(new Date('May 08 2020 12:00:00'),
|
||||
new Date('Nov 07 2019 12:00:00')));
|
||||
});
|
||||
});
|
||||
|
||||
suite('formatDate', () => {
|
||||
test('works for standard format', () => {
|
||||
const stdFormat = 'MMM DD, YYYY';
|
||||
assert.equal('May 08, 2020',
|
||||
formatDate(new Date('May 08 2020 12:00:00'), stdFormat));
|
||||
assert.equal('Feb 28, 2020',
|
||||
formatDate(new Date('Feb 28 2020 12:00:00'), stdFormat));
|
||||
|
||||
const time24Format = 'HH:mm:ss';
|
||||
assert.equal('Feb 28, 2020 12:01:12',
|
||||
formatDate(new Date('Feb 28 2020 12:01:12'), stdFormat + ' '
|
||||
+ time24Format));
|
||||
});
|
||||
test('works for euro format', () => {
|
||||
const euroFormat = 'DD.MM.YYYY';
|
||||
assert.equal('01.12.2019',
|
||||
formatDate(new Date('Dec 01 2019 12:00:00'), euroFormat));
|
||||
assert.equal('20.01.2002',
|
||||
formatDate(new Date('Jan 20 2002 12:00:00'), euroFormat));
|
||||
|
||||
const time24Format = 'HH:mm:ss';
|
||||
assert.equal('28.02.2020 00:01:12',
|
||||
formatDate(new Date('Feb 28 2020 00:01:12'), euroFormat + ' '
|
||||
+ time24Format));
|
||||
});
|
||||
test('works for iso format', () => {
|
||||
const isoFormat = 'YYYY-MM-DD';
|
||||
assert.equal('2015-01-01',
|
||||
formatDate(new Date('Jan 01 2015 12:00:00'), isoFormat));
|
||||
assert.equal('2013-07-03',
|
||||
formatDate(new Date('Jul 03 2013 12:00:00'), isoFormat));
|
||||
|
||||
const timeFormat = 'h:mm:ss A';
|
||||
assert.equal('2013-07-03 5:00:00 AM',
|
||||
formatDate(new Date('Jul 03 2013 05:00:00'), isoFormat + ' '
|
||||
+ timeFormat));
|
||||
assert.equal('2013-07-03 5:00:00 PM',
|
||||
formatDate(new Date('Jul 03 2013 17:00:00'), isoFormat + ' '
|
||||
+ timeFormat));
|
||||
});
|
||||
});
|
||||
});
|
@@ -338,11 +338,6 @@ isarray@0.0.1:
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
|
||||
integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
|
||||
|
||||
moment@^2.24.0:
|
||||
version "2.24.0"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
|
||||
integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
|
||||
|
||||
page@^1.11.5:
|
||||
version "1.11.5"
|
||||
resolved "https://registry.yarnpkg.com/page/-/page-1.11.5.tgz#0cfc8608be337f26f4377f31df0787aef0ca1af7"
|
||||
|
Reference in New Issue
Block a user