1
|
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
|
|
24 from datetime import date, datetime, time
|
|
25
|
|
26 from babel.core import Locale
|
|
27 from babel.util import default_locale
|
|
28
|
|
29 __all__ = ['format_date', 'format_datetime', 'format_time', 'parse_date',
|
|
30 'parse_datetime', 'parse_time']
|
|
31 __docformat__ = 'restructuredtext en'
|
|
32
|
|
33 LC_TIME = default_locale('LC_TIME')
|
|
34
|
|
35 def get_period_names(locale=LC_TIME):
|
|
36 """Return the names for day periods (AM/PM) used by the locale.
|
|
37
|
|
38 >>> get_period_names(locale='en_US')['am']
|
|
39 u'AM'
|
|
40
|
|
41 :param locale: the `Locale` object, or a locale string
|
|
42 :return: the dictionary of period names
|
|
43 :rtype: `dict`
|
|
44 """
|
|
45 return Locale.parse(locale).periods
|
|
46
|
|
47 def get_day_names(width='wide', context='format', locale=LC_TIME):
|
|
48 """Return the day names used by the locale for the specified format.
|
|
49
|
|
50 >>> get_day_names('wide', locale='en_US')[1]
|
|
51 u'Monday'
|
|
52 >>> get_day_names('abbreviated', locale='es')[1]
|
|
53 u'lun'
|
|
54 >>> get_day_names('narrow', context='stand-alone', locale='de_DE')[1]
|
|
55 u'M'
|
|
56
|
|
57 :param width: the width to use, one of "wide", "abbreviated", or "narrow"
|
|
58 :param context: the context, either "format" or "stand-alone"
|
|
59 :param locale: the `Locale` object, or a locale string
|
|
60 :return: the dictionary of day names
|
|
61 :rtype: `dict`
|
|
62 """
|
|
63 return Locale.parse(locale).days[context][width]
|
|
64
|
|
65 def get_month_names(width='wide', context='format', locale=LC_TIME):
|
|
66 """Return the month names used by the locale for the specified format.
|
|
67
|
|
68 >>> get_month_names('wide', locale='en_US')[1]
|
|
69 u'January'
|
|
70 >>> get_month_names('abbreviated', locale='es')[1]
|
|
71 u'ene'
|
|
72 >>> get_month_names('narrow', context='stand-alone', locale='de_DE')[1]
|
|
73 u'J'
|
|
74
|
|
75 :param width: the width to use, one of "wide", "abbreviated", or "narrow"
|
|
76 :param context: the context, either "format" or "stand-alone"
|
|
77 :param locale: the `Locale` object, or a locale string
|
|
78 :return: the dictionary of month names
|
|
79 :rtype: `dict`
|
|
80 """
|
|
81 return Locale.parse(locale).months[context][width]
|
|
82
|
|
83 def get_quarter_names(width='wide', context='format', locale=LC_TIME):
|
|
84 """Return the quarter names used by the locale for the specified format.
|
|
85
|
|
86 >>> get_quarter_names('wide', locale='en_US')[1]
|
|
87 u'1st quarter'
|
|
88 >>> get_quarter_names('abbreviated', locale='de_DE')[1]
|
|
89 u'Q1'
|
|
90
|
|
91 :param width: the width to use, one of "wide", "abbreviated", or "narrow"
|
|
92 :param context: the context, either "format" or "stand-alone"
|
|
93 :param locale: the `Locale` object, or a locale string
|
|
94 :return: the dictionary of quarter names
|
|
95 :rtype: `dict`
|
|
96 """
|
|
97 return Locale.parse(locale).quarters[context][width]
|
|
98
|
|
99 def get_era_names(width='wide', locale=LC_TIME):
|
|
100 """Return the era names used by the locale for the specified format.
|
|
101
|
|
102 >>> get_era_names('wide', locale='en_US')[1]
|
|
103 u'Anno Domini'
|
|
104 >>> get_era_names('abbreviated', locale='de_DE')[1]
|
|
105 u'n. Chr.'
|
|
106
|
|
107 :param width: the width to use, either "wide" or "abbreviated"
|
|
108 :param locale: the `Locale` object, or a locale string
|
|
109 :return: the dictionary of era names
|
|
110 :rtype: `dict`
|
|
111 """
|
|
112 return Locale.parse(locale).eras[width]
|
|
113
|
|
114 def get_date_format(format='medium', locale=LC_TIME):
|
|
115 """Return the date formatting patterns used by the locale for the specified
|
|
116 format.
|
|
117
|
|
118 >>> get_date_format(locale='en_US')
|
|
119 <DateTimeFormatPattern u'MMM d, yyyy'>
|
|
120 >>> get_date_format('full', locale='de_DE')
|
|
121 <DateTimeFormatPattern u'EEEE, d. MMMM yyyy'>
|
|
122
|
|
123 :param format: the format to use, one of "full", "long", "medium", or
|
|
124 "short"
|
|
125 :param locale: the `Locale` object, or a locale string
|
|
126 :return: the date format pattern
|
|
127 :rtype: `dict`
|
|
128 """
|
|
129 return Locale.parse(locale).date_formats[format]
|
|
130
|
|
131 def get_time_format(format='medium', locale=LC_TIME):
|
|
132 """Return the time formatting patterns used by the locale for the specified
|
|
133 format.
|
|
134
|
|
135 >>> get_time_format(locale='en_US')
|
|
136 <DateTimeFormatPattern u'h:mm:ss a'>
|
|
137 >>> get_time_format('full', locale='de_DE')
|
|
138 <DateTimeFormatPattern u"H:mm' Uhr 'z">
|
|
139
|
|
140 :param format: the format to use, one of "full", "long", "medium", or
|
|
141 "short"
|
|
142 :param locale: the `Locale` object, or a locale string
|
|
143 :return: the time format pattern
|
|
144 :rtype: `dict`
|
|
145 """
|
|
146 return Locale.parse(locale).time_formats[format]
|
|
147
|
|
148 def format_date(date, format='medium', locale=LC_TIME):
|
|
149 """Returns a date formatted according to the given pattern.
|
|
150
|
|
151 >>> d = date(2007, 04, 01)
|
|
152 >>> format_date(d, locale='en_US')
|
|
153 u'Apr 1, 2007'
|
|
154 >>> format_date(d, format='full', locale='de_DE')
|
|
155 u'Sonntag, 1. April 2007'
|
|
156
|
|
157 :param date: the ``date`` object
|
|
158 :param format: one of "full", "long", "medium", or "short"
|
|
159 :param locale: a `Locale` object or a locale string
|
|
160 :rtype: `unicode`
|
|
161 """
|
|
162 locale = Locale.parse(locale)
|
|
163 if format in ('full', 'long', 'medium', 'short'):
|
|
164 format = get_date_format(format, locale=locale)
|
|
165 pattern = parse_pattern(format)
|
|
166 return parse_pattern(format).apply(date, locale)
|
|
167
|
|
168 def format_datetime(datetime, format='medium', locale=LC_TIME):
|
|
169 """Returns a date formatted according to the given pattern.
|
|
170
|
|
171 :param datetime: the ``date`` object
|
|
172 :param format: one of "full", "long", "medium", or "short"
|
|
173 :param locale: a `Locale` object or a locale string
|
|
174 :rtype: `unicode`
|
|
175 """
|
|
176 raise NotImplementedError
|
|
177
|
|
178 def format_time(time, format='medium', locale=LC_TIME):
|
|
179 """Returns a time formatted according to the given pattern.
|
|
180
|
|
181 >>> t = time(15, 30)
|
|
182 >>> format_time(t, locale='en_US')
|
|
183 u'3:30:00 PM'
|
|
184 >>> format_time(t, format='short', locale='de_DE')
|
|
185 u'15:30'
|
|
186
|
|
187 :param time: the ``time`` object
|
|
188 :param format: one of "full", "long", "medium", or "short"
|
|
189 :param locale: a `Locale` object or a locale string
|
|
190 :rtype: `unicode`
|
|
191 """
|
|
192 locale = Locale.parse(locale)
|
|
193 if format in ('full', 'long', 'medium', 'short'):
|
|
194 format = get_time_format(format, locale=locale)
|
|
195 return parse_pattern(format).apply(time, locale)
|
|
196
|
|
197 def parse_date(string, locale=LC_TIME):
|
|
198 raise NotImplementedError
|
|
199
|
|
200 def parse_datetime(string, locale=LC_TIME):
|
|
201 raise NotImplementedError
|
|
202
|
|
203 def parse_time(string, locale=LC_TIME):
|
|
204 raise NotImplementedError
|
|
205
|
|
206
|
|
207 class DateTimeFormatPattern(object):
|
|
208
|
|
209 def __init__(self, pattern, format):
|
|
210 self.pattern = pattern
|
|
211 self.format = format
|
|
212
|
|
213 def __repr__(self):
|
|
214 return '<%s %r>' % (type(self).__name__, self.pattern)
|
|
215
|
|
216 def __unicode__(self):
|
|
217 return self.pattern
|
|
218
|
|
219 def __mod__(self, other):
|
|
220 assert type(other) is DateTimeFormat
|
|
221 return self.format % other
|
|
222
|
|
223 def apply(self, datetime, locale):
|
|
224 return self % DateTimeFormat(datetime, locale)
|
|
225
|
|
226
|
|
227 class DateTimeFormat(object):
|
|
228
|
|
229 def __init__(self, value, locale):
|
|
230 assert isinstance(value, (date, datetime, time))
|
|
231 self.value = value
|
|
232 self.locale = Locale.parse(locale)
|
|
233
|
|
234 def __getitem__(self, name):
|
|
235 # TODO: a number of fields missing here
|
|
236 if name[0] == 'G':
|
|
237 return self.format_era(len(name))
|
|
238 elif name[0] == 'y':
|
|
239 return self.format_year(self.value.year, len(name))
|
|
240 elif name[0] == 'Y':
|
|
241 return self.format_year(self.value.isocalendar()[0], len(name))
|
|
242 elif name[0] == 'Q':
|
|
243 return self.format_quarter(len(name))
|
|
244 elif name[0] == 'q':
|
|
245 return self.format_quarter(len(name), context='stand-alone')
|
|
246 elif name[0] == 'M':
|
|
247 return self.format_month(len(name))
|
|
248 elif name[0] == 'L':
|
|
249 return self.format_month(len(name), context='stand-alone')
|
|
250 elif name[0] == 'd':
|
|
251 return self.format(self.value.day, len(name))
|
|
252 elif name[0] == 'E':
|
|
253 return self.format_weekday(len(name))
|
|
254 elif name[0] == 'c':
|
|
255 return self.format_weekday(len(name), context='stand-alone')
|
|
256 elif name[0] == 'a':
|
|
257 return self.format_period()
|
|
258 elif name[0] == 'h':
|
|
259 return self.format(self.value.hour % 12, len(name))
|
|
260 elif name[0] == 'H':
|
|
261 return self.format(self.value.hour, len(name))
|
|
262 elif name[0] == 'm':
|
|
263 return self.format(self.value.minute, len(name))
|
|
264 elif name[0] == 's':
|
|
265 return self.format(self.value.second, len(name))
|
|
266 else:
|
|
267 raise KeyError('Unsupported date/time field %r' % name[0])
|
|
268
|
|
269 def format_era(self, num):
|
|
270 width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[max(3, num)]
|
|
271 era = int(self.value.year >= 0)
|
|
272 return get_era_names(width, self.locale)[era]
|
|
273
|
|
274 def format_year(self, value, num):
|
|
275 year = self.format(value, num)
|
|
276 if num == 2:
|
|
277 year = year[-2:]
|
|
278 return year
|
|
279
|
|
280 def format_month(self, num, context='format'):
|
|
281 if num <= 2:
|
|
282 return ('%%0%dd' % num) % self.value.month
|
|
283 width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[num]
|
|
284 return get_month_names(width, context, self.locale)[self.value.month]
|
|
285
|
|
286 def format_weekday(self, num, context='format'):
|
|
287 width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[max(3, num)]
|
|
288 weekday = self.value.weekday() + 1
|
|
289 return get_day_names(width, context, self.locale)[weekday]
|
|
290
|
|
291 def format_period(self):
|
|
292 period = {0: 'am', 1: 'pm'}[int(self.value.hour > 12)]
|
|
293 return get_period_names(locale=self.locale)[period]
|
|
294
|
|
295 def format(self, value, length):
|
|
296 return ('%%0%dd' % length) % value
|
|
297
|
|
298
|
|
299 PATTERN_CHARS = {
|
|
300 'G': 5, # era
|
|
301 'y': None, 'Y': None, 'u': None, # year
|
|
302 'Q': 4, 'q': 4, # quarter
|
|
303 'M': 5, 'L': 5, # month
|
|
304 'w': 2, 'W': 1, # week
|
|
305 'd': 2, 'D': 3, 'F': 1, 'g': None, # day
|
|
306 'E': 5, 'e': 5, 'c': 5, # week day
|
|
307 'a': 1, # period
|
|
308 'h': 2, 'H': 2, 'K': 2, 'k': 2, # hour
|
|
309 'm': 2, # minute
|
|
310 's': 2, 'S': None, 'A': None, # second
|
|
311 'z': 4, 'Z': 4, 'v': 4 # zone
|
|
312 }
|
|
313
|
|
314 def parse_pattern(pattern):
|
|
315 """Parse date, time, and datetime format patterns.
|
|
316
|
|
317 >>> parse_pattern("MMMMd").format
|
|
318 u'%(MMMM)s%(d)s'
|
|
319 >>> parse_pattern("MMM d, yyyy").format
|
|
320 u'%(MMM)s %(d)s, %(yyyy)s'
|
|
321 >>> parse_pattern("H:mm' Uhr 'z").format
|
|
322 u'%(H)s:%(mm)s Uhr %(z)s'
|
|
323
|
|
324 :param pattern: the formatting pattern to parse
|
|
325 """
|
|
326 if type(pattern) is DateTimeFormatPattern:
|
|
327 return pattern
|
|
328
|
|
329 result = []
|
|
330 quotebuf = None
|
|
331 charbuf = []
|
|
332 fieldchar = ['']
|
|
333 fieldnum = [0]
|
|
334
|
|
335 def append_chars():
|
|
336 result.append(''.join(charbuf).replace('%', '%%'))
|
|
337 del charbuf[:]
|
|
338
|
|
339 def append_field():
|
|
340 limit = PATTERN_CHARS[fieldchar[0]]
|
|
341 if limit is not None and fieldnum[0] > limit:
|
|
342 raise ValueError('Invalid length for field: %r'
|
|
343 % (fieldchar[0] * fieldnum[0]))
|
|
344 result.append('%%(%s)s' % (fieldchar[0] * fieldnum[0]))
|
|
345 fieldchar[0] = ''
|
|
346 fieldnum[0] = 0
|
|
347
|
|
348 for idx, char in enumerate(pattern):
|
|
349 if quotebuf is None:
|
|
350 if char == "'": # quote started
|
|
351 if fieldchar[0]:
|
|
352 append_field()
|
|
353 elif charbuf:
|
|
354 append_chars()
|
|
355 quotebuf = []
|
|
356 elif char in PATTERN_CHARS:
|
|
357 if charbuf:
|
|
358 append_chars()
|
|
359 if char == fieldchar[0]:
|
|
360 fieldnum[0] += 1
|
|
361 else:
|
|
362 if fieldchar[0]:
|
|
363 append_field()
|
|
364 fieldchar[0] = char
|
|
365 fieldnum[0] = 1
|
|
366 else:
|
|
367 if fieldchar[0]:
|
|
368 append_field()
|
|
369 charbuf.append(char)
|
|
370
|
|
371 elif quotebuf is not None:
|
|
372 if char == "'": # quote ended
|
|
373 charbuf.extend(quotebuf)
|
|
374 quotebuf = None
|
|
375 else: # inside quote
|
|
376 quotebuf.append(char)
|
|
377
|
|
378 if fieldchar[0]:
|
|
379 append_field()
|
|
380 elif charbuf:
|
|
381 append_chars()
|
|
382
|
|
383 return DateTimeFormatPattern(pattern, u''.join(result))
|