Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
115 changes: 39 additions & 76 deletions Proposals/0011-concurrency-safe-notifications.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Proposals/0022-writing-direction-attribute.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Writing Direction Attribute

* Proposal: [SF-0022](NNNN-writing-direction-attribute.md)
* Proposal: [SF-0022](0022-writing-direction-attribute.md)
* Authors: [Max Obermeier](https://github.com/themomax)
* Review Manager: [Tina L](https://github.com/itingliu)
* Status: **Approved and Implemented**
Expand Down
16 changes: 8 additions & 8 deletions Sources/FoundationEssentials/FileManager/FileManager+Files.swift
Original file line number Diff line number Diff line change
Expand Up @@ -966,14 +966,14 @@ extension _FileManagerImpl {
if let date = attributes[.modificationDate] as? Date {
let (isecs, fsecs) = modf(date.timeIntervalSince1970)
if let tv_sec = time_t(exactly: isecs),
let tv_nsec = Int(exactly: round(fsecs * 1000000000.0)) {
var timespecs = (timespec(), timespec())
timespecs.0.tv_sec = tv_sec
timespecs.0.tv_nsec = tv_nsec
timespecs.1 = timespecs.0
try withUnsafePointer(to: timespecs) {
try $0.withMemoryRebound(to: timespec.self, capacity: 2) {
if utimensat(AT_FDCWD, fileSystemRepresentation, $0, 0) != 0 {
let tv_usec = suseconds_t(exactly: round(fsecs * 1000000.0)) {
var timevals = (timeval(), timeval())
timevals.0.tv_sec = tv_sec
timevals.0.tv_usec = tv_usec
timevals.1 = timevals.0
try withUnsafePointer(to: timevals) {
try $0.withMemoryRebound(to: timeval.self, capacity: 2) {
if utimes(fileSystemRepresentation, $0) != 0 {
throw CocoaError.errorWithFilePath(path, errno: errno, reading: false)
}
}
Expand Down
9 changes: 0 additions & 9 deletions Sources/FoundationEssentials/TimeZone/TimeZone.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,6 @@ public struct TimeZone : Hashable, Equatable, Sendable {
}
}

internal init?(name: String) {
// Try the cache first
if let cached = TimeZoneCache.cache.fixed(name) {
_tz = cached
} else {
return nil
}
}

/// Returns a time zone identified by a given abbreviation.
///
/// In general, you are discouraged from using abbreviations except for unique instances such as "GMT". Time Zone abbreviations are not standardized and so a given abbreviation may have multiple meanings--for example, "EST" refers to Eastern Time in both the United States and Australia
Expand Down
18 changes: 10 additions & 8 deletions Sources/FoundationEssentials/URL/URL_Swift.swift
Original file line number Diff line number Diff line change
Expand Up @@ -299,16 +299,18 @@ internal final class _SwiftURL: Sendable, Hashable, Equatable {
return builder.string
}
let baseParseInfo = baseURL._swiftURL?._parseInfo
let baseEncodedComponents = baseParseInfo?.encodedComponents ?? []
if let baseUser = baseURL.user(percentEncoded: !baseEncodedComponents.contains(.user)) {
// If we aren't in the special case where we need the original
// string, always leave the base components encoded.
let baseComponentsToDecode = !original ? [] : baseParseInfo?.encodedComponents ?? []
if let baseUser = baseURL.user(percentEncoded: !baseComponentsToDecode.contains(.user)) {
builder.user = baseUser
}
if let basePassword = baseURL.password(percentEncoded: !baseEncodedComponents.contains(.password)) {
if let basePassword = baseURL.password(percentEncoded: !baseComponentsToDecode.contains(.password)) {
builder.password = basePassword
}
if let baseHost = baseParseInfo?.host {
builder.host = baseEncodedComponents.contains(.host) && baseParseInfo!.didPercentEncodeHost ? Parser.percentDecode(baseHost) : String(baseHost)
} else if let baseHost = baseURL.host(percentEncoded: !baseEncodedComponents.contains(.host)) {
builder.host = baseComponentsToDecode.contains(.host) && baseParseInfo!.didPercentEncodeHost ? Parser.percentDecode(baseHost) : String(baseHost)
} else if let baseHost = baseURL.host(percentEncoded: !baseComponentsToDecode.contains(.host)) {
builder.host = baseHost
}
if let basePort = baseParseInfo?.portString {
Expand All @@ -317,8 +319,8 @@ internal final class _SwiftURL: Sendable, Hashable, Equatable {
builder.portString = String(basePort)
}
if builder.path.isEmpty {
builder.path = baseURL.path(percentEncoded: !baseEncodedComponents.contains(.path))
if builder.query == nil, let baseQuery = baseURL.query(percentEncoded: !baseEncodedComponents.contains(.query)) {
builder.path = baseURL.path(percentEncoded: !baseComponentsToDecode.contains(.path))
if builder.query == nil, let baseQuery = baseURL.query(percentEncoded: !baseComponentsToDecode.contains(.query)) {
builder.query = baseQuery
}
} else {
Expand All @@ -327,7 +329,7 @@ internal final class _SwiftURL: Sendable, Hashable, Equatable {
} else if baseURL.hasAuthority && baseURL.path().isEmpty {
"/" + builder.path
} else {
baseURL.path(percentEncoded: !baseEncodedComponents.contains(.path)).merging(relativePath: builder.path)
baseURL.path(percentEncoded: !baseComponentsToDecode.contains(.path)).merging(relativePath: builder.path)
}
builder.path = newPath.removingDotSegments
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ extension Locale.Region {

internal static let _isoRegionCodes: [String] = {
var status = U_ZERO_ERROR
let types = [URGN_WORLD, URGN_CONTINENT, URGN_SUBCONTINENT, URGN_TERRITORY]
let types = [URGN_WORLD, URGN_CONTINENT, URGN_SUBCONTINENT, URGN_TERRITORY, URGN_GROUPING]
var codes: [String] = []
for t in types {
status = U_ZERO_ERROR
Expand All @@ -275,6 +275,182 @@ extension Locale.Region {
}
return codes
}()

/// Categories of a region. See https://www.unicode.org/reports/tr35/tr35-35/tr35-info.html#Territory_Data
@available(FoundationPreview 6.2, *)
public struct Category: Codable, Sendable, Hashable, CustomDebugStringConvertible {
public var debugDescription: String {
switch inner {
case .world:
return "world"
case .continent:
return "continent"
case .subcontinent:
return "subcontinent"
case .territory:
return "territory"
case .grouping:
return "grouping"
}
}

enum Inner {
case world
case continent
case subcontinent
case territory
case grouping
}

var inner: Inner
fileprivate init(_ inner: Inner) {
self.inner = inner
}

var uregionType: URegionType {
switch inner {
case .world:
return URGN_WORLD
case .continent:
return URGN_CONTINENT
case .subcontinent:
return URGN_SUBCONTINENT
case .territory:
return URGN_TERRITORY
case .grouping:
return URGN_GROUPING
}
}

fileprivate init?(uregionType: URegionType) {
switch uregionType {
case URGN_CONTINENT:
self = .init(.continent)
case URGN_WORLD:
self = .init(.world)
case URGN_SUBCONTINENT:
self = .init(.subcontinent)
case URGN_TERRITORY:
self = .init(.territory)
case URGN_GROUPING:
self = .init(.grouping)
default:
return nil
}
}

/// Category representing the whold world.
public static let world: Category = Category(.world)

/// Category representing a continent, regions contained directly by world.
public static let continent: Category = Category(.continent)

/// Category representing a sub-continent, regions contained directly by a continent.
public static let subcontinent: Category = Category(.subcontinent)

/// Category representing a territory.
public static let territory: Category = Category(.territory)

/// Category representing a grouping, regions that has a well defined membership.
public static let grouping: Category = Category(.grouping)

public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let inner: Inner
switch try container.decode(Int.self) {
case 0:
inner = .world
case 1:
inner = .continent
case 2:
inner = .subcontinent
case 3:
inner = .territory
case 4:
inner = .grouping
default:
throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "Unknown Category"))
}
self = .init(inner)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch inner {
case .world:
try container.encode(0)
case .continent:
try container.encode(1)
case .subcontinent:
try container.encode(2)
case .territory:
try container.encode(3)
case .grouping:
try container.encode(4)

}
}
}

/// An array of regions matching the specified categories.
@available(FoundationPreview 6.2, *)
public static func isoRegions(ofCategory category: Category) -> [Locale.Region] {
var status = U_ZERO_ERROR
let values = uregion_getAvailable(category.uregionType, &status)
guard let values, status.isSuccess else {
return []
}
return ICU.Enumerator(enumerator: values).elements.map { Locale.Region($0) }
}

/// The category of the region.
@available(FoundationPreview 6.2, *)
public var category: Category? {
var status = U_ZERO_ERROR
let icuRegion = uregion_getRegionFromCode(identifier, &status)
guard status.isSuccess, let icuRegion else {
return nil
}
let type = uregion_getType(icuRegion)
return Category(uregionType: type)
}

/// An array of the sub-regions, matching the specified category of the region.
@available(FoundationPreview 6.2, *)
public func subRegions(ofCategoy category: Category) -> [Locale.Region] {
var status = U_ZERO_ERROR
let icuRegion = uregion_getRegionFromCode(identifier, &status)
guard let icuRegion, status.isSuccess else {
return []
}

status = U_ZERO_ERROR
let enumerator = uregion_getContainedRegionsOfType(icuRegion, category.uregionType, &status)
guard let enumerator, status.isSuccess else {
return []
}
return ICU.Enumerator(enumerator: enumerator).elements.map { Locale.Region($0) }
}

/// The subcontinent that contains this region, if any.
@available(FoundationPreview 6.2, *)
public var subcontinent: Locale.Region? {
var status = U_ZERO_ERROR
let icuRegion = uregion_getRegionFromCode(identifier, &status)
guard let icuRegion, status.isSuccess else {
return nil
}

guard let containing = uregion_getContainingRegionOfType(icuRegion, URGN_SUBCONTINENT) else {
return nil
}

guard let code = String(validatingCString: uregion_getRegionCode(containing)) else {
return nil
}

return Locale.Region(code)
}
}

@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ extension NSTimeZone {
static func _timeZoneWith(name: String, data: Data?) -> _NSSwiftTimeZone? {
if let data {
// We don't cache data-based TimeZones
guard let tz = TimeZone(name: name) else {
guard let tz = TimeZone(identifier: name) else {
return nil
}
return _NSSwiftTimeZone(timeZone: tz, data: data)
Expand Down
15 changes: 0 additions & 15 deletions Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -841,21 +841,6 @@ private struct FileManagerTests {
}
}

@Test func roundtripModificationDate() async throws {
try await FilePlayground {
"foo"
}.test {
// Precision of modification dates is dependent not only on the platform, but on the file system used
// Ensure that roundtripping supports at least millisecond-level precision, but some file systems may support more up to nanosecond precision
let date = Date(timeIntervalSince1970: 10.003)
try $0.setAttributes([.modificationDate : date], ofItemAtPath: "foo")
let readValue = try #require($0.attributesOfItem(atPath: "foo")[.modificationDate], "No value provided for file modification date")
let possibleDate = readValue as? Date
let readDate = try #require(possibleDate, "File modification date was not a date (found type \(type(of: readValue)))")
#expect(abs(readDate.timeIntervalSince1970 - date.timeIntervalSince1970) <= 0.0005, "File modification date (\(readDate.timeIntervalSince1970)) does not match expected modification date (\(date.timeIntervalSince1970))")
}
}

@Test func implicitlyConvertibleFileAttributes() async throws {
try await FilePlayground {
File("foo", attributes: [.posixPermissions : UInt16(0o644)])
Expand Down
Loading