Mercurial > babel > old > mirror
annotate babel/dates.py @ 228:629357c88d59
Only write unique comments, no duplicates.
author | palgarvio |
---|---|
date | Fri, 20 Jul 2007 14:22:50 +0000 |
parents | 49b089453f81 |
children | d0cd235ede46 |
rev | line source |
---|---|
3 | 1 # -*- coding: utf-8 -*- |
2 # | |
3 # Copyright (C) 2007 Edgewall Software | |
4 # All rights reserved. | |
5 # | |
6 # This software is licensed as described in the file COPYING, which | |
7 # you should have received as part of this distribution. The terms | |
8 # are also available at http://babel.edgewall.org/wiki/License. | |
9 # | |
10 # This software consists of voluntary contributions made by many | |
11 # individuals. For the exact contribution history, see the revision | |
12 # history and logs, available at http://babel.edgewall.org/log/. | |
13 | |
14 """Locale dependent formatting and parsing of dates and times. | |
15 | |
16 The default locale for the functions in this module is determined by the | |
17 following environment variables, in that order: | |
18 | |
19 * ``LC_TIME``, | |
20 * ``LC_ALL``, and | |
21 * ``LANG`` | |
22 """ | |
23 | |
31 | 24 from datetime import date, datetime, time, timedelta, tzinfo |
40
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
25 import re |
3 | 26 |
74
d9c34d2f3d1d
More explicit module-level function names in `babel.core`. Added `Locale.negotiate` class method.
cmlenz
parents:
48
diff
changeset
|
27 from babel.core import default_locale, Locale |
41
359ec55de578
Move function for determining the system default locale to `babel.core`, and make it available as a class method on `Locale`.
cmlenz
parents:
40
diff
changeset
|
28 from babel.util import UTC |
3 | 29 |
30 __all__ = ['format_date', 'format_datetime', 'format_time', 'parse_date', | |
31 'parse_datetime', 'parse_time'] | |
32 __docformat__ = 'restructuredtext en' | |
33 | |
74
d9c34d2f3d1d
More explicit module-level function names in `babel.core`. Added `Locale.negotiate` class method.
cmlenz
parents:
48
diff
changeset
|
34 LC_TIME = default_locale('LC_TIME') |
3 | 35 |
35 | 36 # Aliases for use in scopes where the modules are shadowed by local variables |
37 date_ = date | |
38 datetime_ = datetime | |
39 time_ = time | |
40 | |
3 | 41 def get_period_names(locale=LC_TIME): |
42 """Return the names for day periods (AM/PM) used by the locale. | |
43 | |
44 >>> get_period_names(locale='en_US')['am'] | |
45 u'AM' | |
46 | |
47 :param locale: the `Locale` object, or a locale string | |
48 :return: the dictionary of period names | |
49 :rtype: `dict` | |
50 """ | |
51 return Locale.parse(locale).periods | |
52 | |
53 def get_day_names(width='wide', context='format', locale=LC_TIME): | |
54 """Return the day names used by the locale for the specified format. | |
55 | |
56 >>> get_day_names('wide', locale='en_US')[1] | |
17 | 57 u'Tuesday' |
3 | 58 >>> get_day_names('abbreviated', locale='es')[1] |
17 | 59 u'mar' |
3 | 60 >>> get_day_names('narrow', context='stand-alone', locale='de_DE')[1] |
17 | 61 u'D' |
3 | 62 |
63 :param width: the width to use, one of "wide", "abbreviated", or "narrow" | |
64 :param context: the context, either "format" or "stand-alone" | |
65 :param locale: the `Locale` object, or a locale string | |
66 :return: the dictionary of day names | |
67 :rtype: `dict` | |
68 """ | |
69 return Locale.parse(locale).days[context][width] | |
70 | |
71 def get_month_names(width='wide', context='format', locale=LC_TIME): | |
72 """Return the month names used by the locale for the specified format. | |
73 | |
74 >>> get_month_names('wide', locale='en_US')[1] | |
75 u'January' | |
76 >>> get_month_names('abbreviated', locale='es')[1] | |
77 u'ene' | |
78 >>> get_month_names('narrow', context='stand-alone', locale='de_DE')[1] | |
79 u'J' | |
80 | |
81 :param width: the width to use, one of "wide", "abbreviated", or "narrow" | |
82 :param context: the context, either "format" or "stand-alone" | |
83 :param locale: the `Locale` object, or a locale string | |
84 :return: the dictionary of month names | |
85 :rtype: `dict` | |
86 """ | |
87 return Locale.parse(locale).months[context][width] | |
88 | |
89 def get_quarter_names(width='wide', context='format', locale=LC_TIME): | |
90 """Return the quarter names used by the locale for the specified format. | |
91 | |
92 >>> get_quarter_names('wide', locale='en_US')[1] | |
93 u'1st quarter' | |
94 >>> get_quarter_names('abbreviated', locale='de_DE')[1] | |
95 u'Q1' | |
96 | |
97 :param width: the width to use, one of "wide", "abbreviated", or "narrow" | |
98 :param context: the context, either "format" or "stand-alone" | |
99 :param locale: the `Locale` object, or a locale string | |
100 :return: the dictionary of quarter names | |
101 :rtype: `dict` | |
102 """ | |
103 return Locale.parse(locale).quarters[context][width] | |
104 | |
105 def get_era_names(width='wide', locale=LC_TIME): | |
106 """Return the era names used by the locale for the specified format. | |
107 | |
108 >>> get_era_names('wide', locale='en_US')[1] | |
109 u'Anno Domini' | |
110 >>> get_era_names('abbreviated', locale='de_DE')[1] | |
111 u'n. Chr.' | |
112 | |
113 :param width: the width to use, either "wide" or "abbreviated" | |
114 :param locale: the `Locale` object, or a locale string | |
115 :return: the dictionary of era names | |
116 :rtype: `dict` | |
117 """ | |
118 return Locale.parse(locale).eras[width] | |
119 | |
120 def get_date_format(format='medium', locale=LC_TIME): | |
121 """Return the date formatting patterns used by the locale for the specified | |
122 format. | |
123 | |
124 >>> get_date_format(locale='en_US') | |
14
29ef15a6fd75
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
10
diff
changeset
|
125 <DateTimePattern u'MMM d, yyyy'> |
3 | 126 >>> get_date_format('full', locale='de_DE') |
14
29ef15a6fd75
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
10
diff
changeset
|
127 <DateTimePattern u'EEEE, d. MMMM yyyy'> |
3 | 128 |
129 :param format: the format to use, one of "full", "long", "medium", or | |
130 "short" | |
131 :param locale: the `Locale` object, or a locale string | |
132 :return: the date format pattern | |
35 | 133 :rtype: `DateTimePattern` |
3 | 134 """ |
135 return Locale.parse(locale).date_formats[format] | |
136 | |
35 | 137 def get_datetime_format(format='medium', locale=LC_TIME): |
138 """Return the datetime formatting patterns used by the locale for the | |
139 specified format. | |
140 | |
141 >>> get_datetime_format(locale='en_US') | |
142 u'{1} {0}' | |
143 | |
144 :param format: the format to use, one of "full", "long", "medium", or | |
145 "short" | |
146 :param locale: the `Locale` object, or a locale string | |
147 :return: the datetime format pattern | |
148 :rtype: `unicode` | |
149 """ | |
150 patterns = Locale.parse(locale).datetime_formats | |
151 if format not in patterns: | |
152 format = None | |
153 return patterns[format] | |
154 | |
3 | 155 def get_time_format(format='medium', locale=LC_TIME): |
156 """Return the time formatting patterns used by the locale for the specified | |
157 format. | |
158 | |
159 >>> get_time_format(locale='en_US') | |
14
29ef15a6fd75
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
10
diff
changeset
|
160 <DateTimePattern u'h:mm:ss a'> |
3 | 161 >>> get_time_format('full', locale='de_DE') |
14
29ef15a6fd75
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
10
diff
changeset
|
162 <DateTimePattern u"H:mm' Uhr 'z"> |
3 | 163 |
164 :param format: the format to use, one of "full", "long", "medium", or | |
165 "short" | |
166 :param locale: the `Locale` object, or a locale string | |
167 :return: the time format pattern | |
35 | 168 :rtype: `DateTimePattern` |
3 | 169 """ |
170 return Locale.parse(locale).time_formats[format] | |
171 | |
35 | 172 def format_date(date=None, format='medium', locale=LC_TIME): |
103
1ba215a5774d
Add wrapper class bundling the various formatting functions bound to a specific locale and time-zone.
cmlenz
parents:
74
diff
changeset
|
173 """Return a date formatted according to the given pattern. |
3 | 174 |
175 >>> d = date(2007, 04, 01) | |
176 >>> format_date(d, locale='en_US') | |
177 u'Apr 1, 2007' | |
178 >>> format_date(d, format='full', locale='de_DE') | |
179 u'Sonntag, 1. April 2007' | |
180 | |
18 | 181 If you don't want to use the locale default formats, you can specify a |
182 custom date pattern: | |
183 | |
31 | 184 >>> format_date(d, "EEE, MMM d, ''yy", locale='en') |
18 | 185 u"Sun, Apr 1, '07" |
186 | |
35 | 187 :param date: the ``date`` or ``datetime`` object; if `None`, the current |
188 date is used | |
18 | 189 :param format: one of "full", "long", "medium", or "short", or a custom |
190 date/time pattern | |
21
646f3f7e6a9a
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
20
diff
changeset
|
191 :param locale: a `Locale` object or a locale identifier |
3 | 192 :rtype: `unicode` |
21
646f3f7e6a9a
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
20
diff
changeset
|
193 |
646f3f7e6a9a
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
20
diff
changeset
|
194 :note: If the pattern contains time fields, an `AttributeError` will be |
646f3f7e6a9a
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
20
diff
changeset
|
195 raised when trying to apply the formatting. This is also true if |
646f3f7e6a9a
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
20
diff
changeset
|
196 the value of ``date`` parameter is actually a ``datetime`` object, |
646f3f7e6a9a
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
20
diff
changeset
|
197 as this function automatically converts that to a ``date``. |
3 | 198 """ |
35 | 199 if date is None: |
200 date = date_.today() | |
201 elif isinstance(date, datetime): | |
20
dce4cfd4ba5d
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
18
diff
changeset
|
202 date = date.date() |
36 | 203 |
3 | 204 locale = Locale.parse(locale) |
205 if format in ('full', 'long', 'medium', 'short'): | |
206 format = get_date_format(format, locale=locale) | |
207 pattern = parse_pattern(format) | |
208 return parse_pattern(format).apply(date, locale) | |
209 | |
36 | 210 def format_datetime(datetime=None, format='medium', tzinfo=None, |
211 locale=LC_TIME): | |
103
1ba215a5774d
Add wrapper class bundling the various formatting functions bound to a specific locale and time-zone.
cmlenz
parents:
74
diff
changeset
|
212 """Return a date formatted according to the given pattern. |
3 | 213 |
35 | 214 >>> dt = datetime(2007, 04, 01, 15, 30) |
215 >>> format_datetime(dt, locale='en_US') | |
216 u'Apr 1, 2007 3:30:00 PM' | |
217 | |
36 | 218 For any pattern requiring the display of the time-zone, the third-party |
219 ``pytz`` package is needed to explicitly specify the time-zone: | |
220 | |
221 >>> from pytz import timezone | |
222 >>> format_datetime(dt, 'full', tzinfo=timezone('Europe/Berlin'), | |
223 ... locale='de_DE') | |
224 u'Sonntag, 1. April 2007 17:30 Uhr MESZ' | |
225 >>> format_datetime(dt, "yyyy.MM.dd G 'at' HH:mm:ss zzz", | |
226 ... tzinfo=timezone('US/Eastern'), locale='en') | |
227 u'2007.04.01 AD at 11:30:00 EDT' | |
228 | |
35 | 229 :param datetime: the `datetime` object; if `None`, the current date and |
230 time is used | |
18 | 231 :param format: one of "full", "long", "medium", or "short", or a custom |
232 date/time pattern | |
31 | 233 :param tzinfo: the timezone to apply to the time for display |
21
646f3f7e6a9a
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
20
diff
changeset
|
234 :param locale: a `Locale` object or a locale identifier |
3 | 235 :rtype: `unicode` |
236 """ | |
35 | 237 if datetime is None: |
238 datetime = datetime_.now() | |
36 | 239 elif isinstance(datetime, (int, long)): |
240 datetime = datetime.fromtimestamp(datetime) | |
241 elif isinstance(datetime, time): | |
242 datetime = datetime_.combine(date.today(), datetime) | |
243 if datetime.tzinfo is None: | |
244 datetime = datetime.replace(tzinfo=UTC) | |
245 if tzinfo is not None: | |
246 datetime = datetime.astimezone(tzinfo) | |
103
1ba215a5774d
Add wrapper class bundling the various formatting functions bound to a specific locale and time-zone.
cmlenz
parents:
74
diff
changeset
|
247 if hasattr(tzinfo, 'normalize'): # pytz |
36 | 248 datetime = tzinfo.normalize(datetime) |
249 | |
20
dce4cfd4ba5d
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
18
diff
changeset
|
250 locale = Locale.parse(locale) |
dce4cfd4ba5d
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
18
diff
changeset
|
251 if format in ('full', 'long', 'medium', 'short'): |
35 | 252 return get_datetime_format(format, locale=locale) \ |
36 | 253 .replace('{0}', format_time(datetime, format, tzinfo=None, |
35 | 254 locale=locale)) \ |
255 .replace('{1}', format_date(datetime, format, locale=locale)) | |
256 else: | |
257 return parse_pattern(format).apply(datetime, locale) | |
3 | 258 |
36 | 259 def format_time(time=None, format='medium', tzinfo=None, locale=LC_TIME): |
103
1ba215a5774d
Add wrapper class bundling the various formatting functions bound to a specific locale and time-zone.
cmlenz
parents:
74
diff
changeset
|
260 """Return a time formatted according to the given pattern. |
3 | 261 |
262 >>> t = time(15, 30) | |
263 >>> format_time(t, locale='en_US') | |
264 u'3:30:00 PM' | |
265 >>> format_time(t, format='short', locale='de_DE') | |
266 u'15:30' | |
267 | |
18 | 268 If you don't want to use the locale default formats, you can specify a |
269 custom time pattern: | |
270 | |
271 >>> format_time(t, "hh 'o''clock' a", locale='en') | |
272 u"03 o'clock PM" | |
273 | |
31 | 274 For any pattern requiring the display of the time-zone, the third-party |
275 ``pytz`` package is needed to explicitly specify the time-zone: | |
276 | |
103
1ba215a5774d
Add wrapper class bundling the various formatting functions bound to a specific locale and time-zone.
cmlenz
parents:
74
diff
changeset
|
277 >>> from pytz import timezone |
1ba215a5774d
Add wrapper class bundling the various formatting functions bound to a specific locale and time-zone.
cmlenz
parents:
74
diff
changeset
|
278 >>> t = time(15, 30) |
36 | 279 >>> format_time(t, format='full', tzinfo=timezone('Europe/Berlin'), |
280 ... locale='de_DE') | |
281 u'17:30 Uhr MESZ' | |
282 >>> format_time(t, "hh 'o''clock' a, zzzz", tzinfo=timezone('US/Eastern'), | |
283 ... locale='en') | |
284 u"11 o'clock AM, Eastern Daylight Time" | |
31 | 285 |
35 | 286 :param time: the ``time`` or ``datetime`` object; if `None`, the current |
287 time is used | |
18 | 288 :param format: one of "full", "long", "medium", or "short", or a custom |
289 date/time pattern | |
31 | 290 :param tzinfo: the time-zone to apply to the time for display |
21
646f3f7e6a9a
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
20
diff
changeset
|
291 :param locale: a `Locale` object or a locale identifier |
3 | 292 :rtype: `unicode` |
21
646f3f7e6a9a
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
20
diff
changeset
|
293 |
646f3f7e6a9a
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
20
diff
changeset
|
294 :note: If the pattern contains date fields, an `AttributeError` will be |
646f3f7e6a9a
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
20
diff
changeset
|
295 raised when trying to apply the formatting. This is also true if |
646f3f7e6a9a
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
20
diff
changeset
|
296 the value of ``time`` parameter is actually a ``datetime`` object, |
646f3f7e6a9a
Moved some datetime tests from doctest to unittest, to avoid breaking docutils/epydoc doctest block detection.
cmlenz
parents:
20
diff
changeset
|
297 as this function automatically converts that to a ``time``. |
3 | 298 """ |
35 | 299 if time is None: |
300 time = datetime.now().time() | |
301 elif isinstance(time, (int, long)): | |
20
dce4cfd4ba5d
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
18
diff
changeset
|
302 time = datetime.fromtimestamp(time).time() |
dce4cfd4ba5d
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
18
diff
changeset
|
303 elif isinstance(time, datetime): |
36 | 304 time = time.timetz() |
31 | 305 if time.tzinfo is None: |
36 | 306 time = time.replace(tzinfo=UTC) |
307 if tzinfo is not None: | |
308 dt = datetime.combine(date.today(), time).astimezone(tzinfo) | |
103
1ba215a5774d
Add wrapper class bundling the various formatting functions bound to a specific locale and time-zone.
cmlenz
parents:
74
diff
changeset
|
309 if hasattr(tzinfo, 'normalize'): # pytz |
36 | 310 dt = tzinfo.normalize(dt) |
311 time = dt.timetz() | |
312 | |
3 | 313 locale = Locale.parse(locale) |
314 if format in ('full', 'long', 'medium', 'short'): | |
315 format = get_time_format(format, locale=locale) | |
316 return parse_pattern(format).apply(time, locale) | |
317 | |
318 def parse_date(string, locale=LC_TIME): | |
40
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
319 """Parse a date from a string. |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
320 |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
321 This function uses the date format for the locale as a hint to determine |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
322 the order in which the date fields appear in the string. |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
323 |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
324 >>> parse_date('4/1/04', locale='en_US') |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
325 datetime.date(2004, 4, 1) |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
326 >>> parse_date('01.04.2004', locale='de_DE') |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
327 datetime.date(2004, 4, 1) |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
328 |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
329 :param string: the string containing the date |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
330 :param locale: a `Locale` object or a locale identifier |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
331 :return: the parsed date |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
332 :rtype: `date` |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
333 """ |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
334 # TODO: try ISO format first? |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
335 format = get_date_format(locale=locale).pattern.lower() |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
336 year_idx = format.index('y') |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
337 month_idx = format.index('m') |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
338 if month_idx < 0: |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
339 month_idx = format.index('l') |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
340 day_idx = format.index('d') |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
341 |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
342 indexes = [(year_idx, 'Y'), (month_idx, 'M'), (day_idx, 'D')] |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
343 indexes.sort() |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
344 indexes = dict([(item[1], idx) for idx, item in enumerate(indexes)]) |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
345 |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
346 # FIXME: this currently only supports numbers, but should also support month |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
347 # names, both in the requested locale, and english |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
348 |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
349 numbers = re.findall('(\d+)', string) |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
350 year = numbers[indexes['Y']] |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
351 if len(year) == 2: |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
352 year = 2000 + int(year) |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
353 else: |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
354 year = int(year) |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
355 month = int(numbers[indexes['M']]) |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
356 day = int(numbers[indexes['D']]) |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
357 if month > 12: |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
358 month, day = day, month |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
359 return date(year, month, day) |
3 | 360 |
361 def parse_datetime(string, locale=LC_TIME): | |
48 | 362 """Parse a date and time from a string. |
363 | |
364 This function uses the date and time formats for the locale as a hint to | |
365 determine the order in which the time fields appear in the string. | |
366 | |
367 :param string: the string containing the date and time | |
368 :param locale: a `Locale` object or a locale identifier | |
369 :return: the parsed date/time | |
370 :rtype: `datetime` | |
371 """ | |
3 | 372 raise NotImplementedError |
373 | |
374 def parse_time(string, locale=LC_TIME): | |
48 | 375 """Parse a time from a string. |
40
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
376 |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
377 This function uses the time format for the locale as a hint to determine |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
378 the order in which the time fields appear in the string. |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
379 |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
380 >>> parse_time('15:30:00', locale='en_US') |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
381 datetime.time(15, 30) |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
382 |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
383 :param string: the string containing the time |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
384 :param locale: a `Locale` object or a locale identifier |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
385 :return: the parsed time |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
386 :rtype: `time` |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
387 """ |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
388 # TODO: try ISO format first? |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
389 format = get_time_format(locale=locale).pattern.lower() |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
390 hour_idx = format.index('h') |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
391 if hour_idx < 0: |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
392 hour_idx = format.index('k') |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
393 min_idx = format.index('m') |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
394 sec_idx = format.index('s') |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
395 |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
396 indexes = [(hour_idx, 'H'), (min_idx, 'M'), (sec_idx, 'S')] |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
397 indexes.sort() |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
398 indexes = dict([(item[1], idx) for idx, item in enumerate(indexes)]) |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
399 |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
400 # FIXME: support 12 hour clock, and 0-based hour specification |
48 | 401 # and seconds should be optional, maybe minutes too |
402 # oh, and time-zones, of course | |
40
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
403 |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
404 numbers = re.findall('(\d+)', string) |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
405 hour = int(numbers[indexes['H']]) |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
406 minute = int(numbers[indexes['M']]) |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
407 second = int(numbers[indexes['S']]) |
496a5c3f9d6d
Started implementation of datetime parsing, using a very basic approach for now.
cmlenz
parents:
36
diff
changeset
|
408 return time(hour, minute, second) |
3 | 409 |
410 | |
14
29ef15a6fd75
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
10
diff
changeset
|
411 class DateTimePattern(object): |
3 | 412 |
413 def __init__(self, pattern, format): | |
414 self.pattern = pattern | |
415 self.format = format | |
416 | |
417 def __repr__(self): | |
418 return '<%s %r>' % (type(self).__name__, self.pattern) | |
419 | |
420 def __unicode__(self): | |
421 return self.pattern | |
422 | |
423 def __mod__(self, other): | |
424 assert type(other) is DateTimeFormat | |
425 return self.format % other | |
426 | |
427 def apply(self, datetime, locale): | |
428 return self % DateTimeFormat(datetime, locale) | |
429 | |
430 | |
431 class DateTimeFormat(object): | |
432 | |
433 def __init__(self, value, locale): | |
434 assert isinstance(value, (date, datetime, time)) | |
31 | 435 if isinstance(value, (datetime, time)) and value.tzinfo is None: |
436 value = value.replace(tzinfo=UTC) | |
3 | 437 self.value = value |
438 self.locale = Locale.parse(locale) | |
439 | |
440 def __getitem__(self, name): | |
17 | 441 char = name[0] |
442 num = len(name) | |
443 if char == 'G': | |
444 return self.format_era(char, num) | |
217
15ac328954f5
Dummy/stub implementation for week-in-year and week-in-month date format fields. Also, treat extended year the same as the regular year field, not even ICU seems to handle it specially.
cmlenz
parents:
131
diff
changeset
|
445 elif char in ('y', 'Y', 'u'): |
17 | 446 return self.format_year(char, num) |
447 elif char in ('Q', 'q'): | |
448 return self.format_quarter(char, num) | |
449 elif char in ('M', 'L'): | |
450 return self.format_month(char, num) | |
217
15ac328954f5
Dummy/stub implementation for week-in-year and week-in-month date format fields. Also, treat extended year the same as the regular year field, not even ICU seems to handle it specially.
cmlenz
parents:
131
diff
changeset
|
451 elif char in ('w', 'W'): |
15ac328954f5
Dummy/stub implementation for week-in-year and week-in-month date format fields. Also, treat extended year the same as the regular year field, not even ICU seems to handle it specially.
cmlenz
parents:
131
diff
changeset
|
452 return self.format_week(char, num) |
17 | 453 elif char == 'd': |
454 return self.format(self.value.day, num) | |
223
49b089453f81
Implement day-of-year date format field. Closes #49.
cmlenz
parents:
219
diff
changeset
|
455 elif char == 'D': |
49b089453f81
Implement day-of-year date format field. Closes #49.
cmlenz
parents:
219
diff
changeset
|
456 return self.format_day_of_year(num) |
17 | 457 elif char in ('E', 'e', 'c'): |
458 return self.format_weekday(char, num) | |
459 elif char == 'a': | |
460 return self.format_period(char) | |
461 elif char == 'h': | |
462 return self.format(self.value.hour % 12, num) | |
463 elif char == 'H': | |
464 return self.format(self.value.hour, num) | |
20
dce4cfd4ba5d
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
18
diff
changeset
|
465 elif char == 'K': |
dce4cfd4ba5d
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
18
diff
changeset
|
466 return self.format(self.value.hour % 12 - 1, num) |
dce4cfd4ba5d
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
18
diff
changeset
|
467 elif char == 'k': |
dce4cfd4ba5d
Started documentation for date formatting, plus some code tweaks in that area.
cmlenz
parents:
18
diff
changeset
|
468 return self.format(self.value.hour + 1, num) |
17 | 469 elif char == 'm': |
470 return self.format(self.value.minute, num) | |
471 elif char == 's': | |
472 return self.format(self.value.second, num) | |
218
0a30f3974997
Support for fractional seconds field in date formatting. Closes #47.
cmlenz
parents:
217
diff
changeset
|
473 elif char == 'S': |
219 | 474 return self.format_frac_seconds(num) |
475 elif char == 'A': | |
476 return self.format_milliseconds_in_day(num) | |
31 | 477 elif char in ('z', 'Z', 'v'): |
478 return self.format_timezone(char, num) | |
3 | 479 else: |
17 | 480 raise KeyError('Unsupported date/time field %r' % char) |
3 | 481 |
17 | 482 def format_era(self, char, num): |
3 | 483 width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[max(3, num)] |
484 era = int(self.value.year >= 0) | |
485 return get_era_names(width, self.locale)[era] | |
486 | |
17 | 487 def format_year(self, char, num): |
488 if char.islower(): | |
489 value = self.value.year | |
490 else: | |
491 value = self.value.isocalendar()[0] | |
3 | 492 year = self.format(value, num) |
493 if num == 2: | |
494 year = year[-2:] | |
495 return year | |
496 | |
17 | 497 def format_month(self, char, num): |
3 | 498 if num <= 2: |
499 return ('%%0%dd' % num) % self.value.month | |
500 width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[num] | |
17 | 501 context = {3: 'format', 4: 'format', 5: 'stand-alone'}[num] |
3 | 502 return get_month_names(width, context, self.locale)[self.value.month] |
503 | |
217
15ac328954f5
Dummy/stub implementation for week-in-year and week-in-month date format fields. Also, treat extended year the same as the regular year field, not even ICU seems to handle it specially.
cmlenz
parents:
131
diff
changeset
|
504 def format_week(self, char, num): |
15ac328954f5
Dummy/stub implementation for week-in-year and week-in-month date format fields. Also, treat extended year the same as the regular year field, not even ICU seems to handle it specially.
cmlenz
parents:
131
diff
changeset
|
505 # FIXME: this should really be based on the first_week_day and |
15ac328954f5
Dummy/stub implementation for week-in-year and week-in-month date format fields. Also, treat extended year the same as the regular year field, not even ICU seems to handle it specially.
cmlenz
parents:
131
diff
changeset
|
506 # min_week_days locale data |
15ac328954f5
Dummy/stub implementation for week-in-year and week-in-month date format fields. Also, treat extended year the same as the regular year field, not even ICU seems to handle it specially.
cmlenz
parents:
131
diff
changeset
|
507 if char.islower(): |
15ac328954f5
Dummy/stub implementation for week-in-year and week-in-month date format fields. Also, treat extended year the same as the regular year field, not even ICU seems to handle it specially.
cmlenz
parents:
131
diff
changeset
|
508 return self.value.strftime('%W') |
15ac328954f5
Dummy/stub implementation for week-in-year and week-in-month date format fields. Also, treat extended year the same as the regular year field, not even ICU seems to handle it specially.
cmlenz
parents:
131
diff
changeset
|
509 else: |
15ac328954f5
Dummy/stub implementation for week-in-year and week-in-month date format fields. Also, treat extended year the same as the regular year field, not even ICU seems to handle it specially.
cmlenz
parents:
131
diff
changeset
|
510 return '%d' % ((self.value.day + 6 - self.value.weekday()) / 7 + 1) |
15ac328954f5
Dummy/stub implementation for week-in-year and week-in-month date format fields. Also, treat extended year the same as the regular year field, not even ICU seems to handle it specially.
cmlenz
parents:
131
diff
changeset
|
511 |
17 | 512 def format_weekday(self, char, num): |
513 if num < 3: | |
514 if char.islower(): | |
515 value = 7 - self.locale.first_week_day + self.value.weekday() | |
516 return self.format(value % 7 + 1, num) | |
517 num = 3 | |
518 weekday = self.value.weekday() | |
519 width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[num] | |
520 context = {3: 'format', 4: 'format', 5: 'stand-alone'}[num] | |
3 | 521 return get_day_names(width, context, self.locale)[weekday] |
522 | |
223
49b089453f81
Implement day-of-year date format field. Closes #49.
cmlenz
parents:
219
diff
changeset
|
523 def format_day_of_year(self, num): |
49b089453f81
Implement day-of-year date format field. Closes #49.
cmlenz
parents:
219
diff
changeset
|
524 delta = self.value - date(self.value.year, 1, 1) |
49b089453f81
Implement day-of-year date format field. Closes #49.
cmlenz
parents:
219
diff
changeset
|
525 return self.format(delta.days + 1, num) |
49b089453f81
Implement day-of-year date format field. Closes #49.
cmlenz
parents:
219
diff
changeset
|
526 |
17 | 527 def format_period(self, char): |
3 | 528 period = {0: 'am', 1: 'pm'}[int(self.value.hour > 12)] |
529 return get_period_names(locale=self.locale)[period] | |
530 | |
219 | 531 def format_frac_seconds(self, num): |
218
0a30f3974997
Support for fractional seconds field in date formatting. Closes #47.
cmlenz
parents:
217
diff
changeset
|
532 value = str(self.value.microsecond) |
0a30f3974997
Support for fractional seconds field in date formatting. Closes #47.
cmlenz
parents:
217
diff
changeset
|
533 return self.format(round(float('.%s' % value), num) * 10**num, num) |
0a30f3974997
Support for fractional seconds field in date formatting. Closes #47.
cmlenz
parents:
217
diff
changeset
|
534 |
219 | 535 def format_milliseconds_in_day(self, num): |
536 msecs = self.value.microsecond // 1000 + self.value.second * 1000 + \ | |
537 self.value.minute * 60000 + self.value.hour * 3600000 | |
538 return self.format(msecs, num) | |
539 | |
31 | 540 def format_timezone(self, char, num): |
131 | 541 if char in ('z', 'v'): |
36 | 542 if hasattr(self.value.tzinfo, 'zone'): |
543 zone = self.value.tzinfo.zone | |
31 | 544 else: |
36 | 545 zone = self.value.tzinfo.tzname(self.value) |
131 | 546 |
36 | 547 # Get the canonical time-zone code |
548 zone = self.locale.zone_aliases.get(zone, zone) | |
549 | |
550 # Try explicitly translated zone names first | |
551 display = self.locale.time_zones.get(zone) | |
552 if display: | |
553 if 'long' in display: | |
554 width = {3: 'short', 4: 'long'}[max(3, num)] | |
131 | 555 if char == 'v': |
556 dst = 'generic' | |
557 else: | |
558 dst = self.value.dst() and 'daylight' or 'standard' | |
36 | 559 return display[width][dst] |
560 elif 'city' in display: | |
561 return display['city'] | |
562 | |
563 else: | |
564 return zone.split('/', 1)[1] | |
31 | 565 |
566 elif char == 'Z': | |
567 offset = self.value.utcoffset() | |
36 | 568 seconds = offset.days * 24 * 60 * 60 + offset.seconds |
569 hours, seconds = divmod(seconds, 3600) | |
570 pattern = {3: '%+03d%02d', 4: 'GMT %+03d:%02d'}[max(3, num)] | |
571 return pattern % (hours, seconds // 60) | |
31 | 572 |
3 | 573 def format(self, value, length): |
574 return ('%%0%dd' % length) % value | |
575 | |
576 | |
577 PATTERN_CHARS = { | |
17 | 578 'G': [1, 2, 3, 4, 5], # era |
579 'y': None, 'Y': None, 'u': None, # year | |
580 'Q': [1, 2, 3, 4], 'q': [1, 2, 3, 4], # quarter | |
581 'M': [1, 2, 3, 4, 5], 'L': [1, 2, 3, 4, 5], # month | |
582 'w': [1, 2], 'W': [1], # week | |
583 'd': [1, 2], 'D': [1, 2, 3], 'F': [1], 'g': None, # day | |
584 'E': [1, 2, 3, 4, 5], 'e': [1, 2, 3, 4, 5], 'c': [1, 3, 4, 5], # week day | |
585 'a': [1], # period | |
586 'h': [1, 2], 'H': [1, 2], 'K': [1, 2], 'k': [1, 2], # hour | |
587 'm': [1, 2], # minute | |
588 's': [1, 2], 'S': None, 'A': None, # second | |
589 'z': [1, 2, 3, 4], 'Z': [1, 2, 3, 4], 'v': [1, 4] # zone | |
3 | 590 } |
591 | |
592 def parse_pattern(pattern): | |
593 """Parse date, time, and datetime format patterns. | |
594 | |
595 >>> parse_pattern("MMMMd").format | |
596 u'%(MMMM)s%(d)s' | |
597 >>> parse_pattern("MMM d, yyyy").format | |
598 u'%(MMM)s %(d)s, %(yyyy)s' | |
18 | 599 |
600 Pattern can contain literal strings in single quotes: | |
601 | |
3 | 602 >>> parse_pattern("H:mm' Uhr 'z").format |
603 u'%(H)s:%(mm)s Uhr %(z)s' | |
604 | |
18 | 605 An actual single quote can be used by using two adjacent single quote |
606 characters: | |
607 | |
608 >>> parse_pattern("hh' o''clock'").format | |
609 u"%(hh)s o'clock" | |
610 | |
3 | 611 :param pattern: the formatting pattern to parse |
612 """ | |
14
29ef15a6fd75
* Removed pkg_resources/setuptools requirement from various places.
cmlenz
parents:
10
diff
changeset
|
613 if type(pattern) is DateTimePattern: |
3 | 614 return pattern |
615 | |
616 result = [] | |
617 quotebuf = None | |
618 charbuf = [] | |
619 fieldchar = [''] | |
620 fieldnum = [0] | |
621 | |
622 def append_chars(): | |
623 result.append(''.join(charbuf).replace('%', '%%')) | |
624 del charbuf[:] | |
625 | |
626 def append_field(): | |
627 limit = PATTERN_CHARS[fieldchar[0]] | |
17 | 628 if limit and fieldnum[0] not in limit: |
3 | 629 raise ValueError('Invalid length for field: %r' |
630 % (fieldchar[0] * fieldnum[0])) | |
631 result.append('%%(%s)s' % (fieldchar[0] * fieldnum[0])) | |
632 fieldchar[0] = '' | |
633 fieldnum[0] = 0 | |
634 | |
18 | 635 for idx, char in enumerate(pattern.replace("''", '\0')): |
3 | 636 if quotebuf is None: |
637 if char == "'": # quote started | |
638 if fieldchar[0]: | |
639 append_field() | |
640 elif charbuf: | |
641 append_chars() | |
642 quotebuf = [] | |
643 elif char in PATTERN_CHARS: | |
644 if charbuf: | |
645 append_chars() | |
646 if char == fieldchar[0]: | |
647 fieldnum[0] += 1 | |
648 else: | |
649 if fieldchar[0]: | |
650 append_field() | |
651 fieldchar[0] = char | |
652 fieldnum[0] = 1 | |
653 else: | |
654 if fieldchar[0]: | |
655 append_field() | |
656 charbuf.append(char) | |
657 | |
658 elif quotebuf is not None: | |
18 | 659 if char == "'": # end of quote |
3 | 660 charbuf.extend(quotebuf) |
661 quotebuf = None | |
662 else: # inside quote | |
663 quotebuf.append(char) | |
664 | |
665 if fieldchar[0]: | |
666 append_field() | |
667 elif charbuf: | |
668 append_chars() | |
669 | |
18 | 670 return DateTimePattern(pattern, u''.join(result).replace('\0', "'")) |