diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index dee3e84..988ba00 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -8,9 +8,6 @@ on: branches: [ "master" ] workflow_dispatch: -env: - DEVELOPER_DIR: /Applications/Xcode_15.2.app/Contents/Developer - jobs: analyze: name: Analyze @@ -23,16 +20,19 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Setup Swift + uses: SwiftyLab/setup-swift@latest + - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: swift - + - name: Build - run: swift build -v + run: swift build -v -Xswiftc -swift-version -Xswiftc 6 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 with: category: "/language:swift" @@ -44,20 +44,29 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Setup Swift + uses: SwiftyLab/setup-swift@latest + + - name: Build - run: swift build -v + run: swift build -v -Xswiftc -swift-version -Xswiftc 6 - name: Run tests - run: swift test -v --enable-code-coverage + run: swift test -v -Xswiftc -swift-version -Xswiftc 6 --enable-code-coverage - - name: Convert coverage report - run: xcrun llvm-cov export -format="lcov" .build/debug/SwiftDevKitPackageTests.xctest/Contents/MacOS/SwiftDevKitPackageTests -instr-profile .build/debug/codecov/default.profdata > coverage.lcov + - name: Generate coverage report + run: | + xcrun llvm-cov export -format=lcov \ + .build/debug/SwiftDevKitPackageTests.xctest/Contents/MacOS/SwiftDevKitPackageTests \ + -instr-profile .build/debug/codecov/default.profdata > coverage.lcov - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v5 with: - file: coverage.lcov + files: ./coverage.lcov + token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true + verbose: true lint: name: Lint @@ -80,9 +89,16 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Setup Swift + uses: SwiftyLab/setup-swift@latest + - name: Generate Documentation run: | - swift package --allow-writing-to-directory docs generate-documentation --target SwiftDevKit --output-path docs + swift package --allow-writing-to-directory docs \ + generate-documentation --target SwiftDevKit \ + --output-path docs \ + --transform-for-static-hosting \ + --hosting-base-path SwiftDevKit - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pages@v3 diff --git a/.swiftlint.yml b/.swiftlint.yml index 8fea3f5..2dbff28 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -7,6 +7,7 @@ excluded: disabled_rules: - trailing_comma - comment_spacing + - switch_case_alignment opt_in_rules: - array_init diff --git a/Package.resolved b/Package.resolved index 5671531..f2636e9 100644 --- a/Package.resolved +++ b/Package.resolved @@ -17,24 +17,6 @@ "revision" : "b45d1f2ed151d057b54504d653e0da5552844e34", "version" : "1.0.0" } - }, - { - "identity" : "swift-syntax", - "kind" : "remoteSourceControl", - "location" : "https://github.com/swiftlang/swift-syntax.git", - "state" : { - "revision" : "0687f71944021d616d34d922343dcef086855920", - "version" : "600.0.1" - } - }, - { - "identity" : "swift-testing", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-testing.git", - "state" : { - "revision" : "399f76dcd91e4c688ca2301fa24a8cc6d9927211", - "version" : "0.99.0" - } } ], "version" : 2 diff --git a/Package.swift b/Package.swift index e8b2fdd..be7fd83 100644 --- a/Package.swift +++ b/Package.swift @@ -1,15 +1,16 @@ -// swift-tools-version: 5.9 +// swift-tools-version: 6.0 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "SwiftDevKit", + defaultLocalization: nil, platforms: [ - .iOS(.v13), - .macOS(.v10_15), - .tvOS(.v13), - .watchOS(.v6), + .macOS(.v13), + .iOS(.v16), + .tvOS(.v16), + .watchOS(.v9), ], products: [ // Products define the executables and libraries a package produces, making them visible to other packages. @@ -19,7 +20,6 @@ let package = Package( ], dependencies: [ // Dependencies will be added as needed - .package(url: "https://github.com/apple/swift-testing.git", from: "0.5.0"), .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), ], targets: [ @@ -27,19 +27,27 @@ let package = Package( // Targets can depend on other targets in this package and products from dependencies. .target( name: "SwiftDevKit", - dependencies: [], - swiftSettings: [ - .define("DEBUG", .when(configuration: .debug)), - .enableUpcomingFeature("BareSlashRegexLiterals"), - .enableExperimentalFeature("StrictConcurrency"), + path: "Sources/SwiftDevKit", + exclude: [ + "Documentation.docc/Installation.md", + "Documentation.docc/Architecture.md", + "Documentation.docc/Contributing.md", + "Documentation.docc/Conversion.md", + "Documentation.docc/GettingStarted.md", + "Documentation.docc/SwiftDevKit.md", + ], + resources: [ + .copy("Resources"), ], - plugins: [ - .plugin(name: "Swift-DocC", package: "swift-docc-plugin"), + swiftSettings: [ + .define("SWIFT_STRICT_CONCURRENCY", .when(configuration: .debug)), ]), .testTarget( name: "SwiftDevKitTests", dependencies: [ "SwiftDevKit", - .product(name: "Testing", package: "swift-testing"), + ], + swiftSettings: [ + .define("SWIFT_STRICT_CONCURRENCY", .when(configuration: .debug)), ]), ]) diff --git a/Sources/SwiftDevKit/Conversion/Date+Convertible.swift b/Sources/SwiftDevKit/Conversion/Date+Convertible.swift new file mode 100644 index 0000000..cb4aca1 --- /dev/null +++ b/Sources/SwiftDevKit/Conversion/Date+Convertible.swift @@ -0,0 +1,110 @@ +// Date+Convertible.swift +// SwiftDevKit +// +// Copyright (c) 2025 owdax and The SwiftDevKit Contributors +// MIT License - https://opensource.org/licenses/MIT + +import Foundation + +/// Actor that manages thread-safe access to date formatters. +/// +/// This actor provides a cache for `DateFormatter` instances, ensuring thread safety +/// while maintaining performance through reuse. All formatters are configured with +/// the POSIX locale and UTC timezone for consistent behavior across platforms. +private actor DateFormatterCache { + /// Cache of date formatters keyed by format string + private var formatters: [String: DateFormatter] = [:] + + /// Gets or creates a date formatter for the specified format. + /// + /// - Parameter format: The date format string + /// - Returns: A configured DateFormatter instance + func formatter(for format: String) -> DateFormatter { + if let formatter = formatters[format] { + return formatter + } + + let formatter = DateFormatter() + formatter.dateFormat = format + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.timeZone = TimeZone(secondsFromGMT: 0) + formatters[format] = formatter + return formatter + } +} + +/// Extends Date to support string conversion with thread-safe operations. +/// +/// This extension provides methods for converting dates to and from strings using various formats. +/// All operations are thread-safe and can be called concurrently from multiple tasks. +extension Date: DateConvertible { + /// Thread-safe date formatter cache + private static let formatterCache = DateFormatterCache() + + public func toString(format: String?) async throws -> String { + let dateFormat = format ?? DateFormat.iso8601 + let formatter = await Self.formatterCache.formatter(for: dateFormat) + return formatter.string(from: self) + } + + public static func fromString(_ string: String, format: String?) async throws -> Date { + let dateFormat = format ?? DateFormat.iso8601 + let formatter = await formatterCache.formatter(for: dateFormat) + + guard let date = formatter.date(from: string) else { + throw DateConversionError.invalidFormat(string) + } + + return date + } +} + +// MARK: - Convenience Methods + +public extension Date { + /// Creates a date from an ISO8601 string. + /// + /// This is a convenience method that uses the ISO8601 format for parsing. + /// The operation is thread-safe and can be called concurrently. + /// + /// - Parameter iso8601String: The ISO8601 formatted string (e.g., "2025-01-16T15:30:00Z") + /// - Returns: A new Date instance + /// - Throws: DateConversionError if the string is not valid ISO8601 + static func fromISO8601(_ iso8601String: String) async throws -> Date { + try await fromString(iso8601String, format: DateFormat.iso8601) + } + + /// Converts the date to an ISO8601 string. + /// + /// This is a convenience method that uses the ISO8601 format for formatting. + /// The operation is thread-safe and can be called concurrently. + /// + /// - Returns: An ISO8601 formatted string (e.g., "2025-01-16T15:30:00Z") + /// - Throws: DateConversionError if the conversion fails + func toISO8601() async throws -> String { + try await toString(format: DateFormat.iso8601) + } + + /// Creates a date from an HTTP date string. + /// + /// This is a convenience method that uses the HTTP date format for parsing. + /// The operation is thread-safe and can be called concurrently. + /// + /// - Parameter httpDateString: The HTTP date formatted string (e.g., "Wed, 16 Jan 2025 15:30:00 GMT") + /// - Returns: A new Date instance + /// - Throws: DateConversionError if the string is not a valid HTTP date + static func fromHTTPDate(_ httpDateString: String) async throws -> Date { + try await fromString(httpDateString, format: DateFormat.http) + } + + /// Converts the date to an HTTP date string. + /// + /// This is a convenience method that uses the HTTP date format for formatting. + /// The operation is thread-safe and can be called concurrently. + /// + /// - Returns: An HTTP date formatted string (e.g., "Wed, 16 Jan 2025 15:30:00 GMT") + /// - Throws: DateConversionError if the conversion fails + func toHTTPDate() async throws -> String { + try await toString(format: DateFormat.http) + } +} diff --git a/Sources/SwiftDevKit/Conversion/DateConvertible.swift b/Sources/SwiftDevKit/Conversion/DateConvertible.swift new file mode 100644 index 0000000..4a2db15 --- /dev/null +++ b/Sources/SwiftDevKit/Conversion/DateConvertible.swift @@ -0,0 +1,107 @@ +// DateConvertible.swift +// SwiftDevKit +// +// Copyright (c) 2025 owdax and The SwiftDevKit Contributors +// MIT License - https://opensource.org/licenses/MIT + +import Foundation + +/// A type that can be converted to and from date string representations with thread-safe operations. +/// +/// This protocol provides date-specific conversion capabilities that are safe for concurrent access. +/// It uses an actor-based formatter cache to ensure thread safety when working with `DateFormatter` instances. +/// +/// Example usage: +/// ```swift +/// let date = Date() +/// // Convert to ISO8601 +/// let isoString = try await date.toISO8601() +/// // Convert using custom format +/// let customString = try await date.toString(format: DateFormat.shortDate) +/// ``` +public protocol DateConvertible { + /// Converts the instance to a date string using the specified format. + /// + /// This method is thread-safe and can be called concurrently from multiple tasks. + /// The date formatter cache ensures optimal performance while maintaining thread safety. + /// + /// - Parameter format: The date format to use. If nil, uses ISO8601 format. + /// - Returns: A string representation of the date. + /// - Throws: `DateConversionError` if the conversion fails. + func toString(format: String?) async throws -> String + + /// Creates an instance from a date string using the specified format. + /// + /// This method is thread-safe and can be called concurrently from multiple tasks. + /// The date formatter cache ensures optimal performance while maintaining thread safety. + /// + /// - Parameters: + /// - string: The string to convert from. + /// - format: The date format to use. If nil, uses ISO8601 format. + /// - Returns: An instance of the conforming type. + /// - Throws: `DateConversionError` if the string is not in the expected format. + static func fromString(_ string: String, format: String?) async throws -> Self +} + +/// Errors specific to date conversion operations. +public enum DateConversionError: Error, LocalizedError, Equatable { + /// The date string doesn't match the expected format. + case invalidFormat(String) + /// The date components are invalid (e.g., month > 12). + case invalidComponents + /// The provided format string is invalid. + case invalidFormatString(String) + /// A custom error with a specific message. + case custom(String) + + public var errorDescription: String? { + switch self { + case let .invalidFormat(value): + "Date string doesn't match the expected format: \(value)" + case .invalidComponents: + "Date contains invalid components" + case let .invalidFormatString(format): + "Invalid date format string: \(format)" + case let .custom(message): + message + } + } +} + +/// Common date formats used in applications. +/// +/// This enum provides a set of predefined date format strings that cover common use cases. +/// All formats use the POSIX locale and UTC timezone for consistency across platforms. +public enum DateFormat { + /// ISO8601 format (e.g., "2024-01-16T00:10:00+0000") + /// Commonly used for API communication and data interchange. + public static let iso8601 = "yyyy-MM-dd'T'HH:mm:ssZ" + + /// HTTP format (e.g., "Tue, 16 Jan 2024 00:10:00 GMT") + /// Used in HTTP headers like "If-Modified-Since" and "Last-Modified". + public static let http = "EEE, dd MMM yyyy HH:mm:ss 'GMT'" + + /// Short date (e.g., "01/16/2024") + /// Compact representation for display purposes. + public static let shortDate = "MM/dd/yyyy" + + /// Long date (e.g., "January 16, 2024") + /// Human-readable format with full month name. + public static let longDate = "MMMM dd, yyyy" + + /// Time only (e.g., "00:10:00") + /// For when only the time component is needed. + public static let time = "HH:mm:ss" + + /// Date and time (e.g., "01/16/2024 00:10:00") + /// Combined date and time for complete timestamp display. + public static let dateTime = "MM/dd/yyyy HH:mm:ss" + + /// Year and month (e.g., "January 2024") + /// For month-level granularity display. + public static let yearMonth = "MMMM yyyy" + + /// Compact numeric (e.g., "20240116") + /// For file names or when space is at a premium. + public static let compact = "yyyyMMdd" +} diff --git a/Sources/SwiftDevKit/Conversion/NumericStringConvertible.swift b/Sources/SwiftDevKit/Conversion/NumericStringConvertible.swift new file mode 100644 index 0000000..6cf4d5e --- /dev/null +++ b/Sources/SwiftDevKit/Conversion/NumericStringConvertible.swift @@ -0,0 +1,30 @@ +// NumericStringConvertible.swift +// SwiftDevKit +// +// Copyright (c) 2025 owdax and The SwiftDevKit Contributors +// MIT License - https://opensource.org/licenses/MIT + +import Foundation + +/// Extends numeric types to provide string conversion capabilities. +/// This extension follows the Single Responsibility Principle by focusing solely on numeric string conversion. +public extension StringConvertible where Self: Numeric & LosslessStringConvertible { + func toString() throws -> String { + String(describing: self) + } + + static func fromString(_ string: String) throws -> Self { + guard let value = Self(string) else { + throw StringConversionError.invalidInput(string) + } + return value + } +} + +// Conformance for basic numeric types +extension Int: StringConvertible {} +extension Double: StringConvertible {} +extension Float: StringConvertible {} +extension Int64: StringConvertible {} +extension UInt: StringConvertible {} +extension UInt64: StringConvertible {} diff --git a/Sources/SwiftDevKit/Conversion/StringConvertible.swift b/Sources/SwiftDevKit/Conversion/StringConvertible.swift new file mode 100644 index 0000000..f5f2ac7 --- /dev/null +++ b/Sources/SwiftDevKit/Conversion/StringConvertible.swift @@ -0,0 +1,47 @@ +// StringConvertible.swift +// SwiftDevKit +// +// Copyright (c) 2025 owdax and The SwiftDevKit Contributors +// MIT License - https://opensource.org/licenses/MIT + +import Foundation + +/// A type that can be converted to and from a string representation. +/// +/// This protocol provides a standardized way to convert types to and from their string representations. +/// It follows the Interface Segregation Principle by keeping the interface focused and minimal. +public protocol StringConvertible { + /// Converts the instance to its string representation. + /// + /// - Returns: A string representation of the instance. + /// - Throws: `StringConversionError` if the conversion fails. + func toString() throws -> String + + /// Creates an instance from its string representation. + /// + /// - Parameter string: The string to convert from. + /// - Returns: An instance of the conforming type. + /// - Throws: `StringConversionError` if the conversion fails. + static func fromString(_ string: String) throws -> Self +} + +/// Errors that can occur during string conversion operations. +public enum StringConversionError: Error, LocalizedError, Equatable { + /// The input string is invalid for the requested conversion. + case invalidInput(String) + /// The conversion operation is not supported for this type. + case unsupportedConversion + /// A custom error with a specific message. + case custom(String) + + public var errorDescription: String? { + switch self { + case let .invalidInput(value): + "Invalid input string: \(value)" + case .unsupportedConversion: + "Unsupported conversion operation" + case let .custom(message): + message + } + } +} diff --git a/Sources/SwiftDevKit/Documentation.docc/Contributing.md b/Sources/SwiftDevKit/Documentation.docc/Contributing.md index d221567..8aec62a 100644 --- a/Sources/SwiftDevKit/Documentation.docc/Contributing.md +++ b/Sources/SwiftDevKit/Documentation.docc/Contributing.md @@ -93,4 +93,3 @@ brew install swiftlint swiftformat swiftgen - [Swift API Design Guidelines](https://swift.org/documentation/api-design-guidelines/) - [DocC Documentation](https://developer.apple.com/documentation/docc) -- [Swift Testing Documentation](https://github.com/apple/swift-testing) \ No newline at end of file diff --git a/Sources/SwiftDevKit/Documentation.docc/Conversion.md b/Sources/SwiftDevKit/Documentation.docc/Conversion.md new file mode 100644 index 0000000..4a613aa --- /dev/null +++ b/Sources/SwiftDevKit/Documentation.docc/Conversion.md @@ -0,0 +1,118 @@ +# String and Date Conversion + +@Metadata { + @TechnologyRoot +} + +Learn about SwiftDevKit's string and date conversion capabilities. + +## Overview + +SwiftDevKit provides robust and type-safe ways to convert values to and from strings through the ``StringConvertible`` and ``DateConvertible`` protocols. These features are particularly useful when working with data serialization, user input, API communication, or any scenario where you need to convert between strings and other types. + +## Topics + +### Essentials + +- ``StringConvertible`` +- ``StringConversionError`` +- ``DateConvertible`` +- ``DateConversionError`` +- ``DateFormat`` + +### Common Use Cases + +- Converting numeric types to strings +- Parsing strings into numeric types +- Thread-safe date formatting +- HTTP date handling +- ISO8601 date conversion +- Custom date formats +- Adding string conversion to custom types +- Handling conversion errors + +### Code Examples + +```swift +// Converting numbers to strings +let number = 42 +let string = try await number.toString() // "42" + +// Converting strings to numbers +let parsed = try await Int.fromString("42") // 42 + +// Custom type conversion +struct User: StringConvertible { + let id: Int + + func toString() async throws -> String { + return String(id) + } + + static func fromString(_ string: String) async throws -> Self { + guard let id = Int(string) else { + throw StringConversionError.invalidInput(string) + } + return User(id: id) + } +} + +// Date conversion +let date = Date() +// ISO8601 format +let iso8601 = try await date.toISO8601() // "2025-01-16T15:30:00Z" +// HTTP date format +let httpDate = try await date.toHTTPDate() // "Wed, 16 Jan 2025 15:30:00 GMT" +// Custom format +let custom = try await date.toString(format: DateFormat.shortDate) // "01/16/2025" +``` + +### Error Handling + +The framework provides comprehensive error handling through ``StringConversionError`` and ``DateConversionError``: + +```swift +// String conversion errors +do { + let value = try await Int.fromString("not a number") +} catch let error as StringConversionError { + switch error { + case .invalidInput(let value): + print("Invalid input: \(value)") + } +} + +// Date conversion errors +do { + let date = try await Date.fromString("invalid", format: DateFormat.iso8601) +} catch let error as DateConversionError { + switch error { + case .invalidFormat(let value): + print("Invalid date format: \(value)") + case .invalidComponents: + print("Invalid date components") + case .invalidFormatString(let format): + print("Invalid format string: \(format)") + case .custom(let message): + print(message) + } +} +``` + +### Best Practices + +- Always handle potential conversion errors using try-catch +- Provide clear error messages in custom implementations +- Document expected string formats for custom types +- Use async/await for consistency with the protocols +- Leverage predefined date formats for common use cases +- Take advantage of thread-safe date formatting + +## See Also + +- ``StringConvertible`` +- ``StringConversionError`` +- ``DateConvertible`` +- ``DateConversionError`` +- ``DateFormat`` +- \ No newline at end of file diff --git a/Sources/SwiftDevKit/Documentation.docc/GettingStarted.md b/Sources/SwiftDevKit/Documentation.docc/GettingStarted.md index a27e5ab..b63c19e 100644 --- a/Sources/SwiftDevKit/Documentation.docc/GettingStarted.md +++ b/Sources/SwiftDevKit/Documentation.docc/GettingStarted.md @@ -1,49 +1,122 @@ # Getting Started with SwiftDevKit -Learn how to integrate and start using SwiftDevKit in your projects. +This guide helps you get started with string and date conversion features in SwiftDevKit. ## Overview -SwiftDevKit is designed to be easy to integrate and use in your Swift projects. This guide will help you get started with the basic setup and show you how to use some of the core features. +SwiftDevKit provides simple and type-safe ways to convert values to and from strings, including specialized support for date formatting with thread safety. ## Requirements -- iOS 13.0+ / macOS 10.15+ / tvOS 13.0+ / watchOS 6.0+ -- Xcode 16.0+ +- iOS 16.0+ / macOS 13.0+ / tvOS 16.0+ / watchOS 9.0+ - Swift 5.9+ ## Basic Setup -First, import SwiftDevKit in your source files: +Add SwiftDevKit to your project using Swift Package Manager: ```swift -import SwiftDevKit +dependencies: [ + .package(url: "https://github.com/yourusername/SwiftDevKit.git", from: "1.0.0") +] ``` -### Version Check +## Using String Conversion -You can verify the version of SwiftDevKit you're using: +### Built-in Types + +SwiftDevKit extends common numeric types with string conversion capabilities: ```swift -let version = SwiftDevKit.version -print("Using SwiftDevKit version: \(version)") +let number = 42 +let string = try await number.toString() // "42" +let backToNumber = try await Int.fromString("42") // 42 ``` -### Platform Support +### Custom Types -SwiftDevKit automatically validates if your current environment meets the minimum requirements: +Make your types string-convertible by conforming to `StringConvertible`: ```swift -if SwiftDevKit.isEnvironmentValid { - // Safe to use SwiftDevKit features -} else { - // Handle unsupported platform +struct User: StringConvertible { + let id: Int + + func toString() async throws -> String { + return String(id) + } + + static func fromString(_ string: String) async throws -> Self { + guard let id = Int(string) else { + throw StringConversionError.invalidInput(string) + } + return User(id: id) + } +} +``` + +## Date Conversion + +SwiftDevKit provides thread-safe date formatting with common predefined formats: + +```swift +// Using ISO8601 +let date = Date() +let iso8601 = try await date.toISO8601() // "2025-01-16T15:30:00Z" +let parsedDate = try await Date.fromISO8601(iso8601) + +// Using custom formats +let shortDate = try await date.toString(format: DateFormat.shortDate) // "01/16/2025" +let httpDate = try await date.toHTTPDate() // "Wed, 16 Jan 2025 15:30:00 GMT" +``` + +### Predefined Date Formats + +SwiftDevKit includes commonly used date formats: +- `DateFormat.iso8601` - Standard format for APIs +- `DateFormat.http` - For HTTP headers +- `DateFormat.shortDate` - Compact display format +- `DateFormat.longDate` - Human-readable format +- `DateFormat.dateTime` - Combined date and time +- And more... + +### Thread Safety + +All date conversion operations are thread-safe and can be called concurrently from multiple tasks. The framework uses an actor-based formatter cache to ensure optimal performance while maintaining thread safety. + +## Error Handling + +Handle conversion errors appropriately: + +```swift +do { + let value = try await Int.fromString("not a number") +} catch let error as StringConversionError { + switch error { + case .invalidInput(let value): + print("Invalid input: \(value)") + } +} + +do { + let date = try await Date.fromString("invalid", format: DateFormat.iso8601) +} catch let error as DateConversionError { + switch error { + case .invalidFormat(let value): + print("Invalid date format: \(value)") + case .invalidComponents: + print("Invalid date components") + case .invalidFormatString(let format): + print("Invalid format string: \(format)") + case .custom(let message): + print(message) + } } ``` ## Next Steps -- Check out the guide for detailed installation instructions -- Explore the different categories of utilities available -- Read through the API documentation for specific features -- Visit our [GitHub repository](https://github.com/owdax/SwiftDevKit) for the latest updates \ No newline at end of file +- Explore the API documentation for more details +- Check out example projects in the repository +- Join our community discussions + +For more information, visit the [SwiftDevKit Documentation](https://github.com/yourusername/SwiftDevKit). \ No newline at end of file diff --git a/Sources/SwiftDevKit/Resources/.gitkeep b/Sources/SwiftDevKit/Resources/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/Tests/SwiftDevKitTests/StringConversion/DateConversionTests.swift b/Tests/SwiftDevKitTests/StringConversion/DateConversionTests.swift new file mode 100644 index 0000000..dceeabe --- /dev/null +++ b/Tests/SwiftDevKitTests/StringConversion/DateConversionTests.swift @@ -0,0 +1,52 @@ +// DateConversionTests.swift +// SwiftDevKit +// +// Copyright (c) 2025 owdax and The SwiftDevKit Contributors +// MIT License - https://opensource.org/licenses/MIT + +import Foundation +import SwiftDevKit +import Testing + +@Suite("Date Conversion Tests") +struct DateConversionTests { + @Test("Test date string conversion with various formats") + func testDateStringConversion() async throws { + let date = Date(timeIntervalSince1970: 1_705_363_800) // 2024-01-16 00:10:00 UTC + + // Test ISO8601 + let iso8601 = try await date.toISO8601() + #expect(iso8601 == "2024-01-16T00:10:00+0000") + let parsedISO = try await Date.fromISO8601(iso8601) + #expect(parsedISO.timeIntervalSince1970 == date.timeIntervalSince1970) + + // Test HTTP date + let httpDate = try await date.toHTTPDate() + #expect(httpDate == "Tue, 16 Jan 2024 00:10:00 GMT") + let parsedHTTP = try await Date.fromHTTPDate(httpDate) + #expect(parsedHTTP.timeIntervalSince1970 == date.timeIntervalSince1970) + + // Test custom format + let shortDate = try await date.toString(format: DateFormat.shortDate) + #expect(shortDate == "01/16/2024") + let parsedShort = try await Date.fromString(shortDate, format: DateFormat.shortDate) + let calendar = Calendar.current + let components = calendar.dateComponents([.year, .month, .day], from: parsedShort) + #expect(components.year == 2024) + #expect(components.month == 1) + #expect(components.day == 16) + } + + @Test("Test date conversion error handling") + func testDateConversionErrors() async throws { + // Test invalid format + await #expect(throws: DateConversionError.invalidFormat("invalid")) { + try await Date.fromString("invalid", format: DateFormat.iso8601) + } + + // Test invalid format string + await #expect(throws: Error.self) { + try await Date.fromString("2024-01-16", format: "invalid") + } + } +} diff --git a/Tests/SwiftDevKitTests/StringConversion/StringConversionTests.swift b/Tests/SwiftDevKitTests/StringConversion/StringConversionTests.swift new file mode 100644 index 0000000..20a4446 --- /dev/null +++ b/Tests/SwiftDevKitTests/StringConversion/StringConversionTests.swift @@ -0,0 +1,94 @@ +// StringConversionTests.swift +// SwiftDevKit +// +// Copyright (c) 2025 owdax and The SwiftDevKit Contributors +// MIT License - https://opensource.org/licenses/MIT + +import Foundation +import SwiftDevKit +import Testing + +/// Test suite for string conversion functionality. +/// Following the Arrange-Act-Assert pattern and including both positive and negative test cases. +struct StringConversionTests { + /// Tests string conversion for various numeric types. + func testNumericStringConversion() throws { + // MARK: - Integer Tests + + // Test valid integer conversion + let intValue = try Int.fromString("42") + #expect(intValue == 42) + #expect(try intValue.toString() == "42") + + // Test invalid integer input + #expect(throws: StringConversionError.invalidInput("invalid")) { + try Int.fromString("invalid") + } + + // MARK: - Double Tests + + // Test valid double conversion + let doubleValue = try Double.fromString("3.14") + #expect(doubleValue == 3.14) + #expect(try doubleValue.toString() == "3.14") + + // Test invalid double input + #expect(throws: StringConversionError.invalidInput("invalid")) { + try Double.fromString("invalid") + } + + // MARK: - Float Tests + + // Test valid float conversion + let floatValue = try Float.fromString("1.23") + #expect(floatValue == 1.23) + #expect(try floatValue.toString() == "1.23") + + // Test invalid float input + #expect(throws: StringConversionError.invalidInput("invalid")) { + try Float.fromString("invalid") + } + + // MARK: - Edge Cases + + // Test maximum values + let maxInt = try Int.fromString(String(Int.max)) + #expect(maxInt == Int.max) + + // Test minimum values + let minInt = try Int.fromString(String(Int.min)) + #expect(minInt == Int.min) + + // Test zero + let zero = try Int.fromString("0") + #expect(zero == 0) + + // Test negative numbers + let negative = try Int.fromString("-42") + #expect(negative == -42) + } + + /// Tests error handling in string conversion. + func testStringConversionErrors() throws { + // Test empty string + #expect(throws: StringConversionError.invalidInput("")) { + try Int.fromString("") + } + + // Test whitespace + #expect(throws: StringConversionError.invalidInput(" ")) { + try Int.fromString(" ") + } + + // Test non-numeric characters + #expect(throws: StringConversionError.invalidInput("12.34.56")) { + try Int.fromString("12.34.56") + } + + // Test overflow scenarios + let overflowString = "999999999999999999999999999999" + #expect(throws: StringConversionError.invalidInput(overflowString)) { + try Int.fromString(overflowString) + } + } +} diff --git a/Tests/SwiftDevKitTests/SwiftDevKitTests.swift b/Tests/SwiftDevKitTests/SwiftDevKitTests.swift index 0a53dc9..9e14e3d 100644 --- a/Tests/SwiftDevKitTests/SwiftDevKitTests.swift +++ b/Tests/SwiftDevKitTests/SwiftDevKitTests.swift @@ -4,8 +4,9 @@ // Copyright (c) 2025 owdax and The SwiftDevKit Contributors // MIT License - https://opensource.org/licenses/MIT +import Foundation +import SwiftDevKit import Testing -@testable import SwiftDevKit // MARK: - Core Tests