@@ -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 , * )
0 commit comments