Changeset 10571
- Timestamp:
- Feb 20, 2011, 3:08:40 PM (13 years ago)
- Location:
- trunk/trac
- Files:
-
- 18 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/trac/admin/templates/admin_basics.html
r10178 r10571 50 50 </label> 51 51 </div> 52 <div class="field"> 53 <label>Default date format:<br /> 54 <select name="default_date_format"> 55 <option value="">Browser's language</option> 56 <option value="iso8601" 57 selected="${default_date_format == 'iso8601' or None}">ISO 8601 format</option> 58 </select> 59 </label> 60 </div> 52 61 </fieldset> 53 62 <div class="buttons"> -
trunk/trac/admin/web_ui.py
r10570 r10571 234 234 self.config.set('trac', 'default_language', default_language) 235 235 236 default_date_format = req.args.get('default_date_format') 237 if default_date_format != 'iso8601': 238 default_date_format = '' 239 self.config.set('trac', 'default_date_format', default_date_format) 240 236 241 _save_config(self.config, req, self.log) 237 242 req.redirect(req.href.admin(cat, page)) … … 239 244 default_timezone = self.config.get('trac', 'default_timezone') 240 245 default_language = self.config.get('trac', 'default_language') 246 default_date_format = self.config.get('trac', 'default_date_format') 241 247 242 248 data = { … … 245 251 'default_language': default_language.replace('-', '_'), 246 252 'languages': languages, 253 'default_date_format': default_date_format, 247 254 } 248 255 Chrome(self.env).add_textarea_grips(req) -
trunk/trac/prefs/templates/prefs_datetime.html
r10178 r10571 54 54 </div> 55 55 56 <div class="field"> 57 <label>Date format: 58 <select name="lc_time"> 59 <option value="">Default date format</option> 60 <option value="locale" py:if="locales and languages">Your language setting</option> 61 <option value="iso8601">ISO 8601 format</option> 62 </select> 63 </label> 64 65 <p class="hint">Configuring your date format will result in formatting 66 and parsing datetime displayed on this site to use your date format 67 instead of that of the server.</p> 68 </div> 69 56 70 </body> 57 71 </html> -
trunk/trac/prefs/web_ui.py
r10264 r10571 41 41 IRequestHandler, ITemplateProvider) 42 42 43 _form_fields = ['newsid', 'name', 'email', 'tz', 'language', 'accesskeys'] 43 _form_fields = ['newsid', 'name', 'email', 'tz', 'lc_time', 'language', 44 'accesskeys'] 44 45 45 46 # INavigationContributor methods -
trunk/trac/search/web_ui.py
r10401 r10571 25 25 from trac.perm import IPermissionRequestor 26 26 from trac.search.api import ISearchSource 27 from trac.util.datefmt import format_datetime 27 from trac.util.datefmt import format_datetime, user_time 28 28 from trac.util.presentation import Paginator 29 29 from trac.util.translation import _ … … 213 213 for idx, result in enumerate(results): 214 214 results[idx] = {'href': result[0], 'title': result[1], 215 'date': format_datetime(result[2]),215 'date': user_time(req, format_datetime, result[2]), 216 216 'author': result[3], 'excerpt': result[4]} 217 217 -
trunk/trac/ticket/admin.py
r10188 r10571 22 22 from trac.ticket import model 23 23 from trac.util import getuser 24 from trac.util.datefmt import utc, parse_date, get_datetime_format_hint, \25 format_date, format_datetime24 from trac.util.datefmt import utc, parse_date, format_date, format_datetime, \ 25 get_datetime_format_hint, user_time 26 26 from trac.util.text import print_table, printout, exception_to_unicode 27 27 from trac.util.translation import _, N_, gettext … … 253 253 due = req.args.get('duedate', '') 254 254 if due: 255 mil.due = parse_date(due, req.tz, 'datetime') 255 mil.due = user_time(req, parse_date, due, 256 hint='datetime') 256 257 if req.args.get('completed', False): 257 258 completed = req.args.get('completeddate', '') 258 mil.completed = parse_date(completed, req.tz,259 259 mil.completed = user_time(req, parse_date, completed, 260 hint='datetime') 260 261 if mil.completed > datetime.now(utc): 261 262 raise TracError(_('Completion date may not be in ' … … 285 286 mil.name = name 286 287 if req.args.get('duedate'): 287 mil.due = parse_date(req.args.get('duedate'), 288 req.tz, 'datetime') 288 mil.due = user_time(req, parse_date, 289 req.args.get('duedate'), 290 hint='datetime') 289 291 mil.insert() 290 292 add_notice(req, _('The milestone "%(name)s" has been ' … … 334 336 335 337 data.update({ 336 'datetime_hint': get_datetime_format_hint( )338 'datetime_hint': get_datetime_format_hint(req.lc_time), 337 339 }) 338 340 return 'admin_milestones.html', data … … 429 431 ver.name = req.args.get('name') 430 432 if req.args.get('time'): 431 ver.time = parse_date(req.args.get('time'), req.tz, 432 'datetime') 433 ver.time = user_time(req, parse_date, 434 req.args.get('time'), 435 hint='datetime') 433 436 else: 434 437 ver.time = None # unset … … 455 458 ver.name = name 456 459 if req.args.get('time'): 457 ver.time = parse_date(req.args.get('time'), 458 req.tz, 'datetime') 460 ver.time = user_time(req, parse_date, 461 req.args.get('time'), 462 hint='datetime') 459 463 ver.insert() 460 464 add_notice(req, _('The version "%(name)s" has been ' … … 496 500 497 501 data.update({ 498 'datetime_hint': get_datetime_format_hint( )502 'datetime_hint': get_datetime_format_hint(req.lc_time), 499 503 }) 500 504 return 'admin_versions.html', data -
trunk/trac/ticket/query.py
r10554 r10571 36 36 from trac.util import Ranges, as_bool 37 37 from trac.util.datefmt import format_datetime, from_utimestamp, parse_date, \ 38 to_timestamp, to_utimestamp, utc 38 to_timestamp, to_utimestamp, utc, user_time 39 39 from trac.util.presentation import Paginator 40 40 from trac.util.text import empty, shorten_line … … 266 266 267 267 def count(self, req=None, db=None, cached_ids=None, authname=None, 268 tzinfo=None ):268 tzinfo=None, locale=None): 269 269 """Get the number of matching tickets for the present query. 270 270 … … 272 272 in version 0.14 273 273 """ 274 sql, args = self.get_sql(req, cached_ids, authname, tzinfo )274 sql, args = self.get_sql(req, cached_ids, authname, tzinfo, locale) 275 275 return self._count(sql, args) 276 276 … … 283 283 284 284 def execute(self, req=None, db=None, cached_ids=None, authname=None, 285 tzinfo=None, href=None ):285 tzinfo=None, href=None, locale=None): 286 286 """Retrieve the list of matching tickets. 287 287 … … 295 295 296 296 self.num_items = 0 297 sql, args = self.get_sql(req, cached_ids, authname, tzinfo )297 sql, args = self.get_sql(req, cached_ids, authname, tzinfo, locale) 298 298 self.num_items = self._count(sql, args) 299 299 … … 419 419 return 'query:?' + query_string.replace('&', '\n&\n') 420 420 421 def get_sql(self, req=None, cached_ids=None, authname=None, tzinfo=None): 421 def get_sql(self, req=None, cached_ids=None, authname=None, tzinfo=None, 422 locale=None): 422 423 """Return a (sql, params) tuple for the query.""" 423 424 if req is not None: 424 425 authname = req.authname 425 426 tzinfo = req.tz 427 locale = req.locale 426 428 self.get_columns() 427 429 db = self.env.get_read_db() … … 473 475 if date: 474 476 try: 475 return to_utimestamp( parse_date(date, tzinfo))477 return to_utimestamp(user_time(req, parse_date, date)) 476 478 except TracError, e: 477 479 errors.append(unicode(e)) … … 1127 1129 context.child(ticket), value) 1128 1130 elif col in query.time_fields: 1129 value = format_datetime(value, tzinfo=req.tz) 1131 value = format_datetime(value, '%Y-%m-%d %H:%M:%S', 1132 tzinfo=req.tz) 1130 1133 values.append(unicode(value).encode('utf-8')) 1131 1134 writer.writerow(values) -
trunk/trac/ticket/roadmap.py
r10554 r10571 34 34 from trac.util.datefmt import parse_date, utc, to_utimestamp, \ 35 35 get_datetime_format_hint, format_date, \ 36 format_datetime, from_utimestamp 36 format_datetime, from_utimestamp, user_time 37 37 from trac.util.text import CRLF 38 38 from trac.util.translation import _, tag_ … … 677 677 if 'due' in req.args: 678 678 due = req.args.get('duedate', '') 679 milestone.due = due and parse_date(due, req.tz, 'datetime') or None 679 milestone.due = user_time(req, parse_date, due, hint='datetime') \ 680 if due else None 680 681 else: 681 682 milestone.due = None … … 711 712 # -- check completed date 712 713 if 'completed' in req.args: 713 completed = completed and parse_date(completed, req.tz,714 'datetime') orNone714 completed = user_time(req, parse_date, completed, 715 hint='datetime') if completed else None 715 716 if completed and completed > datetime.now(utc): 716 717 warn(_('Completion date may not be in the future')) … … 761 762 data = { 762 763 'milestone': milestone, 763 'datetime_hint': get_datetime_format_hint( ),764 'datetime_hint': get_datetime_format_hint(req.lc_time), 764 765 'default_due': default_due, 765 766 'milestone_groups': [], -
trunk/trac/ticket/tests/functional.py
r10227 r10571 5 5 from datetime import datetime, timedelta 6 6 7 try: 8 import babel 9 locale_en = babel.Locale('en_US') 10 except ImportError: 11 babel = None 12 locale_en = None 13 7 14 from trac.tests.functional import * 8 from trac.util.datefmt import utc, localtz, format_date 15 from trac.util.datefmt import utc, localtz, format_date, format_datetime 9 16 10 17 … … 395 402 name = "DueMilestone" 396 403 duedate = datetime.now(tz=utc) 397 duedate_string = format_date (duedate, tzinfo=utc)404 duedate_string = format_datetime(duedate, tzinfo=utc, locale=locale_en) 398 405 self._tester.create_milestone(name, due=duedate_string) 399 406 tc.find(duedate_string) … … 414 421 tc.url(milestone_url + '/' + name) 415 422 duedate = datetime.now(tz=utc) 416 duedate_string = format_date (duedate, tzinfo=utc)423 duedate_string = format_datetime(duedate, tzinfo=utc, locale=locale_en) 417 424 tc.formvalue('modifymilestone', 'due', duedate_string) 418 425 tc.submit('save') -
trunk/trac/ticket/tests/query.py
r10328 r10571 5 5 from trac.web.href import Href 6 6 from trac.wiki.formatter import LinkFormatter 7 8 try: 9 from babel import Locale 10 except ImportError: 11 Locale = None 7 12 8 13 import unittest … … 33 38 def setUp(self): 34 39 self.env = EnvironmentStub(default_data=True) 35 self.req = Mock(href=self.env.href, authname='anonymous', tz=utc) 40 locale = Locale.parse('en_US') if Locale else None 41 self.req = Mock(href=self.env.href, authname='anonymous', tz=utc, 42 locale=locale, lc_time=locale) 36 43 37 44 def tearDown(self): -
trunk/trac/ticket/web_ui.py
r10554 r10571 1044 1044 value = Chrome(self.env).format_emails(context, value, ' ') 1045 1045 elif name in ticket.time_fields: 1046 value = format_datetime(value, tzinfo=req.tz) 1046 value = format_datetime(value, '%Y-%m-%d %H:%M:%S', 1047 tzinfo=req.tz) 1047 1048 cols.append(value.encode('utf-8')) 1048 1049 writer.writerow(cols) -
trunk/trac/timeline/web_ui.py
r10443 r10571 30 30 from trac.util import as_int 31 31 from trac.util.datefmt import format_date, format_datetime, parse_date, \ 32 to_utimestamp, utc, pretty_timedelta 32 to_utimestamp, utc, pretty_timedelta, user_time 33 33 from trac.util.text import exception_to_unicode, to_unicode 34 34 from trac.util.translation import _, tag_ … … 109 109 reqfromdate = req.args['from'].strip() 110 110 if reqfromdate: 111 precisedate = parse_date(reqfromdate, req.tz)111 precisedate = user_time(req, parse_date, reqfromdate) 112 112 fromdate = precisedate 113 113 precision = req.args.get('precision', '') … … 140 140 data = {'fromdate': fromdate, 'daysback': daysback, 141 141 'authors': authors, 142 'today': format_date(today, tzinfo=req.tz),143 'yesterday': format_date(today - timedelta(days=1),144 tzinfo=req.tz),142 'today': user_time(req, format_date, today), 143 'yesterday': user_time(req, format_date, 144 today - timedelta(days=1)), 145 145 'precisedate': precisedate, 'precision': precision, 146 146 'events': [], 'filters': [], -
trunk/trac/util/datefmt.py
r10424 r10571 18 18 # Matthew Good <trac@matt-good.net> 19 19 20 import locale21 20 import math 22 21 import re … … 24 23 import time 25 24 from datetime import tzinfo, timedelta, datetime, date 25 from locale import getlocale, getpreferredencoding, LC_TIME 26 27 try: 28 import babel 29 from babel.dates import format_datetime as babel_format_datetime, \ 30 format_date as babel_format_date, \ 31 format_time as babel_format_time, \ 32 get_datetime_format, get_date_format, \ 33 get_time_format, get_month_names, get_period_names 34 except ImportError: 35 babel = None 26 36 27 37 from trac.core import TracError 28 38 from trac.util.text import to_unicode 29 from trac.util.translation import _, ngettext 39 from trac.util.translation import _, ngettext, get_available_locales 30 40 31 41 # Date/time utilities … … 36 46 """Convert `t` into a `datetime` object, using the following rules: 37 47 38 - If `t` is already a `datetime` object, it is simply returned. 48 - If `t` is already a `datetime` object and `tzinfo` is None, it is simply 49 returned. 50 - If `t` is already a `datetime` object and `tzinfo` is not None, it will 51 adjust `t` to `tzinfo` timezone. 39 52 - If `t` is None, the current time will be used. 40 53 - If `t` is a number, it is interpreted as a timestamp. … … 47 60 return datetime.now(tzinfo or localtz) 48 61 elif isinstance(t, datetime): 62 if tzinfo is not None: 63 if t.tzinfo is None: 64 t = t.replace(tzinfo=tzinfo) 65 else: 66 t = t.astimezone(tzinfo) 67 if hasattr(tzinfo, 'normalize'): # pytz 68 t = tzinfo.normalize(t) 49 69 return t 50 70 elif isinstance(t, date): … … 119 139 return '' 120 140 121 122 def format_datetime(t=None, format='%x %X', tzinfo=None): 123 """Format the `datetime` object `t` into an `unicode` string 124 125 If `t` is None, the current time will be used. 126 127 The formatting will be done using the given `format`, which consist 128 of conventional `strftime` keys. In addition the format can be 'iso8601' 129 to specify the international date format (compliant with RFC 3339). 130 131 `tzinfo` will default to the local timezone if left to `None`. 132 """ 141 142 _BABEL_FORMATS = ('short', 'medium', 'long', 'full') 143 _ISO8601_FORMATS = { 144 '%x %X': 'iso8601', '%x': 'iso8601date', '%X': 'iso8601time', 145 'short': 'iso8601', 'medium': 'iso8601', 'long': 'iso8601', 146 'full': 'iso8601', None: 'iso8601'} 147 148 def _format_datetime_without_babel(t, format, tzinfo): 133 149 tz = tzinfo or localtz 134 150 t = to_datetime(t, tzinfo).astimezone(tz) … … 148 164 if not text.endswith('Z'): 149 165 text = text[:-2] + ":" + text[-2:] 150 encoding = locale.getlocale(locale.LC_TIME)[1]\151 or locale.getpreferredencoding() orsys.getdefaultencoding()166 encoding = getlocale(LC_TIME)[1] or getpreferredencoding() \ 167 or sys.getdefaultencoding() 152 168 return unicode(text, encoding, 'replace') 153 169 154 def format_date(t=None, format='%x', tzinfo=None): 170 def format_datetime(t=None, format='%x %X', tzinfo=None, locale=None): 171 """Format the `datetime` object `t` into an `unicode` string 172 173 If `t` is None, the current time will be used. 174 175 The formatting will be done using the given `format`, which consist 176 of conventional `strftime` keys. In addition the format can be 'iso8601' 177 to specify the international date format (compliant with RFC 3339). 178 179 `tzinfo` will default to the local timezone if left to `None`. 180 """ 181 if locale == 'iso8601': 182 format = _ISO8601_FORMATS.get(format, format) 183 return _format_datetime_without_babel(t, format, tzinfo) 184 if babel and locale: 185 if format == '%x': 186 return babel_format_date(t, 'medium', locale) 187 if format == '%X': 188 t = to_datetime(t, tzinfo) 189 return babel_format_time(t, 'medium', None, locale) 190 if format in ('%x %X', None): 191 format = 'medium' 192 if format in _BABEL_FORMATS: 193 t = to_datetime(t, tzinfo) 194 return babel_format_datetime(t, format, None, locale) 195 if format in _BABEL_FORMATS: 196 format = '%x %X' 197 return _format_datetime_without_babel(t, format, tzinfo) 198 199 def format_date(t=None, format='%x', tzinfo=None, locale=None): 155 200 """Convenience method for formatting the date part of a `datetime` object. 156 201 See `format_datetime` for more details. 157 202 """ 203 if locale == 'iso8601': 204 format = _ISO8601_FORMATS.get(format, format) 205 return _format_datetime_without_babel(t, format, tzinfo) 158 206 if format == 'iso8601': 159 207 format = 'iso8601date' 160 return format_datetime(t, format, tzinfo=tzinfo) 161 162 def format_time(t=None, format='%X', tzinfo=None): 208 if babel and locale: 209 if format in ('%x', None): 210 format = 'medium' 211 if format in _BABEL_FORMATS: 212 t = to_datetime(t, tzinfo) 213 return babel_format_date(t, format, locale) 214 if format in _BABEL_FORMATS: 215 format = '%x' 216 return _format_datetime_without_babel(t, format, tzinfo) 217 218 def format_time(t=None, format='%X', tzinfo=None, locale=None): 163 219 """Convenience method for formatting the time part of a `datetime` object. 164 220 See `format_datetime` for more details. 165 221 """ 222 if locale == 'iso8601': 223 format = _ISO8601_FORMATS.get(format, format) 224 return _format_datetime_without_babel(t, format, tzinfo) 166 225 if format == 'iso8601': 167 226 format = 'iso8601time' 168 return format_datetime(t, format, tzinfo=tzinfo) 169 170 def get_date_format_hint(): 227 if babel and locale: 228 if format in ('%X', None): 229 format = 'medium' 230 if format in _BABEL_FORMATS: 231 t = to_datetime(t, tzinfo) 232 return babel_format_time(t, format, None, locale) 233 if format in _BABEL_FORMATS: 234 format = '%X' 235 return _format_datetime_without_babel(t, format, tzinfo) 236 237 def get_date_format_hint(locale=None): 171 238 """Present the default format used by `format_date` in a human readable 172 239 form. … … 174 241 date. 175 242 """ 243 if locale == 'iso8601': 244 return 'YYYY-MM-DD' 245 if babel and locale: 246 format = get_date_format('medium', locale=locale) 247 return format.pattern 248 176 249 t = datetime(1999, 10, 29, tzinfo=utc) 177 250 tmpl = format_date(t, tzinfo=utc) … … 179 252 .replace('10', 'MM', 1).replace('29', 'DD', 1) 180 253 181 def get_datetime_format_hint( ):254 def get_datetime_format_hint(locale=None): 182 255 """Present the default format used by `format_datetime` in a human readable 183 256 form. … … 185 258 date. 186 259 """ 260 if locale == 'iso8601': 261 return u'YYYY-MM-DDThh:mm:ss±hh:mm' 262 if babel and locale: 263 date_pattern = get_date_format('medium', locale=locale).pattern 264 time_pattern = get_time_format('medium', locale=locale).pattern 265 format = get_datetime_format('medium', locale=locale) 266 return format.replace('{0}', time_pattern) \ 267 .replace('{1}', date_pattern) 268 187 269 t = datetime(1999, 10, 29, 23, 59, 58, tzinfo=utc) 188 270 tmpl = format_datetime(t, tzinfo=utc) … … 211 293 ''', re.VERBOSE) 212 294 213 def parse_date(text, tzinfo=None, hint='date'): 214 tzinfo = tzinfo or localtz 215 dt = None 216 text = text.strip() 217 # normalize ISO time 295 def _parse_date_iso8601(text, tzinfo): 218 296 match = _ISO_8601_RE.match(text) 219 297 if match: … … 237 315 hours, minutes, seconds), 238 316 '%Y %m %d %H %M %S ') 239 dt =tzinfo.localize(datetime(*tm[0:6]))317 return tzinfo.localize(datetime(*tm[0:6])) 240 318 except ValueError: 241 319 pass 242 if dt is None: 243 for format in ['%x %X', '%x, %X', '%X %x', '%X, %x', '%x', '%c', 244 '%b %d, %Y']: 245 try: 246 tm = time.strptime(text, format) 247 dt = tzinfo.localize(datetime(*tm[0:6])) 248 break 249 except ValueError: 250 continue 320 321 return None 322 323 def parse_date(text, tzinfo=None, locale=None, hint='date'): 324 tzinfo = tzinfo or localtz 325 text = text.strip() 326 327 dt = _parse_date_iso8601(text, tzinfo) 328 if dt is None and locale != 'iso8601': 329 if babel and locale: 330 dt = _i18n_parse_date(text, tzinfo, locale) 331 else: 332 for format in ['%x %X', '%x, %X', '%X %x', '%X, %x', '%x', '%c', 333 '%b %d, %Y']: 334 try: 335 tm = time.strptime(text, format) 336 dt = tzinfo.localize(datetime(*tm[0:6])) 337 break 338 except ValueError: 339 continue 251 340 if dt is None: 252 341 dt = _parse_relative_time(text, tzinfo) 253 342 if dt is None: 254 343 hint = {'datetime': get_datetime_format_hint, 255 'date': get_date_format_hint}.get(hint, lambda: hint)() 344 'date': get_date_format_hint 345 }.get(hint, lambda(l): hint)(locale) 256 346 raise TracError(_('"%(date)s" is an invalid date, or the date format ' 257 347 'is not known. Try "%(hint)s" instead.', … … 267 357 return dt 268 358 359 def _i18n_parse_date_patterns(): 360 if not babel: 361 return {} 362 363 format_keys = { 364 'y': ('y', 'Y'), 365 'M': ('M',), 366 'd': ('d',), 367 'h': ('h', 'H'), 368 'm': ('m',), 369 's': ('s',), 370 } 371 patterns = {} 372 373 for locale in get_available_locales(): 374 regexp = [r'[0-9]+'] 375 376 date_format = get_date_format('medium', locale=locale) 377 time_format = get_time_format('medium', locale=locale) 378 datetime_format = get_datetime_format('medium', locale=locale) 379 380 formats = ( 381 datetime_format.replace('{0}', time_format.format) \ 382 .replace('{1}', date_format.format), 383 date_format.format) 384 385 orders = [] 386 for format in formats: 387 order = [] 388 for key, chars in format_keys.iteritems(): 389 for char in chars: 390 idx = format.find('%(' + char) 391 if idx != -1: 392 order.append((idx, key)) 393 break 394 order.sort() 395 order = dict((key, idx) for idx, (_, key) in enumerate(order)) 396 orders.append(order) 397 398 month_names = { 399 'jan': 1, 'feb': 2, 'mar': 3, 'apr': 4, 'may': 5, 'jun': 6, 400 'jul': 7, 'aug': 8, 'sep': 9, 'oct': 10, 'nov': 11, 'dec': 12, 401 } 402 if formats[0].find('%(MMM)s') != -1: 403 for width in ('wide', 'abbreviated'): 404 names = get_month_names(width, locale=locale) 405 for num, name in names.iteritems(): 406 name = name.lower() 407 month_names[name] = num 408 regexp.extend(month_names.iterkeys()) 409 410 period_names = {'am': 'am', 'pm': 'pm'} 411 if formats[0].find('%(a)s') != -1: 412 names = get_period_names(locale=locale) 413 for period, name in names.iteritems(): 414 name = name.lower() 415 period_names[name] = period 416 regexp.extend(period_names.iterkeys()) 417 418 patterns[locale] = { 419 'orders': orders, 420 'regexp': re.compile('(%s)' % '|'.join(regexp), 421 re.IGNORECASE | re.UNICODE), 422 'month_names': month_names, 423 'period_names': period_names, 424 } 425 426 return patterns 427 428 _I18N_PARSE_DATE_PATTERNS = _i18n_parse_date_patterns() 429 430 def _i18n_parse_date(text, tzinfo, locale): 431 pattern = _I18N_PARSE_DATE_PATTERNS.get(str(locale)) 432 if pattern is None: 433 return None 434 435 regexp = pattern['regexp'] 436 period_names = pattern['period_names'] 437 month_names = pattern['month_names'] 438 text = text.lower() 439 for order in pattern['orders']: 440 try: 441 return _i18n_parse_date_0(text, order, regexp, period_names, 442 month_names, tzinfo) 443 except ValueError: 444 continue 445 446 return None 447 448 def _i18n_parse_date_0(text, order, regexp, period_names, month_names, tzinfo): 449 matches = regexp.findall(text) 450 if not matches: 451 return None 452 453 period = None 454 for idx, match in enumerate(matches): 455 period = period_names.get(match) 456 if period is not None: 457 del matches[idx] 458 break 459 460 if len(matches) == 5: 461 matches.insert(order['s'], 0) 462 463 values = {} 464 for key, idx in order.iteritems(): 465 if idx < len(matches): 466 value = matches[idx] 467 if key == 'y': 468 if len(value) == 2 and value.isdigit(): 469 value = '20' + value 470 values[key] = value 471 472 if 'y' not in values or 'M' not in values or 'd' not in values: 473 raise ValueError 474 475 for key in ('y', 'M', 'd'): 476 value = values[key] 477 value = month_names.get(value) 478 if value is not None: 479 if key == 'M': 480 values[key] = value 481 else: 482 values[key], values['M'] = values['M'], value 483 break 484 485 values = dict((key, int(value)) for key, value in values.iteritems()) 486 values.setdefault('h', 0) 487 values.setdefault('m', 0) 488 values.setdefault('s', 0) 489 490 if values['h'] < 12 and period == 'pm': 491 values['h'] += 12 492 493 return tzinfo.localize(datetime(values['y'], values['M'], values['d'], 494 values['h'], values['m'], values['s'])) 269 495 270 496 _REL_TIME_RE = re.compile( … … 329 555 return None 330 556 557 # -- formatting/parsing helper functions 558 559 def user_time(req, func, *args, **kwargs): 560 """A helper function which passes to `tzinfo` and `locale` keyword 561 arguments of `func` using `req` parameter. It is expected to be used with 562 `format_*` and `parse_date` methods in `trac.util.datefmt` package. 563 564 :param req: a instance of `Request` 565 :param func: a function which must accept `tzinfo` and `locale` keyword 566 arguments 567 :param args: arguments which pass to `func` function 568 :param kwargs: keyword arguments which pass to `func` function 569 """ 570 if 'tzinfo' not in kwargs: 571 kwargs['tzinfo'] = getattr(req, 'tz', None) 572 if 'locale' not in kwargs: 573 kwargs['locale'] = getattr(req, 'lc_time', None) 574 return func(*args, **kwargs) 331 575 332 576 # -- timezone utilities -
trunk/trac/util/tests/datefmt.py
r9855 r10571 19 19 import unittest 20 20 21 from trac.core import TracError 21 22 from trac.util import datefmt 22 23 … … 146 147 147 148 149 class ISO8601TestCase(unittest.TestCase): 150 def test_format(self): 151 tz = datefmt.timezone('GMT +2:00') 152 t = datetime.datetime(2010, 8, 28, 11, 45, 56, 123456, tz) 153 self.assertEqual('2010-08-28', 154 datefmt.format_date(t, tzinfo=tz, locale='iso8601')) 155 self.assertEqual('11:45:56+02:00', 156 datefmt.format_time(t, tzinfo=tz, locale='iso8601')) 157 self.assertEqual('2010-08-28T11:45:56+02:00', 158 datefmt.format_datetime(t, tzinfo=tz, 159 locale='iso8601')) 160 161 def test_hint(self): 162 try: 163 datefmt.parse_date('***', locale='iso8601', hint='date') 164 except TracError, e: 165 self.assert_('"YYYY-MM-DD"' in unicode(e)) 166 try: 167 datefmt.parse_date('***', locale='iso8601', hint='datetime') 168 except TracError, e: 169 self.assert_(u'"YYYY-MM-DDThh:mm:ss±hh:mm"' in unicode(e)) 170 try: 171 datefmt.parse_date('***', locale='iso8601', hint='foobar') 172 except TracError, e: 173 self.assert_('"foobar"' in unicode(e)) 174 175 176 try: 177 from babel import Locale 178 except: 179 I18nDateFormatTestCase = None 180 else: 181 class I18nDateFormatTestCase(unittest.TestCase): 182 def test_i18n_format_datetime(self): 183 tz = datefmt.timezone('GMT +2:00') 184 t = datetime.datetime(2010, 8, 28, 11, 45, 56, 123456, datefmt.utc) 185 en_US = Locale.parse('en_US') 186 self.assertEqual('Aug 28, 2010 1:45:56 PM', 187 datefmt.format_datetime(t, tzinfo=tz, 188 locale=en_US)) 189 en_GB = Locale.parse('en_GB') 190 self.assertEqual('28 Aug 2010 13:45:56', 191 datefmt.format_datetime(t, tzinfo=tz, 192 locale=en_GB)) 193 fr = Locale.parse('fr') 194 self.assertEqual(u'28 août 2010 13:45:56', 195 datefmt.format_datetime(t, tzinfo=tz, locale=fr)) 196 ja = Locale.parse('ja') 197 self.assertEqual(u'2010/08/28 13:45:56', 198 datefmt.format_datetime(t, tzinfo=tz, locale=ja)) 199 vi = Locale.parse('vi') 200 self.assertEqual(u'13:45:56 28-08-2010', 201 datefmt.format_datetime(t, tzinfo=tz, locale=vi)) 202 zh_CN = Locale.parse('zh_CN') 203 self.assertEqual(u'2010-8-28 下午01:45:56', 204 datefmt.format_datetime(t, tzinfo=tz, 205 locale=zh_CN)) 206 207 def test_i18n_format_date(self): 208 tz = datefmt.timezone('GMT +2:00') 209 t = datetime.datetime(2010, 8, 7, 11, 45, 56, 123456, datefmt.utc) 210 en_US = Locale.parse('en_US') 211 self.assertEqual('Aug 7, 2010', 212 datefmt.format_date(t, tzinfo=tz, locale=en_US)) 213 en_GB = Locale.parse('en_GB') 214 self.assertEqual('7 Aug 2010', 215 datefmt.format_date(t, tzinfo=tz, locale=en_GB)) 216 fr = Locale.parse('fr') 217 self.assertEqual(u'7 août 2010', 218 datefmt.format_date(t, tzinfo=tz, locale=fr)) 219 ja = Locale.parse('ja') 220 self.assertEqual(u'2010/08/07', 221 datefmt.format_date(t, tzinfo=tz, locale=ja)) 222 vi = Locale.parse('vi') 223 self.assertEqual(u'07-08-2010', 224 datefmt.format_date(t, tzinfo=tz, locale=vi)) 225 zh_CN = Locale.parse('zh_CN') 226 self.assertEqual(u'2010-8-7', 227 datefmt.format_date(t, tzinfo=tz, locale=zh_CN)) 228 229 def test_i18n_format_time(self): 230 tz = datefmt.timezone('GMT +2:00') 231 t = datetime.datetime(2010, 8, 28, 11, 45, 56, 123456, datefmt.utc) 232 en_US = Locale.parse('en_US') 233 en_GB = Locale.parse('en_GB') 234 fr = Locale.parse('fr') 235 ja = Locale.parse('ja') 236 vi = Locale.parse('vi') 237 zh_CN = Locale.parse('zh_CN') 238 239 self.assertEqual('1:45:56 PM', 240 datefmt.format_time(t, tzinfo=tz, locale=en_US)) 241 self.assertEqual('13:45:56', 242 datefmt.format_time(t, tzinfo=tz, locale=en_GB)) 243 self.assertEqual('13:45:56', 244 datefmt.format_time(t, tzinfo=tz, locale=fr)) 245 self.assertEqual('13:45:56', 246 datefmt.format_time(t, tzinfo=tz, locale=ja)) 247 self.assertEqual('13:45:56', 248 datefmt.format_time(t, tzinfo=tz, locale=vi)) 249 self.assertEqual(u'下午01:45:56', 250 datefmt.format_time(t, tzinfo=tz, locale=zh_CN)) 251 252 def test_i18n_datetime_hint(self): 253 en_US = Locale.parse('en_US') 254 en_GB = Locale.parse('en_GB') 255 fr = Locale.parse('fr') 256 ja = Locale.parse('ja') 257 vi = Locale.parse('vi') 258 zh_CN = Locale.parse('zh_CN') 259 260 self.assertEqual('MMM d, yyyy h:mm:ss a', 261 datefmt.get_datetime_format_hint(en_US)) 262 self.assertEqual('d MMM yyyy HH:mm:ss', 263 datefmt.get_datetime_format_hint(en_GB)) 264 self.assertEqual('d MMM yyyy HH:mm:ss', 265 datefmt.get_datetime_format_hint(fr)) 266 self.assertEqual('yyyy/MM/dd H:mm:ss', 267 datefmt.get_datetime_format_hint(ja)) 268 self.assertEqual('HH:mm:ss dd-MM-yyyy', 269 datefmt.get_datetime_format_hint(vi)) 270 self.assertEqual('yyyy-M-d ahh:mm:ss', 271 datefmt.get_datetime_format_hint(zh_CN)) 272 273 def test_i18n_date_hint(self): 274 en_US = Locale.parse('en_US') 275 en_GB = Locale.parse('en_GB') 276 fr = Locale.parse('fr') 277 ja = Locale.parse('ja') 278 vi = Locale.parse('vi') 279 zh_CN = Locale.parse('zh_CN') 280 281 self.assertEqual('MMM d, yyyy', 282 datefmt.get_date_format_hint(en_US)) 283 self.assertEqual('d MMM yyyy', 284 datefmt.get_date_format_hint(en_GB)) 285 self.assertEqual('d MMM yyyy', 286 datefmt.get_date_format_hint(fr)) 287 self.assertEqual('yyyy/MM/dd', 288 datefmt.get_date_format_hint(ja)) 289 self.assertEqual('dd-MM-yyyy', 290 datefmt.get_date_format_hint(vi)) 291 self.assertEqual('yyyy-M-d', 292 datefmt.get_date_format_hint(zh_CN)) 293 294 def test_i18n_parse_date_iso8609(self): 295 tz = datefmt.timezone('GMT +2:00') 296 dt = datetime.datetime(2010, 8, 28, 13, 45, 56, 0, tz) 297 d = datetime.datetime(2010, 8, 28, 0, 0, 0, 0, tz) 298 en_US = Locale.parse('en_US') 299 vi = Locale.parse('vi') 300 301 def iso8601(expected, text, tz, locale): 302 self.assertEqual(expected, 303 datefmt.parse_date(text, tz, locale)) 304 305 iso8601(dt, '2010-08-28T15:45:56+0400', tz, en_US) 306 iso8601(dt, '2010-08-28T11:45:56+0000', tz, vi) 307 iso8601(dt, '2010-08-28T11:45:56Z', tz, vi) 308 iso8601(dt, '20100828T144556+0300', tz, en_US) 309 iso8601(dt, '20100828T114556Z', tz, vi) 310 311 iso8601(d, '2010-08-28+0200', tz, en_US) 312 # iso8601(d, '2010-08-28+0000', tz, vi) 313 # iso8601(d, '2010-08-28Z', tz, en_US) 314 iso8601(d, '2010-08-28', tz, vi) 315 iso8601(d, '20100828+0200', tz, en_US) 316 # iso8601(d, '20100828Z', tz, vi) 317 318 def test_i18n_parse_date_datetime(self): 319 tz = datefmt.timezone('GMT +2:00') 320 expected = datetime.datetime(2010, 8, 28, 13, 45, 56, 0, tz) 321 expected_minute = datetime.datetime(2010, 8, 28, 13, 45, 0, 0, tz) 322 en_US = Locale.parse('en_US') 323 en_GB = Locale.parse('en_GB') 324 fr = Locale.parse('fr') 325 ja = Locale.parse('ja') 326 vi = Locale.parse('vi') 327 zh_CN = Locale.parse('zh_CN') 328 329 self.assertEqual(expected, 330 datefmt.parse_date('Aug 28, 2010 1:45:56 PM', tz, 331 en_US)) 332 self.assertEqual(expected, 333 datefmt.parse_date('8 28, 2010 1:45:56 PM', tz, 334 en_US)) 335 self.assertEqual(expected, 336 datefmt.parse_date('28 Aug 2010 1:45:56 PM', tz, 337 en_US)) 338 self.assertEqual(expected, 339 datefmt.parse_date('28 Aug 2010 PM 1:45:56', tz, 340 en_US)) 341 self.assertEqual(expected, 342 datefmt.parse_date('28 Aug 2010 13:45:56', tz, 343 en_US)) 344 self.assertEqual(expected_minute, 345 datefmt.parse_date('28 Aug 2010 PM 1:45', tz, 346 en_US)) 347 348 self.assertEqual(expected, 349 datefmt.parse_date('28 Aug 2010 13:45:56', tz, 350 en_GB)) 351 352 self.assertEqual(expected, 353 datefmt.parse_date(u'28 août 2010 13:45:56', tz, 354 fr)) 355 self.assertEqual(expected, 356 datefmt.parse_date(u'août 28 2010 13:45:56', tz, 357 fr)) 358 self.assertEqual(expected_minute, 359 datefmt.parse_date(u'août 28 2010 13:45', tz, 360 fr)) 361 362 self.assertEqual(expected, 363 datefmt.parse_date('2010/08/28 13:45:56', tz, ja)) 364 self.assertEqual(expected_minute, 365 datefmt.parse_date('2010/08/28 13:45', tz, ja)) 366 367 self.assertEqual(expected, 368 datefmt.parse_date('13:45:56 28-08-2010', tz, vi)) 369 self.assertEqual(expected_minute, 370 datefmt.parse_date('13:45 28-08-2010', tz, vi)) 371 372 self.assertEqual(expected, 373 datefmt.parse_date(u'2010-8-28 下午01:45:56', 374 tz, zh_CN)) 375 self.assertEqual(expected, 376 datefmt.parse_date(u'2010-8-28 01:45:56下午', 377 tz, zh_CN)) 378 self.assertEqual(expected_minute, 379 datefmt.parse_date(u'2010-8-28 下午01:45', tz, 380 zh_CN)) 381 self.assertEqual(expected_minute, 382 datefmt.parse_date(u'2010-8-28 01:45下午', tz, 383 zh_CN)) 384 385 def test_i18n_parse_date_date(self): 386 tz = datefmt.timezone('GMT +2:00') 387 expected = datetime.datetime(2010, 8, 28, 0, 0, 0, 0, tz) 388 en_US = Locale.parse('en_US') 389 en_GB = Locale.parse('en_GB') 390 fr = Locale.parse('fr') 391 ja = Locale.parse('ja') 392 vi = Locale.parse('vi') 393 zh_CN = Locale.parse('zh_CN') 394 395 self.assertEqual(expected, 396 datefmt.parse_date('Aug 28, 2010', tz, en_US)) 397 self.assertEqual(expected, 398 datefmt.parse_date('28 Aug 2010', tz, en_GB)) 399 self.assertEqual(expected, 400 datefmt.parse_date(u'28 août 2010', tz, fr)) 401 self.assertEqual(expected, 402 datefmt.parse_date('2010/08/28', tz, ja)) 403 self.assertEqual(expected, 404 datefmt.parse_date('28-08-2010', tz, vi)) 405 self.assertEqual(expected, 406 datefmt.parse_date(u'2010-8-28', tz, zh_CN)) 407 408 def test_i18n_parse_date_roundtrip(self): 409 tz = datefmt.timezone('GMT +2:00') 410 t = datetime.datetime(2010, 8, 28, 11, 45, 56, 123456, datefmt.utc) 411 expected = datetime.datetime(2010, 8, 28, 13, 45, 56, 0, tz) 412 413 def roundtrip(locale): 414 locale = Locale.parse(locale) 415 formatted = datefmt.format_datetime(t, tzinfo=tz, 416 locale=locale) 417 self.assertEqual(expected, 418 datefmt.parse_date(formatted, tz, locale)) 419 self.assertEqual(formatted, 420 datefmt.format_datetime(expected, tzinfo=tz, 421 locale=locale)) 422 423 roundtrip('ca') 424 roundtrip('cs') 425 roundtrip('de') 426 roundtrip('el') 427 roundtrip('en_GB') 428 roundtrip('en_US') 429 roundtrip('eo') 430 roundtrip('es') 431 roundtrip('es_AR') 432 roundtrip('fa') 433 roundtrip('fi') 434 roundtrip('fr') 435 roundtrip('gl') 436 roundtrip('he') 437 roundtrip('hu') 438 roundtrip('hy') 439 roundtrip('it') 440 roundtrip('ja') 441 roundtrip('ko') 442 roundtrip('nb') 443 roundtrip('nl') 444 roundtrip('pl') 445 roundtrip('pt') 446 roundtrip('pt_BR') 447 roundtrip('ro') 448 roundtrip('ru') 449 roundtrip('sl') 450 roundtrip('sv') 451 roundtrip('tr') 452 roundtrip('vi') 453 roundtrip('zh_CN') 454 roundtrip('zh_TW') 455 456 def test_format_compatibility(self): 457 tz = datefmt.timezone('GMT +2:00') 458 t = datetime.datetime(2010, 8, 28, 11, 45, 56, 123456, datefmt.utc) 459 en_US = Locale.parse('en_US') 460 461 # Converting default format to babel's format 462 self.assertEqual('Aug 28, 2010 1:45:56 PM', 463 datefmt.format_datetime(t, '%x %X', tz, en_US)) 464 self.assertEqual('Aug 28, 2010', 465 datefmt.format_datetime(t, '%x', tz, en_US)) 466 self.assertEqual('1:45:56 PM', 467 datefmt.format_datetime(t, '%X', tz, en_US)) 468 self.assertEqual('Aug 28, 2010', 469 datefmt.format_date(t, '%x', tz, en_US)) 470 self.assertEqual('1:45:56 PM', 471 datefmt.format_time(t, '%X', tz, en_US)) 472 473 # Converting babel's format to strftime format 474 self.assertEqual('08/28/10 13:45:56', 475 datefmt.format_datetime(t, 'medium', tz)) 476 self.assertEqual('08/28/10', datefmt.format_date(t, 'medium', tz)) 477 self.assertEqual('13:45:56', datefmt.format_time(t, 'medium', tz)) 478 479 148 480 def suite(): 149 481 suite = unittest.TestSuite() … … 154 486 suite.addTest(unittest.makeSuite(DateFormatTestCase)) 155 487 suite.addTest(unittest.makeSuite(UTimestampTestCase)) 488 suite.addTest(unittest.makeSuite(ISO8601TestCase)) 489 if I18nDateFormatTestCase: 490 suite.addTest(unittest.makeSuite(I18nDateFormatTestCase, 'test')) 491 else: 492 print "SKIP: utils/tests/datefmt.py (no babel installed)" 156 493 return suite 157 494 -
trunk/trac/web/chrome.py
r10569 r10571 56 56 javascript_quote, exception_to_unicode 57 57 from trac.util.datefmt import pretty_timedelta, format_datetime, format_date, \ 58 format_time, from_utimestamp, http_date, utc 58 format_time, from_utimestamp, http_date, utc, \ 59 user_time 59 60 from trac.util.translation import _, get_available_locales 60 61 from trac.web.api import IRequestHandler, ITemplateStreamFilter, HTTPNotFound … … 782 783 exception_to_unicode(e)) 783 784 show_email_addresses = False 784 tzinfo = None785 if req:786 tzinfo = req.tz787 785 788 786 def dateinfo(date): 789 787 return tag.span(pretty_timedelta(date), 790 title= format_datetime(date))788 title=user_time(req, format_datetime, date)) 791 789 792 790 def get_rel_url(resource, **kwargs): … … 820 818 # Date/time formatting 821 819 'dateinfo': dateinfo, 822 'format_datetime': partial( format_datetime, tzinfo=tzinfo),823 'format_date': partial( format_date, tzinfo=tzinfo),824 'format_time': partial( format_time, tzinfo=tzinfo),820 'format_datetime': partial(user_time, req, format_datetime), 821 'format_date': partial(user_time, req, format_date), 822 'format_time': partial(user_time, req, format_time), 825 823 'fromtimestamp': partial(datetime.datetime.fromtimestamp, 826 tz= tzinfo),824 tz=req and req.tz), 827 825 'from_utimestamp': from_utimestamp, 828 826 -
trunk/trac/web/main.py
r10405 r10571 43 43 read_file, translation 44 44 from trac.util.concurrency import threading 45 from trac.util.datefmt import format_datetime, http_date, localtz, timezone 45 from trac.util.datefmt import format_datetime, http_date, localtz, timezone, \ 46 user_time 46 47 from trac.util.text import exception_to_unicode, shorten_line, to_unicode 47 48 from trac.util.translation import _, get_negotiated_locale, has_babel, \ … … 95 96 """The preferred language to use if no user preference has been set. 96 97 (''since 0.12.1'') 98 """) 99 100 default_date_format = Option('trac', 'default_date_format', '', 101 """The date format. Valid option is 'iso8601' for ISO 8601 format, If 102 not specified, use a browser's language. (''since 0.13'') 97 103 """) 98 104 … … 124 130 'session': self._get_session, 125 131 'locale': self._get_locale, 132 'lc_time': self._get_lc_time, 126 133 'tz': self._get_timezone, 127 134 'form_token': self._get_form_token … … 248 255 self.log.debug("Negotiated locale: %s -> %s", preferred, negotiated) 249 256 return negotiated 257 258 def _get_lc_time(self, req): 259 lc_time = req.session.get('lc_time') 260 if not lc_time or lc_time == 'locale' and not has_babel: 261 lc_time = self.default_date_format 262 if lc_time == 'iso8601': 263 return 'iso8601' 264 return req.locale 250 265 251 266 def _get_timezone(self, req): … … 598 613 template = 'index.html' 599 614 600 data = {'trac': {'version': TRAC_VERSION, 'time': format_datetime()}, 615 data = {'trac': {'version': TRAC_VERSION, 616 'time': user_time(req, format_datetime)}, 601 617 'req': req} 602 618 if req.environ.get('trac.template_vars'): -
trunk/trac/wiki/macros.py
r10550 r10571 29 29 get_resource_summary, get_resource_url 30 30 from trac.util.compat import cleandoc 31 from trac.util.datefmt import format_date, from_utimestamp 31 from trac.util.datefmt import format_date, from_utimestamp, user_time 32 32 from trac.util.html import escape 33 33 from trac.util.presentation import separated … … 266 266 if not 'WIKI_VIEW' in formatter.perm('wiki', name, version): 267 267 continue 268 date = format_date(from_utimestamp(ts)) 268 req = formatter.req 269 date = user_time(req, format_date, from_utimestamp(ts)) 269 270 if date != prevdate: 270 271 prevdate = date -
trunk/trac/wiki/tests/formatter.py
r10425 r10571 4 4 import unittest 5 5 from datetime import datetime 6 7 try: 8 from babel import Locale 9 except ImportError: 10 Locale = None 6 11 7 12 from trac.core import * … … 115 120 116 121 req = Mock(href=Href('/'), abs_href=Href('http://www.example.com/'), 117 authname='anonymous', perm=MockPerm(), tz=None, args={}) 122 authname='anonymous', perm=MockPerm(), tz=utc, args={}, 123 locale=Locale.parse('en_US') if Locale else None) 118 124 if context: 119 125 if isinstance(context, tuple):
Note:
See TracChangeset
for help on using the changeset viewer.