python-intl 0.2.0__tar.gz → 0.4.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.
- python_intl-0.4.0/PKG-INFO +93 -0
- python_intl-0.4.0/README.md +80 -0
- {python_intl-0.2.0 → python_intl-0.4.0}/pyproject.toml +6 -2
- {python_intl-0.2.0 → python_intl-0.4.0}/pyproject.toml.orig +6 -2
- python_intl-0.4.0/python_intl/__init__.py +6 -0
- {python_intl-0.2.0 → python_intl-0.4.0}/python_intl/datetimeformat.py +136 -16
- python_intl-0.2.0/PKG-INFO +0 -42
- python_intl-0.2.0/README.md +0 -29
- python_intl-0.2.0/python_intl/__init__.py +0 -1
- {python_intl-0.2.0 → python_intl-0.4.0}/LICENSE +0 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: python-intl
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Python implementation of the Intl JavaScript API
|
|
5
|
+
Author: David Danier
|
|
6
|
+
Author-email: David Danier <david.danier@gmail.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: pyicu>=2.16.2
|
|
10
|
+
Requires-Python: >=3.12
|
|
11
|
+
Project-URL: Repository, https://github.com/ddanier/python-intl
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# `python-intl`
|
|
15
|
+
|
|
16
|
+
A small library using [PyICU](https://pypi.org/project/pyicu/) to provide a Python
|
|
17
|
+
API similar to what the [`Intl` JavaScript API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl)
|
|
18
|
+
provides. It is not meant to fully behave the same, but instead be close enough so
|
|
19
|
+
the "same" code works on JavaScript and Python, with similar results.
|
|
20
|
+
|
|
21
|
+
**Status:** This is very much work in progress. Currently only `Intl.DateTimeFormat`
|
|
22
|
+
exists and this also is not complete yet.
|
|
23
|
+
|
|
24
|
+
**Note:** There are a lot of tests running the Python implementation against the
|
|
25
|
+
JavaScript one and comparing the results. In general things should be pretty
|
|
26
|
+
stable there. Still there are some cases with known differences. Also note
|
|
27
|
+
that results depend on the ICU version you have installed on your machine.
|
|
28
|
+
|
|
29
|
+
## General notes about the Python adaption
|
|
30
|
+
|
|
31
|
+
All names will be using the Python style rules. This means instead of
|
|
32
|
+
`formatToParts` a method will be called `format_to_parts`. Also a dictionary
|
|
33
|
+
key like `dayPeriod` will be named `day_period`. Python uses snake case, let's
|
|
34
|
+
stick to this.
|
|
35
|
+
|
|
36
|
+
Instead of passing around undefined objects like in JavaScript we want to use
|
|
37
|
+
well defined and typed dataclasses. This for example is true for the
|
|
38
|
+
`Intl.DateTimeFormat` format options, you can use `DateTimeFormatOptions` as
|
|
39
|
+
a clean representation of those. Using a dictionary (which behaves the most
|
|
40
|
+
like those JavaScript objects) is still fine and will automatically converted,
|
|
41
|
+
as seen in the examples here.
|
|
42
|
+
|
|
43
|
+
Many objects like for example the `DateTimeFormatOptions` provide methods to
|
|
44
|
+
convert their Python representation to a JavaScript compatible JSON format
|
|
45
|
+
by returning a `dict` using the JavaScript naming. You can use the `to_json`
|
|
46
|
+
method for this.
|
|
47
|
+
|
|
48
|
+
For example:
|
|
49
|
+
```python
|
|
50
|
+
import python_intl as Intl
|
|
51
|
+
|
|
52
|
+
Intl.DateTimeFormatOptions(time_zone_name="short_offset").to_json()
|
|
53
|
+
# Will return: {'timeZoneName': 'shortOffset'}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Available `Intl` classes
|
|
57
|
+
|
|
58
|
+
### `Intl.DateTimeFormat`
|
|
59
|
+
|
|
60
|
+
#### Example usage
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
import datetime as dt
|
|
64
|
+
import python_intl as Intl
|
|
65
|
+
|
|
66
|
+
datetime = dt.datetime(2026, 8, 15)
|
|
67
|
+
formatter = Intl.DateTimeFormat("de-DE", {"year": "numeric", "month": "2-digit", "day": "2-digit"})
|
|
68
|
+
formatter.format(datetime) # Will output the German format: "15.08.2026"
|
|
69
|
+
datetime_till = dt.datetime(2026, 9, 7)
|
|
70
|
+
formatter.format_range(datetime, datetime_till) # Will output the German format: "15.08. – 07.09.2026"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
#### Compatibility
|
|
74
|
+
|
|
75
|
+
| Method | Status | Python name |
|
|
76
|
+
| ----------------------------------- | :----: | -------------------------------- |
|
|
77
|
+
| `DateTimeFormat.format` | ✅ | |
|
|
78
|
+
| `DateTimeFormat.formatToParts` | ✅ | `DateTimeFormat.format_to_parts` |
|
|
79
|
+
| `DateTimeFormat.supportedLocalesOf` | ❌ | |
|
|
80
|
+
| `DateTimeFormat.formatRange` | ✅ | `DateTimeFormat.format_range` |
|
|
81
|
+
| `DateTimeFormat.formatRangeToParts` | ❌ | |
|
|
82
|
+
| `DateTimeFormat.resolvedOptions` | ❌ | |
|
|
83
|
+
|
|
84
|
+
## Installation
|
|
85
|
+
|
|
86
|
+
Be sure to be able to install `PyICU`, see the installation docs there:
|
|
87
|
+
https://gitlab.pyicu.org/main/pyicu#installing-pyicu
|
|
88
|
+
|
|
89
|
+
**Hint:** I mainly did run into issues with `pkg-config` not finding the ICU library,
|
|
90
|
+
setting `PKG_CONFIG_PATH` accordingly helps most of the time I guess.
|
|
91
|
+
|
|
92
|
+
When this is done you should be able to install `python-intl` using any package
|
|
93
|
+
manager, like `pip install python-intl` or `uv add python-intl`.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# `python-intl`
|
|
2
|
+
|
|
3
|
+
A small library using [PyICU](https://pypi.org/project/pyicu/) to provide a Python
|
|
4
|
+
API similar to what the [`Intl` JavaScript API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl)
|
|
5
|
+
provides. It is not meant to fully behave the same, but instead be close enough so
|
|
6
|
+
the "same" code works on JavaScript and Python, with similar results.
|
|
7
|
+
|
|
8
|
+
**Status:** This is very much work in progress. Currently only `Intl.DateTimeFormat`
|
|
9
|
+
exists and this also is not complete yet.
|
|
10
|
+
|
|
11
|
+
**Note:** There are a lot of tests running the Python implementation against the
|
|
12
|
+
JavaScript one and comparing the results. In general things should be pretty
|
|
13
|
+
stable there. Still there are some cases with known differences. Also note
|
|
14
|
+
that results depend on the ICU version you have installed on your machine.
|
|
15
|
+
|
|
16
|
+
## General notes about the Python adaption
|
|
17
|
+
|
|
18
|
+
All names will be using the Python style rules. This means instead of
|
|
19
|
+
`formatToParts` a method will be called `format_to_parts`. Also a dictionary
|
|
20
|
+
key like `dayPeriod` will be named `day_period`. Python uses snake case, let's
|
|
21
|
+
stick to this.
|
|
22
|
+
|
|
23
|
+
Instead of passing around undefined objects like in JavaScript we want to use
|
|
24
|
+
well defined and typed dataclasses. This for example is true for the
|
|
25
|
+
`Intl.DateTimeFormat` format options, you can use `DateTimeFormatOptions` as
|
|
26
|
+
a clean representation of those. Using a dictionary (which behaves the most
|
|
27
|
+
like those JavaScript objects) is still fine and will automatically converted,
|
|
28
|
+
as seen in the examples here.
|
|
29
|
+
|
|
30
|
+
Many objects like for example the `DateTimeFormatOptions` provide methods to
|
|
31
|
+
convert their Python representation to a JavaScript compatible JSON format
|
|
32
|
+
by returning a `dict` using the JavaScript naming. You can use the `to_json`
|
|
33
|
+
method for this.
|
|
34
|
+
|
|
35
|
+
For example:
|
|
36
|
+
```python
|
|
37
|
+
import python_intl as Intl
|
|
38
|
+
|
|
39
|
+
Intl.DateTimeFormatOptions(time_zone_name="short_offset").to_json()
|
|
40
|
+
# Will return: {'timeZoneName': 'shortOffset'}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Available `Intl` classes
|
|
44
|
+
|
|
45
|
+
### `Intl.DateTimeFormat`
|
|
46
|
+
|
|
47
|
+
#### Example usage
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
import datetime as dt
|
|
51
|
+
import python_intl as Intl
|
|
52
|
+
|
|
53
|
+
datetime = dt.datetime(2026, 8, 15)
|
|
54
|
+
formatter = Intl.DateTimeFormat("de-DE", {"year": "numeric", "month": "2-digit", "day": "2-digit"})
|
|
55
|
+
formatter.format(datetime) # Will output the German format: "15.08.2026"
|
|
56
|
+
datetime_till = dt.datetime(2026, 9, 7)
|
|
57
|
+
formatter.format_range(datetime, datetime_till) # Will output the German format: "15.08. – 07.09.2026"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
#### Compatibility
|
|
61
|
+
|
|
62
|
+
| Method | Status | Python name |
|
|
63
|
+
| ----------------------------------- | :----: | -------------------------------- |
|
|
64
|
+
| `DateTimeFormat.format` | ✅ | |
|
|
65
|
+
| `DateTimeFormat.formatToParts` | ✅ | `DateTimeFormat.format_to_parts` |
|
|
66
|
+
| `DateTimeFormat.supportedLocalesOf` | ❌ | |
|
|
67
|
+
| `DateTimeFormat.formatRange` | ✅ | `DateTimeFormat.format_range` |
|
|
68
|
+
| `DateTimeFormat.formatRangeToParts` | ❌ | |
|
|
69
|
+
| `DateTimeFormat.resolvedOptions` | ❌ | |
|
|
70
|
+
|
|
71
|
+
## Installation
|
|
72
|
+
|
|
73
|
+
Be sure to be able to install `PyICU`, see the installation docs there:
|
|
74
|
+
https://gitlab.pyicu.org/main/pyicu#installing-pyicu
|
|
75
|
+
|
|
76
|
+
**Hint:** I mainly did run into issues with `pkg-config` not finding the ICU library,
|
|
77
|
+
setting `PKG_CONFIG_PATH` accordingly helps most of the time I guess.
|
|
78
|
+
|
|
79
|
+
When this is done you should be able to install `python-intl` using any package
|
|
80
|
+
manager, like `pip install python-intl` or `uv add python-intl`.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "python-intl"
|
|
3
|
-
version = "0.
|
|
4
|
-
description = "
|
|
3
|
+
version = "0.4.0"
|
|
4
|
+
description = "Python implementation of the Intl JavaScript API"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
7
7
|
license-files = ["LICENSE"]
|
|
@@ -80,6 +80,10 @@ ignore = [
|
|
|
80
80
|
"F401",
|
|
81
81
|
]
|
|
82
82
|
|
|
83
|
+
[tool.ruff.lint.isort]
|
|
84
|
+
combine-as-imports = true
|
|
85
|
+
force-wrap-aliases = true
|
|
86
|
+
|
|
83
87
|
[tool.uv.build-backend]
|
|
84
88
|
module-name = "python_intl"
|
|
85
89
|
module-root = ""
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "python-intl"
|
|
3
|
-
version = "0.
|
|
4
|
-
description = "
|
|
3
|
+
version = "0.4.0"
|
|
4
|
+
description = "Python implementation of the Intl JavaScript API"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
7
7
|
{ name = "David Danier", email = "david.danier@gmail.com" }
|
|
@@ -46,6 +46,10 @@ ignore = ["A001","A002","A003","ANN401","C901","N8","B008","F405","F821"]
|
|
|
46
46
|
"conftest.py" = ["S101","ANN","F401"]
|
|
47
47
|
"test_*.py" = ["S101","ANN","F401"]
|
|
48
48
|
|
|
49
|
+
[tool.ruff.lint.isort]
|
|
50
|
+
combine-as-imports = true
|
|
51
|
+
force-wrap-aliases = true
|
|
52
|
+
|
|
49
53
|
[build-system]
|
|
50
54
|
requires = ["uv_build>=0.12.3,<0.13.0"]
|
|
51
55
|
build-backend = "uv_build"
|
|
@@ -49,6 +49,13 @@ if TYPE_CHECKING:
|
|
|
49
49
|
| TimezoneNameFormatT
|
|
50
50
|
)
|
|
51
51
|
|
|
52
|
+
type PatternPartTypeT = Literal[
|
|
53
|
+
"literal", "unknown",
|
|
54
|
+
"era", "year", "month", "weekday", "day", "day_period",
|
|
55
|
+
"hour", "minute", "second", "fraction_second_digits",
|
|
56
|
+
"time_zone_name",
|
|
57
|
+
]
|
|
58
|
+
|
|
52
59
|
# Important: Must be the same as DateTimeFormatOptions
|
|
53
60
|
# (nothing is required, as this will be used to construct a
|
|
54
61
|
# DateTimeFormatOptions instance, so default values apply then)
|
|
@@ -69,8 +76,45 @@ if TYPE_CHECKING:
|
|
|
69
76
|
fraction_second_digits: NotRequired[FractionSecondDigitsFormatT]
|
|
70
77
|
time_zone_name: NotRequired[TimezoneNameFormatT]
|
|
71
78
|
|
|
79
|
+
_PATTERN_SYMBOLS = "GyYuUrQqMLqQdDFgEecabBhHkKmsSAzZOvVxX" # includes unused
|
|
80
|
+
_PATTERN_SYMBOL_TO_TYPE: dict[str, PatternPartTypeT] = {
|
|
81
|
+
"G": "era",
|
|
82
|
+
"y": "year",
|
|
83
|
+
"Y": "year",
|
|
84
|
+
"u": "year",
|
|
85
|
+
"U": "year",
|
|
86
|
+
"r": "year",
|
|
87
|
+
"M": "month",
|
|
88
|
+
"E": "weekday",
|
|
89
|
+
"e": "weekday",
|
|
90
|
+
"c": "weekday",
|
|
91
|
+
"d": "day",
|
|
92
|
+
"a": "day_period",
|
|
93
|
+
"b": "day_period",
|
|
94
|
+
"C": "day_period",
|
|
95
|
+
"h": "hour",
|
|
96
|
+
"H": "hour",
|
|
97
|
+
"k": "hour",
|
|
98
|
+
"K": "hour",
|
|
99
|
+
"m": "minute",
|
|
100
|
+
"s": "second",
|
|
101
|
+
"S": "fraction_second_digits",
|
|
102
|
+
"z": "time_zone_name",
|
|
103
|
+
"Z": "time_zone_name",
|
|
104
|
+
"O": "time_zone_name",
|
|
105
|
+
"v": "time_zone_name",
|
|
106
|
+
"V": "time_zone_name",
|
|
107
|
+
"x": "time_zone_name",
|
|
108
|
+
"X": "time_zone_name",
|
|
109
|
+
}
|
|
110
|
+
_PATTERN_QUOTE = "'"
|
|
72
111
|
|
|
73
|
-
|
|
112
|
+
_COMPONENT_TO_JSON_MAP: dict[str, str] = {
|
|
113
|
+
"day_period": "dayPeriod",
|
|
114
|
+
"fraction_second_digits": "fractionalSecondDigits",
|
|
115
|
+
"time_zone_name": "timeZoneName",
|
|
116
|
+
}
|
|
117
|
+
_TIMEZONE_NAME_TO_JSON_MAP: dict[str | None, str] = {
|
|
74
118
|
"short_offset": "shortOffset",
|
|
75
119
|
"long_offset": "longOffset",
|
|
76
120
|
"short_generic": "shortGeneric",
|
|
@@ -78,7 +122,7 @@ _TIMEZONE_NAME_JS_MAPPING: dict[str | None, str] = {
|
|
|
78
122
|
}
|
|
79
123
|
|
|
80
124
|
|
|
81
|
-
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
125
|
+
@dataclasses.dataclass(frozen=True, kw_only=True, slots=True)
|
|
82
126
|
class DateTimeFormatOptions:
|
|
83
127
|
locale_matcher: LocaleMatcherT = "best fit"
|
|
84
128
|
hour12: Hour12T = None
|
|
@@ -110,7 +154,7 @@ class DateTimeFormatOptions:
|
|
|
110
154
|
("minute", self.minute),
|
|
111
155
|
("second", self.second),
|
|
112
156
|
("fractionalSecondDigits", self.fraction_second_digits),
|
|
113
|
-
("timeZoneName",
|
|
157
|
+
("timeZoneName", _TIMEZONE_NAME_TO_JSON_MAP.get(self.time_zone_name, self.time_zone_name)),
|
|
114
158
|
)
|
|
115
159
|
if v is not None
|
|
116
160
|
}
|
|
@@ -243,8 +287,8 @@ def _options_to_possible_skeletons(options: DateTimeFormatOptions) -> Iterable[s
|
|
|
243
287
|
yield from generate_skeletons("", skeleton_parts)
|
|
244
288
|
|
|
245
289
|
|
|
246
|
-
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
247
|
-
class
|
|
290
|
+
@dataclasses.dataclass(frozen=True, kw_only=True, slots=True)
|
|
291
|
+
class _MatchedFormatPattern:
|
|
248
292
|
skeleton: str
|
|
249
293
|
pattern: str
|
|
250
294
|
|
|
@@ -256,11 +300,25 @@ class FormatPatternNotFoundException(Exception):
|
|
|
256
300
|
pass
|
|
257
301
|
|
|
258
302
|
|
|
303
|
+
@dataclasses.dataclass(kw_only=True, frozen=True, slots=True)
|
|
304
|
+
class DateTimePatternPart:
|
|
305
|
+
type: PatternPartTypeT
|
|
306
|
+
value: str
|
|
307
|
+
_pattern: str | None = None
|
|
308
|
+
|
|
309
|
+
def to_json(self) -> dict[str, str]:
|
|
310
|
+
return {
|
|
311
|
+
"type": _COMPONENT_TO_JSON_MAP.get(self.type, self.type),
|
|
312
|
+
"value": self.value,
|
|
313
|
+
# note: _pattern is internal and not available in JavaScript
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
|
|
259
317
|
@cache
|
|
260
318
|
def _options_to_format_pattern(
|
|
261
319
|
locale: icu.Locale, # ty: ignore[unresolved-attribute]
|
|
262
320
|
options: DateTimeFormatOptions,
|
|
263
|
-
) ->
|
|
321
|
+
) -> _MatchedFormatPattern:
|
|
264
322
|
possible_skeletons = list(_options_to_possible_skeletons(options))
|
|
265
323
|
|
|
266
324
|
generator = icu.DateTimePatternGenerator.createInstance(locale) # ty: ignore[unresolved-attribute]
|
|
@@ -269,7 +327,7 @@ def _options_to_format_pattern(
|
|
|
269
327
|
for skeleton in possible_skeletons:
|
|
270
328
|
pattern = generator.getPatternForSkeleton(skeleton)
|
|
271
329
|
if pattern:
|
|
272
|
-
return
|
|
330
|
+
return _MatchedFormatPattern(
|
|
273
331
|
skeleton=skeleton,
|
|
274
332
|
pattern=pattern,
|
|
275
333
|
)
|
|
@@ -279,7 +337,7 @@ def _options_to_format_pattern(
|
|
|
279
337
|
for skeleton in possible_skeletons:
|
|
280
338
|
pattern = generator.getBestPattern(skeleton)
|
|
281
339
|
if pattern:
|
|
282
|
-
return
|
|
340
|
+
return _MatchedFormatPattern(
|
|
283
341
|
skeleton=skeleton,
|
|
284
342
|
pattern=pattern,
|
|
285
343
|
)
|
|
@@ -303,20 +361,82 @@ class DateTimeFormat:
|
|
|
303
361
|
self.options = DateTimeFormatOptions(**options)
|
|
304
362
|
|
|
305
363
|
@cached_property
|
|
306
|
-
def
|
|
364
|
+
def _icu_locale(self) -> icu.Locale: # ty: ignore[unresolved-attribute]
|
|
307
365
|
return icu.Locale(self.locale) # ty: ignore[unresolved-attribute]
|
|
308
366
|
|
|
309
367
|
@cached_property
|
|
310
|
-
def
|
|
311
|
-
return _options_to_format_pattern(self.
|
|
368
|
+
def _matched_pattern(self) -> _MatchedFormatPattern:
|
|
369
|
+
return _options_to_format_pattern(self._icu_locale, self.options)
|
|
312
370
|
|
|
313
371
|
@cached_property
|
|
314
|
-
def
|
|
315
|
-
return self.
|
|
372
|
+
def _icu_pattern(self) -> str:
|
|
373
|
+
return self._matched_pattern.pattern
|
|
316
374
|
|
|
317
375
|
@cached_property
|
|
318
|
-
def
|
|
319
|
-
return icu.SimpleDateFormat(self.
|
|
376
|
+
def _icu_date_format(self) -> icu.SimpleDateFormat: # ty: ignore[unresolved-attribute]
|
|
377
|
+
return icu.SimpleDateFormat(self._icu_pattern, self._icu_locale) # ty: ignore[unresolved-attribute]
|
|
320
378
|
|
|
321
379
|
def format(self, datetime_: dt.datetime, /) -> str:
|
|
322
|
-
return self.
|
|
380
|
+
return self._icu_date_format.format(datetime_)
|
|
381
|
+
|
|
382
|
+
def format_to_parts(self, datetime_: dt.datetime, /) -> Iterable[DateTimePatternPart]:
|
|
383
|
+
# Based on https://github.com/unicode-org/icu/blob/6fb634d81d10dd4667fb3fbcd1f19d9b9b926e62/icu4c/source/i18n/smpdtfmt.cpp#L1060
|
|
384
|
+
pattern = self._icu_pattern
|
|
385
|
+
in_quote = False
|
|
386
|
+
prev_char = ""
|
|
387
|
+
pattern_length = len(pattern)
|
|
388
|
+
count = 0
|
|
389
|
+
i = 0
|
|
390
|
+
literal_chars: list[str] = []
|
|
391
|
+
while i < pattern_length:
|
|
392
|
+
char = pattern[i]
|
|
393
|
+
|
|
394
|
+
if char != prev_char and count > 0:
|
|
395
|
+
yield DateTimePatternPart(
|
|
396
|
+
type=_PATTERN_SYMBOL_TO_TYPE.get(prev_char, "unknown"),
|
|
397
|
+
value=icu.SimpleDateFormat(prev_char * count, self._icu_locale).format(datetime_), # ty: ignore[unresolved-attribute]
|
|
398
|
+
_pattern=prev_char * count,
|
|
399
|
+
)
|
|
400
|
+
count = 0
|
|
401
|
+
|
|
402
|
+
if char == _PATTERN_QUOTE:
|
|
403
|
+
if (i + 1) < pattern_length and pattern[i + 1] == _PATTERN_QUOTE:
|
|
404
|
+
literal_chars.append(_PATTERN_QUOTE)
|
|
405
|
+
i += 1
|
|
406
|
+
else:
|
|
407
|
+
in_quote = not in_quote
|
|
408
|
+
elif not in_quote and char in _PATTERN_SYMBOLS:
|
|
409
|
+
if literal_chars:
|
|
410
|
+
yield DateTimePatternPart(
|
|
411
|
+
type="literal",
|
|
412
|
+
value="".join(literal_chars),
|
|
413
|
+
)
|
|
414
|
+
literal_chars = []
|
|
415
|
+
prev_char = char
|
|
416
|
+
count += 1
|
|
417
|
+
else:
|
|
418
|
+
literal_chars.append(char)
|
|
419
|
+
|
|
420
|
+
i += 1
|
|
421
|
+
|
|
422
|
+
if count > 0:
|
|
423
|
+
yield DateTimePatternPart(
|
|
424
|
+
type=_PATTERN_SYMBOL_TO_TYPE.get(prev_char, "unknown"),
|
|
425
|
+
value=icu.SimpleDateFormat(prev_char * count, self._icu_locale).format(datetime_), # ty: ignore[unresolved-attribute]
|
|
426
|
+
_pattern=prev_char * count,
|
|
427
|
+
)
|
|
428
|
+
assert not literal_chars # noqa: S101
|
|
429
|
+
elif literal_chars:
|
|
430
|
+
yield DateTimePatternPart(
|
|
431
|
+
type="literal",
|
|
432
|
+
value="".join(literal_chars),
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
@cached_property
|
|
436
|
+
def _icu_dateinterval_format(self) -> icu.DateIntervalFormat: # ty: ignore[unresolved-attribute]
|
|
437
|
+
possible_skeletons = list(_options_to_possible_skeletons(self.options))
|
|
438
|
+
return icu.DateIntervalFormat.createInstance(possible_skeletons[0], self._icu_locale) # ty: ignore[unresolved-attribute]
|
|
439
|
+
|
|
440
|
+
def format_range(self, start_datetime: dt.datetime, end_datetime: dt.datetime) -> str:
|
|
441
|
+
icu_date_interval = icu.DateInterval(start_datetime, end_datetime) # ty: ignore[unresolved-attribute]
|
|
442
|
+
return self._icu_dateinterval_format.format(icu_date_interval)
|
python_intl-0.2.0/PKG-INFO
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: python-intl
|
|
3
|
-
Version: 0.2.0
|
|
4
|
-
Summary: Add your description here
|
|
5
|
-
Author: David Danier
|
|
6
|
-
Author-email: David Danier <david.danier@gmail.com>
|
|
7
|
-
License-Expression: MIT
|
|
8
|
-
License-File: LICENSE
|
|
9
|
-
Requires-Dist: pyicu>=2.16.2
|
|
10
|
-
Requires-Python: >=3.12
|
|
11
|
-
Project-URL: Repository, https://github.com/ddanier/python-intl
|
|
12
|
-
Description-Content-Type: text/markdown
|
|
13
|
-
|
|
14
|
-
# `python-intl`
|
|
15
|
-
|
|
16
|
-
A small library using [PyICU](https://pypi.org/project/pyicu/) to provide a Python
|
|
17
|
-
API similar to what the [`Intl` JavaScript API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl)
|
|
18
|
-
provides.
|
|
19
|
-
|
|
20
|
-
**Status:** This is very much work in progress. Currently only `Intl.DateTimeFormat`
|
|
21
|
-
exists and this also is not complete yet.
|
|
22
|
-
|
|
23
|
-
## `Intl.DateTimeFormat`
|
|
24
|
-
|
|
25
|
-
### Example usage
|
|
26
|
-
|
|
27
|
-
```python
|
|
28
|
-
import datetime
|
|
29
|
-
from python_intl import DateTimeFormat
|
|
30
|
-
|
|
31
|
-
datetime_ = datetime.datetime(2026, 8, 15)
|
|
32
|
-
formatter = DateTimeFormat("de-DE", {"year": "numeric", "month": "2-digit", "day": "2-digit"})
|
|
33
|
-
formatter.format(datetime_) # Will output the German format: "15.08.2026"
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## Installation
|
|
37
|
-
|
|
38
|
-
Be sure to be able to install `PyICU`, see the installation docs there:
|
|
39
|
-
https://gitlab.pyicu.org/main/pyicu#installing-pyicu
|
|
40
|
-
|
|
41
|
-
**Hint:** I mainly did run into issues with `pkg-config` not finding the ICU library,
|
|
42
|
-
setting `PKG_CONFIG_PATH` accordingly helps most of the time I guess.
|
python_intl-0.2.0/README.md
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
# `python-intl`
|
|
2
|
-
|
|
3
|
-
A small library using [PyICU](https://pypi.org/project/pyicu/) to provide a Python
|
|
4
|
-
API similar to what the [`Intl` JavaScript API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl)
|
|
5
|
-
provides.
|
|
6
|
-
|
|
7
|
-
**Status:** This is very much work in progress. Currently only `Intl.DateTimeFormat`
|
|
8
|
-
exists and this also is not complete yet.
|
|
9
|
-
|
|
10
|
-
## `Intl.DateTimeFormat`
|
|
11
|
-
|
|
12
|
-
### Example usage
|
|
13
|
-
|
|
14
|
-
```python
|
|
15
|
-
import datetime
|
|
16
|
-
from python_intl import DateTimeFormat
|
|
17
|
-
|
|
18
|
-
datetime_ = datetime.datetime(2026, 8, 15)
|
|
19
|
-
formatter = DateTimeFormat("de-DE", {"year": "numeric", "month": "2-digit", "day": "2-digit"})
|
|
20
|
-
formatter.format(datetime_) # Will output the German format: "15.08.2026"
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
## Installation
|
|
24
|
-
|
|
25
|
-
Be sure to be able to install `PyICU`, see the installation docs there:
|
|
26
|
-
https://gitlab.pyicu.org/main/pyicu#installing-pyicu
|
|
27
|
-
|
|
28
|
-
**Hint:** I mainly did run into issues with `pkg-config` not finding the ICU library,
|
|
29
|
-
setting `PKG_CONFIG_PATH` accordingly helps most of the time I guess.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from .datetimeformat import DateTimeFormat as DateTimeFormat
|
|
File without changes
|