python-intl 0.6.0__tar.gz → 0.8.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-intl
3
- Version: 0.6.0
3
+ Version: 0.8.0
4
4
  Summary: Python implementation of the Intl JavaScript API
5
5
  Author: David Danier
6
6
  Author-email: David Danier <david.danier@gmail.com>
@@ -54,6 +54,23 @@ Intl.DateTimeFormatOptions(time_zone_name="short_offset").to_json()
54
54
 
55
55
  ## Available `Intl` classes
56
56
 
57
+ ### `Intl.Locale`
58
+
59
+ #### Example usage
60
+
61
+ ```python
62
+ import datetime as dt
63
+ import python_intl as Intl
64
+
65
+ locale = Intl.DateTimeFormat("de-DE")
66
+ ```
67
+
68
+ #### Compatibility
69
+
70
+ `Locale` currently does not support any methods or options. It is mainly there to
71
+ allow using the locale class instead of a string with the other classes. There will
72
+ be more later.
73
+
57
74
  ### `Intl.DateTimeFormat`
58
75
 
59
76
  #### Example usage
@@ -87,6 +104,20 @@ formatter.format_range(datetime, datetime_till)
87
104
 
88
105
  ### `Intl.Collator`
89
106
 
107
+ #### Example usage
108
+
109
+ ```python
110
+ import python_intl as Intl
111
+
112
+ collator = Intl.Collator("de-DE", {"numeric": True})
113
+ collator.compare("10", "9")
114
+ # Result = 1, which means 9 comes before 10
115
+
116
+ # You can also use sorted, although this is not available in JavaScript
117
+ collator.sorted(["10", "9"])
118
+ # Result = ['9', '10']
119
+ ```
120
+
90
121
  #### Compatibility
91
122
 
92
123
  | Method | Status | Python name |
@@ -95,6 +126,12 @@ formatter.format_range(datetime, datetime_till)
95
126
 
96
127
  **Note:** Not all options are currently supported.
97
128
 
129
+ #### Additional methods
130
+
131
+ * `Collator.sorted`: Somewhat like `sorted`, but uses the collator for comparison. If you want
132
+ to sort complex datastructures you can provide a `key` parameter (like with `sorted`) to
133
+ get a comparable string value (for example by returning an attribute).
134
+
98
135
  ## Installation
99
136
 
100
137
  Be sure to be able to install `PyICU`, see the installation docs there:
@@ -105,3 +142,13 @@ setting `PKG_CONFIG_PATH` accordingly helps most of the time I guess.
105
142
 
106
143
  When this is done you should be able to install `python-intl` using any package
107
144
  manager, like `pip install python-intl` or `uv add python-intl`.
145
+
146
+ ## Contributing
147
+
148
+ If you want to contribute to this project, feel free to just fork the project,
149
+ create a dev branch in your fork and then create a pull request (PR). If you
150
+ are unsure about whether your changes really suits the project please create an
151
+ issue first, to talk about this.
152
+
153
+ Please do not contribute AI generated code unless we explicitly talked about
154
+ this and agreed upon doing so first. In general I do not want AI contributions.
@@ -41,6 +41,23 @@ Intl.DateTimeFormatOptions(time_zone_name="short_offset").to_json()
41
41
 
42
42
  ## Available `Intl` classes
43
43
 
44
+ ### `Intl.Locale`
45
+
46
+ #### Example usage
47
+
48
+ ```python
49
+ import datetime as dt
50
+ import python_intl as Intl
51
+
52
+ locale = Intl.DateTimeFormat("de-DE")
53
+ ```
54
+
55
+ #### Compatibility
56
+
57
+ `Locale` currently does not support any methods or options. It is mainly there to
58
+ allow using the locale class instead of a string with the other classes. There will
59
+ be more later.
60
+
44
61
  ### `Intl.DateTimeFormat`
45
62
 
46
63
  #### Example usage
