@@ -18,95 +18,121 @@ limitations under the License.
1818
1919import { Optional } from "matrix-events-sdk" ;
2020
21- import { _t } from "./languageHandler" ;
21+ import { _t , getUserLanguage } from "./languageHandler" ;
2222
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 ;
2626
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 ) ) ;
4235}
4336
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 ) ) ) ;
4644}
4745
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+ } ;
5852}
5953
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 ( ) ;
6170 const now = new Date ( ) ;
62- const days = getDaysArray ( ) ;
63- const months = getMonthsArray ( ) ;
6471 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 ) ;
7281 } 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 ) ;
8090 }
81- return formatFullDate ( date , showTwelveHour ) ;
91+ return formatFullDate ( date , showTwelveHour , false , _locale ) ;
8292}
8393
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 ) ;
93108}
94109
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 ) ;
105131}
106132
107133/**
108134 * 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).
110136 *
111137 * @param date The date to format.
112138 * @returns The date string in ISO8601 format ready to be used with an `<input>`
@@ -115,22 +141,44 @@ export function formatDateForInput(date: Date): string {
115141 const year = `${ date . getFullYear ( ) } ` . padStart ( 4 , "0" ) ;
116142 const month = `${ date . getMonth ( ) + 1 } ` . padStart ( 2 , "0" ) ;
117143 const day = `${ date . getDate ( ) } ` . padStart ( 2 , "0" ) ;
118- const dateInputValue = `${ year } -${ month } -${ day } ` ;
119- return dateInputValue ;
144+ return `${ year } -${ month } -${ day } ` ;
120145}
121146
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 ) ;
127164}
128165
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 ) ;
134182}
135183
136184export function formatSeconds ( inSeconds : number ) : string {
@@ -183,9 +231,8 @@ export function formatTimeLeft(inSeconds: number): string {
183231 } ) ;
184232}
185233
186- const MILLIS_IN_DAY = 86400000 ;
187234function 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 ;
189236}
190237
191238function withinCurrentDay ( prevDate : Date , nextDate : Date ) : boolean {
@@ -210,28 +257,39 @@ export function wantsDateSeparator(prevEventDate: Optional<Date>, nextEventDate:
210257}
211258
212259export function formatFullDateNoDay ( date : Date ) : string {
260+ const locale = getUserLanguage ( ) ;
213261 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, "-" ) ,
216264 } ) ;
217265}
218266
219267/**
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)
222269 * @param date The date to format.
223270 * @returns The date string in ISO format.
224271 */
225272export function formatFullDateNoDayISO ( date : Date ) : string {
226273 return date . toISOString ( ) ;
227274}
228275
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 ) ;
231289}
232290
233291export function formatRelativeTime ( date : Date , showTwelveHour = false ) : string {
234- const now = new Date ( Date . now ( ) ) ;
292+ const now = new Date ( ) ;
235293 if ( withinCurrentDay ( date , now ) ) {
236294 return formatTime ( date , showTwelveHour ) ;
237295 } else {
@@ -245,15 +303,11 @@ export function formatRelativeTime(date: Date, showTwelveHour = false): string {
245303 }
246304}
247305
248- const MINUTE_MS = 60000 ;
249- const HOUR_MS = MINUTE_MS * 60 ;
250- const DAY_MS = HOUR_MS * 24 ;
251-
252306/**
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)
255309 * 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
257311 */
258312export function formatDuration ( durationMs : number ) : string {
259313 if ( durationMs >= DAY_MS ) {
@@ -269,9 +323,9 @@ export function formatDuration(durationMs: number): string {
269323}
270324
271325/**
272- * Formats duration in ms to human readable string
326+ * Formats duration in ms to human- readable string
273327 * 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
275329 */
276330export function formatPreciseDuration ( durationMs : number ) : string {
277331 const days = Math . floor ( durationMs / DAY_MS ) ;
@@ -293,13 +347,13 @@ export function formatPreciseDuration(durationMs: number): string {
293347
294348/**
295349 * 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
298352 * @param timestamp - epoch timestamp
353+ * @param locale - the locale string to use, in BCP 47 format, defaulting to user's selected application locale
299354 * @returns {string } formattedDate
300355 */
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