diff options
author | Mark Wiebe <mwiebe@enthought.com> | 2011-06-07 13:21:12 -0500 |
---|---|---|
committer | Mark Wiebe <mwiebe@enthought.com> | 2011-06-07 13:21:12 -0500 |
commit | a8274369fcbc3332067a8555782cb40cc4684c83 (patch) | |
tree | 00bf53d4e3951cd9e9588edf9876925482dea2e0 | |
parent | fd9ef72daaa66803fd3109502fb584bbda6f407c (diff) | |
parent | 283b2e712bd52e6661f2dc338eb14caae2f5a8da (diff) | |
download | numpy-a8274369fcbc3332067a8555782cb40cc4684c83.tar.gz |
Merge branch 'datetime-fixes'
37 files changed, 8121 insertions, 2988 deletions
diff --git a/numpy/core/_internal.py b/numpy/core/_internal.py index 5298f412b..a5b6d117a 100644 --- a/numpy/core/_internal.py +++ b/numpy/core/_internal.py @@ -166,36 +166,6 @@ def _split(input): return newlist -format_datetime = re.compile(asbytes(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 == asbytes('M8') or typecode == asbytes('datetime64')) - defaults = [asbytes('us'), 1, 1, 1] - names = ['baseunit', 'num', 'den', 'events'] - func = [bytes, 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(asbytes(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 deleted file mode 100644 index d8db18793..000000000 --- a/numpy/core/_mx_datetime_parser.py +++ /dev/null @@ -1,962 +0,0 @@ -#-*- 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 d0b899901..8595d6f02 100644 --- a/numpy/core/arrayprint.py +++ b/numpy/core/arrayprint.py @@ -15,7 +15,7 @@ __docformat__ = 'restructuredtext' import sys import numerictypes as _nt from umath import maximum, minimum, absolute, not_equal, isnan, isinf -from multiarray import format_longfloat +from multiarray import format_longfloat, datetime_as_string from fromnumeric import ravel @@ -72,7 +72,8 @@ def set_printoptions(precision=None, threshold=None, edgeitems=None, - 'bool' - 'int' - - 'timeint' : a `numpy.timeinteger` + - 'timedelta' : a `numpy.timedelta64` + - 'datetime' : a `numpy.datetime64` - 'float' - 'longfloat' : 128-bit floats - 'complexfloat' @@ -83,7 +84,7 @@ def set_printoptions(precision=None, threshold=None, edgeitems=None, Other keys that can be used to set a group of types at once are:: - 'all' : sets all types - - 'int_kind' : sets 'int' and 'timeint' + - 'int_kind' : sets 'int' - 'float_kind' : sets 'float' and 'longfloat' - 'complex_kind' : sets 'complexfloat' and 'longcomplexfloat' - 'str_kind' : sets 'str' and 'numpystr' @@ -239,12 +240,13 @@ def _array2string(a, max_line_width, precision, suppress_small, separator=' ', formatdict = {'bool' : _boolFormatter, 'int' : IntegerFormat(data), - 'timeint' : str, 'float' : FloatFormat(data, precision, suppress_small), 'longfloat' : LongFloatFormat(precision), 'complexfloat' : ComplexFormat(data, precision, suppress_small), 'longcomplexfloat' : LongComplexFormat(precision), + 'datetime' : DatetimeFormat(True, None, -1), + 'timedelta' : TimedeltaFormat(data), 'numpystr' : repr, 'str' : str} if formatter is not None: @@ -253,7 +255,7 @@ def _array2string(a, max_line_width, precision, suppress_small, separator=' ', for key in formatdict.keys(): formatdict[key] = formatter['all'] if 'int_kind' in fkeys: - for key in ['int', 'timeint']: + for key in ['int']: formatdict[key] = formatter['int_kind'] if 'float_kind' in fkeys: for key in ['float', 'longfloat']: @@ -280,8 +282,8 @@ def _array2string(a, max_line_width, precision, suppress_small, separator=' ', if issubclass(dtypeobj, _nt.bool_): format_function = formatdict['bool'] elif issubclass(dtypeobj, _nt.integer): - if issubclass(dtypeobj, _nt.timeinteger): - format_function = formatdict['timeint'] + if issubclass(dtypeobj, _nt.timedelta64): + format_function = formatdict['timedelta'] else: format_function = formatdict['int'] elif issubclass(dtypeobj, _nt.floating): @@ -296,6 +298,8 @@ def _array2string(a, max_line_width, precision, suppress_small, separator=' ', format_function = formatdict['complexfloat'] elif issubclass(dtypeobj, (_nt.unicode_, _nt.string_)): format_function = formatdict['numpystr'] + elif issubclass(dtypeobj, _nt.datetime64): + format_function = formatdict['datetime'] else: format_function = formatdict['str'] @@ -361,7 +365,8 @@ def array2string(a, max_line_width=None, precision=None, - 'bool' - 'int' - - 'timeint' : a `numpy.timeinteger` + - 'timedelta' : a `numpy.timedelta64` + - 'datetime' : a `numpy.datetime64` - 'float' - 'longfloat' : 128-bit floats - 'complexfloat' @@ -372,7 +377,7 @@ def array2string(a, max_line_width=None, precision=None, Other keys that can be used to set a group of types at once are:: - 'all' : sets all types - - 'int_kind' : sets 'int' and 'timeint' + - 'int_kind' : sets 'int' - 'float_kind' : sets 'float' and 'longfloat' - 'complex_kind' : sets 'complexfloat' and 'longcomplexfloat' - 'str_kind' : sets 'str' and 'numpystr' @@ -691,3 +696,30 @@ class ComplexFormat(object): else: i = i + 'j' return r + i + +class DatetimeFormat(object): + def __init__(self, uselocaltime=True, overrideunit=None, tzoffset=-1): + self.local = uselocaltime + self.unit = overrideunit + self.tzoffset = -1 + + def __call__(self, x): + return "'%s'" % datetime_as_string(x, + local=self.local, + unit=self.unit, + tzoffset=self.tzoffset) + +class TimedeltaFormat(object): + def __init__(self, data): + if data.dtype.kind == 'm': + v = data.view('i8') + max_str_len = max(len(str(maximum.reduce(v))), + len(str(minimum.reduce(v)))) + self.format = '%' + str(max_str_len) + 'd' + + def __call__(self, x): + if _MININT < x < _MAXINT: + return self.format % x.astype('i8') + else: + return "%s" % x + diff --git a/numpy/core/code_generators/generate_umath.py b/numpy/core/code_generators/generate_umath.py index 9382b1fae..b018e1948 100644 --- a/numpy/core/code_generators/generate_umath.py +++ b/numpy/core/code_generators/generate_umath.py @@ -12,10 +12,10 @@ 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() - +# Sentinel value to specify using the full type description in the +# function name +class FullTypeDescr(object): + pass class TypeDescription(object): """Type signature for a ufunc. @@ -24,7 +24,7 @@ class TypeDescription(object): ---------- type : str Character representing the nominal type. - func_data : str or None or UsesArraysAsData, optional + func_data : str or None or FullTypeDescr, optional The string representing the expression to insert into the data array, if any. in_ : str or None, optional @@ -100,7 +100,7 @@ class Ufunc(object): docstring: docstring for the ufunc type_descriptions: list of TypeDescription objects """ - def __init__(self, nin, nout, identity, docstring, + def __init__(self, nin, nout, identity, docstring, typereso, *type_descriptions): self.nin = nin self.nout = nout @@ -108,6 +108,7 @@ class Ufunc(object): identity = None_ self.identity = identity self.docstring = docstring + self.typereso = typereso self.type_descriptions = [] for td in type_descriptions: self.type_descriptions.extend(td) @@ -193,6 +194,7 @@ O = 'O' P = 'P' ints = 'bBhHiIlLqQ' times = 'Mm' +timedeltaonly = 'm' intsO = ints + O bints = '?' + ints bintsO = bints + O @@ -209,12 +211,14 @@ allP = bints+times+flts+cmplxP nobool = all[1:] noobj = all[:-3]+all[-2:] nobool_or_obj = all[1:-3]+all[-2:] +nobool_or_datetime = all[1:-2]+all[-1:] intflt = ints+flts intfltcmplx = ints+flts+cmplx nocmplx = bints+times+flts nocmplxO = nocmplx+O nocmplxP = nocmplx+P notimes_or_obj = bints + inexact +nodatetime_or_obj = bints + inexact # Find which code corresponds to int64. int64 = '' @@ -233,58 +237,80 @@ defdict = { 'add' : Ufunc(2, 1, Zero, docstrings.get('numpy.core.umath.add'), + 'PyUFunc_AdditionTypeResolution', TD(notimes_or_obj), - [TypeDescription('M', UsesArraysAsData, 'Mm', 'M'), - TypeDescription('m', UsesArraysAsData, 'mm', 'm'), - TypeDescription('M', UsesArraysAsData, 'mM', 'M'), + [TypeDescription('M', FullTypeDescr, 'Mm', 'M'), + TypeDescription('m', FullTypeDescr, 'mm', 'm'), + TypeDescription('M', FullTypeDescr, 'mM', 'M'), ], TD(O, f='PyNumber_Add'), ), 'subtract' : Ufunc(2, 1, Zero, docstrings.get('numpy.core.umath.subtract'), + 'PyUFunc_SubtractionTypeResolution', TD(notimes_or_obj), - [TypeDescription('M', UsesArraysAsData, 'Mm', 'M'), - TypeDescription('m', UsesArraysAsData, 'mm', 'm'), - TypeDescription('M', UsesArraysAsData, 'MM', 'm'), + [TypeDescription('M', FullTypeDescr, 'Mm', 'M'), + TypeDescription('m', FullTypeDescr, 'mm', 'm'), + TypeDescription('M', FullTypeDescr, 'MM', 'm'), ], TD(O, f='PyNumber_Subtract'), ), 'multiply' : Ufunc(2, 1, One, docstrings.get('numpy.core.umath.multiply'), + 'PyUFunc_MultiplicationTypeResolution', TD(notimes_or_obj), + [TypeDescription('m', FullTypeDescr, 'm' + int64, 'm'), + TypeDescription('m', FullTypeDescr, int64 + 'm', 'm'), + TypeDescription('m', FullTypeDescr, 'md', 'm'), + TypeDescription('m', FullTypeDescr, 'dm', 'm'), + ], TD(O, f='PyNumber_Multiply'), ), 'divide' : Ufunc(2, 1, One, docstrings.get('numpy.core.umath.divide'), + 'PyUFunc_DivisionTypeResolution', TD(intfltcmplx), + [TypeDescription('m', FullTypeDescr, 'm' + int64, 'm'), + TypeDescription('m', FullTypeDescr, 'md', 'm'), + ], TD(O, f='PyNumber_Divide'), ), 'floor_divide' : Ufunc(2, 1, One, docstrings.get('numpy.core.umath.floor_divide'), + 'PyUFunc_DivisionTypeResolution', TD(intfltcmplx), + [TypeDescription('m', FullTypeDescr, 'm' + int64, 'm'), + TypeDescription('m', FullTypeDescr, 'md', 'm'), + ], TD(O, f='PyNumber_FloorDivide'), ), 'true_divide' : Ufunc(2, 1, One, docstrings.get('numpy.core.umath.true_divide'), + 'PyUFunc_DivisionTypeResolution', TD('bBhH', out='d'), TD('iIlLqQ', out='d'), TD(flts+cmplx), + [TypeDescription('m', FullTypeDescr, 'm' + int64, 'm'), + TypeDescription('m', FullTypeDescr, 'md', 'm'), + ], TD(O, f='PyNumber_TrueDivide'), ), 'conjugate' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.conjugate'), + None, TD(ints+flts+cmplx), TD(P, f='conjugate'), ), 'fmod' : Ufunc(2, 1, Zero, docstrings.get('numpy.core.umath.fmod'), + None, TD(ints), TD(flts, f='fmod', astype={'e':'f'}), TD(P, f='fmod'), @@ -292,24 +318,28 @@ defdict = { 'square' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.square'), + None, TD(ints+inexact), TD(O, f='Py_square'), ), 'reciprocal' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.reciprocal'), + None, TD(ints+inexact), TD(O, f='Py_reciprocal'), ), 'ones_like' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.ones_like'), + 'PyUFunc_SimpleUnaryOperationTypeResolution', TD(noobj), TD(O, f='Py_get_one'), ), 'power' : Ufunc(2, 1, One, docstrings.get('numpy.core.umath.power'), + None, TD(ints), TD(inexact, f='pow', astype={'e':'f'}), TD(O, f='npy_ObjectPower'), @@ -317,378 +347,443 @@ defdict = { 'absolute' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.absolute'), - TD(bints+flts+times), + 'PyUFunc_AbsoluteTypeResolution', + TD(bints+flts+timedeltaonly), TD(cmplx, out=('f', 'd', 'g')), TD(O, f='PyNumber_Absolute'), ), '_arg' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath._arg'), + None, TD(cmplx, out=('f', 'd', 'g')), ), 'negative' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.negative'), - TD(bints+flts+times), + 'PyUFunc_SimpleUnaryOperationTypeResolution', + TD(bints+flts+timedeltaonly), TD(cmplx, f='neg'), TD(O, f='PyNumber_Negative'), ), 'sign' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.sign'), - TD(nobool), + 'PyUFunc_SimpleUnaryOperationTypeResolution', + TD(nobool_or_datetime), ), 'greater' : Ufunc(2, 1, None, docstrings.get('numpy.core.umath.greater'), + 'PyUFunc_SimpleBinaryComparisonTypeResolution', TD(all, out='?'), ), 'greater_equal' : Ufunc(2, 1, None, docstrings.get('numpy.core.umath.greater_equal'), + None, TD(all, out='?'), ), 'less' : Ufunc(2, 1, None, docstrings.get('numpy.core.umath.less'), + 'PyUFunc_SimpleBinaryComparisonTypeResolution', TD(all, out='?'), ), 'less_equal' : Ufunc(2, 1, None, docstrings.get('numpy.core.umath.less_equal'), + 'PyUFunc_SimpleBinaryComparisonTypeResolution', TD(all, out='?'), ), 'equal' : Ufunc(2, 1, None, docstrings.get('numpy.core.umath.equal'), + 'PyUFunc_SimpleBinaryComparisonTypeResolution', TD(all, out='?'), ), 'not_equal' : Ufunc(2, 1, None, docstrings.get('numpy.core.umath.not_equal'), + 'PyUFunc_SimpleBinaryComparisonTypeResolution', TD(all, out='?'), ), 'logical_and' : Ufunc(2, 1, One, docstrings.get('numpy.core.umath.logical_and'), - TD(noobj, out='?'), + 'PyUFunc_SimpleBinaryComparisonTypeResolution', + TD(nodatetime_or_obj, out='?'), TD(P, f='logical_and'), ), 'logical_not' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.logical_not'), - TD(noobj, out='?'), + None, + TD(nodatetime_or_obj, out='?'), TD(P, f='logical_not'), ), 'logical_or' : Ufunc(2, 1, Zero, docstrings.get('numpy.core.umath.logical_or'), - TD(noobj, out='?'), + 'PyUFunc_SimpleBinaryComparisonTypeResolution', + TD(nodatetime_or_obj, out='?'), TD(P, f='logical_or'), ), 'logical_xor' : Ufunc(2, 1, None, docstrings.get('numpy.core.umath.logical_xor'), - TD(noobj, out='?'), + 'PyUFunc_SimpleBinaryComparisonTypeResolution', + TD(nodatetime_or_obj, out='?'), TD(P, f='logical_xor'), ), 'maximum' : Ufunc(2, 1, None, docstrings.get('numpy.core.umath.maximum'), + 'PyUFunc_SimpleBinaryOperationTypeResolution', TD(noobj), TD(O, f='npy_ObjectMax') ), 'minimum' : Ufunc(2, 1, None, docstrings.get('numpy.core.umath.minimum'), + 'PyUFunc_SimpleBinaryOperationTypeResolution', TD(noobj), TD(O, f='npy_ObjectMin') ), 'fmax' : Ufunc(2, 1, None, docstrings.get('numpy.core.umath.fmax'), + 'PyUFunc_SimpleBinaryOperationTypeResolution', TD(noobj), TD(O, f='npy_ObjectMax') ), 'fmin' : Ufunc(2, 1, None, docstrings.get('numpy.core.umath.fmin'), + 'PyUFunc_SimpleBinaryOperationTypeResolution', TD(noobj), TD(O, f='npy_ObjectMin') ), 'logaddexp' : Ufunc(2, 1, None, docstrings.get('numpy.core.umath.logaddexp'), + None, TD(flts, f="logaddexp", astype={'e':'f'}) ), 'logaddexp2' : Ufunc(2, 1, None, docstrings.get('numpy.core.umath.logaddexp2'), + None, TD(flts, f="logaddexp2", astype={'e':'f'}) ), -# FIXME: decide if the times should have the bitwise operations. 'bitwise_and' : Ufunc(2, 1, One, docstrings.get('numpy.core.umath.bitwise_and'), + None, TD(bints), TD(O, f='PyNumber_And'), ), 'bitwise_or' : Ufunc(2, 1, Zero, docstrings.get('numpy.core.umath.bitwise_or'), + None, TD(bints), TD(O, f='PyNumber_Or'), ), 'bitwise_xor' : Ufunc(2, 1, None, docstrings.get('numpy.core.umath.bitwise_xor'), + None, TD(bints), TD(O, f='PyNumber_Xor'), ), 'invert' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.invert'), + None, TD(bints), TD(O, f='PyNumber_Invert'), ), 'left_shift' : Ufunc(2, 1, None, docstrings.get('numpy.core.umath.left_shift'), + None, TD(ints), TD(O, f='PyNumber_Lshift'), ), 'right_shift' : Ufunc(2, 1, None, docstrings.get('numpy.core.umath.right_shift'), + None, TD(ints), TD(O, f='PyNumber_Rshift'), ), 'degrees' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.degrees'), + None, TD(fltsP, f='degrees', astype={'e':'f'}), ), 'rad2deg' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.rad2deg'), + None, TD(fltsP, f='rad2deg', astype={'e':'f'}), ), 'radians' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.radians'), + None, TD(fltsP, f='radians', astype={'e':'f'}), ), 'deg2rad' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.deg2rad'), + None, TD(fltsP, f='deg2rad', astype={'e':'f'}), ), 'arccos' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.arccos'), + None, TD(inexact, f='acos', astype={'e':'f'}), TD(P, f='arccos'), ), 'arccosh' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.arccosh'), + None, TD(inexact, f='acosh', astype={'e':'f'}), TD(P, f='arccosh'), ), 'arcsin' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.arcsin'), + None, TD(inexact, f='asin', astype={'e':'f'}), TD(P, f='arcsin'), ), 'arcsinh' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.arcsinh'), + None, TD(inexact, f='asinh', astype={'e':'f'}), TD(P, f='arcsinh'), ), 'arctan' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.arctan'), + None, TD(inexact, f='atan', astype={'e':'f'}), TD(P, f='arctan'), ), 'arctanh' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.arctanh'), + None, TD(inexact, f='atanh', astype={'e':'f'}), TD(P, f='arctanh'), ), 'cos' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.cos'), + None, TD(inexact, f='cos', astype={'e':'f'}), TD(P, f='cos'), ), 'sin' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.sin'), + None, TD(inexact, f='sin', astype={'e':'f'}), TD(P, f='sin'), ), 'tan' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.tan'), + None, TD(inexact, f='tan', astype={'e':'f'}), TD(P, f='tan'), ), 'cosh' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.cosh'), + None, TD(inexact, f='cosh', astype={'e':'f'}), TD(P, f='cosh'), ), 'sinh' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.sinh'), + None, TD(inexact, f='sinh', astype={'e':'f'}), TD(P, f='sinh'), ), 'tanh' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.tanh'), + None, TD(inexact, f='tanh', astype={'e':'f'}), TD(P, f='tanh'), ), 'exp' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.exp'), + None, TD(inexact, f='exp', astype={'e':'f'}), TD(P, f='exp'), ), 'exp2' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.exp2'), + None, TD(inexact, f='exp2', astype={'e':'f'}), TD(P, f='exp2'), ), 'expm1' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.expm1'), + None, TD(inexact, f='expm1', astype={'e':'f'}), TD(P, f='expm1'), ), 'log' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.log'), + None, TD(inexact, f='log', astype={'e':'f'}), TD(P, f='log'), ), 'log2' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.log2'), + None, TD(inexact, f='log2', astype={'e':'f'}), TD(P, f='log2'), ), 'log10' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.log10'), + None, TD(inexact, f='log10', astype={'e':'f'}), TD(P, f='log10'), ), 'log1p' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.log1p'), + None, TD(inexact, f='log1p', astype={'e':'f'}), TD(P, f='log1p'), ), 'sqrt' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.sqrt'), + None, TD(inexact, f='sqrt', astype={'e':'f'}), TD(P, f='sqrt'), ), 'ceil' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.ceil'), + None, TD(flts, f='ceil', astype={'e':'f'}), TD(P, f='ceil'), ), 'trunc' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.trunc'), + None, TD(flts, f='trunc', astype={'e':'f'}), TD(P, f='trunc'), ), 'fabs' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.fabs'), + None, TD(flts, f='fabs', astype={'e':'f'}), TD(P, f='fabs'), ), 'floor' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.floor'), + None, TD(flts, f='floor', astype={'e':'f'}), TD(P, f='floor'), ), 'rint' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.rint'), + None, TD(inexact, f='rint', astype={'e':'f'}), TD(P, f='rint'), ), 'arctan2' : Ufunc(2, 1, None, docstrings.get('numpy.core.umath.arctan2'), + None, TD(flts, f='atan2', astype={'e':'f'}), TD(P, f='arctan2'), ), 'remainder' : Ufunc(2, 1, None, docstrings.get('numpy.core.umath.remainder'), + None, TD(intflt), TD(O, f='PyNumber_Remainder'), ), 'hypot' : Ufunc(2, 1, None, docstrings.get('numpy.core.umath.hypot'), + None, TD(flts, f='hypot', astype={'e':'f'}), TD(P, f='hypot'), ), 'isnan' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.isnan'), + None, TD(inexact, out='?'), ), 'isinf' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.isinf'), + None, TD(inexact, out='?'), ), 'isfinite' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.isfinite'), + None, TD(inexact, out='?'), ), 'signbit' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.signbit'), + None, TD(flts, out='?'), ), 'copysign' : Ufunc(2, 1, None, docstrings.get('numpy.core.umath.copysign'), + None, TD(flts), ), 'nextafter' : Ufunc(2, 1, None, docstrings.get('numpy.core.umath.nextafter'), + None, TD(flts), ), 'spacing' : Ufunc(1, 1, None, docstrings.get('numpy.core.umath.spacing'), + None, TD(flts), ), 'modf' : Ufunc(1, 2, None, docstrings.get('numpy.core.umath.modf'), + None, TD(flts), ), } @@ -751,7 +846,7 @@ def make_arrays(funcdict): thedict = chartotype1 # one input and one output for t in uf.type_descriptions: - if t.func_data not in (None, UsesArraysAsData): + if t.func_data not in (None, FullTypeDescr): funclist.append('NULL') astype = '' if not t.astype is None: @@ -773,11 +868,10 @@ def make_arrays(funcdict): datalist.append('(void *)NULL') #datalist.append('(void *)%s' % t.func_data) sub += 1 - elif t.func_data is UsesArraysAsData: + elif t.func_data is FullTypeDescr: 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') tname = english_upper(chartoname[t.type]) @@ -828,6 +922,10 @@ r"""f = PyUFunc_FromFuncAndData(%s_functions, %s_data, %s_signatures, %d, uf.nin, uf.nout, uf.identity, name, docstring)) + if uf.typereso != None: + mlist.append( + r"((PyUFuncObject *)f)->type_resolution_function = &%s;" % + uf.typereso) mlist.append(r"""PyDict_SetItemString(dictionary, "%s", f);""" % name) mlist.append(r"""Py_DECREF(f);""") code3list.append('\n'.join(mlist)) diff --git a/numpy/core/code_generators/numpy_api.py b/numpy/core/code_generators/numpy_api.py index db2c368dd..a789ae683 100644 --- a/numpy/core/code_generators/numpy_api.py +++ b/numpy/core/code_generators/numpy_api.py @@ -361,6 +361,9 @@ ufunc_funcs_api = { 'PyUFunc_ee_e': 36, 'PyUFunc_ee_e_As_ff_f': 37, 'PyUFunc_ee_e_As_dd_d': 38, + # End 1.6 API + 'PyUFunc_DefaultTypeResolution': 39, + 'PyUFunc_ValidateCasting': 40, } # List of all the dicts which define the C API diff --git a/numpy/core/include/numpy/ndarraytypes.h b/numpy/core/include/numpy/ndarraytypes.h index 2a21a779e..5bed8d70c 100644 --- a/numpy/core/include/numpy/ndarraytypes.h +++ b/numpy/core/include/numpy/ndarraytypes.h @@ -213,21 +213,37 @@ typedef enum { NPY_RAISE=2 } NPY_CLIPMODE; +/* The special not-a-time (NaT) value */ +#define NPY_DATETIME_NAT NPY_MIN_INT64 +/* + * Theoretical maximum length of a DATETIME ISO 8601 string + * YEAR: 21 (64-bit year) + * MONTH: 3 + * DAY: 3 + * HOURS: 3 + * MINUTES: 3 + * SECONDS: 3 + * ATTOSECONDS: 1 + 3*6 + * TIMEZONE: 5 + * NULL TERMINATOR: 1 + */ +#define NPY_DATETIME_MAX_ISO8601_STRLEN (21+3*5+1+3*6+6+1) + 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_FR_Y, /* Years */ + NPY_FR_M, /* Months */ + NPY_FR_W, /* Weeks */ + NPY_FR_B, /* Business days (weekdays, doesn't account for holidays) */ + NPY_FR_D, /* Days */ + NPY_FR_h, /* hours */ + NPY_FR_m, /* minutes */ + NPY_FR_s, /* seconds */ + NPY_FR_ms,/* milliseconds */ + NPY_FR_us,/* microseconds */ + NPY_FR_ns,/* nanoseconds */ + NPY_FR_ps,/* picoseconds */ + NPY_FR_fs,/* femtoseconds */ + NPY_FR_as /* attoseconds */ } NPY_DATETIMEUNIT; #define NPY_DATETIME_NUMUNITS (NPY_FR_as + 1) @@ -676,23 +692,32 @@ typedef struct { typedef struct { NPY_DATETIMEUNIT base; int num; - int den; /* - * Converted to 1 on input for now -- an - * input-only mechanism - */ + /* + * 'den' is unused, kept here for ABI compatibility with 1.6. + * TODO: Remove for 2.0. + */ + int den; int events; } PyArray_DatetimeMetaData; +/* + * This structure contains an exploded view of a date-time value. + * NaT is represented by year == NPY_DATETIME_NAT. + */ typedef struct { - npy_longlong year; - int month, day, hour, min, sec, us, ps, as; + npy_int64 year; + npy_int32 month, day, hour, min, sec, us, ps, as; + npy_int32 event; } npy_datetimestruct; +/* TO BE REMOVED - NOT USED INTERNALLY. */ typedef struct { - npy_longlong day; - int sec, us, ps, as; + npy_int64 day; + npy_int32 sec, us, ps, as; + npy_int32 event; } npy_timedeltastruct; +/* TO BE REMOVED - NOT USED INTERNALLY. */ #if PY_VERSION_HEX >= 0x03000000 #define PyDataType_GetDatetimeMetaData(descr) \ ((descr->metadata == NULL) ? NULL : \ @@ -802,7 +827,7 @@ typedef int (PyArray_FinalizeFunc)(PyArrayObject *, PyObject *); /* * Size of internal buffers used for alignment Make BUFSIZE a multiple - * of sizeof(cdouble) -- ususally 16 so that ufunc buffers are aligned + * of sizeof(cdouble) -- usually 16 so that ufunc buffers are aligned */ #define NPY_MIN_BUFSIZE ((int)sizeof(cdouble)) #define NPY_MAX_BUFSIZE (((int)sizeof(cdouble))*1000000) diff --git a/numpy/core/include/numpy/npy_3kcompat.h b/numpy/core/include/numpy/npy_3kcompat.h index 02355e852..7ff9c8f48 100644 --- a/numpy/core/include/numpy/npy_3kcompat.h +++ b/numpy/core/include/numpy/npy_3kcompat.h @@ -348,7 +348,7 @@ NpyCapsule_Check(PyObject *ptr) return PyCapsule_CheckExact(ptr); } -static void +static NPY_INLINE void simple_capsule_dtor(PyObject *cap) { PyArray_free(PyCapsule_GetPointer(cap, NULL)); @@ -387,7 +387,7 @@ NpyCapsule_Check(PyObject *ptr) return PyCObject_Check(ptr); } -static void +static NPY_INLINE void simple_capsule_dtor(void *ptr) { PyArray_free(ptr); diff --git a/numpy/core/include/numpy/ufuncobject.h b/numpy/core/include/numpy/ufuncobject.h index 34cd72707..ae8f06827 100644 --- a/numpy/core/include/numpy/ufuncobject.h +++ b/numpy/core/include/numpy/ufuncobject.h @@ -9,7 +9,41 @@ extern "C" { typedef void (*PyUFuncGenericFunction) (char **, npy_intp *, npy_intp *, void *); -typedef struct { +/* Forward declaration for the type resolution function */ +struct _tagPyUFuncObject; + +/* + * Given the operands for calling a ufunc, should determine the + * calculation input and output data types and return an inner loop function. + * This function should validate that the casting rule is being followed, + * and fail if it is not. + * + * ufunc: The ufunc object. + * casting: The 'casting' parameter provided to the ufunc. + * operands: An array of length (ufunc->nin + ufunc->nout), + * with the output parameters possibly NULL. + * type_tup: Either NULL, or the type_tup passed to the ufunc. + * out_dtypes: An array which should be populated with new + * references to (ufunc->nin + ufunc->nout) new + * dtypes, one for each input and output. + * out_innerloop: Should be populated with the correct ufunc inner + * loop for the given type. + * out_innerloopdata: Should be populated with the void* data to + * be passed into the out_innerloop function. + * + * Should return 0 on success, -1 on failure (with exception set), + * or -2 if Py_NotImplemented should be returned. + */ +typedef int (PyUFunc_TypeResolutionFunc)( + struct _tagPyUFuncObject *ufunc, + NPY_CASTING casting, + PyArrayObject **operands, + PyObject *type_tup, + PyArray_Descr **out_dtypes, + PyUFuncGenericFunction *out_innerloop, + void **out_innerloopdata); + +typedef struct _tagPyUFuncObject { PyObject_HEAD /* * nin: Number of inputs @@ -70,6 +104,13 @@ typedef struct { int *core_offsets; /* signature string for printing purpose */ char *core_signature; + + /* + * A function which resolves the types and returns an inner loop. + * This is used by the regular ufunc, the reduction operations + * have a different set of rules. + */ + PyUFunc_TypeResolutionFunc *type_resolution_function; } PyUFuncObject; #include "arrayobject.h" diff --git a/numpy/core/numeric.py b/numpy/core/numeric.py index 55ab74168..67235e4d1 100644 --- a/numpy/core/numeric.py +++ b/numpy/core/numeric.py @@ -1332,7 +1332,7 @@ def array_repr(arr, max_line_width=None, precision=None, suppress_small=None): if typeless and arr.size: return cName + "(%s)" % lst else: - typename=arr.dtype.name + typename="'%s'" % arr.dtype.name lf = '' if issubclass(arr.dtype.type, flexible): if arr.dtype.names: diff --git a/numpy/core/numerictypes.py b/numpy/core/numerictypes.py index 7bdfd98c1..6d655cb17 100644 --- a/numpy/core/numerictypes.py +++ b/numpy/core/numerictypes.py @@ -39,8 +39,9 @@ Exported symbols include: longfloat, clongfloat, - datetime_, timedelta_, (these inherit from timeinteger which inherits - from signedinteger) + datetime_ + timedelta_, (this inherits from from signedinteger, as it is + a signed integer with an associated time unit) As part of the type-hierarchy: xx -- is bit-width @@ -91,9 +92,10 @@ Exported symbols include: __all__ = ['sctypeDict', 'sctypeNA', 'typeDict', 'typeNA', 'sctypes', 'ScalarType', 'obj2sctype', 'cast', 'nbytes', 'sctype2char', 'maximum_sctype', 'issctype', 'typecodes', 'find_common_type', - 'issubdtype'] + 'issubdtype','datetime_data','datetime_as_string'] -from numpy.core.multiarray import typeinfo, ndarray, array, empty, dtype +from numpy.core.multiarray import typeinfo, ndarray, array, \ + empty, dtype, datetime_data, datetime_as_string import types as _types import sys @@ -257,6 +259,10 @@ def bitname(obj): char = 'O' base = 'object' bits = 0 + elif name=='datetime64': + char = 'M' + elif name=='timedelta64': + char = 'm' if sys.version_info[0] >= 3: if name=='bytes_': diff --git a/numpy/core/src/multiarray/_datetime.h b/numpy/core/src/multiarray/_datetime.h index 9be7e5673..a91953787 100644 --- a/numpy/core/src/multiarray/_datetime.h +++ b/numpy/core/src/multiarray/_datetime.h @@ -2,17 +2,338 @@ #define _NPY_PRIVATE__DATETIME_H_ NPY_NO_EXPORT void -PyArray_DatetimeToDatetimeStruct(npy_datetime val, NPY_DATETIMEUNIT fr, - npy_datetimestruct *result); +numpy_pydatetime_import(); +/* + * This function returns the a new reference to the + * capsule with the datetime metadata. + */ +NPY_NO_EXPORT PyObject * +get_datetime_metacobj_from_dtype(PyArray_Descr *dtype); + +/* + * This function returns a pointer to the DateTimeMetaData + * contained within the provided datetime dtype. + */ +NPY_NO_EXPORT PyArray_DatetimeMetaData * +get_datetime_metadata_from_dtype(PyArray_Descr *dtype); + +/* + * Both type1 and type2 must be either NPY_DATETIME or NPY_TIMEDELTA. + * Applies the type promotion rules between the two types, returning + * the promoted type. + */ +NPY_NO_EXPORT PyArray_Descr * +datetime_type_promotion(PyArray_Descr *type1, PyArray_Descr *type2); + +/* + * Converts a datetime from a datetimestruct to a datetime based + * on some metadata. + */ +NPY_NO_EXPORT int +convert_datetimestruct_to_datetime(PyArray_DatetimeMetaData *meta, + const npy_datetimestruct *dts, + npy_datetime *out); + +/* + * Parses the metadata string into the metadata C structure. + * + * Returns 0 on success, -1 on failure. + */ +NPY_NO_EXPORT int +parse_datetime_metadata_from_metastr(char *metastr, Py_ssize_t len, + PyArray_DatetimeMetaData *out_meta); + + +/* + * This function returns a reference to a capsule + * which contains the datetime metadata parsed from a metadata + * string. 'metastr' should be NULL-terminated, and len should + * contain its string length. + */ +NPY_NO_EXPORT PyObject * +parse_datetime_metacobj_from_metastr(char *metastr, Py_ssize_t len); + +/* + * Converts a datetype dtype string into a dtype descr object. + * The "type" string should be NULL-terminated, and len should + * contain its string length. + */ +NPY_NO_EXPORT PyArray_Descr * +parse_dtype_from_datetime_typestr(char *typestr, Py_ssize_t len); + +/* + * Creates a new NPY_TIMEDELTA dtype, copying the datetime metadata + * from the given dtype. + */ +NPY_NO_EXPORT PyArray_Descr * +timedelta_dtype_with_copied_meta(PyArray_Descr *dtype); + +/* + * Converts a substring given by 'str' and 'len' into + * a date time unit enum value. The 'metastr' parameter + * is used for error messages, and may be NULL. + * + * Returns 0 on success, -1 on failure. + */ +NPY_NO_EXPORT NPY_DATETIMEUNIT +parse_datetime_unit_from_string(char *str, Py_ssize_t len, char *metastr); + +/* + * Translate divisors into multiples of smaller units. + * 'metastr' is used for the error message if the divisor doesn't work, + * and can be NULL if the metadata didn't come from a string. + * + * Returns 0 on success, -1 on failure. + */ +NPY_NO_EXPORT int +convert_datetime_divisor_to_multiple(PyArray_DatetimeMetaData *meta, + int den, char *metastr); + +/* + * Determines whether the 'divisor' metadata divides evenly into + * the 'dividend' metadata. + */ +NPY_NO_EXPORT npy_bool +datetime_metadata_divides( + PyArray_Descr *dividend, + PyArray_Descr *divisor, + int strict_with_nonlinear_units); + +/* + * Computes the GCD of the two date-time metadata values. Raises + * an exception if there is no reasonable GCD, such as with + * years and days. + * + * Returns a capsule with the GCD metadata. + */ +NPY_NO_EXPORT PyObject * +compute_datetime_metadata_greatest_common_divisor( + PyArray_Descr *type1, + PyArray_Descr *type2, + int strict_with_nonlinear_units); + +/* + * Computes the conversion factor to convert data with 'src_meta' metadata + * into data with 'dst_meta' metadata, not taking into account the events. + * + * To convert a npy_datetime or npy_timedelta, first the event number needs + * to be divided away, then it needs to be scaled by num/denom, and + * finally the event number can be added back in. + * + * If overflow occurs, both out_num and out_denom are set to 0, but + * no error is set. + */ +NPY_NO_EXPORT void +get_datetime_conversion_factor(PyArray_DatetimeMetaData *src_meta, + PyArray_DatetimeMetaData *dst_meta, + npy_int64 *out_num, npy_int64 *out_denom); + +/* + * Given an the capsule datetime metadata object, + * returns a tuple for pickling and other purposes. + */ +NPY_NO_EXPORT PyObject * +convert_datetime_metadata_to_tuple(PyArray_DatetimeMetaData *meta); + +/* + * Converts a metadata tuple into a datetime metadata C struct. + * + * Returns 0 on success, -1 on failure. + */ +NPY_NO_EXPORT int +convert_datetime_metadata_tuple_to_datetime_metadata(PyObject *tuple, + PyArray_DatetimeMetaData *out_meta); + +/* + * Given a tuple representing datetime metadata, + * returns a capsule datetime metadata object. + */ +NPY_NO_EXPORT PyObject * +convert_datetime_metadata_tuple_to_metacobj(PyObject *tuple); + +/* + * Converts an input object into datetime metadata. The input + * may be either a string or a tuple. + * + * Returns 0 on success, -1 on failure. + */ +NPY_NO_EXPORT int +convert_pyobject_to_datetime_metadata(PyObject *obj, + PyArray_DatetimeMetaData *out_meta); + +/* + * 'ret' is a PyUString containing the datetime string, and this + * function appends the metadata string to it. + * + * If 'skip_brackets' is true, skips the '[]' when events == 1. + * + * This function steals the reference 'ret' + */ +NPY_NO_EXPORT PyObject * +append_metastr_to_string(PyArray_DatetimeMetaData *meta, + int skip_brackets, + PyObject *ret); + +/* + * Provides a string length to use for converting datetime + * objects with the given local and unit settings. + */ +NPY_NO_EXPORT int +get_datetime_iso_8601_strlen(int local, NPY_DATETIMEUNIT base); + + +/* + * Parses (almost) standard ISO 8601 date strings. The differences are: + * + * + The date "20100312" is parsed as the year 20100312, not as + * equivalent to "2010-03-12". The '-' in the dates are not optional. + * + Only seconds may have a decimal point, with up to 18 digits after it + * (maximum attoseconds precision). + * + Either a 'T' as in ISO 8601 or a ' ' may be used to separate + * the date and the time. Both are treated equivalently. + * + * 'str' must be a NULL-terminated string, and 'len' must be its length. + * + * Returns 0 on success, -1 on failure. + */ +NPY_NO_EXPORT int +parse_iso_8601_date(char *str, int len, npy_datetimestruct *out); + +/* + * Converts an npy_datetimestruct to an (almost) ISO 8601 + * NULL-terminated string. + * + * If 'local' is non-zero, it produces a string in local time with + * a +-#### timezone offset, otherwise it uses timezone Z (UTC). + * + * 'base' restricts the output to that unit. Set 'base' to + * -1 to auto-detect a base after which all the values are zero. + * + * 'tzoffset' is used if 'local' is enabled, and 'tzoffset' is + * set to a value other than -1. This is a manual override for + * the local time zone to use, as an offset in minutes. + * + * Returns 0 on success, -1 on failure (for example if the output + * string was too short). + */ +NPY_NO_EXPORT int +make_iso_8601_date(npy_datetimestruct *dts, char *outstr, int outlen, + int local, NPY_DATETIMEUNIT base, int tzoffset); + + +/* + * Tests for and converts a Python datetime.datetime or datetime.date + * object into a NumPy npy_datetimestruct. + * + * Returns -1 on error, 0 on success, and 1 (with no error set) + * if obj doesn't have the neeeded date or datetime attributes. + */ +NPY_NO_EXPORT int +convert_pydatetime_to_datetimestruct(PyObject *obj, npy_datetimestruct *out); + +/* + * Converts a PyObject * into a datetime, in any of the input forms supported. + * + * Returns -1 on error, 0 on success. + */ +NPY_NO_EXPORT int +convert_pyobject_to_datetime(PyArray_DatetimeMetaData *meta, PyObject *obj, + npy_datetime *out); + +/* + * Converts a PyObject * into a timedelta, in any of the forms supported + * + * Returns -1 on error, 0 on success. + */ +NPY_NO_EXPORT int +convert_pyobject_to_timedelta(PyArray_DatetimeMetaData *meta, PyObject *obj, + npy_timedelta *out); + + +/* + * Converts a datetime into a PyObject *. + * + * For days or coarser, returns a datetime.date. + * For microseconds or coarser, returns a datetime.datetime. + * For units finer than microseconds, returns an integer. + */ +NPY_NO_EXPORT PyObject * +convert_datetime_to_pyobject(npy_datetime dt, PyArray_DatetimeMetaData *meta); + +/* + * Converts a timedelta into a PyObject *. + * + * Not-a-time is returned as the string "NaT". + * For microseconds or coarser, returns a datetime.timedelta. + * For units finer than microseconds, returns an integer. + */ +NPY_NO_EXPORT PyObject * +convert_timedelta_to_pyobject(npy_timedelta td, PyArray_DatetimeMetaData *meta); + +/* + * Converts a datetime based on the given metadata into a datetimestruct + */ +NPY_NO_EXPORT int +convert_datetime_to_datetimestruct(PyArray_DatetimeMetaData *meta, + npy_datetime dt, + npy_datetimestruct *out); + +/* + * Converts a datetime from a datetimestruct to a datetime based + * on some metadata. The date is assumed to be valid. + * + * TODO: If meta->num is really big, there could be overflow + * + * Returns 0 on success, -1 on failure. + */ +NPY_NO_EXPORT int +convert_datetimestruct_to_datetime(PyArray_DatetimeMetaData *meta, + const npy_datetimestruct *dts, + npy_datetime *out); + +/* + * Adjusts a datetimestruct based on a seconds offset. Assumes + * the current values are valid. + */ +NPY_NO_EXPORT void +add_seconds_to_datetimestruct(npy_datetimestruct *dts, int seconds); + +/* + * Adjusts a datetimestruct based on a minutes offset. Assumes + * the current values are valid. + */ NPY_NO_EXPORT void -PyArray_TimedeltaToTimedeltaStruct(npy_timedelta val, NPY_DATETIMEUNIT fr, - npy_timedeltastruct *result); +add_minutes_to_datetimestruct(npy_datetimestruct *dts, int minutes); + +/* + * Returns true if the datetime metadata matches + */ +NPY_NO_EXPORT npy_bool +has_equivalent_datetime_metadata(PyArray_Descr *type1, PyArray_Descr *type2); -NPY_NO_EXPORT npy_datetime -PyArray_DatetimeStructToDatetime(NPY_DATETIMEUNIT fr, npy_datetimestruct *d); +/* + * Casts a single datetime from having src_meta metadata into + * dst_meta metadata. + * + * Returns 0 on success, -1 on failure. + */ +NPY_NO_EXPORT int +cast_datetime_to_datetime(PyArray_DatetimeMetaData *src_meta, + PyArray_DatetimeMetaData *dst_meta, + npy_datetime src_dt, + npy_datetime *dst_dt); -NPY_NO_EXPORT npy_datetime -PyArray_TimedeltaStructToTimedelta(NPY_DATETIMEUNIT fr, npy_timedeltastruct *d); +/* + * Casts a single timedelta from having src_meta metadata into + * dst_meta metadata. + * + * Returns 0 on success, -1 on failure. + */ +NPY_NO_EXPORT int +cast_timedelta_to_timedelta(PyArray_DatetimeMetaData *src_meta, + PyArray_DatetimeMetaData *dst_meta, + npy_timedelta src_dt, + npy_timedelta *dst_dt); #endif diff --git a/numpy/core/src/multiarray/arrayobject.c b/numpy/core/src/multiarray/arrayobject.c index a3008c888..ba4b2fc59 100644 --- a/numpy/core/src/multiarray/arrayobject.c +++ b/numpy/core/src/multiarray/arrayobject.c @@ -340,8 +340,7 @@ array_repr_builtin(PyArrayObject *self, int repr) max_n = PyArray_NBYTES(self)*4*sizeof(char) + 7; if ((string = (char *)_pya_malloc(max_n)) == NULL) { - PyErr_SetString(PyExc_MemoryError, "out of memory"); - return NULL; + return PyErr_NoMemory(); } if (repr) { @@ -380,7 +379,6 @@ 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. @@ -407,17 +405,13 @@ PyArray_SetStringFunction(PyObject *op, int repr) } /*NUMPY_API - * Set the date time print function to be a Python function. + * This function is scheduled to be removed + * + * TO BE REMOVED - NOT USED INTERNALLY. */ 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; } diff --git a/numpy/core/src/multiarray/arraytypes.c.src b/numpy/core/src/multiarray/arraytypes.c.src index fde95c4cb..e55c7c133 100644 --- a/numpy/core/src/multiarray/arraytypes.c.src +++ b/numpy/core/src/multiarray/arraytypes.c.src @@ -1,7 +1,6 @@ /* -*- c -*- */ #define PY_SSIZE_T_CLEAN #include "Python.h" -#include "datetime.h" #include "structmember.h" #define _MULTIARRAYMODULE @@ -106,8 +105,6 @@ MyPyLong_AsUnsigned@Type@ (PyObject *obj) */ -static char * _SEQUENCE_MESSAGE = "error setting an array element with a sequence"; - /**begin repeat * * #TYPE = BOOL, BYTE, UBYTE, SHORT, USHORT, INT, LONG, UINT, ULONG, @@ -764,459 +761,102 @@ fail: return -1; } -/* - * Acknowledgement: Example code contributed by Marty Fuhr sponsored by - * Google Summer of Code 2009 was used to integrate and adapt the mxDateTime - * parser - */ - -/* #include "datetime.c" --- now included in multiarray_onefile */ - - -/* 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)); - /* This resets meta->events for recursive call */ - meta->events = 1; - 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); -} - +static PyObject * +DATETIME_getitem(char *ip, PyArrayObject *ap) { + npy_datetime dt; + PyArray_DatetimeMetaData *meta = NULL; -NPY_NO_EXPORT PyObject * -PyTimeDelta_FromInt64(timedelta val, PyArray_Descr *descr) -{ - PyArray_DatetimeMetaData *meta; - meta = PyDataType_GetDatetimeMetaData(descr); + /* Get the datetime units metadata */ + meta = get_datetime_metadata_from_dtype(PyArray_DESCR(ap)); 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)); - /* This resets meta->events for recursive call */ - meta->events = 1; - 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); + if ((ap == NULL) || PyArray_ISBEHAVED_RO(ap)) { + dt = *((npy_datetime *)ip); } 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; + ap->descr->f->copyswap(&dt, ip, !PyArray_ISNOTSWAPPED(ap), ap); } - 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); + return convert_datetime_to_pyobject(dt, meta); } -/* - * 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; - int events; - - if (!PyTuple_Check(obj) || PyTuple_GET_SIZE(obj) != 2) { - PyErr_SetString(PyExc_ValueError, - "need a 2-tuple on setting if events > 1"); - return -1; - } - /* Alter the dictionary and call again */ - /* FIXME: not thread safe */ - events = meta->events; - meta->events = 1; - tmp = PyDateTime_AsInt64(PyTuple_GET_ITEM(obj, 0), descr); - meta->events = events; - if (PyErr_Occurred()) { - return -1; - } - /* FIXME: Check for overflow */ - tmp *= 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; +static PyObject * +TIMEDELTA_getitem(char *ip, PyArrayObject *ap) { + npy_timedelta td; + PyArray_DatetimeMetaData *meta = NULL; - meta = PyDataType_GetDatetimeMetaData(descr); + /* Get the datetime units metadata */ + meta = get_datetime_metadata_from_dtype(PyArray_DESCR(ap)); if (meta == NULL) { - PyErr_SetString(PyExc_RuntimeError, - "metadata not set for descriptor"); - return -1; - } - - if (meta->events > 1) { - timedelta tmp; - int events; - - if (!PyTuple_Check(obj) || PyTuple_GET_SIZE(obj) != 2) { - PyErr_SetString(PyExc_ValueError, - "need a 2-tuple on setting if events > 1"); - return -1; - } - /* Alter the dictionary and call again (not thread safe) */ - events = meta->events; - meta->events = 1; - tmp = PyTimeDelta_AsInt64(PyTuple_GET_ITEM(obj, 0), descr); - meta->events = events; - if (PyErr_Occurred()) { - return -1; - } - /* FIXME: Check for overflow */ - tmp *= events; - tmp += MyPyLong_AsLongLong(PyTuple_GET_ITEM(obj, 1)); - if (PyErr_Occurred()) { - return -1; - } - return tmp; + return NULL; } - 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); + td = *((npy_timedelta *)ip); } else { - ap->descr->f->copyswap(&t1, ip, !PyArray_ISNOTSWAPPED(ap), ap); - return PyDateTime_FromInt64((datetime)t1, ap->descr); + ap->descr->f->copyswap(&td, ip, !PyArray_ISNOTSWAPPED(ap), ap); } -} - -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); - } + return convert_timedelta_to_pyobject(td, meta); } -/* FIXME: - * This needs to take - * 1) Integers and Longs (anything that can be converted to an Int) - * 2) Strings (ISO-style dates) - * 3) Datetime Scalars (that it converts based on scalar dtype. - * 4) Datetime and Date objects - * Plus a tuple for meta->events > 1 - * - * 3) is partially implemented, 4) is implemented - */ - static int DATETIME_setitem(PyObject *op, char *ov, PyArrayObject *ap) { /* ensure alignment */ - datetime temp; + npy_datetime temp = 0; + PyArray_DatetimeMetaData *meta = NULL; - if (PyArray_IsScalar(op, Datetime)) { - /* This needs to convert based on type */ - temp = ((PyDatetimeScalarObject *)op)->obval; + /* Get the datetime units metadata */ + meta = get_datetime_metadata_from_dtype(PyArray_DESCR(ap)); + if (meta == NULL) { + return -1; } -#if defined(NPY_PY3K) - else if (PyUString_Check(op)) { -#else - else if (PyUString_Check(op) || PyUnicode_Check(op)) { -#endif - /* FIXME: Converts to DateTime first and therefore does not handle extended notation */ - /* import _mx_datetime_parser - * res = _mx_datetime_parser(name) - * Convert from datetime to Int - */ - PyObject *res, *module; - module = PyImport_ImportModule("numpy.core._mx_datetime_parser"); - if (module == NULL) { return -1; } - res = PyObject_CallMethod(module, "datetime_from_string", "O", op); - Py_DECREF(module); - if (res == NULL) { return -1; } - temp = PyDateTime_AsInt64(res, ap->descr); - Py_DECREF(res); - if (PyErr_Occurred()) return -1; - } - else if (PyInt_Check(op)) { - temp = PyInt_AS_LONG(op); - } - else if (PyLong_Check(op)) { - temp = PyLong_AsLongLong(op); - } - else { - temp = PyDateTime_AsInt64(op, ap->descr); - } - if (PyErr_Occurred()) { - if (PySequence_Check(op)) { - PyErr_Clear(); - PyErr_SetString(PyExc_ValueError, _SEQUENCE_MESSAGE); - } + /* Convert the object into a NumPy datetime */ + if (convert_pyobject_to_datetime(meta, op, &temp) < 0) { return -1; } - if (ap == NULL || PyArray_ISBEHAVED(ap)) - *((datetime *)ov)=temp; + + /* Copy the value into the output */ + if (ap == NULL || PyArray_ISBEHAVED(ap)) { + *((npy_datetime *)ov)=temp; + } else { ap->descr->f->copyswap(ov, &temp, !PyArray_ISNOTSWAPPED(ap), ap); } + return 0; } -/* FIXME: This needs to take - * 1) Integers and Longs (anything that can be converted to an Int) - * 2) Timedelta scalar objects (with resolution conversion) - * 3) Python Timedelta objects - * - * Plus a tuple for meta->events > 1 - */ - static int TIMEDELTA_setitem(PyObject *op, char *ov, PyArrayObject *ap) { /* ensure alignment */ - timedelta temp; + npy_timedelta temp = 0; + PyArray_DatetimeMetaData *meta = NULL; - if (PyArray_IsScalar(op, Timedelta)) { - temp = ((PyTimedeltaScalarObject *)op)->obval; - } - else if (PyInt_Check(op)) { - temp = PyInt_AS_LONG(op); - } - else if (PyLong_Check(op)) { - temp = PyLong_AsLongLong(op); - } - else { - temp = PyTimeDelta_AsInt64(op, ap->descr); + /* Get the datetime units metadata */ + meta = get_datetime_metadata_from_dtype(PyArray_DESCR(ap)); + if (meta == NULL) { + return -1; } - if (PyErr_Occurred()) { - if (PySequence_Check(op)) { - PyErr_Clear(); - PyErr_SetString(PyExc_ValueError, _SEQUENCE_MESSAGE); - } + + /* Convert the object into a NumPy datetime */ + if (convert_pyobject_to_timedelta(meta, op, &temp) < 0) { return -1; } - if (ap == NULL || PyArray_ISBEHAVED(ap)) - *((timedelta *)ov)=temp; + + /* Copy the value into the output */ + if (ap == NULL || PyArray_ISBEHAVED(ap)) { + *((npy_timedelta *)ov)=temp; + } else { ap->descr->f->copyswap(ov, &temp, !PyArray_ISNOTSWAPPED(ap), ap); } + return 0; } @@ -3796,7 +3436,6 @@ _init_datetime_descr(PyArray_Descr *descr) 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; /* FIXME @@ -4128,7 +3767,6 @@ 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/conversion_utils.c b/numpy/core/src/multiarray/conversion_utils.c index e8ac99273..12f2c731b 100644 --- a/numpy/core/src/multiarray/conversion_utils.c +++ b/numpy/core/src/multiarray/conversion_utils.c @@ -628,119 +628,148 @@ PyArray_IntpFromSequence(PyObject *seq, npy_intp *vals, int maxvals) NPY_NO_EXPORT int PyArray_TypestrConvert(int itemsize, int gentype) { - int newtype = gentype; + int newtype = NPY_NOTYPE; - if (gentype == PyArray_GENBOOLLTR) { - if (itemsize == 1) { - newtype = PyArray_BOOL; - } - else { - newtype = PyArray_NOTYPE; - } - } - else if (gentype == PyArray_SIGNEDLTR) { - switch(itemsize) { - case 1: - newtype = PyArray_INT8; - break; - case 2: - newtype = PyArray_INT16; - break; - case 4: - newtype = PyArray_INT32; - break; - case 8: - newtype = PyArray_INT64; + switch (gentype) { + case NPY_GENBOOLLTR: + if (itemsize == 1) { + newtype = NPY_BOOL; + } break; + + case NPY_SIGNEDLTR: + switch(itemsize) { + case 1: + newtype = NPY_INT8; + break; + case 2: + newtype = NPY_INT16; + break; + case 4: + newtype = NPY_INT32; + break; + case 8: + newtype = NPY_INT64; + break; #ifdef PyArray_INT128 - case 16: - newtype = PyArray_INT128; - break; + case 16: + newtype = NPY_INT128; + break; #endif - default: - newtype = PyArray_NOTYPE; - } - } - else if (gentype == PyArray_UNSIGNEDLTR) { - switch(itemsize) { - case 1: - newtype = PyArray_UINT8; - break; - case 2: - newtype = PyArray_UINT16; - break; - case 4: - newtype = PyArray_UINT32; - break; - case 8: - newtype = PyArray_UINT64; + } break; + + case NPY_UNSIGNEDLTR: + switch(itemsize) { + case 1: + newtype = NPY_UINT8; + break; + case 2: + newtype = NPY_UINT16; + break; + case 4: + newtype = NPY_UINT32; + break; + case 8: + newtype = NPY_UINT64; + break; #ifdef PyArray_INT128 - case 16: - newtype = PyArray_UINT128; - break; + case 16: + newtype = NPY_UINT128; + break; #endif - default: - newtype = PyArray_NOTYPE; - break; - } - } - else if (gentype == PyArray_FLOATINGLTR) { - switch(itemsize) { - case 2: - newtype = PyArray_FLOAT16; - break; - case 4: - newtype = PyArray_FLOAT32; - break; - case 8: - newtype = PyArray_FLOAT64; + } break; + + case NPY_FLOATINGLTR: + switch(itemsize) { + case 2: + newtype = NPY_FLOAT16; + break; + case 4: + newtype = NPY_FLOAT32; + break; + case 8: + newtype = NPY_FLOAT64; + break; #ifdef PyArray_FLOAT80 - case 10: - newtype = PyArray_FLOAT80; - break; + case 10: + newtype = NPY_FLOAT80; + break; #endif #ifdef PyArray_FLOAT96 - case 12: - newtype = PyArray_FLOAT96; - break; + case 12: + newtype = NPY_FLOAT96; + break; #endif #ifdef PyArray_FLOAT128 - case 16: - newtype = PyArray_FLOAT128; - break; + case 16: + newtype = NPY_FLOAT128; + break; #endif - default: - newtype = PyArray_NOTYPE; - } - } - else if (gentype == PyArray_COMPLEXLTR) { - switch(itemsize) { - case 8: - newtype = PyArray_COMPLEX64; - break; - case 16: - newtype = PyArray_COMPLEX128; + } break; + + case NPY_COMPLEXLTR: + switch(itemsize) { + case 8: + newtype = NPY_COMPLEX64; + break; + case 16: + newtype = NPY_COMPLEX128; + break; #ifdef PyArray_FLOAT80 - case 20: - newtype = PyArray_COMPLEX160; - break; + case 20: + newtype = NPY_COMPLEX160; + break; #endif #ifdef PyArray_FLOAT96 - case 24: - newtype = PyArray_COMPLEX192; - break; + case 24: + newtype = NPY_COMPLEX192; + break; #endif #ifdef PyArray_FLOAT128 - case 32: - newtype = PyArray_COMPLEX256; - break; + case 32: + newtype = NPY_COMPLEX256; + break; #endif - default: - newtype = PyArray_NOTYPE; - } + } + break; + + case NPY_OBJECTLTR: + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "DType strings 'O4' and 'O8' are deprecated " + "because they are platform specific. Use " + "'O' instead", 0) == 0 && + (itemsize == 4 || itemsize == 8)) { + newtype = NPY_OBJECT; + } + break; + + case NPY_STRINGLTR: + case NPY_STRINGLTR2: + newtype = NPY_STRING; + break; + + case NPY_UNICODELTR: + newtype = NPY_UNICODE; + break; + + case NPY_VOIDLTR: + newtype = NPY_VOID; + break; + + case NPY_DATETIMELTR: + if (itemsize == 8) { + newtype = NPY_DATETIME; + } + break; + + case NPY_TIMEDELTALTR: + if (itemsize == 8) { + newtype = NPY_TIMEDELTA; + } + break; } return newtype; } diff --git a/numpy/core/src/multiarray/convert_datatype.c b/numpy/core/src/multiarray/convert_datatype.c index ee22ba646..73d65c12d 100644 --- a/numpy/core/src/multiarray/convert_datatype.c +++ b/numpy/core/src/multiarray/convert_datatype.c @@ -16,6 +16,7 @@ #include "mapping.h" #include "convert_datatype.h" +#include "_datetime.h" /*NUMPY_API * For backward compatibility @@ -234,20 +235,30 @@ PyArray_CanCastTo(PyArray_Descr *from, PyArray_Descr *to) ret = (npy_bool) PyArray_CanCastSafely(fromtype, totype); if (ret) { /* Check String and Unicode more closely */ - if (fromtype == PyArray_STRING) { - if (totype == PyArray_STRING) { + if (fromtype == NPY_STRING) { + if (totype == NPY_STRING) { ret = (from->elsize <= to->elsize); } - else if (totype == PyArray_UNICODE) { + else if (totype == NPY_UNICODE) { ret = (from->elsize << 2 <= to->elsize); } } - else if (fromtype == PyArray_UNICODE) { - if (totype == PyArray_UNICODE) { + else if (fromtype == NPY_UNICODE) { + if (totype == NPY_UNICODE) { ret = (from->elsize <= to->elsize); } } /* + * For datetime/timedelta, only treat casts moving towards + * more precision as safe. + */ + else if (fromtype == NPY_DATETIME && totype == NPY_DATETIME) { + return datetime_metadata_divides(from, to, 0); + } + else if (fromtype == NPY_TIMEDELTA && totype == NPY_TIMEDELTA) { + return datetime_metadata_divides(from, to, 1); + } + /* * TODO: If totype is STRING or unicode * see if the length is long enough to hold the * stringified value of the object. @@ -289,9 +300,12 @@ dtype_kind_to_ordering(char kind) /* Object kind */ case 'O': return 9; - /* Anything else - ideally shouldn't happen... */ + /* + * Anything else, like datetime, is special cased to + * not fit in this hierarchy + */ default: - return 10; + return -1; } } @@ -359,17 +373,35 @@ PyArray_CanCastTypeTo(PyArray_Descr *from, PyArray_Descr *to, return ret; } - switch (casting) { - case NPY_NO_CASTING: - return (from->elsize == to->elsize) && - PyArray_ISNBO(from->byteorder) == - PyArray_ISNBO(to->byteorder); - case NPY_EQUIV_CASTING: - return (from->elsize == to->elsize); - case NPY_SAFE_CASTING: - return (from->elsize <= to->elsize); + switch (from->type_num) { + case NPY_DATETIME: + case NPY_TIMEDELTA: + switch (casting) { + case NPY_NO_CASTING: + return PyArray_ISNBO(from->byteorder) == + PyArray_ISNBO(to->byteorder) && + has_equivalent_datetime_metadata(from, to); + case NPY_EQUIV_CASTING: + return has_equivalent_datetime_metadata(from, to); + case NPY_SAFE_CASTING: + return datetime_metadata_divides(from, to, + from->type_num == NPY_TIMEDELTA); + default: + return 1; + } + break; default: - return 1; + switch (casting) { + case NPY_NO_CASTING: + return PyArray_EquivTypes(from, to); + case NPY_EQUIV_CASTING: + return (from->elsize == to->elsize); + case NPY_SAFE_CASTING: + return (from->elsize <= to->elsize); + default: + return 1; + } + break; } } /* If safe or same-kind casts are allowed */ @@ -381,9 +413,15 @@ PyArray_CanCastTypeTo(PyArray_Descr *from, PyArray_Descr *to, /* * Also allow casting from lower to higher kinds, according * to the ordering provided by dtype_kind_to_ordering. + * Some kinds, like datetime, don't fit in the hierarchy, + * and are special cased as -1. */ - return dtype_kind_to_ordering(from->kind) <= - dtype_kind_to_ordering(to->kind); + int from_order, to_order; + + from_order = dtype_kind_to_ordering(from->kind); + to_order = dtype_kind_to_ordering(to->kind); + + return from_order != -1 && from_order <= to_order; } else { return 0; @@ -542,7 +580,10 @@ PyArray_PromoteTypes(PyArray_Descr *type1, PyArray_Descr *type2) /* If they're built-in types, use the promotion table */ if (type_num1 < NPY_NTYPES && type_num2 < NPY_NTYPES) { ret_type_num = _npy_type_promotion_table[type_num1][type_num2]; - /* The table doesn't handle string/unicode/void, check the result */ + /* + * The table doesn't handle string/unicode/void/datetime/timedelta, + * so check the result + */ if (ret_type_num >= 0) { return PyArray_DescrFromType(ret_type_num); } @@ -647,10 +688,15 @@ PyArray_PromoteTypes(PyArray_Descr *type1, PyArray_Descr *type2) } switch (type_num1) { - /* BOOL can convert to anything */ + /* BOOL can convert to anything except datetime/void */ case NPY_BOOL: - Py_INCREF(type2); - return type2; + if (type_num2 != NPY_DATETIME && type_num2 != NPY_VOID) { + Py_INCREF(type2); + return type2; + } + else { + break; + } /* For strings and unicodes, take the larger size */ case NPY_STRING: if (type_num2 == NPY_STRING) { @@ -713,13 +759,25 @@ PyArray_PromoteTypes(PyArray_Descr *type1, PyArray_Descr *type2) return type1; } break; + case NPY_DATETIME: + case NPY_TIMEDELTA: + if (type_num2 == NPY_DATETIME || type_num2 == NPY_TIMEDELTA) { + return datetime_type_promotion(type1, type2); + } + break; } switch (type_num2) { - /* BOOL can convert to anything */ + /* BOOL can convert to almost anything */ case NPY_BOOL: - Py_INCREF(type1); - return type1; + if (type_num1 != NPY_DATETIME && type_num1 != NPY_TIMEDELTA && + type_num1 != NPY_VOID) { + Py_INCREF(type1); + return type1; + } + else { + break; + } case NPY_STRING: /* Allow NUMBER -> STRING */ if (PyTypeNum_ISNUMBER(type_num1)) { @@ -733,6 +791,19 @@ PyArray_PromoteTypes(PyArray_Descr *type1, PyArray_Descr *type2) return type2; } break; + case NPY_DATETIME: + if (PyTypeNum_ISINTEGER(type_num1)) { + Py_INCREF(type2); + return type2; + } + break; + case NPY_TIMEDELTA: + if (PyTypeNum_ISINTEGER(type_num1) || + PyTypeNum_ISFLOAT(type_num1)) { + Py_INCREF(type2); + return type2; + } + break; } /* For equivalent types we can return either */ diff --git a/numpy/core/src/multiarray/ctors.c b/numpy/core/src/multiarray/ctors.c index bbbf91f36..c4980d6c0 100644 --- a/numpy/core/src/multiarray/ctors.c +++ b/numpy/core/src/multiarray/ctors.c @@ -950,6 +950,9 @@ PyArray_NewFromDescr(PyTypeObject *subtype, PyArray_Descr *descr, int nd, return NULL; } PyArray_DESCR_REPLACE(descr); + if (descr == NULL) { + return NULL; + } if (descr->type_num == NPY_STRING) { sd = descr->elsize = 1; } @@ -2787,11 +2790,11 @@ PyArray_CopyInto(PyArrayObject *dst, PyArrayObject *src) /*NUMPY_API - PyArray_CheckAxis - - check that axis is valid - convert 0-d arrays to 1-d arrays -*/ + * PyArray_CheckAxis + * + * check that axis is valid + * convert 0-d arrays to 1-d arrays + */ NPY_NO_EXPORT PyObject * PyArray_CheckAxis(PyArrayObject *arr, int *axis, int flags) { diff --git a/numpy/core/src/multiarray/datetime.c b/numpy/core/src/multiarray/datetime.c index ad1d98270..b84a14fde 100644 --- a/numpy/core/src/multiarray/datetime.c +++ b/numpy/core/src/multiarray/datetime.c @@ -5,34 +5,53 @@ #include <time.h> #define _MULTIARRAYMODULE -#define NPY_NO_PREFIX -#include <numpy/ndarrayobject.h> +#include <numpy/arrayobject.h> #include "npy_config.h" #include "numpy/npy_3kcompat.h" +#include "numpy/arrayscalars.h" #include "_datetime.h" -/* For defaults and errors */ -#define NPY_FR_ERR -1 +/* + * Imports the PyDateTime functions so we can create these objects. + * This is called during module initialization + */ +NPY_NO_EXPORT void +numpy_pydatetime_import() +{ + PyDateTime_IMPORT; +} -/* Offset for number of days between Dec 31, 1969 and Jan 1, 0001 -* Assuming Gregorian calendar was always in effect (proleptic Gregorian calendar) -*/ +static int +is_leapyear(npy_int64 year); -/* Calendar Structure for Parsing Long -> Date */ -typedef struct { - int year, month, day; -} ymdstruct; -typedef struct { - int hour, min, sec; -} hmsstruct; +/* For defaults and errors */ +#define NPY_FR_ERR -1 +/* Exported as DATETIMEUNITS in multiarraymodule.c */ +NPY_NO_EXPORT char *_datetime_strings[] = { + NPY_STR_Y, + NPY_STR_M, + NPY_STR_W, + NPY_STR_B, + NPY_STR_D, + NPY_STR_h, + NPY_STR_m, + NPY_STR_s, + NPY_STR_ms, + NPY_STR_us, + NPY_STR_ns, + NPY_STR_ps, + NPY_STR_fs, + NPY_STR_as +}; /* ==================================================== + } == Beginning of section borrowed from mx.DateTime == ==================================================== */ @@ -43,28 +62,12 @@ typedef struct { * 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(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 @@ -84,304 +87,336 @@ day_of_week(npy_longlong absdate) } /* - * 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(npy_longlong year) + ==================================================== + == End of section adapted from mx.DateTime == + ==================================================== +*/ + + +static int +is_leapyear(npy_int64 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; + return (year & 0x3) == 0 && /* year % 4 == 0 */ + ((year % 100) != 0 || + (year % 400) == 0); } /* - * 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 + * Calculates the days offset from the 1970 epoch. */ -static npy_longlong -days_from_ymd(int year, int month, int day) +static npy_int64 +get_datetimestruct_days(const npy_datetimestruct *dts) { + int i, month; + npy_int64 year, days = 0; + int *month_lengths; - /* 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); + year = dts->year - 1970; + days = year * 365; - /* 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; + /* Adjust for leap years */ + if (days >= 0) { + /* + * 1968 is the closest leap year before 1970. + * Exclude the current year, so add 1. + */ + year += 1; + /* Add one day for each 4 years */ + days += year / 4; + /* 1900 is the closest previous year divisible by 100 */ + year += 68; + /* Subtract one day for each 100 years */ + days -= year / 100; + /* 1600 is the closest previous year divisible by 400 */ + year += 300; + /* Add one day for each 400 years */ + days += year / 400; + } + else { + /* + * 1972 is the closest later year after 1970. + * Include the current year, so subtract 2. + */ + year -= 2; + /* Subtract one day for each 4 years */ + days += year / 4; + /* 2000 is the closest later year divisible by 100 */ + year -= 28; + /* Add one day for each 100 years */ + days -= year / 100; + /* 2000 is also the closest later year divisible by 400 */ + /* Subtract one day for each 400 years */ + days += year / 400; + } - /* - * 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; + month_lengths = days_in_month[is_leapyear(dts->year)]; + month = dts->month - 1; - return absdate; + /* Add the months */ + for (i = 0; i < month; ++i) { + days += month_lengths[i]; + } - onError: - return 0; + /* Add the days */ + days += dts->day - 1; + return days; } -/* Returns absolute seconds from an hour, minute, and second - */ -#define secs_from_hms(hour, min, sec, multiplier) (\ - ((hour)*3600 + (min)*60 + (sec)) * (npy_int64)(multiplier)\ -) - /* - * 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 + * Modifies '*days_' to be the day offset within the year, + * and returns the year. */ - -static ymdstruct -days_to_ymdstruct(npy_datetime dlong) +static npy_int64 +days_to_yearsdays(npy_int64 *days_) { - ymdstruct ymd; - long year; - npy_longlong yearoffset; - int leap, dayoffset; - int month = 1, day = 1; - int *monthoffset; + const npy_int64 days_per_400years = (400*365 + 100 - 4 + 1); + /* Adjust so it's relative to the year 2000 (divisible by 400) */ + npy_int64 days = (*days_) - (365*30 + 7), year; - 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 down the 400 year cycle to get the year and day within the year */ + if (days >= 0) { + year = 400 * (days / days_per_400years); + days = days % days_per_400years; + } + else { + year = 400 * ((days - (days_per_400years - 1)) / days_per_400years); + days = days % days_per_400years; + if (days < 0) { + days += days_per_400years; } - break; } - /* Now iterate to find the month */ - monthoffset = month_offset[leap]; - for (month = 1; month < 13; month++) { - if (monthoffset[month] >= dayoffset) - break; + /* Work out the year/day within the 400 year cycle */ + if (days >= 366) { + year += 100 * ((days-1) / (100*365 + 25 - 1)); + days = (days-1) % (100*365 + 25 - 1); + if (days >= 365) { + year += 4 * ((days+1) / (4*365 + 1)); + days = (days+1) % (4*365 + 1); + if (days >= 366) { + year += (days-1) / 365; + days = (days-1) % 365; + } + } } - day = dayoffset - month_offset[leap][month-1]; - ymd.year = year; - ymd.month = month; - ymd.day = day; - - return ymd; + *days_ = days; + return year + 2000; } /* - * Converts an integer number of seconds in a day to hours minutes seconds. - * It assumes seconds is between 0 and 86399. + * Fills in the year, month, day in 'dts' based on the days + * offset from 1970. */ - -static hmsstruct -seconds_to_hmsstruct(npy_longlong dlong) +static void +set_datetimestruct_days(npy_int64 days, npy_datetimestruct *dts) { - int hour, minute, second; - hmsstruct hms; + int *month_lengths, i; - hour = dlong / 3600; - minute = (dlong % 3600) / 60; - second = dlong - (hour*3600 + minute*60); + dts->year = days_to_yearsdays(&days); - hms.hour = hour; - hms.min = minute; - hms.sec = second; - - return hms; + month_lengths = days_in_month[is_leapyear(dts->year)]; + for (i = 0; i < 12; ++i) { + if (days < month_lengths[i]) { + dts->month = i + 1; + dts->day = days + 1; + return; + } + else { + days -= month_lengths[i]; + } + } } /* - ==================================================== - == 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. + * Converts a datetime from a datetimestruct to a datetime based + * on some metadata. The date is assumed to be valid. + * + * TODO: If meta->num is really big, there could be overflow + * + * Returns 0 on success, -1 on failure. */ -NPY_NO_EXPORT npy_datetime -PyArray_DatetimeStructToDatetime(NPY_DATETIMEUNIT fr, npy_datetimestruct *d) +NPY_NO_EXPORT int +convert_datetimestruct_to_datetime(PyArray_DatetimeMetaData *meta, + const npy_datetimestruct *dts, + npy_datetime *out) { npy_datetime ret; - npy_longlong days = 0; /* The absolute number of days since Jan 1, 1970 */ + NPY_DATETIMEUNIT base = meta->base; - if (fr > NPY_FR_M) { - days = days_from_ymd(d->year, d->month, d->day); + /* If the datetimestruct is NaT, return NaT */ + if (dts->year == NPY_DATETIME_NAT) { + *out = NPY_DATETIME_NAT; + return 0; } - if (fr == NPY_FR_Y) { - ret = d->year - 1970; + + if (dts->event < 0 || dts->event >= meta->events) { + PyErr_Format(PyExc_ValueError, + "NumPy datetime event %d is outside range [0,%d)", + (int)dts->event, (int)meta->events); + return -1; } - else if (fr == NPY_FR_M) { - ret = (d->year - 1970) * 12 + d->month - 1; + + if (base == NPY_FR_Y) { + /* Truncate to the year */ + ret = dts->year - 1970; } - 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 (base == NPY_FR_M) { + /* Truncate to the month */ + ret = 12 * (dts->year - 1970) + (dts->month - 1); + } + else { + /* Otherwise calculate the number of days to start */ + npy_int64 days = get_datetimestruct_days(dts); + int dotw; + + switch (base) { + case NPY_FR_W: + /* Truncate to weeks */ + if (days >= 0) { + ret = days / 7; + } + else { + ret = (days - 6) / 7; + } + break; + case NPY_FR_B: + /* TODO: this needs work... */ + dotw = day_of_week(days); + + if (dotw > 4) { + /* Invalid business day */ + ret = 0; + } + else { + npy_int64 x; + if (days >= 0) { + /* offset to adjust first week */ + x = days - 4; + } + else { + x = days - 2; + } + ret = 2 + (x / 7) * 5 + x % 7; + } + break; + case NPY_FR_D: + ret = days; + break; + case NPY_FR_h: + ret = days * 24 + + dts->hour; + break; + case NPY_FR_m: + ret = (days * 24 + + dts->hour) * 60 + + dts->min; + break; + case NPY_FR_s: + ret = ((days * 24 + + dts->hour) * 60 + + dts->min) * 60 + + dts->sec; + break; + case NPY_FR_ms: + ret = (((days * 24 + + dts->hour) * 60 + + dts->min) * 60 + + dts->sec) * 1000 + + dts->us / 1000; + break; + case NPY_FR_us: + ret = (((days * 24 + + dts->hour) * 60 + + dts->min) * 60 + + dts->sec) * 1000000 + + dts->us; + break; + case NPY_FR_ns: + ret = ((((days * 24 + + dts->hour) * 60 + + dts->min) * 60 + + dts->sec) * 1000000 + + dts->us) * 1000 + + dts->ps / 1000; + break; + case NPY_FR_ps: + ret = ((((days * 24 + + dts->hour) * 60 + + dts->min) * 60 + + dts->sec) * 1000000 + + dts->us) * 1000000 + + dts->ps; + break; + case NPY_FR_fs: + /* only 2.6 hours */ + ret = (((((days * 24 + + dts->hour) * 60 + + dts->min) * 60 + + dts->sec) * 1000000 + + dts->us) * 1000000 + + dts->ps) * 1000 + + dts->as / 1000; + break; + case NPY_FR_as: + /* only 9.2 secs */ + ret = (((((days * 24 + + dts->hour) * 60 + + dts->min) * 60 + + dts->sec) * 1000000 + + dts->us) * 1000000 + + dts->ps) * 1000000 + + dts->as; + break; + default: + /* Something got corrupted */ + PyErr_SetString(PyExc_ValueError, + "NumPy datetime metadata with corrupt unit value"); + return -1; } } - else if (fr == NPY_FR_B) { - npy_longlong x; - int dotw = day_of_week(days); - if (dotw > 4) { - /* Invalid business day */ - ret = 0; + /* Divide by the multiplier */ + if (meta->num > 1) { + if (ret >= 0) { + ret /= meta->num; } else { - if (days >= 0) { - /* offset to adjust first week */ - x = days - 4; - } - else { - x = days - 2; - } - ret = 2 + (x / 7) * 5 + x % 7; + ret = (ret - meta->num + 1) / meta->num; } } - 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 * (npy_int64)(86400) + - secs_from_hms(d->hour, d->min, d->sec, 1); - } - else if (fr == NPY_FR_ms) { - ret = days * (npy_int64)(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 *= (npy_int64)(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 *= (npy_int64)(1000 * 1000); - ret = days * num + secs_from_hms(d->hour, d->min, d->sec, 1000000000) - + d->us * (npy_int64)(1000) + (d->ps / 1000); - } - else if (fr == NPY_FR_ps) { - npy_int64 num2 = 1000 * 1000; - npy_int64 num1; - num2 *= (npy_int64)(1000 * 1000); - num1 = (npy_int64)(86400) * num2; - ret = days * num1 + secs_from_hms(d->hour, d->min, d->sec, num2) - + d->us * (npy_int64)(1000000) + d->ps; + /* Add in the event number if needed */ + if (meta->events > 1) { + /* Multiply by the number of events and put in the event number */ + ret = ret * meta->events + dts->event; } - else if (fr == NPY_FR_fs) { - /* only 2.6 hours */ - npy_int64 num2 = 1000000; - num2 *= (npy_int64)(1000000); - num2 *= (npy_int64)(1000); - /* get number of seconds as a postive or negative number */ - if (days >= 0) { - ret = secs_from_hms(d->hour, d->min, d->sec, 1); - } - else { - ret = ((d->hour - 24)*3600 + d->min*60 + d->sec); - } - ret = ret * num2 + d->us * (npy_int64)(1000000000) - + d->ps * (npy_int64)(1000) + (d->as / 1000); - } - else if (fr == NPY_FR_as) { - /* only 9.2 secs */ - npy_int64 num1, num2; + *out = ret; - num1 = 1000000; - num1 *= (npy_int64)(1000000); - num2 = num1 * (npy_int64)(1000000); + return 0; +} - if (days >= 0) { - ret = d->sec; - } - else { - ret = d->sec - 60; - } - ret = ret * num2 + d->us * num1 + d->ps * (npy_int64)(1000000) - + d->as; - } - else { - /* Shouldn't get here */ - PyErr_SetString(PyExc_ValueError, "invalid internal frequency"); - ret = -1; +/*NUMPY_API + * Create a datetime value from a filled datetime struct and resolution unit. + * + * TO BE REMOVED - NOT USED INTERNALLY. + */ +NPY_NO_EXPORT npy_datetime +PyArray_DatetimeStructToDatetime(NPY_DATETIMEUNIT fr, npy_datetimestruct *d) +{ + npy_datetime ret; + PyArray_DatetimeMetaData meta; + + /* Set up a dummy metadata for the conversion */ + meta.base = fr; + meta.num = 1; + meta.events = 1; + + if (convert_datetimestruct_to_datetime(&meta, d, &ret) < 0) { + /* The caller then needs to check PyErr_Occurred() */ + return -1; } return ret; @@ -394,6 +429,8 @@ PyArray_DatetimeStructToDatetime(NPY_DATETIMEUNIT fr, npy_datetimestruct *d) /*NUMPY_API * Create a timdelta value from a filled timedelta struct and resolution unit. + * + * TO BE REMOVED - NOT USED INTERNALLY. */ NPY_NO_EXPORT npy_datetime PyArray_TimedeltaStructToTimedelta(NPY_DATETIMEUNIT fr, npy_timedeltastruct *d) @@ -486,306 +523,306 @@ PyArray_TimedeltaStructToTimedelta(NPY_DATETIMEUNIT fr, npy_timedeltastruct *d) return ret; } - - -/*NUMPY_API - * Fill the datetime struct from the value and resolution unit. +/* + * Converts a datetime based on the given metadata into a datetimestruct */ -NPY_NO_EXPORT void -PyArray_DatetimeToDatetimeStruct(npy_datetime val, NPY_DATETIMEUNIT fr, - npy_datetimestruct *result) +NPY_NO_EXPORT int +convert_datetime_to_datetimestruct(PyArray_DatetimeMetaData *meta, + npy_datetime dt, + npy_datetimestruct *out) { - int year = 1970, month = 1, day = 1, - hour = 0, min = 0, sec = 0, - us = 0, ps = 0, as = 0; + npy_int64 absdays; + npy_int64 perday; + + /* Initialize the output to all zeros */ + memset(out, 0, sizeof(npy_datetimestruct)); + out->year = 1970; + out->month = 1; + out->day = 1; + + /* NaT is signaled in the year */ + if (dt == NPY_DATETIME_NAT) { + out->year = NPY_DATETIME_NAT; + return 0; + } + + /* Extract the event number */ + if (meta->events > 1) { + out->event = dt % meta->events; + dt = dt / meta->events; + if (out->event < 0) { + out->event += meta->events; + --dt; + } + } - npy_int64 tmp; - ymdstruct ymd; - hmsstruct hms; + /* TODO: Change to a mechanism that avoids the potential overflow */ + dt *= meta->num; /* - * 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). + * Note that care must be taken with the / and % operators + * for negative values. */ - 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(sec); - 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) * (npy_int64)(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 *= (npy_int64)(1000); - num1 = (npy_int64)(86400) * num3; - num2 = num1 - 1; + switch (meta->base) { + case NPY_FR_Y: + out->year = 1970 + dt; + break; - 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 *= (npy_int64)(1000); - num2 = num1 * (npy_int64)(1000); + case NPY_FR_M: + if (dt >= 0) { + out->year = 1970 + dt / 12; + out->month = dt % 12 + 1; + } + else { + out->year = 1969 + (dt + 1) / 12; + out->month = 12 + (dt + 1)% 12; + } + break; - 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; + case NPY_FR_W: + /* A week is 7 days */ + set_datetimestruct_days(dt * 7, out); + break; + + case NPY_FR_B: + /* TODO: fix up business days */ + /* Number of business days since Thursday, 1-1-70 */ + /* + * A business 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 (dt >= 0) { + absdays = 7 * ((dt + 3) / 5) + ((dt + 3) % 5) - 3; } else { - hour = 24 + (sec - 3599)/3600; - sec = 3599 + (sec+1)%3600; - min = sec / 60; - sec = sec % 60; + /* Recall how C computes / and % with negative numbers */ + absdays = 7 * ((dt - 1) / 5) + ((dt - 1) % 5) + 1; } - } - us = tmp / 1000000000; - tmp = tmp % 1000000000; - ps = tmp / 1000; - as = (tmp % 1000) * (npy_int64)(1000); - } - else if (fr == NPY_FR_as) { - /* entire range is only += 9.2 seconds */ - npy_int64 num1, num2, num3; - num1 = 1000000; - num2 = num1 * (npy_int64)(1000000); - num3 = num2 * (npy_int64)(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"); + set_datetimestruct_days(absdays, out); + break; + + case NPY_FR_D: + set_datetimestruct_days(dt, out); + break; + + case NPY_FR_h: + perday = 24LL; + + if (dt >= 0) { + set_datetimestruct_days(dt / perday, out); + dt = dt % perday; + } + else { + set_datetimestruct_days((dt - (perday-1)) / perday, out); + dt = (perday-1) + (dt + 1) % perday; + } + out->hour = dt; + break; + + case NPY_FR_m: + perday = 24LL * 60; + + if (dt >= 0) { + set_datetimestruct_days(dt / perday, out); + dt = dt % perday; + } + else { + set_datetimestruct_days((dt - (perday-1)) / perday, out); + dt = (perday-1) + (dt + 1) % perday; + } + out->hour = dt / 60; + out->min = dt % 60; + break; + + case NPY_FR_s: + perday = 24LL * 60 * 60; + + if (dt >= 0) { + set_datetimestruct_days(dt / perday, out); + dt = dt % perday; + } + else { + set_datetimestruct_days((dt - (perday-1)) / perday, out); + dt = (perday-1) + (dt + 1) % perday; + } + out->hour = dt / (60*60); + out->min = (dt / 60) % 60; + out->sec = dt % 60; + break; + + case NPY_FR_ms: + perday = 24LL * 60 * 60 * 1000; + + if (dt >= 0) { + set_datetimestruct_days(dt / perday, out); + dt = dt % perday; + } + else { + set_datetimestruct_days((dt - (perday-1)) / perday, out); + dt = (perday-1) + (dt + 1) % perday; + } + out->hour = dt / (60*60*1000LL); + out->min = (dt / (60*1000LL)) % 60; + out->sec = (dt / 1000LL) % 60; + out->us = (dt % 1000LL) * 1000; + break; + + case NPY_FR_us: + perday = 24LL * 60LL * 60LL * 1000LL * 1000LL; + + if (dt >= 0) { + set_datetimestruct_days(dt / perday, out); + dt = dt % perday; + } + else { + set_datetimestruct_days((dt - (perday-1)) / perday, out); + dt = (perday-1) + (dt + 1) % perday; + } + out->hour = dt / (60*60*1000000LL); + out->min = (dt / (60*1000000LL)) % 60; + out->sec = (dt / 1000000LL) % 60; + out->us = dt % 1000000LL; + break; + + case NPY_FR_ns: + perday = 24LL * 60LL * 60LL * 1000LL * 1000LL * 1000LL; + + if (dt >= 0) { + set_datetimestruct_days(dt / perday, out); + dt = dt % perday; + } + else { + set_datetimestruct_days((dt - (perday-1)) / perday, out); + dt = (perday-1) + (dt + 1) % perday; + } + out->hour = dt / (60*60*1000000000LL); + out->min = (dt / (60*1000000000LL)) % 60; + out->sec = (dt / 1000000000LL) % 60; + out->us = (dt / 1000LL) % 1000000LL; + out->ps = (dt % 1000LL) * 1000; + break; + + case NPY_FR_ps: + perday = 24LL * 60 * 60 * 1000 * 1000 * 1000 * 1000; + + if (dt >= 0) { + set_datetimestruct_days(dt / perday, out); + dt = dt % perday; + } + else { + set_datetimestruct_days((dt - (perday-1)) / perday, out); + dt = (perday-1) + (dt + 1) % perday; + } + out->hour = dt / (60*60*1000000000000LL); + out->min = (dt / (60*1000000000000LL)) % 60; + out->sec = (dt / 1000000000000LL) % 60; + out->us = (dt / 1000000LL) % 1000000LL; + out->ps = dt % 1000000LL; + break; + + case NPY_FR_fs: + /* entire range is only +- 2.6 hours */ + if (dt >= 0) { + out->hour = dt / (60*60*1000000000000000LL); + out->min = (dt / (60*1000000000000000LL)) % 60; + out->sec = (dt / 1000000000000000LL) % 60; + out->us = (dt / 1000000000LL) % 1000000LL; + out->ps = (dt / 1000LL) % 1000000LL; + out->as = (dt % 1000LL) * 1000; + } + else { + npy_datetime minutes; + + minutes = dt / (60*1000000000000000LL); + dt = dt % (60*1000000000000000LL); + if (dt < 0) { + dt += (60*1000000000000000LL); + --minutes; + } + /* Offset the negative minutes */ + add_minutes_to_datetimestruct(out, minutes); + out->sec = (dt / 1000000000000000LL) % 60; + out->us = (dt / 1000000000LL) % 1000000LL; + out->ps = (dt / 1000LL) % 1000000LL; + out->as = (dt % 1000LL) * 1000; + } + break; + + case NPY_FR_as: + /* entire range is only +- 9.2 seconds */ + if (dt >= 0) { + out->sec = (dt / 1000000000000000000LL) % 60; + out->us = (dt / 1000000000000LL) % 1000000LL; + out->ps = (dt / 1000000LL) % 1000000LL; + out->as = dt % 1000000LL; + } + else { + npy_datetime seconds; + + seconds = dt / 1000000000000000000LL; + dt = dt % 1000000000000000000LL; + if (dt < 0) { + dt += 1000000000000000000LL; + --seconds; + } + /* Offset the negative seconds */ + add_seconds_to_datetimestruct(out, seconds); + out->us = (dt / 1000000000000LL) % 1000000LL; + out->ps = (dt / 1000000LL) % 1000000LL; + out->as = dt % 1000000LL; + } + break; + + default: + PyErr_SetString(PyExc_RuntimeError, + "NumPy datetime metadata is corrupted with invalid " + "base unit"); + return -1; } - 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 0; +} + + +/*NUMPY_API + * Fill the datetime struct from the value and resolution unit. + * + * TO BE REMOVED - NOT USED INTERNALLY. + */ +NPY_NO_EXPORT void +PyArray_DatetimeToDatetimeStruct(npy_datetime val, NPY_DATETIMEUNIT fr, + npy_datetimestruct *result) +{ + PyArray_DatetimeMetaData meta; + + /* Set up a dummy metadata for the conversion */ + meta.base = fr; + meta.num = 1; + meta.events = 1; + + if (convert_datetime_to_datetimestruct(&meta, val, result) < 0) { + /* The caller needs to check PyErr_Occurred() */ + return; + } return; } /* * FIXME: Overflow is not handled at all - * To convert from Years, Months, and Business Days, multiplication by the average is done + * 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. + * + * TO BE REMOVED - NOT USED INTERNALLY. */ NPY_NO_EXPORT void PyArray_TimedeltaToTimedeltaStruct(npy_timedelta val, NPY_DATETIMEUNIT fr, @@ -796,9 +833,10 @@ PyArray_TimedeltaToTimedeltaStruct(npy_timedelta val, NPY_DATETIMEUNIT fr, 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). + * 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) { @@ -920,3 +958,3117 @@ PyArray_TimedeltaToTimedeltaStruct(npy_timedelta val, NPY_DATETIMEUNIT fr, } return; } + +/* + * This function returns the a new reference to the + * capsule with the datetime metadata. + */ +NPY_NO_EXPORT PyObject * +get_datetime_metacobj_from_dtype(PyArray_Descr *dtype) +{ + PyObject *metacobj; + + /* Check that the dtype has metadata */ + if (dtype->metadata == NULL) { + PyErr_SetString(PyExc_TypeError, + "Datetime type object is invalid, lacks metadata"); + return NULL; + } + + /* Check that the dtype has unit metadata */ + metacobj = PyDict_GetItemString(dtype->metadata, NPY_METADATA_DTSTR); + if (metacobj == NULL) { + PyErr_SetString(PyExc_TypeError, + "Datetime type object is invalid, lacks unit metadata"); + return NULL; + } + + Py_INCREF(metacobj); + return metacobj; +} + +/* + * This function returns a pointer to the DateTimeMetaData + * contained within the provided datetime dtype. + */ +NPY_NO_EXPORT PyArray_DatetimeMetaData * +get_datetime_metadata_from_dtype(PyArray_Descr *dtype) +{ + PyObject *metacobj; + PyArray_DatetimeMetaData *meta = NULL; + + metacobj = get_datetime_metacobj_from_dtype(dtype); + if (metacobj == NULL) { + return NULL; + } + + /* Check that the dtype has an NpyCapsule for the metadata */ + meta = (PyArray_DatetimeMetaData *)NpyCapsule_AsVoidPtr(metacobj); + if (meta == NULL) { + PyErr_SetString(PyExc_TypeError, + "Datetime type object is invalid, unit metadata is corrupt"); + return NULL; + } + + return meta; +} + +/* + * Converts a substring given by 'str' and 'len' into + * a date time unit multiplier + enum value, which are populated + * into out_meta. Other metadata is left along. + * + * 'metastr' is only used in the error message, and may be NULL. + * + * Returns 0 on success, -1 on failure. + */ +NPY_NO_EXPORT int +parse_datetime_extended_unit_from_string(char *str, Py_ssize_t len, + char *metastr, + PyArray_DatetimeMetaData *out_meta) +{ + char *substr = str, *substrend = NULL; + int den = 1; + + /* First comes an optional integer multiplier */ + out_meta->num = (int)strtol(substr, &substrend, 10); + if (substr == substrend) { + out_meta->num = 1; + } + substr = substrend; + + /* Next comes the unit itself, followed by either '/' or the string end */ + substrend = substr; + while (substrend-str < len && *substrend != '/') { + ++substrend; + } + if (substr == substrend) { + goto bad_input; + } + out_meta->base = parse_datetime_unit_from_string(substr, + substrend-substr, metastr); + if (out_meta->base == -1) { + return -1; + } + substr = substrend; + + /* Next comes an optional integer denominator */ + if (substr-str < len && *substr == '/') { + substr++; + den = (int)strtol(substr, &substrend, 10); + /* If the '/' exists, there must be a number followed by ']' */ + if (substr == substrend || *substrend != ']') { + goto bad_input; + } + substr = substrend + 1; + } + else if (substr-str != len) { + goto bad_input; + } + + if (den != 1) { + if (convert_datetime_divisor_to_multiple( + out_meta, den, metastr) < 0) { + return -1; + } + } + + return 0; + +bad_input: + if (metastr != NULL) { + PyErr_Format(PyExc_TypeError, + "Invalid datetime metadata string \"%s\" at position %d", + metastr, (int)(substr-metastr)); + } + else { + PyErr_Format(PyExc_TypeError, + "Invalid datetime metadata string \"%s\"", + str); + } + + return -1; +} + +/* + * Parses the metadata string into the metadata C structure. + * + * Returns 0 on success, -1 on failure. + */ +NPY_NO_EXPORT int +parse_datetime_metadata_from_metastr(char *metastr, Py_ssize_t len, + PyArray_DatetimeMetaData *out_meta) +{ + char *substr = metastr, *substrend = NULL; + + /* The metadata string must start with a '[' */ + if (len < 3 || *substr++ != '[') { + goto bad_input; + } + + substrend = substr; + while (*substrend != '\0' && *substrend != ']') { + ++substrend; + } + if (*substrend == '\0' || substr == substrend) { + substr = substrend; + goto bad_input; + } + + /* Parse the extended unit inside the [] */ + if (parse_datetime_extended_unit_from_string(substr, substrend-substr, + metastr, out_meta) < 0) { + return -1; + } + + substr = substrend+1; + + /* Finally comes an optional number of events */ + if (substr[0] == '/' && substr[1] == '/') { + substr += 2; + + out_meta->events = (int)strtol(substr, &substrend, 10); + if (substr == substrend || *substrend != '\0') { + goto bad_input; + } + } + else if (*substr != '\0') { + goto bad_input; + } + else { + out_meta->events = 1; + } + + return 0; + +bad_input: + if (substr != metastr) { + PyErr_Format(PyExc_TypeError, + "Invalid datetime metadata string \"%s\" at position %d", + metastr, (int)(substr-metastr)); + } + else { + PyErr_Format(PyExc_TypeError, + "Invalid datetime metadata string \"%s\"", + metastr); + } + + return -1; +} + +NPY_NO_EXPORT PyObject * +parse_datetime_metacobj_from_metastr(char *metastr, Py_ssize_t len) +{ + PyArray_DatetimeMetaData *dt_data; + + dt_data = PyArray_malloc(sizeof(PyArray_DatetimeMetaData)); + if (dt_data == NULL) { + return PyErr_NoMemory(); + } + + /* If there's no metastr, use the default */ + if (len == 0) { + dt_data->num = 1; + dt_data->base = NPY_DATETIME_DEFAULTUNIT; + dt_data->events = 1; + } + else { + if (parse_datetime_metadata_from_metastr(metastr, len, dt_data) < 0) { + PyArray_free(dt_data); + return NULL; + } + } + + return NpyCapsule_FromVoidPtr((void *)dt_data, simple_capsule_dtor); +} + +/* + * Converts a datetype dtype string into a dtype descr object. + * The "type" string should be NULL-terminated. + */ +NPY_NO_EXPORT PyArray_Descr * +parse_dtype_from_datetime_typestr(char *typestr, Py_ssize_t len) +{ + PyArray_Descr *dtype = NULL; + char *metastr = NULL; + int is_timedelta = 0; + Py_ssize_t metalen = 0; + PyObject *metacobj = NULL; + + if (len < 2) { + PyErr_Format(PyExc_TypeError, + "Invalid datetime typestr \"%s\"", + typestr); + return NULL; + } + + /* + * First validate that the root is correct, + * and get the metadata string address + */ + if (typestr[0] == 'm' && typestr[1] == '8') { + is_timedelta = 1; + metastr = typestr + 2; + metalen = len - 2; + } + else if (typestr[0] == 'M' && typestr[1] == '8') { + is_timedelta = 0; + metastr = typestr + 2; + metalen = len - 2; + } + else if (len >= 11 && strncmp(typestr, "timedelta64", 11) == 0) { + is_timedelta = 1; + metastr = typestr + 11; + metalen = len - 11; + } + else if (len >= 10 && strncmp(typestr, "datetime64", 10) == 0) { + is_timedelta = 0; + metastr = typestr + 10; + metalen = len - 10; + } + else { + PyErr_Format(PyExc_TypeError, + "Invalid datetime typestr \"%s\"", + typestr); + return NULL; + } + + /* Create a default datetime or timedelta */ + if (is_timedelta) { + dtype = PyArray_DescrNewFromType(NPY_TIMEDELTA); + } + else { + dtype = PyArray_DescrNewFromType(NPY_DATETIME); + } + if (dtype == NULL) { + return NULL; + } + + /* + * Remove any reference to old metadata dictionary + * And create a new one for this new dtype + */ + Py_XDECREF(dtype->metadata); + dtype->metadata = PyDict_New(); + if (dtype->metadata == NULL) { + Py_DECREF(dtype); + return NULL; + } + + /* Parse the metadata string into a metadata capsule */ + metacobj = parse_datetime_metacobj_from_metastr(metastr, metalen); + if (metacobj == NULL) { + Py_DECREF(dtype); + return NULL; + } + + /* Set the metadata object in the dictionary. */ + if (PyDict_SetItemString(dtype->metadata, NPY_METADATA_DTSTR, + metacobj) < 0) { + Py_DECREF(dtype); + Py_DECREF(metacobj); + return NULL; + } + Py_DECREF(metacobj); + + return dtype; +} + +/* + * Creates a new NPY_TIMEDELTA dtype, copying the datetime metadata + * from the given dtype. + */ +NPY_NO_EXPORT PyArray_Descr * +timedelta_dtype_with_copied_meta(PyArray_Descr *dtype) +{ + PyArray_Descr *ret; + PyObject *metacobj; + + ret = PyArray_DescrNewFromType(NPY_TIMEDELTA); + if (ret == NULL) { + return NULL; + } + Py_XDECREF(ret->metadata); + ret->metadata = PyDict_New(); + if (ret->metadata == NULL) { + Py_DECREF(ret); + return NULL; + } + + metacobj = get_datetime_metacobj_from_dtype(dtype); + if (metacobj == NULL) { + Py_DECREF(ret); + return NULL; + } + + if (PyDict_SetItemString(ret->metadata, NPY_METADATA_DTSTR, + metacobj) < 0) { + Py_DECREF(metacobj); + Py_DECREF(ret); + return NULL; + } + + return ret; +} + +static NPY_DATETIMEUNIT _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. + * 'metastr' is used for the error message if the divisor doesn't work, + * and can be NULL if the metadata didn't come from a string. + * + * This function only affects the 'base' and 'num' values in the metadata. + * + * Returns 0 on success, -1 on failure. + */ +NPY_NO_EXPORT int +convert_datetime_divisor_to_multiple(PyArray_DatetimeMetaData *meta, + int den, char *metastr) +{ + int i, num, ind; + NPY_DATETIMEUNIT *totry; + NPY_DATETIMEUNIT *baseunit; + int q, r; + + ind = ((int)meta->base - (int)NPY_FR_Y)*2; + totry = _multiples_table[ind]; + baseunit = _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 = _multiples_table[ind + 1]; + baseunit[0] = meta->base + 1; + baseunit[1] = meta->base + 2; + if (meta->base == NPY_DATETIME_NUMUNITS - 2) { + num = 1; + } + if (meta->base == NPY_DATETIME_NUMUNITS - 1) { + num = 0; + } + } + + for (i = 0; i < num; i++) { + q = totry[i] / den; + r = totry[i] % den; + if (r == 0) { + break; + } + } + if (i == num) { + if (metastr == NULL) { + PyErr_Format(PyExc_ValueError, + "divisor (%d) is not a multiple of a lower-unit " + "in datetime metadata", den); + } + else { + PyErr_Format(PyExc_ValueError, + "divisor (%d) is not a multiple of a lower-unit " + "in datetime metadata \"%s\"", den, metastr); + } + return -1; + } + meta->base = baseunit[i]; + meta->num *= q; + + return 0; +} + +/* + * Lookup table for factors between datetime units, except + * for years, months, and business days. + */ +static npy_uint32 +_datetime_factors[] = { + 1, /* Years - not used */ + 1, /* Months - not used */ + 7, /* Weeks -> Days */ + 1, /* Business days - not used */ + 24, /* Days -> Hours */ + 60, /* Hours -> Minutes */ + 60, /* Minutes -> Seconds */ + 1000, + 1000, + 1000, + 1000, + 1000, + 1000, + 1 /* Attoseconds are the smallest base unit */ +}; + +/* + * Returns the scale factor between the units. Does not validate + * that bigbase represents larger units than littlebase. + * + * Returns 0 if there is an overflow. + */ +static npy_uint64 +get_datetime_units_factor(NPY_DATETIMEUNIT bigbase, NPY_DATETIMEUNIT littlebase) +{ + npy_uint64 factor = 1; + int unit = (int)bigbase; + while (littlebase > unit) { + factor *= _datetime_factors[unit]; + /* + * Detect overflow by disallowing the top 16 bits to be 1. + * That alows a margin of error much bigger than any of + * the datetime factors. + */ + if (factor&0xff00000000000000ULL) { + return 0; + } + ++unit; + } + return factor; +} + +/* Euclidean algorithm on two positive numbers */ +static npy_uint64 +_uint64_euclidean_gcd(npy_uint64 x, npy_uint64 y) +{ + npy_uint64 tmp; + + if (x > y) { + tmp = x; + x = y; + y = tmp; + } + while (x != y && y != 0) { + tmp = x % y; + x = y; + y = tmp; + } + + return x; +} + +/* + * Computes the conversion factor to convert data with 'src_meta' metadata + * into data with 'dst_meta' metadata, not taking into account the events. + * + * To convert a npy_datetime or npy_timedelta, first the event number needs + * to be divided away, then it needs to be scaled by num/denom, and + * finally the event number can be added back in. + * + * If overflow occurs, both out_num and out_denom are set to 0, but + * no error is set. + */ +NPY_NO_EXPORT void +get_datetime_conversion_factor(PyArray_DatetimeMetaData *src_meta, + PyArray_DatetimeMetaData *dst_meta, + npy_int64 *out_num, npy_int64 *out_denom) +{ + int src_base, dst_base, swapped; + npy_uint64 num = 1, denom = 1, tmp, gcd; + + if (src_meta->base <= dst_meta->base) { + src_base = src_meta->base; + dst_base = dst_meta->base; + swapped = 0; + } + else { + src_base = dst_meta->base; + dst_base = src_meta->base; + swapped = 1; + } + + if (src_base != dst_base) { + /* + * Conversions between years/months and other units use + * the factor averaged over the 400 year leap year cycle. + */ + if (src_base == NPY_FR_Y) { + if (dst_base == NPY_FR_M) { + num *= 12; + } + else if (dst_base == NPY_FR_W) { + num *= (97 + 400*365); + denom *= 400*7; + } + else { + /* Year -> Day */ + num *= (97 + 400*365); + denom *= 400; + /* Day -> dst_base */ + num *= get_datetime_units_factor(NPY_FR_D, dst_base); + } + } + else if (src_base == NPY_FR_M) { + if (dst_base == NPY_FR_W) { + num *= (97 + 400*365); + denom *= 400*12*7; + } + else { + /* Month -> Day */ + num *= (97 + 400*365); + denom *= 400*12; + /* Day -> dst_base */ + num *= get_datetime_units_factor(NPY_FR_D, dst_base); + } + } + else { + num *= get_datetime_units_factor(src_base, dst_base); + } + } + + /* If something overflowed, make both num and denom 0 */ + if (denom == 0) { + *out_num = 0; + *out_denom = 0; + return; + } + + /* Swap the numerator and denominator if necessary */ + if (swapped) { + tmp = num; + num = denom; + denom = tmp; + } + + num *= src_meta->num; + denom *= dst_meta->num; + + /* Return as a fraction in reduced form */ + gcd = _uint64_euclidean_gcd(num, denom); + *out_num = (npy_int64)(num / gcd); + *out_denom = (npy_int64)(denom / gcd); +} + +/* + * Determines whether the 'divisor' metadata divides evenly into + * the 'dividend' metadata. + */ +NPY_NO_EXPORT npy_bool +datetime_metadata_divides( + PyArray_Descr *dividend, + PyArray_Descr *divisor, + int strict_with_nonlinear_units) +{ + PyArray_DatetimeMetaData *meta1, *meta2; + npy_uint64 num1, num2; + + /* Must be datetime types */ + if ((dividend->type_num != NPY_DATETIME && + dividend->type_num != NPY_TIMEDELTA) || + (divisor->type_num != NPY_DATETIME && + divisor->type_num != NPY_TIMEDELTA)) { + return 0; + } + + meta1 = get_datetime_metadata_from_dtype(dividend); + if (meta1 == NULL) { + PyErr_Clear(); + return 0; + } + meta2 = get_datetime_metadata_from_dtype(divisor); + if (meta2 == NULL) { + PyErr_Clear(); + return 0; + } + + /* Events must match */ + if (meta1->events != meta2->events) { + return 0; + } + + num1 = (npy_uint64)meta1->num; + num2 = (npy_uint64)meta2->num; + + /* If the bases are different, factor in a conversion */ + if (meta1->base != meta2->base) { + /* + * Years, Months, and Business days are incompatible with + * all other units (except years and months are compatible + * with each other). + */ + if (meta1->base == NPY_FR_B || meta2->base == NPY_FR_B) { + return 0; + } + else if (meta1->base == NPY_FR_Y) { + if (meta2->base == NPY_FR_M) { + num1 *= 12; + } + else if (strict_with_nonlinear_units) { + return 0; + } + else { + /* Could do something complicated here */ + return 1; + } + } + else if (meta2->base == NPY_FR_Y) { + if (meta1->base == NPY_FR_M) { + num2 *= 12; + } + else if (strict_with_nonlinear_units) { + return 0; + } + else { + /* Could do something complicated here */ + return 1; + } + } + else if (meta1->base == NPY_FR_M || meta2->base == NPY_FR_M) { + if (strict_with_nonlinear_units) { + return 0; + } + else { + /* Could do something complicated here */ + return 1; + } + } + + /* Take the greater base (unit sizes are decreasing in enum) */ + if (meta1->base > meta2->base) { + num2 *= get_datetime_units_factor(meta2->base, meta1->base); + if (num2 == 0) { + return 0; + } + } + else { + num1 *= get_datetime_units_factor(meta1->base, meta2->base); + if (num1 == 0) { + return 0; + } + } + } + + /* Crude, incomplete check for overflow */ + if (num1&0xff00000000000000LL || num2&0xff00000000000000LL ) { + return 0; + } + + return (num1 % num2) == 0; +} + + +NPY_NO_EXPORT PyObject * +compute_datetime_metadata_greatest_common_divisor( + PyArray_Descr *type1, + PyArray_Descr *type2, + int strict_with_nonlinear_units) +{ + PyArray_DatetimeMetaData *meta1, *meta2, *dt_data; + NPY_DATETIMEUNIT base; + npy_uint64 num1, num2, num; + int events = 1; + + if ((type1->type_num != NPY_DATETIME && + type1->type_num != NPY_TIMEDELTA) || + (type2->type_num != NPY_DATETIME && + type2->type_num != NPY_TIMEDELTA)) { + PyErr_SetString(PyExc_TypeError, + "Require datetime types for metadata " + "greatest common divisor operation"); + return NULL; + } + + meta1 = get_datetime_metadata_from_dtype(type1); + if (meta1 == NULL) { + return NULL; + } + meta2 = get_datetime_metadata_from_dtype(type2); + if (meta2 == NULL) { + return NULL; + } + + /* Take the maximum of the events */ + if (meta1->events > meta2->events) { + events = meta1->events; + } + else { + events = meta2->events; + } + + num1 = (npy_uint64)meta1->num; + num2 = (npy_uint64)meta2->num; + + /* First validate that the units have a reasonable GCD */ + if (meta1->base == meta2->base) { + base = meta1->base; + } + else { + /* + * Years, Months, and Business days are incompatible with + * all other units (except years and months are compatible + * with each other). + */ + if (meta1->base == NPY_FR_Y) { + if (meta2->base == NPY_FR_M) { + base = NPY_FR_M; + num1 *= 12; + } + else if (strict_with_nonlinear_units) { + goto incompatible_units; + } + else { + base = meta2->base; + /* Don't multiply num1 since there is no even factor */ + } + } + else if (meta2->base == NPY_FR_Y) { + if (meta1->base == NPY_FR_M) { + base = NPY_FR_M; + num2 *= 12; + } + else if (strict_with_nonlinear_units) { + goto incompatible_units; + } + else { + base = meta1->base; + /* Don't multiply num2 since there is no even factor */ + } + } + else if (meta1->base == NPY_FR_M || + meta1->base == NPY_FR_B || + meta2->base == NPY_FR_M || + meta2->base == NPY_FR_B) { + if (strict_with_nonlinear_units) { + goto incompatible_units; + } + else { + if (meta1->base > meta2->base) { + base = meta1->base; + } + else { + base = meta2->base; + } + + /* + * When combining business days with other units, end + * up with days instead of business days. + */ + if (base == NPY_FR_B) { + base = NPY_FR_D; + } + } + } + + /* Take the greater base (unit sizes are decreasing in enum) */ + if (meta1->base > meta2->base) { + base = meta1->base; + num2 *= get_datetime_units_factor(meta2->base, meta1->base); + if (num2 == 0) { + goto units_overflow; + } + } + else { + base = meta2->base; + num1 *= get_datetime_units_factor(meta1->base, meta2->base); + if (num1 == 0) { + goto units_overflow; + } + } + } + + /* Compute the GCD of the resulting multipliers */ + num = _uint64_euclidean_gcd(num1, num2); + + /* Create and return the metadata capsule */ + dt_data = PyArray_malloc(sizeof(PyArray_DatetimeMetaData)); + if (dt_data == NULL) { + return PyErr_NoMemory(); + } + + dt_data->base = base; + dt_data->num = (int)num; + if (dt_data->num <= 0 || num != (npy_uint64)dt_data->num) { + goto units_overflow; + } + dt_data->events = events; + + return NpyCapsule_FromVoidPtr((void *)dt_data, simple_capsule_dtor); + +incompatible_units: { + PyObject *errmsg; + errmsg = PyUString_FromString("Cannot get " + "a common metadata divisor for types "); + PyUString_ConcatAndDel(&errmsg, + PyObject_Repr((PyObject *)type1)); + PyUString_ConcatAndDel(&errmsg, + PyUString_FromString(" and ")); + PyUString_ConcatAndDel(&errmsg, + PyObject_Repr((PyObject *)type2)); + PyUString_ConcatAndDel(&errmsg, + PyUString_FromString(" because they have " + "incompatible nonlinear base time units")); + PyErr_SetObject(PyExc_TypeError, errmsg); + return NULL; + } +units_overflow: { + PyObject *errmsg; + errmsg = PyUString_FromString("Integer overflow " + "getting a common metadata divisor for types "); + PyUString_ConcatAndDel(&errmsg, + PyObject_Repr((PyObject *)type1)); + PyUString_ConcatAndDel(&errmsg, + PyUString_FromString(" and ")); + PyUString_ConcatAndDel(&errmsg, + PyObject_Repr((PyObject *)type2)); + PyErr_SetObject(PyExc_OverflowError, errmsg); + return NULL; + } +} + +/* + * Uses type1's type_num and the gcd of the metadata to create + * the result type. + */ +static PyArray_Descr * +datetime_gcd_type_promotion(PyArray_Descr *type1, PyArray_Descr *type2) +{ + PyObject *gcdmeta; + PyArray_Descr *dtype; + + /* + * Get the metadata GCD, being strict about nonlinear units for + * timedelta and relaxed for datetime. + */ + gcdmeta = compute_datetime_metadata_greatest_common_divisor( + type1, type2, + type1->type_num == NPY_TIMEDELTA); + if (gcdmeta == NULL) { + return NULL; + } + + /* Create a DATETIME or TIMEDELTA dtype */ + dtype = PyArray_DescrNewFromType(type1->type_num); + if (dtype == NULL) { + Py_DECREF(gcdmeta); + return NULL; + } + + /* Replace the metadata dictionary */ + Py_XDECREF(dtype->metadata); + dtype->metadata = PyDict_New(); + if (dtype->metadata == NULL) { + Py_DECREF(dtype); + Py_DECREF(gcdmeta); + return NULL; + } + + /* Set the metadata object in the dictionary. */ + if (PyDict_SetItemString(dtype->metadata, NPY_METADATA_DTSTR, + gcdmeta) < 0) { + Py_DECREF(dtype); + Py_DECREF(gcdmeta); + return NULL; + } + Py_DECREF(gcdmeta); + + return dtype; +} + +/* + * Both type1 and type2 must be either NPY_DATETIME or NPY_TIMEDELTA. + * Applies the type promotion rules between the two types, returning + * the promoted type. + */ +NPY_NO_EXPORT PyArray_Descr * +datetime_type_promotion(PyArray_Descr *type1, PyArray_Descr *type2) +{ + int type_num1, type_num2; + + type_num1 = type1->type_num; + type_num2 = type2->type_num; + + if (type_num1 == NPY_DATETIME) { + if (type_num2 == NPY_DATETIME) { + return datetime_gcd_type_promotion(type1, type2); + } + else if (type_num2 == NPY_TIMEDELTA) { + Py_INCREF(type1); + return type1; + } + } + else if (type_num1 == NPY_TIMEDELTA) { + if (type_num2 == NPY_DATETIME) { + Py_INCREF(type2); + return type2; + } + else if (type_num2 == NPY_TIMEDELTA) { + return datetime_gcd_type_promotion(type1, type2); + } + } + + PyErr_SetString(PyExc_RuntimeError, + "Called datetime_type_promotion on non-datetype type"); + return NULL; +} + +/* + * Converts a substring given by 'str' and 'len' into + * a date time unit enum value. The 'metastr' parameter + * is used for error messages, and may be NULL. + * + * Returns 0 on success, -1 on failure. + */ +NPY_NO_EXPORT NPY_DATETIMEUNIT +parse_datetime_unit_from_string(char *str, Py_ssize_t len, char *metastr) +{ + /* Use switch statements so the compiler can make it fast */ + if (len == 1) { + switch (str[0]) { + case 'Y': + return NPY_FR_Y; + case 'M': + return NPY_FR_M; + case 'W': + return NPY_FR_W; + case 'B': + return NPY_FR_B; + case 'D': + return NPY_FR_D; + case 'h': + return NPY_FR_h; + case 'm': + return NPY_FR_m; + case 's': + return NPY_FR_s; + } + } + /* All the two-letter units are variants of seconds */ + else if (len == 2 && str[1] == 's') { + switch (str[0]) { + case 'm': + return NPY_FR_ms; + case 'u': + return NPY_FR_us; + case 'n': + return NPY_FR_ns; + case 'p': + return NPY_FR_ps; + case 'f': + return NPY_FR_fs; + case 'a': + return NPY_FR_as; + } + } + + /* If nothing matched, it's an error */ + if (metastr == NULL) { + PyErr_Format(PyExc_TypeError, + "Invalid datetime unit \"%s\" in metadata", + str); + } + else { + PyErr_Format(PyExc_TypeError, + "Invalid datetime unit in metadata string \"%s\"", + metastr); + } + return -1; +} + + +NPY_NO_EXPORT PyObject * +convert_datetime_metadata_to_tuple(PyArray_DatetimeMetaData *meta) +{ + PyObject *dt_tuple; + + dt_tuple = PyTuple_New(3); + if (dt_tuple == NULL) { + return NULL; + } + + PyTuple_SET_ITEM(dt_tuple, 0, + PyBytes_FromString(_datetime_strings[meta->base])); + PyTuple_SET_ITEM(dt_tuple, 1, + PyInt_FromLong(meta->num)); + PyTuple_SET_ITEM(dt_tuple, 2, + PyInt_FromLong(meta->events)); + + return dt_tuple; +} + +/* + * Converts a metadata tuple into a datetime metadata C struct. + * + * Returns 0 on success, -1 on failure. + */ +NPY_NO_EXPORT int +convert_datetime_metadata_tuple_to_datetime_metadata(PyObject *tuple, + PyArray_DatetimeMetaData *out_meta) +{ + char *basestr = NULL; + Py_ssize_t len = 0, tuple_size; + int den = 1; + + if (!PyTuple_Check(tuple)) { + PyObject_Print(tuple, stderr, 0); + PyErr_SetString(PyExc_TypeError, + "Require tuple for tuple to NumPy datetime " + "metadata conversion"); + return -1; + } + + tuple_size = PyTuple_GET_SIZE(tuple); + if (tuple_size < 3 || tuple_size > 4) { + PyErr_SetString(PyExc_TypeError, + "Require tuple of size 3 or 4 for " + "tuple to NumPy datetime metadata conversion"); + return -1; + } + + if (PyBytes_AsStringAndSize(PyTuple_GET_ITEM(tuple, 0), + &basestr, &len) < 0) { + return -1; + } + + out_meta->base = parse_datetime_unit_from_string(basestr, len, NULL); + if (out_meta->base == -1) { + return -1; + } + + /* Convert the values to longs */ + out_meta->num = PyInt_AsLong(PyTuple_GET_ITEM(tuple, 1)); + if (out_meta->num == -1 && PyErr_Occurred()) { + return -1; + } + + if (tuple_size == 3) { + out_meta->events = PyInt_AsLong(PyTuple_GET_ITEM(tuple, 2)); + if (out_meta->events == -1 && PyErr_Occurred()) { + return -1; + } + } + else { + den = PyInt_AsLong(PyTuple_GET_ITEM(tuple, 2)); + if (den == -1 && PyErr_Occurred()) { + return -1; + } + out_meta->events = PyInt_AsLong(PyTuple_GET_ITEM(tuple, 3)); + if (out_meta->events == -1 && PyErr_Occurred()) { + return -1; + } + } + + if (out_meta->num <= 0 || out_meta->events <= 0 || den <= 0) { + PyErr_SetString(PyExc_TypeError, + "Invalid tuple values for " + "tuple to NumPy datetime metadata conversion"); + return -1; + } + + if (den != 1) { + if (convert_datetime_divisor_to_multiple(out_meta, den, NULL) < 0) { + return -1; + } + } + + return 0; +} + +/* + * Converts a metadata tuple into a datetime metadata capsule. + */ +NPY_NO_EXPORT PyObject * +convert_datetime_metadata_tuple_to_metacobj(PyObject *tuple) +{ + PyArray_DatetimeMetaData *dt_data; + + dt_data = PyArray_malloc(sizeof(PyArray_DatetimeMetaData)); + + if (convert_datetime_metadata_tuple_to_datetime_metadata( + tuple, dt_data) < 0) { + PyArray_free(dt_data); + return NULL; + } + + return NpyCapsule_FromVoidPtr((void *)dt_data, simple_capsule_dtor); +} + +/* + * Converts an input object into datetime metadata. The input + * may be either a string or a tuple. + * + * Returns 0 on success, -1 on failure. + */ +NPY_NO_EXPORT int +convert_pyobject_to_datetime_metadata(PyObject *obj, + PyArray_DatetimeMetaData *out_meta) +{ + PyObject *ascii = NULL; + char *str = NULL; + Py_ssize_t len = 0; + + if (PyTuple_Check(obj)) { + return convert_datetime_metadata_tuple_to_datetime_metadata(obj, + out_meta); + } + + /* Get an ASCII string */ + if (PyUnicode_Check(obj)) { + /* Allow unicode format strings: convert to bytes */ + ascii = PyUnicode_AsASCIIString(obj); + if (ascii == NULL) { + return -1; + } + } + else if (PyBytes_Check(obj)) { + ascii = obj; + Py_INCREF(ascii); + } + else { + PyErr_SetString(PyExc_TypeError, + "Invalid object for specifying NumPy datetime metadata"); + return -1; + } + + if (PyBytes_AsStringAndSize(ascii, &str, &len) < 0) { + return -1; + } + + if (len > 0 && str[0] == '[') { + return parse_datetime_metadata_from_metastr(str, len, out_meta); + } + else { + if (parse_datetime_extended_unit_from_string(str, len, + NULL, out_meta) < 0) { + return -1; + } + + /* extended_unit is only 'num' and 'base', we have to fill the rest */ + out_meta->events = 1; + + return 0; + } + +} + +/* + * 'ret' is a PyUString containing the datetime string, and this + * function appends the metadata string to it. + * + * If 'skip_brackets' is true, skips the '[]' when events == 1. + * + * This function steals the reference 'ret' + */ +NPY_NO_EXPORT PyObject * +append_metastr_to_string(PyArray_DatetimeMetaData *meta, + int skip_brackets, + PyObject *ret) +{ + PyObject *res; + int num, events; + char *basestr; + + if (ret == NULL) { + return NULL; + } + + num = meta->num; + events = meta->events; + if (meta->base >= 0 && meta->base < NPY_DATETIME_NUMUNITS) { + basestr = _datetime_strings[meta->base]; + } + else { + PyErr_SetString(PyExc_RuntimeError, + "NumPy datetime metadata is corrupted"); + return NULL; + } + + if (num == 1) { + if (skip_brackets && events == 1) { + res = PyUString_FromFormat("%s", basestr); + } + else { + res = PyUString_FromFormat("[%s]", basestr); + } + } + else { + if (skip_brackets && events == 1) { + res = PyUString_FromFormat("%d%s", num, basestr); + } + else { + res = PyUString_FromFormat("[%d%s]", num, basestr); + } + } + + if (events != 1) { + PyUString_ConcatAndDel(&res, + PyUString_FromFormat("//%d", events)); + } + PyUString_ConcatAndDel(&ret, res); + return ret; +} + +/* + * Adjusts a datetimestruct based on a seconds offset. Assumes + * the current values are valid. + */ +NPY_NO_EXPORT void +add_seconds_to_datetimestruct(npy_datetimestruct *dts, int seconds) +{ + int minutes; + + dts->sec += seconds; + if (dts->sec < 0) { + minutes = dts->sec / 60; + dts->sec = dts->sec % 60; + if (dts->sec < 0) { + --minutes; + dts->sec += 60; + } + add_minutes_to_datetimestruct(dts, minutes); + } + else if (dts->sec >= 60) { + minutes = dts->sec / 60; + dts->sec = dts->sec % 60; + add_minutes_to_datetimestruct(dts, minutes); + } +} + +/* + * Adjusts a datetimestruct based on a minutes offset. Assumes + * the current values are valid. + */ +NPY_NO_EXPORT void +add_minutes_to_datetimestruct(npy_datetimestruct *dts, int minutes) +{ + int isleap; + + /* MINUTES */ + dts->min += minutes; + while (dts->min < 0) { + dts->min += 60; + dts->hour--; + } + while (dts->min >= 60) { + dts->min -= 60; + dts->hour++; + } + + /* HOURS */ + while (dts->hour < 0) { + dts->hour += 24; + dts->day--; + } + while (dts->hour >= 24) { + dts->hour -= 24; + dts->day++; + } + + /* DAYS */ + if (dts->day < 1) { + dts->month--; + if (dts->month < 1) { + dts->year--; + dts->month = 12; + } + isleap = is_leapyear(dts->year); + dts->day += days_in_month[isleap][dts->month-1]; + } + else if (dts->day > 28) { + isleap = is_leapyear(dts->year); + if (dts->day > days_in_month[isleap][dts->month-1]) { + dts->day -= days_in_month[isleap][dts->month-1]; + dts->month++; + if (dts->month > 12) { + dts->year++; + dts->month = 1; + } + } + } +} + +/* + * Parses (almost) standard ISO 8601 date strings. The differences are: + * + * + After the date and time, may place a ' ' followed by an event number. + * + The date "20100312" is parsed as the year 20100312, not as + * equivalent to "2010-03-12". The '-' in the dates are not optional. + * + Only seconds may have a decimal point, with up to 18 digits after it + * (maximum attoseconds precision). + * + Either a 'T' as in ISO 8601 or a ' ' may be used to separate + * the date and the time. Both are treated equivalently. + * + Doesn't (yet) handle the "YYYY-DDD" or "YYYY-Www" formats. + * + Doesn't handle leap seconds (seconds value has 60 in these cases). + * + Doesn't handle 24:00:00 as synonym for midnight (00:00:00) tomorrow + * + Accepts special values "NaT" (not a time), "Today", (current + * day according to local time) and "Now" (current time in UTC). + * + * 'str' must be a NULL-terminated string, and 'len' must be its length. + * + * Returns 0 on success, -1 on failure. + */ +NPY_NO_EXPORT int +parse_iso_8601_date(char *str, int len, npy_datetimestruct *out) +{ + int year_leap = 0; + int i; + char *substr, sublen; + + /* Initialize the output to all zeros */ + memset(out, 0, sizeof(npy_datetimestruct)); + out->month = 1; + out->day = 1; + + /* The empty string and case-variants of "NaT" parse to not-a-time */ + if (len <= 0 || (len == 3 && + tolower(str[0]) == 'n' && + tolower(str[1]) == 'a' && + tolower(str[2]) == 't')) { + out->year = NPY_DATETIME_NAT; + return 0; + } + + /* + * The string "today" resolves to midnight of today's local date in UTC. + * This is perhaps a little weird, but done so that further truncation + * to a 'datetime64[D]' type produces the date you expect, rather than + * switching to an adjacent day depending on the current time and your + * timezone. + */ + if (len == 5 && tolower(str[0]) == 't' && + tolower(str[1]) == 'o' && + tolower(str[2]) == 'd' && + tolower(str[3]) == 'a' && + tolower(str[4]) == 'y') { + time_t rawtime = 0; + struct tm tm_; + + time(&rawtime); +#if defined(_WIN32) + if (localtime_s(&tm_, &rawtime) != 0) { + PyErr_SetString(PyExc_OSError, "Failed to use localtime_s to " + "get local time"); + return -1; + } +#else + /* Other platforms may require something else */ + if (localtime_r(&rawtime, &tm_) == NULL) { + PyErr_SetString(PyExc_OSError, "Failed to use localtime_r to " + "get local time"); + return -1; + } +#endif + out->year = tm_.tm_year + 1900; + out->month = tm_.tm_mon + 1; + out->day = tm_.tm_mday; + return 0; + } + + /* The string "now" resolves to the current UTC time */ + if (len == 3 && tolower(str[0]) == 'n' && + tolower(str[1]) == 'o' && + tolower(str[2]) == 'w') { + time_t rawtime = 0; + PyArray_DatetimeMetaData meta; + time(&rawtime); + + /* Set up a dummy metadata for the conversion */ + meta.base = NPY_FR_s; + meta.num = 1; + meta.events = 1; + + return convert_datetime_to_datetimestruct(&meta, rawtime, out); + } + + substr = str; + sublen = len; + + /* Skip leading whitespace */ + while (sublen > 0 && isspace(*substr)) { + ++substr; + --sublen; + } + + /* Leading '-' sign for negative year */ + if (*substr == '-') { + ++substr; + --sublen; + } + + if (sublen == 0) { + goto parse_error; + } + + /* PARSE THE YEAR (digits until the '-' character) */ + out->year = 0; + while (sublen > 0 && isdigit(*substr)) { + out->year = 10 * out->year + (*substr - '0'); + ++substr; + --sublen; + } + + /* Negate the year if necessary */ + if (str[0] == '-') { + out->year = -out->year; + } + /* Check whether it's a leap-year */ + year_leap = is_leapyear(out->year); + + /* Next character must be a '-' or the end of the string */ + if (sublen == 0) { + goto finish; + } + else if (*substr == '-') { + ++substr; + --sublen; + } + else { + goto parse_error; + } + + /* Can't have a trailing '-' */ + if (sublen == 0) { + goto parse_error; + } + + /* PARSE THE MONTH (2 digits) */ + if (sublen >= 2 && isdigit(substr[0]) && isdigit(substr[1])) { + out->month = 10 * (substr[0] - '0') + (substr[1] - '0'); + + if (out->month < 1 || out->month > 12) { + PyErr_Format(PyExc_ValueError, + "Month out of range in datetime string \"%s\"", str); + goto error; + } + substr += 2; + sublen -= 2; + } + else { + goto parse_error; + } + + /* Next character must be a '-' or the end of the string */ + if (sublen == 0) { + goto finish; + } + else if (*substr == '-') { + ++substr; + --sublen; + } + else { + goto parse_error; + } + + /* Can't have a trailing '-' */ + if (sublen == 0) { + goto parse_error; + } + + /* PARSE THE DAY (2 digits) */ + if (sublen >= 2 && isdigit(substr[0]) && isdigit(substr[1])) { + out->day = 10 * (substr[0] - '0') + (substr[1] - '0'); + + if (out->day < 1 || + out->day > days_in_month[year_leap][out->month-1]) { + PyErr_Format(PyExc_ValueError, + "Day out of range in datetime string \"%s\"", str); + goto error; + } + substr += 2; + sublen -= 2; + } + else { + goto parse_error; + } + + /* Next character must be a 'T', ' ', or end of string */ + if (sublen == 0) { + goto finish; + } + else if (*substr != 'T' && *substr != ' ') { + goto parse_error; + } + else { + ++substr; + --sublen; + } + + /* PARSE THE HOURS (2 digits) */ + if (sublen >= 2 && isdigit(substr[0]) && isdigit(substr[1])) { + out->hour = 10 * (substr[0] - '0') + (substr[1] - '0'); + + if (out->hour < 0 || out->hour >= 24) { + PyErr_Format(PyExc_ValueError, + "Hours out of range in datetime string \"%s\"", str); + goto error; + } + substr += 2; + sublen -= 2; + } + else { + goto parse_error; + } + + /* Next character must be a ':' or the end of the string */ + if (sublen > 0 && *substr == ':') { + ++substr; + --sublen; + } + else { + goto parse_timezone; + } + + /* Can't have a trailing ':' */ + if (sublen == 0) { + goto parse_error; + } + + /* PARSE THE MINUTES (2 digits) */ + if (sublen >= 2 && isdigit(substr[0]) && isdigit(substr[1])) { + out->min = 10 * (substr[0] - '0') + (substr[1] - '0'); + + if (out->hour < 0 || out->min >= 60) { + PyErr_Format(PyExc_ValueError, + "Minutes out of range in datetime string \"%s\"", str); + goto error; + } + substr += 2; + sublen -= 2; + } + else { + goto parse_error; + } + + /* Next character must be a ':' or the end of the string */ + if (sublen > 0 && *substr == ':') { + ++substr; + --sublen; + } + else { + goto parse_timezone; + } + + /* Can't have a trailing ':' */ + if (sublen == 0) { + goto parse_error; + } + + /* PARSE THE SECONDS (2 digits) */ + if (sublen >= 2 && isdigit(substr[0]) && isdigit(substr[1])) { + out->sec = 10 * (substr[0] - '0') + (substr[1] - '0'); + + if (out->sec < 0 || out->sec >= 60) { + PyErr_Format(PyExc_ValueError, + "Seconds out of range in datetime string \"%s\"", str); + goto error; + } + substr += 2; + sublen -= 2; + } + else { + goto parse_error; + } + + /* Next character may be a '.' indicating fractional seconds */ + if (sublen > 0 && *substr == '.') { + ++substr; + --sublen; + } + else { + goto parse_timezone; + } + + /* PARSE THE MICROSECONDS (0 to 6 digits) */ + for (i = 0; i < 6; ++i) { + out->us *= 10; + if (sublen > 0 && isdigit(*substr)) { + out->us += (*substr - '0'); + ++substr; + --sublen; + } + } + + if (sublen == 0 || !isdigit(*substr)) { + goto parse_timezone; + } + + /* PARSE THE PICOSECONDS (0 to 6 digits) */ + for (i = 0; i < 6; ++i) { + out->ps *= 10; + if (sublen > 0 && isdigit(*substr)) { + out->ps += (*substr - '0'); + ++substr; + --sublen; + } + } + + if (sublen == 0 || !isdigit(*substr)) { + goto parse_timezone; + } + + /* PARSE THE ATTOSECONDS (0 to 6 digits) */ + for (i = 0; i < 6; ++i) { + out->as *= 10; + if (sublen > 0 && isdigit(*substr)) { + out->as += (*substr - '0'); + ++substr; + --sublen; + } + } + +parse_timezone: + if (sublen == 0) { + /* + * ISO 8601 states to treat date-times without a timezone offset + * or 'Z' for UTC as local time. The C standard libary functions + * mktime and gmtime allow us to do this conversion. + * + * Only do this timezone adjustment for recent and future years. + */ + if (out->year > 1900 && out->year < 10000) { + time_t rawtime = 0; + struct tm tm_; + + tm_.tm_sec = out->sec; + tm_.tm_min = out->min; + tm_.tm_hour = out->hour; + tm_.tm_mday = out->day; + tm_.tm_mon = out->month - 1; + tm_.tm_year = out->year - 1900; + tm_.tm_isdst = -1; + + /* mktime converts a local 'struct tm' into a time_t */ + rawtime = mktime(&tm_); + if (rawtime == -1) { + PyErr_SetString(PyExc_OSError, "Failed to use mktime to " + "convert local time to UTC"); + goto error; + } + + /* gmtime converts a 'time_t' into a UTC 'struct tm' */ +#if defined(_WIN32) + if (gmtime_s(&tm_, &rawtime) != 0) { + PyErr_SetString(PyExc_OSError, "Failed to use gmtime_s to " + "get a UTC time"); + goto error; + } +#else + /* Other platforms may require something else */ + if (gmtime_r(&rawtime, &tm_) == NULL) { + PyErr_SetString(PyExc_OSError, "Failed to use gmtime_r to " + "get a UTC time"); + goto error; + } +#endif + out->sec = tm_.tm_sec; + out->min = tm_.tm_min; + out->hour = tm_.tm_hour; + out->day = tm_.tm_mday; + out->month = tm_.tm_mon + 1; + out->year = tm_.tm_year + 1900; + } + + goto finish; + } + + /* UTC specifier */ + if (*substr == 'Z') { + if (sublen == 1) { + goto finish; + } + else { + ++substr; + --sublen; + } + } + /* Time zone offset */ + else if (*substr == '-' || *substr == '+') { + int offset_neg = 0, offset_hour = 0, offset_minute = 0; + if (*substr == '-') { + offset_neg = 1; + } + ++substr; + --sublen; + + /* The hours offset */ + if (sublen >= 2 && isdigit(substr[0]) && isdigit(substr[1])) { + offset_hour = 10 * (substr[0] - '0') + (substr[1] - '0'); + substr += 2; + sublen -= 2; + if (offset_hour >= 24) { + PyErr_Format(PyExc_ValueError, + "Timezone hours offset out of range " + "in datetime string \"%s\"", str); + goto error; + } + } + else { + goto parse_error; + } + + /* The minutes offset is optional */ + if (sublen > 0) { + /* Optional ':' */ + if (*substr == ':') { + ++substr; + --sublen; + } + + /* The minutes offset (at the end of the string) */ + if (sublen >= 2 && isdigit(substr[0]) && isdigit(substr[1])) { + offset_minute = 10 * (substr[0] - '0') + (substr[1] - '0'); + substr += 2; + sublen -= 2; + if (offset_minute >= 60) { + PyErr_Format(PyExc_ValueError, + "Timezone minutes offset out of range " + "in datetime string \"%s\"", str); + goto error; + } + } + else { + goto parse_error; + } + } + + /* Apply the time zone offset */ + if (offset_neg) { + offset_hour = -offset_hour; + offset_minute = -offset_minute; + } + add_minutes_to_datetimestruct(out, -60 * offset_hour - offset_minute); + } + + /* May have a ' ' followed by an event number */ + if (sublen == 0) { + goto finish; + } + else if (sublen > 0 && *substr == ' ') { + ++substr; + --sublen; + + while (sublen > 0 && isdigit(*substr)) { + out->event = 10 * out->event + (*substr - '0'); + ++substr; + --sublen; + } + } + else { + goto parse_error; + } + + /* Skip trailing whitespace */ + while (sublen > 0 && isspace(*substr)) { + ++substr; + --sublen; + } + + if (sublen != 0) { + goto parse_error; + } + +finish: + return 0; + +parse_error: + PyErr_Format(PyExc_ValueError, + "Error parsing datetime string \"%s\" at position %d", + str, (int)(substr-str)); + return -1; + +error: + return -1; +} + +/* + * Provides a string length to use for converting datetime + * objects with the given local and unit settings. + */ +NPY_NO_EXPORT int +get_datetime_iso_8601_strlen(int local, NPY_DATETIMEUNIT base) +{ + int len = 0; + + /* If no unit is provided, return the maximum length */ + if (base == -1) { + return NPY_DATETIME_MAX_ISO8601_STRLEN; + } + + switch (base) { + case NPY_FR_as: + len += 3; /* "###" */ + case NPY_FR_fs: + len += 3; /* "###" */ + case NPY_FR_ps: + len += 3; /* "###" */ + case NPY_FR_ns: + len += 3; /* "###" */ + case NPY_FR_us: + len += 3; /* "###" */ + case NPY_FR_ms: + len += 4; /* ".###" */ + case NPY_FR_s: + len += 3; /* ":##" */ + case NPY_FR_m: + len += 3; /* ":##" */ + case NPY_FR_h: + len += 3; /* "T##" */ + case NPY_FR_D: + case NPY_FR_B: + case NPY_FR_W: + len += 3; /* "-##" */ + case NPY_FR_M: + len += 3; /* "-##" */ + case NPY_FR_Y: + len += 21; /* 64-bit year */ + break; + } + + if (base >= NPY_FR_h) { + if (local) { + len += 5; /* "+####" or "-####" */ + } + else { + len += 1; /* "Z" */ + } + } + + len += 1; /* NULL terminator */ + + return len; +} + +/* + * Converts an npy_datetimestruct to an (almost) ISO 8601 + * NULL-terminated string. + * + * If 'local' is non-zero, it produces a string in local time with + * a +-#### timezone offset, otherwise it uses timezone Z (UTC). + * + * 'base' restricts the output to that unit. Set 'base' to + * -1 to auto-detect a base after which all the values are zero. + * + * 'tzoffset' is used if 'local' is enabled, and 'tzoffset' is + * set to a value other than -1. This is a manual override for + * the local time zone to use, as an offset in minutes. + * + * Returns 0 on success, -1 on failure (for example if the output + * string was too short). + */ +NPY_NO_EXPORT int +make_iso_8601_date(npy_datetimestruct *dts, char *outstr, int outlen, + int local, NPY_DATETIMEUNIT base, int tzoffset) +{ + npy_datetimestruct dts_local; + int timezone_offset = 0; + + char *substr = outstr, sublen = outlen; + int tmplen; + + /* Handle NaT */ + if (dts->year == NPY_DATETIME_NAT) { + if (outlen < 4) { + goto string_too_short; + } + outstr[0] = 'N'; + outstr[0] = 'a'; + outstr[0] = 'T'; + outstr[0] = '\0'; + + return 0; + } + + /* Only do local time within a reasonable year range */ + if ((dts->year <= 1900 || dts->year >= 10000) && tzoffset == -1) { + local = 0; + } + + /* Automatically detect a good unit */ + if (base == -1) { + if (dts->as % 1000 != 0) { + base = NPY_FR_as; + } + else if (dts->as != 0) { + base = NPY_FR_fs; + } + else if (dts->ps % 1000 != 0) { + base = NPY_FR_ps; + } + else if (dts->ps != 0) { + base = NPY_FR_ns; + } + else if (dts->us % 1000 != 0) { + base = NPY_FR_us; + } + else if (dts->us != 0) { + base = NPY_FR_ms; + } + else if (dts->sec != 0) { + base = NPY_FR_s; + } + /* + * hours and minutes don't get split up by default, and printing + * in local time forces minutes + */ + else if (local || dts->min != 0 || dts->hour != 0) { + base = NPY_FR_m; + } + /* dates don't get split up by default */ + else { + base = NPY_FR_D; + } + } + /* + * Print business days and weeks with the same precision as days. + * + * TODO: Could print weeks with YYYY-Www format if the week + * epoch is a Monday. + */ + else if (base == NPY_FR_B || base == NPY_FR_W) { + base = NPY_FR_D; + } + + /* Printed dates have no time zone */ + if (base < NPY_FR_h) { + local = 0; + } + + /* Use the C API to convert from UTC to local time */ + if (local && tzoffset == -1) { + time_t rawtime = 0, localrawtime; + struct tm tm_; + + /* + * Convert everything in 'dts' to a time_t, to minutes precision. + * This is POSIX time, which skips leap-seconds, but because + * we drop the seconds value from the npy_datetimestruct, everything + * is ok for this operation. + */ + rawtime = (time_t)get_datetimestruct_days(dts) * 24 * 60 * 60; + rawtime += dts->hour * 60 * 60; + rawtime += dts->min * 60; + + /* localtime converts a 'time_t' into a local 'struct tm' */ +#if defined(_WIN32) + if (localtime_s(&tm_, &rawtime) != 0) { + PyErr_SetString(PyExc_OSError, "Failed to use localtime_s to " + "get a local time"); + return -1; + } +#else + /* Other platforms may require something else */ + if (localtime_r(&rawtime, &tm_) == NULL) { + PyErr_SetString(PyExc_OSError, "Failed to use localtime_r to " + "get a local time"); + return -1; + } +#endif + /* Make a copy of the npy_datetimestruct we can modify */ + dts_local = *dts; + + /* Copy back all the values except seconds */ + dts_local.min = tm_.tm_min; + dts_local.hour = tm_.tm_hour; + dts_local.day = tm_.tm_mday; + dts_local.month = tm_.tm_mon + 1; + dts_local.year = tm_.tm_year + 1900; + + /* Extract the timezone offset that was applied */ + rawtime /= 60; + localrawtime = (time_t)get_datetimestruct_days(&dts_local) * 24 * 60; + localrawtime += dts_local.hour * 60; + localrawtime += dts_local.min; + + timezone_offset = localrawtime - rawtime; + + /* Set dts to point to our local time instead of the UTC time */ + dts = &dts_local; + } + /* Use the manually provided tzoffset */ + else if (local) { + /* Make a copy of the npy_datetimestruct we can modify */ + dts_local = *dts; + dts = &dts_local; + + /* Set and apply the required timezone offset */ + timezone_offset = tzoffset; + add_minutes_to_datetimestruct(dts, timezone_offset); + } + + /* YEAR */ +#ifdef _WIN32 + tmplen = _snprintf(substr, sublen, "%04" NPY_INT64_FMT, dts->year); +#else + tmplen = snprintf(substr, sublen, "%04" NPY_INT64_FMT, dts->year); +#endif + /* If it ran out of space or there isn't space for the NULL terminator */ + if (tmplen < 0 || tmplen >= sublen) { + goto string_too_short; + } + substr += tmplen; + sublen -= tmplen; + + /* Stop if the unit is years */ + if (base == NPY_FR_Y) { + *substr = '\0'; + return 0; + } + + /* MONTH */ + substr[0] = '-'; + if (sublen <= 1 ) { + goto string_too_short; + } + substr[1] = (char)((dts->month / 10) + '0'); + if (sublen <= 2 ) { + goto string_too_short; + } + substr[2] = (char)((dts->month % 10) + '0'); + if (sublen <= 3 ) { + goto string_too_short; + } + substr += 3; + sublen -= 3; + + /* Stop if the unit is months */ + if (base == NPY_FR_M) { + *substr = '\0'; + return 0; + } + + /* DAY */ + substr[0] = '-'; + if (sublen <= 1 ) { + goto string_too_short; + } + substr[1] = (char)((dts->day / 10) + '0'); + if (sublen <= 2 ) { + goto string_too_short; + } + substr[2] = (char)((dts->day % 10) + '0'); + if (sublen <= 3 ) { + goto string_too_short; + } + substr += 3; + sublen -= 3; + + /* Stop if the unit is days */ + if (base == NPY_FR_D) { + *substr = '\0'; + return 0; + } + + /* HOUR */ + substr[0] = 'T'; + if (sublen <= 1 ) { + goto string_too_short; + } + substr[1] = (char)((dts->hour / 10) + '0'); + if (sublen <= 2 ) { + goto string_too_short; + } + substr[2] = (char)((dts->hour % 10) + '0'); + if (sublen <= 3 ) { + goto string_too_short; + } + substr += 3; + sublen -= 3; + + /* Stop if the unit is hours */ + if (base == NPY_FR_h) { + goto add_time_zone; + } + + /* MINUTE */ + substr[0] = ':'; + if (sublen <= 1 ) { + goto string_too_short; + } + substr[1] = (char)((dts->min / 10) + '0'); + if (sublen <= 2 ) { + goto string_too_short; + } + substr[2] = (char)((dts->min % 10) + '0'); + if (sublen <= 3 ) { + goto string_too_short; + } + substr += 3; + sublen -= 3; + + /* Stop if the unit is minutes */ + if (base == NPY_FR_m) { + goto add_time_zone; + } + + /* SECOND */ + substr[0] = ':'; + if (sublen <= 1 ) { + goto string_too_short; + } + substr[1] = (char)((dts->sec / 10) + '0'); + if (sublen <= 2 ) { + goto string_too_short; + } + substr[2] = (char)((dts->sec % 10) + '0'); + if (sublen <= 3 ) { + goto string_too_short; + } + substr += 3; + sublen -= 3; + + /* Stop if the unit is seconds */ + if (base == NPY_FR_s) { + goto add_time_zone; + } + + /* MILLISECOND */ + substr[0] = '.'; + if (sublen <= 1 ) { + goto string_too_short; + } + substr[1] = (char)((dts->us / 100000) % 10 + '0'); + if (sublen <= 2 ) { + goto string_too_short; + } + substr[2] = (char)((dts->us / 10000) % 10 + '0'); + if (sublen <= 3 ) { + goto string_too_short; + } + substr[3] = (char)((dts->us / 1000) % 10 + '0'); + if (sublen <= 4 ) { + goto string_too_short; + } + substr += 4; + sublen -= 4; + + /* Stop if the unit is milliseconds */ + if (base == NPY_FR_ms) { + goto add_time_zone; + } + + /* MICROSECOND */ + substr[0] = (char)((dts->us / 100) % 10 + '0'); + if (sublen <= 1 ) { + goto string_too_short; + } + substr[1] = (char)((dts->us / 10) % 10 + '0'); + if (sublen <= 2 ) { + goto string_too_short; + } + substr[2] = (char)(dts->us % 10 + '0'); + if (sublen <= 3 ) { + goto string_too_short; + } + substr += 3; + sublen -= 3; + + /* Stop if the unit is microseconds */ + if (base == NPY_FR_us) { + goto add_time_zone; + } + + /* NANOSECOND */ + substr[0] = (char)((dts->ps / 100000) % 10 + '0'); + if (sublen <= 1 ) { + goto string_too_short; + } + substr[1] = (char)((dts->ps / 10000) % 10 + '0'); + if (sublen <= 2 ) { + goto string_too_short; + } + substr[2] = (char)((dts->ps / 1000) % 10 + '0'); + if (sublen <= 3 ) { + goto string_too_short; + } + substr += 3; + sublen -= 3; + + /* Stop if the unit is nanoseconds */ + if (base == NPY_FR_ns) { + goto add_time_zone; + } + + /* PICOSECOND */ + substr[0] = (char)((dts->ps / 100) % 10 + '0'); + if (sublen <= 1 ) { + goto string_too_short; + } + substr[1] = (char)((dts->ps / 10) % 10 + '0'); + if (sublen <= 2 ) { + goto string_too_short; + } + substr[2] = (char)(dts->ps % 10 + '0'); + if (sublen <= 3 ) { + goto string_too_short; + } + substr += 3; + sublen -= 3; + + /* Stop if the unit is picoseconds */ + if (base == NPY_FR_ps) { + goto add_time_zone; + } + + /* FEMTOSECOND */ + substr[0] = (char)((dts->as / 100000) % 10 + '0'); + if (sublen <= 1 ) { + goto string_too_short; + } + substr[1] = (char)((dts->as / 10000) % 10 + '0'); + if (sublen <= 2 ) { + goto string_too_short; + } + substr[2] = (char)((dts->as / 1000) % 10 + '0'); + if (sublen <= 3 ) { + goto string_too_short; + } + substr += 3; + sublen -= 3; + + /* Stop if the unit is femtoseconds */ + if (base == NPY_FR_fs) { + goto add_time_zone; + } + + /* ATTOSECOND */ + substr[0] = (char)((dts->as / 100) % 10 + '0'); + if (sublen <= 1 ) { + goto string_too_short; + } + substr[1] = (char)((dts->as / 10) % 10 + '0'); + if (sublen <= 2 ) { + goto string_too_short; + } + substr[2] = (char)(dts->as % 10 + '0'); + if (sublen <= 3 ) { + goto string_too_short; + } + substr += 3; + sublen -= 3; + +add_time_zone: + if (local) { + /* Add the +/- sign */ + if (timezone_offset < 0) { + substr[0] = '-'; + timezone_offset = -timezone_offset; + } + else { + substr[0] = '+'; + } + if (sublen <= 1) { + goto string_too_short; + } + substr += 1; + sublen -= 1; + + /* Add the timezone offset */ + substr[0] = (char)((timezone_offset / (10*60)) % 10 + '0'); + if (sublen <= 1 ) { + goto string_too_short; + } + substr[1] = (char)((timezone_offset / 60) % 10 + '0'); + if (sublen <= 2 ) { + goto string_too_short; + } + substr[2] = (char)(((timezone_offset % 60) / 10) % 10 + '0'); + if (sublen <= 3 ) { + goto string_too_short; + } + substr[3] = (char)((timezone_offset % 60) % 10 + '0'); + if (sublen <= 4 ) { + goto string_too_short; + } + substr += 4; + sublen -= 4; + } + /* UTC "Zulu" time */ + else { + substr[0] = 'Z'; + if (sublen <= 1) { + goto string_too_short; + } + substr += 1; + sublen -= 1; + } + + /* Add a NULL terminator, and return */ + substr[0] = '\0'; + + return 0; + +string_too_short: + /* Put a NULL terminator on anyway */ + if (outlen > 0) { + outstr[outlen-1] = '\0'; + } + + PyErr_Format(PyExc_RuntimeError, + "The string provided for NumPy ISO datetime formatting " + "was too short, with length %d", + outlen); + return -1; +} + +/* + * Tests for and converts a Python datetime.datetime or datetime.date + * object into a NumPy npy_datetimestruct. + * + * While the C API has PyDate_* and PyDateTime_* functions, the following + * implementation just asks for attributes, and thus supports + * datetime duck typing. The tzinfo time zone conversion would require + * this style of access anyway. + * + * Returns -1 on error, 0 on success, and 1 (with no error set) + * if obj doesn't have the neeeded date or datetime attributes. + */ +NPY_NO_EXPORT int +convert_pydatetime_to_datetimestruct(PyObject *obj, npy_datetimestruct *out) +{ + PyObject *tmp; + int isleap; + + /* Initialize the output to all zeros */ + memset(out, 0, sizeof(npy_datetimestruct)); + out->month = 1; + out->day = 1; + + /* Need at least year/month/day attributes */ + if (!PyObject_HasAttrString(obj, "year") || + !PyObject_HasAttrString(obj, "month") || + !PyObject_HasAttrString(obj, "day")) { + return 1; + } + + /* Get the year */ + tmp = PyObject_GetAttrString(obj, "year"); + if (tmp == NULL) { + return -1; + } + out->year = PyInt_AsLong(tmp); + if (out->year == -1 && PyErr_Occurred()) { + Py_DECREF(tmp); + return -1; + } + Py_DECREF(tmp); + + /* Get the month */ + tmp = PyObject_GetAttrString(obj, "month"); + if (tmp == NULL) { + return -1; + } + out->month = PyInt_AsLong(tmp); + if (out->month == -1 && PyErr_Occurred()) { + Py_DECREF(tmp); + return -1; + } + Py_DECREF(tmp); + + /* Get the day */ + tmp = PyObject_GetAttrString(obj, "day"); + if (tmp == NULL) { + return -1; + } + out->day = PyInt_AsLong(tmp); + if (out->day == -1 && PyErr_Occurred()) { + Py_DECREF(tmp); + return -1; + } + Py_DECREF(tmp); + + /* Validate that the month and day are valid for the year */ + if (out->month < 1 || out->month > 12) { + goto invalid_date; + } + isleap = is_leapyear(out->year); + if (out->day < 1 || out->day > days_in_month[isleap][out->month-1]) { + goto invalid_date; + } + + /* Check for time attributes (if not there, return success as a date) */ + if (!PyObject_HasAttrString(obj, "hour") || + !PyObject_HasAttrString(obj, "minute") || + !PyObject_HasAttrString(obj, "second") || + !PyObject_HasAttrString(obj, "microsecond")) { + return 0; + } + + /* Get the hour */ + tmp = PyObject_GetAttrString(obj, "hour"); + if (tmp == NULL) { + return -1; + } + out->hour = PyInt_AsLong(tmp); + if (out->hour == -1 && PyErr_Occurred()) { + Py_DECREF(tmp); + return -1; + } + Py_DECREF(tmp); + + /* Get the minute */ + tmp = PyObject_GetAttrString(obj, "minute"); + if (tmp == NULL) { + return -1; + } + out->min = PyInt_AsLong(tmp); + if (out->min == -1 && PyErr_Occurred()) { + Py_DECREF(tmp); + return -1; + } + Py_DECREF(tmp); + + /* Get the second */ + tmp = PyObject_GetAttrString(obj, "second"); + if (tmp == NULL) { + return -1; + } + out->sec = PyInt_AsLong(tmp); + if (out->sec == -1 && PyErr_Occurred()) { + Py_DECREF(tmp); + return -1; + } + Py_DECREF(tmp); + + /* Get the microsecond */ + tmp = PyObject_GetAttrString(obj, "microsecond"); + if (tmp == NULL) { + return -1; + } + out->us = PyInt_AsLong(tmp); + if (out->us == -1 && PyErr_Occurred()) { + Py_DECREF(tmp); + return -1; + } + Py_DECREF(tmp); + + if (out->hour < 0 || out->hour >= 24 || + out->min < 0 || out->min >= 60 || + out->sec < 0 || out->sec >= 60 || + out->us < 0 || out->us >= 1000000) { + goto invalid_time; + } + + /* Apply the time zone offset if it exists */ + if (PyObject_HasAttrString(obj, "tzinfo")) { + tmp = PyObject_GetAttrString(obj, "tzinfo"); + if (tmp == NULL) { + return -1; + } + if (tmp == Py_None) { + Py_DECREF(tmp); + } + else { + PyObject *offset; + int seconds_offset, minutes_offset; + + /* The utcoffset function should return a timedelta */ + offset = PyObject_CallMethod(tmp, "utcoffset", "O", obj); + if (offset == NULL) { + Py_DECREF(tmp); + return -1; + } + Py_DECREF(tmp); + + /* + * The timedelta should have an attribute "seconds" + * which contains the value we want. + */ + tmp = PyObject_GetAttrString(obj, "seconds"); + if (tmp == NULL) { + return -1; + } + seconds_offset = PyInt_AsLong(tmp); + if (seconds_offset == -1 && PyErr_Occurred()) { + Py_DECREF(tmp); + return -1; + } + Py_DECREF(tmp); + + /* Convert to a minutes offset and apply it */ + minutes_offset = seconds_offset / 60; + + add_minutes_to_datetimestruct(out, -minutes_offset); + } + } + + return 0; + +invalid_date: + PyErr_Format(PyExc_ValueError, + "Invalid date (%d,%d,%d) when converting to NumPy datetime", + (int)out->year, (int)out->month, (int)out->day); + return -1; + +invalid_time: + PyErr_Format(PyExc_ValueError, + "Invalid time (%d,%d,%d,%d) when converting " + "to NumPy datetime", + (int)out->hour, (int)out->min, (int)out->sec, (int)out->us); + return -1; +} + +/* + * Converts a PyObject * into a datetime, in any of the forms supported + * + * Returns -1 on error, 0 on success. + */ +NPY_NO_EXPORT int +convert_pyobject_to_datetime(PyArray_DatetimeMetaData *meta, PyObject *obj, + npy_datetime *out) +{ + if (PyBytes_Check(obj) || PyUnicode_Check(obj)) { + PyObject *bytes = NULL; + char *str = NULL; + int len = 0; + npy_datetimestruct dts; + /* Convert to an ASCII string for the date parser */ + if (PyUnicode_Check(obj)) { + bytes = PyUnicode_AsASCIIString(obj); + if (bytes == NULL) { + return -1; + } + } + else { + bytes = obj; + Py_INCREF(bytes); + } + if (PyBytes_AsStringAndSize(bytes, &str, &len) == -1) { + Py_DECREF(bytes); + return -1; + } + + /* Parse the ISO date */ + if (parse_iso_8601_date(str, len, &dts) < 0) { + Py_DECREF(bytes); + return -1; + } + Py_DECREF(bytes); + + if (convert_datetimestruct_to_datetime(meta, &dts, out) < 0) { + return -1; + } + + return 0; + } + /* Do no conversion on raw integers */ + else if (PyInt_Check(obj)) { + *out = PyInt_AS_LONG(obj); + return 0; + } + else if (PyLong_Check(obj)) { + *out = PyLong_AsLongLong(obj); + return 0; + } + /* Could be a tuple with event number in the second entry */ + else if (PyTuple_Check(obj) && PyTuple_Size(obj) == 2) { + int event, event_old; + if (convert_pyobject_to_datetime(meta, PyTuple_GET_ITEM(obj, 0), + out) < 0) { + return -1; + } + event = (int)PyInt_AsLong(PyTuple_GET_ITEM(obj, 1)); + if (event == -1 && PyErr_Occurred()) { + return -1; + } + if (event < 0 || event >= meta->events) { + PyErr_SetString(PyExc_ValueError, "event value for NumPy " + "datetime is out of range"); + return -1; + } + /* Replace the event with the one from the tuple */ + event_old = *out % meta->events; + if (event_old < 0) { + event_old += meta->events; + } + *out = *out - event_old + event; + + return 0; + } + /* Datetime scalar */ + else if (PyArray_IsScalar(obj, Datetime)) { + PyDatetimeScalarObject *dts = (PyDatetimeScalarObject *)obj; + + return cast_datetime_to_datetime(&dts->obmeta, meta, dts->obval, out); + } + /* Datetime zero-dimensional array */ + else if (PyArray_Check(obj) && + PyArray_NDIM(obj) == 0 && + PyArray_DESCR(obj)->type_num == NPY_DATETIME) { + PyArray_DatetimeMetaData *obj_meta; + npy_datetime dt = 0; + + obj_meta = get_datetime_metadata_from_dtype(PyArray_DESCR(obj)); + if (obj_meta == NULL) { + return -1; + } + PyArray_DESCR(obj)->f->copyswap(&dt, + PyArray_DATA(obj), + !PyArray_ISNOTSWAPPED(obj), + obj); + + return cast_datetime_to_datetime(obj_meta, meta, dt, out); + } + /* Convert from a Python date or datetime object */ + else { + int code; + npy_datetimestruct dts; + + code = convert_pydatetime_to_datetimestruct(obj, &dts); + if (code == -1) { + return -1; + } + else if (code == 0) { + if (convert_datetimestruct_to_datetime(meta, &dts, out) < 0) { + return -1; + } + + return 0; + } + } + + PyErr_SetString(PyExc_ValueError, + "Could not convert object to NumPy datetime"); + return -1; +} + +/* + * Converts a PyObject * into a timedelta, in any of the forms supported + * + * Returns -1 on error, 0 on success. + */ +NPY_NO_EXPORT int +convert_pyobject_to_timedelta(PyArray_DatetimeMetaData *meta, PyObject *obj, + npy_timedelta *out) +{ + /* Do no conversion on raw integers */ + if (PyInt_Check(obj)) { + *out = PyInt_AS_LONG(obj); + return 0; + } + else if (PyLong_Check(obj)) { + *out = PyLong_AsLongLong(obj); + return 0; + } + /* Timedelta scalar */ + else if (PyArray_IsScalar(obj, Timedelta)) { + PyTimedeltaScalarObject *dts = (PyTimedeltaScalarObject *)obj; + + return cast_timedelta_to_timedelta(&dts->obmeta, meta, + dts->obval, out); + } + /* Timedelta zero-dimensional array */ + else if (PyArray_Check(obj) && + PyArray_NDIM(obj) == 0 && + PyArray_DESCR(obj)->type_num == NPY_TIMEDELTA) { + PyArray_DatetimeMetaData *obj_meta; + npy_timedelta dt = 0; + + obj_meta = get_datetime_metadata_from_dtype(PyArray_DESCR(obj)); + if (obj_meta == NULL) { + return -1; + } + PyArray_DESCR(obj)->f->copyswap(&dt, + PyArray_DATA(obj), + !PyArray_ISNOTSWAPPED(obj), + obj); + + return cast_timedelta_to_timedelta(obj_meta, meta, dt, out); + } + /* Convert from a Python timedelta object */ + else if (PyObject_HasAttrString(obj, "days") && + PyObject_HasAttrString(obj, "seconds") && + PyObject_HasAttrString(obj, "microseconds")) { + PyObject *tmp; + PyArray_DatetimeMetaData us_meta; + npy_timedelta td; + npy_int64 days; + int seconds = 0, useconds = 0; + + /* Get the days */ + tmp = PyObject_GetAttrString(obj, "days"); + if (tmp == NULL) { + return -1; + } + days = PyLong_AsLongLong(tmp); + if (days == -1 && PyErr_Occurred()) { + Py_DECREF(tmp); + return -1; + } + Py_DECREF(tmp); + + /* Get the seconds */ + tmp = PyObject_GetAttrString(obj, "seconds"); + if (tmp == NULL) { + return -1; + } + seconds = PyInt_AsLong(tmp); + if (seconds == -1 && PyErr_Occurred()) { + Py_DECREF(tmp); + return -1; + } + Py_DECREF(tmp); + + /* Get the microseconds */ + tmp = PyObject_GetAttrString(obj, "microseconds"); + if (tmp == NULL) { + return -1; + } + useconds = PyInt_AsLong(tmp); + if (useconds == -1 && PyErr_Occurred()) { + Py_DECREF(tmp); + return -1; + } + Py_DECREF(tmp); + + /* + * Convert to a microseconds timedelta, then cast to the + * desired units. + */ + td = days*(24*60*60*1000000LL) + seconds*1000000LL + useconds; + us_meta.base = NPY_FR_us; + us_meta.num = 1; + us_meta.events = 1; + + return cast_timedelta_to_timedelta(&us_meta, meta, td, out); + } + + PyErr_SetString(PyExc_ValueError, + "Could not convert object to NumPy timedelta"); + return -1; +} + +/* + * Converts a datetime into a PyObject *. + * + * Not-a-time is returned as the string "NaT". + * For days or coarser, returns a datetime.date. + * For microseconds or coarser, returns a datetime.datetime. + * For units finer than microseconds, returns an integer. + */ +NPY_NO_EXPORT PyObject * +convert_datetime_to_pyobject(npy_datetime dt, PyArray_DatetimeMetaData *meta) +{ + PyObject *ret = NULL, *tup = NULL; + npy_datetimestruct dts; + + /* Handle not-a-time */ + if (dt == NPY_DATETIME_NAT) { + return PyUString_FromString("NaT"); + } + + /* If the type's precision is greater than microseconds, return an int */ + if (meta->base > NPY_FR_us) { + /* Skip use of a tuple for the events, just return the raw int */ + return PyLong_FromLongLong(dt); + } + + /* Convert to a datetimestruct */ + if (convert_datetime_to_datetimestruct(meta, dt, &dts) < 0) { + return NULL; + } + + /* + * If the year is outside the range of years supported by Python's + * datetime, or the datetime64 falls on a leap second, + * return a raw int. + */ + if (dts.year < 1 || dts.year > 9999 || dts.sec == 60) { + /* Also skip use of a tuple for the events */ + return PyLong_FromLongLong(dt); + } + + /* If the type's precision is greater than days, return a datetime */ + if (meta->base > NPY_FR_D) { + ret = PyDateTime_FromDateAndTime(dts.year, dts.month, dts.day, + dts.hour, dts.min, dts.sec, dts.us); + } + /* Otherwise return a date */ + else { + ret = PyDate_FromDate(dts.year, dts.month, dts.day); + } + + /* If there is one event, just return the datetime */ + if (meta->events == 1) { + return ret; + } + /* Otherwise return a tuple with the event in the second position */ + else { + tup = PyTuple_New(2); + if (tup == NULL) { + Py_DECREF(ret); + return NULL; + } + PyTuple_SET_ITEM(tup, 0, ret); + + ret = PyInt_FromLong(dts.event); + if (ret == NULL) { + Py_DECREF(tup); + return NULL; + } + PyTuple_SET_ITEM(tup, 1, ret); + + return tup; + } +} + +/* + * Converts a timedelta into a PyObject *. + * + * Not-a-time is returned as the string "NaT". + * For microseconds or coarser, returns a datetime.timedelta. + * For units finer than microseconds, returns an integer. + */ +NPY_NO_EXPORT PyObject * +convert_timedelta_to_pyobject(npy_timedelta td, PyArray_DatetimeMetaData *meta) +{ + PyObject *ret = NULL, *tup = NULL; + npy_timedelta value; + int event = 0; + int days = 0, seconds = 0, useconds = 0; + + /* Handle not-a-time */ + if (td == NPY_DATETIME_NAT) { + return PyUString_FromString("NaT"); + } + + /* + * If the type's precision is greater than microseconds or is + * Y/M/B (nonlinear units), return an int + */ + if (meta->base > NPY_FR_us || + meta->base == NPY_FR_Y || + meta->base == NPY_FR_M || + meta->base == NPY_FR_B) { + /* Skip use of a tuple for the events, just return the raw int */ + return PyLong_FromLongLong(td); + } + + value = td; + + /* If there are events, extract the event */ + if (meta->events > 1) { + event = (int)(value % meta->events); + value = value / meta->events; + if (event < 0) { + --value; + event += meta->events; + } + } + + /* Apply the unit multiplier (TODO: overflow treatment...) */ + value *= meta->num; + + /* Convert to days/seconds/useconds */ + switch (meta->base) { + case NPY_FR_W: + value *= 7; + break; + case NPY_FR_D: + break; + case NPY_FR_h: + seconds = (int)((value % 24) * (60*60)); + value = value / 24; + break; + case NPY_FR_m: + seconds = (int)(value % (24*60)) * 60; + value = value / (24*60); + break; + case NPY_FR_s: + seconds = (int)(value % (24*60*60)); + value = value / (24*60*60); + break; + case NPY_FR_ms: + useconds = (int)(value % 1000) * 1000; + value = value / 1000; + seconds = (int)(value % (24*60*60)); + value = value / (24*60*60); + break; + case NPY_FR_us: + useconds = (int)(value % (1000*1000)); + value = value / (1000*1000); + seconds = (int)(value % (24*60*60)); + value = value / (24*60*60); + break; + default: + break; + } + /* + * 'value' represents days, and seconds/useconds are filled. + * + * If it would overflow the datetime.timedelta days, return a raw int + */ + if (value < -999999999 || value > 999999999) { + return PyLong_FromLongLong(td); + } + else { + days = (int)value; + ret = PyDelta_FromDSU(days, seconds, useconds); + if (ret == NULL) { + return NULL; + } + } + + /* If there is one event, just return the datetime */ + if (meta->events == 1) { + return ret; + } + /* Otherwise return a tuple with the event in the second position */ + else { + tup = PyTuple_New(2); + if (tup == NULL) { + Py_DECREF(ret); + return NULL; + } + PyTuple_SET_ITEM(tup, 0, ret); + + ret = PyInt_FromLong(event); + if (ret == NULL) { + Py_DECREF(tup); + return NULL; + } + PyTuple_SET_ITEM(tup, 1, ret); + + return tup; + } +} + +/* + * Returns true if the datetime metadata matches + */ +NPY_NO_EXPORT npy_bool +has_equivalent_datetime_metadata(PyArray_Descr *type1, PyArray_Descr *type2) +{ + PyArray_DatetimeMetaData *meta1, *meta2; + + if ((type1->type_num != NPY_DATETIME && + type1->type_num != NPY_TIMEDELTA) || + (type2->type_num != NPY_DATETIME && + type2->type_num != NPY_TIMEDELTA)) { + return 0; + } + + meta1 = get_datetime_metadata_from_dtype(type1); + if (meta1 == NULL) { + PyErr_Clear(); + return 0; + } + meta2 = get_datetime_metadata_from_dtype(type2); + if (meta2 == NULL) { + PyErr_Clear(); + return 0; + } + + return meta1->base == meta2->base && + meta1->num == meta2->num && + meta1->events == meta2->events; +} + +/* + * Casts a single datetime from having src_meta metadata into + * dst_meta metadata. + * + * Returns 0 on success, -1 on failure. + */ +NPY_NO_EXPORT int +cast_datetime_to_datetime(PyArray_DatetimeMetaData *src_meta, + PyArray_DatetimeMetaData *dst_meta, + npy_datetime src_dt, + npy_datetime *dst_dt) +{ + npy_datetimestruct dts; + + /* If the metadata is the same, short-circuit the conversion */ + if (src_meta->base == dst_meta->base && + src_meta->num == dst_meta->num && + src_meta->events == dst_meta->events) { + *dst_dt = src_dt; + return 0; + } + + /* Otherwise convert through a datetimestruct */ + if (convert_datetime_to_datetimestruct(src_meta, src_dt, &dts) < 0) { + *dst_dt = NPY_DATETIME_NAT; + return -1; + } + if (dts.event >= dst_meta->events) { + dts.event = dts.event % dst_meta->events; + } + if (convert_datetimestruct_to_datetime(dst_meta, &dts, dst_dt) < 0) { + *dst_dt = NPY_DATETIME_NAT; + return -1; + } + + return 0; +} + +/* + * Casts a single timedelta from having src_meta metadata into + * dst_meta metadata. + * + * Returns 0 on success, -1 on failure. + */ +NPY_NO_EXPORT int +cast_timedelta_to_timedelta(PyArray_DatetimeMetaData *src_meta, + PyArray_DatetimeMetaData *dst_meta, + npy_timedelta src_dt, + npy_timedelta *dst_dt) +{ + npy_int64 num = 0, denom = 0; + int event = 0; + + /* If the metadata is the same, short-circuit the conversion */ + if (src_meta->base == dst_meta->base && + src_meta->num == dst_meta->num && + src_meta->events == dst_meta->events) { + *dst_dt = src_dt; + return 0; + } + + /* Get the conversion factor */ + get_datetime_conversion_factor(src_meta, dst_meta, &num, &denom); + + if (num == 0) { + PyErr_SetString(PyExc_OverflowError, + "Integer overflow getting a conversion factor between " + "different timedelta types"); + return -1; + } + + /* Remove the event number from the value */ + if (src_meta->events > 1) { + event = (int)(src_dt % src_meta->events); + src_dt = src_dt / src_meta->events; + if (event < 0) { + --src_dt; + event += src_meta->events; + } + } + + /* Apply the scaling */ + if (src_dt < 0) { + *dst_dt = (src_dt * num - (denom - 1)) / denom; + } + else { + *dst_dt = src_dt * num / denom; + } + + /* Add the event number back in */ + if (dst_meta->events > 1) { + event = event % dst_meta->events; + *dst_dt = (*dst_dt) * dst_meta->events + event; + } + + return 0; +} + diff --git a/numpy/core/src/multiarray/descriptor.c b/numpy/core/src/multiarray/descriptor.c index bf4ea27a6..1fe8f748d 100644 --- a/numpy/core/src/multiarray/descriptor.c +++ b/numpy/core/src/multiarray/descriptor.c @@ -13,6 +13,7 @@ #include "numpy/npy_3kcompat.h" +#include "_datetime.h" #include "common.h" #define _chk_byteorder(arg) (arg == '>' || arg == '<' || \ @@ -102,9 +103,9 @@ array_set_typeDict(PyObject *NPY_UNUSED(ignored), PyObject *args) } static int -_check_for_commastring(char *type, int len) +_check_for_commastring(char *type, Py_ssize_t len) { - int i; + Py_ssize_t i; /* Check for ints at start of string */ if ((type[0] >= '0' @@ -135,9 +136,9 @@ _check_for_commastring(char *type, int len) } static int -_check_for_datetime(char *type, int len) +is_datetime_typestr(char *type, Py_ssize_t len) { - if (len < 1) { + if (len < 2) { return 0; } if (type[1] == '8' && (type[0] == 'M' || type[0] == 'm')) { @@ -251,7 +252,7 @@ _convert_from_tuple(PyObject *obj) newdescr->elsize = type->elsize; newdescr->elsize *= PyArray_MultiplyList(shape.ptr, shape.len); PyDimMem_FREE(shape.ptr); - newdescr->subarray = _pya_malloc(sizeof(PyArray_ArrayDescr)); + newdescr->subarray = PyArray_malloc(sizeof(PyArray_ArrayDescr)); newdescr->flags = type->flags; newdescr->subarray->base = type; type = NULL; @@ -535,262 +536,6 @@ _convert_from_list(PyObject *obj, int align) return NULL; } -/* Exported as DATETIMEUNITS in multiarraymodule.c */ -NPY_NO_EXPORT char *_datetime_strings[] = { - NPY_STR_Y, - NPY_STR_M, - NPY_STR_W, - NPY_STR_B, - NPY_STR_D, - NPY_STR_h, - NPY_STR_m, - 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 NPY_DATETIMEUNIT _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; - NPY_DATETIMEUNIT *totry; - NPY_DATETIMEUNIT *baseunit; - int q, r; - - ind = ((int)meta->base - (int)NPY_FR_Y)*2; - totry = _multiples_table[ind]; - baseunit = _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 = _multiples_table[ind + 1]; - baseunit[0] = meta->base + 1; - baseunit[1] = meta->base + 2; - if (meta->base == NPY_DATETIME_NUMUNITS - 2) { - num = 1; - } - if (meta->base == NPY_DATETIME_NUMUNITS - 1) { - num = 0; - } - } - - 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 = 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 = NpyCapsule_AsVoidPtr(cobj); - dt_tuple = PyTuple_New(4); - - PyTuple_SET_ITEM(dt_tuple, 0, - PyBytes_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; - PyObject *ret; - - dt_data = _pya_malloc(sizeof(PyArray_DatetimeMetaData)); - dt_data->base = _unit_from_str( - PyBytes_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; - } - } - -/* FIXME - * There is no error handling here. - */ - ret = NpyCapsule_FromVoidPtr((void *)dt_data, simple_capsule_dtor); - return ret; -} - -static PyArray_Descr * -_convert_from_datetime_tuple(PyObject *obj) -{ - PyArray_Descr *new; - PyObject *dt_tuple; - PyObject *dt_cobj; - PyObject *datetime_flag; - - 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_flag = PyTuple_GET_ITEM(obj, 1); - if (!PyTuple_Check(dt_tuple) - || PyTuple_GET_SIZE(dt_tuple) != 4 - || !PyInt_Check(datetime_flag)) { - 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_flag)) { - new = PyArray_DescrNewFromType(PyArray_DATETIME); - } - else { - new = PyArray_DescrNewFromType(PyArray_TIMEDELTA); - } - - if (new == NULL) { - return NULL; - } - /* - * Remove any reference to old metadata dictionary - * And create a new one for this new dtype - */ - Py_XDECREF(new->metadata); - if ((new->metadata = PyDict_New()) == NULL) { - 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_SetItemString(new->metadata, NPY_METADATA_DTSTR, 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 (!PyBytes_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 @@ -1288,20 +1033,14 @@ PyArray_DescrConverter(PyObject *obj, PyArray_Descr **at) 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; + if (is_datetime_typestr(type, len)) { + *at = parse_dtype_from_datetime_typestr(type, len); + return (*at) ? PY_SUCCEED : 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); - if (*at) { - return PY_SUCCEED; - } - return PY_FAIL; + return (*at) ? PY_SUCCEED : PY_FAIL; } check_num = (int) type[0]; if ((char) check_num == '>' @@ -1438,7 +1177,13 @@ PyArray_DescrConverter(PyObject *obj, PyArray_Descr **at) return PY_SUCCEED; fail: - PyErr_SetString(PyExc_TypeError, "data type not understood"); + if (PyBytes_Check(obj)) { + PyErr_Format(PyExc_TypeError, "data type \"%s\" not understood", + PyBytes_AS_STRING(obj)); + } + else { + PyErr_SetString(PyExc_TypeError, "data type not understood"); + } *at = NULL; return PY_FAIL; } @@ -1479,7 +1224,7 @@ PyArray_DescrNew(PyArray_Descr *base) Py_XINCREF(new->fields); Py_XINCREF(new->names); if (new->subarray) { - new->subarray = _pya_malloc(sizeof(PyArray_ArrayDescr)); + new->subarray = PyArray_malloc(sizeof(PyArray_ArrayDescr)); memcpy(new->subarray, base->subarray, sizeof(PyArray_ArrayDescr)); Py_INCREF(new->subarray->shape); Py_INCREF(new->subarray->base); @@ -1511,7 +1256,7 @@ arraydescr_dealloc(PyArray_Descr *self) if (self->subarray) { Py_XDECREF(self->subarray->shape); Py_DECREF(self->subarray->base); - _pya_free(self->subarray); + PyArray_free(self->subarray); } Py_XDECREF(self->metadata); Py_TYPE(self)->tp_free((PyObject *)self); @@ -1554,48 +1299,6 @@ arraydescr_subdescr_get(PyArray_Descr *self) (PyObject *)self->subarray->base, 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 = NpyCapsule_AsVoidPtr(tmp); - num = dt_data->num; - den = dt_data->den; - events = dt_data->events; - basestr = _datetime_strings[dt_data->base]; - - if (num == 1) { - tmp = PyUString_FromString(basestr); - } - else { - tmp = PyUString_FromFormat("%d%s", num, basestr); - } - if (den != 1) { - res = PyUString_FromFormat("/%d", den); - PyUString_ConcatAndDel(&tmp, res); - } - - res = PyUString_FromString("["); - PyUString_ConcatAndDel(&res, tmp); - PyUString_ConcatAndDel(&res, PyUString_FromString("]")); - if (events != 1) { - tmp = PyUString_FromFormat("//%d", events); - PyUString_ConcatAndDel(&res, tmp); - } - PyUString_ConcatAndDel(&ret, res); - return ret; -} - NPY_NO_EXPORT PyObject * arraydescr_protocol_typestr_get(PyArray_Descr *self) { @@ -1616,7 +1319,15 @@ arraydescr_protocol_typestr_get(PyArray_Descr *self) ret = PyUString_FromFormat("%c%c%d", endian, basic_, size); if (PyDataType_ISDATETIME(self)) { - ret = _append_to_datetime_typestr(self, ret); + PyArray_DatetimeMetaData *meta; + + meta = get_datetime_metadata_from_dtype(self); + if (meta == NULL) { + Py_DECREF(ret); + return NULL; + } + + ret = append_metastr_to_string(meta, 0, ret); } return ret; @@ -1659,7 +1370,15 @@ arraydescr_typename_get(PyArray_Descr *self) PyUString_ConcatAndDel(&res, p); } if (PyDataType_ISDATETIME(self)) { - res = _append_to_datetime_typestr(self, res); + PyArray_DatetimeMetaData *meta; + + meta = get_datetime_metadata_from_dtype(self); + if (meta == NULL) { + Py_DECREF(res); + return NULL; + } + + res = append_metastr_to_string(meta, 0, res); } return res; @@ -2042,27 +1761,50 @@ arraydescr_new(PyTypeObject *NPY_UNUSED(subtype), PyObject *args, PyObject *kwds * (cleaned metadata dictionary, tuple with (str, num, events)) */ static PyObject * -_get_pickleabletype_from_metadata(PyObject *metadata) +_get_pickleabletype_from_datetime_metadata(PyArray_Descr *dtype) { PyObject *newdict; - PyObject *newtup, *dt_tuple; - PyObject *cobj; + PyObject *ret, *dt_tuple; + PyArray_DatetimeMetaData *meta; - newdict = PyDict_Copy(metadata); - PyDict_DelItemString(newdict, NPY_METADATA_DTSTR); - newtup = PyTuple_New(2); - PyTuple_SET_ITEM(newtup, 0, newdict); + /* Create the 2-item tuple to return */ + ret = PyTuple_New(2); + if (ret == NULL) { + return NULL; + } - cobj = PyDict_GetItemString(metadata, NPY_METADATA_DTSTR); - dt_tuple = _get_datetime_tuple_from_cobj(cobj); + /* Make a cleaned copy of the metadata dictionary */ + newdict = PyDict_Copy(dtype->metadata); + if (newdict == NULL) { + Py_DECREF(ret); + return NULL; + } + PyDict_DelItemString(newdict, NPY_METADATA_DTSTR); + PyTuple_SET_ITEM(ret, 0, newdict); - PyTuple_SET_ITEM(newtup, 1, dt_tuple); + /* Convert the datetime metadata into a tuple */ + meta = get_datetime_metadata_from_dtype(dtype); + if (meta == NULL) { + Py_DECREF(ret); + return NULL; + } + dt_tuple = convert_datetime_metadata_to_tuple(meta); + if (dt_tuple == NULL) { + Py_DECREF(ret); + return NULL; + } + PyTuple_SET_ITEM(ret, 1, dt_tuple); - return newtup; + return ret; } - -/* return a tuple of (callable object, args, state). */ +/* + * return a tuple of (callable object, args, state). + * + * TODO: This method needs to change so that unpickling doesn't + * use __setstate__. This is required for the dtype + * to be an immutable object. + */ static PyObject * arraydescr_reduce(PyArray_Descr *self, PyObject *NPY_UNUSED(args)) { @@ -2129,7 +1871,12 @@ arraydescr_reduce(PyArray_Descr *self, PyObject *NPY_UNUSED(args)) * 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); + newobj = _get_pickleabletype_from_datetime_metadata(self); + if (newobj == NULL) { + Py_DECREF(state); + Py_DECREF(ret); + return NULL; + } PyTuple_SET_ITEM(state, 8, newobj); } else { @@ -2357,7 +2104,7 @@ arraydescr_setstate(PyArray_Descr *self, PyObject *args) if (self->subarray) { Py_XDECREF(self->subarray->base); Py_XDECREF(self->subarray->shape); - _pya_free(self->subarray); + PyArray_free(self->subarray); } self->subarray = NULL; @@ -2401,7 +2148,10 @@ arraydescr_setstate(PyArray_Descr *self, PyObject *args) return NULL; } - self->subarray = _pya_malloc(sizeof(PyArray_ArrayDescr)); + self->subarray = PyArray_malloc(sizeof(PyArray_ArrayDescr)); + if (self->subarray == NULL) { + return PyErr_NoMemory(); + } self->subarray->base = (PyArray_Descr *)PyTuple_GET_ITEM(subarray, 0); Py_INCREF(self->subarray->base); self->subarray->shape = subarray_shape; @@ -2435,7 +2185,11 @@ arraydescr_setstate(PyArray_Descr *self, PyObject *args) PyObject *cobj; self->metadata = PyTuple_GET_ITEM(metadata, 0); Py_INCREF(self->metadata); - cobj = _convert_datetime_tuple_to_cobj(PyTuple_GET_ITEM(metadata, 1)); + cobj = convert_datetime_metadata_tuple_to_metacobj( + PyTuple_GET_ITEM(metadata, 1)); + if (cobj == NULL) { + return NULL; + } PyDict_SetItemString(self->metadata, NPY_METADATA_DTSTR, cobj); Py_DECREF(cobj); } diff --git a/numpy/core/src/multiarray/dtype_transfer.c b/numpy/core/src/multiarray/dtype_transfer.c index f04dbe8eb..14a6c9d6a 100644 --- a/numpy/core/src/multiarray/dtype_transfer.c +++ b/numpy/core/src/multiarray/dtype_transfer.c @@ -16,9 +16,12 @@ #define _MULTIARRAYMODULE #include <numpy/ndarrayobject.h> -#include <numpy/ufuncobject.h> #include <numpy/npy_cpu.h> +#include "numpy/npy_3kcompat.h" + +#include "_datetime.h" + #include "lowlevel_strided_loops.h" #define NPY_LOWLEVEL_BUFFER_BLOCKSIZE 128 @@ -696,6 +699,241 @@ get_nbo_cast_numeric_transfer_function(int aligned, return NPY_SUCCEED; } +/* Does a datetime->datetime or timedelta->timedelta cast */ +typedef struct { + free_strided_transfer_data freefunc; + copy_strided_transfer_data copyfunc; + /* The conversion fraction */ + npy_int64 num, denom; + /* The number of events in the source and destination */ + int src_events, dst_events; + /* + * The metadata for when dealing with Months, Years, or + * Business Days (all of which behave non-linearly). + */ + PyArray_DatetimeMetaData src_meta, dst_meta; +} _strided_datetime_cast_data; + +/* strided cast data copy function */ +void *_strided_datetime_cast_data_copy(void *data) +{ + _strided_datetime_cast_data *newdata = + (_strided_datetime_cast_data *)PyArray_malloc( + sizeof(_strided_datetime_cast_data)); + if (newdata == NULL) { + return NULL; + } + + memcpy(newdata, data, sizeof(_strided_datetime_cast_data)); + + return (void *)newdata; +} + +static void +_strided_to_strided_datetime_general_cast(char *dst, npy_intp dst_stride, + char *src, npy_intp src_stride, + npy_intp N, npy_intp src_itemsize, + void *data) +{ + _strided_datetime_cast_data *d = (_strided_datetime_cast_data *)data; + npy_int64 dt; + npy_datetimestruct dts; + + while (N > 0) { + memcpy(&dt, src, sizeof(dt)); + + if (convert_datetime_to_datetimestruct(&d->src_meta, + dt, &dts) < 0) { + dt = NPY_DATETIME_NAT; + } + else { + dts.event = dts.event % d->dst_meta.events; + if (convert_datetimestruct_to_datetime(&d->dst_meta, + &dts, &dt) < 0) { + dt = NPY_DATETIME_NAT; + } + } + + memcpy(dst, &dt, sizeof(dt)); + + dst += dst_stride; + src += src_stride; + --N; + } +} + +static void +_strided_to_strided_datetime_cast(char *dst, npy_intp dst_stride, + char *src, npy_intp src_stride, + npy_intp N, npy_intp src_itemsize, + void *data) +{ + _strided_datetime_cast_data *d = (_strided_datetime_cast_data *)data; + npy_int64 num = d->num, denom = d->denom; + npy_int64 dt; + int event = 0, src_events = d->src_events, dst_events = d->dst_events; + + while (N > 0) { + memcpy(&dt, src, sizeof(dt)); + + if (dt != NPY_DATETIME_NAT) { + /* Remove the event number from the value */ + if (src_events > 1) { + event = (int)(dt % src_events); + dt = dt / src_events; + if (event < 0) { + --dt; + event += src_events; + } + } + + /* Apply the scaling */ + if (dt < 0) { + dt = (dt * num - (denom - 1)) / denom; + } + else { + dt = dt * num / denom; + } + + /* Add the event number back in */ + if (dst_events > 1) { + event = event % dst_events; + dt = dt * dst_events + event; + } + } + + memcpy(dst, &dt, sizeof(dt)); + + dst += dst_stride; + src += src_stride; + --N; + } +} + +static void +_aligned_strided_to_strided_datetime_cast_no_events(char *dst, + npy_intp dst_stride, + char *src, npy_intp src_stride, + npy_intp N, npy_intp src_itemsize, + void *data) +{ + _strided_datetime_cast_data *d = (_strided_datetime_cast_data *)data; + npy_int64 num = d->num, denom = d->denom; + npy_int64 dt; + + while (N > 0) { + dt = *(npy_int64 *)src; + + if (dt != NPY_DATETIME_NAT) { + /* Apply the scaling */ + if (dt < 0) { + dt = (dt * num - (denom - 1)) / denom; + } + else { + dt = dt * num / denom; + } + } + + *(npy_int64 *)dst = dt; + + dst += dst_stride; + src += src_stride; + --N; + } +} + +/* + * Assumes src_dtype and dst_dtype are both datetimes or both timedeltas + */ +static int +get_nbo_cast_datetime_transfer_function(int aligned, + npy_intp src_stride, npy_intp dst_stride, + PyArray_Descr *src_dtype, PyArray_Descr *dst_dtype, + PyArray_StridedTransferFn **out_stransfer, + void **out_transferdata) +{ + PyArray_DatetimeMetaData *src_meta, *dst_meta; + npy_int64 num = 0, denom = 0; + _strided_datetime_cast_data *data; + + src_meta = get_datetime_metadata_from_dtype(src_dtype); + if (src_meta == NULL) { + return NPY_FAIL; + } + dst_meta = get_datetime_metadata_from_dtype(dst_dtype); + if (dst_meta == NULL) { + return NPY_FAIL; + } + + get_datetime_conversion_factor(src_meta, dst_meta, &num, &denom); + + if (num == 0) { + PyObject *errmsg; + errmsg = PyUString_FromString("Integer overflow " + "getting a conversion factor between types "); + PyUString_ConcatAndDel(&errmsg, + PyObject_Repr((PyObject *)src_dtype)); + PyUString_ConcatAndDel(&errmsg, + PyUString_FromString(" and ")); + PyUString_ConcatAndDel(&errmsg, + PyObject_Repr((PyObject *)dst_dtype)); + PyErr_SetObject(PyExc_OverflowError, errmsg); + return NPY_FAIL; + } + + /* Allocate the data for the casting */ + data = (_strided_datetime_cast_data *)PyArray_malloc( + sizeof(_strided_datetime_cast_data)); + if (data == NULL) { + PyErr_NoMemory(); + *out_stransfer = NULL; + *out_transferdata = NULL; + return NPY_FAIL; + } + data->freefunc = &PyArray_free; + data->copyfunc = &_strided_datetime_cast_data_copy; + data->num = num; + data->denom = denom; + data->src_events = src_meta->events; + data->dst_events = dst_meta->events; + + /* + * Special case the datetime (but not timedelta) with the nonlinear + * units (years, months, business days). For timedelta, an average + * years and months value is used. + */ + if (src_dtype->type_num == NPY_DATETIME && + (src_meta->base == NPY_FR_Y || + src_meta->base == NPY_FR_M || + src_meta->base == NPY_FR_B || + dst_meta->base == NPY_FR_Y || + dst_meta->base == NPY_FR_M || + dst_meta->base == NPY_FR_B)) { + memcpy(&data->src_meta, src_meta, sizeof(data->src_meta)); + memcpy(&data->dst_meta, dst_meta, sizeof(data->dst_meta)); + *out_stransfer = &_strided_to_strided_datetime_general_cast; + } + else if (aligned && data->src_events == 1 && data->dst_events == 1) { + *out_stransfer = &_aligned_strided_to_strided_datetime_cast_no_events; + } + else { + *out_stransfer = &_strided_to_strided_datetime_cast; + } + *out_transferdata = data; + +#if NPY_DT_DBG_TRACING + printf("Dtype transfer from "); + PyObject_Print((PyObject *)src_dtype, stdout, 0); + printf(" to "); + PyObject_Print((PyObject *)dst_dtype, stdout, 0); + printf("\n"); + printf("has conversion fraction %lld/%lld\n", num, denom); +#endif + + + return NPY_SUCCEED; +} + static int get_nbo_cast_transfer_function(int aligned, npy_intp src_stride, npy_intp dst_stride, @@ -722,6 +960,19 @@ get_nbo_cast_transfer_function(int aligned, out_stransfer, out_transferdata); } + /* As a parameterized type, datetime->datetime sometimes needs casting */ + if ((src_dtype->type_num == NPY_DATETIME && + dst_dtype->type_num == NPY_DATETIME) || + (src_dtype->type_num == NPY_TIMEDELTA && + dst_dtype->type_num == NPY_TIMEDELTA)) { + *out_needs_wrap = !PyArray_ISNBO(src_dtype->byteorder) || + !PyArray_ISNBO(dst_dtype->byteorder); + return get_nbo_cast_datetime_transfer_function(aligned, + src_stride, dst_stride, + src_dtype, dst_dtype, + out_stransfer, out_transferdata); + } + *out_needs_wrap = !aligned || !PyArray_ISNBO(src_dtype->byteorder) || !PyArray_ISNBO(dst_dtype->byteorder); @@ -854,7 +1105,9 @@ get_cast_transfer_function(int aligned, npy_intp src_itemsize = src_dtype->elsize, dst_itemsize = dst_dtype->elsize; - if (src_dtype->type_num == dst_dtype->type_num) { + if (src_dtype->type_num == dst_dtype->type_num && + src_dtype->type_num != NPY_DATETIME && + src_dtype->type_num != NPY_TIMEDELTA) { PyErr_SetString(PyExc_ValueError, "low level cast function is for unequal type numbers"); return NPY_FAIL; @@ -2872,7 +3125,8 @@ PyArray_GetDTypeTransferFunction(int aligned, if (src_itemsize == dst_itemsize && src_dtype->kind == dst_dtype->kind && !PyDataType_HASFIELDS(src_dtype) && !PyDataType_HASFIELDS(dst_dtype) && - src_dtype->subarray == NULL && dst_dtype->subarray == NULL) { + src_dtype->subarray == NULL && dst_dtype->subarray == NULL && + src_type_num != NPY_DATETIME && src_type_num != NPY_TIMEDELTA) { /* A custom data type requires that we use its copy/swap */ if (src_type_num >= NPY_NTYPES || dst_type_num >= NPY_NTYPES) { /* @@ -2895,8 +3149,6 @@ PyArray_GetDTypeTransferFunction(int aligned, PyArray_ISNBO(dst_dtype->byteorder), out_stransfer, out_transferdata); } - - } /* The special types, which have no byte-order */ diff --git a/numpy/core/src/multiarray/lowlevel_strided_loops.c.src b/numpy/core/src/multiarray/lowlevel_strided_loops.c.src index fc8d71f46..0a1786bc4 100644 --- a/numpy/core/src/multiarray/lowlevel_strided_loops.c.src +++ b/numpy/core/src/multiarray/lowlevel_strided_loops.c.src @@ -14,7 +14,6 @@ #define _MULTIARRAYMODULE #include <numpy/ndarrayobject.h> -#include <numpy/ufuncobject.h> #include <numpy/npy_cpu.h> #include <numpy/halffloat.h> diff --git a/numpy/core/src/multiarray/methods.c b/numpy/core/src/multiarray/methods.c index 2c4110afd..9502bc9f0 100644 --- a/numpy/core/src/multiarray/methods.c +++ b/numpy/core/src/multiarray/methods.c @@ -185,8 +185,6 @@ array_view(PyArrayObject *self, PyObject *args, PyObject *kwds) if ((out_dtype) && (PyArray_DescrConverter(out_dtype, &dtype) == PY_FAIL)) { - PyErr_SetString(PyExc_ValueError, - "Dtype must be a numpy data-type"); return NULL; } diff --git a/numpy/core/src/multiarray/multiarraymodule.c b/numpy/core/src/multiarray/multiarraymodule.c index f121f6147..f1103ef7f 100644 --- a/numpy/core/src/multiarray/multiarraymodule.c +++ b/numpy/core/src/multiarray/multiarraymodule.c @@ -44,6 +44,7 @@ NPY_NO_EXPORT int NPY_NUMUSERTYPES = 0; #include "numpymemoryview.h" #include "convert_datatype.h" #include "nditer_pywrap.h" +#include "_datetime.h" /* Only here for API compatibility */ NPY_NO_EXPORT PyTypeObject PyBigArray_Type; @@ -1401,39 +1402,6 @@ _equivalent_fields(PyObject *field1, PyObject *field2) { } /* - * compare the metadata for two date-times - * return 1 if they are the same - * or 0 if not - */ -static int -_equivalent_units(PyObject *meta1, PyObject *meta2) -{ - PyObject *cobj1, *cobj2; - PyArray_DatetimeMetaData *data1, *data2; - - /* Same meta object */ - if (meta1 == meta2) { - return 1; - } - - cobj1 = PyDict_GetItemString(meta1, NPY_METADATA_DTSTR); - cobj2 = PyDict_GetItemString(meta2, NPY_METADATA_DTSTR); - if (cobj1 == cobj2) { - return 1; - } - -/* FIXME - * There is no err handling here. - */ - data1 = NpyCapsule_AsVoidPtr(cobj1); - data2 = NpyCapsule_AsVoidPtr(cobj2); - return ((data1->base == data2->base) - && (data1->num == data2->num) - && (data1->den == data2->den) - && (data1->events == data2->events)); -} - -/* * Compare the subarray data for two types. * Return 1 if they are the same, 0 if not. */ @@ -1471,42 +1439,42 @@ _equivalent_subarrays(PyArray_ArrayDescr *sub1, PyArray_ArrayDescr *sub2) * equivalent (same basic kind and same itemsize). */ NPY_NO_EXPORT unsigned char -PyArray_EquivTypes(PyArray_Descr *typ1, PyArray_Descr *typ2) +PyArray_EquivTypes(PyArray_Descr *type1, PyArray_Descr *type2) { - int typenum1, typenum2, size1, size2; + int type_num1, type_num2, size1, size2; - if (typ1 == typ2) { + if (type1 == type2) { return TRUE; } - typenum1 = typ1->type_num; - typenum2 = typ2->type_num; - size1 = typ1->elsize; - size2 = typ2->elsize; + type_num1 = type1->type_num; + type_num2 = type2->type_num; + size1 = type1->elsize; + size2 = type2->elsize; if (size1 != size2) { return FALSE; } - if (PyArray_ISNBO(typ1->byteorder) != PyArray_ISNBO(typ2->byteorder)) { + if (PyArray_ISNBO(type1->byteorder) != PyArray_ISNBO(type2->byteorder)) { return FALSE; } - if (typ1->subarray || typ2->subarray) { - return ((typenum1 == typenum2) - && _equivalent_subarrays(typ1->subarray, typ2->subarray)); + if (type1->subarray || type2->subarray) { + return ((type_num1 == type_num2) + && _equivalent_subarrays(type1->subarray, type2->subarray)); } - if (typenum1 == PyArray_VOID - || typenum2 == PyArray_VOID) { - return ((typenum1 == typenum2) - && _equivalent_fields(typ1->fields, typ2->fields)); + if (type_num1 == NPY_VOID + || type_num2 == NPY_VOID) { + return ((type_num1 == type_num2) + && _equivalent_fields(type1->fields, type2->fields)); } - if (typenum1 == PyArray_DATETIME - || typenum1 == PyArray_DATETIME - || typenum2 == PyArray_TIMEDELTA - || typenum2 == PyArray_TIMEDELTA) { - return ((typenum1 == typenum2) - && _equivalent_units(typ1->metadata, typ2->metadata)); + if (type_num1 == NPY_DATETIME + || type_num1 == NPY_DATETIME + || type_num2 == NPY_TIMEDELTA + || type_num2 == NPY_TIMEDELTA) { + return ((type_num1 == type_num2) + && has_equivalent_datetime_metadata(type1, type2)); } - return typ1->kind == typ2->kind; + return type1->kind == type2->kind; } /*NUMPY_API*/ @@ -2569,36 +2537,11 @@ array_set_ops_function(PyObject *NPY_UNUSED(self), PyObject *NPY_UNUSED(args), } static PyObject * -array_set_datetimeparse_function(PyObject *NPY_UNUSED(self), PyObject *args, - PyObject *kwds) +array_set_datetimeparse_function(PyObject *NPY_UNUSED(self), + PyObject *NPY_UNUSED(args), PyObject *NPY_UNUSED(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; + PyErr_SetString(PyExc_RuntimeError, "This function is to be removed"); + return NULL; } @@ -2844,6 +2787,189 @@ finish: return ret; } +static PyObject * +array_datetime_data(PyObject *NPY_UNUSED(dummy), PyObject *args) +{ + PyArray_Descr *dtype; + PyArray_DatetimeMetaData *meta; + + if(!PyArg_ParseTuple(args, "O&:datetime_data", + PyArray_DescrConverter, &dtype)) { + return NULL; + } + + meta = get_datetime_metadata_from_dtype(dtype); + if (meta == NULL) { + return NULL; + } + + return convert_datetime_metadata_to_tuple(meta); +} + +static PyObject * +array_datetime_as_string(PyObject *NPY_UNUSED(self), PyObject *args, + PyObject *kwds) +{ + PyObject *arr_in = NULL, *unit_in = NULL; + int local = 0, tzoffset = -1; + NPY_DATETIMEUNIT unit; + PyArray_DatetimeMetaData *meta; + int strsize; + + PyArrayObject *ret = NULL; + + NpyIter *iter = NULL; + PyArrayObject *op[2] = {NULL, NULL}; + PyArray_Descr *op_dtypes[2] = {NULL, NULL}; + npy_uint32 flags, op_flags[2]; + + static char *kwlist[] = {"arr", "local", "unit", "tzoffset", NULL}; + + if(!PyArg_ParseTupleAndKeywords(args, kwds, + "O|iOi:datetime_as_string", kwlist, + &arr_in, + &local, + &unit_in, + &tzoffset)) { + goto fail; + } + + op[0] = (PyArrayObject *)PyArray_FromAny(arr_in, + NULL, 0, 0, 0, NULL); + if (PyArray_DESCR(op[0])->type_num != NPY_DATETIME) { + PyErr_SetString(PyExc_TypeError, + "input must have type NumPy datetime"); + goto fail; + } + + /* Get the datetime metadata */ + meta = get_datetime_metadata_from_dtype(PyArray_DESCR(op[0])); + if (meta == NULL) { + goto fail; + } + + /* Use the metadata's unit for printing by default */ + unit = meta->base; + + /* Parse the input unit if provided */ + if (unit_in != NULL && unit_in != Py_None) { + PyObject *strobj; + char *str = NULL; + Py_ssize_t len = 0; + + if (PyUnicode_Check(unit_in)) { + strobj = PyUnicode_AsASCIIString(unit_in); + if (strobj == NULL) { + goto fail; + } + } + else { + strobj = unit_in; + Py_INCREF(strobj); + } + + if (PyBytes_AsStringAndSize(strobj, &str, &len) < 0) { + Py_DECREF(strobj); + goto fail; + } + + /* unit == -1 means to autodetect the unit from the datetime data */ + if (strcmp(str, "auto") == 0) { + unit = -1; + } + else { + unit = parse_datetime_unit_from_string(str, len, NULL); + if (unit == -1) { + Py_DECREF(strobj); + goto fail; + } + } + Py_DECREF(strobj); + } + + if (!local && tzoffset != -1) { + PyErr_SetString(PyExc_ValueError, + "Can only use 'tzoffset' parameter when 'local' is " + "set to True"); + goto fail; + } + + /* Create the output string data type with a big enough length */ + op_dtypes[1] = PyArray_DescrNewFromType(NPY_STRING); + if (op_dtypes[1] == NULL) { + goto fail; + } + strsize = get_datetime_iso_8601_strlen(local, unit); + op_dtypes[1]->elsize = strsize; + + flags = NPY_ITER_ZEROSIZE_OK| + NPY_ITER_BUFFERED; + op_flags[0] = NPY_ITER_READONLY| + NPY_ITER_ALIGNED; + op_flags[1] = NPY_ITER_WRITEONLY| + NPY_ITER_ALLOCATE; + + iter = NpyIter_MultiNew(2, op, flags, NPY_KEEPORDER, NPY_NO_CASTING, + op_flags, op_dtypes); + if (iter == NULL) { + goto fail; + } + + if (NpyIter_GetIterSize(iter) != 0) { + NpyIter_IterNextFunc *iternext; + char **dataptr; + npy_datetime dt; + npy_datetimestruct dts; + + iternext = NpyIter_GetIterNext(iter, NULL); + if (iternext == NULL) { + goto fail; + } + dataptr = NpyIter_GetDataPtrArray(iter); + + do { + /* Get the datetime */ + dt = *(datetime *)dataptr[0]; + /* Convert it to a struct */ + if (convert_datetime_to_datetimestruct(meta, dt, &dts) < 0) { + goto fail; + } + /* Zero the destination string completely */ + memset(dataptr[1], 0, strsize); + /* Convert that into a string */ + if (make_iso_8601_date(&dts, (char *)dataptr[1], strsize, + local, unit, tzoffset) < 0) { + goto fail; + } + } while(iternext(iter)); + } + + ret = NpyIter_GetOperandArray(iter)[1]; + Py_INCREF(ret); + + Py_XDECREF(op[0]); + Py_XDECREF(op[1]); + Py_XDECREF(op_dtypes[0]); + Py_XDECREF(op_dtypes[1]); + if (iter != NULL) { + NpyIter_Deallocate(iter); + } + + return PyArray_Return(ret); + +fail: + Py_XDECREF(op[0]); + Py_XDECREF(op[1]); + Py_XDECREF(op_dtypes[0]); + Py_XDECREF(op_dtypes[1]); + if (iter != NULL) { + NpyIter_Deallocate(iter); + } + + return NULL; +} + + #if !defined(NPY_PY3K) static PyObject * new_buffer(PyObject *NPY_UNUSED(dummy), PyObject *args) @@ -3464,6 +3590,12 @@ static struct PyMethodDef array_module_methods[] = { {"result_type", (PyCFunction)array_result_type, METH_VARARGS, NULL}, + {"datetime_data", + (PyCFunction)array_datetime_data, + METH_VARARGS, NULL}, + {"datetime_as_string", + (PyCFunction)array_datetime_as_string, + METH_VARARGS | METH_KEYWORDS, NULL}, #if !defined(NPY_PY3K) {"newbuffer", (PyCFunction)new_buffer, @@ -3609,9 +3741,10 @@ setup_scalartypes(PyObject *NPY_UNUSED(dict)) SINGLE_INHERIT(LongLong, SignedInteger); #endif - SINGLE_INHERIT(TimeInteger, SignedInteger); - SINGLE_INHERIT(Datetime, TimeInteger); - SINGLE_INHERIT(Timedelta, TimeInteger); + /* Datetime doesn't fit in any category */ + SINGLE_INHERIT(Datetime, Generic); + /* Timedelta is an integer with an associated unit */ + SINGLE_INHERIT(Timedelta, SignedInteger); /* fprintf(stderr, @@ -3733,6 +3866,9 @@ PyMODINIT_FUNC initmultiarray(void) { 1); #endif + /* Initialize access to the PyDateTime API */ + numpy_pydatetime_import(); + /* Add some symbolic constants to the module */ d = PyModule_GetDict(m); if (!d) { diff --git a/numpy/core/src/multiarray/scalarapi.c b/numpy/core/src/multiarray/scalarapi.c index 3d2fae39c..d4ccc42c8 100644 --- a/numpy/core/src/multiarray/scalarapi.c +++ b/numpy/core/src/multiarray/scalarapi.c @@ -94,10 +94,7 @@ scalar_value(PyObject *scalar, PyArray_Descr *descr) _IFCASE(Int); _IFCASE(Long); _IFCASE(LongLong); - if _CHK(TimeInteger) { - _IFCASE(Datetime); - _IFCASE(Timedelta); - } + _IFCASE(Timedelta); } else { /* Unsigned Integer */ @@ -127,6 +124,9 @@ scalar_value(PyObject *scalar, PyArray_Descr *descr) else if (_CHK(Bool)) { return _OBJ(Bool); } + else if (_CHK(Datetime)) { + return _OBJ(Datetime); + } else if (_CHK(Flexible)) { if (_CHK(String)) { return (void *)PyString_AS_STRING(scalar); @@ -522,7 +522,7 @@ PyArray_DescrFromScalar(PyObject *sc) return descr; } - if (PyArray_IsScalar(sc, TimeInteger)) { + if (PyArray_IsScalar(sc, Datetime) || PyArray_IsScalar(sc, Timedelta)) { PyObject *cobj; PyArray_DatetimeMetaData *dt_data; diff --git a/numpy/core/src/multiarray/scalartypes.c.src b/numpy/core/src/multiarray/scalartypes.c.src index 58dc39861..2fea9d28d 100644 --- a/numpy/core/src/multiarray/scalartypes.c.src +++ b/numpy/core/src/multiarray/scalartypes.c.src @@ -21,12 +21,16 @@ #include "numpyos.h" #include "common.h" #include "scalartypes.h" +#include "_datetime.h" NPY_NO_EXPORT PyBoolScalarObject _PyArrayScalar_BoolValues[] = { {PyObject_HEAD_INIT(&PyBoolArrType_Type) 0}, {PyObject_HEAD_INIT(&PyBoolArrType_Type) 1}, }; +/* TimeInteger is deleted, but still here to fill the API slot */ +NPY_NO_EXPORT PyTypeObject PyTimeIntegerArrType_Type; + /* * Inheritance is established later when tp_bases is set (or tp_base for * single inheritance) @@ -34,9 +38,9 @@ NPY_NO_EXPORT PyBoolScalarObject _PyArrayScalar_BoolValues[] = { /**begin repeat * #name = number, integer, signedinteger, unsignedinteger, inexact, - * floating, complexfloating, flexible, character, timeinteger# + * floating, complexfloating, flexible, character# * #NAME = Number, Integer, SignedInteger, UnsignedInteger, Inexact, - * Floating, ComplexFloating, Flexible, Character, TimeInteger# + * Floating, ComplexFloating, Flexible, Character# */ NPY_NO_EXPORT PyTypeObject Py@NAME@ArrType_Type = { #if defined(NPY_PY3K) @@ -107,7 +111,7 @@ gentype_alloc(PyTypeObject *type, Py_ssize_t nitems) PyObject *obj; const size_t size = _PyObject_VAR_SIZE(type, nitems + 1); - obj = (PyObject *)_pya_malloc(size); + obj = (PyObject *)PyArray_malloc(size); memset(obj, 0, size); if (type->tp_itemsize == 0) { PyObject_INIT(obj, type); @@ -333,15 +337,13 @@ gentype_nonzero_number(PyObject *m1) static PyObject * gentype_str(PyObject *self) { - PyArrayObject *arr; - PyObject *ret; + PyObject *arr, *ret = NULL; - arr = (PyArrayObject *)PyArray_FromScalar(self, NULL); - if (arr == NULL) { - return NULL; + arr = PyArray_FromScalar(self, NULL); + if (arr != NULL) { + ret = PyObject_Str((PyObject *)arr); + Py_DECREF(arr); } - ret = PyObject_Str((PyObject *)arr); - Py_DECREF(arr); return ret; } @@ -349,15 +351,14 @@ gentype_str(PyObject *self) static PyObject * gentype_repr(PyObject *self) { - PyArrayObject *arr; - PyObject *ret; + PyObject *arr, *ret = NULL; - arr = (PyArrayObject *)PyArray_FromScalar(self, NULL); - if (arr == NULL) { - return NULL; + arr = PyArray_FromScalar(self, NULL); + if (arr != NULL) { + /* XXX: Why are we using str here? */ + ret = PyObject_Str((PyObject *)arr); + Py_DECREF(arr); } - ret = PyObject_Str((PyObject *)arr); - Py_DECREF(arr); return ret; } @@ -596,6 +597,147 @@ static PyObject * } /**end repeat**/ +static PyObject * +datetimetype_repr(PyObject *self) +{ + PyDatetimeScalarObject *scal; + npy_datetimestruct dts; + PyObject *ret; + char iso[NPY_DATETIME_MAX_ISO8601_STRLEN]; + + if (!PyArray_IsScalar(self, Datetime)) { + PyErr_SetString(PyExc_RuntimeError, + "Called NumPy datetime repr on a non-datetime type"); + return NULL; + } + + scal = (PyDatetimeScalarObject *)self; + + if (convert_datetime_to_datetimestruct(&scal->obmeta, scal->obval, + &dts) < 0) { + return NULL; + } + + if (make_iso_8601_date(&dts, iso, sizeof(iso), 1, + scal->obmeta.base, -1) < 0) { + return NULL; + } + + ret = PyUString_FromString("numpy.datetime64('"); + PyUString_ConcatAndDel(&ret, + PyUString_FromString(iso)); + PyUString_ConcatAndDel(&ret, + PyUString_FromString("','")); + ret = append_metastr_to_string(&scal->obmeta, 1, ret); + PyUString_ConcatAndDel(&ret, + PyUString_FromString("')")); + + return ret; +} + +static PyObject * +timedeltatype_repr(PyObject *self) +{ + PyTimedeltaScalarObject *scal; + PyObject *ret; + + if (!PyArray_IsScalar(self, Timedelta)) { + PyErr_SetString(PyExc_RuntimeError, + "Called NumPy timedelta repr on a non-datetime type"); + return NULL; + } + + scal = (PyTimedeltaScalarObject *)self; + + ret = PyUString_FromFormat("numpy.timedelta64(%lld", scal->obval); + PyUString_ConcatAndDel(&ret, + PyUString_FromString(",'")); + ret = append_metastr_to_string(&scal->obmeta, 1, ret); + PyUString_ConcatAndDel(&ret, + PyUString_FromString("')")); + + return ret; +} + +static PyObject * +datetimetype_str(PyObject *self) +{ + PyDatetimeScalarObject *scal; + npy_datetimestruct dts; + char iso[NPY_DATETIME_MAX_ISO8601_STRLEN]; + + if (!PyArray_IsScalar(self, Datetime)) { + PyErr_SetString(PyExc_RuntimeError, + "Called NumPy datetime str on a non-datetime type"); + return NULL; + } + + scal = (PyDatetimeScalarObject *)self; + + if (convert_datetime_to_datetimestruct(&scal->obmeta, scal->obval, + &dts) < 0) { + return NULL; + } + + if (make_iso_8601_date(&dts, iso, sizeof(iso), 1, + scal->obmeta.base, -1) < 0) { + return NULL; + } + + return PyUString_FromString(iso); +} + +static char *_datetime_strings[] = { + "years", + "months", + "weeks", + "business days", + "days", + "hours", + "minutes", + "seconds", + "milliseconds", + "microseconds", + "nanoseconds", + "picoseconds", + "femtoseconds", + "attoseconds" +}; + +static PyObject * +timedeltatype_str(PyObject *self) +{ + PyTimedeltaScalarObject *scal; + PyObject *ret; + char *basestr = "invalid"; + + if (!PyArray_IsScalar(self, Timedelta)) { + PyErr_SetString(PyExc_RuntimeError, + "Called NumPy timedelta str on a non-datetime type"); + return NULL; + } + + scal = (PyTimedeltaScalarObject *)self; + + /* TODO: Account for events, etc */ + + if (scal->obmeta.base >= 0 && scal->obmeta.base < NPY_DATETIME_NUMUNITS) { + basestr = _datetime_strings[scal->obmeta.base]; + } + else { + PyErr_SetString(PyExc_RuntimeError, + "NumPy datetime metadata is corrupted"); + return NULL; + } + + ret = PyUString_FromFormat("%lld ", + (long long)(scal->obval * scal->obmeta.num)); + PyUString_ConcatAndDel(&ret, + PyUString_FromString(basestr)); + + return ret; +} + /* The REPR values are finfo.precision + 2 */ #define HALFPREC_REPR 5 #define HALFPREC_STR 5 @@ -909,8 +1051,8 @@ gentype_struct_free(PyObject *ptr) context = (PyObject *)PyCapsule_GetContext(ptr); Py_DECREF(context); Py_XDECREF(arrif->descr); - _pya_free(arrif->shape); - _pya_free(arrif); + PyArray_free(arrif->shape); + PyArray_free(arrif); } #else NPY_NO_EXPORT void @@ -919,8 +1061,8 @@ gentype_struct_free(void *ptr, void *arg) PyArrayInterface *arrif = (PyArrayInterface *)ptr; Py_DECREF((PyObject *)arg); Py_XDECREF(arrif->descr); - _pya_free(arrif->shape); - _pya_free(arrif); + PyArray_free(arrif->shape); + PyArray_free(arrif); } #endif @@ -932,7 +1074,7 @@ gentype_struct_get(PyObject *self) PyObject *ret; arr = (PyArrayObject *)PyArray_FromScalar(self, NULL); - inter = (PyArrayInterface *)_pya_malloc(sizeof(PyArrayInterface)); + inter = (PyArrayInterface *)PyArray_malloc(sizeof(PyArrayInterface)); inter->two = 2; inter->nd = 0; inter->flags = arr->flags; @@ -1296,7 +1438,7 @@ gentype_byteswap(PyObject *self, PyObject *args) gentype_getreadbuf(self, 0, (void **)&data); descr = PyArray_DescrFromScalar(self); - newmem = _pya_malloc(descr->elsize); + newmem = PyArray_malloc(descr->elsize); if (newmem == NULL) { Py_DECREF(descr); return PyErr_NoMemory(); @@ -1305,7 +1447,7 @@ gentype_byteswap(PyObject *self, PyObject *args) descr->f->copyswap(newmem, data, 1, NULL); } new = PyArray_Scalar(newmem, descr, NULL); - _pya_free(newmem); + PyArray_free(newmem); Py_DECREF(descr); return new; } @@ -1478,7 +1620,7 @@ gentype_reduce(PyObject *self, PyObject *NPY_UNUSED(args)) int newlen; if (PyArray_IsScalar(self, Unicode)) { - tmp = _pya_malloc(buflen*2); + tmp = PyArray_malloc(buflen*2); if (tmp == NULL) { Py_DECREF(ret); return PyErr_NoMemory(); @@ -1505,7 +1647,7 @@ gentype_reduce(PyObject *self, PyObject *NPY_UNUSED(args)) Py_BuildValue("NN", obj, mod)); #ifndef Py_UNICODE_WIDE fail: - if (alloc) _pya_free((char *)buffer); + if (alloc) PyArray_free((char *)buffer); #endif } return ret; @@ -2212,12 +2354,15 @@ object_arrtype_dealloc(PyObject *v) /**begin repeat * #name = byte, short, int, long, longlong, ubyte, ushort, uint, ulong, * ulonglong, half, float, double, longdouble, cfloat, cdouble, - * clongdouble, string, unicode, object, datetime, timedelta# + * clongdouble, string, unicode, object# + * #Name = Byte, Short, Int, Long, LongLong, UByte, UShort, UInt, ULong, + * ULongLong, Half, Float, Double, LongDouble, CFloat, CDouble, + * CLongDouble, String, Unicode, Object# * #TYPE = BYTE, SHORT, INT, LONG, LONGLONG, UBYTE, USHORT, UINT, ULONG, * ULONGLONG, HALF, FLOAT, DOUBLE, LONGDOUBLE, CFLOAT, CDOUBLE, - * CLONGDOUBLE, STRING, UNICODE, OBJECT, DATETIME, TIMEDELTA# - * #work = 0,0,1,1,1,0,0,0,0,0,0,0,1,0,0,0,0,z,z,0,0,0# - * #default = 0*17,1*2,2,0*2# + * CLONGDOUBLE, STRING, UNICODE, OBJECT# + * #work = 0,0,1,1,1,0,0,0,0,0,0,0,1,0,0,0,0,z,z,0# + * #default = 0*17,1*2,2# */ #define _NPY_UNUSED2_1 @@ -2248,18 +2393,22 @@ static PyObject * if (!PyArg_ParseTuple(args, "|O", &obj)) { return NULL; } - typecode = PyArray_DescrFromType(PyArray_@TYPE@); + typecode = PyArray_DescrFromType(NPY_@TYPE@); + if (typecode == NULL) { + return NULL; + } /* * typecode is new reference and stolen by * PyArray_FromAny but not PyArray_Scalar */ if (obj == NULL) { #if @default@ == 0 - char *mem = malloc(sizeof(npy_@name@)); - - memset(mem, 0, sizeof(npy_@name@)); - robj = PyArray_Scalar(mem, typecode, NULL); - free(mem); + robj = PyArray_Scalar(NULL, typecode, NULL); + if (robj == NULL) { + Py_DECREF(typecode); + return NULL; + } + memset(&((Py@Name@ScalarObject *)robj)->obval, 0, sizeof(npy_@name@)); #elif @default@ == 1 robj = PyArray_Scalar(NULL, typecode, NULL); #elif @default@ == 2 @@ -2339,6 +2488,89 @@ finish: #undef _WORK0 #undef _WORK +/**begin repeat + * #name = datetime, timedelta# + * #Name = Datetime, Timedelta# + * #NAME = DATETIME, TIMEDELTA# + */ + +static PyObject * +@name@_arrtype_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyObject *obj = NULL, *meta_obj = NULL; + Py@Name@ScalarObject *ret; + + if (!PyArg_ParseTuple(args, "|OO", &obj, &meta_obj)) { + return NULL; + } + + /* Allocate the return scalar */ + ret = (Py@Name@ScalarObject *)Py@Name@ArrType_Type.tp_alloc( + &Py@Name@ArrType_Type, 0); + if (ret == NULL) { + return NULL; + } + + /* Incorporate the metadata if its provided */ + if (meta_obj != NULL) { + /* Parse the provided metadata input */ + if (convert_pyobject_to_datetime_metadata(meta_obj, &ret->obmeta) + < 0) { + Py_DECREF(ret); + return NULL; + } + } + else { + ret->obmeta.base = NPY_DATETIME_DEFAULTUNIT; + ret->obmeta.num = 1; + ret->obmeta.events = 1; + } + + if (obj == NULL) { + ret->obval = 0; + } + /* + * If the metadata was unspecified and the input is a datetime/timedelta + * scalar, then copy the input's value and metadata exactly. + */ + else if (meta_obj == NULL && PyArray_IsScalar(obj, @Name@)) { + ret->obval = ((Py@Name@ScalarObject *)obj)->obval; + ret->obmeta = ((Py@Name@ScalarObject *)obj)->obmeta; + } + /* + * If the metadata was unspecified and the input is a datetime/timedelta + * zero-dimensional array, then copy the input's value and metadata + * directly. + */ + else if (meta_obj == NULL && + PyArray_Check(obj) && + PyArray_NDIM(obj) == 0 && + PyArray_DESCR(obj)->type_num == NPY_@NAME@) { + PyArray_DatetimeMetaData *meta; + + meta = get_datetime_metadata_from_dtype(PyArray_DESCR(obj)); + if (meta == NULL) { + Py_DECREF(ret); + return NULL; + } + PyArray_DESCR(obj)->f->copyswap(&ret->obval, + PyArray_DATA(obj), + !PyArray_ISNOTSWAPPED(obj), + obj); + ret->obmeta = *meta; + } + else { + if (convert_pyobject_to_@name@(&ret->obmeta, obj, &ret->obval) + < 0) { + Py_DECREF(ret); + return NULL; + } + } + + return (PyObject *)ret; +} +/**end repeat**/ + /* bool->tp_new only returns Py_True or Py_False */ static PyObject * bool_arrtype_new(PyTypeObject *NPY_UNUSED(type), PyObject *args, PyObject *NPY_UNUSED(kwds)) @@ -3426,18 +3658,21 @@ initialize_casting_tables(void) for (i = 0; i < NPY_NTYPES; ++i) { /* Identity */ _npy_can_cast_safely_table[i][i] = 1; - /* Bool -> <Anything> */ - _npy_can_cast_safely_table[NPY_BOOL][i] = 1; - /* DateTime sits out for these... */ - if (i != PyArray_DATETIME && i != PyArray_TIMEDELTA) { - /* <Anything> -> Object */ - _npy_can_cast_safely_table[i][NPY_OBJECT] = 1; - /* <Anything> -> Void */ - _npy_can_cast_safely_table[i][NPY_VOID] = 1; + if (i != NPY_DATETIME) { + /* + * Bool -> <Anything> except datetime (since + * it conceptually has no zero) + */ + _npy_can_cast_safely_table[NPY_BOOL][i] = 1; } + /* <Anything> -> Object */ + _npy_can_cast_safely_table[i][NPY_OBJECT] = 1; + /* <Anything> -> Void */ + _npy_can_cast_safely_table[i][NPY_VOID] = 1; } _npy_can_cast_safely_table[NPY_STRING][NPY_UNICODE] = 1; + _npy_can_cast_safely_table[NPY_BOOL][NPY_TIMEDELTA] = 1; #ifndef NPY_SIZEOF_BYTE #define NPY_SIZEOF_BYTE 1 @@ -3467,8 +3702,13 @@ initialize_casting_tables(void) #define _FROM_BSIZE NPY_SIZEOF_@FROM_BASENAME@ #define _FROM_NUM (NPY_@FROM_NAME@) - _npy_can_cast_safely_table[_FROM_NUM][PyArray_STRING] = 1; - _npy_can_cast_safely_table[_FROM_NUM][PyArray_UNICODE] = 1; + _npy_can_cast_safely_table[_FROM_NUM][NPY_STRING] = 1; + _npy_can_cast_safely_table[_FROM_NUM][NPY_UNICODE] = 1; + + /* Allow casts from any integer to the TIMEDELTA type */ +#if @from_isint@ || @from_isuint@ + _npy_can_cast_safely_table[_FROM_NUM][NPY_TIMEDELTA] = 1; +#endif /**begin repeat1 * #TO_NAME = BYTE, UBYTE, SHORT, USHORT, INT, UINT, LONG, ULONG, @@ -3576,8 +3816,9 @@ initialize_casting_tables(void) */ for (i = 0; i < NPY_NTYPES; ++i) { _npy_type_promotion_table[i][i] = i; - /* Don't let number promote to string/unicode/void */ - if (i == NPY_STRING || i == NPY_UNICODE || i == NPY_VOID) { + /* Don't let number promote to string/unicode/void/datetime/timedelta */ + if (i == NPY_STRING || i == NPY_UNICODE || i == NPY_VOID || + i == NPY_DATETIME || i == NPY_TIMEDELTA) { /* Promoting these types requires examining their contents */ _npy_type_promotion_table[i][i] = -1; for (j = i+1; j < NPY_NTYPES; ++j) { @@ -3650,9 +3891,6 @@ initialize_casting_tables(void) } } } - /* Special case date-time */ - _npy_type_promotion_table[NPY_DATETIME][NPY_TIMEDELTA] = NPY_DATETIME; - _npy_type_promotion_table[NPY_TIMEDELTA][NPY_DATETIME] = NPY_DATETIME; } @@ -3672,7 +3910,7 @@ initialize_numeric_types(void) PyGenericArrType_Type.tp_getset = gentype_getsets; PyGenericArrType_Type.tp_new = NULL; PyGenericArrType_Type.tp_alloc = gentype_alloc; - PyGenericArrType_Type.tp_free = _pya_free; + PyGenericArrType_Type.tp_free = PyArray_free; PyGenericArrType_Type.tp_repr = gentype_repr; PyGenericArrType_Type.tp_str = gentype_str; PyGenericArrType_Type.tp_richcompare = gentype_richcompare; @@ -3712,7 +3950,7 @@ initialize_numeric_types(void) /**begin repeat * #NAME= Number, Integer, SignedInteger, UnsignedInteger, Inexact, - * Floating, ComplexFloating, Flexible, Character, TimeInteger# + * Floating, ComplexFloating, Flexible, Character# */ Py@NAME@ArrType_Type.tp_flags = BASEFLAGS; /**end repeat**/ @@ -3775,6 +4013,9 @@ initialize_numeric_types(void) PyDoubleArrType_Type.tp_@name@ = doubletype_@name@; PyCDoubleArrType_Type.tp_@name@ = cdoubletype_@name@; + + PyDatetimeArrType_Type.tp_@name@ = datetimetype_@name@; + PyTimedeltaArrType_Type.tp_@name@ = timedeltatype_@name@; /**end repeat**/ PyHalfArrType_Type.tp_print = halftype_print; @@ -3801,6 +4042,7 @@ initialize_numeric_types(void) PyCLongDoubleArrType_Type.@kind@_@name@ = clongdoubletype_@name@; /**end repeat**/ + #if !defined(NPY_PY3K) /**begin repeat * #name = long, hex, oct# diff --git a/numpy/core/src/umath/loops.c.src b/numpy/core/src/umath/loops.c.src index 54e5ac984..31345ff45 100644 --- a/numpy/core/src/umath/loops.c.src +++ b/numpy/core/src/umath/loops.c.src @@ -941,64 +941,70 @@ U@TYPE@_remainder(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(f ***************************************************************************** */ -/**begin repeat - * #type = datetime, timedelta# - * #TYPE = DATETIME, TIMEDELTA# - * #ftype = double, double# - */ - NPY_NO_EXPORT void -@TYPE@_ones_like(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(data)) +TIMEDELTA_negative(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)) { - OUTPUT_LOOP { - *((@type@ *)op1) = 1; + UNARY_LOOP { + const npy_timedelta in1 = *(npy_timedelta *)ip1; + if (in1 == NPY_DATETIME_NAT) { + *((npy_timedelta *)op1) = NPY_DATETIME_NAT; + } + else { + *((npy_timedelta *)op1) = -in1; + } } } NPY_NO_EXPORT void -@TYPE@_negative(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)) +TIMEDELTA_absolute(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)) { UNARY_LOOP { - const @type@ in1 = *(@type@ *)ip1; - *((@type@ *)op1) = (@type@)(-(@type@)in1); + const npy_timedelta in1 = *(npy_timedelta *)ip1; + if (in1 == NPY_DATETIME_NAT) { + *((npy_timedelta *)op1) = NPY_DATETIME_NAT; + } + else { + *((npy_timedelta *)op1) = (in1 >= 0) ? in1 : -in1; + } } } NPY_NO_EXPORT void -@TYPE@_logical_not(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)) +TIMEDELTA_sign(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)) { UNARY_LOOP { - const @type@ in1 = *(@type@ *)ip1; - *((Bool *)op1) = !in1; + const npy_timedelta in1 = *(npy_timedelta *)ip1; + *((npy_timedelta *)op1) = in1 > 0 ? 1 : (in1 < 0 ? -1 : 0); } } - -/**begin repeat1 - * #kind = equal, not_equal, greater, greater_equal, less, less_equal, - * logical_and, logical_or# - * #OP = ==, !=, >, >=, <, <=, &&, ||# +/**begin repeat + * #type = datetime, timedelta# + * #TYPE = DATETIME, TIMEDELTA# */ + NPY_NO_EXPORT void -@TYPE@_@kind@(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)) +@TYPE@_ones_like(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(data)) { - BINARY_LOOP { - const @type@ in1 = *(@type@ *)ip1; - const @type@ in2 = *(@type@ *)ip2; - *((Bool *)op1) = in1 @OP@ in2; + OUTPUT_LOOP { + *((@type@ *)op1) = 1; } } -/**end repeat1**/ +/**begin repeat1 + * #kind = equal, not_equal, greater, greater_equal, less, less_equal# + * #OP = ==, !=, >, >=, <, <=# + */ NPY_NO_EXPORT void -@TYPE@_logical_xor(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)) +@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 && !in2) || (!in1 && in2); + *((npy_bool *)op1) = in1 @OP@ in2; } } +/**end repeat1**/ /**begin repeat1 * #kind = maximum, minimum# @@ -1010,50 +1016,42 @@ NPY_NO_EXPORT void if (IS_BINARY_REDUCE) { BINARY_REDUCE_LOOP(@type@) { const @type@ in2 = *(@type@ *)ip2; - io1 = (io1 @OP@ in2) ? io1 : in2; + io1 = (io1 @OP@ in2 || in2 == NPY_DATETIME_NAT) ? io1 : in2; } *((@type@ *)iop1) = io1; } else { - BINARY_LOOP { - const @type@ in1 = *(@type@ *)ip1; - const @type@ in2 = *(@type@ *)ip2; - *((@type@ *)op1) = (in1 @OP@ in2) ? in1 : in2; - } + BINARY_LOOP { + const @type@ in1 = *(@type@ *)ip1; + const @type@ in2 = *(@type@ *)ip2; + if (in1 == NPY_DATETIME_NAT) { + *((@type@ *)op1) = in2; + } + else if (in2 == NPY_DATETIME_NAT) { + *((@type@ *)op1) = in1; + } + else { + *((@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; + if (in1 == NPY_DATETIME_NAT || in2 == NPY_DATETIME_NAT) { + *((npy_datetime *)op1) = NPY_DATETIME_NAT; + } + else { + *((npy_datetime *)op1) = in1 + in2; + } } } @@ -1063,7 +1061,12 @@ DATETIME_mM_M_add(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(f BINARY_LOOP { const timedelta in1 = *(timedelta *)ip1; const datetime in2 = *(datetime *)ip2; - *((datetime *)op1) = in1 + in2; + if (in1 == NPY_DATETIME_NAT || in2 == NPY_DATETIME_NAT) { + *((npy_datetime *)op1) = NPY_DATETIME_NAT; + } + else { + *((npy_datetime *)op1) = in1 + in2; + } } } @@ -1073,7 +1076,12 @@ TIMEDELTA_mm_m_add(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED( BINARY_LOOP { const timedelta in1 = *(timedelta *)ip1; const timedelta in2 = *(timedelta *)ip2; - *((timedelta *)op1) = in1 + in2; + if (in1 == NPY_DATETIME_NAT || in2 == NPY_DATETIME_NAT) { + *((npy_timedelta *)op1) = NPY_DATETIME_NAT; + } + else { + *((npy_timedelta *)op1) = in1 + in2; + } } } @@ -1083,7 +1091,12 @@ DATETIME_Mm_M_subtract(char **args, intp *dimensions, intp *steps, void *NPY_UNU BINARY_LOOP { const datetime in1 = *(datetime *)ip1; const timedelta in2 = *(timedelta *)ip2; - *((datetime *)op1) = in1 - in2; + if (in1 == NPY_DATETIME_NAT || in2 == NPY_DATETIME_NAT) { + *((npy_datetime *)op1) = NPY_DATETIME_NAT; + } + else { + *((npy_datetime *)op1) = in1 - in2; + } } } @@ -1093,7 +1106,12 @@ DATETIME_MM_m_subtract(char **args, intp *dimensions, intp *steps, void *NPY_UNU BINARY_LOOP { const datetime in1 = *(datetime *)ip1; const datetime in2 = *(datetime *)ip2; - *((timedelta *)op1) = in1 - in2; + if (in1 == NPY_DATETIME_NAT || in2 == NPY_DATETIME_NAT) { + *((npy_timedelta *)op1) = NPY_DATETIME_NAT; + } + else { + *((npy_timedelta *)op1) = in1 - in2; + } } } @@ -1103,10 +1121,113 @@ TIMEDELTA_mm_m_subtract(char **args, intp *dimensions, intp *steps, void *NPY_UN BINARY_LOOP { const timedelta in1 = *(timedelta *)ip1; const timedelta in2 = *(timedelta *)ip2; - *((timedelta *)op1) = in1 - in2; + if (in1 == NPY_DATETIME_NAT || in2 == NPY_DATETIME_NAT) { + *((npy_timedelta *)op1) = NPY_DATETIME_NAT; + } + else { + *((npy_timedelta *)op1) = in1 - in2; + } + } +} + +/* Note: Assuming 'q' == NPY_LONGLONG */ +NPY_NO_EXPORT void +TIMEDELTA_mq_m_multiply(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)) +{ + BINARY_LOOP { + const npy_timedelta in1 = *(npy_timedelta *)ip1; + const npy_int64 in2 = *(npy_int64 *)ip2; + if (in1 == NPY_DATETIME_NAT) { + *((npy_timedelta *)op1) = NPY_DATETIME_NAT; + } + else { + *((npy_timedelta *)op1) = in1 * in2; + } + } +} + +/* Note: Assuming 'q' == NPY_LONGLONG */ +NPY_NO_EXPORT void +TIMEDELTA_qm_m_multiply(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)) +{ + BINARY_LOOP { + const npy_int64 in1 = *(npy_int64 *)ip1; + const npy_timedelta in2 = *(npy_timedelta *)ip2; + if (in2 == NPY_DATETIME_NAT) { + *((npy_timedelta *)op1) = NPY_DATETIME_NAT; + } + else { + *((npy_timedelta *)op1) = in1 * in2; + } } } +NPY_NO_EXPORT void +TIMEDELTA_md_m_multiply(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)) +{ + BINARY_LOOP { + const npy_timedelta in1 = *(npy_timedelta *)ip1; + const double in2 = *(double *)ip2; + if (in1 == NPY_DATETIME_NAT || isnan(in2)) { + *((npy_timedelta *)op1) = NPY_DATETIME_NAT; + } + else { + *((npy_timedelta *)op1) = (npy_timedelta)(in1 * in2); + } + } +} + +NPY_NO_EXPORT void +TIMEDELTA_dm_m_multiply(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)) +{ + BINARY_LOOP { + const double in1 = *(double *)ip1; + const npy_timedelta in2 = *(npy_timedelta *)ip2; + if (isnan(in1) || in2 == NPY_DATETIME_NAT) { + *((npy_timedelta *)op1) = NPY_DATETIME_NAT; + } + else { + *((npy_timedelta *)op1) = (npy_timedelta)(in1 * in2); + } + } +} + +/* Note: Assuming 'q' == NPY_LONGLONG */ +NPY_NO_EXPORT void +TIMEDELTA_mq_m_divide(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)) +{ + BINARY_LOOP { + const npy_timedelta in1 = *(npy_timedelta *)ip1; + const npy_int64 in2 = *(npy_int64 *)ip2; + if (in1 == NPY_DATETIME_NAT || in2 == 0) { + *((npy_timedelta *)op1) = NPY_DATETIME_NAT; + } + else { + *((npy_timedelta *)op1) = in1 / in2; + } + } +} + +NPY_NO_EXPORT void +TIMEDELTA_md_m_divide(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)) +{ + BINARY_LOOP { + const npy_timedelta in1 = *(npy_timedelta *)ip1; + const double in2 = *(double *)ip2; + if (in1 == NPY_DATETIME_NAT) { + *((npy_timedelta *)op1) = NPY_DATETIME_NAT; + } + else { + double result = in1 / in2; + if (isnan(result)) { + *((npy_timedelta *)op1) = NPY_DATETIME_NAT; + } + else { + *((npy_timedelta *)op1) = (npy_timedelta)(result); + } + } + } +} /* ***************************************************************************** diff --git a/numpy/core/src/umath/loops.h b/numpy/core/src/umath/loops.h index abd8de23e..2a827e168 100644 --- a/numpy/core/src/umath/loops.h +++ b/numpy/core/src/umath/loops.h @@ -2535,145 +2535,142 @@ CLONGDOUBLE_fmin(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(fu ***************************************************************************** */ -#line 422 -#define DATETIME_fmax DATETIME_maximum -#define DATETIME_fmin DATETIME_minimum - -#line 422 -#define TIMEDELTA_fmax TIMEDELTA_maximum -#define TIMEDELTA_fmin TIMEDELTA_minimum - - -#line 431 NPY_NO_EXPORT void -DATETIME_equal(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +TIMEDELTA_negative(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); NPY_NO_EXPORT void -TIMEDELTA_equal(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +TIMEDELTA_absolute(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); -#line 431 NPY_NO_EXPORT void -DATETIME_not_equal(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +TIMEDELTA_sign(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); -NPY_NO_EXPORT void -TIMEDELTA_not_equal(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +#line 432 -#line 431 NPY_NO_EXPORT void -DATETIME_greater(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +DATETIME_ones_like(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(data)); +#line 440 NPY_NO_EXPORT void -TIMEDELTA_greater(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +DATETIME_equal(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); -#line 431 +#line 440 NPY_NO_EXPORT void -DATETIME_greater_equal(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +DATETIME_not_equal(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +#line 440 NPY_NO_EXPORT void -TIMEDELTA_greater_equal(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +DATETIME_greater(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); -#line 431 +#line 440 NPY_NO_EXPORT void -DATETIME_less(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +DATETIME_greater_equal(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +#line 440 NPY_NO_EXPORT void -TIMEDELTA_less(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +DATETIME_less(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); -#line 431 +#line 440 NPY_NO_EXPORT void DATETIME_less_equal(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); -NPY_NO_EXPORT void -TIMEDELTA_less_equal(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); -#line 431 +#line 448 NPY_NO_EXPORT void -DATETIME_absolute(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +DATETIME_maximum(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +#line 448 NPY_NO_EXPORT void -TIMEDELTA_absolute(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +DATETIME_minimum(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); -#line 431 -NPY_NO_EXPORT void -DATETIME_logical_and(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); -NPY_NO_EXPORT void -TIMEDELTA_logical_and(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); -#line 431 -NPY_NO_EXPORT void -DATETIME_logical_not(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +#line 432 NPY_NO_EXPORT void -TIMEDELTA_logical_not(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +TIMEDELTA_ones_like(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(data)); -#line 431 +#line 440 NPY_NO_EXPORT void -DATETIME_logical_or(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +TIMEDELTA_equal(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +#line 440 NPY_NO_EXPORT void -TIMEDELTA_logical_or(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +TIMEDELTA_not_equal(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); -#line 431 +#line 440 NPY_NO_EXPORT void -DATETIME_logical_xor(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +TIMEDELTA_greater(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +#line 440 NPY_NO_EXPORT void -TIMEDELTA_logical_xor(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +TIMEDELTA_greater_equal(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); -#line 431 +#line 440 NPY_NO_EXPORT void -DATETIME_maximum(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +TIMEDELTA_less(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +#line 440 NPY_NO_EXPORT void -TIMEDELTA_maximum(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +TIMEDELTA_less_equal(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); + -#line 431 +#line 448 NPY_NO_EXPORT void -DATETIME_minimum(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +TIMEDELTA_maximum(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +#line 448 NPY_NO_EXPORT void TIMEDELTA_minimum(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); -#line 431 -NPY_NO_EXPORT void -DATETIME_negative(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); + + NPY_NO_EXPORT void -TIMEDELTA_negative(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +DATETIME_Mm_M_add(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(data)); -#line 431 NPY_NO_EXPORT void -DATETIME_ones_like(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +DATETIME_mM_M_add(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); NPY_NO_EXPORT void -TIMEDELTA_ones_like(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +TIMEDELTA_mm_m_add(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); -#line 431 NPY_NO_EXPORT void -DATETIME_sign(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +DATETIME_Mm_M_subtract(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); NPY_NO_EXPORT void -TIMEDELTA_sign(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +DATETIME_MM_m_subtract(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +NPY_NO_EXPORT void +TIMEDELTA_mm_m_subtract(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); NPY_NO_EXPORT void -DATETIME_Mm_M_add(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +TIMEDELTA_mq_m_multiply(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); NPY_NO_EXPORT void -DATETIME_mM_M_add(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +TIMEDELTA_qm_m_multiply(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); NPY_NO_EXPORT void -DATETIME_Mm_M_subtract(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +TIMEDELTA_md_m_multiply(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); NPY_NO_EXPORT void -DATETIME_MM_m_subtract(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +TIMEDELTA_dm_m_multiply(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); NPY_NO_EXPORT void -TIMEDELTA_mm_m_add(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +TIMEDELTA_mq_m_divide(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); NPY_NO_EXPORT void -TIMEDELTA_mm_m_subtract(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +TIMEDELTA_md_m_divide(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); + +/* Special case equivalents to above functions */ + +#define TIMEDELTA_mq_m_true_divide TIMEDELTA_mq_m_divide +#define TIMEDELTA_md_m_true_divide TIMEDELTA_md_m_divide +#define TIMEDELTA_mq_m_floor_divide TIMEDELTA_mq_m_divide +#define TIMEDELTA_md_m_floor_divide TIMEDELTA_md_m_divide +#define TIMEDELTA_fmin TIMEDELTA_minimum +#define TIMEDELTA_fmax TIMEDELTA_maximum +#define DATETIME_fmin DATETIME_minimum +#define DATETIME_fmax DATETIME_maximum /* ***************************************************************************** @@ -2681,27 +2678,27 @@ TIMEDELTA_mm_m_subtract(char **args, intp *dimensions, intp *steps, void *NPY_UN ***************************************************************************** */ -#line 466 +#line 511 NPY_NO_EXPORT void OBJECT_equal(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); -#line 466 +#line 511 NPY_NO_EXPORT void OBJECT_not_equal(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); -#line 466 +#line 511 NPY_NO_EXPORT void OBJECT_greater(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); -#line 466 +#line 511 NPY_NO_EXPORT void OBJECT_greater_equal(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); -#line 466 +#line 511 NPY_NO_EXPORT void OBJECT_less(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); -#line 466 +#line 511 NPY_NO_EXPORT void OBJECT_less_equal(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); @@ -2716,3 +2713,4 @@ OBJECT_sign(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); */ #endif + diff --git a/numpy/core/src/umath/loops.h.src b/numpy/core/src/umath/loops.h.src index 1def8ba0f..bf41e4187 100644 --- a/numpy/core/src/umath/loops.h.src +++ b/numpy/core/src/umath/loops.h.src @@ -416,42 +416,87 @@ C@TYPE@_@kind@(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func ***************************************************************************** */ +NPY_NO_EXPORT void +TIMEDELTA_negative(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); + +NPY_NO_EXPORT void +TIMEDELTA_absolute(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); + +NPY_NO_EXPORT void +TIMEDELTA_sign(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); + /**begin repeat + * #type = datetime, timedelta# * #TYPE = DATETIME, TIMEDELTA# */ -#define @TYPE@_fmax @TYPE@_maximum -#define @TYPE@_fmin @TYPE@_minimum -/**end repeat**/ -/**begin repeat - * #kind = equal, not_equal, greater, greater_equal, less, less_equal, - * absolute, logical_and, logical_not, logical_or, logical_xor, maximum, - * minimum, negative, ones_like, sign# +NPY_NO_EXPORT void +@TYPE@_ones_like(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(data)); + +/**begin repeat1 + * #kind = equal, not_equal, greater, greater_equal, less, less_equal# + * #OP = ==, !=, >, >=, <, <=# */ NPY_NO_EXPORT void -DATETIME_@kind@(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +@TYPE@_@kind@(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +/**end repeat1**/ +/**begin repeat1 + * #kind = maximum, minimum# + * #OP = >, <# + **/ NPY_NO_EXPORT void -TIMEDELTA_@kind@(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +@TYPE@_@kind@(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +/**end repeat1**/ + /**end repeat**/ NPY_NO_EXPORT void -DATETIME_Mm_M_add(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +DATETIME_Mm_M_add(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(data)); NPY_NO_EXPORT void DATETIME_mM_M_add(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); NPY_NO_EXPORT void +TIMEDELTA_mm_m_add(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); + +NPY_NO_EXPORT void DATETIME_Mm_M_subtract(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); NPY_NO_EXPORT void DATETIME_MM_m_subtract(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); NPY_NO_EXPORT void -TIMEDELTA_mm_m_add(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +TIMEDELTA_mm_m_subtract(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); NPY_NO_EXPORT void -TIMEDELTA_mm_m_subtract(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); +TIMEDELTA_mq_m_multiply(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); + +NPY_NO_EXPORT void +TIMEDELTA_qm_m_multiply(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); + +NPY_NO_EXPORT void +TIMEDELTA_md_m_multiply(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); + +NPY_NO_EXPORT void +TIMEDELTA_dm_m_multiply(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); + +NPY_NO_EXPORT void +TIMEDELTA_mq_m_divide(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); + +NPY_NO_EXPORT void +TIMEDELTA_md_m_divide(char **args, intp *dimensions, intp *steps, void *NPY_UNUSED(func)); + +/* Special case equivalents to above functions */ + +#define TIMEDELTA_mq_m_true_divide TIMEDELTA_mq_m_divide +#define TIMEDELTA_md_m_true_divide TIMEDELTA_md_m_divide +#define TIMEDELTA_mq_m_floor_divide TIMEDELTA_mq_m_divide +#define TIMEDELTA_md_m_floor_divide TIMEDELTA_md_m_divide +#define TIMEDELTA_fmin TIMEDELTA_minimum +#define TIMEDELTA_fmax TIMEDELTA_maximum +#define DATETIME_fmin DATETIME_minimum +#define DATETIME_fmax DATETIME_maximum /* ***************************************************************************** diff --git a/numpy/core/src/umath/ufunc_object.c b/numpy/core/src/umath/ufunc_object.c index 930c91ca1..36dea2c83 100644 --- a/numpy/core/src/umath/ufunc_object.c +++ b/numpy/core/src/umath/ufunc_object.c @@ -577,12 +577,12 @@ _parse_signature(PyUFuncObject *self, const char *signature) } len = strlen(signature); - self->core_signature = _pya_malloc(sizeof(char) * (len+1)); + self->core_signature = PyArray_malloc(sizeof(char) * (len+1)); if (self->core_signature) { strcpy(self->core_signature, signature); } /* Allocate sufficient memory to store pointers to all dimension names */ - var_names = _pya_malloc(sizeof(char const*) * len); + var_names = PyArray_malloc(sizeof(char const*) * len); if (var_names == NULL) { PyErr_NoMemory(); return -1; @@ -590,9 +590,9 @@ _parse_signature(PyUFuncObject *self, const char *signature) self->core_enabled = 1; self->core_num_dim_ix = 0; - self->core_num_dims = _pya_malloc(sizeof(int) * self->nargs); - self->core_dim_ixs = _pya_malloc(sizeof(int) * len); /* shrink this later */ - self->core_offsets = _pya_malloc(sizeof(int) * self->nargs); + self->core_num_dims = PyArray_malloc(sizeof(int) * self->nargs); + self->core_dim_ixs = PyArray_malloc(sizeof(int) * len); /* shrink this later */ + self->core_offsets = PyArray_malloc(sizeof(int) * self->nargs); if (self->core_num_dims == NULL || self->core_dim_ixs == NULL || self->core_offsets == NULL) { PyErr_NoMemory(); @@ -677,24 +677,24 @@ _parse_signature(PyUFuncObject *self, const char *signature) parse_error = "incomplete signature: not all arguments found"; goto fail; } - self->core_dim_ixs = _pya_realloc(self->core_dim_ixs, + self->core_dim_ixs = PyArray_realloc(self->core_dim_ixs, sizeof(int)*cur_core_dim); /* check for trivial core-signature, e.g. "(),()->()" */ if (cur_core_dim == 0) { self->core_enabled = 0; } - _pya_free((void*)var_names); + PyArray_free((void*)var_names); return 0; fail: - _pya_free((void*)var_names); + PyArray_free((void*)var_names); if (parse_error) { - char *buf = _pya_malloc(sizeof(char) * (len + 200)); + char *buf = PyArray_malloc(sizeof(char) * (len + 200)); if (buf) { sprintf(buf, "%s at position %d in \"%s\"", parse_error, i, signature); PyErr_SetString(PyExc_ValueError, signature); - _pya_free(buf); + PyArray_free(buf); } else { PyErr_NoMemory(); @@ -720,8 +720,7 @@ static int get_ufunc_arguments(PyUFuncObject *self, NPY_CASTING *out_casting, PyObject **out_extobj, PyObject **out_typetup, - int *out_subok, - int *out_any_object) + int *out_subok) { npy_intp i, nargs, nin = self->nin; PyObject *obj, *context; @@ -938,8 +937,6 @@ static int get_ufunc_arguments(PyUFuncObject *self, } } - *out_any_object = any_object; - Py_XDECREF(str_key_obj); return 0; @@ -1066,25 +1063,49 @@ ufunc_loop_matches(PyUFuncObject *self, static int set_ufunc_loop_data_types(PyUFuncObject *self, PyArrayObject **op, PyArray_Descr **out_dtype, - int *types, - npy_intp buffersize, int *out_trivial_loop_ok) + int *types) { - npy_intp i, nin = self->nin, nop = nin + self->nout; + int i, nin = self->nin, nop = nin + self->nout; - *out_trivial_loop_ok = 1; /* Fill the dtypes array */ for (i = 0; i < nop; ++i) { out_dtype[i] = PyArray_DescrFromType(types[i]); if (out_dtype[i] == NULL) { + while (--i >= 0) { + Py_DECREF(out_dtype[i]); + out_dtype[i] = NULL; + } return -1; } + } + + return 0; +} + +/* + * This checks whether a trivial loop is ok, + * making copies of scalar and one dimensional operands if that will + * help. + * + * Returns 1 if a trivial loop is ok, 0 if it is not, and + * -1 if there is an error. + */ +static int +check_for_trivial_loop(PyUFuncObject *self, + PyArrayObject **op, + PyArray_Descr **dtype, + npy_intp buffersize) +{ + npy_intp i, nin = self->nin, nop = nin + self->nout; + + for (i = 0; i < nop; ++i) { /* * If the dtype doesn't match, or the array isn't aligned, * indicate that the trivial loop can't be done. */ - if (*out_trivial_loop_ok && op[i] != NULL && + if (op[i] != NULL && (!PyArray_ISALIGNED(op[i]) || - !PyArray_EquivTypes(out_dtype[i], PyArray_DESCR(op[i])) + !PyArray_EquivTypes(dtype[i], PyArray_DESCR(op[i])) )) { /* * If op[j] is a scalar or small one dimensional @@ -1095,9 +1116,9 @@ set_ufunc_loop_data_types(PyUFuncObject *self, PyArrayObject **op, (PyArray_NDIM(op[i]) == 1 && PyArray_DIM(op[i],0) <= buffersize))) { PyArrayObject *tmp; - Py_INCREF(out_dtype[i]); + Py_INCREF(dtype[i]); tmp = (PyArrayObject *) - PyArray_CastToType(op[i], out_dtype[i], 0); + PyArray_CastToType(op[i], dtype[i], 0); if (tmp == NULL) { return -1; } @@ -1105,12 +1126,12 @@ set_ufunc_loop_data_types(PyUFuncObject *self, PyArrayObject **op, op[i] = tmp; } else { - *out_trivial_loop_ok = 0; + return 0; } } } - return 0; + return 1; } /* @@ -1121,13 +1142,11 @@ find_ufunc_matching_userloop(PyUFuncObject *self, PyArrayObject **op, NPY_CASTING input_casting, NPY_CASTING output_casting, - npy_intp buffersize, int any_object, int use_min_scalar, PyArray_Descr **out_dtype, PyUFuncGenericFunction *out_innerloop, void **out_innerloopdata, - int *out_trivial_loop_ok, int *out_no_castable_output, char *out_err_src_typecode, char *out_err_dst_typecode) @@ -1168,8 +1187,7 @@ find_ufunc_matching_userloop(PyUFuncObject *self, return -1; /* Found a match */ case 1: - set_ufunc_loop_data_types(self, op, out_dtype, types, - buffersize, out_trivial_loop_ok); + set_ufunc_loop_data_types(self, op, out_dtype, types); /* Save the inner loop and its data */ *out_innerloop = funcdata->func; @@ -1199,15 +1217,13 @@ find_ufunc_specified_userloop(PyUFuncObject *self, int *specified_types, PyArrayObject **op, NPY_CASTING casting, - npy_intp buffersize, int any_object, int use_min_scalar, PyArray_Descr **out_dtype, PyUFuncGenericFunction *out_innerloop, - void **out_innerloopdata, - int *out_trivial_loop_ok) + void **out_innerloopdata) { - npy_intp i, j, nin = self->nin, nop = nin + self->nout; + int i, j, nin = self->nin, nop = nin + self->nout; PyUFunc_Loop1d *funcdata; /* Use this to try to avoid repeating the same userdef loop search */ @@ -1261,8 +1277,7 @@ find_ufunc_specified_userloop(PyUFuncObject *self, &err_dst_typecode)) { /* It works */ case 1: - set_ufunc_loop_data_types(self, op, out_dtype, types, - buffersize, out_trivial_loop_ok); + set_ufunc_loop_data_types(self, op, out_dtype, types); /* Save the inner loop and its data */ *out_innerloop = funcdata->func; @@ -1365,10 +1380,6 @@ should_use_min_scalar(PyArrayObject **op, int nop) /* * Does a linear search for the best inner loop of the ufunc. - * When op[i] is a scalar or a one dimensional array smaller than - * the buffersize, and needs a dtype conversion, this function - * may substitute op[i] with a version cast to the correct type. This way, - * the later trivial loop detection has a higher chance of being triggered. * * Note that if an error is returned, the caller must free the non-zero * references in out_dtype. This function does not do its own clean-up. @@ -1378,12 +1389,10 @@ find_best_ufunc_inner_loop(PyUFuncObject *self, PyArrayObject **op, NPY_CASTING input_casting, NPY_CASTING output_casting, - npy_intp buffersize, int any_object, PyArray_Descr **out_dtype, PyUFuncGenericFunction *out_innerloop, - void **out_innerloopdata, - int *out_trivial_loop_ok) + void **out_innerloopdata) { npy_intp i, j, nin = self->nin, nop = nin + self->nout; int types[NPY_MAXARGS]; @@ -1401,9 +1410,8 @@ find_best_ufunc_inner_loop(PyUFuncObject *self, if (self->userloops) { switch (find_ufunc_matching_userloop(self, op, input_casting, output_casting, - buffersize, any_object, use_min_scalar, + any_object, use_min_scalar, out_dtype, out_innerloop, out_innerloopdata, - out_trivial_loop_ok, &no_castable_output, &err_src_typecode, &err_dst_typecode)) { /* Error */ @@ -1452,8 +1460,7 @@ find_best_ufunc_inner_loop(PyUFuncObject *self, return -1; /* Found a match */ case 1: - set_ufunc_loop_data_types(self, op, out_dtype, types, - buffersize, out_trivial_loop_ok); + set_ufunc_loop_data_types(self, op, out_dtype, types); /* Save the inner loop and its data */ *out_innerloop = self->functions[i]; @@ -1494,10 +1501,6 @@ find_best_ufunc_inner_loop(PyUFuncObject *self, /* * Does a linear search for the inner loop of the ufunc specified by type_tup. - * When op[i] is a scalar or a one dimensional array smaller than - * the buffersize, and needs a dtype conversion, this function - * may substitute op[i] with a version cast to the correct type. This way, - * the later trivial loop detection has a higher chance of being triggered. * * Note that if an error is returned, the caller must free the non-zero * references in out_dtype. This function does not do its own clean-up. @@ -1507,12 +1510,10 @@ find_specified_ufunc_inner_loop(PyUFuncObject *self, PyObject *type_tup, PyArrayObject **op, NPY_CASTING casting, - npy_intp buffersize, int any_object, PyArray_Descr **out_dtype, PyUFuncGenericFunction *out_innerloop, - void **out_innerloopdata, - int *out_trivial_loop_ok) + void **out_innerloopdata) { npy_intp i, j, n, nin = self->nin, nop = nin + self->nout; int n_specified = 0; @@ -1619,9 +1620,8 @@ find_specified_ufunc_inner_loop(PyUFuncObject *self, switch (find_ufunc_specified_userloop(self, n_specified, specified_types, op, casting, - buffersize, any_object, use_min_scalar, - out_dtype, out_innerloop, out_innerloopdata, - out_trivial_loop_ok)) { + any_object, use_min_scalar, + out_dtype, out_innerloop, out_innerloopdata)) { /* Error */ case -1: return -1; @@ -1673,8 +1673,7 @@ find_specified_ufunc_inner_loop(PyUFuncObject *self, return -1; /* It worked */ case 1: - set_ufunc_loop_data_types(self, op, out_dtype, types, - buffersize, out_trivial_loop_ok); + set_ufunc_loop_data_types(self, op, out_dtype, types); /* Save the inner loop and its data */ *out_innerloop = self->functions[i]; @@ -1706,6 +1705,1219 @@ find_specified_ufunc_inner_loop(PyUFuncObject *self, return -1; } +/*UFUNC_API + * + * This function applies the default type resolution rules + * for the provided ufunc, filling out_dtypes, out_innerloop, + * and out_innerloopdata. + * + * Returns 0 on success, -1 on error. + */ +NPY_NO_EXPORT int +PyUFunc_DefaultTypeResolution(PyUFuncObject *ufunc, + NPY_CASTING casting, + PyArrayObject **operands, + PyObject *type_tup, + PyArray_Descr **out_dtypes, + PyUFuncGenericFunction *out_innerloop, + void **out_innerloopdata) +{ + int i, nop = ufunc->nin + ufunc->nout; + int retval = 0, any_object = 0; + NPY_CASTING input_casting; + + for (i = 0; i < nop; ++i) { + if (operands[i] != NULL && + PyTypeNum_ISOBJECT(PyArray_DESCR(operands[i])->type_num)) { + any_object = 1; + break; + } + } + + /* + * Decide the casting rules for inputs and outputs. We want + * NPY_SAFE_CASTING or stricter, so that the loop selection code + * doesn't choose an integer loop for float inputs, for example. + */ + input_casting = (casting > NPY_SAFE_CASTING) ? NPY_SAFE_CASTING : casting; + + if (type_tup == NULL) { + /* Find the best ufunc inner loop, and fill in the dtypes */ + retval = find_best_ufunc_inner_loop(ufunc, operands, + input_casting, casting, any_object, + out_dtypes, out_innerloop, out_innerloopdata); + } else { + /* Find the specified ufunc inner loop, and fill in the dtypes */ + retval = find_specified_ufunc_inner_loop(ufunc, type_tup, + operands, casting, any_object, out_dtypes, + out_innerloop, out_innerloopdata); + } + + return retval; +} + +/* + * This function applies special type resolution rules for the case + * where all the functions have the pattern XX->bool, using + * PyArray_ResultType instead of a linear search to get the best + * loop. + * + * Note that a simpler linear search through the functions loop + * is still done, but switching to a simple array lookup for + * built-in types would be better at some point. + * + * Returns 0 on success, -1 on error. + */ +NPY_NO_EXPORT int +PyUFunc_SimpleBinaryComparisonTypeResolution(PyUFuncObject *ufunc, + NPY_CASTING casting, + PyArrayObject **operands, + PyObject *type_tup, + PyArray_Descr **out_dtypes, + PyUFuncGenericFunction *out_innerloop, + void **out_innerloopdata) +{ + int i, type_num, type_num1, type_num2; + char *ufunc_name; + + ufunc_name = ufunc->name ? ufunc->name : "<unnamed ufunc>"; + + if (ufunc->nin != 2 || ufunc->nout != 1) { + PyErr_Format(PyExc_RuntimeError, "ufunc %s is configured " + "to use binary comparison type resolution but has " + "the wrong number of inputs or outputs", + ufunc_name); + return -1; + } + + /* + * Use the default type resolution if there's a custom data type + * or object arrays. + */ + type_num1 = PyArray_DESCR(operands[0])->type_num; + type_num2 = PyArray_DESCR(operands[1])->type_num; + if (type_num1 >= NPY_NTYPES || type_num2 >= NPY_NTYPES || + type_num1 == NPY_OBJECT || type_num2 == NPY_OBJECT) { + return PyUFunc_DefaultTypeResolution(ufunc, casting, operands, + type_tup, out_dtypes, out_innerloop, out_innerloopdata); + } + + if (type_tup == NULL) { + /* Input types are the result type */ + out_dtypes[0] = PyArray_ResultType(2, operands, 0, NULL); + if (out_dtypes[0] == NULL) { + return -1; + } + out_dtypes[1] = out_dtypes[0]; + Py_INCREF(out_dtypes[1]); + } + else { + /* + * If the type tuple isn't a single-element tuple, let the + * default type resolution handle this one. + */ + if (!PyTuple_Check(type_tup) || PyTuple_GET_SIZE(type_tup) != 1) { + return PyUFunc_DefaultTypeResolution(ufunc, casting, operands, + type_tup, out_dtypes, out_innerloop, out_innerloopdata); + } + + if (!PyArray_DescrCheck(PyTuple_GET_ITEM(type_tup, 0))) { + PyErr_SetString(PyExc_ValueError, + "require data type in the type tuple"); + return -1; + } + + out_dtypes[0] = (PyArray_Descr *)PyTuple_GET_ITEM(type_tup, 0); + out_dtypes[1] = out_dtypes[0]; + Py_INCREF(out_dtypes[0]); + Py_INCREF(out_dtypes[1]); + } + + /* Output type is always boolean */ + out_dtypes[2] = PyArray_DescrFromType(NPY_BOOL); + if (out_dtypes[2] == NULL) { + for (i = 0; i < 2; ++i) { + Py_DECREF(out_dtypes[i]); + out_dtypes[i] = NULL; + } + return -1; + } + + /* Check against the casting rules */ + if (PyUFunc_ValidateCasting(ufunc, casting, operands, out_dtypes) < 0) { + for (i = 0; i < 3; ++i) { + Py_DECREF(out_dtypes[i]); + out_dtypes[i] = NULL; + } + return -1; + } + + type_num = out_dtypes[0]->type_num; + + /* If we have a built-in type, search in the functions list */ + if (type_num < NPY_NTYPES) { + char *types = ufunc->types; + int n = ufunc->ntypes; + + for (i = 0; i < n; ++i) { + if (types[3*i] == type_num) { + *out_innerloop = ufunc->functions[i]; + *out_innerloopdata = ufunc->data[i]; + return 0; + } + } + + PyErr_Format(PyExc_TypeError, + "ufunc '%s' not supported for the input types", + ufunc_name); + return -1; + } + else { + PyErr_SetString(PyExc_RuntimeError, + "user type shouldn't have resulted from type promotion"); + return -1; + } +} + +/* + * This function applies special type resolution rules for the case + * where all the functions have the pattern X->X, copying + * the input descr directly so that metadata is maintained. + * + * Note that a simpler linear search through the functions loop + * is still done, but switching to a simple array lookup for + * built-in types would be better at some point. + * + * Returns 0 on success, -1 on error. + */ +NPY_NO_EXPORT int +PyUFunc_SimpleUnaryOperationTypeResolution(PyUFuncObject *ufunc, + NPY_CASTING casting, + PyArrayObject **operands, + PyObject *type_tup, + PyArray_Descr **out_dtypes, + PyUFuncGenericFunction *out_innerloop, + void **out_innerloopdata) +{ + int i, type_num, type_num1; + char *ufunc_name; + + ufunc_name = ufunc->name ? ufunc->name : "<unnamed ufunc>"; + + if (ufunc->nin != 1 || ufunc->nout != 1) { + PyErr_Format(PyExc_RuntimeError, "ufunc %s is configured " + "to use unary operation type resolution but has " + "the wrong number of inputs or outputs", + ufunc_name); + return -1; + } + + /* + * Use the default type resolution if there's a custom data type + * or object arrays. + */ + type_num1 = PyArray_DESCR(operands[0])->type_num; + if (type_num1 >= NPY_NTYPES || type_num1 == NPY_OBJECT) { + return PyUFunc_DefaultTypeResolution(ufunc, casting, operands, + type_tup, out_dtypes, out_innerloop, out_innerloopdata); + } + + if (type_tup == NULL) { + /* Input types are the result type */ + out_dtypes[0] = PyArray_DESCR(operands[0]); + Py_INCREF(out_dtypes[0]); + out_dtypes[1] = out_dtypes[0]; + Py_INCREF(out_dtypes[1]); + } + else { + /* + * If the type tuple isn't a single-element tuple, let the + * default type resolution handle this one. + */ + if (!PyTuple_Check(type_tup) || PyTuple_GET_SIZE(type_tup) != 1) { + return PyUFunc_DefaultTypeResolution(ufunc, casting, operands, + type_tup, out_dtypes, out_innerloop, out_innerloopdata); + } + + if (!PyArray_DescrCheck(PyTuple_GET_ITEM(type_tup, 0))) { + PyErr_SetString(PyExc_ValueError, + "require data type in the type tuple"); + return -1; + } + + out_dtypes[0] = (PyArray_Descr *)PyTuple_GET_ITEM(type_tup, 0); + Py_INCREF(out_dtypes[0]); + out_dtypes[1] = out_dtypes[0]; + Py_INCREF(out_dtypes[1]); + } + + /* Check against the casting rules */ + if (PyUFunc_ValidateCasting(ufunc, casting, operands, out_dtypes) < 0) { + for (i = 0; i < 2; ++i) { + Py_DECREF(out_dtypes[i]); + out_dtypes[i] = NULL; + } + return -1; + } + + type_num = out_dtypes[0]->type_num; + + /* If we have a built-in type, search in the functions list */ + if (type_num < NPY_NTYPES) { + char *types = ufunc->types; + int n = ufunc->ntypes; + + for (i = 0; i < n; ++i) { + if (types[2*i] == type_num) { + *out_innerloop = ufunc->functions[i]; + *out_innerloopdata = ufunc->data[i]; + return 0; + } + } + + PyErr_Format(PyExc_TypeError, + "ufunc '%s' not supported for the input types", + ufunc_name); + return -1; + } + else { + PyErr_SetString(PyExc_RuntimeError, + "user type shouldn't have resulted from type promotion"); + return -1; + } +} + +/* + * This function applies special type resolution rules for the case + * where all the functions have the pattern XX->X, using + * PyArray_ResultType instead of a linear search to get the best + * loop. + * + * Note that a simpler linear search through the functions loop + * is still done, but switching to a simple array lookup for + * built-in types would be better at some point. + * + * Returns 0 on success, -1 on error. + */ +NPY_NO_EXPORT int +PyUFunc_SimpleBinaryOperationTypeResolution(PyUFuncObject *ufunc, + NPY_CASTING casting, + PyArrayObject **operands, + PyObject *type_tup, + PyArray_Descr **out_dtypes, + PyUFuncGenericFunction *out_innerloop, + void **out_innerloopdata) +{ + int i, type_num, type_num1, type_num2; + char *ufunc_name; + + ufunc_name = ufunc->name ? ufunc->name : "<unnamed ufunc>"; + + if (ufunc->nin != 2 || ufunc->nout != 1) { + PyErr_Format(PyExc_RuntimeError, "ufunc %s is configured " + "to use binary operation type resolution but has " + "the wrong number of inputs or outputs", + ufunc_name); + return -1; + } + + /* + * Use the default type resolution if there's a custom data type + * or object arrays. + */ + type_num1 = PyArray_DESCR(operands[0])->type_num; + type_num2 = PyArray_DESCR(operands[1])->type_num; + if (type_num1 >= NPY_NTYPES || type_num2 >= NPY_NTYPES || + type_num1 == NPY_OBJECT || type_num2 == NPY_OBJECT) { + return PyUFunc_DefaultTypeResolution(ufunc, casting, operands, + type_tup, out_dtypes, out_innerloop, out_innerloopdata); + } + + if (type_tup == NULL) { + /* Input types are the result type */ + out_dtypes[0] = PyArray_ResultType(2, operands, 0, NULL); + if (out_dtypes[0] == NULL) { + return -1; + } + out_dtypes[1] = out_dtypes[0]; + Py_INCREF(out_dtypes[1]); + out_dtypes[2] = out_dtypes[0]; + Py_INCREF(out_dtypes[2]); + } + else { + /* + * If the type tuple isn't a single-element tuple, let the + * default type resolution handle this one. + */ + if (!PyTuple_Check(type_tup) || PyTuple_GET_SIZE(type_tup) != 1) { + return PyUFunc_DefaultTypeResolution(ufunc, casting, operands, + type_tup, out_dtypes, out_innerloop, out_innerloopdata); + } + + if (!PyArray_DescrCheck(PyTuple_GET_ITEM(type_tup, 0))) { + PyErr_SetString(PyExc_ValueError, + "require data type in the type tuple"); + return -1; + } + + out_dtypes[0] = (PyArray_Descr *)PyTuple_GET_ITEM(type_tup, 0); + Py_INCREF(out_dtypes[0]); + out_dtypes[1] = out_dtypes[0]; + Py_INCREF(out_dtypes[1]); + out_dtypes[2] = out_dtypes[0]; + Py_INCREF(out_dtypes[2]); + } + + /* Check against the casting rules */ + if (PyUFunc_ValidateCasting(ufunc, casting, operands, out_dtypes) < 0) { + for (i = 0; i < 3; ++i) { + Py_DECREF(out_dtypes[i]); + out_dtypes[i] = NULL; + } + return -1; + } + + type_num = out_dtypes[0]->type_num; + + /* If we have a built-in type, search in the functions list */ + if (type_num < NPY_NTYPES) { + char *types = ufunc->types; + int n = ufunc->ntypes; + + for (i = 0; i < n; ++i) { + if (types[3*i] == type_num) { + *out_innerloop = ufunc->functions[i]; + *out_innerloopdata = ufunc->data[i]; + return 0; + } + } + + PyErr_Format(PyExc_TypeError, + "ufunc '%s' not supported for the input types", + ufunc_name); + return -1; + } + else { + PyErr_SetString(PyExc_RuntimeError, + "user type shouldn't have resulted from type promotion"); + return -1; + } +} + +/* + * This function applies special type resolution rules for the absolute + * ufunc. This ufunc converts complex -> float, so isn't covered + * by the simple unary type resolution. + * + * Returns 0 on success, -1 on error. + */ +NPY_NO_EXPORT int +PyUFunc_AbsoluteTypeResolution(PyUFuncObject *ufunc, + NPY_CASTING casting, + PyArrayObject **operands, + PyObject *type_tup, + PyArray_Descr **out_dtypes, + PyUFuncGenericFunction *out_innerloop, + void **out_innerloopdata) +{ + /* Use the default for complex types, to find the loop producing float */ + if (PyTypeNum_ISCOMPLEX(PyArray_DESCR(operands[0])->type_num)) { + return PyUFunc_DefaultTypeResolution(ufunc, casting, operands, + type_tup, out_dtypes, out_innerloop, out_innerloopdata); + } + else { + return PyUFunc_SimpleUnaryOperationTypeResolution(ufunc, casting, + operands, type_tup, out_dtypes, out_innerloop, + out_innerloopdata); + } +} + + +/* + * This function returns the a new reference to the + * capsule with the datetime metadata. + * + * NOTE: This function is copied from datetime.c in multiarray, + * because umath and multiarray are not linked together. + */ +static PyObject * +get_datetime_metacobj_from_dtype(PyArray_Descr *dtype) +{ + PyObject *metacobj; + + /* Check that the dtype has metadata */ + if (dtype->metadata == NULL) { + PyErr_SetString(PyExc_TypeError, + "Datetime type object is invalid, lacks metadata"); + return NULL; + } + + /* Check that the dtype has unit metadata */ + metacobj = PyDict_GetItemString(dtype->metadata, NPY_METADATA_DTSTR); + if (metacobj == NULL) { + PyErr_SetString(PyExc_TypeError, + "Datetime type object is invalid, lacks unit metadata"); + return NULL; + } + + Py_INCREF(metacobj); + return metacobj; +} + +/* + * This function returns a pointer to the DateTimeMetaData + * contained within the provided datetime dtype. + * + * NOTE: This function is copied from datetime.c in multiarray, + * because umath and multiarray are not linked together. + */ +static PyArray_DatetimeMetaData * +get_datetime_metadata_from_dtype(PyArray_Descr *dtype) +{ + PyObject *metacobj; + PyArray_DatetimeMetaData *meta = NULL; + + metacobj = get_datetime_metacobj_from_dtype(dtype); + if (metacobj == NULL) { + return NULL; + } + + /* Check that the dtype has an NpyCapsule for the metadata */ + meta = (PyArray_DatetimeMetaData *)NpyCapsule_AsVoidPtr(metacobj); + if (meta == NULL) { + PyErr_SetString(PyExc_TypeError, + "Datetime type object is invalid, unit metadata is corrupt"); + return NULL; + } + + return meta; +} + +/* + * Creates a new NPY_TIMEDELTA dtype, copying the datetime metadata + * from the given dtype. + * + * NOTE: This function is copied from datetime.c in multiarray, + * because umath and multiarray are not linked together. + */ +static PyArray_Descr * +timedelta_dtype_with_copied_meta(PyArray_Descr *dtype) +{ + PyArray_Descr *ret; + PyObject *metacobj; + + ret = PyArray_DescrNewFromType(NPY_TIMEDELTA); + if (ret == NULL) { + return NULL; + } + Py_XDECREF(ret->metadata); + ret->metadata = PyDict_New(); + if (ret->metadata == NULL) { + Py_DECREF(ret); + return NULL; + } + + metacobj = get_datetime_metacobj_from_dtype(dtype); + if (metacobj == NULL) { + Py_DECREF(ret); + return NULL; + } + + if (PyDict_SetItemString(ret->metadata, NPY_METADATA_DTSTR, + metacobj) < 0) { + Py_DECREF(metacobj); + Py_DECREF(ret); + return NULL; + } + + return ret; +} + + + +/* + * This function applies the type resolution rules for addition. + * In particular, there are a number of special cases with datetime: + * m8[<A>] + m8[<B>] => m8[gcd(<A>,<B>)] + m8[gcd(<A>,<B>)] + * m8[<A>] + int => m8[<A>] + m8[<A>] + * int + m8[<A>] => m8[<A>] + m8[<A>] + * M8[<A>] + int => M8[<A>] + m8[<A>] + * int + M8[<A>] => m8[<A>] + M8[<A>] + * M8[<A>] + m8[<B>] => M8[<A>] + m8[<A>] + * m8[<A>] + M8[<B>] => m8[<B>] + M8[<B>] + * TODO: Non-linear time unit cases require highly special-cased loops + * M8[<A>] + m8[Y|M|B] + * m8[Y|M|B] + M8[<A>] + */ +NPY_NO_EXPORT int +PyUFunc_AdditionTypeResolution(PyUFuncObject *ufunc, + NPY_CASTING casting, + PyArrayObject **operands, + PyObject *type_tup, + PyArray_Descr **out_dtypes, + PyUFuncGenericFunction *out_innerloop, + void **out_innerloopdata) +{ + int type_num1, type_num2; + char *types; + int i, n; + char *ufunc_name; + + ufunc_name = ufunc->name ? ufunc->name : "<unnamed ufunc>"; + + type_num1 = PyArray_DESCR(operands[0])->type_num; + type_num2 = PyArray_DESCR(operands[1])->type_num; + + /* Use the default when datetime and timedelta are not involved */ + if (!PyTypeNum_ISDATETIME(type_num1) && !PyTypeNum_ISDATETIME(type_num2)) { + return PyUFunc_DefaultTypeResolution(ufunc, casting, operands, + type_tup, out_dtypes, out_innerloop, out_innerloopdata); + } + + if (type_num1 == NPY_TIMEDELTA) { + /* m8[<A>] + m8[<B>] => m8[gcd(<A>,<B>)] + m8[gcd(<A>,<B>)] */ + if (type_num2 == NPY_TIMEDELTA) { + out_dtypes[0] = PyArray_PromoteTypes(PyArray_DESCR(operands[0]), + PyArray_DESCR(operands[1])); + if (out_dtypes[0] == NULL) { + return -1; + } + out_dtypes[1] = out_dtypes[0]; + Py_INCREF(out_dtypes[1]); + out_dtypes[2] = out_dtypes[0]; + Py_INCREF(out_dtypes[2]); + } + /* m8[<A>] + M8[<B>] => m8[<B>] + M8[<B>] */ + else if (type_num2 == NPY_DATETIME) { + /* Make a new NPY_TIMEDELTA, and copy type2's metadata */ + out_dtypes[0] = timedelta_dtype_with_copied_meta( + PyArray_DESCR(operands[1])); + if (out_dtypes[0] == NULL) { + return -1; + } + out_dtypes[1] = PyArray_DESCR(operands[1]); + Py_INCREF(out_dtypes[1]); + out_dtypes[2] = out_dtypes[1]; + Py_INCREF(out_dtypes[2]); + } + /* m8[<A>] + int => m8[<A>] + m8[<A>] */ + else if (PyTypeNum_ISINTEGER(type_num2) || + PyTypeNum_ISBOOL(type_num2)) { + out_dtypes[0] = PyArray_DESCR(operands[0]); + Py_INCREF(out_dtypes[0]); + out_dtypes[1] = out_dtypes[0]; + Py_INCREF(out_dtypes[1]); + out_dtypes[2] = out_dtypes[0]; + Py_INCREF(out_dtypes[2]); + + type_num2 = NPY_TIMEDELTA; + } + else { + goto type_reso_error; + } + } + else if (type_num1 == NPY_DATETIME) { + /* M8[<A>] + m8[<B>] => M8[<A>] + m8[<A>] */ + /* M8[<A>] + int => M8[<A>] + m8[<A>] */ + if (type_num2 == NPY_TIMEDELTA || + PyTypeNum_ISINTEGER(type_num2) || + PyTypeNum_ISBOOL(type_num2)) { + /* Make a new NPY_TIMEDELTA, and copy type1's metadata */ + out_dtypes[1] = timedelta_dtype_with_copied_meta( + PyArray_DESCR(operands[0])); + if (out_dtypes[1] == NULL) { + return -1; + } + out_dtypes[0] = PyArray_DESCR(operands[0]); + Py_INCREF(out_dtypes[0]); + out_dtypes[2] = out_dtypes[0]; + Py_INCREF(out_dtypes[2]); + + type_num2 = NPY_TIMEDELTA; + } + else { + goto type_reso_error; + } + } + else if (PyTypeNum_ISINTEGER(type_num1) || PyTypeNum_ISBOOL(type_num1)) { + /* int + m8[<A>] => m8[<A>] + m8[<A>] */ + if (type_num2 == NPY_TIMEDELTA) { + out_dtypes[0] = PyArray_DESCR(operands[1]); + Py_INCREF(out_dtypes[0]); + out_dtypes[1] = out_dtypes[0]; + Py_INCREF(out_dtypes[1]); + out_dtypes[2] = out_dtypes[0]; + Py_INCREF(out_dtypes[2]); + + type_num1 = NPY_TIMEDELTA; + } + else if (type_num2 == NPY_DATETIME) { + /* Make a new NPY_TIMEDELTA, and copy type2's metadata */ + out_dtypes[0] = timedelta_dtype_with_copied_meta( + PyArray_DESCR(operands[1])); + if (out_dtypes[0] == NULL) { + return -1; + } + out_dtypes[1] = PyArray_DESCR(operands[1]); + Py_INCREF(out_dtypes[1]); + out_dtypes[2] = out_dtypes[1]; + Py_INCREF(out_dtypes[2]); + + type_num1 = NPY_TIMEDELTA; + } + else { + goto type_reso_error; + } + } + else { + goto type_reso_error; + } + + /* Check against the casting rules */ + if (PyUFunc_ValidateCasting(ufunc, casting, operands, out_dtypes) < 0) { + for (i = 0; i < 3; ++i) { + Py_DECREF(out_dtypes[i]); + out_dtypes[i] = NULL; + } + return -1; + } + + /* Search in the functions list */ + types = ufunc->types; + n = ufunc->ntypes; + + for (i = 0; i < n; ++i) { + if (types[3*i] == type_num1 && types[3*i+1] == type_num2) { + *out_innerloop = ufunc->functions[i]; + *out_innerloopdata = ufunc->data[i]; + return 0; + } + } + + PyErr_Format(PyExc_TypeError, + "internal error: could not find appropriate datetime " + "inner loop in %s ufunc", ufunc_name); + return -1; + +type_reso_error: { + PyObject *errmsg; + errmsg = PyUString_FromFormat("ufunc %s cannot use operands " + "with types ", ufunc_name); + PyUString_ConcatAndDel(&errmsg, + PyObject_Repr((PyObject *)PyArray_DESCR(operands[0]))); + PyUString_ConcatAndDel(&errmsg, + PyUString_FromString(" and ")); + PyUString_ConcatAndDel(&errmsg, + PyObject_Repr((PyObject *)PyArray_DESCR(operands[1]))); + PyErr_SetObject(PyExc_TypeError, errmsg); + return -1; + } +} + +/* + * This function applies the type resolution rules for subtraction. + * In particular, there are a number of special cases with datetime: + * m8[<A>] - m8[<B>] => m8[gcd(<A>,<B>)] - m8[gcd(<A>,<B>)] + * m8[<A>] - int => m8[<A>] - m8[<A>] + * int - m8[<A>] => m8[<A>] - m8[<A>] + * M8[<A>] - int => M8[<A>] - m8[<A>] + * M8[<A>] - m8[<B>] => M8[<A>] - m8[<A>] + * TODO: Non-linear time unit cases require highly special-cased loops + * M8[<A>] - m8[Y|M|B] + */ +NPY_NO_EXPORT int +PyUFunc_SubtractionTypeResolution(PyUFuncObject *ufunc, + NPY_CASTING casting, + PyArrayObject **operands, + PyObject *type_tup, + PyArray_Descr **out_dtypes, + PyUFuncGenericFunction *out_innerloop, + void **out_innerloopdata) +{ + int type_num1, type_num2; + char *types; + int i, n; + char *ufunc_name; + + ufunc_name = ufunc->name ? ufunc->name : "<unnamed ufunc>"; + + type_num1 = PyArray_DESCR(operands[0])->type_num; + type_num2 = PyArray_DESCR(operands[1])->type_num; + + /* Use the default when datetime and timedelta are not involved */ + if (!PyTypeNum_ISDATETIME(type_num1) && !PyTypeNum_ISDATETIME(type_num2)) { + return PyUFunc_DefaultTypeResolution(ufunc, casting, operands, + type_tup, out_dtypes, out_innerloop, out_innerloopdata); + } + + if (type_num1 == NPY_TIMEDELTA) { + /* m8[<A>] - m8[<B>] => m8[gcd(<A>,<B>)] - m8[gcd(<A>,<B>)] */ + if (type_num2 == NPY_TIMEDELTA) { + out_dtypes[0] = PyArray_PromoteTypes(PyArray_DESCR(operands[0]), + PyArray_DESCR(operands[1])); + if (out_dtypes[0] == NULL) { + return -1; + } + out_dtypes[1] = out_dtypes[0]; + Py_INCREF(out_dtypes[1]); + out_dtypes[2] = out_dtypes[0]; + Py_INCREF(out_dtypes[2]); + } + /* m8[<A>] - int => m8[<A>] - m8[<A>] */ + else if (PyTypeNum_ISINTEGER(type_num2) || + PyTypeNum_ISBOOL(type_num2)) { + out_dtypes[0] = PyArray_DESCR(operands[0]); + Py_INCREF(out_dtypes[0]); + out_dtypes[1] = out_dtypes[0]; + Py_INCREF(out_dtypes[1]); + out_dtypes[2] = out_dtypes[0]; + Py_INCREF(out_dtypes[2]); + + type_num2 = NPY_TIMEDELTA; + } + else { + goto type_reso_error; + } + } + else if (type_num1 == NPY_DATETIME) { + /* M8[<A>] - m8[<B>] => M8[<A>] - m8[<A>] */ + /* M8[<A>] - int => M8[<A>] - m8[<A>] */ + if (type_num2 == NPY_TIMEDELTA || + PyTypeNum_ISINTEGER(type_num2) || + PyTypeNum_ISBOOL(type_num2)) { + /* Make a new NPY_TIMEDELTA, and copy type1's metadata */ + out_dtypes[1] = timedelta_dtype_with_copied_meta( + PyArray_DESCR(operands[0])); + if (out_dtypes[1] == NULL) { + return -1; + } + out_dtypes[0] = PyArray_DESCR(operands[0]); + Py_INCREF(out_dtypes[0]); + out_dtypes[2] = out_dtypes[0]; + Py_INCREF(out_dtypes[2]); + + type_num2 = NPY_TIMEDELTA; + } + /* M8[<A>] - M8[<A>] (producing m8[<A>])*/ + else if (type_num2 == NPY_DATETIME) { + PyArray_DatetimeMetaData *meta1, *meta2; + + meta1 = get_datetime_metadata_from_dtype( + PyArray_DESCR(operands[0])); + if (meta1 == NULL) { + return -1; + } + meta2 = get_datetime_metadata_from_dtype( + PyArray_DESCR(operands[1])); + if (meta2 == NULL) { + return -1; + } + + /* If the metadata matches up, the subtraction is ok */ + if (meta1->num == meta2->num && + meta1->base == meta2->base && + meta1->events == meta2->events) { + out_dtypes[0] = PyArray_DESCR(operands[1]); + Py_INCREF(out_dtypes[0]); + out_dtypes[1] = out_dtypes[0]; + Py_INCREF(out_dtypes[1]); + /* Make a new NPY_TIMEDELTA, and copy type1's metadata */ + out_dtypes[2] = timedelta_dtype_with_copied_meta( + PyArray_DESCR(operands[0])); + if (out_dtypes[2] == NULL) { + return -1; + } + } + else { + goto type_reso_error; + } + } + else { + goto type_reso_error; + } + } + else if (PyTypeNum_ISINTEGER(type_num1) || PyTypeNum_ISBOOL(type_num1)) { + /* int - m8[<A>] => m8[<A>] - m8[<A>] */ + if (type_num2 == NPY_TIMEDELTA) { + out_dtypes[0] = PyArray_DESCR(operands[1]); + Py_INCREF(out_dtypes[0]); + out_dtypes[1] = out_dtypes[0]; + Py_INCREF(out_dtypes[1]); + out_dtypes[2] = out_dtypes[0]; + Py_INCREF(out_dtypes[2]); + + type_num1 = NPY_TIMEDELTA; + } + else { + goto type_reso_error; + } + } + else { + goto type_reso_error; + } + + /* Check against the casting rules */ + if (PyUFunc_ValidateCasting(ufunc, casting, operands, out_dtypes) < 0) { + for (i = 0; i < 3; ++i) { + Py_DECREF(out_dtypes[i]); + out_dtypes[i] = NULL; + } + return -1; + } + + /* Search in the functions list */ + types = ufunc->types; + n = ufunc->ntypes; + + for (i = 0; i < n; ++i) { + if (types[3*i] == type_num1 && types[3*i+1] == type_num2) { + *out_innerloop = ufunc->functions[i]; + *out_innerloopdata = ufunc->data[i]; + return 0; + } + } + + PyErr_Format(PyExc_TypeError, + "internal error: could not find appropriate datetime " + "inner loop in %s ufunc", ufunc_name); + return -1; + +type_reso_error: { + PyObject *errmsg; + errmsg = PyUString_FromFormat("ufunc %s cannot use operands " + "with types ", ufunc_name); + PyUString_ConcatAndDel(&errmsg, + PyObject_Repr((PyObject *)PyArray_DESCR(operands[0]))); + PyUString_ConcatAndDel(&errmsg, + PyUString_FromString(" and ")); + PyUString_ConcatAndDel(&errmsg, + PyObject_Repr((PyObject *)PyArray_DESCR(operands[1]))); + PyErr_SetObject(PyExc_TypeError, errmsg); + return -1; + } +} + +/* + * This function applies the type resolution rules for multiplication. + * In particular, there are a number of special cases with datetime: + * int## * m8[<A>] => int64 * m8[<A>] + * m8[<A>] * int## => m8[<A>] * int64 + * float## * m8[<A>] => float64 * m8[<A>] + * m8[<A>] * float## => m8[<A>] * float64 + */ +NPY_NO_EXPORT int +PyUFunc_MultiplicationTypeResolution(PyUFuncObject *ufunc, + NPY_CASTING casting, + PyArrayObject **operands, + PyObject *type_tup, + PyArray_Descr **out_dtypes, + PyUFuncGenericFunction *out_innerloop, + void **out_innerloopdata) +{ + int type_num1, type_num2; + char *types; + int i, n; + char *ufunc_name; + + ufunc_name = ufunc->name ? ufunc->name : "<unnamed ufunc>"; + + type_num1 = PyArray_DESCR(operands[0])->type_num; + type_num2 = PyArray_DESCR(operands[1])->type_num; + + /* Use the default when datetime and timedelta are not involved */ + if (!PyTypeNum_ISDATETIME(type_num1) && !PyTypeNum_ISDATETIME(type_num2)) { + return PyUFunc_DefaultTypeResolution(ufunc, casting, operands, + type_tup, out_dtypes, out_innerloop, out_innerloopdata); + } + + if (type_num1 == NPY_TIMEDELTA) { + /* m8[<A>] * int## => m8[<A>] * int64 */ + if (PyTypeNum_ISINTEGER(type_num2) || PyTypeNum_ISBOOL(type_num2)) { + out_dtypes[0] = PyArray_DESCR(operands[0]); + Py_INCREF(out_dtypes[0]); + out_dtypes[1] = PyArray_DescrNewFromType(NPY_INT64); + if (out_dtypes[1] == NULL) { + Py_DECREF(out_dtypes[0]); + out_dtypes[0] = NULL; + return -1; + } + out_dtypes[2] = out_dtypes[0]; + Py_INCREF(out_dtypes[2]); + + type_num2 = NPY_INT64; + } + /* m8[<A>] * float## => m8[<A>] * float64 */ + else if (PyTypeNum_ISFLOAT(type_num2)) { + out_dtypes[0] = PyArray_DESCR(operands[0]); + Py_INCREF(out_dtypes[0]); + out_dtypes[1] = PyArray_DescrNewFromType(NPY_DOUBLE); + if (out_dtypes[1] == NULL) { + Py_DECREF(out_dtypes[0]); + out_dtypes[0] = NULL; + return -1; + } + out_dtypes[2] = out_dtypes[0]; + Py_INCREF(out_dtypes[2]); + + type_num2 = NPY_DOUBLE; + } + else { + goto type_reso_error; + } + } + else if (PyTypeNum_ISINTEGER(type_num1) || PyTypeNum_ISBOOL(type_num1)) { + /* int## * m8[<A>] => int64 * m8[<A>] */ + if (type_num2 == NPY_TIMEDELTA) { + out_dtypes[0] = PyArray_DescrNewFromType(NPY_INT64); + if (out_dtypes[0] == NULL) { + return -1; + } + out_dtypes[1] = PyArray_DESCR(operands[1]); + Py_INCREF(out_dtypes[1]); + out_dtypes[2] = out_dtypes[1]; + Py_INCREF(out_dtypes[2]); + + type_num1 = NPY_INT64; + } + else { + goto type_reso_error; + } + } + else if (PyTypeNum_ISFLOAT(type_num1)) { + /* float## * m8[<A>] => float64 * m8[<A>] */ + if (type_num2 == NPY_TIMEDELTA) { + out_dtypes[0] = PyArray_DescrNewFromType(NPY_DOUBLE); + if (out_dtypes[0] == NULL) { + return -1; + } + out_dtypes[1] = PyArray_DESCR(operands[1]); + Py_INCREF(out_dtypes[1]); + out_dtypes[2] = out_dtypes[1]; + Py_INCREF(out_dtypes[2]); + + type_num1 = NPY_DOUBLE; + } + else { + goto type_reso_error; + } + } + else { + goto type_reso_error; + } + + /* Check against the casting rules */ + if (PyUFunc_ValidateCasting(ufunc, casting, operands, out_dtypes) < 0) { + for (i = 0; i < 3; ++i) { + Py_DECREF(out_dtypes[i]); + out_dtypes[i] = NULL; + } + return -1; + } + + /* Search in the functions list */ + types = ufunc->types; + n = ufunc->ntypes; + + for (i = 0; i < n; ++i) { + if (types[3*i] == type_num1 && types[3*i+1] == type_num2) { + *out_innerloop = ufunc->functions[i]; + *out_innerloopdata = ufunc->data[i]; + return 0; + } + } + + PyErr_Format(PyExc_TypeError, + "internal error: could not find appropriate datetime " + "inner loop in %s ufunc", ufunc_name); + return -1; + +type_reso_error: { + PyObject *errmsg; + errmsg = PyUString_FromFormat("ufunc %s cannot use operands " + "with types ", ufunc_name); + PyUString_ConcatAndDel(&errmsg, + PyObject_Repr((PyObject *)PyArray_DESCR(operands[0]))); + PyUString_ConcatAndDel(&errmsg, + PyUString_FromString(" and ")); + PyUString_ConcatAndDel(&errmsg, + PyObject_Repr((PyObject *)PyArray_DESCR(operands[1]))); + PyErr_SetObject(PyExc_TypeError, errmsg); + return -1; + } +} + +/* + * This function applies the type resolution rules for division. + * In particular, there are a number of special cases with datetime: + * m8[<A>] / int## => m8[<A>] / int64 + * m8[<A>] / float## => m8[<A>] / float64 + */ +NPY_NO_EXPORT int +PyUFunc_DivisionTypeResolution(PyUFuncObject *ufunc, + NPY_CASTING casting, + PyArrayObject **operands, + PyObject *type_tup, + PyArray_Descr **out_dtypes, + PyUFuncGenericFunction *out_innerloop, + void **out_innerloopdata) +{ + int type_num1, type_num2; + char *types; + int i, n; + char *ufunc_name; + + ufunc_name = ufunc->name ? ufunc->name : "<unnamed ufunc>"; + + type_num1 = PyArray_DESCR(operands[0])->type_num; + type_num2 = PyArray_DESCR(operands[1])->type_num; + + /* Use the default when datetime and timedelta are not involved */ + if (!PyTypeNum_ISDATETIME(type_num1) && !PyTypeNum_ISDATETIME(type_num2)) { + return PyUFunc_DefaultTypeResolution(ufunc, casting, operands, + type_tup, out_dtypes, out_innerloop, out_innerloopdata); + } + + if (type_num1 == NPY_TIMEDELTA) { + /* m8[<A>] / int## => m8[<A>] / int64 */ + if (PyTypeNum_ISINTEGER(type_num2)) { + out_dtypes[0] = PyArray_DESCR(operands[0]); + Py_INCREF(out_dtypes[0]); + out_dtypes[1] = PyArray_DescrNewFromType(NPY_INT64); + if (out_dtypes[1] == NULL) { + Py_DECREF(out_dtypes[0]); + out_dtypes[0] = NULL; + return -1; + } + out_dtypes[2] = out_dtypes[0]; + Py_INCREF(out_dtypes[2]); + + type_num2 = NPY_INT64; + } + /* m8[<A>] / float## => m8[<A>] / float64 */ + else if (PyTypeNum_ISFLOAT(type_num2)) { + out_dtypes[0] = PyArray_DESCR(operands[0]); + Py_INCREF(out_dtypes[0]); + out_dtypes[1] = PyArray_DescrNewFromType(NPY_DOUBLE); + if (out_dtypes[1] == NULL) { + Py_DECREF(out_dtypes[0]); + out_dtypes[0] = NULL; + return -1; + } + out_dtypes[2] = out_dtypes[0]; + Py_INCREF(out_dtypes[2]); + + type_num2 = NPY_DOUBLE; + } + else { + goto type_reso_error; + } + } + else { + goto type_reso_error; + } + + /* Check against the casting rules */ + if (PyUFunc_ValidateCasting(ufunc, casting, operands, out_dtypes) < 0) { + for (i = 0; i < 3; ++i) { + Py_DECREF(out_dtypes[i]); + out_dtypes[i] = NULL; + } + return -1; + } + + /* Search in the functions list */ + types = ufunc->types; + n = ufunc->ntypes; + + for (i = 0; i < n; ++i) { + if (types[3*i] == type_num1 && types[3*i+1] == type_num2) { + *out_innerloop = ufunc->functions[i]; + *out_innerloopdata = ufunc->data[i]; + return 0; + } + } + + PyErr_Format(PyExc_TypeError, + "internal error: could not find appropriate datetime " + "inner loop in %s ufunc", ufunc_name); + return -1; + +type_reso_error: { + PyObject *errmsg; + errmsg = PyUString_FromFormat("ufunc %s cannot use operands " + "with types ", ufunc_name); + PyUString_ConcatAndDel(&errmsg, + PyObject_Repr((PyObject *)PyArray_DESCR(operands[0]))); + PyUString_ConcatAndDel(&errmsg, + PyUString_FromString(" and ")); + PyUString_ConcatAndDel(&errmsg, + PyObject_Repr((PyObject *)PyArray_DESCR(operands[1]))); + PyErr_SetObject(PyExc_TypeError, errmsg); + return -1; + } +} + +/*UFUNC_API + * + * Validates that the input operands can be cast to + * the input types, and the output types can be cast to + * the output operands where provided. + * + * Returns 0 on success, -1 (with exception raised) on validation failure. + */ +NPY_NO_EXPORT int +PyUFunc_ValidateCasting(PyUFuncObject *ufunc, + NPY_CASTING casting, + PyArrayObject **operands, + PyArray_Descr **dtypes) +{ + int i, nin = ufunc->nin, nop = nin + ufunc->nout; + char *ufunc_name; + + ufunc_name = ufunc->name ? ufunc->name : "<unnamed ufunc>"; + + for (i = 0; i < nop; ++i) { + if (i < nin) { + if (!PyArray_CanCastArrayTo(operands[i], dtypes[i], casting)) { + PyObject *errmsg; + errmsg = PyUString_FromFormat("Cannot cast ufunc %s " + "input from ", ufunc_name); + PyUString_ConcatAndDel(&errmsg, + PyObject_Repr((PyObject *)PyArray_DESCR(operands[i]))); + PyUString_ConcatAndDel(&errmsg, + PyUString_FromString(" to ")); + PyUString_ConcatAndDel(&errmsg, + PyObject_Repr((PyObject *)dtypes[i])); + PyUString_ConcatAndDel(&errmsg, + PyUString_FromFormat(" with casting rule %s", + _casting_to_string(casting))); + PyErr_SetObject(PyExc_TypeError, errmsg); + return -1; + } + } else if (operands[i] != NULL) { + if (!PyArray_CanCastTypeTo(dtypes[i], + PyArray_DESCR(operands[i]), casting)) { + PyObject *errmsg; + errmsg = PyUString_FromFormat("Cannot cast ufunc %s " + "output from ", ufunc_name); + PyUString_ConcatAndDel(&errmsg, + PyObject_Repr((PyObject *)dtypes[i])); + PyUString_ConcatAndDel(&errmsg, + PyUString_FromString(" to ")); + PyUString_ConcatAndDel(&errmsg, + PyObject_Repr((PyObject *)PyArray_DESCR(operands[i]))); + PyUString_ConcatAndDel(&errmsg, + PyUString_FromFormat(" with casting rule %s", + _casting_to_string(casting))); + PyErr_SetObject(PyExc_TypeError, errmsg); + return -1; + } + } + } + + return 0; +} + static void trivial_two_operand_loop(PyArrayObject **op, PyUFuncGenericFunction innerloop, @@ -2136,8 +3348,7 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *self, int nin, nout; int i, idim, nop; char *ufunc_name; - int retval = -1, any_object = 0, subok = 1; - NPY_CASTING input_casting; + int retval = -1, subok = 1; PyArray_Descr *dtype[NPY_MAXARGS]; @@ -2174,8 +3385,6 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *self, */ PyObject *arr_prep_args = NULL; - int trivial_loop_ok = 0; - NPY_ORDER order = NPY_KEEPORDER; /* * Many things in NumPy do unsafe casting (doing int += float, etc). @@ -2210,7 +3419,7 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *self, /* Get all the arguments */ retval = get_ufunc_arguments(self, args, kwds, - op, &order, &casting, &extobj, &type_tup, &subok, &any_object); + op, &order, &casting, &extobj, &type_tup, &subok); if (retval < 0) { goto fail; } @@ -2292,25 +3501,9 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *self, NPY_UF_DBG_PRINT("Finding inner loop\n"); - /* - * Decide the casting rules for inputs and outputs. We want - * NPY_SAFE_CASTING or stricter, so that the loop selection code - * doesn't choose an integer loop for float inputs, for example. - */ - input_casting = (casting > NPY_SAFE_CASTING) ? NPY_SAFE_CASTING : casting; - if (type_tup == NULL) { - /* Find the best ufunc inner loop, and fill in the dtypes */ - retval = find_best_ufunc_inner_loop(self, op, input_casting, casting, - buffersize, any_object, dtype, - &innerloop, &innerloopdata, &trivial_loop_ok); - } else { - /* Find the specified ufunc inner loop, and fill in the dtypes */ - retval = find_specified_ufunc_inner_loop(self, type_tup, - op, casting, - buffersize, any_object, dtype, - &innerloop, &innerloopdata, &trivial_loop_ok); - } + retval = self->type_resolution_function(self, casting, + op, type_tup, dtype, &innerloop, &innerloopdata); if (retval < 0) { goto fail; } @@ -2408,7 +3601,7 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *self, * Set up the inner strides array. Because we're not doing * buffering, the strides are fixed throughout the looping. */ - inner_strides = (npy_intp *)_pya_malloc( + inner_strides = (npy_intp *)PyArray_malloc( NPY_SIZEOF_INTP * (nop+core_dim_ixs_size)); /* The strides after the first nop match core_dim_ixs */ core_dim_ixs = self->core_dim_ixs; @@ -2504,7 +3697,7 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *self, goto fail; } - _pya_free(inner_strides); + PyArray_free(inner_strides); NpyIter_Deallocate(iter); /* The caller takes ownership of all the references in op */ for (i = 0; i < nop; ++i) { @@ -2522,7 +3715,7 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *self, fail: NPY_UF_DBG_PRINT1("Returning failure code %d\n", retval); if (inner_strides) { - _pya_free(inner_strides); + PyArray_free(inner_strides); } if (iter != NULL) { NpyIter_Deallocate(iter); @@ -2553,8 +3746,7 @@ PyUFunc_GenericFunction(PyUFuncObject *self, int nin, nout; int i, nop; char *ufunc_name; - int retval = -1, any_object = 0, subok = 1; - NPY_CASTING input_casting; + int retval = -1, subok = 1; PyArray_Descr *dtype[NPY_MAXARGS]; @@ -2577,7 +3769,6 @@ PyUFunc_GenericFunction(PyUFuncObject *self, int trivial_loop_ok = 0; - /* TODO: For 1.6, the default should probably be NPY_CORDER */ NPY_ORDER order = NPY_KEEPORDER; /* * Many things in NumPy do unsafe casting (doing int += float, etc). @@ -2593,7 +3784,6 @@ PyUFunc_GenericFunction(PyUFuncObject *self, return -1; } - /* TODO: support generalized ufunc */ if (self->core_enabled) { return PyUFunc_GeneralizedFunction(self, args, kwds, op); } @@ -2617,7 +3807,7 @@ PyUFunc_GenericFunction(PyUFuncObject *self, /* Get all the arguments */ retval = get_ufunc_arguments(self, args, kwds, - op, &order, &casting, &extobj, &type_tup, &subok, &any_object); + op, &order, &casting, &extobj, &type_tup, &subok); if (retval < 0) { goto fail; } @@ -2640,26 +3830,19 @@ PyUFunc_GenericFunction(PyUFuncObject *self, NPY_UF_DBG_PRINT("Finding inner loop\n"); + retval = self->type_resolution_function(self, casting, + op, type_tup, dtype, &innerloop, &innerloopdata); + if (retval < 0) { + goto fail; + } + /* - * Decide the casting rules for inputs and outputs. We want - * NPY_SAFE_CASTING or stricter, so that the loop selection code - * doesn't choose an integer loop for float inputs, for example. + * This checks whether a trivial loop is ok, + * making copies of scalar and one dimensional operands if that will + * help. */ - input_casting = (casting > NPY_SAFE_CASTING) ? NPY_SAFE_CASTING : casting; - - if (type_tup == NULL) { - /* Find the best ufunc inner loop, and fill in the dtypes */ - retval = find_best_ufunc_inner_loop(self, op, input_casting, casting, - buffersize, any_object, dtype, - &innerloop, &innerloopdata, &trivial_loop_ok); - } else { - /* Find the specified ufunc inner loop, and fill in the dtypes */ - retval = find_specified_ufunc_inner_loop(self, type_tup, - op, casting, - buffersize, any_object, dtype, - &innerloop, &innerloopdata, &trivial_loop_ok); - } - if (retval < 0) { + trivial_loop_ok = check_for_trivial_loop(self, op, dtype, buffersize); + if (trivial_loop_ok < 0) { goto fail; } @@ -2681,6 +3864,7 @@ PyUFunc_GenericFunction(PyUFuncObject *self, } } + #if NPY_UF_DBG_TRACING printf("input types:\n"); for (i = 0; i < nin; ++i) { @@ -4177,16 +5361,17 @@ ufunc_generic_call(PyUFuncObject *self, PyObject *args, PyObject *kwds) for(i = 0; i < self->nargs; i++) { mps[i] = NULL; } + errval = PyUFunc_GenericFunction(self, args, kwds, mps); if (errval < 0) { for (i = 0; i < self->nargs; i++) { PyArray_XDECREF_ERR(mps[i]); } - if (errval == -1) + if (errval == -1) { return NULL; + } else if (self->nin == 2 && self->nout == 1) { - /* To allow the other argument to be given a chance - */ + /* To allow the other argument to be given a chance */ Py_INCREF(Py_NotImplemented); return Py_NotImplemented; } @@ -4199,7 +5384,7 @@ ufunc_generic_call(PyUFuncObject *self, PyObject *args, PyObject *kwds) /* Free the input references */ for (i = 0; i < self->nin; i++) { - Py_DECREF(mps[i]); + Py_XDECREF(mps[i]); } /* @@ -4424,7 +5609,7 @@ PyUFunc_FromFuncAndDataAndSignature(PyUFuncGenericFunction *func, void **data, { PyUFuncObject *self; - self = _pya_malloc(sizeof(PyUFuncObject)); + self = PyArray_malloc(sizeof(PyUFuncObject)); if (self == NULL) { return NULL; } @@ -4444,6 +5629,8 @@ PyUFunc_FromFuncAndDataAndSignature(PyUFuncGenericFunction *func, void **data, self->obj = NULL; self->userloops=NULL; + self->type_resolution_function = &PyUFunc_DefaultTypeResolution; + if (name == NULL) { self->name = "?"; } @@ -4484,8 +5671,12 @@ PyUFunc_SetUsesArraysAsData(void **data, size_t i) return 0; } -/* Return 1 if the given data pointer for the loop specifies that it needs the +/* + * Return 1 if the given data pointer for the loop specifies that it needs the * arrays as the data pointer. + * + * NOTE: This is easier to specify with the type_resolution_function + * in the ufunc object. */ static int _does_loop_use_arrays(void *data) @@ -4535,8 +5726,8 @@ _free_loop1d_list(PyUFunc_Loop1d *data) { while (data != NULL) { PyUFunc_Loop1d *next = data->next; - _pya_free(data->arg_types); - _pya_free(data); + PyArray_free(data->arg_types); + PyArray_free(data); data = next; } } @@ -4586,11 +5777,11 @@ PyUFunc_RegisterLoopForType(PyUFuncObject *ufunc, if (key == NULL) { return -1; } - funcdata = _pya_malloc(sizeof(PyUFunc_Loop1d)); + funcdata = PyArray_malloc(sizeof(PyUFunc_Loop1d)); if (funcdata == NULL) { goto fail; } - newtypes = _pya_malloc(sizeof(int)*ufunc->nargs); + newtypes = PyArray_malloc(sizeof(int)*ufunc->nargs); if (newtypes == NULL) { goto fail; } @@ -4645,8 +5836,8 @@ PyUFunc_RegisterLoopForType(PyUFuncObject *ufunc, /* just replace it with new function */ current->func = function; current->data = data; - _pya_free(newtypes); - _pya_free(funcdata); + PyArray_free(newtypes); + PyArray_free(funcdata); } else { /* @@ -4669,8 +5860,8 @@ PyUFunc_RegisterLoopForType(PyUFuncObject *ufunc, fail: Py_DECREF(key); - _pya_free(funcdata); - _pya_free(newtypes); + PyArray_free(funcdata); + PyArray_free(newtypes); if (!PyErr_Occurred()) PyErr_NoMemory(); return -1; } @@ -4682,23 +5873,23 @@ static void ufunc_dealloc(PyUFuncObject *self) { if (self->core_num_dims) { - _pya_free(self->core_num_dims); + PyArray_free(self->core_num_dims); } if (self->core_dim_ixs) { - _pya_free(self->core_dim_ixs); + PyArray_free(self->core_dim_ixs); } if (self->core_offsets) { - _pya_free(self->core_offsets); + PyArray_free(self->core_offsets); } if (self->core_signature) { - _pya_free(self->core_signature); + PyArray_free(self->core_signature); } if (self->ptr) { - _pya_free(self->ptr); + PyArray_free(self->ptr); } Py_XDECREF(self->userloops); Py_XDECREF(self->obj); - _pya_free(self); + PyArray_free(self); } static PyObject * @@ -4956,7 +6147,7 @@ ufunc_get_types(PyUFuncObject *self) if (list == NULL) { return NULL; } - t = _pya_malloc(no+ni+2); + t = PyArray_malloc(no+ni+2); n = 0; for (k = 0; k < nt; k++) { for (j = 0; j<ni; j++) { @@ -4972,7 +6163,7 @@ ufunc_get_types(PyUFuncObject *self) str = PyUString_FromStringAndSize(t, no + ni + 2); PyList_SET_ITEM(list, k, str); } - _pya_free(t); + PyArray_free(t); return list; } diff --git a/numpy/core/src/umath/ufunc_object.h b/numpy/core/src/umath/ufunc_object.h index a8886be05..2a5fd63a1 100644 --- a/numpy/core/src/umath/ufunc_object.h +++ b/numpy/core/src/umath/ufunc_object.h @@ -7,4 +7,75 @@ ufunc_geterr(PyObject *NPY_UNUSED(dummy), PyObject *args); NPY_NO_EXPORT PyObject * ufunc_seterr(PyObject *NPY_UNUSED(dummy), PyObject *args); +NPY_NO_EXPORT int +PyUFunc_SimpleBinaryComparisonTypeResolution(PyUFuncObject *ufunc, + NPY_CASTING casting, + PyArrayObject **operands, + PyObject *type_tup, + PyArray_Descr **out_dtypes, + PyUFuncGenericFunction *out_innerloop, + void **out_innerloopdata); + +NPY_NO_EXPORT int +PyUFunc_SimpleUnaryOperationTypeResolution(PyUFuncObject *ufunc, + NPY_CASTING casting, + PyArrayObject **operands, + PyObject *type_tup, + PyArray_Descr **out_dtypes, + PyUFuncGenericFunction *out_innerloop, + void **out_innerloopdata); + +NPY_NO_EXPORT int +PyUFunc_SimpleBinaryOperationTypeResolution(PyUFuncObject *ufunc, + NPY_CASTING casting, + PyArrayObject **operands, + PyObject *type_tup, + PyArray_Descr **out_dtypes, + PyUFuncGenericFunction *out_innerloop, + void **out_innerloopdata); + +NPY_NO_EXPORT int +PyUFunc_AbsoluteTypeResolution(PyUFuncObject *ufunc, + NPY_CASTING casting, + PyArrayObject **operands, + PyObject *type_tup, + PyArray_Descr **out_dtypes, + PyUFuncGenericFunction *out_innerloop, + void **out_innerloopdata); + +NPY_NO_EXPORT int +PyUFunc_AdditionTypeResolution(PyUFuncObject *ufunc, + NPY_CASTING casting, + PyArrayObject **operands, + PyObject *type_tup, + PyArray_Descr **out_dtypes, + PyUFuncGenericFunction *out_innerloop, + void **out_innerloopdata); + +NPY_NO_EXPORT int +PyUFunc_SubtractionTypeResolution(PyUFuncObject *ufunc, + NPY_CASTING casting, + PyArrayObject **operands, + PyObject *type_tup, + PyArray_Descr **out_dtypes, + PyUFuncGenericFunction *out_innerloop, + void **out_innerloopdata); + +NPY_NO_EXPORT int +PyUFunc_MultiplicationTypeResolution(PyUFuncObject *ufunc, + NPY_CASTING casting, + PyArrayObject **operands, + PyObject *type_tup, + PyArray_Descr **out_dtypes, + PyUFuncGenericFunction *out_innerloop, + void **out_innerloopdata); +NPY_NO_EXPORT int +PyUFunc_DivisionTypeResolution(PyUFuncObject *ufunc, + NPY_CASTING casting, + PyArrayObject **operands, + PyObject *type_tup, + PyArray_Descr **out_dtypes, + PyUFuncGenericFunction *out_innerloop, + void **out_innerloopdata); + #endif diff --git a/numpy/core/src/umath/umathmodule.c.src b/numpy/core/src/umath/umathmodule.c.src index 8d081f85b..b4cece358 100644 --- a/numpy/core/src/umath/umathmodule.c.src +++ b/numpy/core/src/umath/umathmodule.c.src @@ -43,6 +43,32 @@ static PyUFuncGenericFunction pyfunc_functions[] = {PyUFunc_On_Om}; +static int object_ufunc_type_resolution(PyUFuncObject *ufunc, + NPY_CASTING casting, + PyArrayObject **operands, + PyObject *type_tup, + PyArray_Descr **out_dtypes, + PyUFuncGenericFunction *out_innerloop, + void **out_innerloopdata) +{ + int i, nop = ufunc->nin + ufunc->nout; + + out_dtypes[0] = PyArray_DescrFromType(NPY_OBJECT); + if (out_dtypes[0] == NULL) { + return -1; + } + + for (i = 1; i < nop; ++i) { + out_dtypes[i] = out_dtypes[0]; + Py_INCREF(out_dtypes[0]); + } + + *out_innerloop = ufunc->functions[0]; + *out_innerloopdata = ufunc->data[0]; + + return 0; +} + static PyObject * ufunc_frompyfunc(PyObject *NPY_UNUSED(dummy), PyObject *args, PyObject *NPY_UNUSED(kwds)) { /* Keywords are ignored for now */ @@ -62,7 +88,7 @@ ufunc_frompyfunc(PyObject *NPY_UNUSED(dummy), PyObject *args, PyObject *NPY_UNUS PyErr_SetString(PyExc_TypeError, "function must be callable"); return NULL; } - self = _pya_malloc(sizeof(PyUFuncObject)); + self = PyArray_malloc(sizeof(PyUFuncObject)); if (self == NULL) { return NULL; } @@ -85,6 +111,8 @@ ufunc_frompyfunc(PyObject *NPY_UNUSED(dummy), PyObject *args, PyObject *NPY_UNUS self->core_offsets = NULL; self->core_signature = NULL; + self->type_resolution_function = &object_ufunc_type_resolution; + pyname = PyObject_GetAttrString(function, "__name__"); if (pyname) { (void) PyString_AsStringAndSize(pyname, &fname, &fname_len); @@ -115,7 +143,7 @@ ufunc_frompyfunc(PyObject *NPY_UNUSED(dummy), PyObject *args, PyObject *NPY_UNUS if (i) { offset[1] += (sizeof(void *)-i); } - self->ptr = _pya_malloc(offset[0] + offset[1] + sizeof(void *) + + self->ptr = PyArray_malloc(offset[0] + offset[1] + sizeof(void *) + (fname_len + 14)); if (self->ptr == NULL) { Py_XDECREF(pyname); diff --git a/numpy/core/tests/test_arrayprint.py b/numpy/core/tests/test_arrayprint.py index dd86fcbd9..b1dbb1d68 100644 --- a/numpy/core/tests/test_arrayprint.py +++ b/numpy/core/tests/test_arrayprint.py @@ -80,8 +80,6 @@ class TestArray2String(TestCase): "[. o O]") assert_(np.array2string(x, formatter={'int_kind':_format_function}) ==\ "[. o O]") - assert_(np.array2string(x, formatter={'timeint':_format_function}) == \ - "[0 1 2]") assert_(np.array2string(x, formatter={'all':lambda x: "%.4f" % x}) == \ "[0.0000 1.0000 2.0000]") assert_(np.array2string(x, formatter={'int':lambda x: hex(x)}) == \ diff --git a/numpy/core/tests/test_datetime.py b/numpy/core/tests/test_datetime.py index 97fb1151f..bb6594a9a 100644 --- a/numpy/core/tests/test_datetime.py +++ b/numpy/core/tests/test_datetime.py @@ -1,9 +1,12 @@ -from os import path +import os, pickle +import numpy import numpy as np from numpy.testing import * +from numpy.compat import asbytes +import datetime class TestDateTime(TestCase): - def test_creation(self): + def test_datetime_dtype_creation(self): for unit in ['Y', 'M', 'W', 'B', 'D', 'h', 'm', 's', 'ms', 'us', 'ns', 'ps', 'fs', 'as']: @@ -11,7 +14,708 @@ class TestDateTime(TestCase): assert_(dt1 == np.dtype('datetime64[750%s]' % unit)) dt2 = np.dtype('m8[%s]' % unit) assert_(dt2 == np.dtype('timedelta64[%s]' % unit)) + + # Check that the parser rejects bad datetime types + assert_raises(TypeError, np.dtype, 'M8[badunit]') + assert_raises(TypeError, np.dtype, 'm8[badunit]') + assert_raises(TypeError, np.dtype, 'M8[YY]') + assert_raises(TypeError, np.dtype, 'm8[YY]') + assert_raises(TypeError, np.dtype, 'M4') + assert_raises(TypeError, np.dtype, 'm4') + assert_raises(TypeError, np.dtype, 'M7') + assert_raises(TypeError, np.dtype, 'm7') + assert_raises(TypeError, np.dtype, 'M16') + assert_raises(TypeError, np.dtype, 'm16') + def test_datetime_casting_rules(self): + # Cannot cast safely/same_kind between timedelta and datetime + assert_(not np.can_cast('m8', 'M8', casting='same_kind')) + assert_(not np.can_cast('M8', 'm8', casting='same_kind')) + assert_(not np.can_cast('m8', 'M8', casting='safe')) + assert_(not np.can_cast('M8', 'm8', casting='safe')) + + # Can cast safely/same_kind from integer to timedelta + assert_(np.can_cast('i8', 'm8', casting='same_kind')) + assert_(np.can_cast('i8', 'm8', casting='safe')) + + # Cannot cast safely/same_kind from float to timedelta + assert_(not np.can_cast('f4', 'm8', casting='same_kind')) + assert_(not np.can_cast('f4', 'm8', casting='safe')) + + # Cannot cast safely/same_kind from integer to datetime + assert_(not np.can_cast('i8', 'M8', casting='same_kind')) + assert_(not np.can_cast('i8', 'M8', casting='safe')) + + # Cannot cast safely/same_kind from bool to datetime + assert_(not np.can_cast('b1', 'M8', casting='same_kind')) + assert_(not np.can_cast('b1', 'M8', casting='safe')) + # Can cast safely/same_kind from bool to timedelta + assert_(np.can_cast('b1', 'm8', casting='same_kind')) + assert_(np.can_cast('b1', 'm8', casting='safe')) + + # Can cast datetime safely from months/years to days + assert_(np.can_cast('M8[M]', 'M8[D]', casting='safe')) + assert_(np.can_cast('M8[Y]', 'M8[D]', casting='safe')) + # Cannot cast timedelta safely from months/years to days + assert_(not np.can_cast('m8[M]', 'm8[D]', casting='safe')) + assert_(not np.can_cast('m8[Y]', 'm8[D]', casting='safe')) + # Can cast same_kind from months/years to days + assert_(np.can_cast('M8[M]', 'M8[D]', casting='same_kind')) + assert_(np.can_cast('M8[Y]', 'M8[D]', casting='same_kind')) + assert_(np.can_cast('m8[M]', 'm8[D]', casting='same_kind')) + assert_(np.can_cast('m8[Y]', 'm8[D]', casting='same_kind')) + + # Cannot cast safely if the integer multiplier doesn't divide + assert_(not np.can_cast('M8[7h]', 'M8[3h]', casting='safe')) + assert_(not np.can_cast('M8[3h]', 'M8[6h]', casting='safe')) + # But can cast same_kind + assert_(np.can_cast('M8[7h]', 'M8[3h]', casting='same_kind')) + # Can cast safely if the integer multiplier does divide + assert_(np.can_cast('M8[6h]', 'M8[3h]', casting='safe')) + + def test_datetime_scalar_construction(self): + # Construct with different units + assert_equal(np.datetime64('1950-03-12', 'D'), + np.datetime64('1950-03-12')) + assert_equal(np.datetime64('1950-03-12', 's'), + np.datetime64('1950-03-12', 'm')) + + # When constructing from a scalar or zero-dimensional array, + # it either keeps the units or you can override them. + a = np.datetime64('2000-03-18T16Z', 'h') + b = np.array('2000-03-18T16Z', dtype='M8[h]') + + assert_equal(a.dtype, np.dtype('M8[h]')) + assert_equal(b.dtype, np.dtype('M8[h]')) + + assert_equal(np.datetime64(a), a); + assert_equal(np.datetime64(a).dtype, np.dtype('M8[h]')) + + assert_equal(np.datetime64(b), a) + assert_equal(np.datetime64(b).dtype, np.dtype('M8[h]')) + + assert_equal(np.datetime64(a, 's'), a) + assert_equal(np.datetime64(a, 's').dtype, np.dtype('M8[s]')) + + assert_equal(np.datetime64(b, 's'), a) + assert_equal(np.datetime64(b, 's').dtype, np.dtype('M8[s]')) + + # Construction from datetime.date + assert_equal(np.datetime64('1945-03-25'), + np.datetime64(datetime.date(1945,3,25))) + assert_equal(np.datetime64('2045-03-25', 'D'), + np.datetime64(datetime.date(2045,3,25), 'D')) + # Construction from datetime.datetime + assert_equal(np.datetime64('1980-01-25T14:36:22.5Z'), + np.datetime64(datetime.datetime(1980,1,25, + 14,36,22,500000))) + + + def test_timedelta_scalar_construction(self): + # Construct with different units + assert_equal(np.timedelta64(7, 'D'), + np.timedelta64(1, 'W')) + assert_equal(np.timedelta64(120, 's'), + np.timedelta64(2, 'm')) + + # When constructing from a scalar or zero-dimensional array, + # it either keeps the units or you can override them. + a = np.timedelta64(2, 'h') + b = np.array(2, dtype='m8[h]') + + assert_equal(a.dtype, np.dtype('m8[h]')) + assert_equal(b.dtype, np.dtype('m8[h]')) + + assert_equal(np.timedelta64(a), a); + assert_equal(np.timedelta64(a).dtype, np.dtype('m8[h]')) + + assert_equal(np.timedelta64(b), a) + assert_equal(np.timedelta64(b).dtype, np.dtype('m8[h]')) + + assert_equal(np.timedelta64(a, 's'), a) + assert_equal(np.timedelta64(a, 's').dtype, np.dtype('m8[s]')) + + assert_equal(np.timedelta64(b, 's'), a) + assert_equal(np.timedelta64(b, 's').dtype, np.dtype('m8[s]')) + + # Construction from datetime.timedelta + assert_equal(np.timedelta64(5, 'D'), + np.timedelta64(datetime.timedelta(days=5))) + assert_equal(np.timedelta64(102347621, 's'), + np.timedelta64(datetime.timedelta(seconds=102347621))) + assert_equal(np.timedelta64(-10234760000, 'us'), + np.timedelta64(datetime.timedelta( + microseconds=-10234760000))) + assert_equal(np.timedelta64(10234760000, 'us'), + np.timedelta64(datetime.timedelta( + microseconds=10234760000))) + assert_equal(np.timedelta64(1023476, 'ms'), + np.timedelta64(datetime.timedelta(milliseconds=1023476))) + assert_equal(np.timedelta64(10, 'm'), + np.timedelta64(datetime.timedelta(minutes=10))) + assert_equal(np.timedelta64(281, 'h'), + np.timedelta64(datetime.timedelta(hours=281))) + assert_equal(np.timedelta64(28, 'W'), + np.timedelta64(datetime.timedelta(weeks=28))) + + def test_datetime_nat_casting(self): + a = np.array('NaT', dtype='M8[D]') + b = np.datetime64('NaT', '[D]') + + # Arrays + assert_equal(a.astype('M8[s]'), np.array('NaT', dtype='M8[s]')) + assert_equal(a.astype('M8[ms]'), np.array('NaT', dtype='M8[ms]')) + assert_equal(a.astype('M8[M]'), np.array('NaT', dtype='M8[M]')) + assert_equal(a.astype('M8[Y]'), np.array('NaT', dtype='M8[Y]')) + assert_equal(a.astype('M8[W]'), np.array('NaT', dtype='M8[W]')) + + # Scalars -> Scalars + assert_equal(np.datetime64(b, '[s]'), np.datetime64('NaT', '[s]')) + assert_equal(np.datetime64(b, '[ms]'), np.datetime64('NaT', '[ms]')) + assert_equal(np.datetime64(b, '[M]'), np.datetime64('NaT', '[M]')) + assert_equal(np.datetime64(b, '[Y]'), np.datetime64('NaT', '[Y]')) + assert_equal(np.datetime64(b, '[W]'), np.datetime64('NaT', '[W]')) + + # Arrays -> Scalars + assert_equal(np.datetime64(a, '[s]'), np.datetime64('NaT', '[s]')) + assert_equal(np.datetime64(a, '[ms]'), np.datetime64('NaT', '[ms]')) + assert_equal(np.datetime64(a, '[M]'), np.datetime64('NaT', '[M]')) + assert_equal(np.datetime64(a, '[Y]'), np.datetime64('NaT', '[Y]')) + assert_equal(np.datetime64(a, '[W]'), np.datetime64('NaT', '[W]')) + + def test_days_creation(self): + assert_equal(np.array('1599', dtype='M8[D]').astype('i8'), + (1600-1970)*365 - (1972-1600)/4 + 3 - 365) + assert_equal(np.array('1600', dtype='M8[D]').astype('i8'), + (1600-1970)*365 - (1972-1600)/4 + 3) + assert_equal(np.array('1601', dtype='M8[D]').astype('i8'), + (1600-1970)*365 - (1972-1600)/4 + 3 + 366) + assert_equal(np.array('1900', dtype='M8[D]').astype('i8'), + (1900-1970)*365 - (1970-1900)/4) + assert_equal(np.array('1901', dtype='M8[D]').astype('i8'), + (1900-1970)*365 - (1970-1900)/4 + 365) + assert_equal(np.array('1967', dtype='M8[D]').astype('i8'), -3*365 - 1) + assert_equal(np.array('1968', dtype='M8[D]').astype('i8'), -2*365 - 1) + assert_equal(np.array('1969', dtype='M8[D]').astype('i8'), -1*365) + assert_equal(np.array('1970', dtype='M8[D]').astype('i8'), 0*365) + assert_equal(np.array('1971', dtype='M8[D]').astype('i8'), 1*365) + assert_equal(np.array('1972', dtype='M8[D]').astype('i8'), 2*365) + assert_equal(np.array('1973', dtype='M8[D]').astype('i8'), 3*365 + 1) + assert_equal(np.array('1974', dtype='M8[D]').astype('i8'), 4*365 + 1) + assert_equal(np.array('2000', dtype='M8[D]').astype('i8'), + (2000 - 1970)*365 + (2000 - 1972)/4) + assert_equal(np.array('2001', dtype='M8[D]').astype('i8'), + (2000 - 1970)*365 + (2000 - 1972)/4 + 366) + assert_equal(np.array('2400', dtype='M8[D]').astype('i8'), + (2400 - 1970)*365 + (2400 - 1972)/4 - 3) + assert_equal(np.array('2401', dtype='M8[D]').astype('i8'), + (2400 - 1970)*365 + (2400 - 1972)/4 - 3 + 366) + + assert_equal(np.array('1600-02-29', dtype='M8[D]').astype('i8'), + (1600-1970)*365 - (1972-1600)/4 + 3 + 31 + 28) + assert_equal(np.array('1600-03-01', dtype='M8[D]').astype('i8'), + (1600-1970)*365 - (1972-1600)/4 + 3 + 31 + 29) + assert_equal(np.array('2000-02-29', dtype='M8[D]').astype('i8'), + (2000 - 1970)*365 + (2000 - 1972)/4 + 31 + 28) + assert_equal(np.array('2000-03-01', dtype='M8[D]').astype('i8'), + (2000 - 1970)*365 + (2000 - 1972)/4 + 31 + 29) + assert_equal(np.array('2001-03-22', dtype='M8[D]').astype('i8'), + (2000 - 1970)*365 + (2000 - 1972)/4 + 366 + 31 + 28 + 21) + + def test_days_to_pydate(self): + assert_equal(np.array('1599', dtype='M8[D]').astype('O'), + datetime.date(1599, 1, 1)) + assert_equal(np.array('1600', dtype='M8[D]').astype('O'), + datetime.date(1600, 1, 1)) + assert_equal(np.array('1601', dtype='M8[D]').astype('O'), + datetime.date(1601, 1, 1)) + assert_equal(np.array('1900', dtype='M8[D]').astype('O'), + datetime.date(1900, 1, 1)) + assert_equal(np.array('1901', dtype='M8[D]').astype('O'), + datetime.date(1901, 1, 1)) + assert_equal(np.array('2000', dtype='M8[D]').astype('O'), + datetime.date(2000, 1, 1)) + assert_equal(np.array('2001', dtype='M8[D]').astype('O'), + datetime.date(2001, 1, 1)) + assert_equal(np.array('1600-02-29', dtype='M8[D]').astype('O'), + datetime.date(1600, 2, 29)) + assert_equal(np.array('1600-03-01', dtype='M8[D]').astype('O'), + datetime.date(1600, 3, 1)) + assert_equal(np.array('2001-03-22', dtype='M8[D]').astype('O'), + datetime.date(2001, 3, 22)) + + + def test_dtype_comparison(self): + assert_(not (np.dtype('M8[us]') == np.dtype('M8[ms]'))) + assert_(np.dtype('M8[us]') != np.dtype('M8[ms]')) + assert_(np.dtype('M8[D]') != np.dtype('M8[B]')) + assert_(np.dtype('M8[2D]') != np.dtype('M8[D]')) + assert_(np.dtype('M8[D]') != np.dtype('M8[2D]')) + assert_(np.dtype('M8[Y]//3') != np.dtype('M8[Y]')) + + def test_pydatetime_creation(self): + a = np.array(['1960-03-12', datetime.date(1960, 3, 12)], dtype='M8[D]') + assert_equal(a[0], a[1]) + a = np.array(['1960-03-12', datetime.date(1960, 3, 12)], dtype='M8[s]') + assert_equal(a[0], a[1]) + a = np.array(['1999-12-31', datetime.date(1999, 12, 31)], dtype='M8[D]') + assert_equal(a[0], a[1]) + a = np.array(['1999-12-31', datetime.date(1999, 12, 31)], dtype='M8[s]') + assert_equal(a[0], a[1]) + a = np.array(['2000-01-01', datetime.date(2000, 1, 1)], dtype='M8[D]') + assert_equal(a[0], a[1]) + a = np.array(['2000-01-01', datetime.date(2000, 1, 1)], dtype='M8[s]') + assert_equal(a[0], a[1]) + # Will fail if the date changes during the exact right moment + a = np.array(['today', datetime.date.today()], dtype='M8[s]') + assert_equal(a[0], a[1]) + # datetime.datetime.now() returns local time, not UTC + #a = np.array(['now', datetime.datetime.now()], dtype='M8[s]') + #assert_equal(a[0], a[1]) + + def test_pickle(self): + # Check that pickle roundtripping works + dt = np.dtype('M8[7D]//3') + assert_equal(dt, pickle.loads(pickle.dumps(dt))) + dt = np.dtype('M8[B]') + assert_equal(dt, pickle.loads(pickle.dumps(dt))) + + def test_dtype_promotion(self): + # datetime <op> datetime computes the metadata gcd + # timedelta <op> timedelta computes the metadata gcd + for mM in ['m', 'M']: + assert_equal( + np.promote_types(np.dtype(mM+'8[2Y]'), np.dtype(mM+'8[2Y]')), + np.dtype(mM+'8[2Y]')) + assert_equal( + np.promote_types(np.dtype(mM+'8[12Y]'), np.dtype(mM+'8[15Y]')), + np.dtype(mM+'8[3Y]')) + assert_equal( + np.promote_types(np.dtype(mM+'8[62M]'), np.dtype(mM+'8[24M]')), + np.dtype(mM+'8[2M]')) + assert_equal( + np.promote_types(np.dtype(mM+'8[1W]'), np.dtype(mM+'8[2D]')), + np.dtype(mM+'8[1D]')) + assert_equal( + np.promote_types(np.dtype(mM+'8[W]'), np.dtype(mM+'8[13s]')), + np.dtype(mM+'8[s]')) + assert_equal( + np.promote_types(np.dtype(mM+'8[13W]'), np.dtype(mM+'8[49s]')), + np.dtype(mM+'8[7s]')) + # timedelta <op> timedelta raises when there is no reasonable gcd + assert_raises(TypeError, np.promote_types, + np.dtype('m8[Y]'), np.dtype('m8[D]')) + assert_raises(TypeError, np.promote_types, + np.dtype('m8[Y]'), np.dtype('m8[B]')) + assert_raises(TypeError, np.promote_types, + np.dtype('m8[D]'), np.dtype('m8[B]')) + assert_raises(TypeError, np.promote_types, + np.dtype('m8[M]'), np.dtype('m8[W]')) + # timedelta <op> timedelta may overflow with big unit ranges + assert_raises(OverflowError, np.promote_types, + np.dtype('m8[W]'), np.dtype('m8[fs]')) + assert_raises(OverflowError, np.promote_types, + np.dtype('m8[s]'), np.dtype('m8[as]')) + + + def test_pyobject_roundtrip(self): + # All datetime types should be able to roundtrip through object + a = np.array([0,0,0,0,0,0,0,0,0, + -1020040340, -2942398, -1, 0, 1, 234523453, 1199164176], + dtype=np.int64) + for unit in ['M8[as]', 'M8[16fs]', 'M8[ps]', 'M8[us]', + 'M8[as]//12', 'M8[us]//16', 'M8[D]', 'M8[D]//4', + 'M8[W]', 'M8[M]', 'M8[Y]']: + b = a.copy().view(dtype=unit) + b[0] = '-0001-01-01' + b[1] = '-0001-12-31' + b[2] = '0000-01-01' + b[3] = '0001-01-01' + b[4] = '1969-12-31T23:59:59.999999Z' + b[5] = '1970-01-01' + b[6] = '9999-12-31T23:59:59.999999Z' + b[7] = '10000-01-01' + b[8] = 'NaT' + + assert_equal(b.astype(object).astype(unit), b, + "Error roundtripping unit %s" % unit) + + def test_month_truncation(self): + # Make sure that months are truncating correctly + assert_equal(np.array('1945-03-01', dtype='M8[M]'), + np.array('1945-03-31', dtype='M8[M]')) + assert_equal(np.array('1969-11-01', dtype='M8[M]'), + np.array('1969-11-30T23:59:59.999999Z', dtype='M8[M]')) + assert_equal(np.array('1969-12-01', dtype='M8[M]'), + np.array('1969-12-31T23:59:59.999999Z', dtype='M8[M]')) + assert_equal(np.array('1970-01-01', dtype='M8[M]'), + np.array('1970-01-31T23:59:59.999999Z', dtype='M8[M]')) + assert_equal(np.array('1980-02-01', dtype='M8[M]'), + np.array('1980-02-29T23:59:59.999999Z', dtype='M8[M]')) + + def test_different_unit_comparison(self): + # Check some years with units that won't overflow + for unit1 in ['Y', 'M', 'D', '6h', 'h', 'm', 's', '10ms', + 'ms', 'us']: + dt1 = np.dtype('M8[%s]' % unit1) + for unit2 in ['Y', 'M', 'D', 'h', 'm', 's', 'ms', 'us']: + dt2 = np.dtype('M8[%s]' % unit2) + assert_equal(np.array('1945', dtype=dt1), + np.array('1945', dtype=dt2)) + assert_equal(np.array('1970', dtype=dt1), + np.array('1970', dtype=dt2)) + assert_equal(np.array('9999', dtype=dt1), + np.array('9999', dtype=dt2)) + assert_equal(np.array('10000', dtype=dt1), + np.array('10000-01-01', dtype=dt2)) + assert_equal(np.datetime64('1945', unit1), + np.datetime64('1945', unit2)) + assert_equal(np.datetime64('1970', unit1), + np.datetime64('1970', unit2)) + assert_equal(np.datetime64('9999', unit1), + np.datetime64('9999', unit2)) + assert_equal(np.datetime64('10000', unit1), + np.datetime64('10000-01-01', unit2)) + # Check some days with units that won't overflow + for unit1 in ['D', '12h', 'h', 'm', 's', '4s', 'ms', 'us']: + dt1 = np.dtype('M8[%s]' % unit1) + for unit2 in ['D', 'h', 'm', 's', 'ms', 'us']: + dt2 = np.dtype('M8[%s]' % unit2) + assert_equal(np.array('1932-02-17', dtype=dt1), + np.array('1932-02-17T00:00:00Z', dtype=dt2)) + assert_equal(np.array('10000-04-27', dtype=dt1), + np.array('10000-04-27T00:00:00Z', dtype=dt2)) + assert_equal(np.array('today', dtype=dt1), + np.array('today', dtype=dt2)) + assert_equal(np.datetime64('1932-02-17', unit1), + np.datetime64('1932-02-17T00:00:00Z', unit2)) + assert_equal(np.datetime64('10000-04-27', unit1), + np.datetime64('10000-04-27T00:00:00Z', unit2)) + assert_equal(np.datetime64('today', unit1), + np.datetime64('today', unit2)) + + # Shouldn't be able to compare datetime and timedelta + # TODO: Changing to 'same_kind' or 'safe' casting in the ufuncs by + # default is needed to properly catch this kind of thing... + a = np.array('2012-12-21', dtype='M8[D]') + b = np.array(3, dtype='m8[D]') + #assert_raises(TypeError, np.less, a, b) + assert_raises(TypeError, np.less, a, b, casting='same_kind') + + def test_datetime_like(self): + a = np.array([3], dtype='m8[4D]//6') + b = np.array(['2012-12-21'], dtype='M8[D]//3') + + assert_equal(np.ones_like(a).dtype, a.dtype) + assert_equal(np.zeros_like(a).dtype, a.dtype) + assert_equal(np.empty_like(a).dtype, a.dtype) + assert_equal(np.ones_like(b).dtype, b.dtype) + assert_equal(np.zeros_like(b).dtype, b.dtype) + assert_equal(np.empty_like(b).dtype, b.dtype) + + def test_datetime_unary(self): + for tda, tdb, tdzero, tdone, tdmone in \ + [ + # One-dimensional arrays + (np.array([3], dtype='m8[D]'), + np.array([-3], dtype='m8[D]'), + np.array([0], dtype='m8[D]'), + np.array([1], dtype='m8[D]'), + np.array([-1], dtype='m8[D]')), + # NumPy scalars + (np.timedelta64(3, '[D]'), + np.timedelta64(-3, '[D]'), + np.timedelta64(0, '[D]'), + np.timedelta64(1, '[D]'), + np.timedelta64(-1, '[D]'))]: + # negative ufunc + assert_equal(-tdb, tda) + assert_equal((-tdb).dtype, tda.dtype) + assert_equal(np.negative(tdb), tda) + assert_equal(np.negative(tdb).dtype, tda.dtype) + + # absolute ufunc + assert_equal(np.absolute(tdb), tda) + assert_equal(np.absolute(tdb).dtype, tda.dtype) + + # sign ufunc + assert_equal(np.sign(tda), tdone) + assert_equal(np.sign(tdb), tdmone) + assert_equal(np.sign(tdzero), tdzero) + assert_equal(np.sign(tda).dtype, tda.dtype) + + def test_datetime_add(self): + for dta, dtb, dtc, dtnat, tda, tdb, tdc in \ + [ + # One-dimensional arrays + (np.array(['2012-12-21'], dtype='M8[D]'), + np.array(['2012-12-24'], dtype='M8[D]'), + np.array(['1940-12-24'], dtype='M8[D]'), + np.array(['NaT'], dtype='M8[D]'), + np.array([3], dtype='m8[D]'), + np.array([11], dtype='m8[h]'), + np.array([3*24 + 11], dtype='m8[h]')), + # NumPy scalars + (np.datetime64('2012-12-21', '[D]'), + np.datetime64('2012-12-24', '[D]'), + np.datetime64('1940-12-24', '[D]'), + np.datetime64('NaT', '[D]'), + np.timedelta64(3, '[D]'), + np.timedelta64(11, '[h]'), + np.timedelta64(3*24 + 11, '[h]'))]: + # m8 + m8 + assert_equal(tda + tdb, tdc) + assert_equal((tda + tdb).dtype, np.dtype('m8[h]')) + # m8 + bool + assert_equal(tdb + True, tdb + 1) + assert_equal((tdb + True).dtype, np.dtype('m8[h]')) + # m8 + int + assert_equal(tdb + 3*24, tdc) + assert_equal((tdb + 3*24).dtype, np.dtype('m8[h]')) + # bool + m8 + assert_equal(False + tdb, tdb) + assert_equal((False + tdb).dtype, np.dtype('m8[h]')) + # int + m8 + assert_equal(3*24 + tdb, tdc) + assert_equal((3*24 + tdb).dtype, np.dtype('m8[h]')) + # M8 + bool + assert_equal(dta + True, dta + 1) + assert_equal(dtnat + True, dtnat) + assert_equal((dta + True).dtype, np.dtype('M8[D]')) + # M8 + int + assert_equal(dta + 3, dtb) + assert_equal(dtnat + 3, dtnat) + assert_equal((dta + 3).dtype, np.dtype('M8[D]')) + # bool + M8 + assert_equal(False + dta, dta) + assert_equal(False + dtnat, dtnat) + assert_equal((False + dta).dtype, np.dtype('M8[D]')) + # int + M8 + assert_equal(3 + dta, dtb) + assert_equal(3 + dtnat, dtnat) + assert_equal((3 + dta).dtype, np.dtype('M8[D]')) + # M8 + m8 + assert_equal(dta + tda, dtb) + assert_equal(dtnat + tda, dtnat) + assert_equal((dta + tda).dtype, np.dtype('M8[D]')) + # m8 + M8 + assert_equal(tda + dta, dtb) + assert_equal(tda + dtnat, dtnat) + assert_equal((tda + dta).dtype, np.dtype('M8[D]')) + + # In M8 + m8, the M8 controls the result type + assert_equal(dta + tdb, dta) + assert_equal((dta + tdb).dtype, np.dtype('M8[D]')) + assert_equal(dtc + tdb, dtc) + assert_equal((dtc + tdb).dtype, np.dtype('M8[D]')) + assert_equal(tdb + dta, dta) + assert_equal((tdb + dta).dtype, np.dtype('M8[D]')) + assert_equal(tdb + dtc, dtc) + assert_equal((tdb + dtc).dtype, np.dtype('M8[D]')) + + # M8 + M8 + assert_raises(TypeError, np.add, dta, dtb) + + def test_datetime_subtract(self): + for dta, dtb, dtc, dtd, dtnat, tda, tdb, tdc in \ + [ + # One-dimensional arrays + (np.array(['2012-12-21'], dtype='M8[D]'), + np.array(['2012-12-24'], dtype='M8[D]'), + np.array(['1940-12-24'], dtype='M8[D]'), + np.array(['1940-12-24'], dtype='M8[h]'), + np.array(['NaT'], dtype='M8[D]'), + np.array([3], dtype='m8[D]'), + np.array([11], dtype='m8[h]'), + np.array([3*24 - 11], dtype='m8[h]')), + # NumPy scalars + (np.datetime64('2012-12-21', '[D]'), + np.datetime64('2012-12-24', '[D]'), + np.datetime64('1940-12-24', '[D]'), + np.datetime64('1940-12-24', '[h]'), + np.datetime64('NaT', '[D]'), + np.timedelta64(3, '[D]'), + np.timedelta64(11, '[h]'), + np.timedelta64(3*24 - 11, '[h]'))]: + # m8 - m8 + assert_equal(tda - tdb, tdc) + assert_equal((tda - tdb).dtype, np.dtype('m8[h]')) + assert_equal(tdb - tda, -tdc) + assert_equal((tdb - tda).dtype, np.dtype('m8[h]')) + # m8 - bool + assert_equal(tdc - True, tdc - 1) + assert_equal((tdc - True).dtype, np.dtype('m8[h]')) + # m8 - int + assert_equal(tdc - 3*24, -tdb) + assert_equal((tdc - 3*24).dtype, np.dtype('m8[h]')) + # int - m8 + assert_equal(False - tdb, -tdb) + assert_equal((False - tdb).dtype, np.dtype('m8[h]')) + # int - m8 + assert_equal(3*24 - tdb, tdc) + assert_equal((3*24 - tdb).dtype, np.dtype('m8[h]')) + # M8 - bool + assert_equal(dtb - True, dtb - 1) + assert_equal(dtnat - True, dtnat) + assert_equal((dtb - True).dtype, np.dtype('M8[D]')) + # M8 - int + assert_equal(dtb - 3, dta) + assert_equal(dtnat - 3, dtnat) + assert_equal((dtb - 3).dtype, np.dtype('M8[D]')) + # M8 - m8 + assert_equal(dtb - tda, dta) + assert_equal(dtnat - tda, dtnat) + assert_equal((dtb - tda).dtype, np.dtype('M8[D]')) + + # In M8 - m8, the M8 controls the result type + assert_equal(dta - tdb, dta) + assert_equal((dta - tdb).dtype, np.dtype('M8[D]')) + assert_equal(dtc - tdb, dtc) + assert_equal((dtc - tdb).dtype, np.dtype('M8[D]')) + + # M8 - M8 with different metadata + assert_raises(TypeError, np.subtract, dtc, dtd) + # m8 - M8 + assert_raises(TypeError, np.subtract, tda, dta) + # bool - M8 + assert_raises(TypeError, np.subtract, False, dta) + # int - M8 + assert_raises(TypeError, np.subtract, 3, dta) + + def test_datetime_multiply(self): + for dta, tda, tdb, tdc in \ + [ + # One-dimensional arrays + (np.array(['2012-12-21'], dtype='M8[D]'), + np.array([6], dtype='m8[h]'), + np.array([9], dtype='m8[h]'), + np.array([12], dtype='m8[h]')), + # NumPy scalars + (np.datetime64('2012-12-21', '[D]'), + np.timedelta64(6, '[h]'), + np.timedelta64(9, '[h]'), + np.timedelta64(12, '[h]'))]: + # m8 * int + assert_equal(tda * 2, tdc) + assert_equal((tda * 2).dtype, np.dtype('m8[h]')) + # int * m8 + assert_equal(2 * tda, tdc) + assert_equal((2 * tda).dtype, np.dtype('m8[h]')) + # m8 * float + assert_equal(tda * 1.5, tdb) + assert_equal((tda * 1.5).dtype, np.dtype('m8[h]')) + # float * m8 + assert_equal(1.5 * tda, tdb) + assert_equal((1.5 * tda).dtype, np.dtype('m8[h]')) + + # m8 * m8 + assert_raises(TypeError, np.multiply, tda, tdb) + # m8 * M8 + assert_raises(TypeError, np.multiply, dta, tda) + # M8 * m8 + assert_raises(TypeError, np.multiply, tda, dta) + # M8 * int + assert_raises(TypeError, np.multiply, dta, 2) + # int * M8 + assert_raises(TypeError, np.multiply, 2, dta) + # M8 * float + assert_raises(TypeError, np.multiply, dta, 1.5) + # float * M8 + assert_raises(TypeError, np.multiply, 1.5, dta) + + def test_datetime_divide(self): + for dta, tda, tdb, tdc in \ + [ + # One-dimensional arrays + (np.array(['2012-12-21'], dtype='M8[D]'), + np.array([6], dtype='m8[h]'), + np.array([9], dtype='m8[h]'), + np.array([12], dtype='m8[h]')), + # NumPy scalars + (np.datetime64('2012-12-21', '[D]'), + np.timedelta64(6, '[h]'), + np.timedelta64(9, '[h]'), + np.timedelta64(12, '[h]'))]: + # m8 / int + assert_equal(tdc / 2, tda) + assert_equal((tdc / 2).dtype, np.dtype('m8[h]')) + # m8 / float + assert_equal(tda / 0.5, tdc) + assert_equal((tda / 0.5).dtype, np.dtype('m8[h]')) + + # int / m8 + assert_raises(TypeError, np.divide, 2, tdb) + # float / m8 + assert_raises(TypeError, np.divide, 0.5, tdb) + # m8 / m8 + assert_raises(TypeError, np.divide, tda, tdb) + # m8 / M8 + assert_raises(TypeError, np.divide, dta, tda) + # M8 / m8 + assert_raises(TypeError, np.divide, tda, dta) + # M8 / int + assert_raises(TypeError, np.divide, dta, 2) + # int / M8 + assert_raises(TypeError, np.divide, 2, dta) + # M8 / float + assert_raises(TypeError, np.divide, dta, 1.5) + # float / M8 + assert_raises(TypeError, np.divide, 1.5, dta) + + def test_datetime_minmax(self): + # The metadata of the result should become the GCD + # of the operand metadata + a = np.array('1999-03-12T13Z', dtype='M8[2m]') + b = np.array('1999-03-12T12Z', dtype='M8[s]') + assert_equal(np.minimum(a,b), b) + assert_equal(np.minimum(a,b).dtype, np.dtype('M8[s]')) + assert_equal(np.fmin(a,b), b) + assert_equal(np.fmin(a,b).dtype, np.dtype('M8[s]')) + assert_equal(np.maximum(a,b), a) + assert_equal(np.maximum(a,b).dtype, np.dtype('M8[s]')) + assert_equal(np.fmax(a,b), a) + assert_equal(np.fmax(a,b).dtype, np.dtype('M8[s]')) + # Viewed as integers, the comparison is opposite because + # of the units chosen + assert_equal(np.minimum(a.view('i8'),b.view('i8')), a.view('i8')) + + # Interaction with NaT + a = np.array('1999-03-12T13Z', dtype='M8[2m]') + dtnat = np.array('NaT', dtype='M8[D]') + assert_equal(np.minimum(a,dtnat), a) + assert_equal(np.minimum(dtnat,a), a) + assert_equal(np.maximum(a,dtnat), a) + assert_equal(np.maximum(dtnat,a), a) + + # Also do timedelta + a = np.array(3, dtype='m8[h]') + b = np.array(3*3600 - 3, dtype='m8[s]') + assert_equal(np.minimum(a,b), b) + assert_equal(np.minimum(a,b).dtype, np.dtype('m8[s]')) + assert_equal(np.fmin(a,b), b) + assert_equal(np.fmin(a,b).dtype, np.dtype('m8[s]')) + assert_equal(np.maximum(a,b), a) + assert_equal(np.maximum(a,b).dtype, np.dtype('m8[s]')) + assert_equal(np.fmax(a,b), a) + assert_equal(np.fmax(a,b).dtype, np.dtype('m8[s]')) + # Viewed as integers, the comparison is opposite because + # of the units chosen + assert_equal(np.minimum(a.view('i8'),b.view('i8')), a.view('i8')) + + # should raise between datetime and timedelta + # + # TODO: Allowing unsafe casting by + # default in ufuncs strikes again... :( + a = np.array(3, dtype='m8[h]') + b = np.array('1999-03-12T12Z', dtype='M8[s]') + #assert_raises(TypeError, np.minimum, a, b) + #assert_raises(TypeError, np.maximum, a, b) + #assert_raises(TypeError, np.fmin, a, b) + #assert_raises(TypeError, np.fmax, a, b) + assert_raises(TypeError, np.minimum, a, b, casting='same_kind') + assert_raises(TypeError, np.maximum, a, b, casting='same_kind') + assert_raises(TypeError, np.fmin, a, b, casting='same_kind') + assert_raises(TypeError, np.fmax, a, b, casting='same_kind') def test_hours(self): t = np.ones(3, dtype='M8[s]') @@ -63,8 +767,102 @@ class TestDateTime(TestCase): def test_divisor_conversion_as(self): self.assertRaises(ValueError, lambda : np.dtype('M8[as/10]')) + def test_string_parser_variants(self): + # Allow space instead of 'T' between date and time + assert_equal(np.array(['1980-02-29T01:02:03'], np.dtype('M8')), + np.array(['1980-02-29 01:02:03'], np.dtype('M8'))) + # Allow negative years + assert_equal(np.array(['-1980-02-29T01:02:03'], np.dtype('M8')), + np.array(['-1980-02-29 01:02:03'], np.dtype('M8'))) + # UTC specifier + assert_equal(np.array(['-1980-02-29T01:02:03Z'], np.dtype('M8')), + np.array(['-1980-02-29 01:02:03Z'], np.dtype('M8'))) + # Time zone offset + assert_equal(np.array(['1980-02-29T02:02:03Z'], np.dtype('M8')), + np.array(['1980-02-29 00:32:03-0130'], np.dtype('M8'))) + assert_equal(np.array(['1980-02-28T22:32:03Z'], np.dtype('M8')), + np.array(['1980-02-29 00:02:03+01:30'], np.dtype('M8'))) + assert_equal(np.array(['1980-02-29T02:32:03.506Z'], np.dtype('M8')), + np.array(['1980-02-29 00:32:03.506-02'], np.dtype('M8'))) + assert_equal(np.datetime64('1977-03-02T12:30-0230'), + np.datetime64('1977-03-02T15:00Z')) + + + def test_string_parser_error_check(self): + # Arbitrary bad string + assert_raises(ValueError, np.array, ['badvalue'], np.dtype('M8')) + # Character after year must be '-' + assert_raises(ValueError, np.array, ['1980X'], np.dtype('M8')) + # Cannot have trailing '-' + assert_raises(ValueError, np.array, ['1980-'], np.dtype('M8')) + # Month must be in range [1,12] + assert_raises(ValueError, np.array, ['1980-00'], np.dtype('M8')) + assert_raises(ValueError, np.array, ['1980-13'], np.dtype('M8')) + # Month must have two digits + assert_raises(ValueError, np.array, ['1980-1'], np.dtype('M8')) + assert_raises(ValueError, np.array, ['1980-1-02'], np.dtype('M8')) + # 'Mor' is not a valid month + assert_raises(ValueError, np.array, ['1980-Mor'], np.dtype('M8')) + # Cannot have trailing '-' + assert_raises(ValueError, np.array, ['1980-01-'], np.dtype('M8')) + # Day must be in range [1,len(month)] + assert_raises(ValueError, np.array, ['1980-01-0'], np.dtype('M8')) + assert_raises(ValueError, np.array, ['1980-01-00'], np.dtype('M8')) + assert_raises(ValueError, np.array, ['1980-01-32'], np.dtype('M8')) + assert_raises(ValueError, np.array, ['1979-02-29'], np.dtype('M8')) + assert_raises(ValueError, np.array, ['1980-02-30'], np.dtype('M8')) + assert_raises(ValueError, np.array, ['1980-03-32'], np.dtype('M8')) + assert_raises(ValueError, np.array, ['1980-04-31'], np.dtype('M8')) + assert_raises(ValueError, np.array, ['1980-05-32'], np.dtype('M8')) + assert_raises(ValueError, np.array, ['1980-06-31'], np.dtype('M8')) + assert_raises(ValueError, np.array, ['1980-07-32'], np.dtype('M8')) + assert_raises(ValueError, np.array, ['1980-08-32'], np.dtype('M8')) + assert_raises(ValueError, np.array, ['1980-09-31'], np.dtype('M8')) + assert_raises(ValueError, np.array, ['1980-10-32'], np.dtype('M8')) + assert_raises(ValueError, np.array, ['1980-11-31'], np.dtype('M8')) + assert_raises(ValueError, np.array, ['1980-12-32'], np.dtype('M8')) + # Cannot have trailing characters + assert_raises(ValueError, np.array, ['1980-02-03%'], np.dtype('M8')) + assert_raises(ValueError, np.array, ['1980-02-03 q'], np.dtype('M8')) + + # Hours must be in range [0, 23] + assert_raises(ValueError, np.array, ['1980-02-03 25'], np.dtype('M8')) + assert_raises(ValueError, np.array, ['1980-02-03T25'], np.dtype('M8')) + assert_raises(ValueError, np.array, ['1980-02-03 24:01'], + np.dtype('M8')) + assert_raises(ValueError, np.array, ['1980-02-03T24:01'], + np.dtype('M8')) + assert_raises(ValueError, np.array, ['1980-02-03 -1'], np.dtype('M8')) + # No trailing ':' + assert_raises(ValueError, np.array, ['1980-02-03 01:'], np.dtype('M8')) + # Minutes must be in range [0, 59] + assert_raises(ValueError, np.array, ['1980-02-03 01:-1'], + np.dtype('M8')) + assert_raises(ValueError, np.array, ['1980-02-03 01:60'], + np.dtype('M8')) + # No trailing ':' + assert_raises(ValueError, np.array, ['1980-02-03 01:60:'], + np.dtype('M8')) + # Seconds must be in range [0, 59] + assert_raises(ValueError, np.array, ['1980-02-03 01:10:-1'], + np.dtype('M8')) + assert_raises(ValueError, np.array, ['1980-02-03 01:01:60'], + np.dtype('M8')) + # Timezone offset must within a reasonable range + assert_raises(ValueError, np.array, ['1980-02-03 01:01:00+0661'], + np.dtype('M8')) + assert_raises(ValueError, np.array, ['1980-02-03 01:01:00+2500'], + np.dtype('M8')) + assert_raises(ValueError, np.array, ['1980-02-03 01:01:00-0070'], + np.dtype('M8')) + assert_raises(ValueError, np.array, ['1980-02-03 01:01:00-3000'], + np.dtype('M8')) + assert_raises(ValueError, np.array, ['1980-02-03 01:01:00-25:00'], + np.dtype('M8')) + + def test_creation_overflow(self): - date = '1980-03-23 20:00:00' + date = '1980-03-23 20:00:00Z' timesteps = np.array([date], dtype='datetime64[s]')[0].astype(np.int64) for unit in ['ms', 'us', 'ns']: timesteps *= 1000 @@ -75,5 +873,130 @@ class TestDateTime(TestCase): assert_equal(x[0].astype(np.int64), 322689600000000000) + def test_datetime_as_string(self): + # Check all the units with default string conversion + date = '1959-10-13T12:34:56.789012345678901234Z' + + assert_equal(np.datetime_as_string(np.datetime64(date, 'Y')), + '1959') + assert_equal(np.datetime_as_string(np.datetime64(date, 'M')), + '1959-10') + assert_equal(np.datetime_as_string(np.datetime64(date, 'D')), + '1959-10-13') + assert_equal(np.datetime_as_string(np.datetime64(date, 'h')), + '1959-10-13T12Z') + assert_equal(np.datetime_as_string(np.datetime64(date, 'm')), + '1959-10-13T12:34Z') + assert_equal(np.datetime_as_string(np.datetime64(date, 's')), + '1959-10-13T12:34:56Z') + assert_equal(np.datetime_as_string(np.datetime64(date, 'ms')), + '1959-10-13T12:34:56.789Z') + assert_equal(np.datetime_as_string(np.datetime64(date, 'us')), + '1959-10-13T12:34:56.789012Z') + + date = '1969-12-31T23:34:56.789012345678901234Z' + + assert_equal(np.datetime_as_string(np.datetime64(date, 'ns')), + '1969-12-31T23:34:56.789012345Z') + assert_equal(np.datetime_as_string(np.datetime64(date, 'ps')), + '1969-12-31T23:34:56.789012345678Z') + assert_equal(np.datetime_as_string(np.datetime64(date, 'fs')), + '1969-12-31T23:34:56.789012345678901Z') + + date = '1969-12-31T23:59:57.789012345678901234Z' + + assert_equal(np.datetime_as_string(np.datetime64(date, 'as')), + date); + date = '1970-01-01T00:34:56.789012345678901234Z' + + assert_equal(np.datetime_as_string(np.datetime64(date, 'ns')), + '1970-01-01T00:34:56.789012345Z') + assert_equal(np.datetime_as_string(np.datetime64(date, 'ps')), + '1970-01-01T00:34:56.789012345678Z') + assert_equal(np.datetime_as_string(np.datetime64(date, 'fs')), + '1970-01-01T00:34:56.789012345678901Z') + + date = '1970-01-01T00:00:05.789012345678901234Z' + + assert_equal(np.datetime_as_string(np.datetime64(date, 'as')), + date); + + # String conversion with the unit= parameter + a = np.datetime64('2032-07-18T12:23:34.123456Z', 'us') + assert_equal(np.datetime_as_string(a, unit='Y'), '2032') + assert_equal(np.datetime_as_string(a, unit='M'), '2032-07') + assert_equal(np.datetime_as_string(a, unit='W'), '2032-07-18') + assert_equal(np.datetime_as_string(a, unit='D'), '2032-07-18') + assert_equal(np.datetime_as_string(a, unit='h'), '2032-07-18T12Z') + assert_equal(np.datetime_as_string(a, unit='m'), + '2032-07-18T12:23Z') + assert_equal(np.datetime_as_string(a, unit='s'), + '2032-07-18T12:23:34Z') + assert_equal(np.datetime_as_string(a, unit='ms'), + '2032-07-18T12:23:34.123Z') + assert_equal(np.datetime_as_string(a, unit='us'), + '2032-07-18T12:23:34.123456Z') + assert_equal(np.datetime_as_string(a, unit='ns'), + '2032-07-18T12:23:34.123456000Z') + assert_equal(np.datetime_as_string(a, unit='ps'), + '2032-07-18T12:23:34.123456000000Z') + assert_equal(np.datetime_as_string(a, unit='fs'), + '2032-07-18T12:23:34.123456000000000Z') + assert_equal(np.datetime_as_string(a, unit='as'), + '2032-07-18T12:23:34.123456000000000000Z') + + # unit='auto' parameter + assert_equal(np.datetime_as_string( + np.datetime64('2032-07-18T12:23:34.123456Z', 'us'), + unit='auto'), + '2032-07-18T12:23:34.123456Z') + assert_equal(np.datetime_as_string( + np.datetime64('2032-07-18T12:23:34.12Z', 'us'), + unit='auto'), + '2032-07-18T12:23:34.120Z') + assert_equal(np.datetime_as_string( + np.datetime64('2032-07-18T12:23:34Z', 'us'), + unit='auto'), + '2032-07-18T12:23:34Z') + assert_equal(np.datetime_as_string( + np.datetime64('2032-07-18T12:23:00Z', 'us'), + unit='auto'), + '2032-07-18T12:23Z') + # 'auto' doesn't split up hour and minute + assert_equal(np.datetime_as_string( + np.datetime64('2032-07-18T12:00:00Z', 'us'), + unit='auto'), + '2032-07-18T12:00Z') + assert_equal(np.datetime_as_string( + np.datetime64('2032-07-18T00:00:00Z', 'us'), + unit='auto'), + '2032-07-18') + # 'auto' doesn't split up the date + assert_equal(np.datetime_as_string( + np.datetime64('2032-07-01T00:00:00Z', 'us'), + unit='auto'), + '2032-07-01') + assert_equal(np.datetime_as_string( + np.datetime64('2032-01-01T00:00:00Z', 'us'), + unit='auto'), + '2032-01-01') + + # local=True + a = np.datetime64('2010-03-15T06:30Z', 'm') + assert_(np.datetime_as_string(a, local=True) != '2010-03-15T6:30Z') + # local=True with tzoffset + assert_equal(np.datetime_as_string(a, local=True, tzoffset=-60), + '2010-03-15T05:30-0100') + assert_equal(np.datetime_as_string(a, local=True, tzoffset=+30), + '2010-03-15T07:00+0030') + assert_equal(np.datetime_as_string(a, local=True, tzoffset=-5*60), + '2010-03-15T01:30-0500') + +class TestDateTimeData(TestCase): + + def test_basic(self): + a = np.array(['1980-03-23'], dtype=np.datetime64) + assert_equal(np.datetime_data(a.dtype), (asbytes('us'), 1, 1)) + if __name__ == "__main__": run_module_suite() diff --git a/numpy/core/tests/test_dtype.py b/numpy/core/tests/test_dtype.py index 40c3a3eea..c79b755be 100644 --- a/numpy/core/tests/test_dtype.py +++ b/numpy/core/tests/test_dtype.py @@ -33,6 +33,14 @@ class TestBuiltin(TestCase): self.assertTrue(dt.byteorder != dt3.byteorder, "bogus test") assert_dtype_equal(dt, dt3) + def test_invalid_types(self): + # Make sure invalid type strings raise exceptions + for typestr in ['O3', 'O5', 'O7', 'b3', 'h4', 'I5', 'l4', 'l8', + 'L4', 'L8', 'q8', 'q16', 'Q8', 'Q16', 'e3', + 'f5', 'd8', 't8', 'g12', 'g16']: + #print typestr + assert_raises(TypeError, np.dtype, typestr) + class TestRecord(TestCase): def test_equivalent_record(self): """Test whether equivalent record dtypes hash the same.""" diff --git a/numpy/core/tests/test_regression.py b/numpy/core/tests/test_regression.py index d574c42f2..764bf9a41 100644 --- a/numpy/core/tests/test_regression.py +++ b/numpy/core/tests/test_regression.py @@ -1317,7 +1317,7 @@ class TestRegression(TestCase): def test_ticket_1539(self): dtypes = [x for x in np.typeDict.values() if (issubclass(x, np.number) - and not issubclass(x, np.timeinteger))] + and not issubclass(x, np.timedelta_))] a = np.array([], dtypes[0]) failures = [] for x in dtypes: diff --git a/numpy/lib/tests/test_type_check.py b/numpy/lib/tests/test_type_check.py index c8bc87c6e..0f8927614 100644 --- a/numpy/lib/tests/test_type_check.py +++ b/numpy/lib/tests/test_type_check.py @@ -382,13 +382,5 @@ class TestArrayConversion(TestCase): assert_equal(a.__class__,ndarray) assert_(issubdtype(a.dtype,float)) -class TestDateTimeData(object): - - @dec.skipif(not _HAS_CTYPE, "ctypes not available on this python installation") - def test_basic(self): - a = array(['1980-03-23'], dtype=datetime64) - assert_equal(datetime_data(a.dtype), (asbytes('us'), 1, 1, 1)) - - if __name__ == "__main__": run_module_suite() diff --git a/numpy/lib/type_check.py b/numpy/lib/type_check.py index 0ce851fe4..edea02b62 100644 --- a/numpy/lib/type_check.py +++ b/numpy/lib/type_check.py @@ -3,7 +3,7 @@ __all__ = ['iscomplexobj','isrealobj','imag','iscomplex', 'isreal','nan_to_num','real','real_if_close', 'typename','asfarray','mintypecode','asscalar', - 'common_type', 'datetime_data'] + 'common_type'] import numpy.core.numeric as _nx from numpy.core.numeric import asarray, asanyarray, array, isnan, \ @@ -601,47 +601,3 @@ def common_type(*arrays): else: return array_type[0][precision] -def datetime_data(dtype): - """Return (unit, numerator, denominator, events) from a datetime dtype - """ - try: - import ctypes - except ImportError: - raise RuntimeError("Cannot access date-time internals without ctypes installed") - - if dtype.kind not in ['m','M']: - raise ValueError("Not a date-time dtype") - - obj = dtype.metadata[METADATA_DTSTR] - class DATETIMEMETA(ctypes.Structure): - _fields_ = [('base', ctypes.c_int), - ('num', ctypes.c_int), - ('den', ctypes.c_int), - ('events', ctypes.c_int)] - - import sys - if sys.version_info[:2] >= (3, 0): - func = ctypes.pythonapi.PyCapsule_GetPointer - func.argtypes = [ctypes.py_object, ctypes.c_char_p] - func.restype = ctypes.c_void_p - result = func(ctypes.py_object(obj), ctypes.c_char_p(None)) - else: - func = ctypes.pythonapi.PyCObject_AsVoidPtr - func.argtypes = [ctypes.py_object] - func.restype = ctypes.c_void_p - result = func(ctypes.py_object(obj)) - result = ctypes.cast(ctypes.c_void_p(result), ctypes.POINTER(DATETIMEMETA)) - - struct = result[0] - base = struct.base - - # FIXME: This needs to be kept consistent with enum in ndarrayobject.h - from numpy.core.multiarray import DATETIMEUNITS - obj = ctypes.py_object(DATETIMEUNITS) - if sys.version_info[:2] >= (2,7): - result = func(obj, ctypes.c_char_p(None)) - else: - result = func(obj) - _unitnum2name = ctypes.cast(ctypes.c_void_p(result), ctypes.POINTER(ctypes.c_char_p)) - - return (_unitnum2name[base], struct.num, struct.den, struct.events) diff --git a/numpy/ma/core.py b/numpy/ma/core.py index bbd855cf8..2cb888d55 100644 --- a/numpy/ma/core.py +++ b/numpy/ma/core.py @@ -3527,10 +3527,10 @@ class MaskedArray(ndarray): # convert to object array to make filled work names = self.dtype.names if names is None: - res = self._data.astype("|O8") + res = self._data.astype("O") res[m] = f else: - rdtype = _recursive_make_descr(self.dtype, "|O8") + rdtype = _recursive_make_descr(self.dtype, "O") res = self._data.astype(rdtype) _recursive_printoption(res, m, f) else: |