@@ -709,15 +709,16 @@ class SlotExtractor {
709709 }
710710 }
711711
712- // Pattern for times like "730 pm", "1030 am" (digits + space + am/pm)
713- // Supports: am, pm, a.m., p.m., a.m, p.m, a m, p m
714- let amPmPattern = " ^( \\ d{1,4}) \\ s*(?: (?:a \\ .? \\ s*m \\ .?)| (?:p \\ .? \\ s*m \\ .?)) $"
712+ // Pattern for times like "730 pm", "1030 am", "6.pm" (digits + optional space/dot + am/pm)
713+ // Supports: am, pm, a.m., p.m., a.m, p.m, a m, p m, 6.pm, 5.am
714+ let amPmPattern = " ^( \\ d{1,4})[ \\ . \\ s]*([ap]) (?:\\ .? \\ s*m(?: \\ .?)?)? $ "
715715 if let regex = try ? NSRegularExpression ( pattern: amPmPattern) ,
716716 let match = regex. firstMatch ( in: cleanTime, range: NSRange ( cleanTime. startIndex... , in: cleanTime) ) {
717- if let timeRange = Range ( match. range ( at: 1 ) , in: cleanTime) {
717+ if let timeRange = Range ( match. range ( at: 1 ) , in: cleanTime) ,
718+ let amPmRange = Range ( match. range ( at: 2 ) , in: cleanTime) {
718719 let timeDigits = String ( cleanTime [ timeRange] )
719- let amPmText = String ( cleanTime [ cleanTime . index ( cleanTime . startIndex , offsetBy : timeDigits . count ) ... ] ) . trimmingCharacters ( in : . whitespaces )
720- let amPm = amPmText . starts ( with : " a " ) ? " am " : " pm "
720+ let amPmLetter = String ( cleanTime [ amPmRange ] )
721+ let amPm = amPmLetter == " a " ? " am " : " pm "
721722
722723 var hour : Int = 0
723724 var minute : Int = 0
@@ -749,18 +750,16 @@ class SlotExtractor {
749750
750751 // Pattern for "5 30 pm" or "5.30 am" format (hour [space|dot] minute am/pm)
751752 // Supports: am, pm, a.m., p.m., a.m, p.m, a m, p m
752- let hourMinuteAmPmPattern = " ^( \\ d{1,2})[ \\ s.]+?( \\ d{1,2}) \\ s*(?: (?:a \\ .? \\ s*m \\ .?)| (?:p \\ .? \\ s*m \\ .?)) $"
753+ let hourMinuteAmPmPattern = " ^( \\ d{1,2})[ \\ s.]+?( \\ d{1,2})[ \\ . \\ s]*([ap]) (?:\\ .? \\ s*m(?: \\ .?)?)? $ "
753754 if let regex = try ? NSRegularExpression ( pattern: hourMinuteAmPmPattern) ,
754755 let match = regex. firstMatch ( in: cleanTime, range: NSRange ( cleanTime. startIndex... , in: cleanTime) ) {
755756 if let hourRange = Range ( match. range ( at: 1 ) , in: cleanTime) ,
756- let minuteRange = Range ( match. range ( at: 2 ) , in: cleanTime) {
757+ let minuteRange = Range ( match. range ( at: 2 ) , in: cleanTime) ,
758+ let amPmRange = Range ( match. range ( at: 3 ) , in: cleanTime) {
757759 var hour = Int ( cleanTime [ hourRange] ) ?? 0
758760 let minute = Int ( cleanTime [ minuteRange] ) ?? 0
759-
760- // Find AM/PM indicator
761- let minuteEndIndex = cleanTime. index ( cleanTime. startIndex, offsetBy: NSMaxRange ( match. range ( at: 2 ) ) )
762- let amPmText = String ( cleanTime [ minuteEndIndex... ] ) . trimmingCharacters ( in: . whitespaces)
763- let amPm = amPmText. starts ( with: " a " ) ? " am " : " pm "
761+ let amPmLetter = String ( cleanTime [ amPmRange] )
762+ let amPm = amPmLetter == " a " ? " am " : " pm "
764763
765764 // Validate hour and minute ranges
766765 if hour > 12 || minute >= 60 {
@@ -780,18 +779,16 @@ class SlotExtractor {
780779
781780 // Pattern for times with colon like "7:30 pm", "10:30 am"
782781 // Supports: am, pm, a.m., p.m., a.m, p.m, a m, p m
783- let colonAmPmPattern = " ^( \\ d{1,2}):( \\ d{2}) \\ s*(?: (?:a \\ .? \\ s*m \\ .?)| (?:p \\ .? \\ s*m \\ .?)) $"
782+ let colonAmPmPattern = " ^( \\ d{1,2}):( \\ d{2})[ \\ . \\ s]*([ap]) (?:\\ .? \\ s*m(?: \\ .?)?)? $ "
784783 if let regex = try ? NSRegularExpression ( pattern: colonAmPmPattern) ,
785784 let match = regex. firstMatch ( in: cleanTime, range: NSRange ( cleanTime. startIndex... , in: cleanTime) ) {
786785 if let hourRange = Range ( match. range ( at: 1 ) , in: cleanTime) ,
787- let minuteRange = Range ( match. range ( at: 2 ) , in: cleanTime) {
786+ let minuteRange = Range ( match. range ( at: 2 ) , in: cleanTime) ,
787+ let amPmRange = Range ( match. range ( at: 3 ) , in: cleanTime) {
788788 var hour = Int ( cleanTime [ hourRange] ) ?? 0
789789 let minute = Int ( cleanTime [ minuteRange] ) ?? 0
790-
791- // Find AM/PM indicator
792- let minuteEndIndex = cleanTime. index ( cleanTime. startIndex, offsetBy: NSMaxRange ( match. range ( at: 2 ) ) )
793- let amPmText = String ( cleanTime [ minuteEndIndex... ] ) . trimmingCharacters ( in: . whitespaces)
794- let amPm = amPmText. starts ( with: " a " ) ? " am " : " pm "
790+ let amPmLetter = String ( cleanTime [ amPmRange] )
791+ let amPm = amPmLetter == " a " ? " am " : " pm "
795792
796793 // Convert to 24-hour format
797794 if amPm == " pm " && hour != 12 {
@@ -974,12 +971,12 @@ class SlotExtractor {
974971 let isAlarmContext = text. range ( of: " \\ b(?:alarm|wake|remind|alert) \\ b " , options: . regularExpression) != nil
975972
976973 let timePatterns = [
977- // Space/dot separated time with AM/PM - highest priority for "5 30 pm", "10.30 am" format
978- " \\ b( \\ d{1,2}[ \\ s.]+? \\ d{1,2} \\ s*(?: (?:a \\ .? \\ s*m \\ .?)| (?:p \\ .? \\ s*m \\ .?)) )\\ b " ,
974+ // Space/dot separated time with AM/PM - highest priority for "5 30 pm", "10.30 am", "6.pm" format
975+ " \\ b( \\ d{1,2}[ \\ s.]+? \\ d{1,2}[ \\ . \\ s]*([ap]) (?:\\ .? \\ s*m(?: \\ .?)?)? ) \\ b " ,
979976
980977 // Time with AM/PM - capture full time including AM/PM
981- " \\ b( \\ d{1,2}(?:: \\ d{2})? \\ s*(?: (?:a \\ .? \\ s*m \\ .?)| (?:p \\ .? \\ s*m \\ .?)) )\\ b " ,
982- " \\ b( \\ d{3,4} \\ s*(?: (?:a \\ .? \\ s*m \\ .?)| (?:p \\ .? \\ s*m \\ .?))) \\ b " , // For "1030 pm" format
978+ " \\ b( \\ d{1,2}(?:: \\ d{2})?[ \\ . \\ s]*([ap]) (?:\\ .? \\ s*m(?: \\ .?)?)? ) \\ b " ,
979+ " \\ b( \\ d{3,4}[ \\ . \\ s]*([ap]) (?:\\ .? \\ s*m(?: \\ .?)?)?) \\ b " , // For "1030 pm", "630. pm" format
983980
984981 // Duration patterns for timers
985982 " \\ b( \\ d+(?: \\ . \\ d+)?) \\ s*(?:hours?|hrs?|hr|h) \\ b " ,
@@ -999,9 +996,9 @@ class SlotExtractor {
999996 " \\ b(?:set|start|begin|run|timer|stopwatch) \\ s*(?:for|to|at)? \\ s*( \\ d+(?: \\ . \\ d+)?) \\ s*(?:min|mins|minute|minutes|hours?|hrs?|seconds?|secs?) \\ b " ,
1000997
1001998 // Alarm-specific patterns - capture full time with AM/PM (prioritize space-separated)
1002- " \\ b(?:alarm|wake|remind|alert) \\ s*(?:at|for|in)? \\ s*( \\ d{1,2}[ \\ s.]+? \\ d{1,2} \\ s*(?: (?:a \\ .? \\ s*m \\ .?)| (?:p \\ .? \\ s*m \\ .?)) )\\ b " ,
1003- " \\ b(?:alarm|wake|remind|alert) \\ s*(?:at|for|in)? \\ s*( \\ d{1,2}(?:: \\ d{2})? \\ s*(?: (?:a \\ .? \\ s*m \\ .?)| (?:p \\ .? \\ s*m \\ .?)) )\\ b " ,
1004- " \\ b(?:alarm|wake|remind|alert) \\ s*(?:at|for|in)? \\ s*( \\ d{3,4} \\ s*(?: (?:a \\ .? \\ s*m \\ .?)| (?:p \\ .? \\ s*m \\ .?)) )\\ b " ,
999+ " \\ b(?:alarm|wake|remind|alert) \\ s*(?:at|for|in)? \\ s*( \\ d{1,2}[ \\ s.]+? \\ d{1,2}[ \\ . \\ s]*([ap]) (?:\\ .? \\ s*m(?: \\ .?)?)? ) \\ b " ,
1000+ " \\ b(?:alarm|wake|remind|alert) \\ s*(?:at|for|in)? \\ s*( \\ d{1,2}(?:: \\ d{2})?[ \\ . \\ s]*([ap]) (?:\\ .? \\ s*m(?: \\ .?)?)? ) \\ b " ,
1001+ " \\ b(?:alarm|wake|remind|alert) \\ s*(?:at|for|in)? \\ s*( \\ d{3,4}[ \\ . \\ s]*([ap]) (?:\\ .? \\ s*m(?: \\ .?)?)? ) \\ b " ,
10051002
10061003 // "In X time" patterns
10071004 " \\ b(?:in|after|within) \\ s+( \\ d+(?: \\ . \\ d+)?) \\ s*(?:min|mins|minute|minutes|hours?|hrs?|seconds?|secs?) \\ b " ,
@@ -1010,12 +1007,12 @@ class SlotExtractor {
10101007 " \\ b(?:countdown|count \\ s+down) \\ s*(?:from|for)? \\ s*( \\ d+(?: \\ . \\ d+)?) \\ s*(?:min|mins|minute|minutes|hours?|hrs?|seconds?|secs?) \\ b " ,
10111008
10121009 // Direct time patterns for alarm setting - capture full time with AM/PM (prioritize space-separated)
1013- " \\ b(?:set|create|make).*?(?:alarm|wake).*?(?:for|at) \\ s*( \\ d{1,2}[ \\ s.]+? \\ d{1,2} \\ s*(?:am|pm) )\\ b " ,
1014- " \\ b(?:set|create|make).*?(?:alarm|wake).*?(?:for|at) \\ s*( \\ d{3,4} \\ s*(?:am|pm) )\\ b " ,
1015- " \\ b(?:set|create|make).*?(?:alarm|wake).*?(?:for|at) \\ s*( \\ d{1,2}(?:: \\ d{2})? \\ s*(?:am|pm) )\\ b " ,
1016- " \\ b(?:alarm|wake).*?(?:for|at) \\ s*( \\ d{1,2}[ \\ s.]+? \\ d{1,2} \\ s*(?:am|pm) )\\ b " ,
1017- " \\ b(?:alarm|wake).*?(?:for|at) \\ s*( \\ d{3,4} \\ s*(?:am|pm) )\\ b " ,
1018- " \\ b(?:alarm|wake).*?(?:for|at) \\ s*( \\ d{1,2}(?:: \\ d{2})? \\ s*(?:am|pm) )\\ b " ,
1010+ " \\ b(?:set|create|make).*?(?:alarm|wake).*?(?:for|at) \\ s*( \\ d{1,2}[ \\ s.]+? \\ d{1,2}[ \\ . \\ s]*([ap])(?: \\ .? \\ s*m(?: \\ .?)?)? )\\ b " ,
1011+ " \\ b(?:set|create|make).*?(?:alarm|wake).*?(?:for|at) \\ s*( \\ d{3,4}[ \\ . \\ s]*([ap])(?: \\ .? \\ s*m(?: \\ .?)?)? )\\ b " ,
1012+ " \\ b(?:set|create|make).*?(?:alarm|wake).*?(?:for|at) \\ s*( \\ d{1,2}(?:: \\ d{2})?[ \\ . \\ s]*([ap])(?: \\ .? \\ s*m(?: \\ .?)?)? )\\ b " ,
1013+ " \\ b(?:alarm|wake).*?(?:for|at) \\ s*( \\ d{1,2}[ \\ s.]+? \\ d{1,2}[ \\ . \\ s]*([ap])(?: \\ .? \\ s*m(?: \\ .?)?)? )\\ b " ,
1014+ " \\ b(?:alarm|wake).*?(?:for|at) \\ s*( \\ d{3,4}[ \\ . \\ s]*([ap])(?: \\ .? \\ s*m(?: \\ .?)?)? )\\ b " ,
1015+ " \\ b(?:alarm|wake).*?(?:for|at) \\ s*( \\ d{1,2}(?:: \\ d{2})?[ \\ . \\ s]*([ap])(?: \\ .? \\ s*m(?: \\ .?)?)? )\\ b " ,
10191016
10201017 // Standalone numeric patterns for alarm context (no AM/PM)
10211018 " \\ b(?:alarm|wake).*?(?:for|at) \\ s*( \\ d{3,4}) \\ b " ,
@@ -1051,7 +1048,7 @@ class SlotExtractor {
10511048 let timeValue = String ( text [ timeRange] )
10521049
10531050 // Check if this is a time format (contains AM/PM or looks like time in alarm context)
1054- let containsAmPm = timeValue. range ( of: " \\ b(?:(?:a \\ .? \\ s*m \\ .?)|(?:p \\ .? \\ s*m \\ .?)) \\ b " , options: . regularExpression) != nil
1051+ let containsAmPm = timeValue. range ( of: " \\ b(?:(?:a \\ .? \\ s*m \\ .?)|(?:p \\ .? \\ s*m \\ .?)|( \\ .am)|( \\ .pm) )\\ b " , options: . regularExpression) != nil
10551052 let containsSpaceOrDot = timeValue. range ( of: " [ \\ s.] \\ d " , options: . regularExpression) != nil
10561053 let looksLikeTime = timeValue. range ( of: " ^ \\ d{3,4}$ " , options: . regularExpression) != nil || timeValue. contains ( " : " ) || containsSpaceOrDot
10571054
0 commit comments