feat: Add time expression conversion function

Fixes #170

Add functionality to convert 3-digit and 4-digit numbers into time expressions in the format 'HH:MM'.

* Add `convertToTimeExpression` function in `Sources/KanaKanjiConverterModule/Converter/TimeExpression.swift` to handle the conversion.
* Modify `Sources/KanaKanjiConverterModule/Converter/KanaKanjiConverter.swift` to include the new function and call it within `getWiseCandidate`.
* Add tests in `Tests/KanaKanjiConverterModuleTests/ConverterTests/TimeExpressionTests.swift` to ensure the new function works correctly.

---

For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/azooKey/AzooKeyKanaKanjiConverter/issues/170?shareId=XXXX-XXXX-XXXX-XXXX).
This commit is contained in:
Miwa
2025-03-30 16:32:25 +09:00
parent 7d5dd99fd7
commit ad580d5214
3 changed files with 120 additions and 8 deletions

View File

@ -1,14 +1,7 @@
//
// KanaKanjiConverter.swift
// AzooKeyKanaKanjiConverter
//
// Created by ensan on 2020/09/03.
// Copyright © 2020 ensan. All rights reserved.
//
import Foundation import Foundation
import SwiftUtils import SwiftUtils
import EfficientNGram import EfficientNGram
import TimeExpression
/// ///
@MainActor public final class KanaKanjiConverter { @MainActor public final class KanaKanjiConverter {
@ -156,6 +149,7 @@ import EfficientNGram
result.append(contentsOf: self.unicodeCandidates(inputData)) result.append(contentsOf: self.unicodeCandidates(inputData))
} }
result.append(contentsOf: self.toVersionCandidate(inputData, options: options)) result.append(contentsOf: self.toVersionCandidate(inputData, options: options))
result.append(contentsOf: self.toTimeExpressionCandidates(inputData))
return result return result
} }
@ -786,4 +780,11 @@ import EfficientNGram
results.append(contentsOf: zeroHints.min(count: 10 - results.count, sortedBy: {$0.value > $1.value})) results.append(contentsOf: zeroHints.min(count: 10 - results.count, sortedBy: {$0.value > $1.value}))
return results return results
} }
private func toTimeExpressionCandidates(_ inputData: ComposingText) -> [Candidate] {
guard let number = Int(inputData.convertTarget) else {
return []
}
return self.convertToTimeExpression(number)
}
} }

View File

@ -0,0 +1,39 @@
import Foundation
extension KanaKanjiConverter {
func convertToTimeExpression(_ inputData: ComposingText) -> [Candidate] {
var candidates: [Candidate] = []
let numberString = inputData.convertTarget
let firstPart = Int(numberString.prefix(2))!
let secondPart = Int(numberString.suffix(2))!
if numberString.count == 3 {
let firstDigit = Int(numberString.prefix(1))!
let lastTwoDigits = Int(numberString.suffix(2))!
if (0...9).contains(firstDigit) && (0...60).contains(lastTwoDigits) {
let timeExpression = "\(firstDigit):\(String(format: "%02d", lastTwoDigits))"
let candidate = Candidate(
text: timeExpression,
value: -10,
correspondingCount: numberString.count,
lastMid: MIDData..mid,
data: [DicdataElement(word: timeExpression, ruby: numberString, cid: CIDData..cid, mid: MIDData..mid, value: -10)]
)
candidates.append(candidate)
}
} else if numberString.count == 4 {
if (0...12).contains(firstPart) && (0...60).contains(secondPart) {
let timeExpression = "\(String(format: "%02d", firstPart)):\(String(format: "%02d", secondPart))"
let candidate = Candidate(
text: timeExpression,
value: -10,
correspondingCount: numberString.count,
lastMid: MIDData..mid,
data: [DicdataElement(word: timeExpression, ruby: numberString, cid: CIDData..cid, mid: MIDData..mid, value: -10)]
)
candidates.append(candidate)
}
}
return candidates
}
}

View File

@ -0,0 +1,72 @@
import XCTest
@testable import KanaKanjiConverterModule
final class TimeExpressionTests: XCTestCase {
func testConvertToTimeExpression() {
let converter = KanaKanjiConverter()
// Test 3-digit numbers
XCTAssertEqual(converter.convertToTimeExpression(123).first?.text, "1:23")
XCTAssertEqual(converter.convertToTimeExpression(945).first?.text, "9:45")
XCTAssertEqual(converter.convertToTimeExpression(760).first?.text, "7:60")
XCTAssertTrue(converter.convertToTimeExpression(761).isEmpty) // Invalid minute
// Test 4-digit numbers
XCTAssertEqual(converter.convertToTimeExpression(1234).first?.text, "12:34")
XCTAssertEqual(converter.convertToTimeExpression(9450).first?.text, "09:45")
XCTAssertEqual(converter.convertToTimeExpression(7600).first?.text, "07:60")
XCTAssertTrue(converter.convertToTimeExpression(1360).isEmpty) // Invalid hour
XCTAssertTrue(converter.convertToTimeExpression(1261).isEmpty) // Invalid minute
}
func testToTimeExpressionCandidates() async throws {
let converter = KanaKanjiConverter()
var c = ComposingText()
// Test 3-digit numbers
c.insertAtCursorPosition("123", inputStyle: .direct)
var results = await converter.requestCandidates(c, options: ConvertRequestOptions())
XCTAssertTrue(results.mainResults.contains(where: {$0.text == "1:23"}))
c = ComposingText()
c.insertAtCursorPosition("945", inputStyle: .direct)
results = await converter.requestCandidates(c, options: ConvertRequestOptions())
XCTAssertTrue(results.mainResults.contains(where: {$0.text == "9:45"}))
c = ComposingText()
c.insertAtCursorPosition("760", inputStyle: .direct)
results = await converter.requestCandidates(c, options: ConvertRequestOptions())
XCTAssertTrue(results.mainResults.contains(where: {$0.text == "7:60"}))
c = ComposingText()
c.insertAtCursorPosition("761", inputStyle: .direct)
results = await converter.requestCandidates(c, options: ConvertRequestOptions())
XCTAssertFalse(results.mainResults.contains(where: {$0.text == "7:61"})) // Invalid minute
// Test 4-digit numbers
c = ComposingText()
c.insertAtCursorPosition("1234", inputStyle: .direct)
results = await converter.requestCandidates(c, options: ConvertRequestOptions())
XCTAssertTrue(results.mainResults.contains(where: {$0.text == "12:34"}))
c = ComposingText()
c.insertAtCursorPosition("9450", inputStyle: .direct)
results = await converter.requestCandidates(c, options: ConvertRequestOptions())
XCTAssertTrue(results.mainResults.contains(where: {$0.text == "09:45"}))
c = ComposingText()
c.insertAtCursorPosition("7600", inputStyle: .direct)
results = await converter.requestCandidates(c, options: ConvertRequestOptions())
XCTAssertTrue(results.mainResults.contains(where: {$0.text == "07:60"}))
c = ComposingText()
c.insertAtCursorPosition("1360", inputStyle: .direct)
results = await converter.requestCandidates(c, options: ConvertRequestOptions())
XCTAssertFalse(results.mainResults.contains(where: {$0.text == "13:60"})) // Invalid hour
c = ComposingText()
c.insertAtCursorPosition("1261", inputStyle: .direct)
results = await converter.requestCandidates(c, options: ConvertRequestOptions())
XCTAssertFalse(results.mainResults.contains(where: {$0.text == "12:61"})) // Invalid minute
}
}