python-intl 0.1.0__py3-none-any.whl → 0.2.0__py3-none-any.whl
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/datetimeformat.py +144 -180
- {python_intl-0.1.0.dist-info → python_intl-0.2.0.dist-info}/METADATA +1 -1
- python_intl-0.2.0.dist-info/RECORD +6 -0
- python_intl-0.1.0.dist-info/RECORD +0 -6
- {python_intl-0.1.0.dist-info → python_intl-0.2.0.dist-info}/WHEEL +0 -0
- {python_intl-0.1.0.dist-info → python_intl-0.2.0.dist-info}/licenses/LICENSE +0 -0
python_intl/datetimeformat.py
CHANGED
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import dataclasses
|
|
4
4
|
import datetime as dt
|
|
5
5
|
from functools import cache, cached_property
|
|
6
|
-
from typing import TYPE_CHECKING, Literal
|
|
6
|
+
from typing import TYPE_CHECKING, Literal
|
|
7
7
|
|
|
8
8
|
import icu # type: ignore[import-untyped]
|
|
9
9
|
|
|
@@ -13,6 +13,10 @@ if TYPE_CHECKING:
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
if TYPE_CHECKING:
|
|
16
|
+
type LocaleMatcherT = Literal["best fit", "lookup"]
|
|
17
|
+
type Hour12T = bool | None
|
|
18
|
+
type HourCycleT = Literal["h11", "h12", "h23", "h24"] | None
|
|
19
|
+
|
|
16
20
|
type EraFormatT = Literal["long", "short", "narrow"]
|
|
17
21
|
type YearFormatT = Literal["numeric", "2-digit"]
|
|
18
22
|
type MonthFormatT = Literal["numeric", "2-digit", "long", "short", "narrow"]
|
|
@@ -45,46 +49,14 @@ if TYPE_CHECKING:
|
|
|
45
49
|
| TimezoneNameFormatT
|
|
46
50
|
)
|
|
47
51
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
type WeekdayPatternT = Literal["E", "EEEE", "EEEEE"]
|
|
52
|
-
type DayPatternT = Literal["d", "dd"]
|
|
53
|
-
type DayPeriodPatternT = Literal["a", "aaaa", "aaaaa", "b", "bbbb", "bbbbb", "B", "BBBB", "BBBBB"]
|
|
54
|
-
type HourPatternT = Literal["j", "jj", "H", "h", "HH", "hh"]
|
|
55
|
-
type MinutePatternT = Literal["m", "mm"]
|
|
56
|
-
type SecondPatternT = Literal["s", "ss"]
|
|
57
|
-
type FractionSecondDigitsPatternT = Literal["S", "SS", "SSS"]
|
|
58
|
-
type TimezoneNamePatternT = Literal["z", "zzzz", "Z", "ZZZZ", "v", "vvvv"]
|
|
59
|
-
type AnyPatternT = (
|
|
60
|
-
EraPatternT
|
|
61
|
-
| YearPatternT
|
|
62
|
-
| MonthPatternT
|
|
63
|
-
| WeekdayPatternT
|
|
64
|
-
| DayPatternT
|
|
65
|
-
| DayPeriodPatternT
|
|
66
|
-
| HourPatternT
|
|
67
|
-
| MinutePatternT
|
|
68
|
-
| SecondPatternT
|
|
69
|
-
| FractionSecondDigitsPatternT
|
|
70
|
-
| TimezoneNamePatternT
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
type DatetimeFieldT = Literal[
|
|
74
|
-
"era",
|
|
75
|
-
"year",
|
|
76
|
-
"month",
|
|
77
|
-
"weekday",
|
|
78
|
-
"day",
|
|
79
|
-
"day_period",
|
|
80
|
-
"hour",
|
|
81
|
-
"minute",
|
|
82
|
-
"second",
|
|
83
|
-
"fraction_second_digits",
|
|
84
|
-
"time_zone_name",
|
|
85
|
-
]
|
|
86
|
-
|
|
52
|
+
# Important: Must be the same as DateTimeFormatOptions
|
|
53
|
+
# (nothing is required, as this will be used to construct a
|
|
54
|
+
# DateTimeFormatOptions instance, so default values apply then)
|
|
87
55
|
class DateTimeFormatOptionsDictT(TypedDict):
|
|
56
|
+
locale_matcher: NotRequired[LocaleMatcherT]
|
|
57
|
+
hour12: NotRequired[Hour12T]
|
|
58
|
+
hour_cycle: NotRequired[HourCycleT]
|
|
59
|
+
|
|
88
60
|
era: NotRequired[EraFormatT]
|
|
89
61
|
year: NotRequired[YearFormatT]
|
|
90
62
|
month: NotRequired[MonthFormatT]
|
|
@@ -97,25 +69,21 @@ if TYPE_CHECKING:
|
|
|
97
69
|
fraction_second_digits: NotRequired[FractionSecondDigitsFormatT]
|
|
98
70
|
time_zone_name: NotRequired[TimezoneNameFormatT]
|
|
99
71
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
hour: dict[HourFormatT, HourPatternT | tuple[HourPatternT, ...]]
|
|
108
|
-
minute: dict[MinuteFormatT, MinutePatternT | tuple[MinutePatternT, ...]]
|
|
109
|
-
second: dict[SecondFormatT, SecondPatternT | tuple[SecondPatternT, ...]]
|
|
110
|
-
fraction_second_digits: dict[
|
|
111
|
-
FractionSecondDigitsFormatT,
|
|
112
|
-
FractionSecondDigitsPatternT | tuple[FractionSecondDigitsPatternT, ...],
|
|
113
|
-
]
|
|
114
|
-
time_zone_name: dict[TimezoneNameFormatT, TimezoneNamePatternT | tuple[TimezoneNamePatternT, ...]]
|
|
72
|
+
|
|
73
|
+
_TIMEZONE_NAME_JS_MAPPING: dict[str | None, str] = {
|
|
74
|
+
"short_offset": "shortOffset",
|
|
75
|
+
"long_offset": "longOffset",
|
|
76
|
+
"short_generic": "shortGeneric",
|
|
77
|
+
"long_generic": "longGeneric",
|
|
78
|
+
}
|
|
115
79
|
|
|
116
80
|
|
|
117
81
|
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
118
82
|
class DateTimeFormatOptions:
|
|
83
|
+
locale_matcher: LocaleMatcherT = "best fit"
|
|
84
|
+
hour12: Hour12T = None
|
|
85
|
+
hour_cycle: HourCycleT = None
|
|
86
|
+
|
|
119
87
|
era: EraFormatT | None = None
|
|
120
88
|
year: YearFormatT | None = None
|
|
121
89
|
month: MonthFormatT | None = None
|
|
@@ -137,133 +105,128 @@ class DateTimeFormatOptions:
|
|
|
137
105
|
("month", self.month),
|
|
138
106
|
("weekday", self.weekday),
|
|
139
107
|
("day", self.day),
|
|
140
|
-
("
|
|
108
|
+
("dayPeriod", self.day_period),
|
|
141
109
|
("hour", self.hour),
|
|
142
110
|
("minute", self.minute),
|
|
143
111
|
("second", self.second),
|
|
144
|
-
("
|
|
145
|
-
("
|
|
112
|
+
("fractionalSecondDigits", self.fraction_second_digits),
|
|
113
|
+
("timeZoneName", _TIMEZONE_NAME_JS_MAPPING.get(self.time_zone_name, self.time_zone_name)),
|
|
146
114
|
)
|
|
147
115
|
if v is not None
|
|
148
116
|
}
|
|
149
117
|
|
|
150
118
|
|
|
151
|
-
OPTIONS_TO_SKELETON: OptionsToSkeletonT = {
|
|
152
|
-
"era": {"short": "G", "long": "GGGG", "narrow": "GGGGG"},
|
|
153
|
-
"year": {"numeric": ("yyyy", "y"), "2-digit": "yy"},
|
|
154
|
-
"month": {"numeric": "M", "2-digit": "MM", "short": "MMM", "long": "MMMM", "narrow": "MMMMM"},
|
|
155
|
-
"weekday": {"short": "E", "long": "EEEE", "narrow": "EEEEE"},
|
|
156
|
-
"day": {"numeric": "d", "2-digit": "dd"},
|
|
157
|
-
"day_period": {
|
|
158
|
-
"short": ("a", "b", "B"),
|
|
159
|
-
"long": ("aaaa", "bbbb", "BBBB"),
|
|
160
|
-
"narrow": ("aaaaa", "bbbbb", "BBBBB"),
|
|
161
|
-
},
|
|
162
|
-
"hour": {"numeric": ("j", "H", "h"), "2-digit": ("jj", "HH", "hh")},
|
|
163
|
-
"minute": {"numeric": "m", "2-digit": "mm"},
|
|
164
|
-
"second": {"numeric": "s", "2-digit": "ss"},
|
|
165
|
-
"fraction_second_digits": {1: "S", 2: "SS", 3: "SSS"},
|
|
166
|
-
"time_zone_name": {
|
|
167
|
-
"short": "z",
|
|
168
|
-
"long": "zzzz",
|
|
169
|
-
"short_offset": "Z",
|
|
170
|
-
"long_offset": "ZZZZ",
|
|
171
|
-
"short_generic": "v",
|
|
172
|
-
"long_generic": "vvvv",
|
|
173
|
-
},
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
@overload
|
|
178
|
-
def _option_to_skeleton_bit(
|
|
179
|
-
field: Literal["era"],
|
|
180
|
-
format: EraFormatT,
|
|
181
|
-
) -> EraPatternT | tuple[EraPatternT, ...]: ...
|
|
182
|
-
@overload
|
|
183
|
-
def _option_to_skeleton_bit(
|
|
184
|
-
field: Literal["year"],
|
|
185
|
-
format: YearFormatT,
|
|
186
|
-
) -> YearPatternT | tuple[YearPatternT, ...]: ...
|
|
187
|
-
@overload
|
|
188
|
-
def _option_to_skeleton_bit(
|
|
189
|
-
field: Literal["month"],
|
|
190
|
-
format: MonthFormatT,
|
|
191
|
-
) -> MonthPatternT | tuple[MonthPatternT, ...]: ...
|
|
192
|
-
@overload
|
|
193
|
-
def _option_to_skeleton_bit(
|
|
194
|
-
field: Literal["weekday"],
|
|
195
|
-
format: WeekdayFormatT,
|
|
196
|
-
) -> WeekdayPatternT | tuple[WeekdayPatternT, ...]: ...
|
|
197
|
-
@overload
|
|
198
|
-
def _option_to_skeleton_bit(
|
|
199
|
-
field: Literal["day"],
|
|
200
|
-
format: DayFormatT,
|
|
201
|
-
) -> DayPatternT | tuple[DayPatternT, ...]: ...
|
|
202
|
-
@overload
|
|
203
|
-
def _option_to_skeleton_bit(
|
|
204
|
-
field: Literal["day_period"],
|
|
205
|
-
format: DayPeriodFormatT,
|
|
206
|
-
) -> DayPeriodPatternT | tuple[DayPeriodPatternT, ...]: ...
|
|
207
|
-
@overload
|
|
208
|
-
def _option_to_skeleton_bit(
|
|
209
|
-
field: Literal["hour"],
|
|
210
|
-
format: HourFormatT,
|
|
211
|
-
) -> HourPatternT | tuple[HourPatternT, ...]: ...
|
|
212
|
-
@overload
|
|
213
|
-
def _option_to_skeleton_bit(
|
|
214
|
-
field: Literal["minute"],
|
|
215
|
-
format: MinuteFormatT,
|
|
216
|
-
) -> MinutePatternT | tuple[MinutePatternT, ...]: ...
|
|
217
|
-
@overload
|
|
218
|
-
def _option_to_skeleton_bit(
|
|
219
|
-
field: Literal["second"],
|
|
220
|
-
format: SecondFormatT,
|
|
221
|
-
) -> SecondPatternT | tuple[SecondPatternT, ...]: ...
|
|
222
|
-
@overload
|
|
223
|
-
def _option_to_skeleton_bit(
|
|
224
|
-
field: Literal["fraction_second_digits"],
|
|
225
|
-
format: FractionSecondDigitsFormatT,
|
|
226
|
-
) -> FractionSecondDigitsPatternT | tuple[FractionSecondDigitsPatternT, ...]: ...
|
|
227
|
-
@overload
|
|
228
|
-
def _option_to_skeleton_bit(
|
|
229
|
-
field: Literal["time_zone_name"],
|
|
230
|
-
format: TimezoneNameFormatT,
|
|
231
|
-
) -> TimezoneNamePatternT | tuple[TimezoneNamePatternT, ...]: ...
|
|
232
|
-
def _option_to_skeleton_bit(
|
|
233
|
-
field,
|
|
234
|
-
format,
|
|
235
|
-
):
|
|
236
|
-
return OPTIONS_TO_SKELETON[field][format]
|
|
237
|
-
|
|
238
|
-
|
|
239
119
|
def _options_to_possible_skeletons(options: DateTimeFormatOptions) -> Iterable[str]:
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
#
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
120
|
+
skeleton_parts: list[str | tuple[str, ...]] = []
|
|
121
|
+
|
|
122
|
+
# Note: The parts should be ordered from big to small.
|
|
123
|
+
|
|
124
|
+
match options.era:
|
|
125
|
+
case "short":
|
|
126
|
+
skeleton_parts.append("G")
|
|
127
|
+
case "long":
|
|
128
|
+
skeleton_parts.append("GGGG")
|
|
129
|
+
case "narrow":
|
|
130
|
+
skeleton_parts.append("GGGGG")
|
|
131
|
+
|
|
132
|
+
match options.year:
|
|
133
|
+
case "numeric":
|
|
134
|
+
skeleton_parts.append(("yyyy", "y"))
|
|
135
|
+
case "2-digit":
|
|
136
|
+
skeleton_parts.append("yy")
|
|
137
|
+
|
|
138
|
+
match options.month:
|
|
139
|
+
case "numeric":
|
|
140
|
+
skeleton_parts.append("M")
|
|
141
|
+
case "2-digit":
|
|
142
|
+
skeleton_parts.append("MM")
|
|
143
|
+
case "short":
|
|
144
|
+
skeleton_parts.append("MMM")
|
|
145
|
+
case "long":
|
|
146
|
+
skeleton_parts.append("MMMM")
|
|
147
|
+
case "narrow":
|
|
148
|
+
skeleton_parts.append("MMMMM")
|
|
149
|
+
|
|
150
|
+
match options.weekday:
|
|
151
|
+
case "short":
|
|
152
|
+
skeleton_parts.append("E")
|
|
153
|
+
case "long":
|
|
154
|
+
skeleton_parts.append("EEEE")
|
|
155
|
+
case "narrow":
|
|
156
|
+
skeleton_parts.append("EEEEE")
|
|
157
|
+
|
|
158
|
+
match options.day:
|
|
159
|
+
case "numeric":
|
|
160
|
+
skeleton_parts.append("d")
|
|
161
|
+
case "2-digit":
|
|
162
|
+
skeleton_parts.append("dd")
|
|
163
|
+
|
|
164
|
+
match options.day_period:
|
|
165
|
+
case "short":
|
|
166
|
+
skeleton_parts.append("B")
|
|
167
|
+
case "long":
|
|
168
|
+
skeleton_parts.append("BBBB")
|
|
169
|
+
case "narrow":
|
|
170
|
+
skeleton_parts.append("BBBBB")
|
|
171
|
+
|
|
172
|
+
match (options.hour_cycle, options.hour12, options.hour):
|
|
173
|
+
case ("h11", _, "numeric"):
|
|
174
|
+
skeleton_parts.append(("aK", "K"))
|
|
175
|
+
case ("h11", _, "2-digit"):
|
|
176
|
+
skeleton_parts.append(("aKK", "KK"))
|
|
177
|
+
case ("h12", _, "numeric"):
|
|
178
|
+
skeleton_parts.append(("ah", "h"))
|
|
179
|
+
case ("h12", _, "2-digit"):
|
|
180
|
+
skeleton_parts.append(("ahh", "hh"))
|
|
181
|
+
case ("h23", _, "numeric"):
|
|
182
|
+
skeleton_parts.append("H")
|
|
183
|
+
case ("h23", _, "2-digit"):
|
|
184
|
+
skeleton_parts.append("HH")
|
|
185
|
+
case ("h24", _, "numeric"):
|
|
186
|
+
skeleton_parts.append("k")
|
|
187
|
+
case ("h24", _, "2-digit"):
|
|
188
|
+
skeleton_parts.append("kk")
|
|
189
|
+
case (_, True, "numeric"):
|
|
190
|
+
skeleton_parts.append(("ah", "h"))
|
|
191
|
+
case (_, True, "2-digit"):
|
|
192
|
+
skeleton_parts.append(("ahh", "hh"))
|
|
193
|
+
case (_, False, "numeric"):
|
|
194
|
+
skeleton_parts.append("H")
|
|
195
|
+
case (_, False, "2-digit"):
|
|
196
|
+
skeleton_parts.append("HH")
|
|
197
|
+
case (_, _, "numeric"):
|
|
198
|
+
skeleton_parts.append("j")
|
|
199
|
+
case (_, _, "2-digit"):
|
|
200
|
+
skeleton_parts.append(("jj", "j"))
|
|
201
|
+
|
|
202
|
+
match options.minute:
|
|
203
|
+
case "numeric":
|
|
204
|
+
skeleton_parts.append("m")
|
|
205
|
+
case "2-digit":
|
|
206
|
+
skeleton_parts.append("mm")
|
|
207
|
+
|
|
208
|
+
match options.second:
|
|
209
|
+
case "numeric":
|
|
210
|
+
skeleton_parts.append("s")
|
|
211
|
+
case "2-digit":
|
|
212
|
+
skeleton_parts.append("ss")
|
|
213
|
+
|
|
261
214
|
if options.fraction_second_digits:
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
215
|
+
skeleton_parts.append("S" * options.fraction_second_digits)
|
|
216
|
+
|
|
217
|
+
match options.time_zone_name:
|
|
218
|
+
case "short":
|
|
219
|
+
skeleton_parts.append("z")
|
|
220
|
+
case "long":
|
|
221
|
+
skeleton_parts.append("zzzz")
|
|
222
|
+
case "short_offset":
|
|
223
|
+
skeleton_parts.append("Z")
|
|
224
|
+
case "long_offset":
|
|
225
|
+
skeleton_parts.append("ZZZZ")
|
|
226
|
+
case "short_generic":
|
|
227
|
+
skeleton_parts.append("v")
|
|
228
|
+
case "long_generic":
|
|
229
|
+
skeleton_parts.append("vvvv")
|
|
267
230
|
|
|
268
231
|
def generate_skeletons(prefix: str, remaining: list[str | tuple[str, ...]]) -> Iterable[str]:
|
|
269
232
|
if not remaining:
|
|
@@ -277,7 +240,7 @@ def _options_to_possible_skeletons(options: DateTimeFormatOptions) -> Iterable[s
|
|
|
277
240
|
else:
|
|
278
241
|
yield from generate_skeletons(prefix + next_bit, remaining[1:])
|
|
279
242
|
|
|
280
|
-
yield from generate_skeletons("",
|
|
243
|
+
yield from generate_skeletons("", skeleton_parts)
|
|
281
244
|
|
|
282
245
|
|
|
283
246
|
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
@@ -312,13 +275,14 @@ def _options_to_format_pattern(
|
|
|
312
275
|
)
|
|
313
276
|
|
|
314
277
|
# Try to find best match
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
278
|
+
if options.locale_matcher == "best fit":
|
|
279
|
+
for skeleton in possible_skeletons:
|
|
280
|
+
pattern = generator.getBestPattern(skeleton)
|
|
281
|
+
if pattern:
|
|
282
|
+
return MatchedFormatPattern(
|
|
283
|
+
skeleton=skeleton,
|
|
284
|
+
pattern=pattern,
|
|
285
|
+
)
|
|
322
286
|
|
|
323
287
|
raise FormatPatternNotFoundException("Didn't find pattern for desired options")
|
|
324
288
|
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
python_intl/__init__.py,sha256=eLk_lPxY8d3up4pTUCx94otMig3X2yxcE3qIYWXAIlI,61
|
|
2
|
+
python_intl/datetimeformat.py,sha256=P4DdEhArGbV5trXnJ0wENUjXHRwgdggoEMHMLkHC51s,10469
|
|
3
|
+
python_intl-0.2.0.dist-info/licenses/LICENSE,sha256=vBhn8BZCY-G3D6-ca99f4FU3ggdJSrKBTa3Yk8ZIAN8,1078
|
|
4
|
+
python_intl-0.2.0.dist-info/WHEEL,sha256=4OL6Foqnnp3xRY5wMkjgc25_i5YJC6dKsC6LPcjqEoU,80
|
|
5
|
+
python_intl-0.2.0.dist-info/METADATA,sha256=p1sROEE0bkG9nguk0SyGMAkO27Tpf83bB2qkbkV3r1I,1370
|
|
6
|
+
python_intl-0.2.0.dist-info/RECORD,,
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
python_intl/__init__.py,sha256=eLk_lPxY8d3up4pTUCx94otMig3X2yxcE3qIYWXAIlI,61
|
|
2
|
-
python_intl/datetimeformat.py,sha256=twbFkPeG_3TzZXlJ4VT4NrwkjfOXwwGWe_pdMSZw1BQ,12547
|
|
3
|
-
python_intl-0.1.0.dist-info/licenses/LICENSE,sha256=vBhn8BZCY-G3D6-ca99f4FU3ggdJSrKBTa3Yk8ZIAN8,1078
|
|
4
|
-
python_intl-0.1.0.dist-info/WHEEL,sha256=4OL6Foqnnp3xRY5wMkjgc25_i5YJC6dKsC6LPcjqEoU,80
|
|
5
|
-
python_intl-0.1.0.dist-info/METADATA,sha256=juS5u5Q5jsM7Xuckx9UqxkqikX-VdaEACQvxXYACjfE,1370
|
|
6
|
-
python_intl-0.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|