Skip to content

Commit f889fb8

Browse files
Apply same time normalization improvements to Swift SlotExtractor
- Updated AM/PM regex patterns in Swift to handle various formats: 5pm, 5 pm, 6 p.m., 6.pm - Fixed normalizeTimeFormat function patterns: amPmPattern, hourMinuteAmPmPattern, colonAmPmPattern - Updated time extraction patterns in extractValue function for TimerStopwatch and Alarm intents - Enhanced containsAmPm detection to recognize dot separator formats like .pm/.am - Simplified regex patterns from complex nested groups to cleaner capturing groups - Swift version now matches Kotlin functionality for time normalization: '5pm' -> '17:00', '6.pm' -> '18:00'
1 parent 9eb73f7 commit f889fb8

File tree

1 file changed

+31
-34
lines changed

1 file changed

+31
-34
lines changed

examples/whisper.swiftui/whisper.swiftui.demo/Intent/SlotExtractor.swift

Lines changed: 31 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)