summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/lib/utils/datetime/date_format_utility.js
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/lib/utils/datetime/date_format_utility.js')
-rw-r--r--app/assets/javascripts/lib/utils/datetime/date_format_utility.js260
1 files changed, 260 insertions, 0 deletions
diff --git a/app/assets/javascripts/lib/utils/datetime/date_format_utility.js b/app/assets/javascripts/lib/utils/datetime/date_format_utility.js
new file mode 100644
index 00000000000..246f290a90a
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/datetime/date_format_utility.js
@@ -0,0 +1,260 @@
+import dateFormat from 'dateformat';
+import { isString, mapValues, reduce } from 'lodash';
+import { s__, n__, __ } from '../../../locale';
+
+/**
+ * Returns i18n month names array.
+ * If `abbreviated` is provided, returns abbreviated
+ * name.
+ *
+ * @param {Boolean} abbreviated
+ */
+export const getMonthNames = (abbreviated) => {
+ if (abbreviated) {
+ return [
+ s__('Jan'),
+ s__('Feb'),
+ s__('Mar'),
+ s__('Apr'),
+ s__('May'),
+ s__('Jun'),
+ s__('Jul'),
+ s__('Aug'),
+ s__('Sep'),
+ s__('Oct'),
+ s__('Nov'),
+ s__('Dec'),
+ ];
+ }
+ return [
+ s__('January'),
+ s__('February'),
+ s__('March'),
+ s__('April'),
+ s__('May'),
+ s__('June'),
+ s__('July'),
+ s__('August'),
+ s__('September'),
+ s__('October'),
+ s__('November'),
+ s__('December'),
+ ];
+};
+
+/**
+ * Returns month name based on provided date.
+ *
+ * @param {Date} date
+ * @param {Boolean} abbreviated
+ */
+export const monthInWords = (date, abbreviated = false) => {
+ if (!date) {
+ return '';
+ }
+
+ return getMonthNames(abbreviated)[date.getMonth()];
+};
+
+export const dateInWords = (date, abbreviated = false, hideYear = false) => {
+ if (!date) return date;
+
+ const month = date.getMonth();
+ const year = date.getFullYear();
+
+ const monthName = getMonthNames(abbreviated)[month];
+
+ if (hideYear) {
+ return `${monthName} ${date.getDate()}`;
+ }
+
+ return `${monthName} ${date.getDate()}, ${year}`;
+};
+
+/**
+ * Similar to `timeIntervalInWords`, but rounds the return value
+ * to 1/10th of the largest time unit. For example:
+ *
+ * 30 => 30 seconds
+ * 90 => 1.5 minutes
+ * 7200 => 2 hours
+ * 86400 => 1 day
+ * ... etc.
+ *
+ * The largest supported unit is "days".
+ *
+ * @param {Number} intervalInSeconds The time interval in seconds
+ * @returns {String} A humanized description of the time interval
+ */
+export const humanizeTimeInterval = (intervalInSeconds) => {
+ if (intervalInSeconds < 60 /* = 1 minute */) {
+ const seconds = Math.round(intervalInSeconds * 10) / 10;
+ return n__('%d second', '%d seconds', seconds);
+ } else if (intervalInSeconds < 3600 /* = 1 hour */) {
+ const minutes = Math.round(intervalInSeconds / 6) / 10;
+ return n__('%d minute', '%d minutes', minutes);
+ } else if (intervalInSeconds < 86400 /* = 1 day */) {
+ const hours = Math.round(intervalInSeconds / 360) / 10;
+ return n__('%d hour', '%d hours', hours);
+ }
+
+ const days = Math.round(intervalInSeconds / 8640) / 10;
+ return n__('%d day', '%d days', days);
+};
+
+/**
+ * Returns i18n weekday names array.
+ */
+export const getWeekdayNames = () => [
+ __('Sunday'),
+ __('Monday'),
+ __('Tuesday'),
+ __('Wednesday'),
+ __('Thursday'),
+ __('Friday'),
+ __('Saturday'),
+];
+
+/**
+ * Given a date object returns the day of the week in English
+ * @param {date} date
+ * @returns {String}
+ */
+export const getDayName = (date) => getWeekdayNames()[date.getDay()];
+
+/**
+ * Returns the i18n month name from a given date
+ * @example
+ * formatDateAsMonth(new Date('2020-06-28')) -> 'Jun'
+ * @param {String} datetime where month is extracted from
+ * @param {Object} options
+ * @param {Boolean} options.abbreviated whether to use the abbreviated month string, or not
+ * @return {String} the i18n month name
+ */
+export function formatDateAsMonth(datetime, options = {}) {
+ const { abbreviated = true } = options;
+ const month = new Date(datetime).getMonth();
+ return getMonthNames(abbreviated)[month];
+}
+
+/**
+ * @example
+ * dateFormat('2017-12-05','mmm d, yyyy h:MMtt Z' ) -> "Dec 5, 2017 12:00am UTC"
+ * @param {date} datetime
+ * @param {String} format
+ * @param {Boolean} UTC convert local time to UTC
+ * @returns {String}
+ */
+export const formatDate = (datetime, format = 'mmm d, yyyy h:MMtt Z', utc = false) => {
+ if (isString(datetime) && datetime.match(/\d+-\d+\d+ /)) {
+ throw new Error(__('Invalid date'));
+ }
+ return dateFormat(datetime, format, utc);
+};
+
+/**
+ * Formats milliseconds as timestamp (e.g. 01:02:03).
+ * This takes durations longer than a day into account (e.g. two days would be 48:00:00).
+ *
+ * @param milliseconds
+ * @returns {string}
+ */
+export const formatTime = (milliseconds) => {
+ const remainingSeconds = Math.floor(milliseconds / 1000) % 60;
+ const remainingMinutes = Math.floor(milliseconds / 1000 / 60) % 60;
+ const remainingHours = Math.floor(milliseconds / 1000 / 60 / 60);
+ let formattedTime = '';
+ if (remainingHours < 10) formattedTime += '0';
+ formattedTime += `${remainingHours}:`;
+ if (remainingMinutes < 10) formattedTime += '0';
+ formattedTime += `${remainingMinutes}:`;
+ if (remainingSeconds < 10) formattedTime += '0';
+ formattedTime += remainingSeconds;
+ return formattedTime;
+};
+
+/**
+ * Port of ruby helper time_interval_in_words.
+ *
+ * @param {Number} seconds
+ * @return {String}
+ */
+export const timeIntervalInWords = (intervalInSeconds) => {
+ const secondsInteger = parseInt(intervalInSeconds, 10);
+ const minutes = Math.floor(secondsInteger / 60);
+ const seconds = secondsInteger - minutes * 60;
+ const secondsText = n__('%d second', '%d seconds', seconds);
+ return minutes >= 1
+ ? [n__('%d minute', '%d minutes', minutes), secondsText].join(' ')
+ : secondsText;
+};
+
+/**
+ * Accepts a timeObject (see parseSeconds) and returns a condensed string representation of it
+ * (e.g. '1w 2d 3h 1m' or '1h 30m'). Zero value units are not included.
+ * If the 'fullNameFormat' param is passed it returns a non condensed string eg '1 week 3 days'
+ */
+export const stringifyTime = (timeObject, fullNameFormat = false) => {
+ const reducedTime = reduce(
+ timeObject,
+ (memo, unitValue, unitName) => {
+ const isNonZero = Boolean(unitValue);
+
+ if (fullNameFormat && isNonZero) {
+ // Remove traling 's' if unit value is singular
+ const formattedUnitName = unitValue > 1 ? unitName : unitName.replace(/s$/, '');
+ return `${memo} ${unitValue} ${formattedUnitName}`;
+ }
+
+ return isNonZero ? `${memo} ${unitValue}${unitName.charAt(0)}` : memo;
+ },
+ '',
+ ).trim();
+ return reducedTime.length ? reducedTime : '0m';
+};
+
+/**
+ * Accepts seconds and returns a timeObject { weeks: #, days: #, hours: #, minutes: # }
+ * Seconds can be negative or positive, zero or non-zero. Can be configured for any day
+ * or week length.
+ */
+export const parseSeconds = (
+ seconds,
+ { daysPerWeek = 5, hoursPerDay = 8, limitToHours = false, limitToDays = false } = {},
+) => {
+ const DAYS_PER_WEEK = daysPerWeek;
+ const HOURS_PER_DAY = hoursPerDay;
+ const SECONDS_PER_MINUTE = 60;
+ const MINUTES_PER_HOUR = 60;
+ const MINUTES_PER_WEEK = DAYS_PER_WEEK * HOURS_PER_DAY * MINUTES_PER_HOUR;
+ const MINUTES_PER_DAY = HOURS_PER_DAY * MINUTES_PER_HOUR;
+
+ const timePeriodConstraints = {
+ weeks: MINUTES_PER_WEEK,
+ days: MINUTES_PER_DAY,
+ hours: MINUTES_PER_HOUR,
+ minutes: 1,
+ };
+
+ if (limitToDays || limitToHours) {
+ timePeriodConstraints.weeks = 0;
+ }
+
+ if (limitToHours) {
+ timePeriodConstraints.days = 0;
+ }
+
+ let unorderedMinutes = Math.abs(seconds / SECONDS_PER_MINUTE);
+
+ return mapValues(timePeriodConstraints, (minutesPerPeriod) => {
+ if (minutesPerPeriod === 0) {
+ return 0;
+ }
+
+ const periodCount = Math.floor(unorderedMinutes / minutesPerPeriod);
+
+ unorderedMinutes -= periodCount * minutesPerPeriod;
+
+ return periodCount;
+ });
+};