Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c035d64
feat: Add string conversion module with SOLID principles, tests, and …
owdax Jan 15, 2025
87a921f
style: Fix case alignment in StringConversionError
owdax Jan 15, 2025
5832dea
style: Fix switch case alignment in StringConversionError
owdax Jan 15, 2025
3c84370
refactor: Separate sync and async string conversion protocols - Make …
owdax Jan 16, 2025
936fa6d
docs: Enhance date conversion documentation and fix linting issues - …
owdax Jan 16, 2025
3515e62
chore: Remove date conversion for separate PR
owdax Jan 16, 2025
0214fc0
refactor: Reorganize conversion modules and documentation
owdax Jan 16, 2025
67ea84b
style: Fix case alignment in switch statements
owdax Jan 16, 2025
ca9a2fd
style: Update case alignment style in switch statements
owdax Jan 16, 2025
4ca7983
chore: update Package.swift for Swift 6 compatibility
owdax Jan 16, 2025
a760e02
chore: update SwiftLint configuration to resolve case alignment warnings
owdax Jan 16, 2025
bbe0083
chore: fix duplicate unused_import rule in SwiftLint config
owdax Jan 16, 2025
59874d0
ci: update GitHub Actions workflow for Swift 6
owdax Jan 16, 2025
062c449
ci: update Github Actions for swift 6
owdax Jan 16, 2025
f8fed2d
fix: Add Resources directory and update package configuration
owdax Jan 16, 2025
a8006a0
fix: Add CodeQL initialization step to GitHub Actions workflow
owdax Jan 16, 2025
cd2b61d
chore: Update CodeQL actions to v3
owdax Jan 16, 2025
1174596
fix: Add Codecov repository token to prevent rate limiting
owdax Jan 16, 2025
0393da2
chore: update codecov to v4
owdax Jan 16, 2025
857b7be
chore: update codeql to v3
owdax Jan 16, 2025
5146b80
ci: add codecov secret
owdax Jan 16, 2025
743b8a0
chore: update codecov configurations
owdax Jan 16, 2025
7e52f0d
fix: Update Codecov configuration with correct input parameters
owdax Jan 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 30 additions & 14 deletions .github/workflows/swift.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@ on:
branches: [ "master" ]
workflow_dispatch:

env:
DEVELOPER_DIR: /Applications/Xcode_15.2.app/Contents/Developer

jobs:
analyze:
name: Analyze
Expand All @@ -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"

Expand All @@ -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
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ excluded:
disabled_rules:
- trailing_comma
- comment_spacing
- switch_case_alignment

opt_in_rules:
- array_init
Expand Down
18 changes: 0 additions & 18 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 22 additions & 14 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -19,27 +20,34 @@ 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: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
// 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)),
]),
])
110 changes: 110 additions & 0 deletions Sources/SwiftDevKit/Conversion/Date+Convertible.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
Loading