box-ui-elements-mlh
Version:
256 lines (229 loc) • 8.7 kB
Flow
/**
* @flow
* @file Date and time utilities
* @author Box
*/
import isNaN from 'lodash/isNaN';
const MILLISECONDS_PER_SECOND = 1000;
// 24 hours * 60 minutes * 60 seconds * 1000 milliseconds
const MILLISECONDS_PER_DAY = 24 * 60 * 60 * MILLISECONDS_PER_SECOND;
// 60 sec * 1000
const MILLISECONDS_PER_MINUTE = 60 * MILLISECONDS_PER_SECOND;
/**
* RegExp matcher for acceptable ISO 8601 date formats w/ timezone (see below)
* Capture groups structured as follows:
* 1) the date/time portion (2018-06-13T00:00:00.000)
* 2) the milliseconds (if matched)
* 3) the timezone portion (e.g., Z, +03, -0400, +05:00)
* 4) the Z format for timezone (if matched)
* 5) the short format for timezone (if matched)
* 6) the colon-less format for timezone (if matched)
* 7) the colon long format for timezone (if matched)
*/
const RE_ISO8601_DATE = /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,3})?)?((Z$)|(?:[+-](?:([0-2]\d$)|([0-2]\d(?:00|30)$)|([0-2]\d:(?:00|30)$))))$/;
const ISO8601_DATETIME: 1 = 1;
const ISO8601_MILLISECONDS: 2 = 2;
const ISO8601_TIMEZONE: 3 = 3;
const ISO8601_Z_FMT: 4 = 4;
const ISO8601_SHORT_FMT: 5 = 5;
const ISO8601_MEDIUM_FMT: 6 = 6;
const ISO8601_LONG_FMT: 7 = 7;
/**
* Helper to normalize a date value to a date object
* @param dateValue - Date number, string, or object
* @returns {date} the normalized date object
*/
function convertToDate(dateValue: number | string | Date): Date {
return dateValue instanceof Date ? dateValue : new Date(dateValue);
}
/**
* Converts an integer value in seconds to milliseconds.
* @param {number} seconds - The value in seconds
* @returns {number} the value in milliseconds
*/
function convertToMs(seconds: number): number {
return seconds * MILLISECONDS_PER_SECOND;
}
/**
* Checks whether the given date value (in unix milliseconds) is today.
* @param {number|string|Date} dateValue - Date object or integer representing the number of milliseconds since 1/1/1970 UTC
* @returns {boolean} whether the given value is today
*/
function isToday(dateValue: number | string | Date): boolean {
return new Date().toDateString() === convertToDate(dateValue).toDateString();
}
/**
* Checks whether the given date value (in unix milliseconds) is yesterday.
* @param {number|string|Date} dateValue - Date object or integer or representing the number of milliseconds since 1/1/1970 UTC
* @returns {boolean} whether the given value is yesterday
*/
function isYesterday(dateValue: number | string | Date): boolean {
return isToday(convertToDate(dateValue).getTime() + MILLISECONDS_PER_DAY);
}
/**
* Checks whether the given date value (in unix milliseconds) is tomorrow.
* @param {number|string|Date} dateValue - Date object or integer or representing the number of milliseconds since 1/1/1970 UTC
* @returns {boolean} whether the given value is tomorrow
*/
function isTomorrow(dateValue: number | string | Date): boolean {
return isToday(convertToDate(dateValue).getTime() - MILLISECONDS_PER_DAY);
}
/**
* Checks whether the given date value (in unix milliseconds) is in the current month.
* @param {number|string|Date} dateValue - Date object or integer representing the number of milliseconds since 1/1/1970 UTC
* @returns {boolean} whether the given value is in the current month
*/
function isCurrentMonth(dateValue: number | string | Date): boolean {
return new Date().getMonth() === convertToDate(dateValue).getMonth();
}
/**
* Checks whether the given date value (in unix milliseconds) is in the current year.
* @param {number|string|Date} dateValue - Date object or integer representing the number of milliseconds since 1/1/1970 UTC
* @returns {boolean} whether the given value is in the current year
*/
function isCurrentYear(dateValue: number | string | Date): boolean {
return new Date().getFullYear() === convertToDate(dateValue).getFullYear();
}
/**
* Formats a number of seconds as a time string
*
* @param {number} seconds - seconds
* @return {string} a string formatted like 3:57:35
*/
function formatTime(seconds: number): string {
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
const s = Math.floor((seconds % 3600) % 60);
const hour = h > 0 ? `${h.toString()}:` : '';
const sec = s < 10 ? `0${s.toString()}` : s.toString();
let min = m.toString();
if (h > 0 && m < 10) {
min = `0${min}`;
}
return `${hour}${min}:${sec}`;
}
/**
* Adds time to a given dateValue
*
* @param {number|Date} dateValue - date or integer value to add time to
* @param {number} timeToAdd - amount of time to add in ms
* @return {number|Date} the modified date or integer
*/
function addTime(dateValue: number | Date, timeToAdd: number): number | Date {
if (dateValue instanceof Date) {
return new Date(dateValue.getTime() + timeToAdd);
}
return dateValue + timeToAdd;
}
/**
* Will convert
* 2018-06-13T07:00:00.000Z
* to
* 2018-06-13T00:00:00.000Z
*
* This is the opposite of convertISOStringToUTCDate
*
* @param {Date} date
* @return {number}
*/
function convertDateToUnixMidnightTime(date: Date) {
// date is localized to 00:00:00 at system/browser timezone
const utcUnixTimeInMs = date.getTime();
// timezone an integer offset; minutes behind GMT
// we use the browser timezone offset instead of the user's,
// because the datepicker uses the browser to get the "midnight"
// time in the user's timezone with getTime()
const timezoneOffsetInMins = date.getTimezoneOffset();
const timezoneOffsetInMs = timezoneOffsetInMins * MILLISECONDS_PER_MINUTE;
// we need the unix/epoch time for midnight on the date selected
const unixDayMidnightTime = utcUnixTimeInMs - timezoneOffsetInMs;
return unixDayMidnightTime;
}
/**
* Will check to see if a date object is not valid, according to the browser
* JS engine.
*
* @param {Date} date
* @return {boolean} whether the date value passes validation
*/
function isValidDate(date: Date): boolean {
return !isNaN(date.getTime());
}
/**
* Will convert ISO8601-compatible dates (with zone designators)
* 2018-06-13T00:00:00.000-0500
* or
* 2018-06-13T00:00:00.000-05
*
* to
* 2018-06-13T00:00:00.000-05:00
*
* Equivalent formats between the two (e.g., uzing 'Z') will remain unchanged.
* If the date format cannot be converted, it will pass along the existing value
* @param {string} isoString
* @return {string} converted date format, if applicable
*/
function convertISOStringtoRFC3339String(isoString: string): string {
// test that the date format inbound is ISO8601-compatible
if (RE_ISO8601_DATE.test(isoString)) {
// if it is, parse out the timezone part if it's in a longer format
// use the capture groups instead of the split result for the datetime and the time zone
const parseDate = isoString.split(RE_ISO8601_DATE);
let dateTime = parseDate[ISO8601_DATETIME];
const milliseconds = parseDate[ISO8601_MILLISECONDS];
const timeZone = parseDate[ISO8601_TIMEZONE];
// add milliseconds if missing, to standardize output
if (!milliseconds) {
dateTime += '.000';
}
if (parseDate[ISO8601_Z_FMT]) {
return isoString;
}
if (parseDate[ISO8601_SHORT_FMT]) {
return `${dateTime + timeZone}:00`;
}
if (parseDate[ISO8601_MEDIUM_FMT]) {
return `${dateTime + timeZone.substr(0, 3)}:${timeZone.substr(3)}`;
}
if (parseDate[ISO8601_LONG_FMT]) {
return isoString;
}
}
return isoString;
}
/**
* Will convert
* 2018-06-13T00:00:00.000Z
* to
* 2018-06-13T07:00:00.000Z
*
* This is the opposite of convertDateToUnixMidnightTime
*
* @param {string} isoString - ISO string in UTC time zone
*/
function convertISOStringToUTCDate(isoString: string): Date {
// get date in UTC midnight time
const utcDate = new Date(convertISOStringtoRFC3339String(isoString));
const utcTime = utcDate.getTime();
// get browser's timezone
const timezoneOffsetInMins = utcDate.getTimezoneOffset();
const timezoneOffsetInMs = timezoneOffsetInMins * MILLISECONDS_PER_MINUTE;
// return date in utc timezone
const localizedUnixTimeInMs = utcTime + timezoneOffsetInMs;
return new Date(localizedUnixTimeInMs);
}
export {
convertToDate,
convertToMs,
convertDateToUnixMidnightTime,
convertISOStringToUTCDate,
convertISOStringtoRFC3339String,
isToday,
isTomorrow,
isValidDate,
isYesterday,
isCurrentMonth,
isCurrentYear,
formatTime,
addTime,
};