########################################################################
# $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
    }

