From cae0a76a3d509050f192db4fe2fd46e5a6f267f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Thu, 17 Dec 2009 11:39:54 +0100 Subject: [PATCH] Remove egenix-mx-base and replace it by python-dateutil --- CHANGELOG | 1 + INSTALL | 2 +- setup.py | 2 +- trytond/backend/sqlite/database.py | 20 ++++++------ trytond/ir/cron.py | 25 +++++++-------- trytond/ir/default.py | 14 +++------ trytond/ir/lang.py | 17 +++++----- trytond/ir/model.py | 4 +-- trytond/ir/sequence.py | 8 ++--- trytond/report/report.py | 8 ++--- trytond/res/request.py | 2 +- trytond/server.py | 6 ---- trytond/tests/test_tryton.py | 11 ++++++- trytond/tools/__init__.py | 1 + trytond/tools/convert.py | 4 +-- trytond/tools/datetime_strftime.py | 50 ++++++++++++++++++++++++++++++ 16 files changed, 112 insertions(+), 63 deletions(-) create mode 100644 trytond/tools/datetime_strftime.py diff --git a/CHANGELOG b/CHANGELOG index 8bf4061302..d180faf9e3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +* Remove egenix-mx-base and replace it by python-dateutil * Add cache to safe_eval * Rename HttpDaemon into XMLRPCDaemon * Improve TranslateFactory to fetch all translations for a report in one query diff --git a/INSTALL b/INSTALL index 7349c897ba..4a6eb2bf9f 100644 --- a/INSTALL +++ b/INSTALL @@ -6,8 +6,8 @@ Prerequisites * Python 2.4 or later (http://www.python.org/) * lxml (http://codespeak.net/lxml/) - * egenix-mx-base (http://www.egenix.com/products/python/) * relatorio 0.2.0 or later (http://relatorio.openhex.org/) + * python-dateutil (http://labix.org/python-dateutil) * Optional: psycopg 2 or later (http://www.initd.org/) * Optional: pywebdav 0.9.3 or later (http://code.google.com/p/pywebdav/) * Optional: pydot (http://code.google.com/p/pydot/) diff --git a/setup.py b/setup.py index 757d85f653..b250b8bbc2 100644 --- a/setup.py +++ b/setup.py @@ -43,9 +43,9 @@ setup(name=PACKAGE, license=LICENSE, install_requires=[ 'lxml', - 'egenix-mx-base', 'relatorio >= 0.2.0', 'Genshi', + 'python-dateutil', ], extras_require={ 'PostgreSQL': ['psycopg2 >= 2.0'], diff --git a/trytond/backend/sqlite/database.py b/trytond/backend/sqlite/database.py index d7e8a6cc6d..2774eca21c 100644 --- a/trytond/backend/sqlite/database.py +++ b/trytond/backend/sqlite/database.py @@ -7,9 +7,9 @@ from trytond.session import Session import logging import os import re -import mx.DateTime from decimal import Decimal import datetime +import time _FIX_ROWCOUNT = False try: @@ -35,37 +35,37 @@ def extract(lookup_type, date): hours, minutes, seconds = map(int, timepart_full[0].split(":")) if len(timepart_full) == 2: microseconds = int(timepart_full[1]) - seconds += microseconds / 1000000.0 - date = mx.DateTime.DateTime(year, month, day, hours, minutes, seconds) + date = datetime.datetime(year, month, day, hours, minutes, seconds, + microseconds) except: return None if lookup_type.lower() == 'century': return date.year / 100 + (date.year % 100 and 1 or 0) elif lookup_type.lower() == 'decade': - return date.year.year / 10 + return date.year / 10 elif lookup_type.lower() == 'dow': return (date.weekday() + 1) % 7 elif lookup_type.lower() == 'doy': - return date.day_of_year + return date.timetuple().tm_yday elif lookup_type.lower() == 'epoch': - return date.ticks() + return int(time.mktime(date.timetuple())) elif lookup_type.lower() == 'microseconds': - return int(a.second * 1000000) + return date.microsecond elif lookup_type.lower() == 'millennium': return date.year / 1000 + (date.year % 1000 and 1 or 0) elif lookup_type.lower() == 'milliseconds': - return int(a.second * 1000) + return a.microsecond / 1000 elif lookup_type.lower() == 'quarter': return date.month / 4 + 1 elif lookup_type.lower() == 'week': - return date.iso_week[1] + return date.isocalendar()[1] return getattr(date, lookup_type) def date_trunc(_type, date): if _type == 'second': return date try: - date = mx.DateTime.strptime(date, '%Y-%m-%d %H:%M:%S') + time.strptime(date, '%Y-%m-%d %H:%M:%S') except: return None if _type == 'year': diff --git a/trytond/ir/cron.py b/trytond/ir/cron.py index 3dd358a7ff..4d66fb4edb 100644 --- a/trytond/ir/cron.py +++ b/trytond/ir/cron.py @@ -11,18 +11,18 @@ SPEC: Execute "model.function(*eval(args))" periodically from trytond.backend import Database from trytond.model import ModelView, ModelSQL, fields from trytond.tools import safe_eval -from mx import DateTime import datetime +from dateutil.relativedelta import relativedelta import traceback import sys _INTERVALTYPES = { - 'work_days': lambda interval: DateTime.RelativeDateTime(days=interval), - 'days': lambda interval: DateTime.RelativeDateTime(days=interval), - 'hours': lambda interval: DateTime.RelativeDateTime(hours=interval), - 'weeks': lambda interval: DateTime.RelativeDateTime(days=7*interval), - 'months': lambda interval: DateTime.RelativeDateTime(months=interval), - 'minutes': lambda interval: DateTime.RelativeDateTime(minutes=interval), + 'work_days': lambda interval: relativedelta(days=interval), + 'days': lambda interval: relativedelta(days=interval), + 'hours': lambda interval: relativedelta(hours=interval), + 'weeks': lambda interval: relativedelta(weeks=interval), + 'months': lambda interval: relativedelta(months=interval), + 'minutes': lambda interval: relativedelta(minutes=interval), } class Cron(ModelSQL, ModelView): @@ -147,7 +147,7 @@ class Cron(ModelSQL, ModelView): cursor.rollback() def pool_jobs(self, db_name): - now = DateTime.now() + now = datetime.datetime.now() try: database = Database(db_name).connect() cursor = database.cursor() @@ -168,9 +168,7 @@ class Cron(ModelSQL, ModelView): for cron in crons: cursor.execute('UPDATE ir_cron SET running = %s ' \ 'WHERE id = %s', (True, cron['id'])) - nextcall = DateTime.strptime( - cron['nextcall'].strftime('%Y-%m-%d %H:%M:%S'), - '%Y-%m-%d %H:%M:%S') + nextcall = cron['nextcall'] numbercall = cron['numbercall'] done = False @@ -191,9 +189,8 @@ class Cron(ModelSQL, ModelView): addsql_param = [False] cursor.execute("UPDATE ir_cron SET nextcall = %s, " \ "running = %s, numbercall = %s" + addsql + " " \ - "WHERE id = %s", - [nextcall.strftime('%Y-%m-%d %H:%M:%S'), False, - numbercall] + addsql_param + [cron['id']]) + "WHERE id = %s", [nextcall, False, numbercall] \ + + addsql_param + [cron['id']]) cursor.commit() except Exception, e: cursor.rollback() diff --git a/trytond/ir/default.py b/trytond/ir/default.py index deb9007c2e..683695b26f 100644 --- a/trytond/ir/default.py +++ b/trytond/ir/default.py @@ -4,8 +4,8 @@ from trytond.model import ModelView, ModelSQL, fields from trytond.tools import Cache, safe_eval from decimal import Decimal -import mx.DateTime import datetime +import time class Default(ModelSQL, ModelView): @@ -59,15 +59,11 @@ class Default(ModelSQL, ModelView): elif default.field.ttype == 'numeric': res[default.field.name] = Decimal(default.value) elif default.field.ttype == 'date': - date = mx.DateTime.strptime(default.value, '%Y-%m-%d') - res[default.field.name] = datetime.date(date.year, - date.month, date.day) + res[default.field.name] = datetime.date(*time.strptime( + default.value, '%Y-%m-%d')[:3]) elif default.field.ttype == 'datetime': - date = mx.DateTime.strptime(default.value, - '%Y-%m-%d %H:%M:%S') - res[default.field.name] = datetime.datetime(date.year, - date.month, date.day, date.hour, date.minute, - int(date.second)) + res[default.field.name] = datetime.datetime(*time.strptime( + default.value, '%Y-%m-%d %H:%M:%S')[:6]) else: res[default.field.name] = safe_eval(default.value) return res diff --git a/trytond/ir/lang.py b/trytond/ir/lang.py index dd56697776..6777e352ef 100644 --- a/trytond/ir/lang.py +++ b/trytond/ir/lang.py @@ -3,7 +3,7 @@ "Lang" from trytond.model import ModelView, ModelSQL, fields from trytond.model.cacheable import Cacheable -from trytond.tools import safe_eval +from trytond.tools import safe_eval, datetime_strftime import time from time_locale import TIME_LOCALE @@ -114,7 +114,7 @@ class Lang(ModelSQL, ModelView, Cacheable): ''' for lang in self.browse(cursor, user, ids): try: - time.strftime(lang.date, time.localtime()) + datetime_strftime(datetime.datetime.now(), lang.date) except: return False if '%Y' not in lang.date: @@ -326,20 +326,21 @@ class Lang(ModelSQL, ModelView, Cacheable): return s.replace('<', '').replace('>', '') - def strftime(self, timetuple, code, format): + def strftime(self, datetime, code, format): ''' - Convert timetuple to a string as specified by the format argument. + Convert datetime to a string as specified by the format argument. - :param timetuple: a tuple or struct_time representing a time + :param datetime: a datetime :param code: locale code :param format: a string :return: a unicode string ''' if code in TIME_LOCALE: for f, i in (('%a', 6), ('%A', 6), ('%b', 1), ('%B', 1)): - format = format.replace(f, TIME_LOCALE[code][f][timetuple[i]]) + format = format.replace(f, + TIME_LOCALE[code][f][datetime.timetuple()[i]]) format = format.replace('%p', TIME_LOCALE[code]['%p']\ - [timetuple[3] < 12 and 0 or 1]).encode('utf-8') - return time.strftime(format, timetuple).decode('utf-8') + [datetime.timetuple()[3] < 12 and 0 or 1]).encode('utf-8') + return datetime_strftime(datetime, format).decode('utf-8') Lang() diff --git a/trytond/ir/model.py b/trytond/ir/model.py index 9922e46e45..7d5f9ae8b3 100644 --- a/trytond/ir/model.py +++ b/trytond/ir/model.py @@ -5,7 +5,7 @@ from trytond.model import ModelView, ModelSQL, fields from trytond.tools import Cache from trytond.report import Report from trytond.wizard import Wizard -import time +import datetime import base64 import re IDENTIFIER = re.compile(r'^[a-zA-z_][a-zA-Z0-9_]*$') @@ -320,7 +320,7 @@ class ModelData(ModelSQL, ModelView): ] def default_date_init(self, cursor, user, context=None): - return time.strftime('%Y-%m-%d %H:%M:%S') + return datetime.datetime.now() def default_inherit(self, cursor, user, context=None): return False diff --git a/trytond/ir/sequence.py b/trytond/ir/sequence.py index 02e0e63bb5..d199c74322 100644 --- a/trytond/ir/sequence.py +++ b/trytond/ir/sequence.py @@ -1,9 +1,9 @@ #This file is part of Tryton. The COPYRIGHT file at the top level of #this repository contains the full copyright notices and license terms. -import time from trytond.model import ModelView, ModelSQL, fields from string import Template import datetime +from trytond.tools import datetime_strftime class SequenceType(ModelSQL, ModelView): @@ -82,9 +82,9 @@ class Sequence(ModelSQL, ModelView): date_obj = self.pool.get('ir.date') if not date: date = date_obj.today(cursor, user, context=context) - year = date.strftime('%Y') - month = date.strftime('%m') - day = date.strftime('%d') + year = datetime_strftime(date, '%Y') + month = datetime_strftime(date, '%m') + day = datetime_strftime(date, '%d') return Template(string or '').substitute( year=year, month=month, diff --git a/trytond/report/report.py b/trytond/report/report.py index ef76fdb973..782363e161 100644 --- a/trytond/report/report.py +++ b/trytond/report/report.py @@ -28,7 +28,6 @@ import traceback from trytond.config import CONFIG from trytond.backend import DatabaseIntegrityError import inspect -import mx.DateTime import logging PARENTS = { @@ -311,10 +310,11 @@ class Report(object): value = str(value)[:19] locale_format = locale_format + ' %H:%M:%S' string_pattern = '%Y-%m-%d %H:%M:%S' - date = mx.DateTime.strptime(str(value), string_pattern) + date = datetime.datetime(*time.strptime(str(value), + string_pattern)[:6]) else: - date = mx.DateTime.DateTime(*(value.timetuple()[:6])) - return lang_obj.strftime(date.tuple(), code, locale_format) + date = datetime.datetime(*(value.timetuple()[:6])) + return lang_obj.strftime(date, code, locale_format) if currency: return lang_obj.currency(lang, value, currency, grouping=grouping) return lang_obj.format(lang, '%.' + str(digits) + 'f', value, diff --git a/trytond/res/request.py b/trytond/res/request.py index ff1f51505d..bf15e9c2b5 100644 --- a/trytond/res/request.py +++ b/trytond/res/request.py @@ -118,7 +118,7 @@ class Request(ModelSQL, ModelView): request_history_obj.create(cursor, user, values, context=context) self.write(cursor, user, ids, { 'state': 'waiting', - 'date_send': time.strftime('%Y-%m-%d %H:%M:%S'), + 'date_send': datetime.datetime.now(), }, context=context) return True diff --git a/trytond/server.py b/trytond/server.py index b10d43ca08..8b7fb41bba 100644 --- a/trytond/server.py +++ b/trytond/server.py @@ -11,12 +11,6 @@ import logging.handlers import sys, os, signal import time from config import CONFIG -import mx.DateTime - -if not hasattr(mx.DateTime, 'strptime'): - mx.DateTime.strptime = lambda x, y: mx.DateTime.mktime( - time.strptime(x, y)) - from getpass import getpass try: import hashlib diff --git a/trytond/tests/test_tryton.py b/trytond/tests/test_tryton.py index f6a5f2cf52..5f03ff0555 100644 --- a/trytond/tests/test_tryton.py +++ b/trytond/tests/test_tryton.py @@ -13,7 +13,7 @@ import unittest import time from decimal import Decimal from trytond import pysocket -from trytond.tools import reduce_ids, safe_eval +from trytond.tools import reduce_ids, safe_eval, datetime_strftime from lxml import etree import datetime @@ -130,6 +130,15 @@ class ToolsTestCase(unittest.TestCase): self.assertRaises(Exception, safe_eval, "().__class__.mro()[1].__subclasses__()") + def test0070datetime_strftime(self): + ''' + Test datetime_strftime + ''' + self.assert_(datetime_strftime(datetime.date(2005, 3, 2), + '%Y-%m-%d'), '2005-03-02') + self.assert_(datetime_strftime(datetime.date(1805, 3, 2), + '%Y-%m-%d'), '1805-03-02') + class FieldsTestCase(unittest.TestCase): ''' diff --git a/trytond/tools/__init__.py b/trytond/tools/__init__.py index 048e066e42..c710b3d7c1 100644 --- a/trytond/tools/__init__.py +++ b/trytond/tools/__init__.py @@ -2,3 +2,4 @@ #this repository contains the full copyright notices and license terms. from misc import * from convert import * +from datetime_strftime import * diff --git a/trytond/tools/convert.py b/trytond/tools/convert.py index f01ec49c4a..54a0f03b60 100644 --- a/trytond/tools/convert.py +++ b/trytond/tools/convert.py @@ -815,7 +815,7 @@ class TrytondXmlHandler(sax.handler.ContentHandler): 'module': module, 'db_id': db_id, 'values': values, - 'date_update': time.strftime('%Y-%m-%d %H:%M:%S'), + 'date_update': datetime.datetime.now(), }) for table, inherit_mdata_id in inherit_mdata_ids: self.modeldata_obj.write(cursor, user, inherit_mdata_id, { @@ -824,7 +824,7 @@ class TrytondXmlHandler(sax.handler.ContentHandler): 'module': module, 'db_id': inherit_db_ids[table], 'values': values, - 'date_update': time.strftime('%Y-%m-%d %H:%M:%S'), + 'date_update': datetime.datetime.now(), }) # reset_browsercord to keep cache memory low self.fs2db.reset_browsercord(module, object_ref._name, db_id) diff --git a/trytond/tools/datetime_strftime.py b/trytond/tools/datetime_strftime.py new file mode 100644 index 0000000000..65f6fa50f4 --- /dev/null +++ b/trytond/tools/datetime_strftime.py @@ -0,0 +1,50 @@ +#This file is part of Tryton. The COPYRIGHT file at the top level of +#this repository contains the full copyright notices and license terms. +#Copyright (c) 2002-2007 John D. Hunter; All Rights Reserved +import time + +def datetime_strftime(dt, fmt): + ''' + Allow datetime strftime formatting for years before 1900. + See http://bugs.python.org/issue1777412 + ''' + if dt.year > 1900: + return dt.strftime(fmt) + + def _findall(text, substr): + # Also finds overlaps + sites = [] + i = 0 + while True: + j = text.find(substr, i) + if j == -1: + break + sites.append(j) + i = j + 1 + return sites + + year = dt.year + # For every non-leap year century, advance by + # 6 years to get into the 28-year repeat cycle + delta = 2000 - year + off = 6 * (delta // 100 + delta // 400) + year = year + off + # Move to around the year 2000 + year = year + ((2000 - year) // 28) * 28 + timetuple = dt.timetuple() + s1 = time.strftime(fmt, (year,) + timetuple[1:]) + sites1 = _findall(s1, str(year)) + + s2 = time.strftime(fmt, (year + 28,) + timetuple[1:]) + sites2 = _findall(s2, str(year + 28)) + + sites = [] + for site in sites1: + if site in sites2: + sites.append(site) + + s = s1 + syear = "%4d" % (dt.year,) + for site in sites: + s = s[:site] + syear + s[site+4:] + return s