Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/src/core/date.ts
3557 views
1
/*
2
* date.ts
3
*
4
* Copyright (C) 2020-2022 Posit Software, PBC
5
*/
6
import momentGuess from "moment-guess";
7
8
import { parse } from "datetime/parse";
9
import dayjs from "dayjs/dayjs.min.js";
10
import advancedPlugin from "../resources/library/dayjs/plugins/advanced.js";
11
import timezonePlugin from "../resources/library/dayjs/plugins/timezone.js";
12
import utcPlugin from "../resources/library/dayjs/plugins/utc.js";
13
import isoWeekPlugin from "../resources/library/dayjs/plugins/isoweek.js";
14
import weekOfYearPlugin from "../resources/library/dayjs/plugins/weekofyear.js";
15
import weekYearPlugin from "../resources/library/dayjs/plugins/weekyear.js";
16
17
import { existsSync } from "../deno_ral/fs.ts";
18
import { toFileUrl } from "../deno_ral/path.ts";
19
import { resourcePath } from "./resources.ts";
20
21
// Special date constants
22
export const kLastModified = "last-modified";
23
export const kToday = "today";
24
export const kNow = "now";
25
26
export type DateFormat = "full" | "long" | "medium" | "short" | "iso" | string;
27
export type TimeFormat = "full" | "long" | "medium" | "short";
28
29
export function today(): Date {
30
const today = new Date();
31
today.setHours(0);
32
today.setMinutes(0);
33
today.setSeconds(0);
34
today.setMilliseconds(0);
35
return today;
36
}
37
38
export function resolveAndFormatDate(
39
input: string | string[],
40
date?: unknown,
41
format?: string,
42
) {
43
const resolveDate = (date?: unknown) => {
44
if (date) {
45
if (typeof date === "string") {
46
return {
47
value: date,
48
format: format || "iso",
49
};
50
} else if (typeof date === "object") {
51
const schemaDate = date as { value: string; format?: string };
52
return {
53
value: schemaDate.value,
54
format: schemaDate.format || format || "iso",
55
};
56
}
57
} else {
58
return undefined;
59
}
60
};
61
62
// Resolve the date type
63
const resolvedDate = resolveDate(date);
64
if (resolvedDate) {
65
// Process any special dates
66
if (isSpecialDate(resolvedDate.value)) {
67
// Replace the date with its resolved form
68
resolvedDate.value = parseSpecialDate(
69
input,
70
resolvedDate.value,
71
);
72
}
73
74
// Read and format the date
75
const parsed = parsePandocDate(resolvedDate.value);
76
77
// Since there is no date format specified, we
78
// should default format this so it isn't a timestamp
79
return formatDate(
80
parsed,
81
resolvedDate.format,
82
);
83
}
84
}
85
86
export function resolveDate(input: string | string[], val: unknown) {
87
if (isSpecialDate(val)) {
88
return parseSpecialDate(input, val);
89
} else {
90
return val;
91
}
92
}
93
94
export function isSpecialDate(val?: unknown) {
95
return val === kLastModified || val === kToday ||
96
val === kNow;
97
}
98
99
export function parseSpecialDate(
100
input: string | string[],
101
val: unknown,
102
): string {
103
if (val === kLastModified) {
104
if (!Array.isArray(input)) {
105
input = [input];
106
}
107
108
let lastModifiedTs = 0;
109
for (const inp of input) {
110
const stat = Deno.statSync(inp);
111
if (stat.mtime) {
112
lastModifiedTs = Math.max(lastModifiedTs, stat.mtime.getTime());
113
}
114
}
115
116
// Format as an ISO timestamp
117
return formatDate(new Date(lastModifiedTs), "YYYY-MM-DDTHH:mm:ssZ");
118
} else if (val === kToday) {
119
return formatDate(today(), "YYYY-MM-DDTHH:mm:ssZ");
120
} else if (val === kNow) {
121
return formatDate(new Date(), "YYYY-MM-DDTHH:mm:ssZ");
122
} else {
123
return val as string;
124
}
125
}
126
127
export function initDayJsPlugins() {
128
dayjs.extend(utcPlugin);
129
dayjs.extend(timezonePlugin);
130
dayjs.extend(isoWeekPlugin);
131
dayjs.extend(weekYearPlugin);
132
dayjs.extend(weekOfYearPlugin);
133
dayjs.extend(advancedPlugin);
134
}
135
136
export async function setDateLocale(localeStr: string) {
137
localeStr = localeStr.toLowerCase();
138
if (localeStr !== dayjs.locale()) {
139
// Try to find the language + region (e.g. fr-CA) first
140
// but fall back to just the language (e.g. fr)
141
const findLocale = () => {
142
const locales = [localeStr];
143
if (localeStr.includes("-")) {
144
locales.push(localeStr.split("-")[0]);
145
}
146
147
for (const locale of locales) {
148
const path = resourcePath(
149
`library/dayjs/locale/${locale}.js`,
150
);
151
if (existsSync(path)) {
152
return {
153
locale,
154
path,
155
};
156
}
157
}
158
return undefined;
159
};
160
161
const locale = findLocale();
162
if (locale) {
163
const localeUrl = toFileUrl(locale.path).href;
164
const localeModule = await import(localeUrl);
165
dayjs.locale(localeModule.default, null, true);
166
dayjs.locale(locale.locale);
167
}
168
}
169
}
170
171
// Formats a date for a locale using either the shorthand form ("full")
172
// or a format string ("d-M-yyyy")
173
export const formatDate = (
174
date: Date,
175
dateStyle: DateFormat,
176
timeStyle?: TimeFormat,
177
) => {
178
if (
179
dateStyle === "full" || dateStyle === "long" || dateStyle === "medium" ||
180
dateStyle === "short"
181
) {
182
const options: Intl.DateTimeFormatOptions = {
183
dateStyle,
184
};
185
if (timeStyle) {
186
options.timeStyle = timeStyle;
187
}
188
return date.toLocaleString(dayjs.locale(), options);
189
} else {
190
if (dateStyle === "iso") dateStyle = "YYYY-MM-DD";
191
return dayjs(date).format(dateStyle);
192
}
193
};
194
195
export const formattedDate = (
196
dateStr: string,
197
dateFormat: string,
198
) => {
199
const date = parsePandocDate(dateStr);
200
201
if (date) {
202
const formatted = formatDate(
203
date,
204
dateFormat,
205
);
206
return formatted;
207
} else {
208
return undefined;
209
}
210
};
211
212
export const parsePandocDate = (dateRaw: string): Date => {
213
const formats = [
214
"MM/dd/yyyy",
215
"MM-dd-yyyy",
216
"MM/dd/yy",
217
"MM-dd-yy",
218
"yyyy-MM-dd",
219
"dd MM yyyy",
220
"MM dd, yyyy",
221
];
222
const parseFormat = (dateStr: string) => {
223
for (const format of formats) {
224
try {
225
const date = parse(dateStr, format);
226
return date;
227
} catch {
228
// This date wouldn't parse, try other formats
229
}
230
}
231
232
// Try to guess the format
233
try {
234
const formats = momentGuess(dateRaw);
235
if (formats) {
236
try {
237
// momentGuess could return more than one format if the date is
238
// ambiguous. If so, just take the first format
239
const format = Array.isArray(formats) ? formats[0] : formats;
240
const date = dayjs(dateStr, format);
241
return date.toDate();
242
} catch {
243
// Couldn't parse, keep going
244
}
245
}
246
} catch {
247
// Couldn't parse, keep going
248
}
249
250
// Try ISO date parse
251
try {
252
return new Date(dateStr);
253
} catch {
254
return undefined;
255
}
256
};
257
// Trying parsing format strings
258
return parseFormat(dateRaw);
259
};
260
261