diff options
33 files changed, 4403 insertions, 341 deletions
diff --git a/doc/neps/datetime-proposal.rst b/doc/neps/datetime-proposal.rst new file mode 100644 index 000000000..39e3f7a98 --- /dev/null +++ b/doc/neps/datetime-proposal.rst @@ -0,0 +1,655 @@ +==================================================================== + A proposal for implementing some date/time types in NumPy +==================================================================== + +:Author: Travis Oliphant +:Contact: oliphant@enthought.com +:Date: 2009-06-09 + +Revised only slightly from the third proposal by + +:Author: Francesc Alted i Abad +:Contact: faltet@pytables.com +:Author: Ivan Vilata i Balaguer +:Contact: ivan@selidor.net +:Date: 2008-07-30 + + +Executive summary +================= + +A date/time mark is something very handy to have in many fields where +one has to deal with data sets. While Python has several modules that +define a date/time type (like the integrated ``datetime`` [1]_ or +``mx.DateTime`` [2]_), NumPy has a lack of them. + +We are proposing the addition of date/time types to fill this gap. +The requirements for the proposed types are two-fold: 1) they have +to be fast to operate with and 2) they have to be as compatible as +possible with the existing ``datetime`` module that comes with Python. + + +Types proposed +============== + +It is virtually impossible to come up with a single date/time type +that fills the needs of every use case. As a result, we propose two +general date-time types: 1) ``timedelta64`` -- a relative time and 2) +``datetime64`` -- an absolute time. + +Each of these times are represented internally as 64-bit signed +integers that refer to a particular unit (hour, minute, microsecond, +etc.). There are several pre-defined units as well as the ability to +create rational multiples of these units. A representation is also +supported such that the stored date-time integer can encode both the +number of a particular unit as well as a number of sequential events +tracked for each unit. + +The ``datetime64`` represents an absolute time. Internally it is +represented as the number of time units between the intended time and +the epoch (12:00am on January 1, 1970 --- POSIX time including its +lack of leap seconds). + +.. Important: The information that provides meaning to the integers stored in + the date/time dtypes are stored as metadata which is a new feature to be + added to the dtype object. + +Time units +=========== + +The 64-bit integer time can represent several different basic units as +well as derived units. The basic units are listed in the following +table: + +======== ================ ======================= ========================== + Time unit Time span Time span (years) +------------------------- ----------------------- -------------------------- + Code Meaning Relative Time Absolute Time +======== ================ ======================= ========================== + Y year +- 9.2e18 years [9.2e18 BC, 9.2e18 AC] + M month +- 7.6e17 years [7.6e17 BC, 7.6e17 AC] + W week +- 1.7e17 years [1.7e17 BC, 1.7e17 AC] + B business day +- 3.5e16 years [3.5e16 BC, 3.5e16 AC] + D day +- 2.5e16 years [2.5e16 BC, 2.5e16 AC] + h hour +- 1.0e15 years [1.0e15 BC, 1.0e15 AC] + m minute +- 1.7e13 years [1.7e13 BC, 1.7e13 AC] + s second +- 2.9e12 years [ 2.9e9 BC, 2.9e9 AC] + ms millisecond +- 2.9e9 years [ 2.9e6 BC, 2.9e6 AC] + us microsecond +- 2.9e6 years [290301 BC, 294241 AC] + ns nanosecond +- 292 years [ 1678 AC, 2262 AC] + ps picosecond +- 106 days [ 1969 AC, 1970 AC] + fs femtosecond +- 2.6 hours [ 1969 AC, 1970 AC] + as attosecond +- 9.2 seconds [ 1969 AC, 1970 AC] +======== ================ ======================= ========================== + +A time unit is specified by a string consisting of a base-type given in +the above table + +Besides these basic code units, the user can create derived units +consisting of multiples of any basic unit: 100ns, 3M, 15m, etc. + +A limited number of divisions of any basic unit can be used to create multiples +of a higher-resolution unit provided the divisor can be divided evenly into +the number of higher-resolution units available. +For example: Y/4 is just short-hand for -> (12M)/4 -> 3M and Y/4 will be represented + after creation as 3M +The first lower unit found to have an even divisor will be chosen + (up to 3 lower units). The following standardized definitions are used + in this specific case to find acceptable divisors + + +Y - 12M, 52W, 365D +M - 4W, 30D, 720h +W - 5B,7D, 168h, 10080m +B - 24h, 1440m, 86400s +D - 24h, 1440m, 86400s +h - 60m, 3600s +m - 60s, 60000ms + +s, ms, us, ns, ps, fs (use 1000 and 1000000 of the next two + available lower units respectively). + + +Finally, a date-time data-type can be created with support for tracking +sequential events within a basic unit: [D]//100, [Y]//4 (notice the +required brackets). These ``modulo`` event units provide the following +interpretation to the date-time integer: + + * the divisor is the number of events in each period + * the (integer) quotient is the integer number representing the base units + * the remainder is the particular event in the period. + +Modulo event-units can be combined with any derived units, but brackets +are required. Thus [100ns]//50 which allows recording 50 events for +every 100ns so that 0 represents the first event in the first 100ns +tick, 1 represents the second event in the first 100ns tick, while 50 +represents the first event in the second 100ns tick, and 51 represents +the second event in the second 100ns tick. + +To fully specify a date-time type, the time unit string must be +combined with either the string for a datetime64 ('M8') or a +timedelta64 ('m8') using brackets '[]'. Therefore, a fully-specified +string representing a date-time dtype is 'M8[Y]' or (for a more +complicated example) 'M8[7s/9]//5'. + +If a time unit is not specified, then it defaults to [us]. Thus 'M8' is +equivalent to 'M8[us]' (except when modulo event-units are desired -- +i.e. you cannot specify 'M8[us]//5' as 'M8//5' + +``datetime64`` +============== + +This dtype represents a time that is absolute (i.e. not relative). It +is implemented internally as an ``int64`` type. The integer represents +units from the internal POSIX epoch (see [3]_). Like POSIX, the +representation of a date doesn't take leap seconds into account. + +In time unit *conversions* and time *representations* (but not in other +time computations), the value -2**63 (0x8000000000000000) is interpreted +as an invalid or unknown date, *Not a Time* or *NaT*. See the section +on time unit conversions for more information. + +The value of an absolute date is thus *an integer number of units of +the chosen time unit* passed since the epoch. If the integer is a +negative number, then the magnitude of the integer represents the +number of units prior to the epoch. When working with business days, +Saturdays and Sundays are simply ignored from the count (i.e. day 3 in +business days is not Saturday 1970-01-03, but Monday 1970-01-05). + +Building a ``datetime64`` dtype +-------------------------------- + +The proposed ways to specify the time unit in the dtype constructor are: + +Using the long string notation:: + + dtype('datetime64[us]') + +Using the short string notation:: + + dtype('M8[us]') + +If a time unit is not specified, then it defaults to [us]. Thus 'M8' +is equivalent to 'M8[us]'. + + +Setting and getting values +--------------------------- + +The objects with this dtype can be set in a series of ways:: + + t = numpy.ones(3, dtype='M8[s]') + t[0] = 1199164176 # assign to July 30th, 2008 at 17:31:00 + t[1] = datetime.datetime(2008, 7, 30, 17, 31, 01) # with datetime module + t[2] = '2008-07-30T17:31:02' # with ISO 8601 + +And can be get in different ways too:: + + str(t[0]) --> 2008-07-30T17:31:00 + repr(t[1]) --> datetime64(1199164177, 's') + str(t[0].item()) --> 2008-07-30 17:31:00 # datetime module object + repr(t[0].item()) --> datetime.datetime(2008, 7, 30, 17, 31) # idem + str(t) --> [2008-07-30T17:31:00 2008-07-30T17:31:01 2008-07-30T17:31:02] + repr(t) --> array([1199164176, 1199164177, 1199164178], + dtype='datetime64[s]') + +Comparisons +------------ + +The comparisons will be supported too:: + + numpy.array(['1980'], 'M8[Y]') == numpy.array(['1979'], 'M8[Y]') + --> [False] + +including applying broadcasting:: + + numpy.array(['1979', '1980'], 'M8[Y]') == numpy.datetime64('1980', 'Y') + --> [False, True] + +The following should also work:: + + numpy.array(['1979', '1980'], 'M8[Y]') == '1980-01-01' + --> [False, True] + +because the right hand expression can be broadcasted into an array of 2 +elements of dtype 'M8[Y]'. + +Compatibility issues +--------------------- + +This will be fully compatible with the ``datetime`` class of the +``datetime`` module of Python only when using a time unit of +microseconds. For other time units, the conversion process will lose +precision or will overflow as needed. The conversion from/to a +``datetime`` object doesn't take leap seconds into account. + + +``timedelta64`` +=============== + +It represents a time that is relative (i.e. not absolute). It is +implemented internally as an ``int64`` type. + +In time unit *conversions* and time *representations* (but not in other +time computations), the value -2**63 (0x8000000000000000) is interpreted +as an invalid or unknown time, *Not a Time* or *NaT*. See the section +on time unit conversions for more information. + +The value of a time delta is *an integer number of units of the +chosen time unit*. + +Building a ``timedelta64`` dtype +--------------------------------- + +The proposed ways to specify the time unit in the dtype constructor are: + +Using the long string notation:: + + dtype('timedelta64[us]') + +Using the short string notation:: + + dtype('m8[us]') + +If a time unit is not specified, then a default of [us] is assumed. +Thus 'm8' and 'm8[us]' are equivalent. + +Setting and getting values +--------------------------- + +The objects with this dtype can be set in a series of ways:: + + t = numpy.ones(3, dtype='m8[ms]') + t[0] = 12 # assign to 12 ms + t[1] = datetime.timedelta(0, 0, 13000) # 13 ms + t[2] = '0:00:00.014' # 14 ms + +And can be get in different ways too:: + + str(t[0]) --> 0:00:00.012 + repr(t[1]) --> timedelta64(13, 'ms') + str(t[0].item()) --> 0:00:00.012000 # datetime module object + repr(t[0].item()) --> datetime.timedelta(0, 0, 12000) # idem + str(t) --> [0:00:00.012 0:00:00.014 0:00:00.014] + repr(t) --> array([12, 13, 14], dtype="timedelta64[ms]") + +Comparisons +------------ + +The comparisons will be supported too:: + + numpy.array([12, 13, 14], 'm8[ms]') == numpy.array([12, 13, 13], 'm8[ms]') + --> [True, True, False] + +or by applying broadcasting:: + + numpy.array([12, 13, 14], 'm8[ms]') == numpy.timedelta64(13, 'ms') + --> [False, True, False] + +The following should work too:: + + numpy.array([12, 13, 14], 'm8[ms]') == '0:00:00.012' + --> [True, False, False] + +because the right hand expression can be broadcasted into an array of 3 +elements of dtype 'm8[ms]'. + +Compatibility issues +--------------------- + +This will be fully compatible with the ``timedelta`` class of the +``datetime`` module of Python only when using a time unit of +microseconds. For other units, the conversion process will loose +precision or will overflow as needed. + + +Examples of use +=============== + +Here is an example of use for the ``datetime64``:: + + In [5]: numpy.datetime64(42, 'us') + Out[5]: datetime64(42, 'us') + + In [6]: print numpy.datetime64(42, 'us') + 1970-01-01T00:00:00.000042 # representation in ISO 8601 format + + In [7]: print numpy.datetime64(367.7, 'D') # decimal part is lost + 1971-01-02 # still ISO 8601 format + + In [8]: numpy.datetime('2008-07-18T12:23:18', 'm') # from ISO 8601 + Out[8]: datetime64(20273063, 'm') + + In [9]: print numpy.datetime('2008-07-18T12:23:18', 'm') + Out[9]: 2008-07-18T12:23 + + In [10]: t = numpy.zeros(5, dtype="datetime64[ms]") + + In [11]: t[0] = datetime.datetime.now() # setter in action + + In [12]: print t + [2008-07-16T13:39:25.315 1970-01-01T00:00:00.000 + 1970-01-01T00:00:00.000 1970-01-01T00:00:00.000 + 1970-01-01T00:00:00.000] + + In [13]: repr(t) + Out[13]: array([267859210457, 0, 0, 0, 0], dtype="datetime64[ms]") + + In [14]: t[0].item() # getter in action + Out[14]: datetime.datetime(2008, 7, 16, 13, 39, 25, 315000) + + In [15]: print t.dtype + dtype('datetime64[ms]') + +And here it goes an example of use for the ``timedelta64``:: + + In [5]: numpy.timedelta64(10, 'us') + Out[5]: timedelta64(10, 'us') + + In [6]: print numpy.timedelta64(10, 'us') + 0:00:00.000010 + + In [7]: print numpy.timedelta64(3600.2, 'm') # decimal part is lost + 2 days, 12:00 + + In [8]: t1 = numpy.zeros(5, dtype="datetime64[ms]") + + In [9]: t2 = numpy.ones(5, dtype="datetime64[ms]") + + In [10]: t = t2 - t1 + + In [11]: t[0] = datetime.timedelta(0, 24) # setter in action + + In [12]: print t + [0:00:24.000 0:00:01.000 0:00:01.000 0:00:01.000 0:00:01.000] + + In [13]: print repr(t) + Out[13]: array([24000, 1, 1, 1, 1], dtype="timedelta64[ms]") + + In [14]: t[0].item() # getter in action + Out[14]: datetime.timedelta(0, 24) + + In [15]: print t.dtype + dtype('timedelta64[s]') + + +Operating with date/time arrays +=============================== + +``datetime64`` vs ``datetime64`` +-------------------------------- + +The only arithmetic operation allowed between absolute dates is +subtraction:: + + In [10]: numpy.ones(3, "M8[s]") - numpy.zeros(3, "M8[s]") + Out[10]: array([1, 1, 1], dtype=timedelta64[s]) + +But not other operations:: + + In [11]: numpy.ones(3, "M8[s]") + numpy.zeros(3, "M8[s]") + TypeError: unsupported operand type(s) for +: 'numpy.ndarray' and 'numpy.ndarray' + +Comparisons between absolute dates are allowed. + +Casting rules +~~~~~~~~~~~~~ + +When operating (basically, only the subtraction will be allowed) two +absolute times with different unit times, the outcome would be to raise +an exception. This is because the ranges and time-spans of the different +time units can be very different, and it is not clear at all what time +unit will be preferred for the user. For example, this should be +allowed:: + + >>> numpy.ones(3, dtype="M8[Y]") - numpy.zeros(3, dtype="M8[Y]") + array([1, 1, 1], dtype="timedelta64[Y]") + +But the next should not:: + + >>> numpy.ones(3, dtype="M8[Y]") - numpy.zeros(3, dtype="M8[ns]") + raise numpy.IncompatibleUnitError # what unit to choose? + + +``datetime64`` vs ``timedelta64`` +--------------------------------- + +It will be possible to add and subtract relative times from absolute +dates:: + + In [10]: numpy.zeros(5, "M8[Y]") + numpy.ones(5, "m8[Y]") + Out[10]: array([1971, 1971, 1971, 1971, 1971], dtype=datetime64[Y]) + + In [11]: numpy.ones(5, "M8[Y]") - 2 * numpy.ones(5, "m8[Y]") + Out[11]: array([1969, 1969, 1969, 1969, 1969], dtype=datetime64[Y]) + +But not other operations:: + + In [12]: numpy.ones(5, "M8[Y]") * numpy.ones(5, "m8[Y]") + TypeError: unsupported operand type(s) for *: 'numpy.ndarray' and 'numpy.ndarray' + +Casting rules +~~~~~~~~~~~~~ + +In this case the absolute time should have priority for determining the +time unit of the outcome. That would represent what the people wants to +do most of the times. For example, this would allow to do:: + + >>> series = numpy.array(['1970-01-01', '1970-02-01', '1970-09-01'], + dtype='datetime64[D]') + >>> series2 = series + numpy.timedelta(1, 'Y') # Add 2 relative years + >>> series2 + array(['1972-01-01', '1972-02-01', '1972-09-01'], + dtype='datetime64[D]') # the 'D'ay time unit has been chosen + + +``timedelta64`` vs ``timedelta64`` +---------------------------------- + +Finally, it will be possible to operate with relative times as if they +were regular int64 dtypes *as long as* the result can be converted back +into a ``timedelta64``:: + + In [10]: numpy.ones(3, 'm8[us]') + Out[10]: array([1, 1, 1], dtype="timedelta64[us]") + + In [11]: (numpy.ones(3, 'm8[M]') + 2) ** 3 + Out[11]: array([27, 27, 27], dtype="timedelta64[M]") + +But:: + + In [12]: numpy.ones(5, 'm8') + 1j + TypeError: the result cannot be converted into a ``timedelta64`` + +Casting rules +~~~~~~~~~~~~~ + +When combining two ``timedelta64`` dtypes with different time units the +outcome will be the shorter of both ("keep the precision" rule). For +example:: + + In [10]: numpy.ones(3, 'm8[s]') + numpy.ones(3, 'm8[m]') + Out[10]: array([61, 61, 61], dtype="timedelta64[s]") + +However, due to the impossibility to know the exact duration of a +relative year or a relative month, when these time units appear in one +of the operands, the operation will not be allowed:: + + In [11]: numpy.ones(3, 'm8[Y]') + numpy.ones(3, 'm8[D]') + raise numpy.IncompatibleUnitError # how to convert relative years to days? + +In order to being able to perform the above operation a new NumPy +function, called ``change_timeunit`` is proposed. Its signature will +be:: + + change_timeunit(time_object, new_unit, reference) + +where 'time_object' is the time object whose unit is to be changed, +'new_unit' is the desired new time unit, and 'reference' is an absolute +date (NumPy datetime64 scalar) that will be used to allow the conversion +of relative times in case of using time units with an uncertain number +of smaller time units (relative years or months cannot be expressed in +days). + +With this, the above operation can be done as follows:: + + In [10]: t_years = numpy.ones(3, 'm8[Y]') + + In [11]: t_days = numpy.change_timeunit(t_years, 'D', '2001-01-01') + + In [12]: t_days + numpy.ones(3, 'm8[D]') + Out[12]: array([366, 366, 366], dtype="timedelta64[D]") + + +dtype vs time units conversions +=============================== + +For changing the date/time dtype of an existing array, we propose to use +the ``.astype()`` method. This will be mainly useful for changing time +units. + +For example, for absolute dates:: + + In[10]: t1 = numpy.zeros(5, dtype="datetime64[s]") + + In[11]: print t1 + [1970-01-01T00:00:00 1970-01-01T00:00:00 1970-01-01T00:00:00 + 1970-01-01T00:00:00 1970-01-01T00:00:00] + + In[12]: print t1.astype('datetime64[D]') + [1970-01-01 1970-01-01 1970-01-01 1970-01-01 1970-01-01] + +For relative times:: + + In[10]: t1 = numpy.ones(5, dtype="timedelta64[s]") + + In[11]: print t1 + [1 1 1 1 1] + + In[12]: print t1.astype('timedelta64[ms]') + [1000 1000 1000 1000 1000] + +Changing directly from/to relative to/from absolute dtypes will not be +supported:: + + In[13]: numpy.zeros(5, dtype="datetime64[s]").astype('timedelta64') + TypeError: data type cannot be converted to the desired type + +Business days have the peculiarity that they do not cover a continuous +line of time (they have gaps at weekends). Thus, when converting from +any ordinary time to business days, it can happen that the original time +is not representable. In that case, the result of the conversion is +*Not a Time* (*NaT*):: + + In[10]: t1 = numpy.arange(5, dtype="datetime64[D]") + + In[11]: print t1 + [1970-01-01 1970-01-02 1970-01-03 1970-01-04 1970-01-05] + + In[12]: t2 = t1.astype("datetime64[B]") + + In[13]: print t2 # 1970 begins in a Thursday + [1970-01-01 1970-01-02 NaT NaT 1970-01-05] + +When converting back to ordinary days, NaT values are left untouched +(this happens in all time unit conversions):: + + In[14]: t3 = t2.astype("datetime64[D]") + + In[13]: print t3 + [1970-01-01 1970-01-02 NaT NaT 1970-01-05] + +Necessary changes to NumPy +========================== + +In order to facilitate the addition of the date-time data-types a few changes +to NumPy were made: + +Addition of metadata to dtypes +------------------------------ + +All data-types now have a metadata dictionary. It can be set using the +metadata keyword during construction of the object. + +Date-time data-types will place the word "__frequency__" in the meta-data +dictionary containing a 4-tuple with the following parameters. + +(basic unit string (str), + number of multiples (int), + number of sub-divisions (int), + number of events (int)). + +Simple time units like 'D' for days will thus be specified by ('D', 1, 1, 1) in +the "__frequency__" key of the metadata. More complicated time units (like '[2W/5]//50') will be indicated by ('D', 2, 5, 50). + +The "__frequency__" key is reserved for metadata and cannot be set with a +dtype constructor. + + +Ufunc interface extension +------------------------- + +ufuncs that have datetime and timedelta arguments can use the Python API +during ufunc calls (to raise errors). + +There is a new ufunc C-API call to set the data for a particular +function pointer (for a particular set of data-types) to be the list of arrays +passed in to the ufunc. + +Array Intervace Extensions +-------------------------- + +The array interface is extended to both handle datetime and timedelta +typestr (including extended notation). + +In addition, the typestr element of the __array_interface__ can be a tuple +as long as the version string is 4. The tuple is +('typestr', metadata dictionary). + +This extension to the typestr concept extends to the descr portion of +the __array_interface__. Thus, the second element in the tuple of a +list of tuples describing a data-format can itself be a tuple of +('typestr', metadata dictionary). + + +Final considerations +==================== + +Why the ``origin`` metadata disappeared +--------------------------------------- + +During the discussion of the date/time dtypes in the NumPy list, the +idea of having an ``origin`` metadata that complemented the definition +of the absolute ``datetime64`` was initially found to be useful. + +However, after thinking more about this, we found that the combination +of an absolute ``datetime64`` with a relative ``timedelta64`` does offer +the same functionality while removing the need for the additional +``origin`` metadata. This is why we have removed it from this proposal. + +Operations with mixed time units +-------------------------------- + +Whenever an operation between two time values of the same dtype with the +same unit is accepted, the same operation with time values of different +units should be possible (e.g. adding a time delta in seconds and one in +microseconds), resulting in an adequate time unit. The exact semantics +of this kind of operations is defined int the "Casting rules" +subsections of the "Operating with date/time arrays" section. + +Due to the peculiarities of business days, it is most probable that +operations mixing business days with other time units will not be +allowed. + + +.. [1] http://docs.python.org/lib/module-datetime.html +.. [2] http://www.egenix.com/products/python/mxBase/mxDateTime +.. [3] http://en.wikipedia.org/wiki/Unix_time + + +.. Local Variables: +.. mode: rst +.. coding: utf-8 +.. fill-column: 72 +.. End: + diff --git a/numpy/core/_internal.py b/numpy/core/_internal.py index 7d5c3a49e..b89c94d9b 100644 --- a/numpy/core/_internal.py +++ b/numpy/core/_internal.py @@ -3,6 +3,7 @@ import re import sys +from _mx_datetime_parser import * if (sys.byteorder == 'little'): _nbo = '<' @@ -77,11 +78,18 @@ def _usefields(adict, align): # a simple typestring def _array_descr(descriptor): + from multiarray import METADATA_DTSTR fields = descriptor.fields if fields is None: subdtype = descriptor.subdtype if subdtype is None: - return descriptor.str + if descriptor.metadata is None: + return descriptor.str + else: + new = descriptor.metadata.copy() + # Eliminate any key related to internal implementation + _ = new.pop(METADATA_DTSTR, None) + return (descriptor.str, new) else: return (_array_descr(subdtype[0]), subdtype[1]) @@ -156,6 +164,34 @@ def _split(input): return newlist +format_datetime = re.compile(r"""(?P<typecode>M8|m8|datetime64|timedelta64) + ([[] + ((?P<num>\d+)? + (?P<baseunit>Y|M|W|B|D|h|m|s|ms|us|ns|ps|fs|as) + (/(?P<den>\d+))? + []]) + (//(?P<events>\d+))?)?""", re.X) +# Return (baseunit, num, den, events), datetime +# from date-time string +def _datetimestring(astr): + res = format_datetime.match(astr) + if res is None: + raise ValueError, "Incorrect date-time string." + typecode = res.group('typecode') + datetime = (typecode == 'M8' or typecode == 'datetime64') + defaults = ['us', 1, 1, 1] + names = ['baseunit', 'num', 'den', 'events'] + func = [str, int, int, int] + dt_tuple = [] + for i, name in enumerate(names): + value = res.group(name) + if value: + dt_tuple.append(func[i](value)) + else: + dt_tuple.append(defaults[i]) + + return tuple(dt_tuple), datetime + format_re = re.compile(r'(?P<order1>[<>|=]?)(?P<repeats> *[(]?[ ,0-9]*[)]? *)(?P<order2>[<>|=]?)(?P<dtype>[A-Za-z0-9.]*)') # astr is a string (perhaps comma separated) diff --git a/numpy/core/_mx_datetime_parser.py b/numpy/core/_mx_datetime_parser.py new file mode 100644 index 000000000..f1b330f00 --- /dev/null +++ b/numpy/core/_mx_datetime_parser.py @@ -0,0 +1,962 @@ +#-*- coding: latin-1 -*- +""" +Date/Time string parsing module. + +This code is a slightly modified version of Parser.py found in mx.DateTime +version 3.0.0 + +As such, it is subject to the terms of the eGenix public license version 1.1.0. + +FIXME: Add license.txt to NumPy +""" + +__all__ = ['date_from_string', 'datetime_from_string'] + +import types +import re +import datetime as dt + +class RangeError(Exception): pass + +# Enable to produce debugging output +_debug = 0 + +# REs for matching date and time parts in a string; These REs +# parse a superset of ARPA, ISO, American and European style dates. +# Timezones are supported via the Timezone submodule. + +_year = '(?P<year>-?\d+\d(?!:))' +_fullyear = '(?P<year>-?\d+\d\d(?!:))' +_year_epoch = '(?:' + _year + '(?P<epoch> *[ABCDE\.]+)?)' +_fullyear_epoch = '(?:' + _fullyear + '(?P<epoch> *[ABCDE\.]+)?)' +_relyear = '(?:\((?P<relyear>[-+]?\d+)\))' + +_month = '(?P<month>\d?\d(?!:))' +_fullmonth = '(?P<month>\d\d(?!:))' +_litmonth = ('(?P<litmonth>' + 'jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec|' + 'mär|mae|mrz|mai|okt|dez|' + 'fev|avr|juin|juil|aou|aoû|déc|' + 'ene|abr|ago|dic|' + 'out' + ')[a-z,\.;]*') +litmonthtable = { + # English + 'jan':1, 'feb':2, 'mar':3, 'apr':4, 'may':5, 'jun':6, + 'jul':7, 'aug':8, 'sep':9, 'oct':10, 'nov':11, 'dec':12, + # German + 'mär':3, 'mae':3, 'mrz':3, 'mai':5, 'okt':10, 'dez':12, + # French + 'fev':2, 'avr':4, 'juin':6, 'juil':7, 'aou':8, 'aoû':8, + 'déc':12, + # Spanish + 'ene':1, 'abr':4, 'ago':8, 'dic':12, + # Portuguese + 'out':10, + } +_relmonth = '(?:\((?P<relmonth>[-+]?\d+)\))' + +_day = '(?P<day>\d?\d(?!:))' +_usday = '(?P<day>\d?\d(?!:))(?:st|nd|rd|th|[,\.;])?' +_fullday = '(?P<day>\d\d(?!:))' +_litday = ('(?P<litday>' + 'mon|tue|wed|thu|fri|sat|sun|' + 'die|mit|don|fre|sam|son|' + 'lun|mar|mer|jeu|ven|sam|dim|' + 'mie|jue|vie|sab|dom|' + 'pri|seg|ter|cua|qui' + ')[a-z]*') +litdaytable = { + # English + 'mon':0, 'tue':1, 'wed':2, 'thu':3, 'fri':4, 'sat':5, 'sun':6, + # German + 'die':1, 'mit':2, 'don':3, 'fre':4, 'sam':5, 'son':6, + # French + 'lun':0, 'mar':1, 'mer':2, 'jeu':3, 'ven':4, 'sam':5, 'dim':6, + # Spanish + 'mie':2, 'jue':3, 'vie':4, 'sab':5, 'dom':6, + # Portuguese + 'pri':0, 'seg':1, 'ter':2, 'cua':3, 'qui':4, + } +_relday = '(?:\((?P<relday>[-+]?\d+)\))' + +_hour = '(?P<hour>[012]?\d)' +_minute = '(?P<minute>[0-6]\d)' +_second = '(?P<second>[0-6]\d(?:[.,]\d+)?)' + +_days = '(?P<days>\d*\d(?:[.,]\d+)?)' +_hours = '(?P<hours>\d*\d(?:[.,]\d+)?)' +_minutes = '(?P<minutes>\d*\d(?:[.,]\d+)?)' +_seconds = '(?P<seconds>\d*\d(?:[.,]\d+)?)' + +_reldays = '(?:\((?P<reldays>[-+]?\d+(?:[.,]\d+)?)\))' +_relhours = '(?:\((?P<relhours>[-+]?\d+(?:[.,]\d+)?)\))' +_relminutes = '(?:\((?P<relminutes>[-+]?\d+(?:[.,]\d+)?)\))' +_relseconds = '(?:\((?P<relseconds>[-+]?\d+(?:[.,]\d+)?)\))' + +_sign = '(?:(?P<sign>[-+]) *)' +_week = 'W(?P<week>\d?\d)' +_zone = '(?P<zone>[A-Z]+|[+-]\d\d?:?(?:\d\d)?)' +_ampm = '(?P<ampm>[ap][m.]+)' + +_time = (_hour + ':' + _minute + '(?::' + _second + '|[^:]|$) *' + + _ampm + '? *' + _zone + '?') +_isotime = _hour + ':?' + _minute + ':?' + _second + '? *' + _zone + '?' + +_yeardate = _year +_weekdate = _year + '-?(?:' + _week + '-?' + _day + '?)?' +_eurodate = _day + '\.' + _month + '\.' + _year_epoch + '?' +_usdate = _month + '/' + _day + '(?:/' + _year_epoch + '|[^/]|$)' +_altusdate = _month + '-' + _day + '-' + _fullyear_epoch +_isodate = _year + '-' + _month + '-?' + _day + '?(?!:)' +_altisodate = _year + _fullmonth + _fullday + '(?!:)' +_usisodate = _fullyear + '/' + _fullmonth + '/' + _fullday +_litdate = ('(?:'+ _litday + ',? )? *' + + _usday + ' *' + + '[- ] *(?:' + _litmonth + '|'+ _month +') *[- ] *' + + _year_epoch + '?') +_altlitdate = ('(?:'+ _litday + ',? )? *' + + _litmonth + '[ ,.a-z]+' + + _usday + + '(?:[ a-z]+' + _year_epoch + ')?') +_eurlitdate = ('(?:'+ _litday + ',?[ a-z]+)? *' + + '(?:'+ _usday + '[ a-z]+)? *' + + _litmonth + + '(?:[ ,.a-z]+' + _year_epoch + ')?') + +_relany = '[*%?a-zA-Z]+' + +_relisodate = ('(?:(?:' + _relany + '|' + _year + '|' + _relyear + ')-' + + '(?:' + _relany + '|' + _month + '|' + _relmonth + ')-' + + '(?:' + _relany + '|' + _day + '|' + _relday + '))') + +_asctime = ('(?:'+ _litday + ',? )? *' + + _usday + ' *' + + '[- ] *(?:' + _litmonth + '|'+ _month +') *[- ]' + + '(?:[0-9: ]+)' + + _year_epoch + '?') + +_relisotime = ('(?:(?:' + _relany + '|' + _hour + '|' + _relhours + '):' + + '(?:' + _relany + '|' + _minute + '|' + _relminutes + ')' + + '(?::(?:' + _relany + '|' + _second + '|' + _relseconds + '))?)') + +_isodelta1 = (_sign + '?' + + _days + ':' + _hours + ':' + _minutes + ':' + _seconds) +_isodelta2 = (_sign + '?' + + _hours + ':' + _minutes + ':' + _seconds) +_isodelta3 = (_sign + '?' + + _hours + ':' + _minutes) +_litdelta = (_sign + '?' + + '(?:' + _days + ' *d[a-z]*[,; ]*)?' + + '(?:' + _hours + ' *h[a-z]*[,; ]*)?' + + '(?:' + _minutes + ' *m[a-z]*[,; ]*)?' + + '(?:' + _seconds + ' *s[a-z]*[,; ]*)?') +_litdelta2 = (_sign + '?' + + '(?:' + _days + ' *d[a-z]*[,; ]*)?' + + _hours + ':' + _minutes + '(?::' + _seconds + ')?') + +_timeRE = re.compile(_time, re.I) +_isotimeRE = re.compile(_isotime, re.I) +_isodateRE = re.compile(_isodate, re.I) +_altisodateRE = re.compile(_altisodate, re.I) +_usisodateRE = re.compile(_usisodate, re.I) +_yeardateRE = re.compile(_yeardate, re.I) +_eurodateRE = re.compile(_eurodate, re.I) +_usdateRE = re.compile(_usdate, re.I) +_altusdateRE = re.compile(_altusdate, re.I) +_litdateRE = re.compile(_litdate, re.I) +_altlitdateRE = re.compile(_altlitdate, re.I) +_eurlitdateRE = re.compile(_eurlitdate, re.I) +_relisodateRE = re.compile(_relisodate, re.I) +_asctimeRE = re.compile(_asctime, re.I) +_isodelta1RE = re.compile(_isodelta1) +_isodelta2RE = re.compile(_isodelta2) +_isodelta3RE = re.compile(_isodelta3) +_litdeltaRE = re.compile(_litdelta) +_litdelta2RE = re.compile(_litdelta2) +_relisotimeRE = re.compile(_relisotime, re.I) + +# Available date parsers +_date_formats = ('euro', + 'usiso', 'us', 'altus', + 'iso', 'altiso', + 'lit', 'altlit', 'eurlit', + 'year', 'unknown') + +# Available time parsers +_time_formats = ('standard', + 'iso', + 'unknown') + +_zoneoffset = ('(?:' + '(?P<zonesign>[+-])?' + '(?P<hours>\d\d?)' + ':?' + '(?P<minutes>\d\d)?' + '(?P<extra>\d+)?' + ')' + ) + +_zoneoffsetRE = re.compile(_zoneoffset) + +_zonetable = { + # Timezone abbreviations + # Std Summer + + # Standards + 'UT':0, + 'UTC':0, + 'GMT':0, + + # A few common timezone abbreviations + 'CET':1, 'CEST':2, 'CETDST':2, # Central European + 'MET':1, 'MEST':2, 'METDST':2, # Mean European + 'MEZ':1, 'MESZ':2, # Mitteleuropäische Zeit + 'EET':2, 'EEST':3, 'EETDST':3, # Eastern Europe + 'WET':0, 'WEST':1, 'WETDST':1, # Western Europe + 'MSK':3, 'MSD':4, # Moscow + 'IST':5.5, # India + 'JST':9, # Japan + 'KST':9, # Korea + 'HKT':8, # Hong Kong + + # US time zones + 'AST':-4, 'ADT':-3, # Atlantic + 'EST':-5, 'EDT':-4, # Eastern + 'CST':-6, 'CDT':-5, # Central + 'MST':-7, 'MDT':-6, # Midwestern + 'PST':-8, 'PDT':-7, # Pacific + + # Australian time zones + 'CAST':9.5, 'CADT':10.5, # Central + 'EAST':10, 'EADT':11, # Eastern + 'WAST':8, 'WADT':9, # Western + 'SAST':9.5, 'SADT':10.5, # Southern + + # US military time zones + 'Z': 0, + 'A': 1, + 'B': 2, + 'C': 3, + 'D': 4, + 'E': 5, + 'F': 6, + 'G': 7, + 'H': 8, + 'I': 9, + 'K': 10, + 'L': 11, + 'M': 12, + 'N':-1, + 'O':-2, + 'P':-3, + 'Q':-4, + 'R':-5, + 'S':-6, + 'T':-7, + 'U':-8, + 'V':-9, + 'W':-10, + 'X':-11, + 'Y':-12 + } + + +def utc_offset(zone): + """ utc_offset(zonestring) + + Return the UTC time zone offset in minutes. + + zone must be string and can either be given as +-HH:MM, + +-HHMM, +-HH numeric offset or as time zone + abbreviation. Daylight saving time must be encoded into the + zone offset. + + Timezone abbreviations are treated case-insensitive. + + """ + if not zone: + return 0 + uzone = zone.upper() + if uzone in _zonetable: + return _zonetable[uzone]*60 + offset = _zoneoffsetRE.match(zone) + if not offset: + raise ValueError,'wrong format or unkown time zone: "%s"' % zone + zonesign,hours,minutes,extra = offset.groups() + if extra: + raise ValueError,'illegal time zone offset: "%s"' % zone + offset = int(hours or 0) * 60 + int(minutes or 0) + if zonesign == '-': + offset = -offset + return offset + +def add_century(year): + + """ Sliding window approach to the Y2K problem: adds a suitable + century to the given year and returns it as integer. + + The window used depends on the current year. If adding the current + century to the given year gives a year within the range + current_year-70...current_year+30 [both inclusive], then the + current century is added. Otherwise the century (current + 1 or + - 1) producing the least difference is chosen. + + """ + + current_year=dt.datetime.now().year + current_century=(dt.datetime.now().year / 100) * 100 + + if year > 99: + # Take it as-is + return year + year = year + current_century + diff = year - current_year + if diff >= -70 and diff <= 30: + return year + elif diff < -70: + return year + 100 + else: + return year - 100 + + +def _parse_date(text): + """ + Parses the date part given in text and returns a tuple + (text,day,month,year,style) with the following meanings: + + * text gives the original text without the date part + + * day,month,year give the parsed date + + * style gives information about which parser was successful: + 'euro' - the European date parser + 'us' - the US date parser + 'altus' - the alternative US date parser (with '-' instead of '/') + 'iso' - the ISO date parser + 'altiso' - the alternative ISO date parser (without '-') + 'usiso' - US style ISO date parser (yyyy/mm/dd) + 'lit' - the US literal date parser + 'altlit' - the alternative US literal date parser + 'eurlit' - the Eurpean literal date parser + 'unknown' - no date part was found, defaultdate was used + + Formats may be set to a tuple of style strings specifying which of the above + parsers to use and in which order to try them. + Default is to try all of them in the above order. + + ``defaultdate`` provides the defaults to use in case no date part is found. + Most other parsers default to the current year January 1 if some of these + date parts are missing. + + If ``'unknown'`` is not given in formats and the date cannot be parsed, + a :exc:`ValueError` is raised. + + """ + match = None + style = '' + + formats = _date_formats + + us_formats=('us', 'altus') + iso_formats=('iso', 'altiso', 'usiso') + + now=dt.datetime.now + + # Apply parsers in the order given in formats + for format in formats: + + if format == 'euro': + # European style date + match = _eurodateRE.search(text) + if match is not None: + day,month,year,epoch = match.groups() + if year: + if len(year) == 2: + # Y2K problem: + year = add_century(int(year)) + else: + year = int(year) + else: + defaultdate = now() + year = defaultdate.year + if epoch and 'B' in epoch: + year = -year + 1 + month = int(month) + day = int(day) + # Could have mistaken euro format for us style date + # which uses month, day order + if month > 12 or month == 0: + match = None + continue + break + + elif format == 'year': + # just a year specified + match = _yeardateRE.match(text) + if match is not None: + year = match.groups()[0] + if year: + if len(year) == 2: + # Y2K problem: + year = add_century(int(year)) + else: + year = int(year) + else: + defaultdate = now() + year = defaultdate.year + day = 1 + month = 1 + break + + elif format in iso_formats: + # ISO style date + if format == 'iso': + match = _isodateRE.search(text) + elif format == 'altiso': + match = _altisodateRE.search(text) + # Avoid mistaking ISO time parts ('Thhmmss') for dates + if match is not None: + left, right = match.span() + if left > 0 and \ + text[left - 1:left] == 'T': + match = None + continue + else: + match = _usisodateRE.search(text) + if match is not None: + year,month,day = match.groups() + if len(year) == 2: + # Y2K problem: + year = add_century(int(year)) + else: + year = int(year) + # Default to January 1st + if not month: + month = 1 + else: + month = int(month) + if not day: + day = 1 + else: + day = int(day) + break + + elif format in us_formats: + # US style date + if format == 'us': + match = _usdateRE.search(text) + else: + match = _altusdateRE.search(text) + if match is not None: + month,day,year,epoch = match.groups() + if year: + if len(year) == 2: + # Y2K problem: + year = add_century(int(year)) + else: + year = int(year) + else: + defaultdate = now() + year = defaultdate.year + if epoch and 'B' in epoch: + year = -year + 1 + # Default to 1 if no day is given + if day: + day = int(day) + else: + day = 1 + month = int(month) + # Could have mistaken us format for euro style date + # which uses day, month order + if month > 12 or month == 0: + match = None + continue + break + + elif format == 'lit': + # US style literal date + match = _litdateRE.search(text) + if match is not None: + litday,day,litmonth,month,year,epoch = match.groups() + break + + elif format == 'altlit': + # Alternative US style literal date + match = _altlitdateRE.search(text) + if match is not None: + litday,litmonth,day,year,epoch = match.groups() + month = '<missing>' + break + + elif format == 'eurlit': + # European style literal date + match = _eurlitdateRE.search(text) + if match is not None: + litday,day,litmonth,year,epoch = match.groups() + month = '<missing>' + break + + elif format == 'unknown': + # No date part: use defaultdate + defaultdate = now() + year = defaultdate.year + month = defaultdate.month + day = defaultdate.day + style = format + break + + # Check success + if match is not None: + # Remove date from text + left, right = match.span() + if 0 and _debug: + print 'parsed date:',repr(text[left:right]),\ + 'giving:',year,month,day + text = text[:left] + text[right:] + style = format + + elif not style: + # Not recognized: raise an error + raise ValueError, 'unknown date format: "%s"' % text + + # Literal date post-processing + if style in ('lit', 'altlit', 'eurlit'): + if 0 and _debug: print match.groups() + # Default to current year, January 1st + if not year: + defaultdate = now() + year = defaultdate.year + else: + if len(year) == 2: + # Y2K problem: + year = add_century(int(year)) + else: + year = int(year) + if epoch and 'B' in epoch: + year = -year + 1 + if litmonth: + litmonth = litmonth.lower() + try: + month = litmonthtable[litmonth] + except KeyError: + raise ValueError,\ + 'wrong month name: "%s"' % litmonth + elif month: + month = int(month) + else: + month = 1 + if day: + day = int(day) + else: + day = 1 + + #print '_parse_date:',text,day,month,year,style + return text,day,month,year,style + +def _parse_time(text): + + """ Parses a time part given in text and returns a tuple + (text,hour,minute,second,offset,style) with the following + meanings: + + * text gives the original text without the time part + * hour,minute,second give the parsed time + * offset gives the time zone UTC offset + * style gives information about which parser was successful: + 'standard' - the standard parser + 'iso' - the ISO time format parser + 'unknown' - no time part was found + + formats may be set to a tuple specifying the parsers to use: + 'standard' - standard time format with ':' delimiter + 'iso' - ISO time format (superset of 'standard') + 'unknown' - default to 0:00:00, 0 zone offset + + If 'unknown' is not given in formats and the time cannot be + parsed, a ValueError is raised. + + """ + match = None + style = '' + + formats=_time_formats + + # Apply parsers in the order given in formats + for format in formats: + + # Standard format + if format == 'standard': + match = _timeRE.search(text) + if match is not None: + hour,minute,second,ampm,zone = match.groups() + style = 'standard' + break + + # ISO format + if format == 'iso': + match = _isotimeRE.search(text) + if match is not None: + hour,minute,second,zone = match.groups() + ampm = None + style = 'iso' + break + + # Default handling + elif format == 'unknown': + hour,minute,second,offset = 0,0,0.0,0 + style = 'unknown' + break + + if not style: + # If no default handling should be applied, raise an error + raise ValueError, 'unknown time format: "%s"' % text + + # Post-processing + if match is not None: + + if zone: + # Convert to UTC offset + offset = utc_offset(zone) + else: + offset = 0 + + hour = int(hour) + if ampm: + if ampm[0] in ('p', 'P'): + # 12pm = midday + if hour < 12: + hour = hour + 12 + else: + # 12am = midnight + if hour >= 12: + hour = hour - 12 + if minute: + minute = int(minute) + else: + minute = 0 + if not second: + second = 0.0 + else: + if ',' in second: + second = second.replace(',', '.') + second = float(second) + + # Remove time from text + left,right = match.span() + if 0 and _debug: + print 'parsed time:',repr(text[left:right]),\ + 'giving:',hour,minute,second,offset + text = text[:left] + text[right:] + + #print '_parse_time:',text,hour,minute,second,offset,style + return text,hour,minute,second,offset,style + +### + +def datetime_from_string(text): + + """ datetime_from_string(text, [formats, defaultdate]) + + Returns a datetime instance reflecting the date and time given + in text. In case a timezone is given, the returned instance + will point to the corresponding UTC time value. Otherwise, the + value is set as given in the string. + + formats may be set to a tuple of strings specifying which of + the following parsers to use and in which order to try + them. Default is to try all of them in the order given below: + + 'euro' - the European date parser + 'us' - the US date parser + 'altus' - the alternative US date parser (with '-' instead of '/') + 'iso' - the ISO date parser + 'altiso' - the alternative ISO date parser (without '-') + 'usiso' - US style ISO date parser (yyyy/mm/dd) + 'lit' - the US literal date parser + 'altlit' - the alternative US literal date parser + 'eurlit' - the Eurpean literal date parser + 'unknown' - if no date part is found, use defaultdate + + defaultdate provides the defaults to use in case no date part + is found. Most of the parsers default to the current year + January 1 if some of these date parts are missing. + + If 'unknown' is not given in formats and the date cannot + be parsed, a ValueError is raised. + + time_formats may be set to a tuple of strings specifying which + of the following parsers to use and in which order to try + them. Default is to try all of them in the order given below: + + 'standard' - standard time format HH:MM:SS (with ':' delimiter) + 'iso' - ISO time format (superset of 'standard') + 'unknown' - default to 00:00:00 in case the time format + cannot be parsed + + Defaults to 00:00:00.00 for time parts that are not included + in the textual representation. + + If 'unknown' is not given in time_formats and the time cannot + be parsed, a ValueError is raised. + + """ + origtext = text + + text,hour,minute,second,offset,timestyle = _parse_time(origtext) + text,day,month,year,datestyle = _parse_date(text) + + if 0 and _debug: + print 'tried time/date on %s, date=%s, time=%s' % (origtext, + datestyle, + timestyle) + + # If this fails, try the ISO order (date, then time) + if timestyle in ('iso', 'unknown'): + text,day,month,year,datestyle = _parse_date(origtext) + text,hour,minute,second,offset,timestyle = _parse_time(text) + if 0 and _debug: + print 'tried ISO on %s, date=%s, time=%s' % (origtext, + datestyle, + timestyle) + + try: + microsecond = int(round(1000000 * (second % 1))) + second = int(second) + return dt.datetime(year,month,day,hour,minute,second, microsecond) - \ + dt.timedelta(minutes=offset) + except ValueError, why: + raise RangeError,\ + 'Failed to parse "%s": %s' % (origtext, why) + +def date_from_string(text): + + """ date_from_string(text, [formats, defaultdate]) + + Returns a datetime instance reflecting the date given in + text. A possibly included time part is ignored. + + formats and defaultdate work just like for + datetime_from_string(). + + """ + _text,day,month,year,datestyle = _parse_date(text) + + try: + return dt.datetime(year,month,day) + except ValueError, why: + raise RangeError,\ + 'Failed to parse "%s": %s' % (text, why) + +def validateDateTimeString(text): + + """ validateDateTimeString(text, [formats, defaultdate]) + + Validates the given text and returns 1/0 depending on whether + text includes parseable date and time values or not. + + formats works just like for datetime_from_string() and defines + the order of date/time parsers to apply. It defaults to the + same list of parsers as for datetime_from_string(). + + XXX Undocumented ! + + """ + try: + datetime_from_string(text) + except ValueError, why: + return 0 + return 1 + + +def validateDateString(text): + + """ validateDateString(text, [formats, defaultdate]) + + Validates the given text and returns 1/0 depending on whether + text includes a parseable date value or not. + + formats works just like for datetime_from_string() and defines + the order of date/time parsers to apply. It defaults to the + same list of parsers as for datetime_from_string(). + + XXX Undocumented ! + + """ + try: + date_from_string(text) + except ValueError, why: + return 0 + return 1 + +### Tests + +def _test(): + + import sys + + t = dt.datetime.now() + _date = t.strftime('%Y-%m-%d') + + print 'Testing DateTime Parser...' + + l = [ + + # Literal formats + ('Sun Nov 6 08:49:37 1994', '1994-11-06 08:49:37.00'), + ('sun nov 6 08:49:37 1994', '1994-11-06 08:49:37.00'), + ('sUN NOV 6 08:49:37 1994', '1994-11-06 08:49:37.00'), + ('Sunday, 06-Nov-94 08:49:37 GMT', '1994-11-06 08:49:37.00'), + ('Sun, 06 Nov 1994 08:49:37 GMT', '1994-11-06 08:49:37.00'), + ('06-Nov-94 08:49:37', '1994-11-06 08:49:37.00'), + ('06-Nov-94', '1994-11-06 00:00:00.00'), + ('06-NOV-94', '1994-11-06 00:00:00.00'), + ('November 19 08:49:37', '%s-11-19 08:49:37.00' % t.year), + ('Nov. 9', '%s-11-09 00:00:00.00' % t.year), + ('Sonntag, der 6. November 1994, 08:49:37 GMT', '1994-11-06 08:49:37.00'), + ('6. November 2001, 08:49:37', '2001-11-06 08:49:37.00'), + ('sep 6', '%s-09-06 00:00:00.00' % t.year), + ('sep 6 2000', '2000-09-06 00:00:00.00'), + ('September 29', '%s-09-29 00:00:00.00' % t.year), + ('Sep. 29', '%s-09-29 00:00:00.00' % t.year), + ('6 sep', '%s-09-06 00:00:00.00' % t.year), + ('29 September', '%s-09-29 00:00:00.00' % t.year), + ('29 Sep.', '%s-09-29 00:00:00.00' % t.year), + ('sep 6 2001', '2001-09-06 00:00:00.00'), + ('Sep 6, 2001', '2001-09-06 00:00:00.00'), + ('September 6, 2001', '2001-09-06 00:00:00.00'), + ('sep 6 01', '2001-09-06 00:00:00.00'), + ('Sep 6, 01', '2001-09-06 00:00:00.00'), + ('September 6, 01', '2001-09-06 00:00:00.00'), + ('30 Apr 2006 20:19:00', '2006-04-30 20:19:00.00'), + + # ISO formats + ('1994-11-06 08:49:37', '1994-11-06 08:49:37.00'), + ('010203', '2001-02-03 00:00:00.00'), + ('2001-02-03 00:00:00.00', '2001-02-03 00:00:00.00'), + ('2001-02 00:00:00.00', '2001-02-01 00:00:00.00'), + ('2001-02-03', '2001-02-03 00:00:00.00'), + ('2001-02', '2001-02-01 00:00:00.00'), + ('20000824/2300', '2000-08-24 23:00:00.00'), + ('20000824/0102', '2000-08-24 01:02:00.00'), + ('20000824', '2000-08-24 00:00:00.00'), + ('20000824/020301', '2000-08-24 02:03:01.00'), + ('20000824 020301', '2000-08-24 02:03:01.00'), + ('20000824T020301', '2000-08-24 02:03:01.00'), + ('20000824 020301', '2000-08-24 02:03:01.00'), + ('2000-08-24 02:03:01.00', '2000-08-24 02:03:01.00'), + ('T020311', '%s 02:03:11.00' % _date), + ('2003-12-9', '2003-12-09 00:00:00.00'), + ('03-12-9', '2003-12-09 00:00:00.00'), + ('003-12-9', '0003-12-09 00:00:00.00'), + ('0003-12-9', '0003-12-09 00:00:00.00'), + ('2003-1-9', '2003-01-09 00:00:00.00'), + ('03-1-9', '2003-01-09 00:00:00.00'), + ('003-1-9', '0003-01-09 00:00:00.00'), + ('0003-1-9', '0003-01-09 00:00:00.00'), + + # US formats + ('06/11/94 08:49:37', '1994-06-11 08:49:37.00'), + ('11/06/94 08:49:37', '1994-11-06 08:49:37.00'), + ('9/23/2001', '2001-09-23 00:00:00.00'), + ('9-23-2001', '2001-09-23 00:00:00.00'), + ('9/6', '%s-09-06 00:00:00.00' % t.year), + ('09/6', '%s-09-06 00:00:00.00' % t.year), + ('9/06', '%s-09-06 00:00:00.00' % t.year), + ('09/06', '%s-09-06 00:00:00.00' % t.year), + ('9/6/2001', '2001-09-06 00:00:00.00'), + ('09/6/2001', '2001-09-06 00:00:00.00'), + ('9/06/2001', '2001-09-06 00:00:00.00'), + ('09/06/2001', '2001-09-06 00:00:00.00'), + ('9-6-2001', '2001-09-06 00:00:00.00'), + ('09-6-2001', '2001-09-06 00:00:00.00'), + ('9-06-2001', '2001-09-06 00:00:00.00'), + ('09-06-2001', '2001-09-06 00:00:00.00'), + ('2002/05/28 13:10:56.114700 GMT+2', '2002-05-28 13:10:56.114700'), + ('1970/01/01', '1970-01-01 00:00:00.00'), + ('20021025 12:00 PM', '2002-10-25 12:00:00.00'), + ('20021025 12:30 PM', '2002-10-25 12:30:00.00'), + ('20021025 12:00 AM', '2002-10-25 00:00:00.00'), + ('20021025 12:30 AM', '2002-10-25 00:30:00.00'), + ('20021025 1:00 PM', '2002-10-25 13:00:00.00'), + ('20021025 2:00 AM', '2002-10-25 02:00:00.00'), + ('Thursday, February 06, 2003 12:40 PM', '2003-02-06 12:40:00.00'), + ('Mon, 18 Sep 2006 23:03:00', '2006-09-18 23:03:00.00'), + + # European formats + ('6.11.2001, 08:49:37', '2001-11-06 08:49:37.00'), + ('06.11.2001, 08:49:37', '2001-11-06 08:49:37.00'), + ('06.11. 08:49:37', '%s-11-06 08:49:37.00' % t.year), + #('21/12/2002', '2002-12-21 00:00:00.00'), + #('21/08/2002', '2002-08-21 00:00:00.00'), + #('21-08-2002', '2002-08-21 00:00:00.00'), + #('13/01/03', '2003-01-13 00:00:00.00'), + #('13/1/03', '2003-01-13 00:00:00.00'), + #('13/1/3', '2003-01-13 00:00:00.00'), + #('13/01/3', '2003-01-13 00:00:00.00'), + + # Time only formats + ('01:03', '%s 01:03:00.00' % _date), + ('01:03:11', '%s 01:03:11.00' % _date), + ('01:03:11.50', '%s 01:03:11.500000' % _date), + ('01:03:11.50 AM', '%s 01:03:11.500000' % _date), + ('01:03:11.50 PM', '%s 13:03:11.500000' % _date), + ('01:03:11.50 a.m.', '%s 01:03:11.500000' % _date), + ('01:03:11.50 p.m.', '%s 13:03:11.500000' % _date), + + # Invalid formats + ('6..2001, 08:49:37', '%s 08:49:37.00' % _date), + ('9//2001', 'ignore'), + ('06--94 08:49:37', 'ignore'), + ('20-03 00:00:00.00', 'ignore'), + ('9/2001', 'ignore'), + ('9-6', 'ignore'), + ('09-6', 'ignore'), + ('9-06', 'ignore'), + ('09-06', 'ignore'), + ('20000824/23', 'ignore'), + ('November 1994 08:49:37', 'ignore'), + ] + + # Add Unicode versions + try: + unicode + except NameError: + pass + else: + k = [] + for text, result in l: + k.append((unicode(text), result)) + l.extend(k) + + for text, reference in l: + try: + value = datetime_from_string(text) + except: + if reference is None: + continue + else: + value = str(sys.exc_info()[1]) + valid_datetime = validateDateTimeString(text) + valid_date = validateDateString(text) + + if reference[-3:] == '.00': reference = reference[:-3] + + if str(value) != reference and \ + not reference == 'ignore': + print 'Failed to parse "%s"' % text + print ' expected: %s' % (reference or '<exception>') + print ' parsed: %s' % value + elif _debug: + print 'Parsed "%s" successfully' % text + if _debug: + if not valid_datetime: + print ' "%s" failed date/time validation' % text + if not valid_date: + print ' "%s" failed date validation' % text + + et = dt.datetime.now() + print 'done. (after %f seconds)' % ((et-t).seconds) + +if __name__ == '__main__': + _test() diff --git a/numpy/core/arrayprint.py b/numpy/core/arrayprint.py index 5e5a96b68..c8bc9438a 100644 --- a/numpy/core/arrayprint.py +++ b/numpy/core/arrayprint.py @@ -189,10 +189,13 @@ def _array2string(a, max_line_width, precision, suppress_small, separator=' ', # make sure True and False line up. format_function = _boolFormatter elif issubclass(dtypeobj, _nt.integer): - max_str_len = max(len(str(maximum.reduce(data))), - len(str(minimum.reduce(data)))) - format = '%' + str(max_str_len) + 'd' - format_function = lambda x: _formatInteger(x, format) + if issubclass(dtypeobj, _nt.timeinteger): + format_function = str + else: + max_str_len = max(len(str(maximum.reduce(data))), + len(str(minimum.reduce(data)))) + format = '%' + str(max_str_len) + 'd' + format_function = lambda x: _formatInteger(x, format) elif issubclass(dtypeobj, _nt.floating): if issubclass(dtypeobj, _nt.longfloat): format_function = _longfloatFormatter(precision) diff --git a/numpy/core/code_generators/generate_numpy_api.py b/numpy/core/code_generators/generate_numpy_api.py index 06bb6d4d2..69f8c2026 100644 --- a/numpy/core/code_generators/generate_numpy_api.py +++ b/numpy/core/code_generators/generate_numpy_api.py @@ -2,12 +2,12 @@ import os import genapi types = ['Generic','Number','Integer','SignedInteger','UnsignedInteger', - 'Inexact', + 'Inexact', 'TimeInteger', 'Floating', 'ComplexFloating', 'Flexible', 'Character', 'Byte','Short','Int', 'Long', 'LongLong', 'UByte', 'UShort', 'UInt', 'ULong', 'ULongLong', 'Float', 'Double', 'LongDouble', 'CFloat', 'CDouble', 'CLongDouble', 'Object', 'String', 'Unicode', - 'Void'] + 'Void', 'Datetime', 'Timedelta'] h_template = r""" #ifdef _MULTIARRAYMODULE @@ -123,7 +123,7 @@ _import_array(void) } #elif NPY_BYTE_ORDER == NPY_LITTLE_ENDIAN if (st != NPY_CPU_LITTLE) { - PyErr_Format(PyExc_RuntimeError, "FATAL: module compiled as"\ + PyErr_Format(PyExc_RuntimeError, "FATAL: module compiled as "\ "little endian, but detected different endianness at runtime"); return -1; } diff --git a/numpy/core/code_generators/generate_umath.py b/numpy/core/code_generators/generate_umath.py index fd829933a..60a8b5a30 100644 --- a/numpy/core/code_generators/generate_umath.py +++ b/numpy/core/code_generators/generate_umath.py @@ -1,5 +1,9 @@ -import re, textwrap -import sys, os +import os +import re +import struct +import sys +import textwrap + sys.path.insert(0, os.path.dirname(__file__)) import ufunc_docstrings as docstrings sys.path.pop(0) @@ -8,16 +12,25 @@ Zero = "PyUFunc_Zero" One = "PyUFunc_One" None_ = "PyUFunc_None" +# Sentinel value to specify that the loop for the given TypeDescription uses the +# pointer to arrays as its func_data. +UsesArraysAsData = object() + + class TypeDescription(object): - """Type signature for a ufunc + """Type signature for a ufunc. Attributes ---------- - - type: character representing the type - func_data: - in_: - out: + type : str + Character representing the nominal type. + func_data : str or None or UsesArraysAsData, optional + The string representing the expression to insert into the data array, if + any. + in_ : str or None, optional + The typecode(s) of the inputs. + out : str or None, optional + The typecode(s) of the outputs. """ def __init__(self, type, f=None, in_=None, out=None): self.type = type @@ -94,6 +107,39 @@ class Ufunc(object): for td in self.type_descriptions: td.finish_signature(self.nin, self.nout) +# String-handling utilities to avoid locale-dependence. + +import string +UPPER_TABLE = string.maketrans(string.ascii_lowercase, string.ascii_uppercase) + +def english_upper(s): + """ Apply English case rules to convert ASCII strings to all upper case. + + This is an internal utility function to replace calls to str.upper() such + that we can avoid changing behavior with changing locales. In particular, + Turkish has distinct dotted and dotless variants of the Latin letter "I" in + both lowercase and uppercase. Thus, "i".upper() != "I" in a "tr" locale. + + Parameters + ---------- + s : str + + Returns + ------- + uppered : str + + Examples + -------- + >>> from numpy.lib.utils import english_upper + >>> english_upper('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_') + 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_' + >>> english_upper('') + '' + """ + uppered = s.translate(UPPER_TABLE) + return uppered + + #each entry in defdict is a Ufunc object. #name: [string of chars for which it is defined, @@ -122,16 +168,19 @@ chartoname = {'?': 'bool', 'F': 'cfloat', 'D': 'cdouble', 'G': 'clongdouble', + 'M': 'datetime', + 'm': 'timedelta', 'O': 'OBJECT', - # M is like O, but calls a method of the object instead + # '.' is like 'O', but calls a method of the object instead # of a function - 'M': 'OBJECT', + '.': 'OBJECT', } -all = '?bBhHiIlLqQfdgFDGO' +all = '?bBhHiIlLqQfdgFDGOMm' O = 'O' -M = 'M' +M = '.' ints = 'bBhHiIlLqQ' +times = 'Mm' intsO = ints + O bints = '?' + ints bintsO = bints + O @@ -144,33 +193,51 @@ cmplxM = cmplx + M inexact = flts + cmplx noint = inexact+O nointM = inexact+M -allM = bints+flts+cmplxM +allM = bints+times+flts+cmplxM nobool = all[1:] -nobool_or_obj = all[1:-1] +noobj = all[:-3]+all[-2:] +nobool_or_obj = all[1:-3]+all[-2:] intflt = ints+flts -intfltcmplx = nobool_or_obj -nocmplx = bints+flts +intfltcmplx = ints+flts+cmplx +nocmplx = bints+times+flts nocmplxO = nocmplx+O nocmplxM = nocmplx+M -noobj = all[:-1] +notimes_or_obj = bints + inexact + +# Find which code corresponds to int64. +int64 = '' +uint64 = '' +for code in 'bhilq': + if struct.calcsize(code) == 8: + int64 = code + uint64 = english_upper(code) + break defdict = { 'add' : Ufunc(2, 1, Zero, docstrings.get('numpy.core.umath.add'), - TD(noobj), + TD(notimes_or_obj), + [TypeDescription('M', UsesArraysAsData, 'Mm', 'M'), + TypeDescription('m', UsesArraysAsData, 'mm', 'm'), + TypeDescription('M', UsesArraysAsData, 'mM', 'M'), + ], TD(O, f='PyNumber_Add'), ), 'subtract' : Ufunc(2, 1, Zero, docstrings.get('numpy.core.umath.subtract'), - TD(noobj), + TD(notimes_or_obj), + [TypeDescription('M', UsesArraysAsData, 'Mm', 'M'), + TypeDescription('m', UsesArraysAsData, 'mm', 'm'), + TypeDescription('M', UsesArraysAsData, 'MM', 'm'), + ], TD(O, f='PyNumber_Subtract'), ), 'multiply' : Ufunc(2, 1, One, docstrings.get('numpy.core.umath.multiply'), - TD(noobj), + TD(notimes_or_obj), TD(O, f='PyNumber_Multiply'), ), 'divide' : @@ -196,7 +263,7 @@ defdict = { 'conjugate' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.conjugate'), - TD(nobool_or_obj), + TD(ints+flts+cmplx), TD(M, f='conjugate'), ), 'fmod' : @@ -209,13 +276,13 @@ defdict = { 'square' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.square'), - TD(nobool_or_obj), + TD(ints+inexact), TD(O, f='Py_square'), ), 'reciprocal' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.reciprocal'), - TD(nobool_or_obj), + TD(ints+inexact), TD(O, f='Py_reciprocal'), ), 'ones_like' : @@ -234,14 +301,14 @@ defdict = { 'absolute' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.absolute'), - TD(nocmplx), + TD(bints+flts+times), TD(cmplx, out=('f', 'd', 'g')), TD(O, f='PyNumber_Absolute'), ), 'negative' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.negative'), - TD(nocmplx), + TD(bints+flts+times), TD(cmplx, f='neg'), TD(O, f='PyNumber_Negative'), ), @@ -338,6 +405,7 @@ defdict = { docstrings.get('numpy.core.umath.logaddexp2'), TD(flts, f="logaddexp2") ), +# FIXME: decide if the times should have the bitwise operations. 'bitwise_and' : Ufunc(2, 1, One, docstrings.get('numpy.core.umath.bitwise_and'), @@ -608,7 +676,7 @@ chartotype1 = {'f': 'f_f', 'D': 'D_D', 'G': 'G_G', 'O': 'O_O', - 'M': 'O_O_method'} + '.': 'O_O_method'} chartotype2 = {'f': 'ff_f', 'd': 'dd_d', @@ -617,45 +685,12 @@ chartotype2 = {'f': 'ff_f', 'D': 'DD_D', 'G': 'GG_G', 'O': 'OO_O', - 'M': 'OO_O_method'} + '.': 'OO_O_method'} #for each name # 1) create functions, data, and signature # 2) fill in functions and data in InitOperators # 3) add function. -# String-handling utilities to avoid locale-dependence. - -import string -UPPER_TABLE = string.maketrans(string.ascii_lowercase, string.ascii_uppercase) - -def english_upper(s): - """ Apply English case rules to convert ASCII strings to all upper case. - - This is an internal utility function to replace calls to str.upper() such - that we can avoid changing behavior with changing locales. In particular, - Turkish has distinct dotted and dotless variants of the Latin letter "I" in - both lowercase and uppercase. Thus, "i".upper() != "I" in a "tr" locale. - - Parameters - ---------- - s : str - - Returns - ------- - uppered : str - - Examples - -------- - >>> from numpy.lib.utils import english_upper - >>> english_upper('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_') - 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_' - >>> english_upper('') - '' - """ - uppered = s.translate(UPPER_TABLE) - return uppered - - def make_arrays(funcdict): # functions array contains an entry for every type implemented # NULL should be placed where PyUfunc_ style function will be filled in later @@ -679,7 +714,7 @@ def make_arrays(funcdict): thedict = chartotype1 # one input and one output for t in uf.type_descriptions: - if t.func_data is not None: + if t.func_data not in (None, UsesArraysAsData): funclist.append('NULL') astr = '%s_functions[%d] = PyUFunc_%s;' % \ (name, k, thedict[t.type]) @@ -689,7 +724,7 @@ def make_arrays(funcdict): (name, k, t.func_data) code2list.append(astr) datalist.append('(void *)NULL') - elif t.type == 'M': + elif t.type == '.': datalist.append('(void *)"%s"' % t.func_data) else: astr = '%s_data[%d] = (void *) %s;' % \ @@ -698,8 +733,13 @@ def make_arrays(funcdict): datalist.append('(void *)NULL') #datalist.append('(void *)%s' % t.func_data) sub += 1 + elif t.func_data is UsesArraysAsData: + tname = english_upper(chartoname[t.type]) + datalist.append('(void *)NULL') + funclist.append('%s_%s_%s_%s' % (tname, t.in_, t.out, name)) + code2list.append('PyUFunc_SetUsesArraysAsData(%s_data, %s);' % (name, k)) else: - datalist.append('(void *)NULL'); + datalist.append('(void *)NULL') tname = english_upper(chartoname[t.type]) funclist.append('%s_%s' % (tname, name)) diff --git a/numpy/core/code_generators/numpy_api_order.txt b/numpy/core/code_generators/numpy_api_order.txt index 2b71fc547..72f8d5c82 100644 --- a/numpy/core/code_generators/numpy_api_order.txt +++ b/numpy/core/code_generators/numpy_api_order.txt @@ -175,3 +175,8 @@ PyArray_GetEndianness PyArray_GetNDArrayCFeatureVersion PyArray_Correlate2 PyArray_NeighborhoodIterNew +PyArray_SetDatetimeParseFunction +PyArray_DatetimeToDatetimeStruct +PyArray_TimedeltaToTimedeltaStruct +PyArray_DatetimeStructToDatetime +PyArray_TimedeltaStructToTimedelt diff --git a/numpy/core/code_generators/ufunc_api_order.txt b/numpy/core/code_generators/ufunc_api_order.txt index d80f30ec8..4737e4502 100644 --- a/numpy/core/code_generators/ufunc_api_order.txt +++ b/numpy/core/code_generators/ufunc_api_order.txt @@ -32,3 +32,4 @@ PyUFunc_getfperr PyUFunc_handlefperr PyUFunc_ReplaceLoopBySignature PyUFunc_FromFuncAndDataAndSignature +PyUFunc_SetUsesArraysAsData diff --git a/numpy/core/include/numpy/arrayscalars.h b/numpy/core/include/numpy/arrayscalars.h index 01ed1e1a6..4d840e719 100644 --- a/numpy/core/include/numpy/arrayscalars.h +++ b/numpy/core/include/numpy/arrayscalars.h @@ -110,6 +110,18 @@ typedef struct { PyObject * obval; } PyObjectScalarObject; +typedef struct { + PyObject_HEAD + npy_datetime obval; + PyArray_DatetimeMetaData obmeta; +} PyDatetimeScalarObject; + +typedef struct { + PyObject_HEAD + npy_timedelta obval; + PyArray_DatetimeMetaData obmeta; +} PyTimedeltaScalarObject; + typedef struct { PyObject_HEAD diff --git a/numpy/core/include/numpy/ndarrayobject.h b/numpy/core/include/numpy/ndarrayobject.h index b6c366cdd..34b080732 100644 --- a/numpy/core/include/numpy/ndarrayobject.h +++ b/numpy/core/include/numpy/ndarrayobject.h @@ -78,12 +78,15 @@ enum NPY_TYPES { NPY_BOOL=0, NPY_OBJECT=17, NPY_STRING, NPY_UNICODE, NPY_VOID, + NPY_DATETIME, NPY_TIMEDELTA, NPY_NTYPES, NPY_NOTYPE, NPY_CHAR, /* special flag */ NPY_USERDEF=256 /* leave room for characters */ }; +#define NPY_METADATA_DTSTR "__frequency__" + /* basetype array priority */ #define NPY_PRIORITY 0.0 @@ -128,6 +131,8 @@ enum NPY_TYPECHAR { NPY_BOOLLTR = '?', NPY_STRINGLTR2 = 'a', NPY_UNICODELTR = 'U', NPY_VOIDLTR = 'V', + NPY_DATETIMELTR = 'M', + NPY_TIMEDELTALTR = 'm', NPY_CHARLTR = 'c', /* No Descriptor, just a define -- this let's @@ -182,6 +187,40 @@ typedef enum { NPY_RAISE=2 } NPY_CLIPMODE; +typedef enum { + NPY_FR_Y, + NPY_FR_M, + NPY_FR_W, + NPY_FR_B, + NPY_FR_D, + NPY_FR_h, + NPY_FR_m, + NPY_FR_s, + NPY_FR_ms, + NPY_FR_us, + NPY_FR_ns, + NPY_FR_ps, + NPY_FR_fs, + NPY_FR_as +} NPY_DATETIMEUNIT; + +#define NPY_DATETIME_NUMUNITS (NPY_FR_as + 1) +#define NPY_DATETIME_DEFAULTUNIT NPY_FR_us + +#define NPY_STR_Y "Y" +#define NPY_STR_M "M" +#define NPY_STR_W "W" +#define NPY_STR_B "B" +#define NPY_STR_D "D" +#define NPY_STR_h "h" +#define NPY_STR_s "s" +#define NPY_STR_ms "ms" +#define NPY_STR_us "us" +#define NPY_STR_ns "ns" +#define NPY_STR_ps "ps" +#define NPY_STR_fs "fs" +#define NPY_STR_as "as" + /* This is to typedef npy_intp to the appropriate pointer size for this * platform. Py_intptr_t, Py_uintptr_t are defined in pyport.h. */ @@ -478,6 +517,8 @@ typedef struct _PyArray_Descr { PyArray_ArrFuncs *f; /* a table of functions specific for each basic data descriptor */ + + PyObject *metadata; /* Metadata about this dtype */ } PyArray_Descr; typedef struct _arr_descr { @@ -532,6 +573,27 @@ typedef struct { int flags; } PyArray_Chunk; + +typedef struct { + NPY_DATETIMEUNIT base; + int num; + int den; /* Converted to 1 on input for now -- an input-only mechanism */ + int events; +} PyArray_DatetimeMetaData; + +typedef struct { + npy_longlong year; + int month, day, hour, min, sec, us, ps, as; +} npy_datetimestruct; + +typedef struct { + npy_longlong day; + int sec, us, ps, as; +} npy_timedeltastruct; + + +#define PyDataType_GetDatetimeMetaData(descr) ((descr->metadata == NULL) ? NULL : ((PyArray_DatetimeMetaData *)(PyCObject_AsVoidPtr(PyDict_GetItemString(descr->metadata, NPY_METADATA_DTSTR))))) + typedef int (PyArray_FinalizeFunc)(PyArrayObject *, PyObject *); /* Means c-style contiguous (last index varies the fastest). The @@ -1071,6 +1133,9 @@ PyArrayNeighborhoodIter_Next(PyArrayNeighborhoodIterObject* iter); #define PyTypeNum_ISFLEXIBLE(type) (((type) >=NPY_STRING) && \ ((type) <=NPY_VOID)) +#define PyTypeNum_ISDATETIME(type) (((type) >=NPY_DATETIME) && \ + ((type) <=NPY_TIMEDELTA)) + #define PyTypeNum_ISUSERDEF(type) (((type) >= NPY_USERDEF) && \ ((type) < NPY_USERDEF+ \ NPY_NUMUSERTYPES)) @@ -1091,6 +1156,7 @@ PyArrayNeighborhoodIter_Next(PyArrayNeighborhoodIterObject* iter); #define PyDataType_ISCOMPLEX(obj) PyTypeNum_ISCOMPLEX(((PyArray_Descr*)(obj))->type_num) #define PyDataType_ISPYTHON(obj) PyTypeNum_ISPYTHON(((PyArray_Descr*)(obj))->type_num) #define PyDataType_ISFLEXIBLE(obj) PyTypeNum_ISFLEXIBLE(((PyArray_Descr*)(obj))->type_num) +#define PyDataType_ISDATETIME(obj) PyTypeNum_ISDATETIME(((PyArray_Descr*)(obj))->type_num) #define PyDataType_ISUSERDEF(obj) PyTypeNum_ISUSERDEF(((PyArray_Descr*)(obj))->type_num) #define PyDataType_ISEXTENDED(obj) PyTypeNum_ISEXTENDED(((PyArray_Descr*)(obj))->type_num) #define PyDataType_ISOBJECT(obj) PyTypeNum_ISOBJECT(((PyArray_Descr*)(obj))->type_num) @@ -1106,6 +1172,7 @@ PyArrayNeighborhoodIter_Next(PyArrayNeighborhoodIterObject* iter); #define PyArray_ISCOMPLEX(obj) PyTypeNum_ISCOMPLEX(PyArray_TYPE(obj)) #define PyArray_ISPYTHON(obj) PyTypeNum_ISPYTHON(PyArray_TYPE(obj)) #define PyArray_ISFLEXIBLE(obj) PyTypeNum_ISFLEXIBLE(PyArray_TYPE(obj)) +#define PyArray_ISDATETIME(obj) PyTypeNum_ISDATETIME(PyArray_TYPE(obj)) #define PyArray_ISUSERDEF(obj) PyTypeNum_ISUSERDEF(PyArray_TYPE(obj)) #define PyArray_ISEXTENDED(obj) PyTypeNum_ISEXTENDED(PyArray_TYPE(obj)) #define PyArray_ISOBJECT(obj) PyTypeNum_ISOBJECT(PyArray_TYPE(obj)) diff --git a/numpy/core/include/numpy/noprefix.h b/numpy/core/include/numpy/noprefix.h index dc4f71c70..ca66d2970 100644 --- a/numpy/core/include/numpy/noprefix.h +++ b/numpy/core/include/numpy/noprefix.h @@ -54,9 +54,13 @@ compatibility measure*/ #define Complex256 npy_complex256 #define intp npy_intp #define uintp npy_uintp +#define datetime npy_datetime +#define timedelta npy_timedelta #define SIZEOF_INTP NPY_SIZEOF_INTP #define SIZEOF_UINTP NPY_SIZEOF_UINTP +#define SIZEOF_DATETIME NPY_SIZEOF_DATETIME +#define SIZEOF_TIMEDELTA NPY_SIZEOF_TIMEDELTA #define LONGLONG_FMT NPY_LONGLONG_FMT #define ULONGLONG_FMT NPY_ULONGLONG_FMT @@ -97,6 +101,10 @@ compatibility measure*/ #define MAX_LONGLONG NPY_MAX_LONGLONG #define MIN_LONGLONG NPY_MIN_LONGLONG #define MAX_ULONGLONG NPY_MAX_ULONGLONG +#define MIN_DATETIME NPY_MIN_DATETIME +#define MAX_DATETIME NPY_MAX_DATETIME +#define MIN_TIMEDELTA NPY_MIN_TIMEDELTA +#define MAX_TIMEDELTA NPY_MAX_TIMEDELTA #define SIZEOF_LONGDOUBLE NPY_SIZEOF_LONGDOUBLE #define SIZEOF_LONGLONG NPY_SIZEOF_LONGLONG @@ -109,6 +117,8 @@ compatibility measure*/ #define BITSOF_FLOAT NPY_BITSOF_FLOAT #define BITSOF_DOUBLE NPY_BITSOF_DOUBLE #define BITSOF_LONGDOUBLE NPY_BITSOF_LONGDOUBLE +#define BITSOF_DATETIME NPY_BITSOF_DATETIME +#define BITSOF_TIMEDELTA NPY_BITSOF_TIMEDELTA #define PyArray_UCS4 npy_ucs4 #define _pya_malloc PyArray_malloc diff --git a/numpy/core/include/numpy/npy_common.h b/numpy/core/include/numpy/npy_common.h index 8d553a67a..8f39c8871 100644 --- a/numpy/core/include/numpy/npy_common.h +++ b/numpy/core/include/numpy/npy_common.h @@ -110,6 +110,10 @@ typedef struct {npy_longdouble real, imag;} npy_clongdouble; #define NPY_MAX_INT256 NPY_LONGLONG_SUFFIX(57896044618658097711785492504343953926634992332820282019728792003956564819967) #define NPY_MIN_INT256 (-NPY_MAX_INT256 - NPY_LONGLONG_SUFFIX(1)) #define NPY_MAX_UINT256 NPY_ULONGLONG_SUFFIX(115792089237316195423570985008687907853269984665640564039457584007913129639935) +#define NPY_MIN_DATETIME NPY_MIN_INT64 +#define NPY_MAX_DATETIME NPY_MAX_INT64 +#define NPY_MIN_TIMEDELTA NPY_MIN_INT64 +#define NPY_MAX_TIMEDELTA NPY_MAX_INT64 /* Need to find the number of bits for each type and make definitions accordingly. @@ -143,6 +147,9 @@ typedef struct {npy_longdouble real, imag;} npy_clongdouble; #define NPY_MIN_LONG LONG_MIN #define NPY_MAX_ULONG ULONG_MAX +#define NPY_SIZEOF_DATETIME 8 +#define NPY_SIZEOF_TIMEDELTA 8 + #define NPY_BITSOF_BOOL (sizeof(npy_bool)*CHAR_BIT) #define NPY_BITSOF_CHAR CHAR_BIT #define NPY_BITSOF_SHORT (NPY_SIZEOF_SHORT * CHAR_BIT) @@ -152,6 +159,8 @@ typedef struct {npy_longdouble real, imag;} npy_clongdouble; #define NPY_BITSOF_FLOAT (NPY_SIZEOF_FLOAT * CHAR_BIT) #define NPY_BITSOF_DOUBLE (NPY_SIZEOF_DOUBLE * CHAR_BIT) #define NPY_BITSOF_LONGDOUBLE (NPY_SIZEOF_LONGDOUBLE * CHAR_BIT) +#define NPY_BITSOF_DATETIME (NPY_SIZEOF_DATETIME * CHAR_BIT) +#define NPY_BITSOF_TIMEDELTA (NPY_SIZEOF_TIMEDELTA * CHAR_BIT) #if NPY_BITSOF_LONG == 8 #define NPY_INT8 NPY_LONG @@ -198,6 +207,12 @@ typedef struct {npy_longdouble real, imag;} npy_clongdouble; #define PyUInt64ArrType_Type PyULongArrType_Type #define NPY_INT64_FMT NPY_LONG_FMT #define NPY_UINT64_FMT NPY_ULONG_FMT + typedef long npy_datetime; +#define NPY_DATETIME_FMT NPY_LONG_FMT + typedef long npy_timedelta; +#define NPY_TIMEDELTA_FMT NPY_LONG_FMT +#define MyPyLong_FromInt64 PyLong_FromLong +#define MyPyLong_AsInt64 PyLong_AsLong #elif NPY_BITSOF_LONG == 128 #define NPY_INT128 NPY_LONG #define NPY_UINT128 NPY_ULONG @@ -272,6 +287,12 @@ typedef struct {npy_longdouble real, imag;} npy_clongdouble; # define PyUInt64ArrType_Type PyULongLongArrType_Type #define NPY_INT64_FMT NPY_LONGLONG_FMT #define NPY_UINT64_FMT NPY_ULONGLONG_FMT + typedef npy_longlong npy_datetime; +# define NPY_DATETIME_FMT NPY_LONGLONG_FMT + typedef npy_longlong npy_timedelta; +# define NPY_TIMEDELTA_FMT NPY_LONGLONG_FMT +# define MyPyLong_FromInt64 PyLong_FromLongLong +# define MyPyLong_AsInt64 PyLong_AsLongLong # endif # define NPY_MAX_LONGLONG NPY_MAX_INT64 # define NPY_MIN_LONGLONG NPY_MIN_INT64 @@ -360,6 +381,12 @@ typedef struct {npy_longdouble real, imag;} npy_clongdouble; # define PyUInt64ArrType_Type PyUIntArrType_Type #define NPY_INT64_FMT NPY_INT_FMT #define NPY_UINT64_FMT NPY_UINT_FMT + typedef int npy_datetime; +# define NPY_DATETIME_FMT NPY_INT_FMT + typedef int npy_timedelta; +# define NPY_TIMEDELTA_FMT NPY_INT_FMT +# define MyPyLong_FromInt64 PyLong_FromLong +# define MyPyLong_AsInt64 PyLong_AsLong #endif #elif NPY_BITSOF_INT == 128 #ifndef NPY_INT128 @@ -428,6 +455,12 @@ typedef struct {npy_longdouble real, imag;} npy_clongdouble; # define PyUInt64ArrType_Type PyUShortArrType_Type #define NPY_INT64_FMT NPY_SHORT_FMT #define NPY_UINT64_FMT NPY_USHORT_FMT + typedef short npy_datetime; +# define NPY_DATETIME_FMT NPY_SHORT_FMT + typedef short npy_timedelta; +# define NPY_TIMEDELTA_FMT NPY_SHORT_FMT +# define MyPyLong_FromInt64 PyLong_FromLong +# define MyPyLong_AsInt64 PyLong_AsLong #endif #elif NPY_BITSOF_SHORT == 128 #ifndef NPY_INT128 @@ -497,6 +530,12 @@ typedef struct {npy_longdouble real, imag;} npy_clongdouble; # define PyUInt64ArrType_Type PyUByteArrType_Type #define NPY_INT64_FMT NPY_BYTE_FMT #define NPY_UINT64_FMT NPY_UBYTE_FMT + typedef signed char npy_datetime; +# define NPY_DATETIME_FMT NPY_BYTE_FMT + typedef signed char npy_timedelta; +# define NPY_TIMEDELTA_FMT NPY_BYTE_FMT +# define MyPyLong_FromInt64 PyLong_FromLong +# define MyPyLong_AsInt64 PyLong_AsLong #endif #elif NPY_BITSOF_CHAR == 128 #ifndef NPY_INT128 diff --git a/numpy/core/include/numpy/old_defines.h b/numpy/core/include/numpy/old_defines.h index c21665268..a9dfd5c21 100644 --- a/numpy/core/include/numpy/old_defines.h +++ b/numpy/core/include/numpy/old_defines.h @@ -33,6 +33,8 @@ #define PyArray_STRING NPY_STRING #define PyArray_UNICODE NPY_UNICODE #define PyArray_VOID NPY_VOID +#define PyArray_DATETIME NPY_DATETIME +#define PyArray_TIMEDELTA NPY_TIMEDELTA #define PyArray_NTYPES NPY_NTYPES #define PyArray_NOTYPE NPY_NOTYPE #define PyArray_CHAR NPY_CHAR @@ -108,6 +110,8 @@ #define PyArray_STRINGLTR2 NPY_STRINGLTR2 #define PyArray_UNICODELTR NPY_UNICODELTR #define PyArray_VOIDLTR NPY_VOIDLTR +#define PyArray_DATETIMELTR NPY_DATETIMELTR +#define PyArray_TIMEDELTALTR NPY_TIMEDELTALTR #define PyArray_CHARLTR NPY_CHARLTR #define PyArray_INTPLTR NPY_INTPLTR #define PyArray_UINTPLTR NPY_UINTPLTR diff --git a/numpy/core/include/numpy/ufuncobject.h b/numpy/core/include/numpy/ufuncobject.h index 9bbedc9e4..6bbf3de13 100644 --- a/numpy/core/include/numpy/ufuncobject.h +++ b/numpy/core/include/numpy/ufuncobject.h @@ -67,6 +67,9 @@ typedef struct { #define UFUNC_ERR_DEFAULT 0 /* Error mode that avoids look-up (no checking) */ +#define UFUNC_OBJ_ISOBJECT 1 +#define UFUNC_OBJ_NEEDS_API 2 + /* Default user error mode */ #define UFUNC_ERR_DEFAULT2 \ (UFUNC_ERR_PRINT << UFUNC_SHIFT_DIVIDEBYZERO) + \ @@ -132,7 +135,8 @@ typedef struct { */ npy_intp steps[NPY_MAXARGS]; - int obj; /* This loop uses object arrays */ + int obj; /* This loop uses object arrays or needs the Python API */ + /* Flags: UFUNC_OBJ_ISOBJECT, UFUNC_OBJ_NEEDS_API */ int notimplemented; /* The loop caused notimplemented */ int objfunc; /* This loop calls object functions (an inner-loop function with argument types */ @@ -193,8 +197,8 @@ typedef struct { #if NPY_ALLOW_THREADS -#define NPY_LOOP_BEGIN_THREADS do {if (!(loop->obj)) _save = PyEval_SaveThread();} while (0) -#define NPY_LOOP_END_THREADS do {if (!(loop->obj)) PyEval_RestoreThread(_save);} while (0) +#define NPY_LOOP_BEGIN_THREADS do {if (!(loop->obj & UFUNC_OBJ_NEEDS_API)) _save = PyEval_SaveThread();} while (0) +#define NPY_LOOP_END_THREADS do {if (!(loop->obj & UFUNC_OBJ_NEEDS_API)) PyEval_RestoreThread(_save);} while (0) #else #define NPY_LOOP_BEGIN_THREADS #define NPY_LOOP_END_THREADS @@ -232,7 +236,7 @@ typedef struct _loop1d_info { #define UFUNC_PYVALS_NAME "UFUNC_PYVALS" #define UFUNC_CHECK_ERROR(arg) \ - do {if (((arg)->obj && PyErr_Occurred()) || \ + do {if ((((arg)->obj & UFUNC_OBJ_NEEDS_API) && PyErr_Occurred()) || \ ((arg)->errormask && \ PyUFunc_checkfperr((arg)->errormask, \ (arg)->errobj, \ diff --git a/numpy/core/numerictypes.py b/numpy/core/numerictypes.py index d329ed70e..70315f1e0 100644 --- a/numpy/core/numerictypes.py +++ b/numpy/core/numerictypes.py @@ -15,6 +15,7 @@ Exported symbols include: uint8 uint16 uint32 uint64 uint128 float16 float32 float64 float96 float128 float256 complex32 complex64 complex128 complex192 complex256 complex512 + datetime64 timedelta64 c-based names @@ -31,10 +32,14 @@ Exported symbols include: int_, uint, longlong, ulonglong, + single, csingle, float_, complex_, longfloat, clongfloat, + datetime, timedelta, (these inherit from timeinteger which inherits from signedinteger) + + As part of the type-hierarchy: xx -- is bit-width generic @@ -85,7 +90,6 @@ import types as _types # as numerictypes.bool, etc. from __builtin__ import bool, int, long, float, complex, object, unicode, str - # String-handling utilities to avoid locale-dependence. # "import string" is costly to import! @@ -627,7 +631,8 @@ typecodes = {'Character':'c', 'Complex':'FDG', 'AllInteger':'bBhHiIlLqQpP', 'AllFloat':'fdgFDG', - 'All':'?bhilqpBHILQPfdgFDGSUVO'} + 'Datetime': 'Mm', + 'All':'?bhilqpBHILQPfdgFDGSUVOMm'} # backwards compatibility --- deprecated name typeDict = sctypeDict @@ -638,11 +643,13 @@ typeNA = sctypeNA # i -> signed integer # f -> floating point # c -> complex +# M -> datetime +# m -> timedelta # S -> string # U -> Unicode string # V -> record # O -> Python object -_kind_list = ['b', 'u', 'i', 'f', 'c', 'S', 'U', 'V', 'O'] +_kind_list = ['b', 'u', 'i', 'f', 'c', 'S', 'U', 'V', 'O', 'M', 'm'] __test_types = typecodes['AllInteger'][:-2]+typecodes['AllFloat']+'O' __len_test_types = len(__test_types) diff --git a/numpy/core/src/multiarray/arrayobject.c b/numpy/core/src/multiarray/arrayobject.c index 1a1cd2dc8..2f49e03a5 100644 --- a/numpy/core/src/multiarray/arrayobject.c +++ b/numpy/core/src/multiarray/arrayobject.c @@ -319,6 +319,7 @@ array_repr_builtin(PyArrayObject *self, int repr) static PyObject *PyArray_StrFunction = NULL; static PyObject *PyArray_ReprFunction = NULL; +static PyObject *PyArray_DatetimeParseFunction = NULL; /*NUMPY_API * Set the array print function to be a Python function. @@ -344,6 +345,21 @@ PyArray_SetStringFunction(PyObject *op, int repr) } } +/*NUMPY_API + * Set the date time print function to be a Python function. + */ +NPY_NO_EXPORT void +PyArray_SetDatetimeParseFunction(PyObject *op) +{ + /* Dispose of previous callback */ + Py_XDECREF(PyArray_DatetimeParseFunction); + /* Add a reference to the new callback */ + Py_XINCREF(op); + /* Remember new callback */ + PyArray_DatetimeParseFunction = op; +} + + static PyObject * array_repr(PyArrayObject *self) { @@ -912,7 +928,7 @@ array_richcompare(PyArrayObject *self, PyObject *other, int cmp_op) Py_DECREF(result); result = _void_compare (self, - (PyArrayObject *)array_other, + (PyArrayObject *)array_other, cmp_op); Py_DECREF(array_other); } diff --git a/numpy/core/src/multiarray/arraytypes.c.src b/numpy/core/src/multiarray/arraytypes.c.src index 58bad98e1..420fcea7d 100644 --- a/numpy/core/src/multiarray/arraytypes.c.src +++ b/numpy/core/src/multiarray/arraytypes.c.src @@ -76,6 +76,9 @@ MyPyLong_AsUnsigned@Type@ (PyObject *obj) /**end repeat**/ + +static char * _SEQUENCE_MESSAGE = "error setting an array element with a sequence"; + /****************** getitem and setitem **********************/ /**begin repeat @@ -94,6 +97,7 @@ MyPyLong_AsUnsigned@Type@ (PyObject *obj) * #kind = Bool, Byte, UByte, Short, UShort, Int, Long, UInt, ULong, * LongLong, ULongLong, Float, Double# */ + static PyObject * @TYP@_getitem(char *ip, PyArrayObject *ap) { @typ@ t1; @@ -121,8 +125,7 @@ static int if (PyErr_Occurred()) { if (PySequence_Check(op)) { PyErr_Clear(); - PyErr_SetString(PyExc_ValueError, "setting an array" \ - " element with a sequence."); + PyErr_SetString(PyExc_ValueError, _SEQUENCE_MESSAGE); } return -1; } @@ -137,6 +140,378 @@ static int /**end repeat**/ +/* Acknowledgment: Example code contributed by Marty Fuhr sponsored by + Google Summer of Code 2009 was used to integrate and adapt the mxDateTime parser */ +#include "parse_datetime.c" +/* End of contributed code. */ + + +/* DateTime Objects in Python only keep microsecond resolution. + + When converting from datetime objects with an event component return a tuple: + (baseunit, number of event) where baseunit follows is a datetime type and + number of events is a Python integer + */ + + +/* Return a Python Datetime Object from a number representing the number of + units since the epoch (1970-01-01T00:00:00Z) ignoring leap seconds. +*/ + +NPY_NO_EXPORT PyObject * +PyDateTime_FromNormalized(npy_datetime val, NPY_DATETIMEUNIT base) +{ + npy_datetimestruct ydate; + + /* Must be here to use PyDateTime_FromDateAndTime */ + PyDateTime_IMPORT; + + /* We just truncate the unused variables and don't wory about overflow */ + PyArray_DatetimeToDatetimeStruct(val, base, &ydate); + + /* FIXME?: We discard ydate.ns, ydate.ps, ydate.fs, and ydate.as */ + return PyDateTime_FromDateAndTime(ydate.year, ydate.month, ydate.day, + ydate.hour, ydate.min, ydate.sec, + ydate.us); +} + +/* We also can lose precision and range here. Ignored. + Don't use this function if you care. + */ + +NPY_NO_EXPORT PyObject * +PyTimeDelta_FromNormalized(npy_timedelta val, NPY_DATETIMEUNIT base) +{ + npy_timedeltastruct td; + + PyDateTime_IMPORT; + + PyArray_TimedeltaToTimedeltaStruct(val, base, &td); + + /* We discard td.ps and td.as */ + + return PyDelta_FromDSU(td.day, td.sec, td.us); +} + + +NPY_NO_EXPORT PyObject * +PyDateTime_FromInt64(datetime val, PyArray_Descr *descr) +{ + PyArray_DatetimeMetaData *meta; + + meta = PyDataType_GetDatetimeMetaData(descr); + if (meta == NULL) { + PyErr_SetString(PyExc_RuntimeError, "metadata not set for descriptor"); + return NULL; + } + + if (meta->events > 1) { + int events, rem, div; + PyObject *obj; + obj = PyTuple_New(2); + events = meta->events; + div = val / events; + rem = val % events; + PyTuple_SET_ITEM(obj, 1, PyInt_FromLong(rem)); + meta->events = 1; /* This resets meta->events for recursive call */ + PyTuple_SET_ITEM(obj, 0, PyDateTime_FromInt64(div, descr)); + meta->events = events; + if (PyErr_Occurred()) { + Py_DECREF(obj); + return NULL; + } + return obj; + } + + /* We normalize the number to a base-unit and then return a + Python Datetime Object + + FIXME? : We silently truncate if it doesn't fit, either too wide (e.g. 10 BC) + or too narrow (nanoseconds) + */ + + /* Normalization and then conversion to Datetime */ + + /* FIXME? : Check for Overflow... */ + return PyDateTime_FromNormalized(val*meta->num, meta->base); +} + + +NPY_NO_EXPORT PyObject * +PyTimeDelta_FromInt64(timedelta val, PyArray_Descr *descr) +{ + PyArray_DatetimeMetaData *meta; + meta = PyDataType_GetDatetimeMetaData(descr); + if (meta == NULL) { + PyErr_SetString(PyExc_RuntimeError, "metadata not set for descriptor"); + return NULL; + } + + if (meta->events > 1) { + int events, rem, div; + PyObject *obj; + obj = PyTuple_New(2); + events = meta->events; + div = val / events; + rem = val % events; + PyTuple_SET_ITEM(obj, 1, PyInt_FromLong(rem)); + meta->events = 1; /* This resets meta->events for recursive call */ + PyTuple_SET_ITEM(obj, 0, PyTimeDelta_FromInt64(div, descr)); + meta->events = events; + if (PyErr_Occurred()) { + Py_DECREF(obj); + return NULL; + } + return obj; + } + + /* FIXME? : Check for Overflow */ + return PyTimeDelta_FromNormalized(val*meta->num, meta->base); +} + + + +NPY_NO_EXPORT npy_datetime +PyDateTime_AsNormalized(PyObject *obj, NPY_DATETIMEUNIT base) +{ + npy_datetimestruct ydate; + + /* Must be here to use PyDateTime_FromDateAndTime */ + PyDateTime_IMPORT; + + if (!PyDateTime_Check(obj) && !PyDate_Check(obj)) { + PyErr_SetString(PyExc_ValueError, "Must be a datetime.date or datetime.datetime object"); + return -1; + } + + ydate.year = PyDateTime_GET_YEAR(obj); + ydate.month = PyDateTime_GET_MONTH(obj); + ydate.day = PyDateTime_GET_DAY(obj); + + if (PyDateTime_Check(obj)) { + ydate.hour = PyDateTime_DATE_GET_HOUR(obj); + ydate.min = PyDateTime_DATE_GET_MINUTE(obj); + ydate.sec = PyDateTime_DATE_GET_SECOND(obj); + ydate.us = PyDateTime_DATE_GET_MICROSECOND(obj); + } + else { + ydate.hour = 0; + ydate.min = 0; + ydate.sec = 0; + ydate.us = 0; + } + + ydate.ps = 0; + ydate.as = 0; + + /* We just truncate the unused variables and don't wory about overflow */ + return PyArray_DatetimeStructToDatetime(base, &ydate); +} + +NPY_NO_EXPORT npy_timedelta +PyTimeDelta_AsNormalized(PyObject *obj, NPY_DATETIMEUNIT base) +{ + npy_timedeltastruct td; + + PyDateTime_IMPORT; + + if (!PyDelta_Check(obj)) { + PyErr_SetString(PyExc_ValueError, "Must be a datetime.timedelta object"); + return -1; + } + + td.day = ((PyDateTime_Delta *)obj)->days; + td.sec = ((PyDateTime_Delta *)obj)->seconds; + td.us = ((PyDateTime_Delta *)obj)->microseconds; + td.ps = 0; + td.as = 0; + + return PyArray_TimedeltaStructToTimedelta(base, &td); +} + + +/* These expect: + + * a 2-tuple if meta->events > 1 (baseobj, num-counts) + * where baseobj is a datetime object or a timedelta object + * respectively. + +*/ + +NPY_NO_EXPORT npy_datetime +PyDateTime_AsInt64(PyObject *obj, PyArray_Descr *descr) +{ + PyArray_DatetimeMetaData *meta; + npy_datetime res; + + meta = PyDataType_GetDatetimeMetaData(descr); + if (meta == NULL) { + PyErr_SetString(PyExc_RuntimeError, "metadata not set for descriptor"); + return -1; + } + + + if (meta->events > 1) { + datetime tmp; + if (!PyTuple_Check(obj) || PyTuple_GET_SIZE(obj) != 2) { + PyErr_SetString(PyExc_ValueError, "need a 2-tuple on setting if events > 1"); + return -1; + } + tmp = PyDateTime_AsInt64(PyTuple_GET_ITEM(obj, 0), descr); + if (PyErr_Occurred()) return -1; + /* FIXME: Check for overflow */ + tmp *= meta->events; + tmp += MyPyLong_AsLongLong(PyTuple_GET_ITEM(obj, 1)); + if (PyErr_Occurred()) return -1; + return tmp; + } + + res = PyDateTime_AsNormalized(obj, meta->base); + return res / meta->num; +} + + +NPY_NO_EXPORT timedelta +PyTimeDelta_AsInt64(PyObject *obj, PyArray_Descr *descr) +{ + PyArray_DatetimeMetaData *meta; + npy_timedelta res; + meta = PyDataType_GetDatetimeMetaData(descr); + if (meta == NULL) { + PyErr_SetString(PyExc_RuntimeError, "metadata not set for descriptor"); + return -1; + } + + if (meta->events > 1) { + timedelta tmp; + if (!PyTuple_Check(obj) || PyTuple_GET_SIZE(obj) != 2) { + PyErr_SetString(PyExc_ValueError, "need a 2-tuple on setting if events > 1"); + return -1; + } + tmp = PyTimeDelta_AsInt64(PyTuple_GET_ITEM(obj, 0), descr); + if (PyErr_Occurred()) return -1; + /* FIXME: Check for overflow */ + tmp *= meta->events; + tmp += MyPyLong_AsLongLong(PyTuple_GET_ITEM(obj, 1)); + if (PyErr_Occurred()) return -1; + return tmp; + } + + res = PyTimeDelta_AsNormalized(obj, meta->base); + return res / meta->num; +} + + +/* Always return DateTime Object after normalizing to basic units (or a tuple + if meta->events > 1): + + Problem: DateTime does not support all the resolutions (ns) nor the dynamic + range (pre 1 AD) of NumPy Date-times. + + * getitem is not used that much --- if losing resolution hurts, stick + with the array scalar versions of the date-time. + + * considered returning array scalars here just like longdouble. + This has the problem of recursion in some cases (because in a few + places the code expects getitem to return a Python-system object) + + * considered returning different things depending on the resolution but + this would make it hard to write generic code --- + but do you need to write generic code on all the frequencies + because they cover a wide range. + + Solution: The use-case of actually wanting a date-time object when the resolution + and dynamic range match, make it the compelling default. When it + does fails, there are alternatives for the programmer to use. + + New question: Should we change (c)longdouble at this point? to return Python Float? + +*/ + +static PyObject * +DATETIME_getitem(char *ip, PyArrayObject *ap) { + datetime t1; + + if ((ap==NULL) || PyArray_ISBEHAVED_RO(ap)) { + t1 = *((datetime *)ip); + return PyDateTime_FromInt64((datetime)t1, ap->descr); + } + else { + ap->descr->f->copyswap(&t1, ip, !PyArray_ISNOTSWAPPED(ap), ap); + return PyDateTime_FromInt64((datetime)t1, ap->descr); + } +} + + +static PyObject * +TIMEDELTA_getitem(char *ip, PyArrayObject *ap) { + timedelta t1; + + if ((ap==NULL) || PyArray_ISBEHAVED_RO(ap)) { + t1 = *((timedelta *)ip); + return PyTimeDelta_FromInt64((timedelta)t1, ap->descr); + } + else { + ap->descr->f->copyswap(&t1, ip, !PyArray_ISNOTSWAPPED(ap), ap); + return PyTimeDelta_FromInt64((timedelta)t1, ap->descr); + } +} + + +static int +DATETIME_setitem(PyObject *op, char *ov, PyArrayObject *ap) { + datetime temp; /* ensures alignment */ + + if (PyArray_IsScalar(op, Datetime)) { + temp = ((PyDatetimeScalarObject *)op)->obval; + } + else { + temp = PyDateTime_AsInt64(op, ap->descr); + } + if (PyErr_Occurred()) { + if (PySequence_Check(op)) { + PyErr_Clear(); + PyErr_SetString(PyExc_ValueError, _SEQUENCE_MESSAGE); + } + return -1; + } + if (ap == NULL || PyArray_ISBEHAVED(ap)) + *((datetime *)ov)=temp; + else { + ap->descr->f->copyswap(ov, &temp, !PyArray_ISNOTSWAPPED(ap), + ap); + } + return 0; +} + + +static int +TIMEDELTA_setitem(PyObject *op, char *ov, PyArrayObject *ap) { + timedelta temp; /* ensures alignment */ + + if (PyArray_IsScalar(op, Timedelta)) { + temp = ((PyTimedeltaScalarObject *)op)->obval; + } + else { + temp = PyTimeDelta_AsInt64(op, ap->descr); + } + if (PyErr_Occurred()) { + if (PySequence_Check(op)) { + PyErr_Clear(); + PyErr_SetString(PyExc_ValueError, _SEQUENCE_MESSAGE); + } + return -1; + } + if (ap == NULL || PyArray_ISBEHAVED(ap)) + *((timedelta *)ov)=temp; + else { + ap->descr->f->copyswap(ov, &temp, !PyArray_ISNOTSWAPPED(ap), + ap); + } + return 0; +} + + /**begin repeat * * #TYP = CFLOAT, CDOUBLE# @@ -211,6 +586,9 @@ static int /**end repeat**/ +/* These return array scalars which is different than other date-types. + */ + static PyObject * LONGDOUBLE_getitem(char *ip, PyArrayObject *ap) { @@ -684,17 +1062,21 @@ fail: /**begin repeat * * #TOTYPE = BYTE, UBYTE, SHORT, USHORT, INT, UINT, LONG, ULONG, - * LONGLONG, ULONGLONG, FLOAT, DOUBLE, LONGDOUBLE# + * LONGLONG, ULONGLONG, FLOAT, DOUBLE, LONGDOUBLE, DATETIME, + * TIMEDELTA# * #totype = byte, ubyte, short, ushort, int, uint, long, ulong, - * longlong, ulonglong, float, double, longdouble# + * longlong, ulonglong, float, double, longdouble, datetime, + * timedelta# */ /**begin repeat1 * * #FROMTYPE = BYTE, UBYTE, SHORT, USHORT, INT, UINT, LONG, ULONG, - * LONGLONG, ULONGLONG, FLOAT, DOUBLE, LONGDOUBLE# + * LONGLONG, ULONGLONG, FLOAT, DOUBLE, LONGDOUBLE, DATETIME, + * TIMEDELTA# * #fromtype = byte, ubyte, short, ushort, int, uint, long, ulong, - * longlong, ulonglong, float, double, longdouble# + * longlong, ulonglong, float, double, longdouble, datetime, + * timedelta# */ static void @FROMTYPE@_to_@TOTYPE@(@fromtype@ *ip, @totype@ *op, intp n, @@ -728,9 +1110,11 @@ static void /**begin repeat * * #FROMTYPE = BOOL, BYTE, UBYTE, SHORT, USHORT, INT, UINT, LONG, ULONG, - * LONGLONG, ULONGLONG, FLOAT, DOUBLE, LONGDOUBLE# + * LONGLONG, ULONGLONG, FLOAT, DOUBLE, LONGDOUBLE, DATETIME, + * TIMEDELTA# * #fromtype = Bool, byte, ubyte, short, ushort, int, uint, long, ulong, - * longlong, ulonglong, float, double, longdouble# + * longlong, ulonglong, float, double, longdouble, datetime, + * timedelta# */ static void @FROMTYPE@_to_BOOL(@fromtype@ *ip, Bool *op, intp n, @@ -761,9 +1145,11 @@ static void /**begin repeat * #TOTYPE = BYTE, UBYTE, SHORT, USHORT, INT, UINT, LONG, ULONG, - * LONGLONG, ULONGLONG, FLOAT, DOUBLE, LONGDOUBLE# + * LONGLONG, ULONGLONG, FLOAT, DOUBLE, LONGDOUBLE, DATETIME, + * TIMEDELTA# * #totype = byte, ubyte, short, ushort, int, uint, long, ulong, - * longlong, ulonglong, float, double, longdouble# + * longlong, ulonglong, float, double, longdouble, datetime, + * timedelta# */ static void BOOL_to_@TOTYPE@(Bool *ip, @totype@ *op, intp n, @@ -783,9 +1169,11 @@ BOOL_to_@TOTYPE@(Bool *ip, @totype@ *op, intp n, /**begin repeat1 * #FROMTYPE = BOOL, BYTE, UBYTE, SHORT, USHORT, INT, UINT, LONG, ULONG, - * LONGLONG, ULONGLONG, FLOAT, DOUBLE, LONGDOUBLE# + * LONGLONG, ULONGLONG, FLOAT, DOUBLE, LONGDOUBLE, DATETIME, + * TIMEDELTA# * #fromtype = Bool, byte, ubyte, short, ushort, int, uint, long, ulong, - * longlong, ulonglong, float, double, longdouble# + * longlong, ulonglong, float, double, longdouble, datetime, + * timedelta# */ static void @FROMTYPE@_to_@TOTYPE@(@fromtype@ *ip, @totype@ *op, intp n, @@ -827,11 +1215,13 @@ static void * * #FROMTYPE = BOOL, BYTE, UBYTE, SHORT, USHORT, INT, UINT, LONG, ULONG, * LONGLONG, ULONGLONG, FLOAT, DOUBLE, LONGDOUBLE, - * CFLOAT, CDOUBLE, CLONGDOUBLE, STRING, UNICODE, VOID, OBJECT# + * CFLOAT, CDOUBLE, CLONGDOUBLE, STRING, UNICODE, VOID, OBJECT, + * DATETIME, TIMEDELTA# * #fromtype = Bool, byte, ubyte, short, ushort, int, uint, long, ulong, * longlong, ulonglong, float, double, longdouble, - * cfloat, cdouble, clongdouble, char, char, char, PyObject *# - * #skip = 1*17, aip->descr->elsize*3, 1# + * cfloat, cdouble, clongdouble, char, char, char, PyObject *, + * datetime, timedelta# + * #skip = 1*17, aip->descr->elsize*3, 1*3# */ static void @FROMTYPE@_to_OBJECT(@fromtype@ *ip, PyObject **op, intp n, PyArrayObject *aip, @@ -863,6 +1253,8 @@ static void #define _NPY_UNUSEDCFLOAT NPY_UNUSED #define _NPY_UNUSEDCDOUBLE NPY_UNUSED #define _NPY_UNUSEDCLONGDOUBLE NPY_UNUSED +#define _NPY_UNUSEDDATETIME NPY_UNUSED +#define _NPY_UNUSEDTIMEDELTA NPY_UNUSED #define _NPY_UNUSEDSTRING #define _NPY_UNUSEDVOID #define _NPY_UNUSEDUNICODE @@ -871,11 +1263,13 @@ static void * * #TOTYPE = BOOL, BYTE, UBYTE, SHORT, USHORT, INT, UINT, LONG, ULONG, * LONGLONG, ULONGLONG, FLOAT, DOUBLE, LONGDOUBLE, - * CFLOAT, CDOUBLE, CLONGDOUBLE, STRING, UNICODE, VOID# + * CFLOAT, CDOUBLE, CLONGDOUBLE, STRING, UNICODE, VOID, DATETIME, + * TIMEDELTA# * #totype = Bool, byte, ubyte, short, ushort, int, uint, long, ulong, * longlong, ulonglong, float, double, longdouble, - * cfloat, cdouble, clongdouble, char, char, char# - * #skip = 1*17, aip->descr->elsize*3# + * cfloat, cdouble, clongdouble, char, char, char, datetime, + * timedelta# + * #skip = 1*17, aip->descr->elsize*3, 1*2# */ static void OBJECT_to_@TOTYPE@(PyObject **ip, @totype@ *op, intp n, @@ -898,13 +1292,13 @@ OBJECT_to_@TOTYPE@(PyObject **ip, @totype@ *op, intp n, /**begin repeat * - * #from = STRING*20, UNICODE*20, VOID*20# - * #fromtyp = char*60# - * #to = (BOOL, BYTE, UBYTE, SHORT, USHORT, INT, UINT, LONG, ULONG, LONGLONG, ULONGLONG, FLOAT, DOUBLE, LONGDOUBLE, CFLOAT, CDOUBLE, CLONGDOUBLE, STRING, UNICODE, VOID)*3# - * #totyp = (Bool, byte, ubyte, short, ushort, int, uint, long, ulong, longlong, ulonglong, float, double, longdouble, cfloat, cdouble, clongdouble, char, char, char)*3# - * #oskip = (1*17,aop->descr->elsize*3)*3# - * #convert = 1*17, 0*3, 1*17, 0*3, 0*20# - * #convstr = (Int*9, Long*2, Float*3, Complex*3, Tuple*3)*3# + * #from = STRING*22, UNICODE*22, VOID*22# + * #fromtyp = char*66# + * #to = (BOOL, BYTE, UBYTE, SHORT, USHORT, INT, UINT, LONG, ULONG, LONGLONG, ULONGLONG, FLOAT, DOUBLE, LONGDOUBLE, CFLOAT, CDOUBLE, CLONGDOUBLE, STRING, UNICODE, VOID, DATETIME, TIMEDELTA)*3# + * #totyp = (Bool, byte, ubyte, short, ushort, int, uint, long, ulong, longlong, ulonglong, float, double, longdouble, cfloat, cdouble, clongdouble, char, char, char, datetime, timedelta)*3# + * #oskip = (1*17,aop->descr->elsize*3,1*2)*3# + * #convert = 1*17, 0*3, 1*2, 1*17, 0*3, 1*2, 0*22# + * #convstr = (Int*9, Long*2, Float*3, Complex*3, Tuple*3, Long*2)*3# */ static void @from@_to_@to@(@fromtyp@ *ip, @totyp@ *op, intp n, PyArrayObject *aip, @@ -940,10 +1334,10 @@ static void /**begin repeat * - * #to = STRING*17, UNICODE*17, VOID*17# - * #totyp = char*17, char*17, char*17# - * #from = (BOOL, BYTE, UBYTE, SHORT, USHORT, INT, UINT, LONG, ULONG, LONGLONG, ULONGLONG, FLOAT, DOUBLE, LONGDOUBLE, CFLOAT, CDOUBLE, CLONGDOUBLE)*3# - * #fromtyp = (Bool, byte, ubyte, short, ushort, int, uint, long, ulong, longlong, ulonglong, float, double, longdouble, cfloat, cdouble, clongdouble)*3# + * #to = STRING*19, UNICODE*19, VOID*19# + * #totyp = char*19, char*19, char*19# + * #from = (BOOL, BYTE, UBYTE, SHORT, USHORT, INT, UINT, LONG, ULONG, LONGLONG, ULONGLONG, FLOAT, DOUBLE, LONGDOUBLE, CFLOAT, CDOUBLE, CLONGDOUBLE, DATETIME, TIMEDELTA)*3# + * #fromtyp = (Bool, byte, ubyte, short, ushort, int, uint, long, ulong, longlong, ulonglong, float, double, longdouble, cfloat, cdouble, clongdouble, datetime, timedelta)*3# */ static void @from@_to_@to@(@fromtyp@ *ip, @totyp@ *op, intp n, PyArrayObject *aip, @@ -1032,7 +1426,8 @@ BOOL_scan(FILE *fp, Bool *ip, void *NPY_UNUSED(ignore), PyArray_Descr *NPY_UNUSE } /**begin repeat - * #fname = CFLOAT, CDOUBLE, CLONGDOUBLE, OBJECT, STRING, UNICODE, VOID# + * #fname = CFLOAT, CDOUBLE, CLONGDOUBLE, OBJECT, STRING, UNICODE, VOID, + * DATETIME, TIMEDELTA# */ #define @fname@_scan NULL /**end repeat**/ @@ -1040,10 +1435,12 @@ BOOL_scan(FILE *fp, Bool *ip, void *NPY_UNUSED(ignore), PyArray_Descr *NPY_UNUSE /****************** fromstr *************************************/ /**begin repeat - * #fname = BYTE, UBYTE, SHORT, USHORT, INT, UINT, LONG, ULONG, LONGLONG, ULONGLONG# - * #type = byte, ubyte, short, ushort, int, uint, long, ulong, longlong, ulonglong# - * #func = (l, ul)*5# - * #btype = (long, ulong)*5# + * #fname = BYTE, UBYTE, SHORT, USHORT, INT, UINT, LONG, ULONG, LONGLONG, + * ULONGLONG, DATETIME, TIMEDELTA# + * #type = byte, ubyte, short, ushort, int, uint, long, ulong, longlong, + * ulonglong, datetime, timedelta# + * #func = (l, ul)*5, l, l# + * #btype = (long, ulong)*5, long, long# */ static int @fname@_fromstr(char *str, @type@ *ip, char **endptr, PyArray_Descr *NPY_UNUSED(ignore)) @@ -1085,9 +1482,12 @@ static int /**begin repeat * - * #fname = SHORT, USHORT, INT, UINT, LONG, ULONG, LONGLONG, ULONGLONG, FLOAT, DOUBLE, LONGDOUBLE# - * #fsize = SHORT, SHORT, INT, INT, LONG, LONG, LONGLONG, LONGLONG, FLOAT, DOUBLE, LONGDOUBLE# - * #type = short, ushort, int, uint, long, ulong, longlong, ulonglong, float, double, longdouble# + * #fname = SHORT, USHORT, INT, UINT, LONG, ULONG, LONGLONG, ULONGLONG, FLOAT, + * DOUBLE, LONGDOUBLE, DATETIME, TIMEDELTA# + * #fsize = SHORT, SHORT, INT, INT, LONG, LONG, LONGLONG, LONGLONG, FLOAT, + * DOUBLE, LONGDOUBLE, DATETIME, TIMEDELTA# + * #type = short, ushort, int, uint, long, ulong, longlong, ulonglong, float, + * double, longdouble, datetime, timedelta# */ static void @fname@_copyswapn (void *dst, intp dstride, void *src, intp sstride, @@ -1572,8 +1972,8 @@ UNICODE_copyswap (char *dst, char *src, int swap, PyArrayObject *arr) /****************** nonzero **********************************/ /**begin repeat -#fname=BOOL,BYTE,UBYTE,SHORT,USHORT,INT,UINT,LONG,ULONG,LONGLONG,ULONGLONG,FLOAT,DOUBLE,LONGDOUBLE# -#type=Bool, byte, ubyte, short, ushort, int, uint, long, ulong, longlong, ulonglong, float, double, longdouble# +#fname=BOOL,BYTE,UBYTE,SHORT,USHORT,INT,UINT,LONG,ULONG,LONGLONG,ULONGLONG,FLOAT,DOUBLE,LONGDOUBLE,DATETIME,TIMEDELTA# +#type=Bool, byte, ubyte, short, ushort, int, uint, long, ulong, longlong, ulonglong, float, double, longdouble, datetime, timedelta# */ static Bool @fname@_nonzero (@type@ *ip, PyArrayObject *ap) @@ -1766,9 +2166,9 @@ BOOL_compare(Bool *ip1, Bool *ip2, PyArrayObject *NPY_UNUSED(ap)) /**begin repeat * #TYPE = BYTE, UBYTE, SHORT, USHORT, INT, UINT, LONG, ULONG, - * LONGLONG, ULONGLONG# + * LONGLONG, ULONGLONG, DATETIME, TIMEDELTA# * #type = byte, ubyte, short, ushort, int, uint, long, ulong, - * longlong, ulonglong# + * longlong, ulonglong, datetime, timedelta# */ static int @@ -2019,9 +2419,9 @@ finish: /**begin repeat -#fname= BOOL,BYTE, UBYTE, SHORT, USHORT, INT, UINT, LONG, ULONG, LONGLONG, ULONGLONG, FLOAT, DOUBLE, LONGDOUBLE, CFLOAT, CDOUBLE, CLONGDOUBLE# -#type= Bool, byte, ubyte, short, ushort, int, uint, long, ulong, longlong, ulonglong, float, double, longdouble, float, double, longdouble# -#incr= ip++*14, ip+=2*3# +#fname= BOOL,BYTE, UBYTE, SHORT, USHORT, INT, UINT, LONG, ULONG, LONGLONG, ULONGLONG, FLOAT, DOUBLE, LONGDOUBLE, CFLOAT, CDOUBLE, CLONGDOUBLE, DATETIME, TIMEDELTA# +#type= Bool, byte, ubyte, short, ushort, int, uint, long, ulong, longlong, ulonglong, float, double, longdouble, float, double, longdouble, datetime, timedelta# +#incr= ip++*14, ip+=2*3, ip++*2# */ static int @@ -2109,9 +2509,9 @@ BOOL_dot(char *ip1, intp is1, char *ip2, intp is2, char *op, intp n, } /**begin repeat -#name=BYTE, UBYTE, SHORT, USHORT, INT, UINT, LONG, ULONG, LONGLONG, ULONGLONG, FLOAT, DOUBLE, LONGDOUBLE# -#type= byte, ubyte, short, ushort, int, uint, long, ulong, longlong, ulonglong, float, double, longdouble# -#out= long, ulong, long, ulong, long, ulong, long, ulong, longlong, ulonglong, float, double, longdouble# +#name=BYTE, UBYTE, SHORT, USHORT, INT, UINT, LONG, ULONG, LONGLONG, ULONGLONG, FLOAT, DOUBLE, LONGDOUBLE, DATETIME, TIMEDELTA# +#type= byte, ubyte, short, ushort, int, uint, long, ulong, longlong, ulonglong, float, double, longdouble, datetime, timedelta# +#out= long, ulong, long, ulong, long, ulong, long, ulong, longlong, ulonglong, float, double, longdouble, datetime, timedelta# */ static void @name@_dot(char *ip1, intp is1, char *ip2, intp is2, char *op, intp n, @@ -2209,8 +2609,8 @@ finish: } /**begin repeat -#NAME=BYTE,UBYTE,SHORT,USHORT,INT,UINT,LONG,ULONG,LONGLONG,ULONGLONG,FLOAT,DOUBLE,LONGDOUBLE# -#typ=byte,ubyte,short,ushort,int,uint,long,ulong,longlong,ulonglong,float,double,longdouble# +#NAME=BYTE,UBYTE,SHORT,USHORT,INT,UINT,LONG,ULONG,LONGLONG,ULONGLONG,FLOAT,DOUBLE,LONGDOUBLE,DATETIME,TIMEDELTA# +#typ=byte,ubyte,short,ushort,int,uint,long,ulong,longlong,ulonglong,float,double,longdouble,datetime,timedelta# */ static void @NAME@_fill(@typ@ *buffer, intp length, void *NPY_UNUSED(ignored)) @@ -2275,8 +2675,8 @@ static void /**end repeat**/ /**begin repeat -#NAME=SHORT,USHORT,INT,UINT,LONG,ULONG,LONGLONG,ULONGLONG,FLOAT,DOUBLE,LONGDOUBLE,CFLOAT,CDOUBLE,CLONGDOUBLE# -#typ=short,ushort,int,uint,long,ulong,longlong,ulonglong,float,double,longdouble,cfloat,cdouble,clongdouble# +#NAME=SHORT,USHORT,INT,UINT,LONG,ULONG,LONGLONG,ULONGLONG,FLOAT,DOUBLE,LONGDOUBLE,CFLOAT,CDOUBLE,CLONGDOUBLE,DATETIME,TIMEDELTA# +#typ=short,ushort,int,uint,long,ulong,longlong,ulonglong,float,double,longdouble,cfloat,cdouble,clongdouble,datetime,timedelta# */ static void @NAME@_fillwithscalar(@typ@ *buffer, intp length, @typ@ *value, void *NPY_UNUSED(ignored)) @@ -2297,8 +2697,8 @@ static void *************************/ /**begin repeat -#name=BOOL,BYTE, UBYTE, SHORT, USHORT, INT, UINT, LONG, ULONG, LONGLONG, ULONGLONG, FLOAT, DOUBLE, LONGDOUBLE# -#type= Bool, byte, ubyte, short, ushort, int, uint, long, ulong, longlong, ulonglong, float, double, longdouble# +#name=BOOL, BYTE, UBYTE, SHORT, USHORT, INT, UINT, LONG, ULONG, LONGLONG, ULONGLONG, FLOAT, DOUBLE, LONGDOUBLE, DATETIME, TIMEDELTA# +#type= Bool, byte, ubyte, short, ushort, int, uint, long, ulong, longlong, ulonglong, float, double, longdouble, datetime, timedelta# */ static void @name@_fastclip(@type@ *in, intp ni, @type@ *min, @type@ *max, @type@ *out) @@ -2396,8 +2796,8 @@ static void *************************/ /**begin repeat -#name=BOOL,BYTE, UBYTE, SHORT, USHORT, INT, UINT, LONG, ULONG, LONGLONG, ULONGLONG, FLOAT, DOUBLE, LONGDOUBLE,CFLOAT, CDOUBLE, CLONGDOUBLE# -#type= Bool, byte, ubyte, short, ushort, int, uint, long, ulong, longlong, ulonglong, float, double, longdouble,cfloat, cdouble, clongdouble# +#name=BOOL,BYTE, UBYTE, SHORT, USHORT, INT, UINT, LONG, ULONG, LONGLONG, ULONGLONG, FLOAT, DOUBLE, LONGDOUBLE,CFLOAT, CDOUBLE, CLONGDOUBLE, DATETIME, TIMEDELTA# +#type= Bool, byte, ubyte, short, ushort, int, uint, long, ulong, longlong, ulonglong, float, double, longdouble,cfloat, cdouble, clongdouble, datetime, timedelta# */ static void @name@_fastputmask(@type@ *in, Bool *mask, intp ni, @type@ *vals, intp nv) @@ -2433,8 +2833,8 @@ static void *************************/ /**begin repeat -#name=BOOL,BYTE, UBYTE, SHORT, USHORT, INT, UINT, LONG, ULONG, LONGLONG, ULONGLONG, FLOAT, DOUBLE, LONGDOUBLE,CFLOAT, CDOUBLE, CLONGDOUBLE# -#type= Bool, byte, ubyte, short, ushort, int, uint, long, ulong, longlong, ulonglong, float, double, longdouble,cfloat, cdouble, clongdouble# +#name=BOOL,BYTE, UBYTE, SHORT, USHORT, INT, UINT, LONG, ULONG, LONGLONG, ULONGLONG, FLOAT, DOUBLE, LONGDOUBLE,CFLOAT, CDOUBLE, CLONGDOUBLE, DATETIME, TIMEDELTA# +#type= Bool, byte, ubyte, short, ushort, int, uint, long, ulong, longlong, ulonglong, float, double, longdouble,cfloat, cdouble, clongdouble, datetime, timedelta# */ static int @name@_fasttake(@type@ *dest, @type@ *src, intp *indarray, @@ -2550,7 +2950,9 @@ static PyArray_ArrFuncs _Py@NAME@_ArrFuncs = { (PyArray_VectorUnaryFunc*)@from@_to_OBJECT, (PyArray_VectorUnaryFunc*)@from@_to_STRING, (PyArray_VectorUnaryFunc*)@from@_to_UNICODE, - (PyArray_VectorUnaryFunc*)@from@_to_VOID + (PyArray_VectorUnaryFunc*)@from@_to_VOID, + (PyArray_VectorUnaryFunc*)@from@_to_DATETIME, + (PyArray_VectorUnaryFunc*)@from@_to_TIMEDELTA }, (PyArray_GetItemFunc*)@from@_getitem, (PyArray_SetItemFunc*)@from@_setitem, @@ -2598,13 +3000,13 @@ static PyArray_Descr @from@_Descr = { /**begin repeat -#from= BOOL,BYTE,UBYTE,SHORT,USHORT,INT,UINT,LONG,ULONG,LONGLONG,ULONGLONG,FLOAT,DOUBLE,LONGDOUBLE,CFLOAT,CDOUBLE,CLONGDOUBLE,OBJECT# -#num= 1*14,2*3,1# -#fromtyp= Bool, byte, ubyte, short, ushort, int, uint, long, ulong, longlong, ulonglong, float, double, longdouble, float, double, longdouble, PyObject *# -#NAME= Bool, Byte, UByte, Short, UShort, Int, UInt, Long, ULong, LongLong, ULongLong, Float, Double, LongDouble, CFloat, CDouble, CLongDouble, Object# -#kind= GENBOOL, SIGNED, UNSIGNED, SIGNED, UNSIGNED, SIGNED, UNSIGNED, SIGNED, UNSIGNED, SIGNED, UNSIGNED, FLOATING, FLOATING, FLOATING, COMPLEX, COMPLEX, COMPLEX, OBJECT# -#endian= |*3, =*14, |# -#isobject= 0*17,NPY_OBJECT_DTYPE_FLAGS# +#from= BOOL,BYTE,UBYTE,SHORT,USHORT,INT,UINT,LONG,ULONG,LONGLONG,ULONGLONG,FLOAT,DOUBLE,LONGDOUBLE,CFLOAT,CDOUBLE,CLONGDOUBLE,OBJECT,DATETIME,TIMEDELTA# +#num= 1*14,2*3,1*3# +#fromtyp= Bool, byte, ubyte, short, ushort, int, uint, long, ulong, longlong, ulonglong, float, double, longdouble, float, double, longdouble, PyObject *, datetime, timedelta# +#NAME= Bool, Byte, UByte, Short, UShort, Int, UInt, Long, ULong, LongLong, ULongLong, Float, Double, LongDouble, CFloat, CDouble, CLongDouble, Object, Datetime, Timedelta# +#kind= GENBOOL, SIGNED, UNSIGNED, SIGNED, UNSIGNED, SIGNED, UNSIGNED, SIGNED, UNSIGNED, SIGNED, UNSIGNED, FLOATING, FLOATING, FLOATING, COMPLEX, COMPLEX, COMPLEX, OBJECT, DATETIME, TIMEDELTA# +#endian= |*3, =*14, |, =*2# +#isobject= 0*17,NPY_OBJECT_DTYPE_FLAGS,0*2# */ static PyArray_ArrFuncs _Py@NAME@_ArrFuncs = { @@ -2629,7 +3031,9 @@ static PyArray_ArrFuncs _Py@NAME@_ArrFuncs = { (PyArray_VectorUnaryFunc*)@from@_to_OBJECT, (PyArray_VectorUnaryFunc*)@from@_to_STRING, (PyArray_VectorUnaryFunc*)@from@_to_UNICODE, - (PyArray_VectorUnaryFunc*)@from@_to_VOID + (PyArray_VectorUnaryFunc*)@from@_to_VOID, + (PyArray_VectorUnaryFunc*)@from@_to_DATETIME, + (PyArray_VectorUnaryFunc*)@from@_to_TIMEDELTA }, (PyArray_GetItemFunc*)@from@_getitem, (PyArray_SetItemFunc*)@from@_setitem, @@ -2675,6 +3079,25 @@ NPY_NO_EXPORT PyArray_Descr @from@_Descr = { /**end repeat**/ +static void +_init_datetime_descr(PyArray_Descr *descr) +{ + PyArray_DatetimeMetaData *dt_data; + PyObject *cobj; + + dt_data = _pya_malloc(sizeof(PyArray_DatetimeMetaData)); + dt_data->base = NPY_FR_us; + dt_data->num = 1; + dt_data->den = 1; + dt_data->events = 1; + + cobj = PyCObject_FromVoidPtr((void *)dt_data, _pya_free); + descr->metadata = PyDict_New(); + PyDict_SetItemString(descr->metadata, NPY_METADATA_DTSTR, cobj); + Py_DECREF(cobj); + +} + #define _MAX_LETTER 128 static char _letter_to_num[_MAX_LETTER]; @@ -2700,6 +3123,8 @@ static PyArray_Descr *_builtin_descrs[] = { &STRING_Descr, &UNICODE_Descr, &VOID_Descr, + &DATETIME_Descr, + &TIMEDELTA_Descr, }; /*NUMPY_API @@ -2753,6 +3178,14 @@ PyArray_DescrFromType(int type) else { Py_INCREF(ret); } + + /* Make sure dtype metadata is initialized for DATETIME */ + if (PyTypeNum_ISDATETIME(type)) { + if (ret->metadata == NULL) { + _init_datetime_descr(ret); + } + } + return ret; } @@ -2768,14 +3201,14 @@ set_typeinfo(PyObject *dict) } /**begin repeat -#name=BOOL,BYTE,UBYTE,SHORT,USHORT,INT,UINT,INTP,UINTP,LONG,ULONG,LONGLONG,ULONGLONG,FLOAT,DOUBLE,LONGDOUBLE,CFLOAT,CDOUBLE,CLONGDOUBLE,OBJECT,STRING,UNICODE,VOID# +#name=BOOL,BYTE,UBYTE,SHORT,USHORT,INT,UINT,INTP,UINTP,LONG,ULONG,LONGLONG,ULONGLONG,FLOAT,DOUBLE,LONGDOUBLE,CFLOAT,CDOUBLE,CLONGDOUBLE,OBJECT,STRING,UNICODE,VOID,DATETIME,TIMEDELTA# */ _letter_to_num[PyArray_@name@LTR] = PyArray_@name@; /**end repeat**/ _letter_to_num[PyArray_STRINGLTR2] = PyArray_STRING; /**begin repeat -#name=BOOL,BYTE,UBYTE,SHORT,USHORT,INT,UINT,LONG,ULONG,LONGLONG,ULONGLONG,FLOAT,DOUBLE,LONGDOUBLE,CFLOAT,CDOUBLE,CLONGDOUBLE,OBJECT,STRING,UNICODE,VOID# +#name=BOOL,BYTE,UBYTE,SHORT,USHORT,INT,UINT,LONG,ULONG,LONGLONG,ULONGLONG,FLOAT,DOUBLE,LONGDOUBLE,CFLOAT,CDOUBLE,CLONGDOUBLE,OBJECT,STRING,UNICODE,VOID,DATETIME,TIMEDELTA# */ @name@_Descr.fields = Py_None; /**end repeat**/ @@ -2857,6 +3290,22 @@ set_typeinfo(PyObject *dict) (PyObject *)\ &PyVoidArrType_Type)); Py_DECREF(s); + PyDict_SetItemString(infodict, "DATETIME", + s=Py_BuildValue("ciiiNNO", PyArray_DATETIMELTR, + PyArray_DATETIME, + sizeof(npy_datetime)*CHAR_BIT, + _ALIGN(npy_datetime), + MyPyLong_FromInt64(MAX_DATETIME), MyPyLong_FromInt64(MIN_DATETIME), + (PyObject *)&PyDatetimeArrType_Type)); + Py_DECREF(s); + PyDict_SetItemString(infodict, "TIMEDELTA", + s=Py_BuildValue("ciiiNNO", PyArray_TIMEDELTALTR, + PyArray_TIMEDELTA, + sizeof(npy_timedelta)*CHAR_BIT, + _ALIGN(npy_timedelta), + MyPyLong_FromInt64(MAX_TIMEDELTA), MyPyLong_FromInt64(MIN_TIMEDELTA), + (PyObject *)&PyTimedeltaArrType_Type)); + Py_DECREF(s); #define SETTYPE(name) \ Py_INCREF(&Py##name##ArrType_Type); \ @@ -2868,6 +3317,7 @@ set_typeinfo(PyObject *dict) SETTYPE(Integer); SETTYPE(Inexact); SETTYPE(SignedInteger); + SETTYPE(TimeInteger); SETTYPE(UnsignedInteger); SETTYPE(Floating); SETTYPE(ComplexFloating); diff --git a/numpy/core/src/multiarray/common.c b/numpy/core/src/multiarray/common.c index f764f1830..83d9a1a72 100644 --- a/numpy/core/src/multiarray/common.c +++ b/numpy/core/src/multiarray/common.c @@ -564,114 +564,3 @@ _IsWriteable(PyArrayObject *ap) } return TRUE; } - -NPY_NO_EXPORT void -_unaligned_strided_byte_copy(char *dst, intp outstrides, char *src, - intp instrides, intp N, int elsize) -{ - intp i; - char *tout = dst; - char *tin = src; - -#define _COPY_N_SIZE(size) \ - for(i=0; i<N; i++) { \ - memcpy(tout, tin, size); \ - tin += instrides; \ - tout += outstrides; \ - } \ - return - - switch(elsize) { - case 8: - _COPY_N_SIZE(8); - case 4: - _COPY_N_SIZE(4); - case 1: - _COPY_N_SIZE(1); - case 2: - _COPY_N_SIZE(2); - case 16: - _COPY_N_SIZE(16); - default: - _COPY_N_SIZE(elsize); - } -#undef _COPY_N_SIZE - -} - -NPY_NO_EXPORT void -_strided_byte_swap(void *p, intp stride, intp n, int size) -{ - char *a, *b, c = 0; - int j, m; - - switch(size) { - case 1: /* no byteswap necessary */ - break; - case 4: - for (a = (char*)p; n > 0; n--, a += stride - 1) { - b = a + 3; - c = *a; *a++ = *b; *b-- = c; - c = *a; *a = *b; *b = c; - } - break; - case 8: - for (a = (char*)p; n > 0; n--, a += stride - 3) { - b = a + 7; - c = *a; *a++ = *b; *b-- = c; - c = *a; *a++ = *b; *b-- = c; - c = *a; *a++ = *b; *b-- = c; - c = *a; *a = *b; *b = c; - } - break; - case 2: - for (a = (char*)p; n > 0; n--, a += stride) { - b = a + 1; - c = *a; *a = *b; *b = c; - } - break; - default: - m = size/2; - for (a = (char *)p; n > 0; n--, a += stride - m) { - b = a + (size - 1); - for (j = 0; j < m; j++) { - c=*a; *a++ = *b; *b-- = c; - } - } - break; - } -} - -NPY_NO_EXPORT void -byte_swap_vector(void *p, intp n, int size) -{ - _strided_byte_swap(p, (intp) size, n, size); - return; -} - -/* If numitems > 1, then dst must be contiguous */ -NPY_NO_EXPORT void -copy_and_swap(void *dst, void *src, int itemsize, intp numitems, - intp srcstrides, int swap) -{ - intp i; - char *s1 = (char *)src; - char *d1 = (char *)dst; - - - if ((numitems == 1) || (itemsize == srcstrides)) { - memcpy(d1, s1, itemsize*numitems); - } - else { - for (i = 0; i < numitems; i++) { - memcpy(d1, s1, itemsize); - d1 += itemsize; - s1 += srcstrides; - } - } - - if (swap) { - byte_swap_vector(d1, numitems, itemsize); - } -} - diff --git a/numpy/core/src/multiarray/common.h b/numpy/core/src/multiarray/common.h index 3dbf1994e..67ee9b66f 100644 --- a/numpy/core/src/multiarray/common.h +++ b/numpy/core/src/multiarray/common.h @@ -27,20 +27,6 @@ _IsAligned(PyArrayObject *ap); NPY_NO_EXPORT Bool _IsWriteable(PyArrayObject *ap); -NPY_NO_EXPORT void -_unaligned_strided_byte_copy(char *dst, intp outstrides, char *src, - intp instrides, intp N, int elsize); - -NPY_NO_EXPORT void -_strided_byte_swap(void *p, intp stride, intp n, int size); - -NPY_NO_EXPORT void -byte_swap_vector(void *p, intp n, int size); - -NPY_NO_EXPORT void -copy_and_swap(void *dst, void *src, int itemsize, intp numitems, - intp srcstrides, int swap); - #ifndef Py_UNICODE_WIDE #include "ucsnarrow.h" #endif diff --git a/numpy/core/src/multiarray/convert_datatype.c b/numpy/core/src/multiarray/convert_datatype.c index eb5d79e3f..a667bb30e 100644 --- a/numpy/core/src/multiarray/convert_datatype.c +++ b/numpy/core/src/multiarray/convert_datatype.c @@ -510,6 +510,10 @@ PyArray_CanCastSafely(int fromtype, int totype) if (totype == PyArray_BOOL) { return 0; } + if (fromtype == PyArray_DATETIME || fromtype == PyArray_TIMEDELTA || + totype == PyArray_DATETIME || totype == PyArray_TIMEDELTA) { + return 0; + } if (totype == PyArray_OBJECT || totype == PyArray_VOID) { return 1; } diff --git a/numpy/core/src/multiarray/ctors.c b/numpy/core/src/multiarray/ctors.c index 27cb54d1d..e4873913a 100644 --- a/numpy/core/src/multiarray/ctors.c +++ b/numpy/core/src/multiarray/ctors.c @@ -417,6 +417,116 @@ _unaligned_strided_byte_move(char *dst, intp outstrides, char *src, } +NPY_NO_EXPORT void +_unaligned_strided_byte_copy(char *dst, intp outstrides, char *src, + intp instrides, intp N, int elsize) +{ + intp i; + char *tout = dst; + char *tin = src; + +#define _COPY_N_SIZE(size) \ + for(i=0; i<N; i++) { \ + memcpy(tout, tin, size); \ + tin += instrides; \ + tout += outstrides; \ + } \ + return + + switch(elsize) { + case 8: + _COPY_N_SIZE(8); + case 4: + _COPY_N_SIZE(4); + case 1: + _COPY_N_SIZE(1); + case 2: + _COPY_N_SIZE(2); + case 16: + _COPY_N_SIZE(16); + default: + _COPY_N_SIZE(elsize); + } +#undef _COPY_N_SIZE + +} + +NPY_NO_EXPORT void +_strided_byte_swap(void *p, intp stride, intp n, int size) +{ + char *a, *b, c = 0; + int j, m; + + switch(size) { + case 1: /* no byteswap necessary */ + break; + case 4: + for (a = (char*)p; n > 0; n--, a += stride - 1) { + b = a + 3; + c = *a; *a++ = *b; *b-- = c; + c = *a; *a = *b; *b = c; + } + break; + case 8: + for (a = (char*)p; n > 0; n--, a += stride - 3) { + b = a + 7; + c = *a; *a++ = *b; *b-- = c; + c = *a; *a++ = *b; *b-- = c; + c = *a; *a++ = *b; *b-- = c; + c = *a; *a = *b; *b = c; + } + break; + case 2: + for (a = (char*)p; n > 0; n--, a += stride) { + b = a + 1; + c = *a; *a = *b; *b = c; + } + break; + default: + m = size/2; + for (a = (char *)p; n > 0; n--, a += stride - m) { + b = a + (size - 1); + for (j = 0; j < m; j++) { + c=*a; *a++ = *b; *b-- = c; + } + } + break; + } +} + +NPY_NO_EXPORT void +byte_swap_vector(void *p, intp n, int size) +{ + _strided_byte_swap(p, (intp) size, n, size); + return; +} + +/* If numitems > 1, then dst must be contiguous */ +NPY_NO_EXPORT void +copy_and_swap(void *dst, void *src, int itemsize, intp numitems, + intp srcstrides, int swap) +{ + intp i; + char *s1 = (char *)src; + char *d1 = (char *)dst; + + + if ((numitems == 1) || (itemsize == srcstrides)) { + memcpy(d1, s1, itemsize*numitems); + } + else { + for (i = 0; i < numitems; i++) { + memcpy(d1, s1, itemsize); + d1 += itemsize; + s1 += srcstrides; + } + } + + if (swap) { + byte_swap_vector(d1, numitems, itemsize); + } +} + static int _copy_from0d(PyArrayObject *dest, PyArrayObject *src, int usecopy, int swap) { @@ -1433,23 +1543,6 @@ PyArray_NewFromDescr(PyTypeObject *subtype, PyArray_Descr *descr, int nd, return NULL; } -/* - * This is the main array creation routine. - * - * Flags argument has multiple related meanings - * depending on data and strides: - * - * If data is given, then flags is flags associated with data. - * If strides is not given, then a contiguous strides array will be created - * and the CONTIGUOUS bit will be set. If the flags argument - * has the FORTRAN bit set, then a FORTRAN-style strides array will be - * created (and of course the FORTRAN flag bit will be set). - * - * If data is not given but created here, then flags will be DEFAULT - * and a non-zero flags argument can be used to indicate a FORTRAN style - * array is desired. - */ - /*NUMPY_API * Generic new array creation routine. */ @@ -3234,6 +3327,23 @@ PyArray_FromIter(PyObject *obj, PyArray_Descr *dtype, intp count) return (PyObject *)ret; } +/* + * This is the main array creation routine. + * + * Flags argument has multiple related meanings + * depending on data and strides: + * + * If data is given, then flags is flags associated with data. + * If strides is not given, then a contiguous strides array will be created + * and the CONTIGUOUS bit will be set. If the flags argument + * has the FORTRAN bit set, then a FORTRAN-style strides array will be + * created (and of course the FORTRAN flag bit will be set). + * + * If data is not given but created here, then flags will be DEFAULT + * and a non-zero flags argument can be used to indicate a FORTRAN style + * array is desired. + */ + NPY_NO_EXPORT size_t _array_fill_strides(intp *strides, intp *dims, int nd, size_t itemsize, int inflag, int *objflags) diff --git a/numpy/core/src/multiarray/ctors.h b/numpy/core/src/multiarray/ctors.h index 50b321ea7..5fd1d8e58 100644 --- a/numpy/core/src/multiarray/ctors.h +++ b/numpy/core/src/multiarray/ctors.h @@ -53,4 +53,18 @@ NPY_NO_EXPORT size_t _array_fill_strides(intp *strides, intp *dims, int nd, size_t itemsize, int inflag, int *objflags); +NPY_NO_EXPORT void +_unaligned_strided_byte_copy(char *dst, intp outstrides, char *src, + intp instrides, intp N, int elsize); + +NPY_NO_EXPORT void +_strided_byte_swap(void *p, intp stride, intp n, int size); + +NPY_NO_EXPORT void +copy_and_swap(void *dst, void *src, int itemsize, intp numitems, + intp srcstrides, int swap); + +NPY_NO_EXPORT void +byte_swap_vector(void *p, intp n, int size); + #endif diff --git a/numpy/core/src/multiarray/descriptor.c b/numpy/core/src/multiarray/descriptor.c index 45fdc2cea..edcab8b09 100644 --- a/numpy/core/src/multiarray/descriptor.c +++ b/numpy/core/src/multiarray/descriptor.c @@ -125,6 +125,21 @@ _check_for_commastring(char *type, int len) return 0; } +static int +_check_for_datetime(char *type, int len) +{ + if (len < 1) return 0; + if (type[1] == '8' && (type[0] == 'M' || type[0] == 'm')) + return 1; + if (len < 10) return 0; + if (strncmp(type, "datetime64", 10) == 0) return 1; + if (len < 11) return 0; + if (strncmp(type, "timedelta64", 11) == 0) return 1; + return 0; +} + + + #undef _chk_byteorder static PyArray_Descr * @@ -175,6 +190,12 @@ _convert_from_tuple(PyObject *obj) type->elsize = itemsize; } } + else if PyDict_Check(val) { /* Assume it's a metadata dictionary */ + if (PyDict_Merge(type->metadata, val, 0) == -1) { + Py_DECREF(type); + return NULL; + } + } else { /* * interpret next item as shape (if it's a tuple) @@ -229,7 +250,11 @@ _convert_from_tuple(PyObject *obj) * * (field-name, data-type (either a list or a string), and an optional * shape parameter). + * + * field-name can be a string or a 2-tuple + * data-type can now be a list, string, or 2-tuple (string, metadata dictionary)) */ + static PyArray_Descr * _convert_from_array_descr(PyObject *obj, int align) { @@ -443,6 +468,241 @@ _convert_from_list(PyObject *obj, int align) return NULL; } +static char *_datetime_strings[] = { + NPY_STR_Y, + NPY_STR_M, + NPY_STR_W, + NPY_STR_B, + NPY_STR_D, + NPY_STR_h, + NPY_STR_s, + NPY_STR_ms, + NPY_STR_us, + NPY_STR_ns, + NPY_STR_ps, + NPY_STR_fs, + NPY_STR_as +}; + +static NPY_DATETIMEUNIT + _unit_from_str(char *base) +{ + NPY_DATETIMEUNIT unit; + + if (base == NULL) + return NPY_DATETIME_DEFAULTUNIT; + + unit = NPY_FR_Y; + while (unit < NPY_DATETIME_NUMUNITS) { + if (strcmp(base, _datetime_strings[unit]) == 0) + break; + unit++; + } + + if (unit == NPY_DATETIME_NUMUNITS) + return NPY_DATETIME_DEFAULTUNIT; + + return unit; +} + +static int _multiples_table[16][4] = { + {12, 52, 365}, /* NPY_FR_Y */ + {NPY_FR_M, NPY_FR_W, NPY_FR_D}, + {4, 30, 720}, /* NPY_FR_M */ + {NPY_FR_W, NPY_FR_D, NPY_FR_h}, + {5, 7, 168, 10080}, /* NPY_FR_W */ + {NPY_FR_B, NPY_FR_D, NPY_FR_h, NPY_FR_m}, + {24, 1440, 86400}, /* NPY_FR_B */ + {NPY_FR_h, NPY_FR_m, NPY_FR_s}, + {24, 1440, 86400}, /* NPY_FR_D */ + {NPY_FR_h, NPY_FR_m, NPY_FR_s}, + {60, 3600}, /* NPY_FR_h */ + {NPY_FR_m, NPY_FR_s}, + {60, 60000}, /* NPY_FR_m */ + {NPY_FR_s, NPY_FR_ms}, + {1000, 1000000}, /* >=NPY_FR_s */ + {0, 0} +}; + + +/* Translate divisors into multiples of smaller units */ +static int +_convert_divisor_to_multiple(PyArray_DatetimeMetaData *meta) +{ + int i, num, ind; + int *totry; + NPY_DATETIMEUNIT *baseunit; + int q, r; + + ind = ((int)meta->base - (int)NPY_FR_Y)*2; + totry = _multiples_table[ind]; + baseunit = (NPY_DATETIMEUNIT *)_multiples_table[ind+1]; + + num = 3; + if (meta->base == NPY_FR_W) num = 4; + else if (meta->base > NPY_FR_D) num = 2; + + if (meta->base >= NPY_FR_s) { + ind = ((int)NPY_FR_s - (int)NPY_FR_Y)*2; + totry = _multiples_table[ind]; + baseunit = (NPY_DATETIMEUNIT *)_multiples_table[ind+1]; + baseunit[0] = meta->base - 1; + baseunit[1] = meta->base - 2; + if (meta->base == NPY_DATETIME_NUMUNITS-1) num = 1; + } + + for (i=0; i<num; i++) { + q = totry[i] / meta->den; + r = totry[i] % meta->den; + if (r==0) break; + } + if (i==num) { + PyErr_Format(PyExc_ValueError, "divisor (%d) is not a multiple of a lower-unit", meta->den); + return -1; + } + meta->base = (NPY_DATETIMEUNIT) baseunit[i]; + meta->den = 1; + meta->num = q; + + return 0; +} + + +static PyObject * +_get_datetime_tuple_from_cobj(PyObject *cobj) +{ + PyArray_DatetimeMetaData *dt_data; + PyObject *dt_tuple; + + dt_data = PyCObject_AsVoidPtr(cobj); + dt_tuple = PyTuple_New(4); + + PyTuple_SET_ITEM(dt_tuple, 0, + PyString_FromString(_datetime_strings[dt_data->base])); + PyTuple_SET_ITEM(dt_tuple, 1, + PyInt_FromLong(dt_data->num)); + PyTuple_SET_ITEM(dt_tuple, 2, + PyInt_FromLong(dt_data->den)); + PyTuple_SET_ITEM(dt_tuple, 3, + PyInt_FromLong(dt_data->events)); + + return dt_tuple; +} + +static PyObject * +_convert_datetime_tuple_to_cobj(PyObject *tuple) +{ + PyArray_DatetimeMetaData *dt_data; + + dt_data = _pya_malloc(sizeof(PyArray_DatetimeMetaData)); + + dt_data->base = _unit_from_str\ + (PyString_AsString(PyTuple_GET_ITEM(tuple, 0))); + + /* Assumes other objects are Python integers */ + dt_data->num = PyInt_AS_LONG(PyTuple_GET_ITEM(tuple, 1)); + dt_data->den = PyInt_AS_LONG(PyTuple_GET_ITEM(tuple, 2)); + dt_data->events = PyInt_AS_LONG(PyTuple_GET_ITEM(tuple, 3)); + + if (dt_data->den > 1) { + if (_convert_divisor_to_multiple(dt_data) < 0) return NULL; + } + + return PyCObject_FromVoidPtr((void *)dt_data, _pya_free); +} + +static PyArray_Descr * +_convert_from_datetime_tuple(PyObject *obj) +{ + PyArray_Descr *new; + PyObject *dt_tuple; + PyObject *dt_cobj; + PyObject *datetime; + static PyObject *freq_key=NULL; + + + if (!PyTuple_Check(obj) || PyTuple_GET_SIZE(obj)!=2) { + PyErr_SetString(PyExc_RuntimeError, "_datetimestring is " \ + "not returning a tuple with length 2"); + return NULL; + } + + dt_tuple = PyTuple_GET_ITEM(obj, 0); + datetime = PyTuple_GET_ITEM(obj, 1); + if (!PyTuple_Check(dt_tuple) || PyTuple_GET_SIZE(dt_tuple) != 4 || \ + !PyInt_Check(datetime)) { + PyErr_SetString(PyExc_RuntimeError, "_datetimestring is " \ + "not returning a length 4 tuple and an integer"); + return NULL; + } + + /* Create new timedelta or datetime dtype */ + if (PyObject_IsTrue(datetime)) { + new = PyArray_DescrNewFromType(PyArray_DATETIME); + } + else { + new = PyArray_DescrNewFromType(PyArray_TIMEDELTA); + } + + if (new == NULL) return NULL; + + /* Add correct metadata */ + if (freq_key == NULL) { + freq_key = PyString_InternFromString(NPY_METADATA_DTSTR); + if (freq_key == NULL) return NULL; + } + + if (new->metadata == NULL) { + if ((new->metadata = PyDict_New())== NULL) return NULL; + } + if (!PyDict_Check(new->metadata)) { + PyErr_SetString(PyExc_RuntimeError, "metadata is not a dictionary"); + return NULL; + } + + dt_cobj = _convert_datetime_tuple_to_cobj(dt_tuple); + if (dt_cobj == NULL) { /* Failure in conversion */ + Py_DECREF(new); + return NULL; + } + + /* Assume this sets a new reference to dt_cobj */ + PyDict_SetItem(new->metadata, freq_key, dt_cobj); + Py_DECREF(dt_cobj); + + return new; +} + + +static PyArray_Descr * +_convert_from_datetime(PyObject *obj) +{ + PyObject *tupleobj; + PyArray_Descr *res; + PyObject *_numpy_internal; + + if (!PyString_Check(obj)) { + return NULL; + } + _numpy_internal = PyImport_ImportModule("numpy.core._internal"); + if (_numpy_internal == NULL) { + return NULL; + } + tupleobj = PyObject_CallMethod(_numpy_internal, "_datetimestring", "O", obj); + Py_DECREF(_numpy_internal); + if (!tupleobj) return NULL; + /* tuple of a standard tuple (baseunit, num, den, events) and a + timedelta boolean + */ + res = _convert_from_datetime_tuple(tupleobj); + Py_DECREF(tupleobj); + if (!res && !PyErr_Occurred()) { + PyErr_SetString(PyExc_ValueError, "invalid data-type"); + return NULL; + } + return res; +} + /* * comma-separated string @@ -552,6 +812,8 @@ _use_inherit(PyArray_Descr *type, PyObject *newobj, int *errflag) * must have at least two and up to four * keys These must all be sequences of the same length. * + * can also have an additional key called "metadata" which can be any dictionary + * * "names" --- field names * "formats" --- the data-type descriptors for the field. * @@ -605,6 +867,7 @@ _convert_from_dict(PyObject *obj, int align) PyArray_Descr *new; PyObject *fields = NULL; PyObject *names, *offsets, *descrs, *titles; + PyObject *metadata; int n, i; int totalsize; int maxalign = 0; @@ -745,6 +1008,19 @@ _convert_from_dict(PyObject *obj, int align) new->names = names; new->fields = fields; new->hasobject = dtypeflags; + + metadata = PyDict_GetItemString(obj, "metadata"); + + if (new->metadata == NULL) { + new->metadata = metadata; + Py_XINCREF(new->metadata); + } + else if (metadata != NULL) { + if (PyDict_Merge(new->metadata, metadata, 0) == -1) { + Py_DECREF(new); + return NULL; + } + } return new; fail: @@ -868,6 +1144,14 @@ PyArray_DescrConverter(PyObject *obj, PyArray_Descr **at) if (len <= 0) { goto fail; } + /* check for datetime format */ + if ((len > 1) && _check_for_datetime(type, len)) { + *at = _convert_from_datetime(obj); + if (*at) { + return PY_SUCCEED; + } + return PY_FAIL; + } /* check for commas present or first (or second) element a digit */ if (_check_for_commastring(type, len)) { *at = _convert_from_commastring(obj, 0); @@ -1043,7 +1327,9 @@ PyArray_DescrNew(PyArray_Descr *base) Py_INCREF(new->subarray->shape); Py_INCREF(new->subarray->base); } - Py_XINCREF(new->typeobj); + Py_XINCREF(new->typeobj); + Py_XINCREF(new->metadata); + return new; } @@ -1070,6 +1356,7 @@ arraydescr_dealloc(PyArray_Descr *self) Py_DECREF(self->subarray->base); _pya_free(self->subarray); } + Py_XDECREF(self->metadata); self->ob_type->tp_free((PyObject *)self); } @@ -1110,13 +1397,55 @@ arraydescr_subdescr_get(PyArray_Descr *self) self->subarray->shape); } +static PyObject * +_append_to_datetime_typestr(PyArray_Descr *self, PyObject *ret) +{ + PyObject *tmp; + PyObject *res; + int num, den, events; + char *basestr; + PyArray_DatetimeMetaData *dt_data; + + /* This shouldn't happen */ + if (self->metadata == NULL) return ret; + + tmp = PyDict_GetItemString(self->metadata, NPY_METADATA_DTSTR); + dt_data = PyCObject_AsVoidPtr(tmp); + num = dt_data->num; + den = dt_data->den; + events = dt_data->events; + basestr = _datetime_strings[dt_data->base]; + + if (num == 1) { + tmp = PyString_FromString(basestr); + } + else { + tmp = PyString_FromFormat("%d%s", num, basestr); + } + if (den != 1) { + res = PyString_FromFormat("/%d", den); + PyString_ConcatAndDel(&tmp, res); + } + + res = PyString_FromString("["); + PyString_ConcatAndDel(&res, tmp); + PyString_ConcatAndDel(&res, PyString_FromString("]")); + if (events != 1) { + tmp = PyString_FromFormat("//%d", events); + PyString_ConcatAndDel(&res, tmp); + } + PyString_ConcatAndDel(&ret, res); + return ret; +} + NPY_NO_EXPORT PyObject * arraydescr_protocol_typestr_get(PyArray_Descr *self) { char basic_ = self->kind; char endian = self->byteorder; int size = self->elsize; - + PyObject *ret; + if (endian == '=') { endian = '<'; if (!PyArray_IsNativeByteOrder(endian)) { @@ -1126,7 +1455,13 @@ arraydescr_protocol_typestr_get(PyArray_Descr *self) if (self->type_num == PyArray_UNICODE) { size >>= 2; } - return PyString_FromFormat("%c%c%d", endian, basic_, size); + + ret = PyString_FromFormat("%c%c%d", endian, basic_, size); + if (PyDataType_ISDATETIME(self)) { + ret = _append_to_datetime_typestr(self, ret); + } + return ret; + } static PyObject * @@ -1165,6 +1500,9 @@ arraydescr_typename_get(PyArray_Descr *self) p = PyString_FromFormat("%d", self->elsize * 8); PyString_ConcatAndDel(&res, p); } + if (PyDataType_ISDATETIME(self)) { + res = _append_to_datetime_typestr(self, res); + } return res; } @@ -1301,6 +1639,16 @@ arraydescr_fields_get(PyArray_Descr *self) } static PyObject * +arraydescr_metadata_get(PyArray_Descr *self) +{ + if (self->metadata == NULL) { + Py_INCREF(Py_None); + return Py_None; + } + return PyDictProxy_New(self->metadata); +} + +static PyObject * arraydescr_hasobject_get(PyArray_Descr *self) { PyObject *res; @@ -1407,6 +1755,9 @@ static PyGetSetDef arraydescr_getsets[] = { {"fields", (getter)arraydescr_fields_get, NULL, NULL, NULL}, + {"metadata", + (getter)arraydescr_metadata_get, + NULL, NULL, NULL}, {"names", (getter)arraydescr_names_get, (setter)arraydescr_names_set, @@ -1417,21 +1768,42 @@ static PyGetSetDef arraydescr_getsets[] = { {NULL, NULL, NULL, NULL, NULL}, }; +static int +_invalid_metadata_check(PyObject *metadata) +{ + PyObject *res; + + /* borrowed reference */ + res = PyDict_GetItemString(metadata, NPY_METADATA_DTSTR); + if (res == NULL) return 0; + + PyErr_SetString(PyExc_ValueError, "cannot set " NPY_METADATA_DTSTR \ + " in dtype metadata"); + return 1; +} + static PyObject * arraydescr_new(PyTypeObject *NPY_UNUSED(subtype), PyObject *args, PyObject *kwds) { - PyObject *odescr; + PyObject *odescr, *ometadata=NULL; PyArray_Descr *descr, *conv; Bool align = FALSE; Bool copy = FALSE; - static char *kwlist[] = {"dtype", "align", "copy", NULL}; + Bool copied = FALSE; + static char *kwlist[] = {"dtype", "align", "copy", "metadata", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O&O&", + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O&O&O!", kwlist, &odescr, PyArray_BoolConverter, &align, - PyArray_BoolConverter, ©)) { + PyArray_BoolConverter, ©, + &PyDict_Type, &ometadata + )) { return NULL; } + + if ((ometadata != NULL) && (_invalid_metadata_check(ometadata))) + return NULL; + if (align) { if (!PyArray_DescrAlignConverter(odescr, &conv)) { return NULL; @@ -1445,10 +1817,64 @@ arraydescr_new(PyTypeObject *NPY_UNUSED(subtype), PyObject *args, PyObject *kwds descr = PyArray_DescrNew(conv); Py_DECREF(conv); conv = descr; + copied = TRUE; + } + + if ((ometadata != NULL)) { + /* We need to be sure to make a new copy of the data-type and any + underlying dictionary */ + if (!copied) { + descr = PyArray_DescrNew(conv); + Py_DECREF(conv); + conv = descr; + } + if ((conv->metadata != NULL)) { + /* Make a copy of the metadata before merging with ometadata + so that this data-type descriptor has it's own copy + */ + odescr = conv->metadata; /* Save a reference */ + conv->metadata = PyDict_Copy(odescr); + Py_DECREF(odescr); /* Decrement the old reference */ + + /* Update conv->metadata with anything new in metadata + keyword, but do not over-write anything already there + */ + if (PyDict_Merge(conv->metadata, ometadata, 0) != 0) { + Py_DECREF(conv); + return NULL; + } + } + else { + /* Make a copy of the input dictionary */ + conv->metadata = PyDict_Copy(ometadata); + } } return (PyObject *)conv; } +/* Return a tuple of (cleaned metadata dictionary, + tuple with (str, num, events)) +*/ +static PyObject * +_get_pickleabletype_from_metadata(PyObject *metadata) +{ + PyObject *newdict; + PyObject *newtup, *dt_tuple; + PyObject *cobj; + + newdict = PyDict_Copy(metadata); + PyDict_DelItemString(newdict, NPY_METADATA_DTSTR); + newtup = PyTuple_New(2); + PyTuple_SET_ITEM(newtup, 0, newdict); + + cobj = PyDict_GetItemString(metadata, NPY_METADATA_DTSTR); + dt_tuple = _get_datetime_tuple_from_cobj(cobj); + + PyTuple_SET_ITEM(newtup, 1, dt_tuple); + + return newtup; +} + /* return a tuple of (callable object, args, state). */ static PyObject * @@ -1459,7 +1885,7 @@ arraydescr_reduce(PyArray_Descr *self, PyObject *NPY_UNUSED(args)) * change the format. Be sure to handle the old versions in * arraydescr_setstate. */ - const int version = 3; + const int version = 4; PyObject *ret, *mod, *obj; PyObject *state; char endian; @@ -1507,7 +1933,7 @@ arraydescr_reduce(PyArray_Descr *self, PyObject *NPY_UNUSED(args)) endian = '>'; } } - state = PyTuple_New(8); + state = PyTuple_New(9); PyTuple_SET_ITEM(state, 0, PyInt_FromLong(version)); PyTuple_SET_ITEM(state, 1, PyString_FromFormat("%c", endian)); PyTuple_SET_ITEM(state, 2, arraydescr_subdescr_get(self)); @@ -1536,6 +1962,25 @@ arraydescr_reduce(PyArray_Descr *self, PyObject *NPY_UNUSED(args)) PyTuple_SET_ITEM(state, 5, PyInt_FromLong(elsize)); PyTuple_SET_ITEM(state, 6, PyInt_FromLong(alignment)); PyTuple_SET_ITEM(state, 7, PyInt_FromLong(self->hasobject)); + if (self->metadata) { + if (PyDataType_ISDATETIME(self)) { + PyObject *newobj; + /* Handle CObject in NPY_METADATA_DTSTR key separately */ + /* newobj is a tuple of cleaned metadata dictionary + and tuple of date_time info (str, num, den, events) */ + newobj = _get_pickleabletype_from_metadata(self->metadata); + PyTuple_SET_ITEM(state, 8, newobj); + } + else { + Py_INCREF(self->metadata); + PyTuple_SET_ITEM(state, 8, self->metadata); + } + } + else { + PyTuple_SET_ITEM(state, 8, Py_None); + Py_INCREF(Py_None); + } + PyTuple_SET_ITEM(ret, 2, state); return ret; } @@ -1581,9 +2026,9 @@ static PyObject * arraydescr_setstate(PyArray_Descr *self, PyObject *args) { int elsize = -1, alignment = -1; - int version = 3; + int version = 4; char endian; - PyObject *subarray, *fields, *names = NULL; + PyObject *subarray, *fields, *names = NULL, *metadata=NULL; int incref_names = 1; int dtypeflags = 0; @@ -1597,6 +2042,13 @@ arraydescr_setstate(PyArray_Descr *self, PyObject *args) return NULL; } switch (PyTuple_GET_SIZE(PyTuple_GET_ITEM(args,0))) { + case 9: + if (!PyArg_ParseTuple(args, "(icOOOiiiO)", &version, &endian, + &subarray, &names, &fields, &elsize, + &alignment, &dtypeflags, &metadata)) { + return NULL; + } + break; case 8: if (!PyArg_ParseTuple(args, "(icOOOiii)", &version, &endian, &subarray, &names, &fields, &elsize, @@ -1635,7 +2087,7 @@ arraydescr_setstate(PyArray_Descr *self, PyObject *args) * If we ever need another pickle format, increment the version * number. But we should still be able to handle the old versions. */ - if (version < 0 || version > 3) { + if (version < 0 || version > 4) { PyErr_Format(PyExc_ValueError, "can't handle version %d of numpy.dtype pickle", version); @@ -1707,6 +2159,25 @@ arraydescr_setstate(PyArray_Descr *self, PyObject *args) if (version < 3) { self->hasobject = _descr_find_object(self); } + + Py_XDECREF(self->metadata); + if (PyDataType_ISDATETIME(self) && (metadata != Py_None) && (metadata != NULL)) { + PyObject *cobj; + self->metadata = PyTuple_GET_ITEM(metadata, 0); + Py_INCREF(self->metadata); + cobj = _convert_datetime_tuple_to_cobj(PyTuple_GET_ITEM(metadata, 1)); + PyDict_SetItemString(self->metadata, NPY_METADATA_DTSTR, cobj); + Py_DECREF(cobj); + } + else { + /* We have a borrowed reference to metadata so no need + to alter reference count + */ + if (metadata == Py_None) metadata = NULL; + self->metadata = metadata; + Py_XINCREF(metadata); + } + Py_INCREF(Py_None); return Py_None; } diff --git a/numpy/core/src/multiarray/multiarraymodule.c b/numpy/core/src/multiarray/multiarraymodule.c index c45952910..a793e0364 100644 --- a/numpy/core/src/multiarray/multiarraymodule.c +++ b/numpy/core/src/multiarray/multiarraymodule.c @@ -1957,6 +1957,36 @@ array_set_ops_function(PyObject *NPY_UNUSED(self), PyObject *NPY_UNUSED(args), P return oldops; } +static PyObject * +array_set_datetimeparse_function(PyObject *NPY_UNUSED(dummy), PyObject *args, PyObject *kwds) +{ + PyObject *op = NULL; + static char *kwlist[] = {"f", NULL}; + PyObject *_numpy_internal; + + if(!PyArg_ParseTupleAndKeywords(args, kwds, "|O", kwlist, + &op)) { + return NULL; + } + /* reset the array_repr function to built-in */ + if (op == Py_None) { + _numpy_internal = PyImport_ImportModule("numpy.core._internal"); + if (_numpy_internal == NULL) return NULL; + op = PyObject_GetAttrString(_numpy_internal, "datetime_from_string"); + } + else { /* Must balance reference count increment in both branches */ + if (!PyCallable_Check(op)) { + PyErr_SetString(PyExc_TypeError, "Argument must be callable."); + return NULL; + } + Py_INCREF(op); + } + PyArray_SetDatetimeParseFunction(op); + Py_DECREF(op); + Py_INCREF(Py_None); + return Py_None; +} + /*NUMPY_API * Where @@ -2367,10 +2397,12 @@ static struct PyMethodDef array_module_methods[] = { {"set_numeric_ops", (PyCFunction)array_set_ops_function, METH_VARARGS|METH_KEYWORDS, NULL}, + {"set_datetimeparse_function", + (PyCFunction)array_set_datetimeparse_function, + METH_VARARGS|METH_KEYWORDS, NULL}, {"set_typeDict", (PyCFunction)array_set_typeDict, METH_VARARGS, NULL}, - {"array", (PyCFunction)_array_fromobject, METH_VARARGS|METH_KEYWORDS, NULL}, @@ -2554,6 +2586,10 @@ setup_scalartypes(PyObject *NPY_UNUSED(dict)) SINGLE_INHERIT(LongLong, SignedInteger); #endif + SINGLE_INHERIT(TimeInteger, SignedInteger); + SINGLE_INHERIT(Datetime, TimeInteger); + SINGLE_INHERIT(Timedelta, TimeInteger); + /* fprintf(stderr, "tp_free = %p, PyObject_Del = %p, int_tp_free = %p, base.tp_free = %p\n", @@ -2692,16 +2728,23 @@ PyMODINIT_FUNC initmultiarray(void) { if (PyErr_Occurred()) { goto err; } + /* * PyExc_Exception should catch all the standard errors that are - * now raised instead of the string exception "multiarray.error". + * now raised instead of the string exception "multiarray.error" + * This is for backward compatibility with existing code. */ PyDict_SetItemString (d, "error", PyExc_Exception); - s = PyString_FromString("3.0"); + + s = PyString_FromString("3.1"); PyDict_SetItemString(d, "__version__", s); Py_DECREF(s); + s = PyString_InternFromString(NPY_METADATA_DTSTR); + PyDict_SetItemString(d, "METADATA_DTSTR", s); + Py_DECREF(s); + #define ADDCONST(NAME) \ s = PyInt_FromLong(NPY_##NAME); \ PyDict_SetItemString(d, #NAME, s); \ diff --git a/numpy/core/src/multiarray/parse_datetime.c b/numpy/core/src/multiarray/parse_datetime.c new file mode 100644 index 000000000..76ef9dcb3 --- /dev/null +++ b/numpy/core/src/multiarray/parse_datetime.c @@ -0,0 +1,862 @@ + +#include <datetime.h> +#include <time.h> + +/* For defaults and errors */ +#define NPY_FR_ERR -1 + +/* Offset for number of days between Dec 31, 1969 and Jan 1, 0001 +* Assuming Gregorian calendar was always in effect (proleptic Gregorian calendar) +*/ + +/* Calendar Structure for Parsing Long -> Date */ +typedef struct { + int year, month, day; +} ymdstruct; + +typedef struct { + int hour, min, sec; +} hmsstruct; + + +/* + ==================================================== + == Beginning of section borrowed from mx.DateTime == + ==================================================== +*/ + +/* + Functions in the following section are borrowed from mx.DateTime version + 2.0.6, and hence this code is subject to the terms of the egenix public + license version 1.0.0 +*/ + +#define Py_AssertWithArg(x,errortype,errorstr,a1) {if (!(x)) {PyErr_Format(errortype,errorstr,a1);goto onError;}} + +/* Table with day offsets for each month (0-based, without and with leap) */ +static int month_offset[2][13] = { + { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }, + { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 } +}; + +/* Table of number of days in a month (0-based, without and with leap) */ +static int days_in_month[2][12] = { + { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, + { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } +}; + +/* Return 1/0 iff year points to a leap year in calendar. */ +static int +is_leapyear(register long year) +{ + return (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)); +} + + +/* Return the day of the week for the given absolute date. */ +/* Monday is 0 and Sunday is 6 */ +static int +day_of_week(npy_longlong absdate) +{ + /* Add in four for the Thursday on Jan 1, 1970 (epoch offset)*/ + absdate += 4; + + if (absdate >= 0) { + return absdate % 7; + } + else { + return 6 + (absdate + 1) % 7; + } +} + +/* Return the year offset, that is the absolute date of the day + 31.12.(year-1) since 31.12.1969 in the proleptic Gregorian calendar. +*/ +static npy_longlong +year_offset(register npy_longlong year) +{ + /* Note that 477 == 1969/4 - 1969/100 + 1969/400 */ + year--; + if (year >= 0 || -1/4 == -1) + return (year-1969)*365 + year/4 - year/100 + year/400 - 477; + else + return (year-1969)*365 + (year-3)/4 - (year-99)/100 + (year-399)/400 - 477; +} + +/* Modified version of mxDateTime function + * Returns absolute number of days since Jan 1, 1970 + * assuming a proleptic Gregorian Calendar + * Raises a ValueError if out of range month or day + * day -1 is Dec 31, 1969, day 0 is Jan 1, 1970, day 1 is Jan 2, 1970 + */ +static npy_longlong +days_from_ymd(int year, int month, int day) +{ + + /* Calculate the absolute date */ + int leap; + npy_longlong yearoffset, absdate; + + /* Is it a leap year ? */ + leap = is_leapyear(year); + + /* Negative month values indicate months relative to the years end */ + if (month < 0) month += 13; + Py_AssertWithArg(month >= 1 && month <= 12, + PyExc_ValueError, + "month out of range (1-12): %i", + month); + + /* Negative values indicate days relative to the months end */ + if (day < 0) day += days_in_month[leap][month - 1] + 1; + Py_AssertWithArg(day >= 1 && day <= days_in_month[leap][month - 1], + PyExc_ValueError, + "day out of range: %i", + day); + + /* Number of days between Dec 31, (year - 1) and Dec 31, 1969 + * (can be negative). + */ + yearoffset = year_offset(year); + + if (PyErr_Occurred()) goto onError; + + /* Calculate the number of days using yearoffset */ + /* Jan 1, 1970 is day 0 and thus Dec. 31, 1969 is day -1 */ + absdate = day-1 + month_offset[leap][month - 1] + yearoffset; + + return absdate; + + onError: + return 0; + +} + +/* Returns absolute seconds from an hour, minute, and second + */ +#define secs_from_hms(hour, min, sec) ((hour)*3600 + (min)*60 + (sec)) + +/* Takes a number of days since Jan 1, 1970 (positive or negative) + * and returns the year. month, and day in the proleptic + * Gregorian calendar + * + * Examples: + * + * -1 returns 1969, 12, 31 + * 0 returns 1970, 1, 1 + * 1 returns 1970, 1, 2 + */ + +static ymdstruct +days_to_ymdstruct(npy_datetime dlong) +{ + ymdstruct ymd; + register long year; + npy_longlong yearoffset; + int leap, dayoffset; + int month = 1, day = 1; + int *monthoffset; + + dlong += 1; + + /* Approximate year */ + year = 1970 + dlong / 365.2425; + + /* Apply corrections to reach the correct year */ + while (1) { + /* Calculate the year offset */ + yearoffset = year_offset(year); + + /* Backward correction: absdate must be greater than the + yearoffset */ + if (yearoffset >= dlong) { + year--; + continue; + } + + dayoffset = dlong - yearoffset; + leap = is_leapyear(year); + + /* Forward correction: non leap years only have 365 days */ + if (dayoffset > 365 && !leap) { + year++; + continue; + } + break; + } + + /* Now iterate to find the month */ + monthoffset = month_offset[leap]; + for (month = 1; month < 13; month++) { + if (monthoffset[month] >= dayoffset) + break; + } + day = dayoffset - month_offset[leap][month-1]; + + ymd.year = year; + ymd.month = month; + ymd.day = day; + + return ymd; +} + +/* Converts an integer number of seconds in a day to hours minutes seconds. + It assumes seconds is between 0 and 86399. + */ + +static hmsstruct +seconds_to_hmsstruct(npy_longlong dlong) +{ + int hour, minute, second; + hmsstruct hms; + + hour = dlong / 3600; + minute = (dlong % 3600) / 60; + second = dlong - (hour*3600 + minute*60); + + hms.hour = hour; + hms.min = minute; + hms.sec = second; + + return hms; +} + +/* + ==================================================== + == End of section adapted from mx.DateTime == + ==================================================== +*/ + + +/*================================================== +// Parsing DateTime struct and returns a date-time number +// ================================================= + + Structure is assumed to be already normalized +*/ + +/*NUMPY_API + * Create a datetime value from a filled datetime struct and resolution unit. + */ +NPY_NO_EXPORT npy_datetime +PyArray_DatetimeStructToDatetime(NPY_DATETIMEUNIT fr, npy_datetimestruct *d) +{ + npy_datetime ret; + npy_longlong days; /* The absolute number of days since Jan 1, 1970 */ + + if (fr > NPY_FR_M) days = days_from_ymd(d->year, d->month, d->day); + + if (fr == NPY_FR_Y) { + ret = d->year - 1970; + } + else if (fr == NPY_FR_M) { + ret = (d->year - 1970) * 12 + d->month - 1; + } + else if (fr == NPY_FR_W) { /* This is just 7-days for now. */ + if (days >= 0) + ret = days / 7; + else + ret = (days - 6) / 7; + } + else if (fr == NPY_FR_B) { + npy_longlong x; + int dotw = day_of_week(days); + if (dotw > 4) ret = 0; /* Invalid business day */ + else { + if (days >= 0) { /* offset to adjust first week */ + x = days - 4; + } + else { + x = days - 2; + } + ret = 2 + (x / 7) * 5 + x % 7; + } + } + else if (fr == NPY_FR_D) { + ret = days; + } + else if (fr == NPY_FR_h) { + ret = days * 24 + d->hour; + } + else if (fr == NPY_FR_m) { + ret = days * 1440 + d->hour * 60 + d->min; + } + else if (fr == NPY_FR_s) { + ret = days * 86400 + secs_from_hms(d->hour, d->min, d->sec); + } + else if (fr == NPY_FR_ms) { + ret = days * 86400000 + secs_from_hms(d->hour, d->min, d->sec) * 1000 + + (d->us / 1000); + } + else if (fr == NPY_FR_us) { + npy_int64 num = 86400 * 1000; + num *= 1000; + ret = days * num + secs_from_hms(d->hour, d->min, d->sec) * 1000000 + + d->us; + } + else if (fr == NPY_FR_ns) { + npy_int64 num = 86400 * 1000; + num *= 1000 * 1000; + ret = days * num + secs_from_hms(d->hour, d->min, d->sec) * 1000000000 + + d->us * 1000 + (d->ps / 1000); + } + else if (fr == NPY_FR_ps) { + npy_int64 num2 = 1000 * 1000; + npy_int64 num1; + + num2 *= 1000 * 1000; + num1 = 86400 * num2; + ret = days * num1 + secs_from_hms(d->hour, d->min, d->sec) * num2 + + d->us * 1000000 + d->ps; + } + else if (fr == NPY_FR_fs) { /* only 2.6 hours */ + npy_int64 num2 = 1000000; + num2 *= 1000000; + num2 *= 1000; + + /* get number of seconds as a postive or negative number */ + if (days >= 0) { + ret = secs_from_hms(d->hour, d->min, d->sec); + } + else { + ret = ((d->hour - 24)*3600 + d->min*60 + d->sec); + } + ret = ret * num2 + d->us * 1000000000 + d->ps * 1000 + (d->as / 1000); + } + else if (fr == NPY_FR_as) { /* only 9.2 secs */ + npy_int64 num1, num2; + + num1 = 1000000; + num1 *= 1000000; + num2 = num1 * 1000000; + + if (days >= 0) { + ret = d->sec; + } + else { + ret = d->sec - 60; + } + ret = ret * num2 + d->us * num1 + d->ps * 1000000 + d->as; + } + else { /* Shouldn't get here */ + PyErr_SetString(PyExc_ValueError, "invalid internal frequency"); + ret = -1; + } + + return ret; +} + +/* Uses Average values when frequency is Y, M, or B */ + +#define _DAYS_PER_MONTH 30.436875 +#define _DAYS_PER_YEAR 365.2425 + +/*NUMPY_API + * Create a timdelta value from a filled timedelta struct and resolution unit. + */ +NPY_NO_EXPORT npy_datetime +PyArray_TimedeltaStructToTimedelta(NPY_DATETIMEUNIT fr, npy_timedeltastruct *d) +{ + npy_datetime ret; + + if (fr == NPY_FR_Y) { + ret = d->day / _DAYS_PER_YEAR; + } + else if (fr == NPY_FR_M) { + ret = d->day / _DAYS_PER_MONTH; + } + else if (fr == NPY_FR_W) { /* This is just 7-days for now. */ + if (d->day >= 0) { + ret = d->day / 7; + } + else { + ret = (d->day - 6) / 7; + } + } + else if (fr == NPY_FR_B) { + /* What is the meaning of a relative Business day? + + This assumes you want to take the day difference and + convert it to business-day difference (removing 2 every 7). + */ + ret = (d->day / 7) * 5 + d->day % 7; + } + else if (fr == NPY_FR_D) { + ret = d->day; + } + else if (fr == NPY_FR_h) { + ret = d->day + d->sec / 3600; + } + else if (fr == NPY_FR_m) { + ret = d->day * 1440 + d->sec / 60; + } + else if (fr == NPY_FR_s) { + ret = d->day * 86400 + d->sec; + } + else if (fr == NPY_FR_ms) { + ret = d->day * 86400000 + d->sec * 1000 + d->us / 1000; + } + else if (fr == NPY_FR_us) { + npy_int64 num = 86400000; + num *= 1000; + ret = d->day * num + d->sec * 1000000 + d->us; + } + else if (fr == NPY_FR_ns) { + npy_int64 num = 86400000; + num *= 1000000; + ret = d->day * num + d->sec * 1000000000 + d->us * 1000 + (d->ps / 1000); + } + else if (fr == NPY_FR_ps) { + npy_int64 num2, num1; + + num2 = 1000000; + num2 *= 1000000; + num1 = 86400 * num2; + + ret = d->day * num1 + d->sec * num2 + d->us * 1000000 + d->ps; + } + else if (fr == NPY_FR_fs) { /* only 2.6 hours */ + npy_int64 num2 = 1000000000; + num2 *= 1000000; + ret = d->sec * num2 + d->us * 1000000000 + d->ps * 1000 + (d->as / 1000); + } + else if (fr == NPY_FR_as) { /* only 9.2 secs */ + npy_int64 num1, num2; + + num1 = 1000000; + num1 *= 1000000; + num2 = num1 * 1000000; + ret = d->sec * num2 + d->us * num1 + d->ps * 1000000 + d->as; + } + else { /* Shouldn't get here */ + PyErr_SetString(PyExc_ValueError, "invalid internal frequency"); + ret = -1; + } + + return ret; +} + + + +/*NUMPY_API + * Fill the datetime struct from the value and resolution unit. + */ +NPY_NO_EXPORT void +PyArray_DatetimeToDatetimeStruct(npy_datetime val, NPY_DATETIMEUNIT fr, + npy_datetimestruct *result) +{ + int year = 1970, month = 1, day = 1, + hour = 0, min = 0, sec = 0, + us = 0, ps = 0, as = 0; + + npy_int64 tmp; + ymdstruct ymd; + hmsstruct hms; + + /* Note that what looks like val / N and val % N for positive numbers maps to + [val - (N-1)] / N and [N-1 + (val+1) % N] for negative numbers (with the 2nd + value, the remainder, being positive in both cases). + */ + + if (fr == NPY_FR_Y) { + year = 1970 + val; + } + else if (fr == NPY_FR_M) { + if (val >= 0) { + year = 1970 + val / 12; + month = val % 12 + 1; + } + else { + year = 1969 + (val + 1) / 12; + month = 12 + (val + 1)% 12; + } + } + else if (fr == NPY_FR_W) { + /* A week is the same as 7 days */ + ymd = days_to_ymdstruct(val * 7); + year = ymd.year; + month = ymd.month; + day = ymd.day; + } + else if (fr == NPY_FR_B) { /* Number of business days since Thursday, 1-1-70 */ + npy_longlong absdays; + /* A buisness day is M T W Th F (i.e. all but Sat and Sun.) */ + /* Convert the business day to the number of actual days. + + Must convert [0,1,2,3,4,5,6,7,...] to + [0,1,4,5,6,7,8,11,...] + and [...,-9,-8,-7,-6,-5,-4,-3,-2,-1,0] to + [...,-13,-10,-9,-8,-7,-6,-3,-2,-1,0] + */ + if (val >= 0) { + absdays = 7 * ((val + 3) / 5) + ((val + 3) % 5) - 3; + } + else { /* Recall how C computes / and % with negative numbers */ + absdays = 7 * ((val - 1) / 5) + ((val - 1) % 5) + 1; + } + ymd = days_to_ymdstruct(absdays); + year = ymd.year; + month = ymd.month; + day = ymd.day; + } + else if (fr == NPY_FR_D) { + ymd = days_to_ymdstruct(val); + year = ymd.year; + month = ymd.month; + day = ymd.day; + } + else if (fr == NPY_FR_h) { + if (val >= 0) { + ymd = days_to_ymdstruct(val / 24); + hour = val % 24; + } + else { + ymd = days_to_ymdstruct((val - 23) / 24); + hour = 23 + (val + 1) % 24; + } + year = ymd.year; + month = ymd.month; + day = ymd.day; + } + else if (fr == NPY_FR_m) { + if (val >= 0) { + ymd = days_to_ymdstruct(val / 1440); + min = val % 1440; + } + else { + ymd = days_to_ymdstruct((val - 1439) / 1440); + min = 1439 + (val + 1) % 1440; + } + hms = seconds_to_hmsstruct(min * 60); + year = ymd.year; + month = ymd.month; + day = ymd.day; + hour = hms.hour; + min = hms.min; + } + else if (fr == NPY_FR_s) { + if (val >= 0) { + ymd = days_to_ymdstruct(val / 86400); + sec = val % 86400; + } + else { + ymd = days_to_ymdstruct((val - 86399) / 86400); + sec = 86399 + (val + 1) % 86400; + } + hms = seconds_to_hmsstruct(val); + year = ymd.year; + month = ymd.month; + day = ymd.day; + hour = hms.hour; + min = hms.min; + sec = hms.sec; + } + else if (fr == NPY_FR_ms) { + if (val >= 0) { + ymd = days_to_ymdstruct(val / 86400000); + tmp = val % 86400000; + } + else { + ymd = days_to_ymdstruct((val - 86399999) / 86400000); + tmp = 86399999 + (val + 1) % 86399999; + } + hms = seconds_to_hmsstruct(tmp / 1000); + us = (tmp % 1000)*1000; + year = ymd.year; + month = ymd.month; + day = ymd.day; + hour = hms.hour; + min = hms.min; + sec = hms.sec; + } + else if (fr == NPY_FR_us) { + npy_int64 num1, num2; + num1 = 86400000; + num1 *= 1000; + num2 = num1 - 1; + if (val >= 0) { + ymd = days_to_ymdstruct(val / num1); + tmp = val % num1; + } + else { + ymd = days_to_ymdstruct((val - num2)/ num1); + tmp = num2 + (val + 1) % num1; + } + hms = seconds_to_hmsstruct(tmp / 1000000); + us = tmp % 1000000; + year = ymd.year; + month = ymd.month; + day = ymd.day; + hour = hms.hour; + min = hms.min; + sec = hms.sec; + } + else if (fr == NPY_FR_ns) { + npy_int64 num1, num2, num3; + num1 = 86400000; + num1 *= 1000000000; + num2 = num1 - 1; + num3 = 1000000; + num3 *= 1000000; + if (val >= 0) { + ymd = days_to_ymdstruct(val / num1); + tmp = val % num1; + } + else { + ymd = days_to_ymdstruct((val - num2)/ num1); + tmp = num2 + (val + 1) % num1; + } + hms = seconds_to_hmsstruct(tmp / 1000000000); + tmp = tmp % 1000000000; + us = tmp / 1000; + ps = (tmp % 1000) * 1000; + year = ymd.year; + month = ymd.month; + day = ymd.day; + hour = hms.hour; + min = hms.min; + sec = hms.sec; + } + else if (fr == NPY_FR_ps) { + npy_int64 num1, num2, num3; + num3 = 1000000000; + num3 *= 1000; + num1 = 86400 * num3; + num2 = num1 - 1; + + if (val >= 0) { + ymd = days_to_ymdstruct(val / num1); + tmp = val % num1; + } + else { + ymd = days_to_ymdstruct((val - num2)/ num1); + tmp = num2 + (val + 1) % num1; + } + hms = seconds_to_hmsstruct(tmp / num3); + tmp = tmp % num3; + us = tmp / 1000000; + ps = tmp % 1000000; + year = ymd.year; + month = ymd.month; + day = ymd.day; + hour = hms.hour; + min = hms.min; + sec = hms.sec; + } + else if (fr == NPY_FR_fs) { /* entire range is only += 2.6 hours */ + npy_int64 num1, num2; + num1 = 1000000000; + num1 *= 1000; + num2 = num1 * 1000; + + if (val >= 0) { + sec = val / num2; + tmp = val % num2; + hms = seconds_to_hmsstruct(sec); + hour = hms.hour; + min = hms.min; + sec = hms.sec; + } + else { /* tmp (number of fs) will be positive after this segment */ + year = 1969; + day = 31; + month = 12; + sec = (val - (num2-1))/num2; + tmp = (num2-1) + (val + 1) % num2; + if (sec == 0) { /* we are at the last second */ + hour = 23; + min = 59; + sec = 59; + } + else { + hour = 24 + (sec - 3599)/3600; + sec = 3599 + (sec+1)%3600; + min = sec / 60; + sec = sec % 60; + } + } + us = tmp / 1000000000; + tmp = tmp % 1000000000; + ps = tmp / 1000; + as = (tmp % 1000) * 1000; + } + else if (fr == NPY_FR_as) { /* entire range is only += 9.2 seconds */ + npy_int64 num1, num2, num3; + num1 = 1000000; + num2 = num1 * 1000000; + num3 = num2 * 1000000; + if (val >= 0) { + hour = 0; + min = 0; + sec = val / num3; + tmp = val % num3; + } + else { + year = 1969; + day = 31; + month = 12; + hour = 23; + min = 59; + sec = 60 + (val - (num3-1)) / num3; + tmp = (num3-1) + (val+1) % num3; + } + us = tmp / num2; + tmp = tmp % num2; + ps = tmp / num1; + as = tmp % num1; + } + else { + PyErr_SetString(PyExc_RuntimeError, "invalid internal time resolution"); + } + + result->year = year; + result->month = month; + result->day = day; + result->hour = hour; + result->min = min; + result->sec = sec; + result->us = us; + result->ps = ps; + result->as = as; + + return; +} + +/* FIXME: Overflow is not handled at all */ +/* To convert from Years, Months, and Business Days, multiplication by the average is done + */ + +/*NUMPY_API + * Fill the timedelta struct from the timedelta value and resolution unit. + */ +NPY_NO_EXPORT void +PyArray_TimedeltaToTimedeltaStruct(npy_timedelta val, NPY_DATETIMEUNIT fr, + npy_timedeltastruct *result) +{ + npy_longlong day=0; + int sec=0, us=0, ps=0, as=0; + npy_bool negative=0; + + /* Note that what looks like val / N and val % N for positive numbers maps to + [val - (N-1)] / N and [N-1 + (val+1) % N] for negative numbers (with the 2nd + value, the remainder, being positive in both cases). + */ + + if (val < 0) { + val = -val; + negative = 1; + } + if (fr == NPY_FR_Y) { + day = val * _DAYS_PER_YEAR; + } + else if (fr == NPY_FR_M) { + day = val * _DAYS_PER_MONTH; + } + else if (fr == NPY_FR_W) { + day = val * 7; + } + else if (fr == NPY_FR_B) { /* Number of business days since Thursday, 1-1-70 */ + day = (val * 7) / 5; + } + else if (fr == NPY_FR_D) { + day = val; + } + else if (fr == NPY_FR_h) { + day = val / 24; + sec = (val % 24)*3600; + } + else if (fr == NPY_FR_m) { + day = val / 1440; + sec = (val % 1440)*60; + } + else if (fr == NPY_FR_s) { + day = val / 86400; + sec = val % 86400; + } + else if (fr == NPY_FR_ms) { + day = val / 86400000; + val = val % 86400000; + sec = val / 1000; + us = (val % 1000)*1000; + } + else if (fr == NPY_FR_us) { + npy_int64 num1; + num1 = 86400000; + num1 *= 1000; + day = val / num1; + us = val % num1; + sec = us / 1000000; + us = us % 1000000; + } + else if (fr == NPY_FR_ns) { + npy_int64 num1; + num1 = 86400000; + num1 *= 1000000; + day = val / num1; + val = val % num1; + sec = val / 1000000000; + val = val % 1000000000; + us = val / 1000; + ps = (val % 1000) * 1000; + } + else if (fr == NPY_FR_ps) { + npy_int64 num1, num2; + num2 = 1000000000; + num2 *= 1000; + num1 = 86400 * num2; + + day = val / num1; + ps = val % num1; + sec = ps / num2; + ps = ps % num2; + us = ps / 1000000; + ps = ps % 1000000; + } + else if (fr == NPY_FR_fs) { /* entire range is only += 9.2 hours */ + npy_int64 num1, num2; + num1 = 1000000000; + num2 = num1 * 1000000; + + day = 0; + sec = val / num2; + val = val % num2; + us = val / num1; + val = val % num1; + ps = val / 1000; + as = (val % 1000) * 1000; + } + else if (fr == NPY_FR_as) { /* entire range is only += 2.6 seconds */ + npy_int64 num1, num2, num3; + num1 = 1000000; + num2 = num1 * 1000000; + num3 = num2 * 1000000; + day = 0; + sec = val / num3; + as = val % num3; + us = as / num2; + as = as % num2; + ps = as / num1; + as = as % num1; + } + else { + PyErr_SetString(PyExc_RuntimeError, "invalid internal time resolution"); + } + + if (negative) { + result->day = -day; + result->sec = -sec; + result->us = -us; + result->ps = -ps; + result->as = -as; + } + else { + result->day = day; + result->sec = sec; + result->us = us; + result->ps = ps; + result->as = as; + } + return; + +} + diff --git a/numpy/core/src/multiarray/scalarapi.c b/numpy/core/src/multiarray/scalarapi.c index 035e0863f..b66d29ae7 100644 --- a/numpy/core/src/multiarray/scalarapi.c +++ b/numpy/core/src/multiarray/scalarapi.c @@ -11,7 +11,7 @@ #include "config.h" -#include "common.h" +#include "ctors.h" #include "descriptor.h" #include "scalartypes.h" @@ -60,6 +60,8 @@ scalar_value(PyObject *scalar, PyArray_Descr *descr) CASE(CDOUBLE, CDouble); CASE(CLONGDOUBLE, CLongDouble); CASE(OBJECT, Object); + CASE(DATETIME, Datetime); + CASE(TIMEDELTA, Timedelta); #undef CASE case NPY_STRING: return (void *)PyString_AS_STRING(scalar); @@ -87,6 +89,8 @@ scalar_value(PyObject *scalar, PyArray_Descr *descr) _IFCASE(Int); _IFCASE(Long); _IFCASE(LongLong); + _IFCASE(Datetime); + _IFCASE(Timedelta); } else { /* Unsigned Integer */ @@ -604,6 +608,16 @@ PyArray_Scalar(void *data, PyArray_Descr *descr, PyObject *base) if (obj == NULL) { return NULL; } + if PyTypeNum_ISDATETIME(type_num) { + /* We need to copy the resolution information over to the scalar */ + /* Get the void * from the metadata dictionary */ + PyObject *cobj; + PyArray_DatetimeMetaData *dt_data; + cobj = PyDict_GetItemString(descr->metadata, NPY_METADATA_DTSTR); + dt_data = PyCObject_AsVoidPtr(cobj); + memcpy(&(((PyDatetimeScalarObject *)obj)->obmeta), dt_data, + sizeof(PyArray_DatetimeMetaData)); + } if PyTypeNum_ISFLEXIBLE(type_num) { if (type_num == PyArray_STRING) { destptr = PyString_AS_STRING(obj); diff --git a/numpy/core/src/multiarray/scalartypes.c.src b/numpy/core/src/multiarray/scalartypes.c.src index aba376be5..e50106866 100644 --- a/numpy/core/src/multiarray/scalartypes.c.src +++ b/numpy/core/src/multiarray/scalartypes.c.src @@ -32,9 +32,9 @@ NPY_NO_EXPORT PyBoolScalarObject _PyArrayScalar_BoolValues[] = { /**begin repeat * #name = number, integer, signedinteger, unsignedinteger, inexact, - * floating, complexfloating, flexible, character# + * floating, complexfloating, flexible, character, timeinteger# * #NAME = Number, Integer, SignedInteger, UnsignedInteger, Inexact, - * Floating, ComplexFloating, Flexible, Character# + * Floating, ComplexFloating, Flexible, Character, TimeInteger# */ NPY_NO_EXPORT PyTypeObject Py@NAME@ArrType_Type = { #if defined(NPY_PY3K) @@ -1908,12 +1908,12 @@ object_arrtype_dealloc(PyObject *v) /**begin repeat * #name = byte, short, int, long, longlong, ubyte, ushort, uint, ulong, * ulonglong, float, double, longdouble, cfloat, cdouble, clongdouble, - * string, unicode, object# + * string, unicode, object, datetime, timedelta# * #TYPE = BYTE, SHORT, INT, LONG, LONGLONG, UBYTE, USHORT, UINT, ULONG, * ULONGLONG, FLOAT, DOUBLE, LONGDOUBLE, CFLOAT, CDOUBLE, CLONGDOUBLE, - * STRING, UNICODE, OBJECT# - * #work = 0,0,1,1,1,0,0,0,0,0,0,1,0,0,0,0,z,z,0# - * #default = 0*16,1*2,2# + * STRING, UNICODE, OBJECT, DATETIME, TIMEDELTA# + * #work = 0,0,1,1,1,0,0,0,0,0,0,1,0,0,0,0,z,z,0,0,0# + * #default = 0*16,1*2,2,0*2# */ #define _NPY_UNUSED2_1 @@ -2312,7 +2312,50 @@ ulonglong_arrtype_hash(PyObject *obj) } return x; } + +#endif + + +/**begin repeat + * #lname=datetime, timedelta# + * #name=Datetime,Timedelta# + */ +#if SIZEOF_LONG==SIZEOF_DATETIME +static long +@lname@_arrtype_hash(PyObject *obj) +{ + long x = (long)(((Py@name@ScalarObject *)obj)->obval); + if (x == -1) { + x = -2; + } + return x; +} +#elif SIZEOF_LONGLONG==SIZEOF_DATETIME +static long +@lname@_arrtype_hash(PyObject *obj) +{ + long y; + longlong x = (((Py@name@ScalarObject *)obj)->obval); + + if ((x <= LONG_MAX)) { + y = (long) x; + } + else { + union Mask { + long hashvals[2]; + longlong v; + } both; + + both.v = x; + y = both.hashvals[0] + (1000003)*both.hashvals[1]; + } + if (y == -1) { + y = -2; + } + return y; +} #endif +/**end repeat**/ @@ -2729,9 +2772,10 @@ NPY_NO_EXPORT PyTypeObject Py@NAME@ArrType_Type = { /**begin repeat * #NAME = Byte, Short, Int, Long, LongLong, UByte, UShort, UInt, ULong, - * ULongLong, Float, Double, LongDouble# - * #name = int*5, uint*5, float*3# - * #CNAME = (CHAR, SHORT, INT, LONG, LONGLONG)*2, FLOAT, DOUBLE, LONGDOUBLE# + * ULongLong, Float, Double, LongDouble, Datetime, Timedelta# + * #name = int*5, uint*5, float*3, datetime, timedelta# + * #CNAME = (CHAR, SHORT, INT, LONG, LONGLONG)*2, FLOAT, DOUBLE, LONGDOUBLE, + * DATETIME, TIMEDELTA# */ #if BITSOF_@CNAME@ == 8 #define _THIS_SIZE "8" @@ -2969,7 +3013,7 @@ initialize_numeric_types(void) /**begin repeat * #NAME= Number, Integer, SignedInteger, UnsignedInteger, Inexact, - * Floating, ComplexFloating, Flexible, Character# + * Floating, ComplexFloating, Flexible, Character, TimeInteger# */ Py@NAME@ArrType_Type.tp_flags = BASEFLAGS; /**end repeat**/ @@ -2977,10 +3021,10 @@ initialize_numeric_types(void) /**begin repeat * #name = bool, byte, short, int, long, longlong, ubyte, ushort, uint, * ulong, ulonglong, float, double, longdouble, cfloat, cdouble, - * clongdouble, string, unicode, void, object# + * clongdouble, string, unicode, void, object, datetime, timedelta# * #NAME = Bool, Byte, Short, Int, Long, LongLong, UByte, UShort, UInt, * ULong, ULongLong, Float, Double, LongDouble, CFloat, CDouble, - * CLongDouble, String, Unicode, Void, Object# + * CLongDouble, String, Unicode, Void, Object, Datetime, Timedelta# */ Py@NAME@ArrType_Type.tp_flags = BASEFLAGS; Py@NAME@ArrType_Type.tp_new = @name@_arrtype_new; @@ -2989,9 +3033,11 @@ initialize_numeric_types(void) /**begin repeat * #name = bool, byte, short, ubyte, ushort, uint, ulong, ulonglong, - * float, longdouble, cfloat, clongdouble, void, object# + * float, longdouble, cfloat, clongdouble, void, object, datetime, + * timedelta# * #NAME = Bool, Byte, Short, UByte, UShort, UInt, ULong, ULongLong, - * Float, LongDouble, CFloat, CLongDouble, Void, Object# + * Float, LongDouble, CFloat, CLongDouble, Void, Object, Datetime, + * Timedelta# */ Py@NAME@ArrType_Type.tp_hash = @name@_arrtype_hash; /**end repeat**/ @@ -3069,7 +3115,9 @@ static PyTypeObject *typeobjects[] = { &PyObjectArrType_Type, &PyStringArrType_Type, &PyUnicodeArrType_Type, - &PyVoidArrType_Type + &PyVoidArrType_Type, + &PyDatetimeArrType_Type, + &PyTimedeltaArrType_Type }; NPY_NO_EXPORT int diff --git a/numpy/core/src/multiarray/testcalcs.py b/numpy/core/src/multiarray/testcalcs.py new file mode 100644 index 000000000..138877d87 --- /dev/null +++ b/numpy/core/src/multiarray/testcalcs.py @@ -0,0 +1,71 @@ +from scipy import weave + +class YMD(object): + year = 0 + month = 0 + days = 0 + + +month_offset = [ + [ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 ], + [ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 ] +] + +days_in_month = [ + [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ], + [ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ] +] + +def is_leapyear(year): + return (year % 4 == 0) & ((year % 100 != 0) | (year % 400 == 0)) + + +# Return the year offset, that is the absolute date of the day +# 31.12.(year-1) since 31.12.1969 in the proleptic Gregorian calendar. + +def year_offset(year): + code = """ + year-=1970; + if ((year+1969) >= 0 || -1/4 == -1) + return_val = year*365 + year/4 - year/100 + year/400; + else + return_val = year*365 + (year-3)/4 - (year-99)/100 + (year-399)/400; + """ + return weave.inline(code,['year']) + + +def days_from_ymd(year, month, day): + + leap = is_leapyear(year) + + # Negative month values indicate months relative to the years end */ + if (month < 0): month += 13 + if not (month >= 1 and month<=12): + raise ValueError, "month out of range (1-21): %d" % month + + # Negative values indicate days relative to the months end */ + if (day < 0): day += days_in_month[leap][month - 1] + 1 + if not (day >= 1 and day <= days_in_month[leap][month-1]): + raise ValueError, "day out of range: %d" % day + + # Number of days between Dec 31, (year - 1) and Dec 31, 1969 + # (can be negative). + # + yearoffset = year_offset(year); + + # Calculate the number of days using yearoffset */ + # Jan 1, 1970 is day 0 and thus Dec. 31, 1969 is day -1 */ + absdate = day-1 + month_offset[leap][month - 1] + yearoffset; + + return absdate; + + +def ymd_from_days(days): + ymd = YMD() + + year = 1970 + days / 365.2425 + + + + + diff --git a/numpy/core/src/umath/funcs.inc.src b/numpy/core/src/umath/funcs.inc.src index 9dd4a0cab..840146f07 100644 --- a/numpy/core/src/umath/funcs.inc.src +++ b/numpy/core/src/umath/funcs.inc.src @@ -1,3 +1,5 @@ +/* -*- c -*- */ + /* * This file is for the definitions of the non-c99 functions used in ufuncs. * All the complex ufuncs are defined here along with a smattering of real and diff --git a/numpy/core/src/umath/loops.c.src b/numpy/core/src/umath/loops.c.src index 01f971ae6..10cfa8716 100644 --- a/numpy/core/src/umath/loops.c.src +++ b/numpy/core/src/umath/loops.c.src @@ -798,6 +798,173 @@ U@TYPE@_remainder(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(f /* ***************************************************************************** + ** DATETIME LOOPS ** + ***************************************************************************** + */ + +/**begin repeat + * #type = datetime, timedelta# + * #TYPE = DATETIME, TIMEDELTA# + * #ftype = double, double# + */ + +#define @TYPE@_fmax @TYPE@_maximum +#define @TYPE@_fmin @TYPE@_minimum + +NPY_NO_EXPORT void +@TYPE@_ones_like(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(data)) +{ + OUTPUT_LOOP { + *((@type@ *)op1) = 1; + } +} + +NPY_NO_EXPORT void +@TYPE@_negative(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)) +{ + UNARY_LOOP { + const @type@ in1 = *(@type@ *)ip1; + *((@type@ *)op1) = (@type@)(-(@type@)in1); + } +} + +NPY_NO_EXPORT void +@TYPE@_logical_not(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)) +{ + UNARY_LOOP { + const @type@ in1 = *(@type@ *)ip1; + *((Bool *)op1) = !in1; + } +} + + +/**begin repeat1 + * #kind = equal, not_equal, greater, greater_equal, less, less_equal, + * logical_and, logical_or# + * #OP = ==, !=, >, >=, <, <=, &&, ||# + */ +NPY_NO_EXPORT void +@TYPE@_@kind@(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)) +{ + BINARY_LOOP { + const @type@ in1 = *(@type@ *)ip1; + const @type@ in2 = *(@type@ *)ip2; + *((Bool *)op1) = in1 @OP@ in2; + } +} +/**end repeat1**/ + +NPY_NO_EXPORT void +@TYPE@_logical_xor(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)) +{ + BINARY_LOOP { + const @type@ in1 = *(@type@ *)ip1; + const @type@ in2 = *(@type@ *)ip2; + *((Bool *)op1)= (in1 && !in2) || (!in1 && in2); + } +} + +/**begin repeat1 + * #kind = maximum, minimum# + * #OP = >, <# + **/ +NPY_NO_EXPORT void +@TYPE@_@kind@(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)) +{ + BINARY_LOOP { + const @type@ in1 = *(@type@ *)ip1; + const @type@ in2 = *(@type@ *)ip2; + *((@type@ *)op1) = (in1 @OP@ in2) ? in1 : in2; + } +} +/**end repeat1**/ + +NPY_NO_EXPORT void +@TYPE@_absolute(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)) +{ + UNARY_LOOP { + const @type@ in1 = *(@type@ *)ip1; + *((@type@ *)op1) = (in1 >= 0) ? in1 : -in1; + } +} + +NPY_NO_EXPORT void +@TYPE@_sign(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)) +{ + UNARY_LOOP { + const @type@ in1 = *(@type@ *)ip1; + *((@type@ *)op1) = in1 > 0 ? 1 : (in1 < 0 ? -1 : 0); + } +} + +/**end repeat**/ + +/* FIXME: implement the following correctly using the metadata: data is the + sequence of ndarrays in the same order as args. + */ +NPY_NO_EXPORT void +DATETIME_Mm_M_add(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(data)) +{ + BINARY_LOOP { + const datetime in1 = *(datetime *)ip1; + const timedelta in2 = *(timedelta *)ip2; + *((datetime *)op1) = in1 + in2; + } +} + +NPY_NO_EXPORT void +DATETIME_mM_M_add(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)) +{ + BINARY_LOOP { + const timedelta in1 = *(timedelta *)ip1; + const datetime in2 = *(datetime *)ip2; + *((datetime *)op1) = in1 + in2; + } +} + +NPY_NO_EXPORT void +TIMEDELTA_mm_m_add(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)) +{ + BINARY_LOOP { + const timedelta in1 = *(timedelta *)ip1; + const timedelta in2 = *(timedelta *)ip2; + *((timedelta *)op1) = in1 + in2; + } +} + +NPY_NO_EXPORT void +DATETIME_Mm_M_subtract(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)) +{ + BINARY_LOOP { + const datetime in1 = *(datetime *)ip1; + const timedelta in2 = *(timedelta *)ip2; + *((datetime *)op1) = in1 - in2; + } +} + +NPY_NO_EXPORT void +DATETIME_MM_m_subtract(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)) +{ + BINARY_LOOP { + const datetime in1 = *(datetime *)ip1; + const datetime in2 = *(datetime *)ip2; + *((timedelta *)op1) = in1 - in2; + } +} + +NPY_NO_EXPORT void +TIMEDELTA_mm_m_subtract(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)) +{ + BINARY_LOOP { + const timedelta in1 = *(timedelta *)ip1; + const timedelta in2 = *(timedelta *)ip2; + *((timedelta *)op1) = in1 - in2; + } +} + + +/* + ***************************************************************************** ** FLOAT LOOPS ** ***************************************************************************** */ diff --git a/numpy/core/src/umath/ufunc_object.c b/numpy/core/src/umath/ufunc_object.c index 5a075b4f2..94152ccb7 100644 --- a/numpy/core/src/umath/ufunc_object.c +++ b/numpy/core/src/umath/ufunc_object.c @@ -42,6 +42,9 @@ /* ---------------------------------------------------------------- */ +static int +_does_loop_use_arrays(void *data); + /* * fpstatus is the ufunc_formatted hardware status * errmask is the handling mask specified by the user. @@ -210,6 +213,8 @@ _lowest_type(char intype) case PyArray_INT: case PyArray_LONG: case PyArray_LONGLONG: + case PyArray_DATETIME: + case PyArray_TIMEDELTA: return PyArray_BYTE; /* case PyArray_UBYTE */ case PyArray_USHORT: @@ -1231,8 +1236,8 @@ construct_arrays(PyUFuncLoopObject *loop, PyObject *args, PyArrayObject **mps, /* We don't do strings */ if (flexible && !object) { - loop->notimplemented = 1; - return nargs; + loop->notimplemented = 1; + return nargs; } /* @@ -1528,14 +1533,21 @@ construct_arrays(PyUFuncLoopObject *loop, PyObject *args, PyArrayObject **mps, loop->meth = BUFFER_UFUNCLOOP; loop->needbuffer[i] = 1; } - if (!loop->obj + if (!(loop->obj & UFUNC_OBJ_ISOBJECT) && ((mps[i]->descr->type_num == PyArray_OBJECT) || (arg_types[i] == PyArray_OBJECT))) { - loop->obj = 1; + loop->obj = UFUNC_OBJ_ISOBJECT|UFUNC_OBJ_NEEDS_API; + } + if (!(loop->obj & UFUNC_OBJ_NEEDS_API) + && ((mps[i]->descr->type_num == PyArray_DATETIME) + || (mps[i]->descr->type_num == PyArray_TIMEDELTA) + || (arg_types[i] == PyArray_DATETIME) + || (arg_types[i] == PyArray_TIMEDELTA))) { + loop->obj = UFUNC_OBJ_NEEDS_API; } } - if (self->core_enabled && loop->obj) { + if (self->core_enabled && (loop->obj & UFUNC_OBJ_ISOBJECT)) { PyErr_SetString(PyExc_TypeError, "Object type not allowed in ufunc with signature"); return -1; @@ -1741,7 +1753,7 @@ construct_arrays(PyUFuncLoopObject *loop, PyObject *args, PyArrayObject **mps, PyErr_NoMemory(); return -1; } - if (loop->obj) { + if (loop->obj & UFUNC_OBJ_ISOBJECT) { memset(loop->buffer[0], 0, memsize); } castptr = loop->buffer[0] + loop->bufsize*cnt + scbufsize*scnt; @@ -1775,13 +1787,18 @@ construct_arrays(PyUFuncLoopObject *loop, PyObject *args, PyArrayObject **mps, else { loop->bufptr[i] = loop->buffer[i]; } - if (!loop->objfunc && loop->obj) { + if (!loop->objfunc && (loop->obj & UFUNC_OBJ_ISOBJECT)) { if (arg_types[i] == PyArray_OBJECT) { loop->objfunc = 1; } } } } + + if (_does_loop_use_arrays(loop->funcdata)) { + loop->funcdata = (void*)mps; + } + return nargs; } @@ -2102,7 +2119,7 @@ PyUFunc_GenericFunction(PyUFuncObject *self, PyObject *args, PyObject *kwds, for (i = 0; i <self->nargs; i++) { copyswapn[i] = mps[i]->descr->f->copyswapn; mpselsize[i] = mps[i]->descr->elsize; - pyobject[i] = (loop->obj + pyobject[i] = ((loop->obj & UFUNC_OBJ_ISOBJECT) && (mps[i]->descr->type_num == PyArray_OBJECT)); laststrides[i] = iters[i]->strides[loop->lastdim]; if (steps[i] && laststrides[i] != mpselsize[i]) { @@ -2482,7 +2499,14 @@ construct_reduce(PyUFuncObject *self, PyArrayObject **arr, PyArrayObject *out, /* Determine if object arrays are involved */ if (otype == PyArray_OBJECT || aar->descr->type_num == PyArray_OBJECT) { - loop->obj = 1; + loop->obj = UFUNC_OBJ_ISOBJECT | UFUNC_OBJ_NEEDS_API; + } + else if ((otype == PyArray_DATETIME) + || (aar->descr->type_num == PyArray_DATETIME) + || (otype == PyArray_TIMEDELTA) + || (aar->descr->type_num == PyArray_TIMEDELTA)) + { + loop->obj = UFUNC_OBJ_NEEDS_API; } else { loop->obj = 0; @@ -2633,7 +2657,7 @@ construct_reduce(PyUFuncObject *self, PyArrayObject **arr, PyArrayObject *out, if (loop->buffer == NULL) { goto fail; } - if (loop->obj) { + if (loop->obj & UFUNC_OBJ_ISOBJECT) { memset(loop->buffer, 0, _size); } loop->castbuf = loop->buffer + loop->bufsize*aar->descr->elsize; @@ -2649,7 +2673,7 @@ construct_reduce(PyUFuncObject *self, PyArrayObject **arr, PyArrayObject *out, if (loop->buffer == NULL) { goto fail; } - if (loop->obj) { + if (loop->obj & UFUNC_OBJ_ISOBJECT) { memset(loop->buffer, 0, _size); } loop->bufptr[1] = loop->buffer; @@ -2693,7 +2717,7 @@ PyUFunc_Reduce(PyUFuncObject *self, PyArrayObject *arr, PyArrayObject *out, case ZERO_EL_REDUCELOOP: /* fprintf(stderr, "ZERO..%d\n", loop->size); */ for (i = 0; i < loop->size; i++) { - if (loop->obj) { + if (loop->obj & UFUNC_OBJ_ISOBJECT) { Py_INCREF(*((PyObject **)loop->idptr)); } memmove(loop->bufptr[0], loop->idptr, loop->outsize); @@ -2703,7 +2727,7 @@ PyUFunc_Reduce(PyUFuncObject *self, PyArrayObject *arr, PyArrayObject *out, case ONE_EL_REDUCELOOP: /*fprintf(stderr, "ONEDIM..%d\n", loop->size); */ while (loop->index < loop->size) { - if (loop->obj) { + if (loop->obj & UFUNC_OBJ_ISOBJECT) { Py_INCREF(*((PyObject **)loop->it->dataptr)); } memmove(loop->bufptr[0], loop->it->dataptr, loop->outsize); @@ -2716,7 +2740,7 @@ PyUFunc_Reduce(PyUFuncObject *self, PyArrayObject *arr, PyArrayObject *out, /*fprintf(stderr, "NOBUFFER..%d\n", loop->size); */ while (loop->index < loop->size) { /* Copy first element to output */ - if (loop->obj) { + if (loop->obj & UFUNC_OBJ_ISOBJECT) { Py_INCREF(*((PyObject **)loop->it->dataptr)); } memmove(loop->bufptr[0], loop->it->dataptr, loop->outsize); @@ -2752,7 +2776,7 @@ PyUFunc_Reduce(PyUFuncObject *self, PyArrayObject *arr, PyArrayObject *out, arr->descr->f->copyswap(loop->buffer, loop->inptr, loop->swap, NULL); loop->cast(loop->buffer, loop->castbuf, 1, NULL, NULL); - if (loop->obj) { + if (loop->obj & UFUNC_OBJ_ISOBJECT) { Py_XINCREF(*((PyObject **)loop->castbuf)); } memcpy(loop->bufptr[0], loop->castbuf, loop->outsize); @@ -2835,7 +2859,7 @@ PyUFunc_Accumulate(PyUFuncObject *self, PyArrayObject *arr, PyArrayObject *out, /* Accumulate */ /* fprintf(stderr, "ZERO..%d\n", loop->size); */ for (i = 0; i < loop->size; i++) { - if (loop->obj) { + if (loop->obj & UFUNC_OBJ_ISOBJECT) { Py_INCREF(*((PyObject **)loop->idptr)); } memcpy(loop->bufptr[0], loop->idptr, loop->outsize); @@ -2846,7 +2870,7 @@ PyUFunc_Accumulate(PyUFuncObject *self, PyArrayObject *arr, PyArrayObject *out, /* Accumulate */ /* fprintf(stderr, "ONEDIM..%d\n", loop->size); */ while (loop->index < loop->size) { - if (loop->obj) { + if (loop->obj & UFUNC_OBJ_ISOBJECT) { Py_INCREF(*((PyObject **)loop->it->dataptr)); } memmove(loop->bufptr[0], loop->it->dataptr, loop->outsize); @@ -2860,7 +2884,7 @@ PyUFunc_Accumulate(PyUFuncObject *self, PyArrayObject *arr, PyArrayObject *out, /* fprintf(stderr, "NOBUFFER..%d\n", loop->size); */ while (loop->index < loop->size) { /* Copy first element to output */ - if (loop->obj) { + if (loop->obj & UFUNC_OBJ_ISOBJECT) { Py_INCREF(*((PyObject **)loop->it->dataptr)); } memmove(loop->bufptr[0], loop->it->dataptr, loop->outsize); @@ -2899,7 +2923,7 @@ PyUFunc_Accumulate(PyUFuncObject *self, PyArrayObject *arr, PyArrayObject *out, arr->descr->f->copyswap(loop->buffer, loop->inptr, loop->swap, NULL); loop->cast(loop->buffer, loop->castbuf, 1, NULL, NULL); - if (loop->obj) { + if (loop->obj & UFUNC_OBJ_ISOBJECT) { Py_XINCREF(*((PyObject **)loop->castbuf)); } memcpy(loop->bufptr[0], loop->castbuf, loop->outsize); @@ -3024,7 +3048,7 @@ PyUFunc_Reduceat(PyUFuncObject *self, PyArrayObject *arr, PyArrayObject *ind, ptr = (intp *)ind->data; for (i = 0; i < nn; i++) { loop->bufptr[1] = loop->it->dataptr + (*ptr)*loop->steps[1]; - if (loop->obj) { + if (loop->obj & UFUNC_OBJ_ISOBJECT) { Py_XINCREF(*((PyObject **)loop->bufptr[1])); } memcpy(loop->bufptr[0], loop->bufptr[1], loop->outsize); @@ -3055,7 +3079,7 @@ PyUFunc_Reduceat(PyUFuncObject *self, PyArrayObject *arr, PyArrayObject *ind, while (loop->index < loop->size) { ptr = (intp *)ind->data; for (i = 0; i < nn; i++) { - if (loop->obj) { + if (loop->obj & UFUNC_OBJ_ISOBJECT) { Py_XINCREF(*((PyObject **)loop->idptr)); } memcpy(loop->bufptr[0], loop->idptr, loop->outsize); @@ -3445,6 +3469,9 @@ ufunc_generic_call(PyUFuncObject *self, PyObject *args, PyObject *kwds) * PyErr_SetString(PyExc_TypeError,""); * return NULL; */ + /* This is expected by at least the ndarray rich_comparisons + to allow for additional handling for strings. + */ Py_INCREF(Py_NotImplemented); return Py_NotImplemented; } @@ -3734,6 +3761,27 @@ PyUFunc_FromFuncAndDataAndSignature(PyUFuncGenericFunction *func, void **data, return (PyObject *)self; } +/* Specify that the loop specified by the given index should use the array of + * input and arrays as the data pointer to the loop. + */ +/*UFUNC_API*/ +NPY_NO_EXPORT int +PyUFunc_SetUsesArraysAsData(void **data, size_t i) +{ + data[i] = (void*)PyUFunc_SetUsesArraysAsData; + return 0; +} + +/* Return 1 if the given data pointer for the loop specifies that it needs the + * arrays as the data pointer. + */ +static int +_does_loop_use_arrays(void *data) +{ + return (data == PyUFunc_SetUsesArraysAsData); +} + + /* * This is the first-part of the CObject structure. * diff --git a/numpy/core/tests/test_dtype.py b/numpy/core/tests/test_dtype.py index 60d2b16bc..d3f6d8654 100644 --- a/numpy/core/tests/test_dtype.py +++ b/numpy/core/tests/test_dtype.py @@ -81,5 +81,23 @@ class TestMonsterType(TestCase): ('yi', np.dtype((a, (3, 2))))]) self.failUnless(hash(c) == hash(d)) +class TestMetadata(TestCase): + def test_no_metadata(self): + d = np.dtype(int) + self.assertEqual(d.metadata, None) + + def test_metadata_takes_dict(self): + d = np.dtype(int, metadata={'datum': 1}) + self.assertEqual(d.metadata, {'datum': 1}) + + def test_metadata_rejects_nondict(self): + self.assertRaises(TypeError, np.dtype, int, metadata='datum') + self.assertRaises(TypeError, np.dtype, int, metadata=1) + self.assertRaises(TypeError, np.dtype, int, metadata=None) + + def test_nested_metadata(self): + d = np.dtype([('a', np.dtype(int, metadata={'datum': 1}))]) + self.assertEqual(d['a'].metadata, {'datum': 1}) + if __name__ == "__main__": run_module_suite() diff --git a/numpy/lib/tests/test_arrayterator.py b/numpy/lib/tests/test_arrayterator.py index 421569651..c600d45eb 100644 --- a/numpy/lib/tests/test_arrayterator.py +++ b/numpy/lib/tests/test_arrayterator.py @@ -41,3 +41,7 @@ def test(): # Check that all elements are iterated correctly assert list(c.flat) == list(d.flat) + +if __name__ == '__main__': + from numpy.testing import run_module_suite + run_module_suite() |