Skip to content

Commit 961bd28

Browse files
authored
Merge pull request #1390 from swiftlang/automerge/merge-main-2025-06-30_09-01
Merge `main` into `release/6.2`
2 parents 7d9957d + eb084da commit 961bd28

34 files changed

+2646
-2373
lines changed

Proposals/0011-concurrency-safe-notifications.md

Lines changed: 39 additions & 76 deletions
Large diffs are not rendered by default.

Proposals/0022-writing-direction-attribute.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Writing Direction Attribute
22

3-
* Proposal: [SF-0022](NNNN-writing-direction-attribute.md)
3+
* Proposal: [SF-0022](0022-writing-direction-attribute.md)
44
* Authors: [Max Obermeier](https://github.com/themomax)
55
* Review Manager: [Tina L](https://github.com/itingliu)
66
* Status: **Approved and Implemented**

Sources/FoundationEssentials/FileManager/FileManager+Files.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -966,14 +966,14 @@ extension _FileManagerImpl {
966966
if let date = attributes[.modificationDate] as? Date {
967967
let (isecs, fsecs) = modf(date.timeIntervalSince1970)
968968
if let tv_sec = time_t(exactly: isecs),
969-
let tv_nsec = Int(exactly: round(fsecs * 1000000000.0)) {
970-
var timespecs = (timespec(), timespec())
971-
timespecs.0.tv_sec = tv_sec
972-
timespecs.0.tv_nsec = tv_nsec
973-
timespecs.1 = timespecs.0
974-
try withUnsafePointer(to: timespecs) {
975-
try $0.withMemoryRebound(to: timespec.self, capacity: 2) {
976-
if utimensat(AT_FDCWD, fileSystemRepresentation, $0, 0) != 0 {
969+
let tv_usec = suseconds_t(exactly: round(fsecs * 1000000.0)) {
970+
var timevals = (timeval(), timeval())
971+
timevals.0.tv_sec = tv_sec
972+
timevals.0.tv_usec = tv_usec
973+
timevals.1 = timevals.0
974+
try withUnsafePointer(to: timevals) {
975+
try $0.withMemoryRebound(to: timeval.self, capacity: 2) {
976+
if utimes(fileSystemRepresentation, $0) != 0 {
977977
throw CocoaError.errorWithFilePath(path, errno: errno, reading: false)
978978
}
979979
}

Sources/FoundationEssentials/TimeZone/TimeZone.swift

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,6 @@ public struct TimeZone : Hashable, Equatable, Sendable {
7171
}
7272
}
7373

74-
internal init?(name: String) {
75-
// Try the cache first
76-
if let cached = TimeZoneCache.cache.fixed(name) {
77-
_tz = cached
78-
} else {
79-
return nil
80-
}
81-
}
82-
8374
/// Returns a time zone identified by a given abbreviation.
8475
///
8576
/// 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

Sources/FoundationEssentials/URL/URL_Swift.swift

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -299,16 +299,18 @@ internal final class _SwiftURL: Sendable, Hashable, Equatable {
299299
return builder.string
300300
}
301301
let baseParseInfo = baseURL._swiftURL?._parseInfo
302-
let baseEncodedComponents = baseParseInfo?.encodedComponents ?? []
303-
if let baseUser = baseURL.user(percentEncoded: !baseEncodedComponents.contains(.user)) {
302+
// If we aren't in the special case where we need the original
303+
// string, always leave the base components encoded.
304+
let baseComponentsToDecode = !original ? [] : baseParseInfo?.encodedComponents ?? []
305+
if let baseUser = baseURL.user(percentEncoded: !baseComponentsToDecode.contains(.user)) {
304306
builder.user = baseUser
305307
}
306-
if let basePassword = baseURL.password(percentEncoded: !baseEncodedComponents.contains(.password)) {
308+
if let basePassword = baseURL.password(percentEncoded: !baseComponentsToDecode.contains(.password)) {
307309
builder.password = basePassword
308310
}
309311
if let baseHost = baseParseInfo?.host {
310-
builder.host = baseEncodedComponents.contains(.host) && baseParseInfo!.didPercentEncodeHost ? Parser.percentDecode(baseHost) : String(baseHost)
311-
} else if let baseHost = baseURL.host(percentEncoded: !baseEncodedComponents.contains(.host)) {
312+
builder.host = baseComponentsToDecode.contains(.host) && baseParseInfo!.didPercentEncodeHost ? Parser.percentDecode(baseHost) : String(baseHost)
313+
} else if let baseHost = baseURL.host(percentEncoded: !baseComponentsToDecode.contains(.host)) {
312314
builder.host = baseHost
313315
}
314316
if let basePort = baseParseInfo?.portString {
@@ -317,8 +319,8 @@ internal final class _SwiftURL: Sendable, Hashable, Equatable {
317319
builder.portString = String(basePort)
318320
}
319321
if builder.path.isEmpty {
320-
builder.path = baseURL.path(percentEncoded: !baseEncodedComponents.contains(.path))
321-
if builder.query == nil, let baseQuery = baseURL.query(percentEncoded: !baseEncodedComponents.contains(.query)) {
322+
builder.path = baseURL.path(percentEncoded: !baseComponentsToDecode.contains(.path))
323+
if builder.query == nil, let baseQuery = baseURL.query(percentEncoded: !baseComponentsToDecode.contains(.query)) {
322324
builder.query = baseQuery
323325
}
324326
} else {
@@ -327,7 +329,7 @@ internal final class _SwiftURL: Sendable, Hashable, Equatable {
327329
} else if baseURL.hasAuthority && baseURL.path().isEmpty {
328330
"/" + builder.path
329331
} else {
330-
baseURL.path(percentEncoded: !baseEncodedComponents.contains(.path)).merging(relativePath: builder.path)
332+
baseURL.path(percentEncoded: !baseComponentsToDecode.contains(.path)).merging(relativePath: builder.path)
331333
}
332334
builder.path = newPath.removingDotSegments
333335
}

Sources/FoundationInternationalization/Locale/Locale+Components_ICU.swift

Lines changed: 177 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ extension Locale.Region {
263263

264264
internal static let _isoRegionCodes: [String] = {
265265
var status = U_ZERO_ERROR
266-
let types = [URGN_WORLD, URGN_CONTINENT, URGN_SUBCONTINENT, URGN_TERRITORY]
266+
let types = [URGN_WORLD, URGN_CONTINENT, URGN_SUBCONTINENT, URGN_TERRITORY, URGN_GROUPING]
267267
var codes: [String] = []
268268
for t in types {
269269
status = U_ZERO_ERROR
@@ -275,6 +275,182 @@ extension Locale.Region {
275275
}
276276
return codes
277277
}()
278+
279+
/// Categories of a region. See https://www.unicode.org/reports/tr35/tr35-35/tr35-info.html#Territory_Data
280+
@available(FoundationPreview 6.2, *)
281+
public struct Category: Codable, Sendable, Hashable, CustomDebugStringConvertible {
282+
public var debugDescription: String {
283+
switch inner {
284+
case .world:
285+
return "world"
286+
case .continent:
287+
return "continent"
288+
case .subcontinent:
289+
return "subcontinent"
290+
case .territory:
291+
return "territory"
292+
case .grouping:
293+
return "grouping"
294+
}
295+
}
296+
297+
enum Inner {
298+
case world
299+
case continent
300+
case subcontinent
301+
case territory
302+
case grouping
303+
}
304+
305+
var inner: Inner
306+
fileprivate init(_ inner: Inner) {
307+
self.inner = inner
308+
}
309+
310+
var uregionType: URegionType {
311+
switch inner {
312+
case .world:
313+
return URGN_WORLD
314+
case .continent:
315+
return URGN_CONTINENT
316+
case .subcontinent:
317+
return URGN_SUBCONTINENT
318+
case .territory:
319+
return URGN_TERRITORY
320+
case .grouping:
321+
return URGN_GROUPING
322+
}
323+
}
324+
325+
fileprivate init?(uregionType: URegionType) {
326+
switch uregionType {
327+
case URGN_CONTINENT:
328+
self = .init(.continent)
329+
case URGN_WORLD:
330+
self = .init(.world)
331+
case URGN_SUBCONTINENT:
332+
self = .init(.subcontinent)
333+
case URGN_TERRITORY:
334+
self = .init(.territory)
335+
case URGN_GROUPING:
336+
self = .init(.grouping)
337+
default:
338+
return nil
339+
}
340+
}
341+
342+
/// Category representing the whold world.
343+
public static let world: Category = Category(.world)
344+
345+
/// Category representing a continent, regions contained directly by world.
346+
public static let continent: Category = Category(.continent)
347+
348+
/// Category representing a sub-continent, regions contained directly by a continent.
349+
public static let subcontinent: Category = Category(.subcontinent)
350+
351+
/// Category representing a territory.
352+
public static let territory: Category = Category(.territory)
353+
354+
/// Category representing a grouping, regions that has a well defined membership.
355+
public static let grouping: Category = Category(.grouping)
356+
357+
public init(from decoder: Decoder) throws {
358+
let container = try decoder.singleValueContainer()
359+
let inner: Inner
360+
switch try container.decode(Int.self) {
361+
case 0:
362+
inner = .world
363+
case 1:
364+
inner = .continent
365+
case 2:
366+
inner = .subcontinent
367+
case 3:
368+
inner = .territory
369+
case 4:
370+
inner = .grouping
371+
default:
372+
throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "Unknown Category"))
373+
}
374+
self = .init(inner)
375+
}
376+
377+
public func encode(to encoder: Encoder) throws {
378+
var container = encoder.singleValueContainer()
379+
switch inner {
380+
case .world:
381+
try container.encode(0)
382+
case .continent:
383+
try container.encode(1)
384+
case .subcontinent:
385+
try container.encode(2)
386+
case .territory:
387+
try container.encode(3)
388+
case .grouping:
389+
try container.encode(4)
390+
391+
}
392+
}
393+
}
394+
395+
/// An array of regions matching the specified categories.
396+
@available(FoundationPreview 6.2, *)
397+
public static func isoRegions(ofCategory category: Category) -> [Locale.Region] {
398+
var status = U_ZERO_ERROR
399+
let values = uregion_getAvailable(category.uregionType, &status)
400+
guard let values, status.isSuccess else {
401+
return []
402+
}
403+
return ICU.Enumerator(enumerator: values).elements.map { Locale.Region($0) }
404+
}
405+
406+
/// The category of the region.
407+
@available(FoundationPreview 6.2, *)
408+
public var category: Category? {
409+
var status = U_ZERO_ERROR
410+
let icuRegion = uregion_getRegionFromCode(identifier, &status)
411+
guard status.isSuccess, let icuRegion else {
412+
return nil
413+
}
414+
let type = uregion_getType(icuRegion)
415+
return Category(uregionType: type)
416+
}
417+
418+
/// An array of the sub-regions, matching the specified category of the region.
419+
@available(FoundationPreview 6.2, *)
420+
public func subRegions(ofCategoy category: Category) -> [Locale.Region] {
421+
var status = U_ZERO_ERROR
422+
let icuRegion = uregion_getRegionFromCode(identifier, &status)
423+
guard let icuRegion, status.isSuccess else {
424+
return []
425+
}
426+
427+
status = U_ZERO_ERROR
428+
let enumerator = uregion_getContainedRegionsOfType(icuRegion, category.uregionType, &status)
429+
guard let enumerator, status.isSuccess else {
430+
return []
431+
}
432+
return ICU.Enumerator(enumerator: enumerator).elements.map { Locale.Region($0) }
433+
}
434+
435+
/// The subcontinent that contains this region, if any.
436+
@available(FoundationPreview 6.2, *)
437+
public var subcontinent: Locale.Region? {
438+
var status = U_ZERO_ERROR
439+
let icuRegion = uregion_getRegionFromCode(identifier, &status)
440+
guard let icuRegion, status.isSuccess else {
441+
return nil
442+
}
443+
444+
guard let containing = uregion_getContainingRegionOfType(icuRegion, URGN_SUBCONTINENT) else {
445+
return nil
446+
}
447+
448+
guard let code = String(validatingCString: uregion_getRegionCode(containing)) else {
449+
return nil
450+
}
451+
452+
return Locale.Region(code)
453+
}
278454
}
279455

280456
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)

Sources/FoundationInternationalization/TimeZone/TimeZone_ObjC.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ extension NSTimeZone {
2323
static func _timeZoneWith(name: String, data: Data?) -> _NSSwiftTimeZone? {
2424
if let data {
2525
// We don't cache data-based TimeZones
26-
guard let tz = TimeZone(name: name) else {
26+
guard let tz = TimeZone(identifier: name) else {
2727
return nil
2828
}
2929
return _NSSwiftTimeZone(timeZone: tz, data: data)

Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -841,21 +841,6 @@ private struct FileManagerTests {
841841
}
842842
}
843843

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

0 commit comments

Comments
 (0)