Commit 9daeccfb authored by Tim Zallmann's avatar Tim Zallmann

Merge branch '38869-datetime' into 'master'

Export date utility functions as ES6 modules

See merge request gitlab-org/gitlab-ce!15782
parents 54c2d7bd 114f93cc
......@@ -2,6 +2,7 @@
/* global Pager */
import Cookies from 'js-cookie';
import { localTimeAgo } from './lib/utils/datetime_utility';
class Activities {
constructor() {
......@@ -15,7 +16,7 @@ class Activities {
}
updateTooltips() {
gl.utils.localTimeAgo($('.js-timeago', '.content_list'));
localTimeAgo($('.js-timeago', '.content_list'));
}
reloadActivities() {
......
......@@ -4,6 +4,7 @@
/* global Pager */
import { pluralize } from './lib/utils/text_utility';
import { localTimeAgo } from './lib/utils/datetime_utility';
export default (function () {
const CommitsList = {};
......@@ -91,7 +92,7 @@ export default (function () {
$commitsHeadersLast.find('span.commits-count').text(`${commitsCount} ${pluralize('commit', commitsCount)}`);
}
gl.utils.localTimeAgo($processedData.find('.js-timeago'));
localTimeAgo($processedData.find('.js-timeago'));
return processedData;
};
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, no-var, object-shorthand, consistent-return, no-unused-vars, comma-dangle, vars-on-top, prefer-template, max-len */
import { localTimeAgo } from './lib/utils/datetime_utility';
export default class Compare {
constructor(opts) {
......@@ -81,7 +82,7 @@ export default class Compare {
loading.hide();
$target.html(html);
var className = '.' + $target[0].className.replace(' ', '.');
gl.utils.localTimeAgo($('.js-timeago', className));
localTimeAgo($('.js-timeago', className));
}
});
}
......
<script>
import actionBtn from './action_btn.vue';
import { getTimeago } from '../../lib/utils/datetime_utility';
export default {
props: {
......@@ -21,7 +22,7 @@
},
computed: {
timeagoDate() {
return gl.utils.getTimeago().format(this.deployKey.created_at);
return getTimeago().format(this.deployKey.created_at);
},
editDeployKeyPath() {
return `${this.endpoint}/${this.deployKey.id}/edit`;
......
......@@ -2,6 +2,7 @@
/* global NoteModel */
import Vue from 'vue';
import { localTimeAgo } from '../../lib/utils/datetime_utility';
class DiscussionModel {
constructor (discussionId) {
......@@ -71,7 +72,7 @@ class DiscussionModel {
$(`${discussionSelector} .discussion-header`).append(data.discussion_headline_html);
}
gl.utils.localTimeAgo($('.js-timeago', `${discussionSelector}`));
localTimeAgo($('.js-timeago', `${discussionSelector}`));
} else {
$discussionHeadline.remove();
}
......
......@@ -3,6 +3,7 @@ import { visitUrl } from './lib/utils/url_utility';
import bp from './breakpoints';
import { bytesToKiB } from './lib/utils/number_utils';
import { setCiStatusFavicon } from './lib/utils/common_utils';
import { timeFor } from './lib/utils/datetime_utility';
export default class Job {
constructor(options) {
......@@ -261,7 +262,7 @@ export default class Job {
if ($date.length) {
const date = $date.text();
return $date.text(
gl.utils.timeFor(new Date(date.replace(/([0-9]+)-([0-9]+)-([0-9]+)/g, '$1/$2/$3')), ' '),
timeFor(new Date(date.replace(/([0-9]+)-([0-9]+)-([0-9]+)/g, '$1/$2/$3'))),
);
}
}
......
class Cache {
export default class Cache {
constructor() {
this.internalStorage = { };
}
......@@ -15,5 +15,3 @@ class Cache {
delete this.internalStorage[key];
}
}
export default Cache;
/* eslint-disable import/prefer-default-export */
export const BYTES_IN_KIB = 1024;
export const HIDDEN_CLASS = 'hidden';
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, comma-dangle, no-unused-expressions, prefer-template, max-len */
import timeago from 'timeago.js';
import dateFormat from 'vendor/date.format';
import { pluralize } from './text_utility';
import {
lang,
s__,
......@@ -12,121 +9,125 @@ import {
window.timeago = timeago;
window.dateFormat = dateFormat;
(function() {
(function(w) {
var base;
var timeagoInstance;
/**
* Given a date object returns the day of the week in English
* @param {date} date
* @returns {String}
*/
export const getDayName = date => ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][date.getDay()];
if (w.gl == null) {
w.gl = {};
}
if ((base = w.gl).utils == null) {
base.utils = {};
}
w.gl.utils.days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
/**
* @example
* dateFormat('2017-12-05','mmm d, yyyy h:MMtt Z' ) -> "Dec 5, 2017 12:00am GMT+0000"
* @param {date} datetime
* @returns {String}
*/
export const formatDate = datetime => dateFormat(datetime, 'mmm d, yyyy h:MMtt Z');
w.gl.utils.formatDate = function(datetime) {
return dateFormat(datetime, 'mmm d, yyyy h:MMtt Z');
let timeagoInstance;
/**
* Sets a timeago Instance
*/
export function getTimeago() {
if (!timeagoInstance) {
const localeRemaining = function getLocaleRemaining(number, index) {
return [
[s__('Timeago|less than a minute ago'), s__('Timeago|in a while')],
[s__('Timeago|less than a minute ago'), s__('Timeago|%s seconds remaining')],
[s__('Timeago|about a minute ago'), s__('Timeago|1 minute remaining')],
[s__('Timeago|%s minutes ago'), s__('Timeago|%s minutes remaining')],
[s__('Timeago|about an hour ago'), s__('Timeago|1 hour remaining')],
[s__('Timeago|about %s hours ago'), s__('Timeago|%s hours remaining')],
[s__('Timeago|a day ago'), s__('Timeago|1 day remaining')],
[s__('Timeago|%s days ago'), s__('Timeago|%s days remaining')],
[s__('Timeago|a week ago'), s__('Timeago|1 week remaining')],
[s__('Timeago|%s weeks ago'), s__('Timeago|%s weeks remaining')],
[s__('Timeago|a month ago'), s__('Timeago|1 month remaining')],
[s__('Timeago|%s months ago'), s__('Timeago|%s months remaining')],
[s__('Timeago|a year ago'), s__('Timeago|1 year remaining')],
[s__('Timeago|%s years ago'), s__('Timeago|%s years remaining')],
][index];
};
w.gl.utils.getDayName = function(date) {
return this.days[date.getDay()];
const locale = function getLocale(number, index) {
return [
[s__('Timeago|less than a minute ago'), s__('Timeago|in a while')],
[s__('Timeago|less than a minute ago'), s__('Timeago|in %s seconds')],
[s__('Timeago|about a minute ago'), s__('Timeago|in 1 minute')],
[s__('Timeago|%s minutes ago'), s__('Timeago|in %s minutes')],
[s__('Timeago|about an hour ago'), s__('Timeago|in 1 hour')],
[s__('Timeago|about %s hours ago'), s__('Timeago|in %s hours')],
[s__('Timeago|a day ago'), s__('Timeago|in 1 day')],
[s__('Timeago|%s days ago'), s__('Timeago|in %s days')],
[s__('Timeago|a week ago'), s__('Timeago|in 1 week')],
[s__('Timeago|%s weeks ago'), s__('Timeago|in %s weeks')],
[s__('Timeago|a month ago'), s__('Timeago|in 1 month')],
[s__('Timeago|%s months ago'), s__('Timeago|in %s months')],
[s__('Timeago|a year ago'), s__('Timeago|in 1 year')],
[s__('Timeago|%s years ago'), s__('Timeago|in %s years')],
][index];
};
w.gl.utils.localTimeAgo = function($timeagoEls, setTimeago = true) {
$timeagoEls.each((i, el) => {
if (setTimeago) {
// Recreate with custom template
$(el).tooltip({
template: '<div class="tooltip local-timeago" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
});
}
timeago.register(lang, locale);
timeago.register(`${lang}-remaining`, localeRemaining);
timeagoInstance = timeago();
}
el.classList.add('js-timeago-render');
});
return timeagoInstance;
}
gl.utils.renderTimeago($timeagoEls);
};
/**
* For the given element, renders a timeago instance.
* @param {jQuery} $els
*/
export const renderTimeago = ($els) => {
const timeagoEls = $els || document.querySelectorAll('.js-timeago-render');
w.gl.utils.getTimeago = function() {
var locale;
if (!timeagoInstance) {
const localeRemaining = function(number, index) {
return [
[s__('Timeago|less than a minute ago'), s__('Timeago|in a while')],
[s__('Timeago|less than a minute ago'), s__('Timeago|%s seconds remaining')],
[s__('Timeago|about a minute ago'), s__('Timeago|1 minute remaining')],
[s__('Timeago|%s minutes ago'), s__('Timeago|%s minutes remaining')],
[s__('Timeago|about an hour ago'), s__('Timeago|1 hour remaining')],
[s__('Timeago|about %s hours ago'), s__('Timeago|%s hours remaining')],
[s__('Timeago|a day ago'), s__('Timeago|1 day remaining')],
[s__('Timeago|%s days ago'), s__('Timeago|%s days remaining')],
[s__('Timeago|a week ago'), s__('Timeago|1 week remaining')],
[s__('Timeago|%s weeks ago'), s__('Timeago|%s weeks remaining')],
[s__('Timeago|a month ago'), s__('Timeago|1 month remaining')],
[s__('Timeago|%s months ago'), s__('Timeago|%s months remaining')],
[s__('Timeago|a year ago'), s__('Timeago|1 year remaining')],
[s__('Timeago|%s years ago'), s__('Timeago|%s years remaining')]
][index];
};
locale = function(number, index) {
return [
[s__('Timeago|less than a minute ago'), s__('Timeago|in a while')],
[s__('Timeago|less than a minute ago'), s__('Timeago|in %s seconds')],
[s__('Timeago|about a minute ago'), s__('Timeago|in 1 minute')],
[s__('Timeago|%s minutes ago'), s__('Timeago|in %s minutes')],
[s__('Timeago|about an hour ago'), s__('Timeago|in 1 hour')],
[s__('Timeago|about %s hours ago'), s__('Timeago|in %s hours')],
[s__('Timeago|a day ago'), s__('Timeago|in 1 day')],
[s__('Timeago|%s days ago'), s__('Timeago|in %s days')],
[s__('Timeago|a week ago'), s__('Timeago|in 1 week')],
[s__('Timeago|%s weeks ago'), s__('Timeago|in %s weeks')],
[s__('Timeago|a month ago'), s__('Timeago|in 1 month')],
[s__('Timeago|%s months ago'), s__('Timeago|in %s months')],
[s__('Timeago|a year ago'), s__('Timeago|in 1 year')],
[s__('Timeago|%s years ago'), s__('Timeago|in %s years')]
][index];
};
timeago.register(lang, locale);
timeago.register(`${lang}-remaining`, localeRemaining);
timeagoInstance = timeago();
}
return timeagoInstance;
};
// timeago.js sets timeouts internally for each timeago value to be updated in real time
getTimeago().render(timeagoEls, lang);
};
w.gl.utils.timeFor = function(time, suffix, expiredLabel) {
var timefor;
if (!time) {
return '';
}
if (new Date(time) < new Date()) {
expiredLabel || (expiredLabel = s__('Timeago|Past due'));
timefor = expiredLabel;
} else {
timefor = gl.utils.getTimeago().format(time, `${lang}-remaining`).trim();
}
return timefor;
};
/**
* For the given elements, sets a tooltip with a formatted date.
* @param {jQuery}
* @param {Boolean} setTimeago
*/
export const localTimeAgo = ($timeagoEls, setTimeago = true) => {
$timeagoEls.each((i, el) => {
if (setTimeago) {
// Recreate with custom template
$(el).tooltip({
template: '<div class="tooltip local-timeago" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',
});
}
w.gl.utils.renderTimeago = function($els) {
const timeagoEls = $els || document.querySelectorAll('.js-timeago-render');
el.classList.add('js-timeago-render');
});
// timeago.js sets timeouts internally for each timeago value to be updated in real time
gl.utils.getTimeago().render(timeagoEls, lang);
};
renderTimeago($timeagoEls);
};
w.gl.utils.getDayDifference = function(a, b) {
var millisecondsPerDay = 1000 * 60 * 60 * 24;
var date1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
var date2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
/**
* Returns remaining or passed time over the given time.
* @param {*} time
* @param {*} expiredLabel
*/
export const timeFor = (time, expiredLabel) => {
if (!time) {
return '';
}
if (new Date(time) < new Date()) {
return expiredLabel || s__('Timeago|Past due');
}
return getTimeago().format(time, `${lang}-remaining`).trim();
};
return Math.floor((date2 - date1) / millisecondsPerDay);
};
})(window);
}).call(window);
export const getDayDifference = (a, b) => {
const millisecondsPerDay = 1000 * 60 * 60 * 24;
const date1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
const date2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
return Math.floor((date2 - date1) / millisecondsPerDay);
};
/**
* Port of ruby helper time_interval_in_words.
......@@ -162,3 +163,10 @@ export function dateInWords(date, abbreviated = false) {
return `${monthName} ${date.getDate()}, ${year}`;
}
window.gl = window.gl || {};
window.gl.utils = {
...(window.gl.utils || {}),
getTimeago,
localTimeAgo,
};
......@@ -28,7 +28,7 @@ import './commit/image_file';
// lib/utils
import { handleLocationHash } from './lib/utils/common_utils';
import './lib/utils/datetime_utility';
import { localTimeAgo, renderTimeago } from './lib/utils/datetime_utility';
import { getLocationHash, visitUrl } from './lib/utils/url_utility';
// behaviors
......@@ -186,7 +186,7 @@ $(function () {
return $(this).parents('form').submit();
// Form submitter
});
gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), true);
localTimeAgo($('abbr.timeago, .js-timeago'), true);
// Disable form buttons while a form is submitting
$body.on('ajax:complete, ajax:beforeSend, submit', 'form', function (e) {
var buttons;
......@@ -275,7 +275,7 @@ $(function () {
loadAwardsHandler();
new Aside();
gl.utils.renderTimeago();
renderTimeago();
$(document).trigger('init.scrolling-tabs');
......
......@@ -14,6 +14,7 @@ import {
import { getLocationHash } from './lib/utils/url_utility';
import initDiscussionTab from './image_diff/init_discussion_tab';
import Diff from './diff';
import { localTimeAgo } from './lib/utils/datetime_utility';
import syntaxHighlight from './syntax_highlight';
/* eslint-disable max-len */
......@@ -248,7 +249,7 @@ import syntaxHighlight from './syntax_highlight';
url: `${source}.json`,
success: (data) => {
document.querySelector('div#commits').innerHTML = data.html;
gl.utils.localTimeAgo($('.js-timeago', 'div#commits'));
localTimeAgo($('.js-timeago', 'div#commits'));
this.commitsLoaded = true;
this.scrollToElement('#commits');
},
......@@ -295,7 +296,7 @@ import syntaxHighlight from './syntax_highlight';
gl.diffNotesCompileComponents();
}
gl.utils.localTimeAgo($('.js-timeago', 'div#diffs'));
localTimeAgo($('.js-timeago', 'div#diffs'));
syntaxHighlight($('#diffs .js-syntax-highlight'));
if (this.diffViewType() === 'parallel' && this.isDiffAction(this.currentAction)) {
......
......@@ -2,6 +2,7 @@
/* global Issuable */
/* global ListMilestone */
import _ from 'underscore';
import { timeFor } from './lib/utils/datetime_utility';
(function() {
this.MilestoneSelect = (function() {
......@@ -216,7 +217,7 @@ import _ from 'underscore';
$value.css('display', '');
if (data.milestone != null) {
data.milestone.full_path = _this.currentProject.full_path;
data.milestone.remaining = gl.utils.timeFor(data.milestone.due_date);
data.milestone.remaining = timeFor(data.milestone.due_date);
data.milestone.name = data.milestone.title;
$value.html(milestoneLinkTemplate(data.milestone));
return $sidebarCollapsedValue.find('span').html(collapsedSidebarLabelTemplate(data.milestone));
......
......@@ -25,6 +25,7 @@ import Autosave from './autosave';
import TaskList from './task_list';
import { ajaxPost, isInViewport, getPagePath, scrollToElement, isMetaKey } from './lib/utils/common_utils';
import imageDiffHelper from './image_diff/helpers/index';
import { localTimeAgo } from './lib/utils/datetime_utility';
window.autosize = Autosize;
......@@ -311,7 +312,7 @@ export default class Notes {
setupNewNote($note) {
// Update datetime format on the recent note
gl.utils.localTimeAgo($note.find('.js-timeago'), false);
localTimeAgo($note.find('.js-timeago'), false);
this.collapseLongCommitList();
this.taskList.init();
......@@ -463,7 +464,7 @@ export default class Notes {
this.renderDiscussionAvatar(diffAvatarContainer, noteEntity);
}
gl.utils.localTimeAgo($('.js-timeago'), false);
localTimeAgo($('.js-timeago'), false);
Notes.checkMergeRequestStatus();
return this.updateNotesCount(1);
}
......
import _ from 'underscore';
import d3 from 'd3';
import { getDayName, getDayDifference } from '../lib/utils/datetime_utility';
const LOADING_HTML = `
<div class="text-center">
......@@ -17,7 +18,7 @@ function getSystemDate(systemUtcOffsetSeconds) {
function formatTooltipText({ date, count }) {
const dateObject = new Date(date);
const dateDayName = gl.utils.getDayName(dateObject);
const dateDayName = getDayName(dateObject);
const dateText = dateObject.format('mmm d, yyyy');
let contribText = 'No contributions';
......@@ -51,7 +52,7 @@ export default class ActivityCalendar {
const oneYearAgo = new Date(today);
oneYearAgo.setFullYear(today.getFullYear() - 1);
const days = gl.utils.getDayDifference(oneYearAgo, today);
const days = getDayDifference(oneYearAgo, today);
for (let i = 0; i <= days; i += 1) {
const date = new Date(oneYearAgo);
......
import ActivityCalendar from './activity_calendar';
import { localTimeAgo } from '../lib/utils/datetime_utility';
/**
* UserTabs
......@@ -138,7 +139,7 @@ export default class UserTabs {
const tabSelector = `div#${action}`;
this.$parentEl.find(tabSelector).html(data.html);
this.loaded[action] = true;
gl.utils.localTimeAgo($('.js-timeago', tabSelector));
localTimeAgo($('.js-timeago', tabSelector));
},
});
}
......
import '~/lib/utils/datetime_utility';
import { getTimeago } from '~/lib/utils/datetime_utility';
import { visitUrl } from '../../lib/utils/url_utility';
import Flash from '../../flash';
import MemoryUsage from './mr_widget_memory_usage';
......@@ -17,7 +17,7 @@ export default {
},
methods: {
formatDate(date) {
return gl.utils.getTimeago().format(date);
return getTimeago().format(date);
},
hasExternalUrls(deployment = {}) {
return deployment.external_url && deployment.external_url_formatted;
......
import Timeago from 'timeago.js';
import { getStateKey } from '../dependencies';
import { formatDate } from '../../lib/utils/datetime_utility';
export default class MergeRequestStore {
......@@ -122,7 +123,7 @@ export default class MergeRequestStore {
static getEventObject(event) {
return {
author: MergeRequestStore.getAuthorObject(event),
updatedAt: gl.utils.formatDate(MergeRequestStore.getEventUpdatedAtDate(event)),
updatedAt: formatDate(MergeRequestStore.getEventUpdatedAtDate(event)),
formattedUpdatedAt: MergeRequestStore.getEventDate(event),
};
}
......
import { getTimeago } from '../../lib/utils/datetime_utility';
export default {
name: 'MemoryGraph',
props: {
......@@ -16,7 +18,7 @@ export default {
},
computed: {
getFormattedMedian() {
const deployedSince = gl.utils.getTimeago().format(this.deploymentTime * 1000);
const deployedSince = getTimeago().format(this.deploymentTime * 1000);
return `Deployed ${deployedSince}`;
},
},
......
import '../../lib/utils/datetime_utility';
import { formatDate, getTimeago } from '../../lib/utils/datetime_utility';
/**
* Mixin with time ago methods used in some vue components
......@@ -6,13 +6,13 @@ import '../../lib/utils/datetime_utility';
export default {
methods: {
timeFormated(time) {
const timeago = gl.utils.getTimeago();
const timeago = getTimeago();
return timeago.format(time);
},
tooltipTitle(time) {
return gl.utils.formatDate(time);
return formatDate(time);
},
},
};
import * as datetimeUtility from '~/lib/utils/datetime_utility';
(() => {
describe('Date time utils', () => {
describe('timeFor', () => {
it('returns `past due` when in past', () => {
const date = new Date();
date.setFullYear(date.getFullYear() - 1);
expect(
gl.utils.timeFor(date),
).toBe('Past due');
});
it('returns remaining time when in the future', () => {
const date = new Date();
date.setFullYear(date.getFullYear() + 1);
// Add a day to prevent a transient error. If date is even 1 second
// short of a full year, timeFor will return '11 months remaining'
date.setDate(date.getDate() + 1);
expect(
gl.utils.timeFor(date),
).toBe('1 year remaining');
});
describe('Date time utils', () => {
describe('timeFor', () => {
it('returns `past due` when in past', () => {
const date = new Date();
date.setFullYear(date.getFullYear() - 1);
expect(
datetimeUtility.timeFor(date),
).toBe('Past due');
});
describe('get day name', () => {
it('should return Sunday', () => {
const day = gl.utils.getDayName(new Date('07/17/2016'));
expect(day).toBe('Sunday');
});
it('should return Monday', () => {
const day = gl.utils.getDayName(new Date('07/18/2016'));
expect(day).toBe('Monday');
});
it('should return Tuesday', () => {
const day = gl.utils.getDayName(new Date('07/19/2016'));
expect(day).toBe('Tuesday');
});
it('should return Wednesday', () => {
const day = gl.utils.getDayName(new Date('07/20/2016'));
expect(day).toBe('Wednesday');
});
it('should return Thursday', () => {
const day = gl.utils.getDayName(new Date('07/21/2016'));
expect(day).toBe('Thursday');
});
it('should return Friday', () => {
const day = gl.utils.getDayName(new Date('07/22/2016'));
expect(day).toBe('Friday');
});
it('should return Saturday', () => {
const day = gl.utils.getDayName(new Date('07/23/2016'));
expect(day).toBe('Saturday');
});
});
it('returns remaining time when in the future', () => {
const date = new Date();
date.setFullYear(date.getFullYear() + 1);
// Add a day to prevent a transient error. If date is even 1 second
// short of a full year, timeFor will return '11 months remaining'
date.setDate(date.getDate() + 1);
describe('get day difference', () => {
it('should return 7', () => {
const firstDay = new Date('07/01/2016');
const secondDay = new Date('07/08/2016');
const difference = gl.utils.getDayDifference(firstDay, secondDay);
expect(difference).toBe(7);
});
it('should return 31', () => {
const firstDay = new Date('07/01/2016');
const secondDay = new Date('08/01/2016');
const difference = gl.utils.getDayDifference(firstDay, secondDay);
expect(difference).toBe(31);
});
it('should return 365', () => {
const firstDay = new Date('07/02/2015');
const secondDay = new Date('07/01/2016');
const difference = gl.utils.getDayDifference(firstDay, secondDay);
expect(difference).toBe(365);
});
expect(
datetimeUtility.timeFor(date),
).toBe('1 year remaining');
});
});
describe('timeIntervalInWords', () => {