1010import type { MediaType , ParsedMessage , ParserOptions , WhatsAppFormat } from '../types'
1111import { chunkMessage , createChunkedMessages , normalizeApostrophes } from './index'
1212
13- // WhatsApp iOS format: [MM/DD/YY, H:MM:SS AM/PM] Sender: Message
13+ // WhatsApp iOS format:
14+ // - [MM/DD/YY, H:MM:SS AM/PM] Sender: Message
15+ // - [DD/MM/YY, HH:MM:SS] Sender: Message (locale-dependent 24-hour export)
1416// Notes:
1517// - \u200E is left-to-right mark that WhatsApp iOS exports include at line start
1618// - \u202F is narrow no-break space between time and AM/PM
1719const IOS_MESSAGE_PATTERN =
18- / ^ [ \u200E ] ? \[ ( \d { 1 , 2 } \/ \d { 1 , 2 } \/ \d { 2 , 4 } ) , \s * ( \d { 1 , 2 } : \d { 2 } : \d { 2 } [ \s \u202F ] * [ A P ] M ) \] \s * ( [ ^ : ] + ) : \s * ( .* ) $ /
20+ / ^ [ \u200E ] ? \[ ( \d { 1 , 2 } \/ \d { 1 , 2 } \/ \d { 2 , 4 } ) , \s * ( \d { 1 , 2 } : \d { 2 } (?: : \d { 2 } ) ? [ \s \u202F ] * (?: [ A P ] M ) ? ) \] \s * ( [ ^ : ] + ) : \s * ( .* ) $ /
1921
2022// WhatsApp Android format: MM/DD/YY, H:MM - Sender: Message
2123const ANDROID_MESSAGE_PATTERN =
@@ -53,20 +55,69 @@ const SYSTEM_PATTERNS: readonly RegExp[] = [
5355// URL extraction pattern
5456const URL_PATTERN = / h t t p s ? : \/ \/ [ ^ \s < > " ' ) \] ] + / gi
5557
56- /**
57- * Parse a WhatsApp timestamp (iOS format: MM/DD/YY H:MM:SS AM/PM)
58- */
59- function parseIosTimestamp ( dateStr : string , timeStr : string ) : Date {
58+ type DateOrder = 'month-first' | 'day-first'
59+
60+ function normalizeYear ( year : string | undefined ) : number {
61+ if ( ! year ) {
62+ return 2025
63+ }
64+
65+ return Number . parseInt ( year . length === 2 ? `20${ year } ` : year , 10 )
66+ }
67+
68+ function resolveDateOrder ( dateStr : string , defaultOrder : DateOrder ) : DateOrder {
69+ const dateParts = dateStr . split ( '/' )
70+ if ( dateParts . length !== 3 ) {
71+ throw new Error ( `Invalid date format: ${ dateStr } ` )
72+ }
73+
74+ const [ first , second ] = dateParts
75+ const firstValue = Number . parseInt ( first ?? '0' , 10 )
76+ const secondValue = Number . parseInt ( second ?? '0' , 10 )
77+
78+ if ( firstValue > 12 && secondValue <= 12 ) {
79+ return 'day-first'
80+ }
81+
82+ if ( secondValue > 12 && firstValue <= 12 ) {
83+ return 'month-first'
84+ }
85+
86+ return defaultOrder
87+ }
88+
89+ function parseDateParts (
90+ dateStr : string ,
91+ defaultOrder : DateOrder
92+ ) : { year : number ; month : number ; day : number } {
6093 const dateParts = dateStr . split ( '/' )
6194 if ( dateParts . length !== 3 ) {
6295 throw new Error ( `Invalid date format: ${ dateStr } ` )
6396 }
6497
65- const [ month , day , year ] = dateParts
66- const fullYear = year ?. length === 2 ? `20${ year } ` : year
98+ const [ first , second , year ] = dateParts
99+ const dateOrder = resolveDateOrder ( dateStr , defaultOrder )
100+ const month = dateOrder === 'month-first' ? first : second
101+ const day = dateOrder === 'month-first' ? second : first
102+
103+ return {
104+ year : normalizeYear ( year ) ,
105+ month : Number . parseInt ( month ?? '1' , 10 ) ,
106+ day : Number . parseInt ( day ?? '1' , 10 )
107+ }
108+ }
109+
110+ /**
111+ * Parse a WhatsApp timestamp (iOS format: bracketed 12-hour or 24-hour time)
112+ */
113+ function parseIosTimestamp ( dateStr : string , timeStr : string ) : Date {
114+ const { year, month, day } = parseDateParts (
115+ dateStr ,
116+ / [ A P ] M / i. test ( timeStr ) ? 'month-first' : 'day-first'
117+ )
67118
68119 // Parse time with AM/PM
69- const timeMatch = timeStr . match ( / ( \d { 1 , 2 } ) : ( \d { 2 } ) : ( \d { 2 } ) \s * ( [ A P ] M ) / i)
120+ const timeMatch = timeStr . match ( / ( \d { 1 , 2 } ) : ( \d { 2 } ) (?: : ( \d { 2 } ) ) ? [ \s \u202F ] * ( [ A P ] M ) ? / i)
70121 if ( ! timeMatch ) {
71122 throw new Error ( `Invalid time format: ${ timeStr } ` )
72123 }
@@ -80,9 +131,9 @@ function parseIosTimestamp(dateStr: string, timeStr: string): Date {
80131 }
81132
82133 return new Date (
83- Number . parseInt ( fullYear ?? '2025' , 10 ) ,
84- Number . parseInt ( month ?? '1' , 10 ) - 1 ,
85- Number . parseInt ( day ?? '1' , 10 ) ,
134+ year ,
135+ month - 1 ,
136+ day ,
86137 hour ,
87138 Number . parseInt ( minute ?? '0' , 10 ) ,
88139 Number . parseInt ( second ?? '0' , 10 )
@@ -93,13 +144,7 @@ function parseIosTimestamp(dateStr: string, timeStr: string): Date {
93144 * Parse a WhatsApp timestamp (Android format: MM/DD/YY H:MM)
94145 */
95146function parseAndroidTimestamp ( dateStr : string , timeStr : string ) : Date {
96- const dateParts = dateStr . split ( '/' )
97- if ( dateParts . length !== 3 ) {
98- throw new Error ( `Invalid date format: ${ dateStr } ` )
99- }
100-
101- const [ month , day , year ] = dateParts
102- const fullYear = year ?. length === 2 ? `20${ year } ` : year
147+ const { year, month, day } = parseDateParts ( dateStr , 'month-first' )
103148
104149 const timeParts = timeStr . split ( ':' )
105150 if ( timeParts . length !== 2 ) {
@@ -109,9 +154,9 @@ function parseAndroidTimestamp(dateStr: string, timeStr: string): Date {
109154 const [ hour , minute ] = timeParts
110155
111156 return new Date (
112- Number . parseInt ( fullYear ?? '2025' , 10 ) ,
113- Number . parseInt ( month ?? '1' , 10 ) - 1 ,
114- Number . parseInt ( day ?? '1' , 10 ) ,
157+ year ,
158+ month - 1 ,
159+ day ,
115160 Number . parseInt ( hour ?? '0' , 10 ) ,
116161 Number . parseInt ( minute ?? '0' , 10 )
117162 )
0 commit comments