UNPKG

@instawork/design-system

Version:

The design system for Instawork's web apps

140 lines 5.68 kB
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