@@ -74,6 +91,20 @@ formatter.format_range(datetime, datetime_till)
74
91
 
75
92
  ### `Intl.Collator`
76
93
 
94
+ #### Example usage
95
+
96
+ ```python
97
+ import python_intl as Intl
98
+
99
+ collator = Intl.Collator("de-DE", {"numeric": True})
100
+ collator.compare("10", "9")
101
+ # Result = 1, which means 9 comes before 10
102
+
103
+ # You can also use sorted, although this is not available in JavaScript
104
+ collator.sorted(["10", "9"])
105
+ # Result = ['9', '10']
106
+ ```
107
+
77
108
  #### Compatibility
78
109
 
79
110
  | Method | Status | Python name |
@@ -82,6 +113,12 @@ formatter.format_range(datetime, datetime_till)
82
113
 
83
114
  **Note:** Not all options are currently supported.
84
115
 
116
+ #### Additional methods
117
+
118
+ * `Collator.sorted`: Somewhat like `sorted`, but uses the collator for comparison. If you want
119
+ to sort complex datastructures you can provide a `key` parameter (like with `sorted`) to
120
+ get a comparable string value (for example by returning an attribute).
121
+
85
122
  ## Installation
86
123
 
87
124
  Be sure to be able to install `PyICU`, see the installation docs there:
@@ -92,3 +129,13 @@ setting `PKG_CONFIG_PATH` accordingly helps most of the time I guess.
92
129
 
93
130
  When this is done you should be able to install `python-intl` using any package
94
131
  manager, like `pip install python-intl` or `uv add python-intl`.
