@instawork/design-system
Version:
The design system for Instawork's web apps
140 lines • 5.68 kB
JavaScript
import { DateTime } from 'luxon';
import { pluralize } from './pluralize';
export const DATE_FORMAT_PYTHON_NAIVE = 'yyyy-LL-dd HH:mm:ss';
export const DATE_FORMAT_PYTHON_AWARE = `${DATE_FORMAT_PYTHON_NAIVE}ZZZ`;
export const DATE_FORMATS = [
DATE_FORMAT_PYTHON_NAIVE,
DATE_FORMAT_PYTHON_AWARE,
];
function getLocale(navigator) {
return navigator.language || navigator.userLanguage || navigator.systemLanguage;
}
function hasTimeZoneSupport() {
try {
const locale = getLocale(window.navigator);
// see http://kangax.github.io/compat-table/esintl/#test-DateTimeFormat_accepts_IANA_timezone_names
new Intl.DateTimeFormat(locale, {
timeZone: 'America/Los_Angeles',
timeZoneName: 'long'
}).format();
return true;
}
catch (err) {
return false;
}
}
export const TIME_ZONE_SUPPORT = hasTimeZoneSupport();
export class DateUtil {
/**
* Attempts to construct a full date time value from a full datetime or partial time value.
*
* @param input
* @param timeZone - converts the resulting DateTime to the specified timeZone
* @param baseDate - used as the date when the input includes only the time
*/
static tryParseDateTime(input, { timeZone, baseDate } = {}) {
if (!input) {
return undefined;
}
if (!timeZone && baseDate) {
baseDate = baseDate instanceof DateTime ? baseDate : this.tryParseDateTime(baseDate);
if (baseDate && baseDate.isValid) {
timeZone = baseDate.zoneName;
}
}
let options = {};
if (timeZone) {
options.zone = timeZone;
}
const fromISO = DateTime.fromISO(input, options);
if (fromISO.isValid) {
return fromISO;
}
for (const format of DATE_FORMATS) {
const fromFormat = DateTime.fromFormat(input, format, options);
if (fromFormat.isValid) {
return fromFormat;
}
}
if (typeof baseDate === 'string') {
baseDate = this.tryParseDateTime(baseDate);
if (!baseDate.isValid) {
return baseDate;
}
}
if (!baseDate) {
return DateTime.invalid('No base date');
}
return this.tryParseTime(input, { timeZone, baseDate });
}
/**
* Attempt to construct a DateTime using the input as time, and the date from the "originalValue".
*/
static tryParseTime(input, { timeZone, baseDate }) {
// add a space before the meridian if it's missing
input = input.replace(/\d([ap]m?)$/i, (match, meridian) => ` ${meridian}`);
const time = DateTime.fromFormat(input, 't');
if (!time.isValid) {
return time;
}
const { year, month, day } = baseDate;
const { hour, minute } = time;
return DateTime.fromObject({ year, month, day, hour, minute }, { zone: timeZone });
}
/**
* Formats the hours with a minimum of 1 decimal places, and a maximum of 2 decimal places.
*/
static formatHours(hours) {
// round to 2 decimals
const fixed2 = hours.toFixed(2);
// leave off any trailing 0 in the hundredths place
const formattedHours = fixed2[fixed2.length - 1] === '0' ? hours.toFixed(1) : fixed2;
return `${formattedHours} ${pluralize(hours, 'hour', 'hours')}`;
}
/**
* Formats the hours with a minimum of 1 decimal places, and a maximum of 2 decimal places.
* @param diff
* @param abbreviatedLabels - Use "hrs" and "mins" instead of "hours" and "minutes"; defaults to `true`
* @param prefixPositiveValues - Prefix values greater than zero with a "+" sign; defaults to `true`
*/
static formatHoursDiff(diff, abbreviatedLabels = true, prefixPositiveValues = true) {
if (!diff) {
return '';
}
diff = diff.shiftTo('hours', 'minutes');
const minutes = diff.minutes.toFixed(0);
const [minsSingular, minsPlural, hoursSingular, hoursPlural] = abbreviatedLabels ? ['min', 'mins', 'hr', 'hrs'] : ['minute', 'minutes', 'hour', 'hours'];
const minsLabel = pluralize(minutes, minsSingular, minsPlural);
const positiveLabel = prefixPositiveValues ? '+' : '';
const sign = diff.valueOf() > 0 ? positiveLabel : ''; // negative values will already have a sign
if (diff.hours === 0) {
return `${sign}${minutes} ${minsLabel}`;
}
const hoursLabel = pluralize(diff.hours, hoursSingular, hoursPlural);
if (minutes === '0') {
return `${sign}${diff.hours} ${hoursLabel}`;
}
return `${sign}${diff.hours} ${hoursLabel} ${minutes} ${minsLabel}`;
}
/**
* Formats an informational message that the hours total includes time from changing to or from DST.
*/
static formatDstChange(offsetChange) {
const offsetChangeStr = (offsetChange > 0 ? '+' : '') + offsetChange;
const hoursLabel = pluralize(offsetChange, 'hour', 'hours');
return `Includes ${offsetChangeStr} ${hoursLabel} from DST change`;
}
static invalidate(value, reason, explanation) {
return Object.assign(DateTime.fromMillis(value.valueOf()), {
invalid: {
reason,
explanation,
},
});
}
}
DateUtil.TIME_ZONE_SUPPORT = TIME_ZONE_SUPPORT;
DateUtil.DATE_FORMAT_PYTHON_NAIVE = DATE_FORMAT_PYTHON_NAIVE;
DateUtil.DATE_FORMAT_PYTHON_AWARE = DATE_FORMAT_PYTHON_AWARE;
DateUtil.DATE_FORMATS = DATE_FORMATS;
//# sourceMappingURL=date-util.js.map