Use same lock values as mobile clients
[ProtonMail-WebClient.git] / packages / shared / lib / date-fns-utc / format.ts
blobab3f5b3ccce5480c8b0e2b7e7c3eb43ef73b5b1e
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';
7 /**
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:
13  *
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)
19  *
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.
23  */
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);
33     if (!matcherResult) {
34         return '';
35     }
36     return matcherResult[1].replace(doubleQuoteRegExp, "'");
39 const toInteger = (dirtyNumber: any) => {
40     if (dirtyNumber === null || dirtyNumber === true || dirtyNumber === false) {
41         return NaN;
42     }
44     const number = Number(dirtyNumber);
46     if (Number.isNaN(number)) {
47         return number;
48     }
50     return number < 0 ? Math.ceil(number) : Math.floor(number);
53 export interface Options {
54     locale?: Locale;
55     weekStartsOn?: WeekStartsOn;
56     firstWeekContainsDate?: number;
57     useAdditionalWeekYearTokens?: boolean;
58     useAdditionalDayOfYearTokens?: boolean;
60 /**
61  * Same as the format function from date-fns, but formats in the "UTC timezone"
62  */
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) {
79         return '';
80     }
82     const formattingTokensMatchResult = longFormatMatchResult
83         .map((substring) => {
84             const firstCharacter = substring[0];
85             if (firstCharacter === 'p' || firstCharacter === 'P') {
86                 const longFormatter = longFormatters[firstCharacter];
87                 return longFormatter(substring, locale.formatLong, formatterOptions);
88             }
89             return substring;
90         })
91         .join('')
92         .match(formattingTokensRegExp);
94     if (!formattingTokensMatchResult) {
95         return '';
96     }
98     return formattingTokensMatchResult
99         .map((substring) => {
100             // Replace two single quote characters with one single quote character
101             if (substring === "''") {
102                 return "'";
103             }
105             const firstCharacter = substring[0];
106             if (firstCharacter === "'") {
107                 return cleanEscapedString(substring);
108             }
110             const formatter = formatters[firstCharacter];
111             if (formatter) {
112                 return formatter(utcDate, substring, locale.localize, formatterOptions);
113             }
115             if (firstCharacter.match(unescapedLatinCharacterRegExp)) {
116                 throw new Error(`Format string contains an unescaped latin alphabet character \`${firstCharacter}\``);
117             }
119             return substring;
120         })
121         .join('');
124 export default formatUTC;