Viewing file: DateTime.py (24.07 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
######################################################################## # $Header: /var/local/cvsroot/4Suite/Ft/Xml/Xslt/Exslt/DateTime.py,v 1.24 2003/09/13 04:31:57 mbrown Exp $ """ Implementation of EXSLT Dates and Times module
Copyright 2003 Fourthought, Inc. (USA). Detailed license and copyright information: http://4suite.org/COPYRIGHT Project home, documentation, distributions: http://4suite.org/ """
import re, calendar from math import ceil from xml.dom import Node from Ft.Lib import boolean, number from Ft.Xml.Xslt.Exslt.MessageSource import Error as ExsltError from Ft.Xml.Xslt import XsltElement, XsltRuntimeException from Ft.Xml.XPath import Conversions from Ft.Lib import Time as FtTime
EXSL_DATE_TIME_NS = 'http://exslt.org/dates-and-times' ISO_8601_DATETIME = '%Y-%m-%dT%H:%M:%S' ISO_8601_DATE = '%Y-%m-%d' ISO_8601_TIME = '%H:%M:%S'
## _mm2ddtab is a lookup table that gives the difference ## in days between the first day of month1 and the first ## day of month 2. If month1 is numerically greater than month2, ## then month2 is assumed to be in the next year.
## Example: ## _mm2ddtab[1][3] -> 59 (Jan 1 - Mar 1 is 59 days) ## _mm2ddtab[3][1] -> 306
## Note that this table doesn't know about leap days. ## Actually, it may not be needed. ##_mm2ddtab = [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], ## [0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334], ## [0, 334, 0, 28, 59, 89, 120, 150, 181, 212, 242, 273, 303], ## [0, 306, 337, 0, 31, 61, 92, 122, 153, 184, 214, 245, 275], ## [0, 275, 306, 334, 0, 30, 61, 91, 122, 153, 183, 214, 244], ## [0, 245, 276, 304, 335, 0, 31, 61, 92, 123, 153, 184, 214], ## [0, 214, 245, 273, 304, 334, 0, 30, 61, 92, 122, 153, 183], ## [0, 184, 215, 243, 274, 304, 335, 0, 31, 62, 92, 123, 153], ## [0, 153, 184, 212, 243, 273, 304, 334, 0, 31, 61, 92, 122], ## [0, 122, 153, 181, 212, 242, 273, 303, 334, 0, 30, 61, 91], ## [0, 92, 123, 151, 182, 212, 243, 273, 304, 335, 0, 31, 61], ## [0, 61, 92, 120, 151, 181, 212, 242, 273, 304, 334, 0, 30], ## [0, 31, 62, 90, 121, 151, 182, 212, 243, 274, 304, 335, 0]]
## EXSLT Dates and Times: Core Elements ## EXSLT Dates and Times: Core Functions
def DateTime(context): """ The date:date-time function returns the current local date/time as an ISO 8601 formatted date/time string, with a time zone. """ return unicode(FtTime.FromPythonTime())
def Date(context, dateTime=None): """ The date:date function returns the date portion of the dateTime argument if present, or of the current local date/time. The argument can be given in xs:dateTime or xs:date format.
Non-conformance issues: The dateTime argument must be given in ISO 8601 date/time or date format, so an xs:date format with a time zone is not supported. Also, EXSLT requires that the result must include a time zone unless there was an argument given and it did not have a time zone. If a date/time argument is given, this implementation returns the date with no time zone info. """ return_local = 1 #dateTime = _coerce(dateTime) if dateTime is None: dateTime = FtTime.FromPythonTime() elif not isinstance(dateTime,FtTime.DT): st = Conversions.StringValue(dateTime) if u'Z' in st: return_local = 0 if st: try: dateTime = FtTime.FromISO8601(st) except SyntaxError: return u''
return unicode(dateTime.asISO8601Date(local=return_local))
def Time(context, dateTime=None): """ The date:time function returns the time portion of the dateTime argument if present, or of the current local date/time. The argument can be given in xs:dateTime or xs:time format.
Non-conformance issue: EXSLT requires that the result must include a time zone unless there was an argument given and it did not have a time zone. If an argument is given, this implementation returns the result with time zone info, always. """ return_local = 1 #dateTime = _coerce(dateTime) if dateTime is None: dateTime = FtTime.FromPythonTime() elif not isinstance(dateTime,FtTime.DT): st = Conversions.StringValue(dateTime) if u'Z' in st: return_local = 0 if st: try: dateTime = FtTime.FromISO8601(st) except SyntaxError: return u''
return unicode(dateTime.asISO8601Time(local=return_local))[1:]
def Year(context, dateTime=None): """The date:year function returns the year portion of the dateTime supplied, or of the current year, as an integer.""" dateTime = _coerce(dateTime) if dateTime is None: return number.nan return dateTime.year(local=1)
def LeapYear(context, dateTime=None): """The date:leap-year function returns true if the year argument (defaults to current year) is a leap year, false otherwise.""" dateTime = _coerce(dateTime) if dateTime is None: return number.nan if dateTime.isLeapYear(): return boolean.true return boolean.false
def MonthInYear(context,dateTime=None): """The date:month-in-year function returns the month portion of the dateTime argument (defaults to current year) as an integer.""" dateTime = _coerce(dateTime) if dateTime is None: return number.nan return dateTime.month(local=1)
def MonthName(context,dateTime=None): """The date:month-name function returns the full English name of the month portion of a date.""" dateTime = _coerce(dateTime) if dateTime is None: return u'' return dateTime.monthName(local=1)
def MonthAbbreviation(context, dateTime=None): """The date:month-abbreviation function returns the abbreviation of the month of a date.""" dateTime = _coerce(dateTime) if dateTime is None: return u'' return dateTime.abbreviatedMonthName(local=1)
def WeekInYear(context, dateTime=None): """The date:week-in-year function returns a number representing the week of the year a date is in.""" dateTime = _coerce(dateTime) if dateTime is None: return number.nan return dateTime.weekInYear(local=1)
def DayInYear(context, dateTime=None): """The date:day-in-year function returns a number representing the position of a date in the year.""" dateTime = _coerce(dateTime) if dateTime is None: return number.nan return dateTime.dayInYear(local=1)
def DayInMonth(context, dateTime=None): """The date:day-in-month function returns the numerical date, i.e. 27 for the 27th of March.""" dateTime = _coerce(dateTime) if dateTime is None: return number.nan return dateTime.day(local=1)
def DayOfWeekInMonth(context, dateTime=None): """The date:day-of-week-in-month function returns the day-of-the-week in a month of a date as a number (e.g. 3 for the 3rd Tuesday in May).""" dateTime = _coerce(dateTime) if dateTime is None: return number.nan day = dateTime.day(local=1) return ((day - 1) / 7) + 1
def DayInWeek(context, dateTime=None): """The date:day-in-week function returns a number representing the weekday of a given date. Sunday is 1, Saturday is 7.""" dateTime = _coerce(dateTime) if dateTime is None: return number.nan # Returns 0 - 6, where 0 = Monday weekday = dateTime.dayOfWeek(local=1) return weekday + (2*(weekday<6) - 5*(weekday>5))
def DayName(context, dateTime=None): """The date:day-name function returns the full English day name of a given date.""" dateTime = _coerce(dateTime) if dateTime is None: return u'' # Returns 0 - 6, where 0 = Monday weekday = dateTime.dayOfWeek(local=1) names = ('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday') return names[weekday]
def DayAbbreviation(context, dateTime=None): """The date:day-abbreviation function returns the English abbreviation for the day name of a given date.""" dateTime = _coerce(dateTime) if dateTime is None: return u'' return dateTime.abberviatedWeekdayName(local=1)
def HourInDay(context, dateTime=None): """The date:hour-in-date function returns the hour portion of a date- time string as an integer.""" dateTime = _coerce(dateTime) if dateTime is None: return number.nan return dateTime.hour(local=1)
def MinuteInHour(context, dateTime=None): """The date:minute-in-hour function returns the minute portion of a date-time string as an integer.""" dateTime = _coerce(dateTime) if dateTime is None: return number.nan return dateTime.minute(local=1)
def SecondInMinute(context, dateTime=None): """The date:second-in-minute function returns the seconds portion of a date-time string as an integer.""" dateTime = _coerce(dateTime) if dateTime is None: return number.nan return dateTime.second()
## EXSLT Dates and Times: Other Elements (unstable)
class DateFormatElement(XsltElement): pass
## EXSLT Dates and Times: Other Functions (unstable)
def FormatDate(context, dateTime, pattern): print "Warning: FormatDate not implemented" return "error"
def ParseDate(context, dateString, pattern): print "Warning: ParseDate not implemented" return "error"
def WeekInMonth(context, dateTime=None): print "Warning: WeekInMonth not implemented" return -1
def Difference(context, dateTime1, dateTime2): """The date:difference function returns the difference between dt1 and dt2 as a duration in string form.
According to the spec (http://exslt.org/date/functions/difference/index.html)
"If the date/time string with the least specific format is in either xs:gYearMonth or xs:gYear format, then the number of days, hours, minutes and seconds in the duration string must be equal to zero. (The format of the string will be PnYnM.) The number of months specified in the duration must be less than 12."
"Otherwise, the number of years and months in the duration string must be equal to zero. (The format of the string will be PnDTnHnMnS.) The number of seconds specified in the duration string must be less than 60; the number of minutes must be less than 60; the number of hours must be less than 24."
This method does not account for leap seconds, so you should not expect precise results over long time spans. """ ## First we determine the chronological order ## of the date-time strings. If they are properly ## formatted, string comparison should yield ## correct results. dt1 = Conversions.StringValue(dateTime1) dt2 = Conversions.StringValue(dateTime2) if dt1 == dt2: return 'P0D' elif dt1 < dt2: start, end = dt1, dt2 sign = '' else: start, end = dt2, dt1 sign = '-' ## This is a kludge. I think we need to normalize ## time zones, but for now we'll just ignore them. if len(start) > 19 and start[-1] == 'Z': start = start[:-1] elif len(start) > 19 and start[-6] == '-' or start[-6] == '+': start = start[:-6] if len(end) > 19 and end[-1] == 'Z': end = end[:-1] elif len(end) > 19 and end[-6] == '-' or end[-6] == '+': end = end[:-6] format = ISO_8601_DATETIME if len(start) <= 10 and len(end) <= 10: format = ISO_8601_DATE if len(end) > len(start): start = start[:10]+"T00:00:00" if len(start) > len(end): end = end[:10]+"T00:00:00" syr, smo, sday, shr, smin, ssec = _strptime(start, format)[:6] eyr, emo, eday, ehr, emin, esec = _strptime(end, format)[:6]
#mktime appears to ignore day-of-week & julian-day, and dst=-1 means "don't know" ssse = time.mktime((syr, smo, sday, shr, smin, ssec) + (0,0,-1)) esse = time.mktime((eyr, emo, eday, ehr, emin, esec) + (0,0,-1)) rem = int(esse - ssse) #hdiff = esse - ssse #rem = abs(hdiff) #sign = unicode(hdiff/rem)[:-1] ddiff = rem/86400 rem = rem % 86400 hdiff = rem/3600 rem = rem % 3600 mdiff = rem/60 sdiff = rem % 60
return "%sP%sDT%sH%sM%sS" % (sign, ddiff, hdiff, mdiff, sdiff)
updiff = 0 # TEST yrdiff = eyr - syr # years difference if yrdiff < 0: raise ValueError, 'yrdiff must be non-negative' elif yrdiff > 0: updiff = 1 modiff = emo - smo # months difference if not updiff and modiff < 0: raise ValueError, 'if yrdiff is 0, modiff must be non-negative' elif modiff > 0: updiff = 1 ## If modiff is negative, add 12 to it ## and subtract 1 from yrdiff isneg = modiff < 0 modiff = modiff + isneg * 12 yrdiff = yrdiff - isneg ddiff = eday - sday # days difference if not updiff and ddiff < 0: raise ValueError, 'If yrdiff and modiff are 0, modiff must be non-negative' elif ddiff > 0: updiff = 1 ## If ddiff is negative, increment it ## appropriately to wrap around the end of ## the month, and decrement modiff or ydiff isneg = ddiff < 0 ddiff = ddiff + isneg * _moLen(syr, smo) if modiff: modiff = modiff - isneg elif yrdiff: yrdiff = yrdiff - isneg hrdiff = ehr - shr if not updiff and hrdiff < 0: raise ValueError, 'If the date difference is 0, hrdiff must be non-negative' elif hrdiff > 0: updiff = 1 ## isneg = hrdiff < 0 hrdiff = hrdiff + isneg * 24 if ddiff: ddiff = ddiff - isneg elif modiff: modiff = modiff - isneg elif yrdiff: yrdiff = yrdiff - isneg mindiff = emin - smin if not updiff and mindiff < 0: raise ValueError, 'If the date and hour are the same, mindiff must be non-negative' elif mindiff > 0: updiff = 1 ## isneg = mindiff < 0 mindiff = mindiff + isneg * 60 if hrdiff: hrdiff = hrdiff - isneg elif ddiff: ddiff = ddiff - isneg elif modiff: modiff = modiff - isneg elif yrdiff: yrdiff = yrdiff - isneg sdiff = esec - ssec if not updiff and sdiff < 0: raise ValueError, 'If the date, hour, and minute are the same, sdiff must be non-negative' ## isneg = sdiff < 0 sdiff = sdiff + isneg * 60 if mindiff: mindiff = mindiff - isneg elif hrdiff: hrdiff = hrdiff - isneg elif ddiff: ddiff = ddiff - isneg elif modiff: modiff = modiff - isneg elif yrdiff: yrdiff = yrdiff - isneg
return "%sP%sY%sM%sDT%sH%sM%sS" % (sign, yrdiff, modiff, ddiff, hrdiff, mindiff, sdiff)
import re durationRE = re.compile('(-)?P(\d+Y)?(\d+M)?(\d+D)?(T(\d+H)?(\d+M)?(\d+(?:\.\d+)?S)?)?$')
def Add(context, dateTime, duration): """The date:add function returns the result of adding a duration to a dateTime.""" # FIXME: fields in dateTime that are unspecified should be removed from result, # per http://www.w3.org/TR/xmlschema-2/#adding-durations-to-dateTimes dts = Conversions.StringValue(dateTime) dateOnly = dts.find('T') + 1 dateTimeIsLocal = (dts[-1] != 'Z') and dateOnly dateTime = _coerce(dateTime) if dateTime is None: return u'' rawResult = list(dateTime.asPythonTimeTuple()) m = durationRE.match(duration) if m: g = m.groups() # check the duration isn't of the form 'P1Y2M3DT' which isn't allowed if g[4]: for v in g[5:]: if v is not None: break else: raise XsltRuntimeException(ExsltError.ILLEGAL_DURATION_FORMAT, context.currentInstruction, duration) neg = 1 + (-2 * (g[0] == '-')) # will be -1 or +1 for i,j in zip([1,2,3,5,6], range(5)): if g[i] is not None: rawResult[j] += neg * int(g[i][:-1]) if g[7] is not None: rawResult[6] += neg * float(g[7][:-1]) else: raise XsltRuntimeException(ExsltError.ILLEGAL_DURATION_FORMAT, context.currentInstruction, duration) ## No leap seconds. That would be hideous to code, ## and only useful in a very few cases. if rawResult[5] > 59: rawResult[5] = rawResult[5] - 60 rawResult[4] = rawResult[4] + 1 elif rawResult[5] < 0: rawResult[5] = rawResult[5] + 60 rawResult[4] = rawResult[4] - 1 if rawResult[4] > 59: rawResult[4] = rawResult[4] - 60 rawResult[3] = rawResult[3] + 1 elif rawResult[4] < 0: rawResult[4] = rawResult[4] + 60 rawResult[3] = rawResult[3] - 1 if rawResult[3] > 23: rawResult[3] = rawResult[3] - 24 rawResult[2] = rawResult[2] + 1 elif rawResult[3] < 0: rawResult[3] = rawResult[3] + 24 rawResult[2] = rawResult[2] - 1 moLen1 = _moLen(rawResult[0], rawResult[1]) moLen0 = _moLen(rawResult[0], rawResult[1] - 1) if rawResult[2] > moLen1: rawResult[2] = rawResult[2] - moLen1 rawResult[1] = rawResult[1] + 1 elif rawResult[2] < 1: rawResult[2] = rawResult[2] + moLen0 rawResult[1] = rawResult[1] - 1 if rawResult[1] > 12: rawResult[1] = rawResult[1] - 12 rawResult[0] = rawResult[0] + 1 elif rawResult[1] < 1: rawResult[1] = rawResult[1] + 12 rawResult[0] = rawResult[0] - 1 dt = FtTime.FromPythonTimeTuple(rawResult) result = dt.asISO8601DateTime(local=dateTimeIsLocal) return result
def AddDuration(context, duration1, duration2): print "Warning: date:add-duration() not implemented" return ""
def Sum(context, nodeset): print "Warning: date:sum() not implemented" return ""
def Seconds(context, dateOrDuration): dod = Conversions.StringValue(dateOrDuration) if dod[0] == 'P': return SecondsFromDuration(dod) return ""
def SecondsFromDuration(duration): DURATION_PAT = re.compile("P(([0-9]+)Y)?(([0-9][0-9]?)M)?(([0-9][0-9]?)DT)?(([0-9][0-9]?)H)?(([0-9][0-9]?)M)?(([0-9][0-9]?)S)?") m = DURATION_PAT.match(duration) #We only use days, hours, mins and seconds, which are determinate in number of seconds if (m.group(2) and int(m.group(2))) or (m.group(4) and int(m.group(4))): #FIXME: Make a proper exception for this raise Exception("Indeterminate Duration") seconds = (m.group(6) and 3600*24*int(m.group(6)) or 0) + \ (m.group(8) and 3600*int(m.group(8)) or 0) + \ (m.group(10) and 60*int(m.group(10)) or 0) + \ (m.group(12) and int(m.group(12)) or 0) return seconds
def SecondsFromDate(context, date): print "Warning: date:seconds() of a time not implemented" return ""
def Duration(context, seconds): print "Warning: date:duration() not implemented" return ""
## Utility functions for internal use def _coerce(dateTime): if dateTime is None: ## broken because constructor requires argument ##return DateTime() return FtTime.FromPythonTime() elif isinstance(dateTime,FtTime.DT): return dateTime st = Conversions.StringValue(dateTime) try: rt = FtTime.FromISO8601(st) except SyntaxError: rt = None return rt
def _moLen(year, month): ## Returns the number of days in a given month. if month not in range(1,13): yoff, month = divmod(month,12) if month == 0: month = 12 yoff = yoff - 1 year = year + yoff
return (None, 31, 28+calendar.isleap(year), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)[month]
def _strptime(time, format): ## Converts a formatted date-time string to a ## time tuple. Intended to replicate ## time.strptime, which is platform-dependent. if format.find('%') >= 0: ## If the format string contains % characters, ## deem it to be Python syntax. This means you can't ## have any %s in a Java-syntax format string. ## Anyone have a problem with this? return _strptimePython(time, format) else: return _strptimeJava(time, format)
def _strptimePython(time, format): ## Parse a date-time string using Python syntax. pass
def _strptimeJava(time, format): ## Parse a date-time string using Java syntax. pass
def _strptimePython(timestr, format): ## Parse a date-time string using Python syntax. faketime = time.localtime() preresult = { 'year': faketime[0], 'month': faketime[1], 'day': faketime[2], 'hour': faketime[3], 'minute': faketime[4], 'second': faketime[5] } tpos = 0 fpos = 0 while format: if format[0] == '%': fieldmark = format[1] offset = 2 try: if fieldmark == 'd': preresult['day'] = int(timestr[:2]) elif fieldmark == 'H': preresult['hour'] = int(timestr[:2]) elif fieldmark == 'M': preresult['minute'] = int(timestr[:2]) elif fieldmark == 'm': preresult['month'] = int(timestr[:2]) elif fieldmark == 'S': preresult['second'] = int(timestr[:2]) elif fieldmark == 'Y': preresult['year'] = int(timestr[:4]) offset = 4 elif fieldmark in ['A','a','B','b','c','d','I','j', 'p','U','W','w','X','x','y','Z']: ##These directives aren't supported yet pass elif fieldmark == '%' and timestr[0] == '%': pass else: raise ValueError, 'Format mismatch in %s'%format except ValueError: raise ValueError, 'Format mismatch in %s'%format format = format[2:] timestr = timestr[offset:] elif timestr[0] == format[0]: timestr = timestr[1:] format = format[1:] else: raise ValueError, 'Format mismatch in %s'%format if timestr: ## The while loop should consume everything raise ValueError, 'Format mismatch in %s'%format ## The following steps will normalize the time tuple ## (items such as day of week may be inconsistent) t = time.mktime((preresult['year'], preresult['month'], preresult['day'], preresult['hour'], preresult['minute'], preresult['second'], 0, 0, 0)) return time.localtime(t)
import time if hasattr(time,'strptime'): _strptimePython = time.strptime
def _strptimeJava(time, format): ## Parse a date-time string using Java syntax. pass
ExtNamespaces = { EXSL_DATE_TIME_NS : 'date', }
ExtElements = { ## (EXSL_DATE_TIME_NS, 'date-format'): DateFormatElement }
ExtFunctions = { (EXSL_DATE_TIME_NS, 'date-time'): DateTime, (EXSL_DATE_TIME_NS, 'date'): Date, (EXSL_DATE_TIME_NS, 'time'): Time, (EXSL_DATE_TIME_NS, 'year'): Year, (EXSL_DATE_TIME_NS, 'leap-year'): LeapYear, (EXSL_DATE_TIME_NS, 'month-in-year'): MonthInYear, (EXSL_DATE_TIME_NS, 'month-name'): MonthName, (EXSL_DATE_TIME_NS, 'month-abbreviation'): MonthAbbreviation, (EXSL_DATE_TIME_NS, 'week-in-year'): WeekInYear, (EXSL_DATE_TIME_NS, 'day-in-year'): DayInYear, (EXSL_DATE_TIME_NS, 'day-in-month'): DayInMonth, (EXSL_DATE_TIME_NS, 'day-of-week-in-month'): DayOfWeekInMonth, (EXSL_DATE_TIME_NS, 'day-in-week'): DayInWeek, (EXSL_DATE_TIME_NS, 'day-name'): DayName, (EXSL_DATE_TIME_NS, 'day-abbreviation'): DayAbbreviation, (EXSL_DATE_TIME_NS, 'hour-in-day'): HourInDay, (EXSL_DATE_TIME_NS, 'minute-in-hour'): MinuteInHour, (EXSL_DATE_TIME_NS, 'second-in-minute'): SecondInMinute, #(EXSL_DATE_TIME_NS, 'format-date'): FormatDate, #(EXSL_DATE_TIME_NS, 'parse-date'): ParseDate, #(EXSL_DATE_TIME_NS, 'week-in-month'): WeekInMonth, (EXSL_DATE_TIME_NS, 'difference'): Difference, (EXSL_DATE_TIME_NS, 'add'): Add, #(EXSL_DATE_TIME_NS, 'add-duration'): AddDuration, #(EXSL_DATE_TIME_NS, 'sum'): Sum, (EXSL_DATE_TIME_NS, 'seconds'): Seconds, #(EXSL_DATE_TIME_NS, 'duration'): Duration }
|