1 import formatters from 'date-fns/_lib/format/formatters/index';
2 import longFormatters from 'date-fns/_lib/format/longFormatters';
3 import defaultLocale from 'date-fns/locale/en-US';
5 import type { WeekStartsOn } from './interface';
8 * We copy here (with some refactor) the code for the format function from the 'date-fns' library.
9 * What the format function from 'date-fns' does for extracting from a date (think of it as a UNIX timestamp)
10 * the hours, day, month, etc that will be displayed, is to create a fake UTC time that mimics local time.
11 * The days, hours, etc are then extracted with the JS functions Date.getUTCDate, Date.getUTCHours, and some
12 * other UTC functions that date-fns has. This is achieved with the following lines of code in src/format/index.js:
14 * // Convert the date in system timezone to the same date in UTC+00:00 timezone.
15 * // This ensures that when UTC functions will be implemented, locales will be compatible with them.
16 * // See an issue about UTC functions: https://github.com/date-fns/date-fns/issues/376
17 * const timezoneOffset = getTimezoneOffsetInMilliseconds(originalDate)
18 * const utcDate = subMilliseconds(originalDate, timezoneOffset)
20 * We want a format function that treats a UTC date as it is, without artificially transforming it to a fake UTC time
21 * that mimics local time. Because of DST issues, we cannot undo the timezone offset with a wrapper of the format function,
22 * so for our formatUTC function we simply remove the problematic lines above.
25 const escapedStringRegExp = /^'([^]*?)'?$/;
26 const doubleQuoteRegExp = /''/g;
27 const formattingTokensRegExp = /[yYQqMLwIdDecihHKkms]o|(\w)\1*|''|'(''|[^'])+('|$)|./g;
28 const longFormattingTokensRegExp = /P+p+|P+|p+|''|'(''|[^'])+('|$)|./g;
29 const unescapedLatinCharacterRegExp = /[a-zA-Z]/;
31 const cleanEscapedString = (input: string) => {
32 const matcherResult = input.match(escapedStringRegExp);
36 return matcherResult[1].replace(doubleQuoteRegExp, "'");
39 const toInteger = (dirtyNumber: any) => {
40 if (dirtyNumber === null || dirtyNumber === true || dirtyNumber === false) {
44 const number = Number(dirtyNumber);
46 if (Number.isNaN(number)) {
50 return number < 0 ? Math.ceil(number) : Math.floor(number);
53 export interface Options {
55 weekStartsOn?: WeekStartsOn;
56 firstWeekContainsDate?: number;
57 useAdditionalWeekYearTokens?: boolean;
58 useAdditionalDayOfYearTokens?: boolean;
61 * Same as the format function from date-fns, but formats in the "UTC timezone"
63 const formatUTC = (utcDate: Date, formatString: string, options: Options = {}) => {
64 const locale = options.locale || defaultLocale;
65 const localeFirstWeekContainsDate = locale.options?.firstWeekContainsDate;
66 const defaultFirstWeekContainsDate =
67 localeFirstWeekContainsDate === undefined ? 1 : toInteger(localeFirstWeekContainsDate);
68 const firstWeekContainsDate =
69 options.firstWeekContainsDate === undefined
70 ? defaultFirstWeekContainsDate
71 : toInteger(options.firstWeekContainsDate);
72 const localeWeekStartsOn = locale?.options?.weekStartsOn;
73 const defaultWeekStartsOn = localeWeekStartsOn === undefined ? 0 : toInteger(localeWeekStartsOn);
74 const weekStartsOn = options.weekStartsOn === undefined ? defaultWeekStartsOn : toInteger(options.weekStartsOn);
75 const formatterOptions = { firstWeekContainsDate, weekStartsOn, locale, _originalDate: utcDate };
77 const longFormatMatchResult = formatString.match(longFormattingTokensRegExp);
78 if (!longFormatMatchResult) {
82 const formattingTokensMatchResult = longFormatMatchResult
84 const firstCharacter = substring[0];
85 if (firstCharacter === 'p' || firstCharacter === 'P') {
86 const longFormatter = longFormatters[firstCharacter];
87 return longFormatter(substring, locale.formatLong, formatterOptions);
92 .match(formattingTokensRegExp);
94 if (!formattingTokensMatchResult) {
98 return formattingTokensMatchResult
100 // Replace two single quote characters with one single quote character
101 if (substring === "''") {
105 const firstCharacter = substring[0];
106 if (firstCharacter === "'") {
107 return cleanEscapedString(substring);
110 const formatter = formatters[firstCharacter];
112 return formatter(utcDate, substring, locale.localize, formatterOptions);
115 if (firstCharacter.match(unescapedLatinCharacterRegExp)) {
116 throw new Error(`Format string contains an unescaped latin alphabet character \`${firstCharacter}\``);
124 export default formatUTC;