UNPKG

@singleton-i18n/js-core-sdk

Version:

A JavaScript Singleton client library for internationalization and localization that leverage data from Singleton service. The library works both for the browser and as a Node.js module.

570 lines (569 loc) 20.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.defaultDateFormatter = exports.DateFormatter = void 0; /* * Copyright 2019-2023 VMware, Inc. * SPDX-License-Identifier: EPL-2.0 */ class DateFormatter { constructor() { } /** * Convert number or string to standard time * @param date * @return instance of Date or origin string */ getStandardTime(date) { const NUMBER_STRING = /^-?\d+$/; if (typeof date === 'string') { date = NUMBER_STRING.test(date) ? toInt(date) : getDateByString(date); } if (typeof date === 'number') { date = new Date(date); } return date; } /** * Get locale date time from standard date string * @return Formatted locale date time string */ getformattedString(date, pattern, i18nData, minusSign = '-', timezone) { this.i18nData = i18nData; const rules = this.getRules(pattern) ? this.getRules(pattern) : pattern; const parts = this.patternFilter(rules); let dateTimezoneOffset = date.getTimezoneOffset(); if (timezone) { dateTimezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset); date = convertTimezoneToLocal(date, timezone); } let text = ''; parts.forEach(value => { const fn = this.getFormatFunctionByRule(value, minusSign); text += fn ? fn(date, dateTimezoneOffset) : value === '\'\'' ? '\'' : value.replace(/(^'|'$)/g, '').replace(/''/g, '\''); }); return text; } /** * Get rules from pattern by type * @return The function to get locale string */ getRules(pattern) { let rules; switch (pattern) { case 'shortTime': rules = this.getRulesInPattern('time', 'short'); break; case 'mediumTime': rules = this.getRulesInPattern('time', 'medium'); break; case 'longTime': rules = this.getRulesInPattern('time', 'long'); break; case 'fullTime': rules = this.getRulesInPattern('time', 'full'); break; case 'shortDate': rules = this.getRulesInPattern('date', 'short'); break; case 'mediumDate': rules = this.getRulesInPattern('date', 'medium'); break; case 'longDate': rules = this.getRulesInPattern('date', 'long'); break; case 'fullDate': rules = this.getRulesInPattern('date', 'full'); break; case 'short': const shortDate = this.getRulesInPattern('date', 'short'); const shortTime = this.getRulesInPattern('time', 'short'); rules = this.formatDateTimeRules('short', [shortTime, shortDate]); break; case 'medium': const mediumDate = this.getRulesInPattern('date', 'medium'); const mediumTime = this.getRulesInPattern('time', 'medium'); rules = this.formatDateTimeRules('medium', [mediumTime, mediumDate]); break; case 'full': const fullDate = this.getRulesInPattern('date', 'full'); const fullTime = this.getRulesInPattern('time', 'full'); rules = this.formatDateTimeRules('full', [fullTime, fullDate]); break; case 'long': const longDate = this.getRulesInPattern('date', 'long'); const longTime = this.getRulesInPattern('time', 'long'); rules = this.formatDateTimeRules('long', [longTime, longDate]); break; default: rules = this.getRulesInPattern('dateTime', pattern); } return rules; } getRulesInPattern(name, type) { const rules = name !== 'dateTime' ? this.i18nData[name + 'Formats'][type] : this.i18nData[name + 'Formats']['availableFormats'][type]; return rules; } /** * The date-time pattern shows how to combine separate patterns for date (represented by {1}) * and time (represented by {0}) into a single pattern. */ formatDateTimeRules(type, rulesArray) { const format = this.i18nData.dateTimeFormats[type]; const rules = format.trim().replace(/\{(\d)\}/g, (match, key) => { return (rulesArray != null && rulesArray[+key]) ? rulesArray[+key] : match; }); return rules; } /** * Get locale date string from pattern */ dateStrGetter(name, width, type = 'format') { return (date) => { let text = ''; switch (name) { case 'Day': text = this.getLocaleString(date, name, width, type); break; case 'Month': text = this.getLocaleString(date, name, width, type); break; case 'dayPeriods': text = this.getDaysPeriods(date, name, width); break; case 'eras': text = this.getEras(date, name, width); break; } return text; }; } getLocaleString(date, name, width, type = 'format') { const firstLetter = type.slice(0, 1); type = type.replace(firstLetter, firstLetter.toUpperCase()); const namestr = name + 's'; const patternName = namestr.toLowerCase() + type; const value = getPartOfDate(date, name); const pattern = this.i18nData[patternName][width]; return pattern[value]; } /** * Get periods of the day */ getDaysPeriods(date, name, width) { const pattern = this.i18nData.dayPeriodsFormat[width]; return date.getHours() < 12 ? pattern[0] : pattern[1]; } /** * Get eras */ getEras(date, name, width) { const pattern = this.i18nData.eras[width]; return date.getFullYear() < 0 ? pattern[0] : pattern[1]; } /** * Convert pattern string to pattern array */ patternFilter(pattern) { let parts = [], match; const mainWord = 'GyMLwWdEahHKmsSzZO', wordRange = 'G{1,5}|y{1,4}|M{1,5}|L{1,5}|w{1,2}|W{1}|d{1,2}|E{1,6}|a{1,5}|h{1,2}|H{1,2}|K{1,2}|m{1,2}|s{1,2}|S{1,3}|x|z{1,4}|Z{1,5}|O{1,4}'; const reg = `((?:[^` + mainWord + `']+)|(?:'(?:[^']|'')*')|(?:` + wordRange + '))([\\s\\S]*)'; const DATE_FORMATS_SPLIT = new RegExp(reg); while (pattern) { match = DATE_FORMATS_SPLIT.exec(pattern); if (match) { parts = this.concat(parts, match, 1); pattern = parts.pop(); } else { parts.push(pattern); pattern = null; } } return parts; } concat(array1, array2, index) { return array1.concat([].slice.call(array2, index)); } /** * Corresponding function of the rule */ getFormatFunctionByRule(rule, minusSign) { let fn = null; switch (rule) { // Era string for the current date case 'G': case 'GG': case 'GGG': fn = this.dateStrGetter('eras', 'abbreviated'); break; case 'GGGG': fn = this.dateStrGetter('eras', 'wide'); break; case 'GGGGG': fn = this.dateStrGetter('eras', 'narrow'); break; // Calendar year (numeric). case 'y': fn = dateGetter('FullYear', 1, 0, false, true); break; case 'yy': fn = dateGetter('FullYear', 2, 0, true, true); break; case 'yyy': fn = dateGetter('FullYear', 3, 0, false, true); break; case 'yyyy': fn = dateGetter('FullYear', 4, 0, false, true); break; // Month number/name, format style (intended to be used in conjunction with ‘d’ for day number). case 'M': case 'L': fn = dateGetter('Month', 1, 1); break; case 'MM': case 'LL': fn = dateGetter('Month', 2, 1); break; case 'MMM': fn = this.dateStrGetter('Month', 'abbreviated'); break; case 'MMMM': fn = this.dateStrGetter('Month', 'wide'); break; case 'MMMMM': fn = this.dateStrGetter('Month', 'narrow'); break; // Stand-Alone Month number/name (intended to be used without ‘d’ for day number). case 'LLL': fn = this.dateStrGetter('Month', 'abbreviated', 'standalone'); break; case 'LLLL': fn = this.dateStrGetter('Month', 'wide', 'standalone'); break; case 'LLLLL': fn = this.dateStrGetter('Month', 'narrow', 'standalone'); break; // Week of Year (numeric). case 'w': fn = weekGetter(1); break; case 'ww': fn = weekGetter(2); break; // Week of Month (numeric) case 'W': fn = weekGetter(1, true); break; // Day of month (numeric). case 'd': fn = dateGetter('Date', 1); break; case 'dd': fn = dateGetter('Date', 2); break; // Day of week name, format style. case 'E': case 'EE': case 'EEE': fn = this.dateStrGetter('Day', 'abbreviated'); break; case 'EEEE': fn = this.dateStrGetter('Day', 'wide'); break; case 'EEEEE': fn = this.dateStrGetter('Day', 'narrow'); break; case 'EEEEEE': fn = this.dateStrGetter('Day', 'short'); break; // AM, PM case 'a': case 'aa': case 'aaa': fn = this.dateStrGetter('dayPeriods', 'abbreviated'); break; case 'aaaa': fn = this.dateStrGetter('dayPeriods', 'wide'); break; case 'aaaaa': fn = this.dateStrGetter('dayPeriods', 'narrow'); break; // Hour [1-12]. case 'h': fn = dateGetter('Hours', 1, -12); break; case 'hh': fn = dateGetter('Hours', 2, -12); break; // Hour [0-23]. case 'H': fn = dateGetter('Hours', 1); break; case 'HH': fn = dateGetter('Hours', 2); break; // Hour K [0-11] case 'K': fn = dateGetter('Hours', 1, -13); break; case 'KK': fn = dateGetter('Hours', 2, -13); break; // Minute (numeric). case 'm': fn = dateGetter('Minutes', 1); break; case 'mm': fn = dateGetter('Minutes', 2); break; // Second (numeric). case 's': fn = dateGetter('Seconds', 1); break; case 'ss': fn = dateGetter('Seconds', 2); break; // Fractional second padded case 'S': fn = dateGetter('Milliseconds', 1); break; case 'SS': fn = dateGetter('Milliseconds', 2); break; case 'SSS': fn = dateGetter('Milliseconds', 3); break; case 'x': fn = dateGetter('UnixTimeStamp'); break; // Timezone ISO8601 short format (-0430) case 'Z': case 'ZZ': case 'ZZZ': fn = timeZoneGetter('short', minusSign); break; case 'ZZZZZ': fn = timeZoneGetter('extended', minusSign); break; // Timezone GMT short format (GMT+4) case 'O': case 'OO': case 'OOO': case 'z': case 'zz': case 'zzz': fn = timeZoneGetter('shortGMT', minusSign); break; case 'OOOO': case 'ZZZZ': case 'zzzz': fn = timeZoneGetter('long', minusSign); break; default: return null; } return fn; } } exports.DateFormatter = DateFormatter; /** * Format date from iso8601 time string */ function IsoToDate(match) { const date = new Date(0), dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear, timeSetter = match[8] ? date.setUTCHours : date.setHours; let tzHour = 0, tzMin = 0; if (match[9]) { tzHour = toInt(match[9] + match[10]); tzMin = toInt(match[9] + match[11]); } dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3])); const h = toInt(match[4] || 0) - tzHour; const m = toInt(match[5] || 0) - tzMin; const s = toInt(match[6] || 0); const ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000); timeSetter.call(date, h, m, s, ms); return date; } function padNumber(num, digits, trim, negWrap) { if (!digits) { return num; } const ZERO_CHAR = '0'; let neg = ''; if (num < 0 || (negWrap && num <= 0)) { if (negWrap) { num = -num + 1; } else { num = -num; neg = '-'; } } num = '' + num; while (num.length < digits) { num = ZERO_CHAR + num; } if (trim) { num = num.substr(num.length - digits); } return neg + num; } /** * Convert string to standard date string * @param dateString eg '2017,12,06' R_ISO8601_STR-> '2015/06/15T09:03:01+0900' * @return Date / string */ function getDateByString(dateString) { let string; const isBrowserSide = typeof window !== 'undefined' && this === window; const isIE = isBrowserSide && navigator.userAgent.indexOf('Trident'); const isSafari = isBrowserSide && navigator.userAgent.indexOf('Safari') > -1 && navigator.userAgent.indexOf('Chrome') < 0; string = dateString.indexOf('/') ? dateString.replace(/\//g, '-') : dateString; if ((isSafari || isIE) && string.indexOf('T') < 0) { string = string.replace(/-/g, '/'); } let match; const R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; if ((match = string.match(R_ISO8601_STR))) { return IsoToDate(match); } else { const date = new Date(string); if (date.toString() === 'Invalid Date') { return dateString; } return date; } } /** * Get date string from date * @param name * @param size * @param offset * @param trim * @param negWrap */ function dateGetter(name, size, offset, trim, negWrap) { offset = offset || 0; return function (date) { let value = getPartOfDate(date, name); if (offset > 0 || value > -offset) { value += offset; } if (value === 0 && offset === -12) { value = 12; } return padNumber(value, size, trim, negWrap); }; } function getPartOfDate(date, name) { switch (name) { case 'FullYear': return date.getFullYear(); case 'Month': return date.getMonth(); case 'Date': return date.getDate(); case 'Hours': return date.getHours(); case 'Minutes': return date.getMinutes(); case 'Seconds': return date.getSeconds(); case 'Milliseconds': return date.getMilliseconds(); case 'UnixTimeStamp': return date.valueOf(); case 'Day': return date.getDay(); default: throw new Error(`Unknown DateType value "${name}".`); } } /** * Get time difference from timezone */ function timezoneToOffset(timezone, fallback) { const ALL_COLONS = /:/g; // Support: IE 9-11 only, Edge 13-15+ // IE/Edge do not "understand" colon (`:`) in timezone timezone = timezone.replace(ALL_COLONS, ''); const requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000; const isNaN = Number.isNaN || function isNumberNaN(num) { return num !== num; }; return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset; } function convertTimezoneToLocal(date, timezone) { // get Time difference between local time and GMT const dateTimezoneOffset = date.getTimezoneOffset(); // get Time difference between timezone time and GMT const timezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset); return addDateMinutes(date, -1 * (timezoneOffset - dateTimezoneOffset)); } /** *Returns a date formatter that transforms a date and an offset into a timezone with ISO8601 or * GMT format depending on the width (eg: short = +0430, short:GMT = GMT+4, long = GMT+04:30) */ function timeZoneGetter(type, minusSign) { return (date, offset) => { const zone = -1 * offset; const hours = zone > 0 ? Math.floor(zone / 60) : Math.ceil(zone / 60); switch (type) { case 'short': return ((zone >= 0) ? '+' : '') + padNumber(hours, 2) + padNumber(Math.abs(zone % 60), 2); case 'shortGMT': return 'GMT' + ((zone >= 0) ? '+' : '') + padNumber(hours, 1); case 'long': return 'GMT' + ((zone >= 0) ? '+' : '') + padNumber(hours, 2) + ':' + padNumber(Math.abs(zone % 60), 2); case 'extended': if (offset === 0) { return 'Z'; } else { return ((zone >= 0) ? '+' : '') + padNumber(hours, 2) + ':' + padNumber(Math.abs(zone % 60), 2); } } }; } function addDateMinutes(date, minutes) { date = new Date(date.getTime()); date.setMinutes(date.getMinutes() + minutes); return date; } function getFirstThursdayOfYear(year) { // 0 = index of January const dayOfWeekOnFirst = (new Date(year, 0, 1)).getDay(); // 4 = index of Thursday (+1 to account for 1st = 5) // 11 = index of *next* Thursday (+1 account for 1st = 12) return new Date(year, 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst); } function getThursdayThisWeek(datetime) { return new Date(datetime.getFullYear(), datetime.getMonth(), // 4 = index of Thursday datetime.getDate() + (4 - datetime.getDay())); } function weekGetter(size, isWeek = false) { return function (date) { let result; if (isWeek) { const nbDaysBefore1stDayOfMonth = new Date(date.getFullYear(), date.getMonth(), 1).getDay() - 1; const today = date.getDate(); result = 1 + Math.floor((today + nbDaysBefore1stDayOfMonth) / 7); } else { const firstThurs = getFirstThursdayOfYear(date.getFullYear()), thisThurs = getThursdayThisWeek(date); const diff = +thisThurs - +firstThurs; result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week } return padNumber(result, size); }; } function toInt(str) { return parseInt(str, 10); } exports.defaultDateFormatter = new DateFormatter();