@@ -18,95 +18,121 @@ limitations under the License.
18
18
19
19
import { Optional } from "matrix-events-sdk" ;
20
20
21
- import { _t } from "./languageHandler" ;
21
+ import { _t , getUserLanguage } from "./languageHandler" ;
22
22
23
- function getDaysArray ( ) : string [ ] {
24
- return [ _t ( "Sun" ) , _t ( "Mon" ) , _t ( "Tue" ) , _t ( "Wed" ) , _t ( "Thu" ) , _t ( "Fri" ) , _t ( "Sat" ) ] ;
25
- }
23
+ export const MINUTE_MS = 60000 ;
24
+ export const HOUR_MS = MINUTE_MS * 60 ;
25
+ export const DAY_MS = HOUR_MS * 24 ;
26
26
27
- function getMonthsArray ( ) : string [ ] {
28
- return [
29
- _t ( "Jan" ) ,
30
- _t ( "Feb" ) ,
31
- _t ( "Mar" ) ,
32
- _t ( "Apr" ) ,
33
- _t ( "May" ) ,
34
- _t ( "Jun" ) ,
35
- _t ( "Jul" ) ,
36
- _t ( "Aug" ) ,
37
- _t ( "Sep" ) ,
38
- _t ( "Oct" ) ,
39
- _t ( "Nov" ) ,
40
- _t ( "Dec" ) ,
41
- ] ;
27
+ /**
28
+ * Returns array of 7 weekday names, from Sunday to Saturday, internationalised to the user's language.
29
+ * @param weekday - format desired "short" | "long" | "narrow"
30
+ */
31
+ export function getDaysArray ( weekday : Intl . DateTimeFormatOptions [ "weekday" ] = "short" ) : string [ ] {
32
+ const sunday = 1672574400000 ; // 2023-01-01 12:00 UTC
33
+ const { format } = new Intl . DateTimeFormat ( getUserLanguage ( ) , { weekday, timeZone : "UTC" } ) ;
34
+ return [ ...Array ( 7 ) . keys ( ) ] . map ( ( day ) => format ( sunday + day * DAY_MS ) ) ;
42
35
}
43
36
44
- function pad ( n : number ) : string {
45
- return ( n < 10 ? "0" : "" ) + n ;
37
+ /**
38
+ * Returns array of 12 month names, from January to December, internationalised to the user's language.
39
+ * @param month - format desired "numeric" | "2-digit" | "long" | "short" | "narrow"
40
+ */
41
+ export function getMonthsArray ( month : Intl . DateTimeFormatOptions [ "month" ] = "short" ) : string [ ] {
42
+ const { format } = new Intl . DateTimeFormat ( getUserLanguage ( ) , { month, timeZone : "UTC" } ) ;
43
+ return [ ...Array ( 12 ) . keys ( ) ] . map ( ( m ) => format ( Date . UTC ( 2021 , m ) ) ) ;
46
44
}
47
45
48
- function twelveHourTime ( date : Date , showSeconds = false ) : string {
49
- let hours = date . getHours ( ) % 12 ;
50
- const minutes = pad ( date . getMinutes ( ) ) ;
51
- const ampm = date . getHours ( ) >= 12 ? _t ( "PM" ) : _t ( "AM" ) ;
52
- hours = hours ? hours : 12 ; // convert 0 -> 12
53
- if ( showSeconds ) {
54
- const seconds = pad ( date . getSeconds ( ) ) ;
55
- return `${ hours } :${ minutes } :${ seconds } ${ ampm } ` ;
56
- }
57
- return `${ hours } :${ minutes } ${ ampm } ` ;
46
+ // XXX: Ideally we could just specify `hour12: boolean` but it has issues on Chrome in the `en` locale
47
+ // https://support.google.com/chrome/thread/29828561?hl=en
48
+ function getTwelveHourOptions ( showTwelveHour : boolean ) : Intl . DateTimeFormatOptions {
49
+ return {
50
+ hourCycle : showTwelveHour ? "h12" : "h23" ,
51
+ } ;
58
52
}
59
53
60
- export function formatDate ( date : Date , showTwelveHour = false ) : string {
54
+ /**
55
+ * Formats a given date to a date & time string.
56
+ *
57
+ * The output format depends on how far away the given date is from now.
58
+ * Will use the browser's default time zone.
59
+ * If the date is today it will return a time string excluding seconds. See {@formatTime }.
60
+ * If the date is within the last 6 days it will return the name of the weekday along with the time string excluding seconds.
61
+ * If the date is within the same year then it will return the weekday, month and day of the month along with the time string excluding seconds.
62
+ * Otherwise, it will return a string representing the full date & time in a human friendly manner. See {@formatFullDate }.
63
+ * @param date - date object to format
64
+ * @param showTwelveHour - whether to use 12-hour rather than 24-hour time. Defaults to `false` (24 hour mode).
65
+ * Overrides the default from the locale, whether `true` or `false`.
66
+ * @param locale - the locale string to use, in BCP 47 format, defaulting to user's selected application locale
67
+ */
68
+ export function formatDate ( date : Date , showTwelveHour = false , locale ?: string ) : string {
69
+ const _locale = locale ?? getUserLanguage ( ) ;
61
70
const now = new Date ( ) ;
62
- const days = getDaysArray ( ) ;
63
- const months = getMonthsArray ( ) ;
64
71
if ( date . toDateString ( ) === now . toDateString ( ) ) {
65
- return formatTime ( date , showTwelveHour ) ;
66
- } else if ( now . getTime ( ) - date . getTime ( ) < 6 * 24 * 60 * 60 * 1000 ) {
67
- // TODO: use standard date localize function provided in counterpart
68
- return _t ( "%(weekDayName)s %(time)s" , {
69
- weekDayName : days [ date . getDay ( ) ] ,
70
- time : formatTime ( date , showTwelveHour ) ,
71
- } ) ;
72
+ return formatTime ( date , showTwelveHour , _locale ) ;
73
+ } else if ( now . getTime ( ) - date . getTime ( ) < 6 * DAY_MS ) {
74
+ // Time is within the last 6 days (or in the future)
75
+ return new Intl . DateTimeFormat ( _locale , {
76
+ ...getTwelveHourOptions ( showTwelveHour ) ,
77
+ weekday : "short" ,
78
+ hour : "numeric" ,
79
+ minute : "2-digit" ,
80
+ } ) . format ( date ) ;
72
81
} else if ( now . getFullYear ( ) === date . getFullYear ( ) ) {
73
- // TODO: use standard date localize function provided in counterpart
74
- return _t ( "%(weekDayName)s, %(monthName)s %(day)s %(time)s" , {
75
- weekDayName : days [ date . getDay ( ) ] ,
76
- monthName : months [ date . getMonth ( ) ] ,
77
- day : date . getDate ( ) ,
78
- time : formatTime ( date , showTwelveHour ) ,
79
- } ) ;
82
+ return new Intl . DateTimeFormat ( _locale , {
83
+ ...getTwelveHourOptions ( showTwelveHour ) ,
84
+ weekday : "short" ,
85
+ month : "short" ,
86
+ day : "numeric" ,
87
+ hour : "numeric" ,
88
+ minute : "2-digit" ,
89
+ } ) . format ( date ) ;
80
90
}
81
- return formatFullDate ( date , showTwelveHour ) ;
91
+ return formatFullDate ( date , showTwelveHour , false , _locale ) ;
82
92
}
83
93
84
- export function formatFullDateNoTime ( date : Date ) : string {
85
- const days = getDaysArray ( ) ;
86
- const months = getMonthsArray ( ) ;
87
- return _t ( "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s" , {
88
- weekDayName : days [ date . getDay ( ) ] ,
89
- monthName : months [ date . getMonth ( ) ] ,
90
- day : date . getDate ( ) ,
91
- fullYear : date . getFullYear ( ) ,
92
- } ) ;
94
+ /**
95
+ * Formats a given date to a human-friendly string with short weekday.
96
+ * Will use the browser's default time zone.
97
+ * @example "Thu, 17 Nov 2022" in en-GB locale
98
+ * @param date - date object to format
99
+ * @param locale - the locale string to use, in BCP 47 format, defaulting to user's selected application locale
100
+ */
101
+ export function formatFullDateNoTime ( date : Date , locale ?: string ) : string {
102
+ return new Intl . DateTimeFormat ( locale ?? getUserLanguage ( ) , {
103
+ weekday : "short" ,
104
+ month : "short" ,
105
+ day : "numeric" ,
106
+ year : "numeric" ,
107
+ } ) . format ( date ) ;
93
108
}
94
109
95
- export function formatFullDate ( date : Date , showTwelveHour = false , showSeconds = true ) : string {
96
- const days = getDaysArray ( ) ;
97
- const months = getMonthsArray ( ) ;
98
- return _t ( "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s" , {
99
- weekDayName : days [ date . getDay ( ) ] ,
100
- monthName : months [ date . getMonth ( ) ] ,
101
- day : date . getDate ( ) ,
102
- fullYear : date . getFullYear ( ) ,
103
- time : showSeconds ? formatFullTime ( date , showTwelveHour ) : formatTime ( date , showTwelveHour ) ,
104
- } ) ;
110
+ /**
111
+ * Formats a given date to a date & time string, optionally including seconds.
112
+ * Will use the browser's default time zone.
113
+ * @example "Thu, 17 Nov 2022, 4:58:32 pm" in en-GB locale with showTwelveHour=true and showSeconds=true
114
+ * @param date - date object to format
115
+ * @param showTwelveHour - whether to use 12-hour rather than 24-hour time. Defaults to `false` (24 hour mode).
116
+ * Overrides the default from the locale, whether `true` or `false`.
117
+ * @param showSeconds - whether to include seconds in the time portion of the string
118
+ * @param locale - the locale string to use, in BCP 47 format, defaulting to user's selected application locale
119
+ */
120
+ export function formatFullDate ( date : Date , showTwelveHour = false , showSeconds = true , locale ?: string ) : string {
121
+ return new Intl . DateTimeFormat ( locale ?? getUserLanguage ( ) , {
122
+ ...getTwelveHourOptions ( showTwelveHour ) ,
123
+ weekday : "short" ,
124
+ month : "short" ,
125
+ day : "numeric" ,
126
+ year : "numeric" ,
127
+ hour : "numeric" ,
128
+ minute : "2-digit" ,
129
+ second : showSeconds ? "2-digit" : undefined ,
130
+ } ) . format ( date ) ;
105
131
}
106
132
107
133
/**
108
134
* Formats dates to be compatible with attributes of a `<input type="date">`. Dates
109
- * should be formatted like "2020-06-23" (formatted according to ISO8601)
135
+ * should be formatted like "2020-06-23" (formatted according to ISO8601).
110
136
*
111
137
* @param date The date to format.
112
138
* @returns The date string in ISO8601 format ready to be used with an `<input>`
@@ -115,22 +141,44 @@ export function formatDateForInput(date: Date): string {
115
141
const year = `${ date . getFullYear ( ) } ` . padStart ( 4 , "0" ) ;
116
142
const month = `${ date . getMonth ( ) + 1 } ` . padStart ( 2 , "0" ) ;
117
143
const day = `${ date . getDate ( ) } ` . padStart ( 2 , "0" ) ;
118
- const dateInputValue = `${ year } -${ month } -${ day } ` ;
119
- return dateInputValue ;
144
+ return `${ year } -${ month } -${ day } ` ;
120
145
}
121
146
122
- export function formatFullTime ( date : Date , showTwelveHour = false ) : string {
123
- if ( showTwelveHour ) {
124
- return twelveHourTime ( date , true ) ;
125
- }
126
- return pad ( date . getHours ( ) ) + ":" + pad ( date . getMinutes ( ) ) + ":" + pad ( date . getSeconds ( ) ) ;
147
+ /**
148
+ * Formats a given date to a time string including seconds.
149
+ * Will use the browser's default time zone.
150
+ * @example "4:58:32 PM" in en-GB locale with showTwelveHour=true
151
+ * @example "16:58:32" in en-GB locale with showTwelveHour=false
152
+ * @param date - date object to format
153
+ * @param showTwelveHour - whether to use 12-hour rather than 24-hour time. Defaults to `false` (24 hour mode).
154
+ * Overrides the default from the locale, whether `true` or `false`.
155
+ * @param locale - the locale string to use, in BCP 47 format, defaulting to user's selected application locale
156
+ */
157
+ export function formatFullTime ( date : Date , showTwelveHour = false , locale ?: string ) : string {
158
+ return new Intl . DateTimeFormat ( locale ?? getUserLanguage ( ) , {
159
+ ...getTwelveHourOptions ( showTwelveHour ) ,
160
+ hour : "numeric" ,
161
+ minute : "2-digit" ,
162
+ second : "2-digit" ,
163
+ } ) . format ( date ) ;
127
164
}
128
165
129
- export function formatTime ( date : Date , showTwelveHour = false ) : string {
130
- if ( showTwelveHour ) {
131
- return twelveHourTime ( date ) ;
132
- }
133
- return pad ( date . getHours ( ) ) + ":" + pad ( date . getMinutes ( ) ) ;
166
+ /**
167
+ * Formats a given date to a time string excluding seconds.
168
+ * Will use the browser's default time zone.
169
+ * @example "4:58 PM" in en-GB locale with showTwelveHour=true
170
+ * @example "16:58" in en-GB locale with showTwelveHour=false
171
+ * @param date - date object to format
172
+ * @param showTwelveHour - whether to use 12-hour rather than 24-hour time. Defaults to `false` (24 hour mode).
173
+ * Overrides the default from the locale, whether `true` or `false`.
174
+ * @param locale - the locale string to use, in BCP 47 format, defaulting to user's selected application locale
175
+ */
176
+ export function formatTime ( date : Date , showTwelveHour = false , locale ?: string ) : string {
177
+ return new Intl . DateTimeFormat ( locale ?? getUserLanguage ( ) , {
178
+ ...getTwelveHourOptions ( showTwelveHour ) ,
179
+ hour : "numeric" ,
180
+ minute : "2-digit" ,
181
+ } ) . format ( date ) ;
134
182
}
135
183
136
184
export function formatSeconds ( inSeconds : number ) : string {
@@ -183,9 +231,8 @@ export function formatTimeLeft(inSeconds: number): string {
183
231
} ) ;
184
232
}
185
233
186
- const MILLIS_IN_DAY = 86400000 ;
187
234
function withinPast24Hours ( prevDate : Date , nextDate : Date ) : boolean {
188
- return Math . abs ( prevDate . getTime ( ) - nextDate . getTime ( ) ) <= MILLIS_IN_DAY ;
235
+ return Math . abs ( prevDate . getTime ( ) - nextDate . getTime ( ) ) <= DAY_MS ;
189
236
}
190
237
191
238
function withinCurrentDay ( prevDate : Date , nextDate : Date ) : boolean {
@@ -210,28 +257,39 @@ export function wantsDateSeparator(prevEventDate: Optional<Date>, nextEventDate:
210
257
}
211
258
212
259
export function formatFullDateNoDay ( date : Date ) : string {
260
+ const locale = getUserLanguage ( ) ;
213
261
return _t ( "%(date)s at %(time)s" , {
214
- date : date . toLocaleDateString ( ) . replace ( / \/ / g, "-" ) ,
215
- time : date . toLocaleTimeString ( ) . replace ( / : / g, "-" ) ,
262
+ date : date . toLocaleDateString ( locale ) . replace ( / \/ / g, "-" ) ,
263
+ time : date . toLocaleTimeString ( locale ) . replace ( / : / g, "-" ) ,
216
264
} ) ;
217
265
}
218
266
219
267
/**
220
- * Returns an ISO date string without textual description of the date (ie: no "Wednesday" or
221
- * similar)
268
+ * Returns an ISO date string without textual description of the date (ie: no "Wednesday" or similar)
222
269
* @param date The date to format.
223
270
* @returns The date string in ISO format.
224
271
*/
225
272
export function formatFullDateNoDayISO ( date : Date ) : string {
226
273
return date . toISOString ( ) ;
227
274
}
228
275
229
- export function formatFullDateNoDayNoTime ( date : Date ) : string {
230
- return date . getFullYear ( ) + "/" + pad ( date . getMonth ( ) + 1 ) + "/" + pad ( date . getDate ( ) ) ;
276
+ /**
277
+ * Formats a given date to a string.
278
+ * Will use the browser's default time zone.
279
+ * @example 17/11/2022 in en-GB locale
280
+ * @param date - date object to format
281
+ * @param locale - the locale string to use, in BCP 47 format, defaulting to user's selected application locale
282
+ */
283
+ export function formatFullDateNoDayNoTime ( date : Date , locale ?: string ) : string {
284
+ return new Intl . DateTimeFormat ( locale ?? getUserLanguage ( ) , {
285
+ year : "numeric" ,
286
+ month : "numeric" ,
287
+ day : "numeric" ,
288
+ } ) . format ( date ) ;
231
289
}
232
290
233
291
export function formatRelativeTime ( date : Date , showTwelveHour = false ) : string {
234
- const now = new Date ( Date . now ( ) ) ;
292
+ const now = new Date ( ) ;
235
293
if ( withinCurrentDay ( date , now ) ) {
236
294
return formatTime ( date , showTwelveHour ) ;
237
295
} else {
@@ -245,15 +303,11 @@ export function formatRelativeTime(date: Date, showTwelveHour = false): string {
245
303
}
246
304
}
247
305
248
- const MINUTE_MS = 60000 ;
249
- const HOUR_MS = MINUTE_MS * 60 ;
250
- const DAY_MS = HOUR_MS * 24 ;
251
-
252
306
/**
253
- * Formats duration in ms to human readable string
254
- * Returns value in biggest possible unit (day, hour, min, second)
307
+ * Formats duration in ms to human- readable string
308
+ * Returns value in the biggest possible unit (day, hour, min, second)
255
309
* Rounds values up until unit threshold
256
- * ie . 23:13:57 -> 23h, 24:13:57 -> 1d, 44:56:56 -> 2d
310
+ * i.e . 23:13:57 -> 23h, 24:13:57 -> 1d, 44:56:56 -> 2d
257
311
*/
258
312
export function formatDuration ( durationMs : number ) : string {
259
313
if ( durationMs >= DAY_MS ) {
@@ -269,9 +323,9 @@ export function formatDuration(durationMs: number): string {
269
323
}
270
324
271
325
/**
272
- * Formats duration in ms to human readable string
326
+ * Formats duration in ms to human- readable string
273
327
* Returns precise value down to the nearest second
274
- * ie . 23:13:57 -> 23h 13m 57s, 44:56:56 -> 1d 20h 56m 56s
328
+ * i.e . 23:13:57 -> 23h 13m 57s, 44:56:56 -> 1d 20h 56m 56s
275
329
*/
276
330
export function formatPreciseDuration ( durationMs : number ) : string {
277
331
const days = Math . floor ( durationMs / DAY_MS ) ;
@@ -293,13 +347,13 @@ export function formatPreciseDuration(durationMs: number): string {
293
347
294
348
/**
295
349
* Formats a timestamp to a short date
296
- * (eg 25/12/22 in uk locale)
297
- * localised by system locale
350
+ * Similar to { @formatFullDateNoDayNoTime } but with 2-digit on day, month, year.
351
+ * @example 25/12/22 in en-GB locale
298
352
* @param timestamp - epoch timestamp
353
+ * @param locale - the locale string to use, in BCP 47 format, defaulting to user's selected application locale
299
354
* @returns {string } formattedDate
300
355
*/
301
- export const formatLocalDateShort = ( timestamp : number ) : string =>
302
- new Intl . DateTimeFormat (
303
- undefined , // locales
304
- { day : "2-digit" , month : "2-digit" , year : "2-digit" } ,
305
- ) . format ( timestamp ) ;
356
+ export const formatLocalDateShort = ( timestamp : number , locale ?: string ) : string =>
357
+ new Intl . DateTimeFormat ( locale ?? getUserLanguage ( ) , { day : "2-digit" , month : "2-digit" , year : "2-digit" } ) . format (
358
+ timestamp ,
359
+ ) ;
0 commit comments