132
+
133
+ ## Contributing
134
+
135
+ If you want to contribute to this project, feel free to just fork the project,
136
+ create a dev branch in your fork and then create a pull request (PR). If you
137
+ are unsure about whether your changes really suits the project please create an
138
+ issue first, to talk about this.
139
+
140
+ Please do not contribute AI generated code unless we explicitly talked about
141
+ this and agreed upon doing so first. In general I do not want AI contributions.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "python-intl"
3
- version = "0.6.0"
3
+ version = "0.8.0"
4
4
  description = "Python implementation of the Intl JavaScript API"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -35,7 +35,6 @@ markers = [
35
35
 
36
36
  [tool.ruff]
37
37
  line-length = 115
38
- target-version = "py312"
39
38
  output-format = "grouped"
40
39
 
41
40
  [tool.ruff.lint]
@@ -69,6 +68,7 @@ ignore = [
69
68
  "B008",
70
69
  "F405",
71
70
  "F821",
71
+ "TD003",
72
72
  ]
73
73
 
74
74
  [tool.ruff.lint.per-file-ignores]
@@ -88,6 +88,9 @@ ignore = [
88
88
  combine-as-imports = true
89
89
  force-wrap-aliases = true
90
90
 
91
+ [tool.pyright]
92
+ pythonVersion = "3.12"
93
+
91
94
  [tool.uv.build-backend]
92
95
  module-name = "python_intl"
93
96
  module-root = ""
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "python-intl"
3
- version = "0.6.0"
3
+ version = "0.8.0"
4
4
  description = "Python implementation of the Intl JavaScript API"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -36,12 +36,11 @@ markers = [
36
36
 
37
37
  [tool.ruff]
38
38
  line-length = 115
39
- target-version = "py312"
40
39
  output-format = "grouped"
41
40
 
42
41
  [tool.ruff.lint]
43
42
  select = ["F","E","W","C","I","N","UP","ANN","S","B","A","COM","C4","T20","PT","ARG","TD","RUF"]
44
- ignore = ["A001","A002","A003","ANN401","C901","N8","B008","F405","F821"]
43
+ ignore = ["A001","A002","A003","ANN401","C901","N8","B008","F405","F821","TD003"]
45
44
 
46
45
  [tool.ruff.lint.per-file-ignores]
47
46
  "__init__.py" = ["F401"]
@@ -52,6 +51,9 @@ ignore = ["A001","A002","A003","ANN401","C901","N8","B008","F405","F821"]
52
51
  combine-as-imports = true
53
52
  force-wrap-aliases = true
54
53
 
54
+ [tool.pyright]
55
+ pythonVersion = "3.12"
56
+
55
57
  [build-system]
56
58
  requires = ["uv_build>=0.12.3,<0.13.0"]
57
59
  build-backend = "uv_build"
@@ -0,0 +1,34 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ if TYPE_CHECKING:
4
+ from typing import Literal
5
+
6
+ type LocaleMatcherT = Literal["best fit", "lookup"]
7
+ type Hour12T = bool
8
+ type HourCycleT = Literal["h11", "h12", "h23", "h24"]
9
+
10
+ type EraFormatT = Literal["long", "short", "narrow"]
11
+ type YearFormatT = Literal["numeric", "2-digit"]
12
+ type MonthFormatT = Literal["numeric", "2-digit", "long", "short", "narrow"]
13
+ type WeekdayFormatT = Literal["long", "short", "narrow"]
14
+ type DayFormatT = Literal["numeric", "2-digit"]
15
+ type DayPeriodFormatT = Literal["long", "short", "narrow"]
16
+ type HourFormatT = Literal["numeric", "2-digit"]
17
+ type MinuteFormatT = Literal["numeric", "2-digit"]
18
+ type SecondFormatT = Literal["numeric", "2-digit"]
19
+ type FractionSecondDigitsFormatT = Literal[1, 2, 3]
20
+ type TimezoneNameFormatT = Literal[
21
+ "short",
22
+ "long",
23
+ "short_offset",
24
+ "long_offset",
25
+ "short_generic",
26
+ "long_generic",
27
+ ]
28
+
29
+ # type UsageT = Literal["sort", "search"]
30
+ # type CollationT = Literal["emoji", "pinyin", "stroke"]
31
+ type CaseFirstT = Literal["upper", "lower", "false"]
32
+ type SensitivityT = Literal["base", "accent", "case", "variant"]
33
+
34
+ type ComparisonResultT = Literal[-1, 0, 1]
@@ -1,24 +1,19 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import dataclasses
4
+ import functools
5
+ from collections.abc import Callable, Iterable
4
6
  from functools import cached_property
5
- from typing import TYPE_CHECKING, Literal
7
+ from typing import TYPE_CHECKING, overload
6
8
 
7
9
  import icu # type: ignore[import-untyped]
8
10
 
9
- if TYPE_CHECKING:
10
- from typing import NotRequired, TypedDict
11
-
11
+ from .locale import Locale
12
12
 
13
13
  if TYPE_CHECKING:
14
- from ._types import LocaleMatcherT
15
-
16
- # type UsageT = Literal["sort", "search"]
17
- # type CollationT = Literal["emoji", "pinyin", "stroke"]
18
- type CaseFirstT = Literal["upper", "lower", "false"]
19
- type SensitivityT = Literal["base", "accent", "case", "variant"]
14
+ from typing import NotRequired, TypedDict
20
15
 
21
- type ComparisonResultT = Literal[-1, 0, 1]
16
+ from ._types import CaseFirstT, ComparisonResultT, LocaleMatcherT, SensitivityT
22
17
 
23
18
  # Important: Must be the same as CollatorOptions
24
19
  # (nothing is required, as this will be used to construct a
@@ -67,15 +62,18 @@ _COLLATOR_RESULT_TO_RESULT: dict[icu.UCollationResult, ComparisonResultT] = { #
67
62
 
68
63
 
69
64
  class Collator:
70
- locale: str
65
+ locale: Locale
71
66
  options: CollatorOptions
72
67
 
73
68
  def __init__(
74
69
  self,
75
- locale: str,
70
+ locale: Locale | str,
76
71
  options: CollatorOptions | CollatorOptionsDictT | None = None,
77
72
  ) -> None:
78
- self.locale = locale
73
+ if isinstance(locale, Locale):
74
+ self.locale = locale
75
+ else:
76
+ self.locale = Locale(locale)
79
77
  if options is None:
80
78
  self.options = CollatorOptions()
81
79
  elif isinstance(options, CollatorOptions):
@@ -83,13 +81,9 @@ class Collator:
83
81
  else:
84
82
  self.options = CollatorOptions(**options)
85
83
 
86
- @cached_property
87
- def _icu_locale(self) -> icu.Locale: # ty: ignore[unresolved-attribute]
88
- return icu.Locale(self.locale) # ty: ignore[unresolved-attribute]
89
-
90
84
  @cached_property
91
85
  def _icu_collator(self) -> icu.Collator: # ty: ignore[unresolved-attribute]
92
- collator = icu.Collator.createInstance(self._icu_locale) # ty: ignore[unresolved-attribute]
86
+ collator = icu.Collator.createInstance(self.locale._icu_locale) # ty: ignore[unresolved-attribute]
93
87
 
94
88
  if self.options.numeric:
95
89
  collator.setAttribute(icu.UCollAttribute.NUMERIC_COLLATION, icu.UCollAttributeValue.ON) # ty: ignore[unresolved-attribute]
@@ -98,7 +92,7 @@ class Collator:
98
92
  self.options.ignore_punctuation
99
93
  or (
100
94
  self.options.ignore_punctuation is None
101
- and self._icu_locale.getLanguage() == "th"
95
+ and self.locale._icu_locale.getLanguage() == "th"
102
96
  )
103
97
  ):
104
98
  collator.setAttribute(icu.UCollAttribute.ALTERNATE_HANDLING, icu.UCollAttributeValue.SHIFTED) # ty: ignore[unresolved-attribute]
@@ -124,3 +118,17 @@ class Collator:
124
118
 
125
119
  def compare(self, string_a: str, string_b: str) -> ComparisonResultT:
126
120
  return _COLLATOR_RESULT_TO_RESULT[self._icu_collator.compare(string_a, string_b)]
121
+
122
+ @overload
123
+ def sorted(self, items: Iterable[str], /, key: None = None) -> Iterable[str]: ...
124
+ @overload
125
+ def sorted[T](self, items: Iterable[T], /, key: Callable[[T], str]) -> Iterable[T]: ...
126
+ def sorted(self, items, /, key = None):
127
+ return sorted(
128
+ items,
129
+ key=functools.cmp_to_key(
130
+ (lambda a, b: self.compare(key(a), key(b)))
131
+ if key
132
+ else self.compare,
133
+ ),
134
+ )
@@ -7,50 +7,30 @@ from typing import TYPE_CHECKING, Literal
7
7
 
8
8
  import icu # type: ignore[import-untyped]
9
9
 
10
+ from .locale import Locale
11
+
10
12
  if TYPE_CHECKING:
11
13
  from collections.abc import Iterable
12
14
  from typing import NotRequired, TypedDict
13
15
 
14
-
15
- if TYPE_CHECKING:
16
- from ._types import LocaleMatcherT
17
-
18
- type Hour12T = bool | None
19
- type HourCycleT = Literal["h11", "h12", "h23", "h24"] | None
20
-
21
- type EraFormatT = Literal["long", "short", "narrow"]
22
- type YearFormatT = Literal["numeric", "2-digit"]
23
- type MonthFormatT = Literal["numeric", "2-digit", "long", "short", "narrow"]
24
- type WeekdayFormatT = Literal["long", "short", "narrow"]
25
- type DayFormatT = Literal["numeric", "2-digit"]
26
- type DayPeriodFormatT = Literal["long", "short", "narrow"]
27
- type HourFormatT = Literal["numeric", "2-digit"]
28
- type MinuteFormatT = Literal["numeric", "2-digit"]
29
- type SecondFormatT = Literal["numeric", "2-digit"]
30
- type FractionSecondDigitsFormatT = Literal[1, 2, 3]
31
- type TimezoneNameFormatT = Literal[
32
- "short",
33
- "long",
34
- "short_offset",
35
- "long_offset",
36
- "short_generic",
37
- "long_generic",
38
- ]
39
- type AnyFormatT = (
40
- EraFormatT
41
- | YearFormatT
42
- | MonthFormatT
43
- | WeekdayFormatT
44
- | DayFormatT
45
- | DayPeriodFormatT
46
- | HourFormatT
47
- | MinuteFormatT
48
- | SecondFormatT
49
- | FractionSecondDigitsFormatT
50
- | TimezoneNameFormatT
16
+ from ._types import (
17
+ DayFormatT,
18
+ DayPeriodFormatT,
19
+ EraFormatT,
20
+ FractionSecondDigitsFormatT,
21
+ Hour12T,
22
+ HourCycleT,
23
+ HourFormatT,
24
+ LocaleMatcherT,
25
+ MinuteFormatT,
26
+ MonthFormatT,
27
+ SecondFormatT,
28
+ TimezoneNameFormatT,
29
+ WeekdayFormatT,
30
+ YearFormatT,
51
31
  )
52
32
 
53
- type PatternPartTypeT = Literal[
33
+ type DatetimePatternPartTypeT = Literal[
54
34
  "literal", "unknown",
55
35
  "era", "year", "month", "weekday", "day", "day_period",
56
36
  "hour", "minute", "second", "fraction_second_digits",
@@ -78,7 +58,7 @@ if TYPE_CHECKING:
78
58
  time_zone_name: NotRequired[TimezoneNameFormatT]
79
59
 
80
60
  _PATTERN_SYMBOLS = "GyYuUrQqMLqQdDFgEecabBhHkKmsSAzZOvVxX" # includes unused
81
- _PATTERN_SYMBOL_TO_TYPE: dict[str, PatternPartTypeT] = {
61
+ _PATTERN_SYMBOL_TO_TYPE: dict[str, DatetimePatternPartTypeT] = {
82
62
  "G": "era",
83
63
  "y": "year",
84
64
  "Y": "year",
@@ -92,6 +72,7 @@ _PATTERN_SYMBOL_TO_TYPE: dict[str, PatternPartTypeT] = {
92
72
  "d": "day",
93
73
  "a": "day_period",
94
74
  "b": "day_period",
75
+ "B": "day_period",
95
76
  "C": "day_period",
96
77
  "h": "hour",
97
78
  "H": "hour",
@@ -108,13 +89,19 @@ _PATTERN_SYMBOL_TO_TYPE: dict[str, PatternPartTypeT] = {
108
89
  "x": "time_zone_name",
109
90
  "X": "time_zone_name",
110
91
  }
111
- _PATTERN_FIELD_TO_TYPE: dict[icu.UDateTimePatternField, PatternPartTypeT] = { # ty: ignore[unresolved-attribute]
92
+ _PATTERN_FIELD_TO_TYPE: dict[icu.UDateTimePatternField, DatetimePatternPartTypeT] = { # ty: ignore[unresolved-attribute]
112
93
  icu.DateFormat.ERA_FIELD: "era", # ty: ignore[unresolved-attribute]
113
94
  icu.DateFormat.YEAR_FIELD: "year", # ty: ignore[unresolved-attribute]
114
95
  icu.DateFormat.MONTH_FIELD: "month", # ty: ignore[unresolved-attribute]
115
96
  icu.DateFormat.DAY_OF_WEEK_FIELD: "weekday", # ty: ignore[unresolved-attribute]
116
97
  icu.DateFormat.DATE_FIELD: "day", # ty: ignore[unresolved-attribute]
117
98
  icu.DateFormat.AM_PM_FIELD: "day_period", # ty: ignore[unresolved-attribute]
99
+ # TODO(ddanier): Use this instead once PyICU has those values in the enum:
100
+ # https://gitlab.pyicu.org/main/pyicu/-/work_items/180
101
+ # icu.DateFormat.AM_PM_MIDNIGHT_NOON_FIELD: "day_period",
102
+ # icu.DateFormat.FLEXIBLE_DAY_PERIOD_FIELD: "day_period",
103
+ 35: "day_period",
104
+ 36: "day_period",
118
105
  icu.DateFormat.HOUR0_FIELD: "hour", # ty: ignore[unresolved-attribute]
119
106
  icu.DateFormat.HOUR1_FIELD: "hour", # ty: ignore[unresolved-attribute]
120
107
  icu.DateFormat.HOUR_OF_DAY0_FIELD: "hour", # ty: ignore[unresolved-attribute]
@@ -146,8 +133,8 @@ _SOURCE_TO_JSON_MAP: dict[str, str] = {
146
133
  @dataclasses.dataclass(frozen=True, kw_only=True, slots=True)
147
134
  class DateTimeFormatOptions:
148
135
  locale_matcher: LocaleMatcherT = "best fit"
149
- hour12: Hour12T = None
150
- hour_cycle: HourCycleT = None
136
+ hour12: Hour12T | None = None
137
+ hour_cycle: HourCycleT | None = None
151
138
 
152
139
  era: EraFormatT | None = None
153
140
  year: YearFormatT | None = None
@@ -165,6 +152,10 @@ class DateTimeFormatOptions:
165
152
  return {
166
153
  k: v
167
154
  for k, v in (
155
+ ("localeMatcher", self.locale_matcher),
156
+ ("hour12", self.hour12),
157
+ ("hourCycle", self.hour_cycle),
158
+
168
159
  ("era", self.era),
169
160
  ("year", self.year),
170
161
  ("month", self.month),
@@ -323,7 +314,7 @@ class FormatPatternNotFoundException(Exception):
323
314
 
324
315
  @dataclasses.dataclass(kw_only=True, frozen=True, slots=True)
325
316
  class DateTimePatternPart:
326
- type: PatternPartTypeT
317
+ type: DatetimePatternPartTypeT
327
318
  value: str
328
319
  _pattern: str | None = None
329
320
 
@@ -337,7 +328,7 @@ class DateTimePatternPart:
337
328
 
338
329
  @dataclasses.dataclass(kw_only=True, frozen=True, slots=True)
339
330
  class DateTimeIntervalPatternPart:
340
- type: PatternPartTypeT
331
+ type: DatetimePatternPartTypeT
341
332
  value: str
342
333
  source: Literal["start_range", "end_range", "shared"]
343
334
 
@@ -398,15 +389,18 @@ class _PartSpan:
398
389
 
399
390
 
400
391
  class DateTimeFormat:
401
- locale: str
392
+ locale: Locale
402
393
  options: DateTimeFormatOptions
403
394
 
404
395
  def __init__(
405
396
  self,
406
- locale: str,
397
+ locale: Locale | str,
407
398
  options: DateTimeFormatOptions | DateTimeFormatOptionsDictT | None = None,
408
399
  ) -> None:
409
- self.locale = locale
400
+ if isinstance(locale, Locale):
401
+ self.locale = locale
402
+ else:
403
+ self.locale = Locale(locale)
410
404
  if options is None:
411
405
  self.options = DateTimeFormatOptions()
412
406
  elif isinstance(options, DateTimeFormatOptions):
@@ -414,13 +408,9 @@ class DateTimeFormat:
414
408
  else:
415
409
  self.options = DateTimeFormatOptions(**options)
416
410
 
417
- @cached_property
418
- def _icu_locale(self) -> icu.Locale: # ty: ignore[unresolved-attribute]
419
- return icu.Locale(self.locale) # ty: ignore[unresolved-attribute]
420
-
421
411
  @cached_property
422
412
  def _matched_pattern(self) -> _MatchedFormatPattern:
423
- return _options_to_format_pattern(self._icu_locale, self.options)
413
+ return _options_to_format_pattern(self.locale._icu_locale, self.options)
424
414
 
425
415
  @cached_property
426
416
  def _icu_pattern(self) -> str:
@@ -428,7 +418,7 @@ class DateTimeFormat:
428
418
 
429
419
  @cached_property
430
420
  def _icu_date_format(self) -> icu.SimpleDateFormat: # ty: ignore[unresolved-attribute]
431
- return icu.SimpleDateFormat(self._icu_pattern, self._icu_locale) # ty: ignore[unresolved-attribute]
421
+ return icu.SimpleDateFormat(self._icu_pattern, self.locale._icu_locale) # ty: ignore[unresolved-attribute]
432
422
 
433
423
  def format(self, datetime_: dt.datetime, /) -> str:
434
424
  return self._icu_date_format.format(datetime_)
@@ -448,7 +438,7 @@ class DateTimeFormat:
448
438
  if char != prev_char and count > 0:
449
439
  yield DateTimePatternPart(
450
440
  type=_PATTERN_SYMBOL_TO_TYPE.get(prev_char, "unknown"),
451
- value=icu.SimpleDateFormat(prev_char * count, self._icu_locale).format(datetime_), # ty: ignore[unresolved-attribute]
441
+ value=icu.SimpleDateFormat(prev_char * count, self.locale._icu_locale).format(datetime_), # ty: ignore[unresolved-attribute]
452
442
  _pattern=prev_char * count,
453
443
  )
454
444
  count = 0
@@ -476,7 +466,7 @@ class DateTimeFormat:
476
466
  if count > 0:
477
467
  yield DateTimePatternPart(
478
468
  type=_PATTERN_SYMBOL_TO_TYPE.get(prev_char, "unknown"),
479
- value=icu.SimpleDateFormat(prev_char * count, self._icu_locale).format(datetime_), # ty: ignore[unresolved-attribute]
469
+ value=icu.SimpleDateFormat(prev_char * count, self.locale._icu_locale).format(datetime_), # ty: ignore[unresolved-attribute]
480
470
  _pattern=prev_char * count,
481
471
  )
482
472
  assert not literal_chars # noqa: S101
@@ -489,7 +479,7 @@ class DateTimeFormat:
489
479
  @cached_property
490
480
  def _icu_dateinterval_format(self) -> icu.DateIntervalFormat: # ty: ignore[unresolved-attribute]
491
481
  possible_skeletons = list(_options_to_possible_skeletons(self.options))
492
- return icu.DateIntervalFormat.createInstance(possible_skeletons[0], self._icu_locale) # ty: ignore[unresolved-attribute]
482
+ return icu.DateIntervalFormat.createInstance(possible_skeletons[0], self.locale._icu_locale) # ty: ignore[unresolved-attribute]
493
483
 
494
484
  def format_range(
495
485
  self,
@@ -0,0 +1,16 @@
1
+ from __future__ import annotations
2
+
3
+ from functools import cached_property
4
+
5
+ import icu # type: ignore[import-untyped]
6
+
7
+
8
+ class Locale:
9
+ tag: str
10
+
11
+ def __init__(self, tag: str) -> None:
12
+ self.tag = tag
13
+
14
+ @cached_property
15
+ def _icu_locale(self) -> icu.Locale: # ty: ignore[unresolved-attribute]
16
+ return icu.Locale(self.tag) # ty: ignore[unresolved-attribute]
@@ -1,3 +0,0 @@
1
- from typing import Literal
2
-
3
- type LocaleMatcherT = Literal["best fit", "lookup"]
File without changes