diff options
-rw-r--r-- | doc/source/reference/arrays.datetime.rst | 121 | ||||
-rw-r--r-- | numpy/add_newdocs.py | 173 | ||||
-rw-r--r-- | numpy/core/_internal.py | 7 | ||||
-rw-r--r-- | numpy/core/src/multiarray/datetime_busday.c | 16 | ||||
-rw-r--r-- | numpy/core/src/multiarray/datetime_busdaycal.c | 123 | ||||
-rw-r--r-- | numpy/core/tests/test_datetime.py | 28 | ||||
-rw-r--r-- | numpy/lib/_iotools.py | 33 | ||||
-rw-r--r-- | numpy/lib/function_base.py | 6 | ||||
-rw-r--r-- | numpy/lib/tests/test_function_base.py | 5 | ||||
-rw-r--r-- | numpy/lib/tests/test_io.py | 9 | ||||
-rw-r--r-- | numpy/lib/tests/test_twodim_base.py | 5 | ||||
-rw-r--r-- | numpy/ma/core.py | 4 | ||||
-rw-r--r-- | pavement.py | 22 | ||||
-rw-r--r-- | release.sh | 19 |
14 files changed, 384 insertions, 187 deletions
diff --git a/doc/source/reference/arrays.datetime.rst b/doc/source/reference/arrays.datetime.rst index 2fd6dfcdd..d0f037bcb 100644 --- a/doc/source/reference/arrays.datetime.rst +++ b/doc/source/reference/arrays.datetime.rst @@ -23,7 +23,7 @@ be either a :ref:`date unit <arrays.dtypes.dateunits>` or a :ref:`time unit <arrays.dtypes.timeunits>`. The date units are years ('Y'), months ('M'), weeks ('W'), and days ('D'), while the time units are hours ('h'), minutes ('m'), seconds ('s'), milliseconds ('ms'), and -more SI-prefix seconds-based units. +some additional SI-prefix seconds-based units. .. admonition:: Example @@ -139,7 +139,7 @@ simple datetime calculations. >>> np.timedelta64(1,'W') / np.timedelta64(1,'D') 7.0 -There are two Timedelta units, years and months, which are treated +There are two Timedelta units ('Y', years and 'M', months) which are treated specially, because how much time they represent changes depending on when they are used. While a timedelta day unit is equivalent to 24 hours, there is no way to convert a month unit into days, because @@ -167,7 +167,13 @@ other units based on input data. Datetimes are always stored based on POSIX time (though having a TAI mode which allows for accounting of leap-seconds is proposed), with a epoch of 1970-01-01T00:00Z. This means the supported dates are -always a symmetric interval around 1970. +always a symmetric interval around the epoch, called "time span" in the +table below. + +The length of the span is the range of a 64-bit integer times the length +of the date or unit. For example, the time span for 'W' (week) is exactly +7 times longer than the time span for 'D' (day), and the time span for +'D' (day) is exactly 24 times longer than the time span for 'h' (hour). Here are the date units: @@ -176,10 +182,10 @@ Here are the date units: ======== ================ ======================= ========================== Code Meaning Time span (relative) Time span (absolute) ======== ================ ======================= ========================== - Y year +- 9.2e18 years [9.2e18 BC, 9.2e18 AD] - M month +- 7.6e17 years [7.6e17 BC, 7.6e17 AD] - W week +- 1.7e17 years [1.7e17 BC, 1.7e17 AD] - D day +- 2.5e16 years [2.5e16 BC, 2.5e16 AD] + Y year +/- 9.2e18 years [9.2e18 BC, 9.2e18 AD] + M month +/- 7.6e17 years [7.6e17 BC, 7.6e17 AD] + W week +/- 1.7e17 years [1.7e17 BC, 1.7e17 AD] + D day +/- 2.5e16 years [2.5e16 BC, 2.5e16 AD] ======== ================ ======================= ========================== And here are the time units: @@ -189,28 +195,34 @@ And here are the time units: ======== ================ ======================= ========================== Code Meaning Time span (relative) Time span (absolute) ======== ================ ======================= ========================== - h hour +- 1.0e15 years [1.0e15 BC, 1.0e15 AD] - m minute +- 1.7e13 years [1.7e13 BC, 1.7e13 AD] - s second +- 2.9e12 years [ 2.9e9 BC, 2.9e9 AD] - ms millisecond +- 2.9e9 years [ 2.9e6 BC, 2.9e6 AD] - us microsecond +- 2.9e6 years [290301 BC, 294241 AD] - ns nanosecond +- 292 years [ 1678 AD, 2262 AD] - ps picosecond +- 106 days [ 1969 AD, 1970 AD] - fs femtosecond +- 2.6 hours [ 1969 AD, 1970 AD] - as attosecond +- 9.2 seconds [ 1969 AD, 1970 AD] + h hour +/- 1.0e15 years [1.0e15 BC, 1.0e15 AD] + m minute +/- 1.7e13 years [1.7e13 BC, 1.7e13 AD] + s second +/- 2.9e12 years [ 2.9e9 BC, 2.9e9 AD] + ms millisecond +/- 2.9e9 years [ 2.9e6 BC, 2.9e6 AD] + us microsecond +/- 2.9e6 years [290301 BC, 294241 AD] + ns nanosecond +/- 292 years [ 1678 AD, 2262 AD] + ps picosecond +/- 106 days [ 1969 AD, 1970 AD] + fs femtosecond +/- 2.6 hours [ 1969 AD, 1970 AD] + as attosecond +/- 9.2 seconds [ 1969 AD, 1970 AD] ======== ================ ======================= ========================== Business Day Functionality ========================== -To allow the datetime to be used in contexts where accounting for weekends -and holidays is important, NumPy includes a set of functions for -working with business days. +To allow the datetime to be used in contexts where only certain days of +the week are valid, NumPy includes a set of "busday" (business day) +functions. + +The default for busday functions is that the only valid days are Monday +through Friday (the usual business days). The implementation is based on +a "weekmask" containing 7 Boolean flags to indicate valid days; custom +weekmasks are possible that specify other sets of valid days. + +The "busday" functions can additionally check a list of "holiday" dates, +specific dates that are not valid days. The function :func:`busday_offset` allows you to apply offsets -specified in business days to datetimes with a unit of 'day'. By default, -a business date is defined to be any date which falls on Monday through -Friday, but this can be customized with a weekmask and a list of holidays. +specified in business days to datetimes with a unit of 'D' (day). .. admonition:: Example @@ -266,7 +278,7 @@ is necessary to get a desired answer. The function is also useful for computing some kinds of days like holidays. In Canada and the U.S., Mother's day is on -the second Sunday in May, which can be computed with a special +the second Sunday in May, which can be computed with a custom weekmask. .. admonition:: Example @@ -274,11 +286,66 @@ weekmask. >>> np.busday_offset('2012-05', 1, roll='forward', weekmask='Sun') numpy.datetime64('2012-05-13','D') -When performance is important for manipulating many business date +When performance is important for manipulating many business dates with one particular choice of weekmask and holidays, there is an object :class:`busdaycalendar` which stores the data necessary in an optimized form. -The other two functions for business days are :func:`is_busday` -and :func:`busday_count`, which are more straightforward and -not explained here. +np.is_busday(): +``````````````` +To test a datetime64 value to see if it is a valid day, use :func:`is_busday`. + +.. admonition:: Example + + >>> np.is_busday(np.datetime64('2011-07-15')) # a Friday + True + >>> np.is_busday(np.datetime64('2011-07-16')) # a Saturday + False + >>> np.is_busday(np.datetime64('2011-07-16'), weekmask="Sat Sun") + True + >>> a = np.arange(np.datetime64('2011-07-11'), np.datetime64('2011-07-18')) + >>> np.is_busday(a) + array([ True, True, True, True, True, False, False], dtype='bool') + +np.busday_count(): +`````````````````` +To find how many valid days there are in a specified range of datetime64 +dates, use :func:`busday_count`: + +.. admonition:: Example + + >>> np.busday_count(np.datetime64('2011-07-11'), np.datetime64('2011-07-18')) + 5 + >>> np.busday_count(np.datetime64('2011-07-18'), np.datetime64('2011-07-11')) + -5 + +If you have an array of datetime64 day values, and you want a count of +how many of them are valid dates, you can do this: + +.. admonition:: Example + + >>> a = np.arange(np.datetime64('2011-07-11'), np.datetime64('2011-07-18')) + >>> np.count_nonzero(np.is_busday(a)) + 5 + + + +Custom Weekmasks +---------------- + +Here are several examples of custom weekmask values. These examples +specify the "busday" default of Monday through Friday being valid days. + +Some examples:: + + # Positional sequences; positions are Monday through Sunday. + # Length of the sequence must be exactly 7. + weekmask = [1, 1, 1, 1, 1, 0, 0] + # list or other sequence; 0 == invalid day, 1 == valid day + weekmask = "1111100" + # string '0' == invalid day, '1' == valid day + + # string abbreviations from this list: Mon Tue Wed Thu Fri Sat Sun + weekmask = "Mon Tue Wed Thu Fri" + # any amount of whitespace is allowed; abbreviations are case-sensitive. + weekmask = "MonTue Wed Thu\tFri" diff --git a/numpy/add_newdocs.py b/numpy/add_newdocs.py index 1e1d237a4..334bd8c4b 100644 --- a/numpy/add_newdocs.py +++ b/numpy/add_newdocs.py @@ -5993,44 +5993,52 @@ add_newdoc('numpy.core.multiarray', 'busdaycalendar', """ busdaycalendar(weekmask='1111100', holidays=None) - A business day calendar object that efficiently stores - information defining business days for the business - day-related functions. + A business day calendar object that efficiently stores information + defining valid days for the busday family of functions. + + The default valid days are Monday through Friday ("business days"). + A busdaycalendar object can be specified with any set of weekly + valid days, plus an optional "holiday" dates that always will be invalid. + + Once a busdaycalendar object is created, the weekmask and holidays + cannot be modified. .. versionadded:: 1.7.0 Parameters ---------- weekmask : str or array_like of bool, optional - A seven-element array indicating which of Monday through Sunday may - be valid business days. May be specified as a list or array, like - [1,1,1,1,1,0,0], a length-seven string like '1111100', or a string - of three-letter weekday names, like 'MonTueWedThuFri'. The latter - string representation is most useful when only one day of the - week is important, like 'Mon' if you want to calculate the date - of Easter. + A seven-element array indicating which of Monday through Sunday are + valid days. May be specified as a length-seven list or array, like + [1,1,1,1,1,0,0]; a length-seven string, like '1111100'; or a string + like "Mon Tue Wed Thu Fri", made up of 3-character abbreviations for + weekdays, optionally separated by white space. Valid abbreviations + are: Mon Tue Wed Thu Fri Sat Sun holidays : array_like of datetime64[D], optional - An array of dates which should be blacked out from being considered - as business days. They may be specified in any order, and NaT - (not-a-time) dates are ignored. Internally, this list is normalized - into a form suited for fast business day calculations. + An array of dates to consider as invalid dates, no matter which + weekday they fall upon. Holiday dates may be specified in any + order, and NaT (not-a-time) dates are ignored. This list is + saved in a normalized form that is suited for fast calculations + of valid days. Returns ------- out : busdaycalendar A business day calendar object containing the specified - weekmask and holidays. + weekmask and holidays values. See Also -------- - is_busday : Returns a boolean array indicating valid business days. - busday_offset : Applies an offset counted in business days. - busday_count : Counts how many business days are in a half-open date range. + is_busday : Returns a boolean array indicating valid days. + busday_offset : Applies an offset counted in valid days. + busday_count : Counts how many valid days are in a half-open date range. Attributes ---------- - weekmask : seven-element array of bool - holidays : sorted array of datetime64[D] + Note: once a busdaycalendar object is created, you cannot modify the + weekmask or holidays. The attributes return copies of internal data. + weekmask : (copy) seven-element array of bool + holidays : (copy) sorted array of datetime64[D] Examples -------- @@ -6046,17 +6054,16 @@ add_newdoc('numpy.core.multiarray', 'busdaycalendar', """) add_newdoc('numpy.core.multiarray', 'busdaycalendar', ('weekmask', - """A copy of the seven-element boolean mask indicating valid business days.""")) + """A copy of the seven-element boolean mask indicating valid days.""")) add_newdoc('numpy.core.multiarray', 'busdaycalendar', ('holidays', - """A copy of the holiday array indicating blacked out business days.""")) + """A copy of the holiday array indicating additional invalid days.""")) add_newdoc('numpy.core.multiarray', 'is_busday', """ is_busday(dates, weekmask='1111100', holidays=None, busdaycal=None, out=None) - Calculates which of the given dates are valid business days, and - which are not. + Calculates which of the given dates are valid days, and which are not. .. versionadded:: 1.7.0 @@ -6065,20 +6072,19 @@ add_newdoc('numpy.core.multiarray', 'is_busday', dates : array_like of datetime64[D] The array of dates to process. weekmask : str or array_like of bool, optional - A seven-element array indicating which of Monday through Sunday may - be valid business days. May be specified as a list or array, like - [1,1,1,1,1,0,0], a length-seven string like '1111100', or a string - of three-letter weekday names, like 'MonTueWedThuFri'. The latter - string representation is most useful when only one day of the - week is important, like 'Mon' if you want to calculate the date - of Easter. + A seven-element array indicating which of Monday through Sunday are + valid days. May be specified as a length-seven list or array, like + [1,1,1,1,1,0,0]; a length-seven string, like '1111100'; or a string + like "Mon Tue Wed Thu Fri", made up of 3-character abbreviations for + weekdays, optionally separated by white space. Valid abbreviations + are: Mon Tue Wed Thu Fri Sat Sun holidays : array_like of datetime64[D], optional - An array of dates which should be blacked out from being considered - as business days. They may be specified in any order, and NaT - (not-a-time) dates are ignored. Internally, this list is normalized - into a form suited for fast business day calculations. + An array of dates to consider as invalid dates. They may be + specified in any order, and NaT (not-a-time) dates are ignored. + This list is saved in a normalized form that is suited for + fast calculations of valid days. busdaycal : busdaycalendar, optional - A `busdaycalendar` object which specifies the business days. If this + A `busdaycalendar` object which specifies the valid days. If this parameter is provided, neither weekmask nor holidays may be provided. out : array of bool, optional @@ -6088,13 +6094,13 @@ add_newdoc('numpy.core.multiarray', 'is_busday', ------- out : array of bool An array with the same shape as ``dates``, containing True for - each valid business day, and False for the others. + each valid day, and False for each invalid day. See Also -------- - busdaycalendar: An object for efficiently specifying which are business days. - busday_offset : Applies an offset counted in business days. - busday_count : Counts how many business days are in a half-open date range. + busdaycalendar: An object that specifies a custom set of valid days. + busday_offset : Applies an offset counted in valid days. + busday_count : Counts how many valid days are in a half-open date range. Examples -------- @@ -6108,9 +6114,9 @@ add_newdoc('numpy.core.multiarray', 'busday_offset', """ busday_offset(dates, offsets, roll='raise', weekmask='1111100', holidays=None, busdaycal=None, out=None) - First adjusts the date to fall on a business day according to + First adjusts the date to fall on a valid day according to the ``roll`` rule, then applies offsets to the given dates - counted in business days. + counted in valid days. .. versionadded:: 1.7.0 @@ -6121,36 +6127,35 @@ add_newdoc('numpy.core.multiarray', 'busday_offset', offsets : array_like of int The array of offsets, which is broadcast with ``dates``. roll : {'raise', 'nat', 'forward', 'following', 'backward', 'preceding', 'modifiedfollowing', 'modifiedpreceding'}, optional - How to treat dates that do not fall on a business day. The default + How to treat dates that do not fall on a valid day. The default is 'raise'. - * 'raise' means to raise an exception for invalid business days. - * 'nat' means to return a NaT (not-a-time) for invalid business days. - * 'forward' and 'following' mean to take the first business day + * 'raise' means to raise an exception for an invalid day. + * 'nat' means to return a NaT (not-a-time) for an invalid day. + * 'forward' and 'following' mean to take the first valid day later in time. - * 'backward' and 'preceding' mean to take the first business day + * 'backward' and 'preceding' mean to take the first valid day earlier in time. - * 'modifiedfollowing' means to take the first business day + * 'modifiedfollowing' means to take the first valid day later in time unless it is across a Month boundary, in which - case to take the first business day earlier in time. - * 'modifiedpreceding' means to take the first business day + case to take the first valid day earlier in time. + * 'modifiedpreceding' means to take the first valid day earlier in time unless it is across a Month boundary, in which - case to take the first business day later in time. + case to take the first valid day later in time. weekmask : str or array_like of bool, optional - A seven-element array indicating which of Monday through Sunday may - be valid business days. May be specified as a list or array, like - [1,1,1,1,1,0,0], a length-seven string like '1111100', or a string - of three-letter weekday names, like 'MonTueWedThuFri'. The latter - string representation is most useful when only one day of the - week is important, like 'Mon' if you want to calculate the date - of Easter. + A seven-element array indicating which of Monday through Sunday are + valid days. May be specified as a length-seven list or array, like + [1,1,1,1,1,0,0]; a length-seven string, like '1111100'; or a string + like "Mon Tue Wed Thu Fri", made up of 3-character abbreviations for + weekdays, optionally separated by white space. Valid abbreviations + are: Mon Tue Wed Thu Fri Sat Sun holidays : array_like of datetime64[D], optional - An array of dates which should be blacked out from being considered - as business days. They may be specified in any order, and NaT - (not-a-time) dates are ignored. Internally, this list is normalized - into a form suited for fast business day calculations. + An array of dates to consider as invalid dates. They may be + specified in any order, and NaT (not-a-time) dates are ignored. + This list is saved in a normalized form that is suited for + fast calculations of valid days. busdaycal : busdaycalendar, optional - A `busdaycalendar` object which specifies the business days. If this + A `busdaycalendar` object which specifies the valid days. If this parameter is provided, neither weekmask nor holidays may be provided. out : array of datetime64[D], optional @@ -6164,9 +6169,9 @@ add_newdoc('numpy.core.multiarray', 'busday_offset', See Also -------- - busdaycalendar: An object for efficiently specifying which are business days. - is_busday : Returns a boolean array indicating valid business days. - busday_count : Counts how many business days are in a half-open date range. + busdaycalendar: An object that specifies a custom set of valid days. + is_busday : Returns a boolean array indicating valid days. + busday_count : Counts how many valid days are in a half-open date range. Examples -------- @@ -6199,9 +6204,12 @@ add_newdoc('numpy.core.multiarray', 'busday_count', """ busday_count(begindates, enddates, weekmask='1111100', holidays=[], busdaycal=None, out=None) - Counts the number of business days between `begindates` and + Counts the number of valid days between `begindates` and `enddates`, not including the day of `enddates`. + If ``enddates`` specifies a date value that is earlier than the + corresponding ``begindates`` date value, the count will be negative. + .. versionadded:: 1.7.0 Parameters @@ -6212,20 +6220,19 @@ add_newdoc('numpy.core.multiarray', 'busday_count', The array of the end dates for counting, which are excluded from the count themselves. weekmask : str or array_like of bool, optional - A seven-element array indicating which of Monday through Sunday may - be valid business days. May be specified as a list or array, like - [1,1,1,1,1,0,0], a length-seven string like '1111100', or a string - of three-letter weekday names, like 'MonTueWedThuFri'. The latter - string representation is most useful when only one day of the - week is important, like 'Mon' if you want to calculate the date - of Easter. + A seven-element array indicating which of Monday through Sunday are + valid days. May be specified as a length-seven list or array, like + [1,1,1,1,1,0,0]; a length-seven string, like '1111100'; or a string + like "Mon Tue Wed Thu Fri", made up of 3-character abbreviations for + weekdays, optionally separated by white space. Valid abbreviations + are: Mon Tue Wed Thu Fri Sat Sun holidays : array_like of datetime64[D], optional - An array of dates which should be blacked out from being considered - as business days. They may be specified in any order, and NaT - (not-a-time) dates are ignored. Internally, this list is normalized - into a form suited for fast business day calculations. + An array of dates to consider as invalid dates. They may be + specified in any order, and NaT (not-a-time) dates are ignored. + This list is saved in a normalized form that is suited for + fast calculations of valid days. busdaycal : busdaycalendar, optional - A `busdaycalendar` object which specifies the business days. If this + A `busdaycalendar` object which specifies the valid days. If this parameter is provided, neither weekmask nor holidays may be provided. out : array of int, optional @@ -6235,14 +6242,14 @@ add_newdoc('numpy.core.multiarray', 'busday_count', ------- out : array of int An array with a shape from broadcasting ``begindates`` and ``enddates`` - together, containing the number of business days between + together, containing the number of valid days between the begin and end dates. See Also -------- - busdaycalendar: An object for efficiently specifying which are business days. - is_busday : Returns a boolean array indicating valid business days. - busday_offset : Applies an offset counted in business days. + busdaycalendar: An object that specifies a custom set of valid days. + is_busday : Returns a boolean array indicating valid days. + busday_offset : Applies an offset counted in valid days. Examples -------- diff --git a/numpy/core/_internal.py b/numpy/core/_internal.py index 713687199..3d6702095 100644 --- a/numpy/core/_internal.py +++ b/numpy/core/_internal.py @@ -90,8 +90,11 @@ def _array_descr(descriptor): else: new = descriptor.metadata.copy() # Eliminate any key related to internal implementation - _ = new.pop(METADATA_DTSTR, None) - return (descriptor.str, new) + new.pop(METADATA_DTSTR, None) + if new: + return (descriptor.str, new) + else: + return descriptor.str else: return (_array_descr(subdtype[0]), subdtype[1]) diff --git a/numpy/core/src/multiarray/datetime_busday.c b/numpy/core/src/multiarray/datetime_busday.c index 7ad033248..5a0078b76 100644 --- a/numpy/core/src/multiarray/datetime_busday.c +++ b/numpy/core/src/multiarray/datetime_busday.c @@ -365,6 +365,7 @@ apply_business_day_count(npy_datetime date_begin, npy_datetime date_end, { npy_int64 count, whole_weeks; int day_of_week = 0; + int swapped = 0; /* If we get a NaT, raise an error */ if (date_begin == NPY_DATETIME_NAT || date_end == NPY_DATETIME_NAT) { @@ -375,10 +376,16 @@ apply_business_day_count(npy_datetime date_begin, npy_datetime date_end, } /* Trivial empty date range */ - if (date_begin >= date_end) { + if (date_begin == date_end) { *out = 0; return 0; } + else if (date_begin > date_end) { + npy_datetime tmp = date_begin; + date_begin = date_end; + date_end = tmp; + swapped = 1; + } /* Remove any earlier holidays */ holidays_begin = find_earliest_holiday_on_or_after(date_begin, @@ -411,6 +418,10 @@ apply_business_day_count(npy_datetime date_begin, npy_datetime date_end, } } + if (swapped) { + count = -count; + } + *out = count; return 0; } @@ -563,6 +574,9 @@ finish: * the end date. This is the low-level function which requires already * cleaned input data. * + * If dates_begin is before dates_end, the result is positive. If + * dates_begin is after dates_end, it is negative. + * * dates_begin: An array of dates with 'datetime64[D]' data type. * dates_end: An array of dates with 'datetime64[D]' data type. * out: Either NULL, or an array with 'int64' data type diff --git a/numpy/core/src/multiarray/datetime_busdaycal.c b/numpy/core/src/multiarray/datetime_busdaycal.c index c406fac7b..1d047a547 100644 --- a/numpy/core/src/multiarray/datetime_busdaycal.c +++ b/numpy/core/src/multiarray/datetime_busdaycal.c @@ -46,6 +46,7 @@ PyArray_WeekMaskConverter(PyObject *weekmask_in, npy_bool *weekmask) if (PyBytes_Check(obj)) { char *str; Py_ssize_t len; + int i; if (PyBytes_AsStringAndSize(obj, &str, &len) < 0) { Py_DECREF(obj); @@ -54,7 +55,6 @@ PyArray_WeekMaskConverter(PyObject *weekmask_in, npy_bool *weekmask) /* Length 7 is a string like "1111100" */ if (len == 7) { - int i; for (i = 0; i < 7; ++i) { switch(str[i]) { case '0': @@ -64,70 +64,81 @@ PyArray_WeekMaskConverter(PyObject *weekmask_in, npy_bool *weekmask) weekmask[i] = 1; break; default: - goto invalid_weekmask_string; + goto general_weekmask_string; } } goto finish; } - /* Length divisible by 3 is a string like "Mon" or "MonWedFri" */ - else if (len % 3 == 0) { - int i; - memset(weekmask, 0, 7); - for (i = 0; i < len; i += 3) { - switch (str[i]) { - case 'M': - if (str[i+1] == 'o' && str[i+2] == 'n') { - weekmask[0] = 1; - } - else { - goto invalid_weekmask_string; - } - break; - case 'T': - if (str[i+1] == 'u' && str[i+2] == 'e') { - weekmask[1] = 1; - } - else if (str[i+1] == 'h' && str[i+2] == 'u') { - weekmask[3] = 1; - } - else { - goto invalid_weekmask_string; - } - break; - case 'W': - if (str[i+1] == 'e' && str[i+2] == 'd') { - weekmask[2] = 1; - } - else { - goto invalid_weekmask_string; - } - break; - case 'F': - if (str[i+1] == 'r' && str[i+2] == 'i') { - weekmask[4] = 1; - } - else { - goto invalid_weekmask_string; - } - break; - case 'S': - if (str[i+1] == 'a' && str[i+2] == 't') { - weekmask[5] = 1; - } - else if (str[i+1] == 'u' && str[i+2] == 'n') { - weekmask[6] = 1; - } - else { - goto invalid_weekmask_string; - } - break; - } + +general_weekmask_string: + /* a string like "SatSun" or "Mon Tue Wed" */ + memset(weekmask, 0, 7); + for (i = 0; i < len; i += 3) { + while (isspace(str[i])) + ++i; + + if (i == len) { + goto finish; + } + else if (i + 2 >= len) { + goto invalid_weekmask_string; } - goto finish; + switch (str[i]) { + case 'M': + if (str[i+1] == 'o' && str[i+2] == 'n') { + weekmask[0] = 1; + } + else { + goto invalid_weekmask_string; + } + break; + case 'T': + if (str[i+1] == 'u' && str[i+2] == 'e') { + weekmask[1] = 1; + } + else if (str[i+1] == 'h' && str[i+2] == 'u') { + weekmask[3] = 1; + } + else { + goto invalid_weekmask_string; + } + break; + case 'W': + if (str[i+1] == 'e' && str[i+2] == 'd') { + weekmask[2] = 1; + } + else { + goto invalid_weekmask_string; + } + break; + case 'F': + if (str[i+1] == 'r' && str[i+2] == 'i') { + weekmask[4] = 1; + } + else { + goto invalid_weekmask_string; + } + break; + case 'S': + if (str[i+1] == 'a' && str[i+2] == 't') { + weekmask[5] = 1; + } + else if (str[i+1] == 'u' && str[i+2] == 'n') { + weekmask[6] = 1; + } + else { + goto invalid_weekmask_string; + } + break; + default: + goto invalid_weekmask_string; + } } + goto finish; + invalid_weekmask_string: PyErr_Format(PyExc_ValueError, "Invalid business day weekmask string \"%s\"", diff --git a/numpy/core/tests/test_datetime.py b/numpy/core/tests/test_datetime.py index 82577c0fc..a624768b6 100644 --- a/numpy/core/tests/test_datetime.py +++ b/numpy/core/tests/test_datetime.py @@ -1440,8 +1440,28 @@ class TestDateTime(TestCase): # Default M-F weekmask assert_equal(bdd.weekmask, np.array([1,1,1,1,1,0,0], dtype='?')) + # Check string weekmask with varying whitespace. + bdd = np.busdaycalendar(weekmask="Sun TueWed Thu\tFri") + assert_equal(bdd.weekmask, np.array([0,1,1,1,1,0,1], dtype='?')) + + # Check length 7 0/1 string + bdd = np.busdaycalendar(weekmask="0011001") + assert_equal(bdd.weekmask, np.array([0,0,1,1,0,0,1], dtype='?')) + + # Check length 7 string weekmask. + bdd = np.busdaycalendar(weekmask="Mon Tue") + assert_equal(bdd.weekmask, np.array([1,1,0,0,0,0,0], dtype='?')) + # All-zeros weekmask should raise assert_raises(ValueError, np.busdaycalendar, weekmask=[0,0,0,0,0,0,0]) + # weekday names must be correct case + assert_raises(ValueError, np.busdaycalendar, weekmask="satsun") + # All-zeros weekmask should raise + assert_raises(ValueError, np.busdaycalendar, weekmask="") + # Invalid weekday name codes should raise + assert_raises(ValueError, np.busdaycalendar, weekmask="Mon Tue We") + assert_raises(ValueError, np.busdaycalendar, weekmask="Max") + assert_raises(ValueError, np.busdaycalendar, weekmask="Monday Tue") def test_datetime_busday_holidays_offset(self): # With exactly one holiday @@ -1624,11 +1644,17 @@ class TestDateTime(TestCase): roll='forward', busdaycal=bdd) assert_equal(np.busday_count('2011-01-01', dates, busdaycal=bdd), np.arange(366)) + # Returns negative value when reversed + assert_equal(np.busday_count(dates, '2011-01-01', busdaycal=bdd), + -np.arange(366)) dates = np.busday_offset('2011-12-31', -np.arange(366), roll='forward', busdaycal=bdd) assert_equal(np.busday_count(dates, '2011-12-31', busdaycal=bdd), np.arange(366)) + # Returns negative value when reversed + assert_equal(np.busday_count('2011-12-31', dates, busdaycal=bdd), + -np.arange(366)) # Can't supply both a weekmask/holidays and busdaycal assert_raises(ValueError, np.busday_offset, '2012-01-03', '2012-02-03', @@ -1638,6 +1664,8 @@ class TestDateTime(TestCase): # Number of Mondays in March 2011 assert_equal(np.busday_count('2011-03', '2011-04', weekmask='Mon'), 4) + # Returns negative value when reversed + assert_equal(np.busday_count('2011-04', '2011-03', weekmask='Mon'), -4) def test_datetime_is_busday(self): holidays=['2011-01-01', '2011-10-10', '2011-11-11', '2011-11-24', diff --git a/numpy/lib/_iotools.py b/numpy/lib/_iotools.py index 27c1e76db..7921b4116 100644 --- a/numpy/lib/_iotools.py +++ b/numpy/lib/_iotools.py @@ -503,10 +503,25 @@ class StringConverter(object): (_defaulttype, _defaultfunc, _defaultfill) = zip(*_mapper) # @classmethod + def _getdtype(cls, val): + """Returns the dtype of the input variable.""" + return np.array(val).dtype + # + @classmethod def _getsubdtype(cls, val): """Returns the type of the dtype of the input variable.""" return np.array(val).dtype.type # + # This is a bit annoying. We want to return the "general" type in most cases + # (ie. "string" rather than "S10"), but we want to return the specific type + # for datetime64 (ie. "datetime64[us]" rather than "datetime64"). + @classmethod + def _dtypeortype(cls, dtype): + """Returns dtype for datetime64 and type of dtype otherwise.""" + if dtype.type == np.datetime64: + return dtype + return dtype.type + # @classmethod def upgrade_mapper(cls, func, default=None): """ @@ -561,12 +576,12 @@ class StringConverter(object): self.func = str2bool self._status = 0 self.default = default or False - ttype = np.bool + dtype = np.dtype('bool') else: # Is the input a np.dtype ? try: self.func = None - ttype = np.dtype(dtype_or_func).type + dtype = np.dtype(dtype_or_func) except TypeError: # dtype_or_func must be a function, then if not hasattr(dtype_or_func, '__call__'): @@ -581,11 +596,11 @@ class StringConverter(object): default = self.func(asbytes('0')) except ValueError: default = None - ttype = self._getsubdtype(default) + dtype = self._getdtype(default) # Set the status according to the dtype _status = -1 for (i, (deftype, func, default_def)) in enumerate(self._mapper): - if np.issubdtype(ttype, deftype): + if np.issubdtype(dtype.type, deftype): _status = i if default is None: self.default = default_def @@ -603,9 +618,9 @@ class StringConverter(object): # If the status is 1 (int), change the function to # something more robust. if self.func == self._mapper[1][1]: - if issubclass(ttype, np.uint64): + if issubclass(dtype.type, np.uint64): self.func = np.uint64 - elif issubclass(ttype, np.int64): + elif issubclass(dtype.type, np.int64): self.func = np.int64 else: self.func = lambda x : int(float(x)) @@ -618,7 +633,7 @@ class StringConverter(object): self.missing_values = set(list(missing_values) + [asbytes('')]) # self._callingfunction = self._strict_call - self.type = ttype + self.type = self._dtypeortype(dtype) self._checked = False self._initial_default = default # @@ -747,13 +762,13 @@ class StringConverter(object): # Don't reset the default to None if we can avoid it if default is not None: self.default = default - self.type = self._getsubdtype(default) + self.type = self._dtypeortype(self._getdtype(default)) else: try: tester = func(testing_value or asbytes('1')) except (TypeError, ValueError): tester = None - self.type = self._getsubdtype(tester) + self.type = self._dtypeortype(self._getdtype(tester)) # Add the missing values to the existing set if missing_values is not None: if _is_bytes_like(missing_values): diff --git a/numpy/lib/function_base.py b/numpy/lib/function_base.py index d20304a1b..8a0c0b37d 100644 --- a/numpy/lib/function_base.py +++ b/numpy/lib/function_base.py @@ -335,11 +335,11 @@ def histogramdd(sample, bins=10, range=None, normed=False, weights=None): Found bin edge of size <= 0. Did you specify `bins` with non-monotonic sequence?""") + nbin = asarray(nbin) + # Handle empty input. if N == 0: - return np.zeros(D), edges - - nbin = asarray(nbin) + return np.zeros(nbin-2), edges # Compute the bin number each sample falls into. Ncount = {} diff --git a/numpy/lib/tests/test_function_base.py b/numpy/lib/tests/test_function_base.py index 9df1a916f..105389d6d 100644 --- a/numpy/lib/tests/test_function_base.py +++ b/numpy/lib/tests/test_function_base.py @@ -749,7 +749,10 @@ class TestHistogramdd(TestCase): def test_empty(self): a, b = histogramdd([[], []], bins=([0,1], [0,1])) - assert_array_max_ulp(a, array([ 0., 0.])) + assert_array_max_ulp(a, array([[ 0.]])) + a, b = np.histogramdd([[], [], []], bins=2) + assert_array_max_ulp(a, np.zeros((2, 2, 2))) + def test_bins_errors(self): """There are two ways to specify bins. Check for the right errors when diff --git a/numpy/lib/tests/test_io.py b/numpy/lib/tests/test_io.py index e83c82ecd..f9da258dc 100644 --- a/numpy/lib/tests/test_io.py +++ b/numpy/lib/tests/test_io.py @@ -776,6 +776,15 @@ M 33 21.99 dtype=[('date', np.object_), ('stid', float)]) assert_equal(test, control) + def test_converters_cornercases2(self): + "Test the conversion to datetime64." + converter = {'date': lambda s: np.datetime64(strptime(s, '%Y-%m-%d %H:%M:%SZ'))} + data = StringIO('2009-02-03 12:00:00Z, 72214.0') + test = np.ndfromtxt(data, delimiter=',', dtype=None, + names=['date', 'stid'], converters=converter) + control = np.array((datetime(2009, 02, 03), 72214.), + dtype=[('date', 'datetime64[us]'), ('stid', float)]) + assert_equal(test, control) def test_unused_converter(self): "Test whether unused converters are forgotten" diff --git a/numpy/lib/tests/test_twodim_base.py b/numpy/lib/tests/test_twodim_base.py index a7c0e85c8..37165f672 100644 --- a/numpy/lib/tests/test_twodim_base.py +++ b/numpy/lib/tests/test_twodim_base.py @@ -224,7 +224,10 @@ class TestHistogram2d(TestCase): def test_empty(self): a, edge1, edge2 = histogram2d([],[], bins=([0,1],[0,1])) - assert_array_max_ulp(a, array([ 0., 0.])) + assert_array_max_ulp(a, array([[ 0.]])) + + a, edge1, edge2 = histogram2d([], [], bins=4) + assert_array_max_ulp(a, np.zeros((4, 4))) class TestTri(TestCase): diff --git a/numpy/ma/core.py b/numpy/ma/core.py index 4c40a7c30..e2e954a97 100644 --- a/numpy/ma/core.py +++ b/numpy/ma/core.py @@ -143,6 +143,8 @@ default_filler = {'b': True, 'u' : 999999, 'V' : '???', 'U' : 'N/A', + 'M8[D]' : np.datetime64('NaT', 'D'), + 'M8[us]' : np.datetime64('NaT', 'us') } max_filler = ntypes._minvals max_filler.update([(k, -np.inf) for k in [np.float32, np.float64]]) @@ -198,6 +200,8 @@ def default_fill_value(obj): elif isinstance(obj, np.dtype): if obj.subdtype: defval = default_filler.get(obj.subdtype[0].kind, '?') + elif obj.kind == 'M': + defval = default_filler.get(obj.str[1:], '?') else: defval = default_filler.get(obj.kind, '?') elif isinstance(obj, float): diff --git a/pavement.py b/pavement.py index c5a52b455..afb177279 100644 --- a/pavement.py +++ b/pavement.py @@ -101,7 +101,7 @@ finally: RELEASE_NOTES = 'doc/release/2.0.0-notes.rst' # Start/end of the log (from git) -LOG_START = 'svn/tags/1.5.0' +LOG_START = 'v1.6.0' LOG_END = 'master' @@ -398,8 +398,22 @@ def pdf(): #------------------ # Mac OS X targets #------------------ -def dmg_name(fullversion, pyver): - return "numpy-%s-py%s-python.org.dmg" % (fullversion, pyver) +def dmg_name(fullversion, pyver, osxver=None): + """Return name for dmg installer. + + Notes + ----- + Python 2.7 has two binaries, one for 10.3 (ppc, i386) and one for 10.6 + (i386, x86_64). All other Python versions at python.org at the moment + have binaries for 10.3 only. The "macosx%s" part of the dmg name should + correspond to the python.org naming scheme. + """ + # assume that for the py2.7/osx10.6 build the deployment target is set + # (should be done in the release script). + if not osxver: + osxver = os.environ.get('MACOSX_DEPLOYMENT_TARGET', '10.3') + return "numpy-%s-py%s-python.org-macosx%s.dmg" % (fullversion, pyver, + osxver) def macosx_version(): if not sys.platform == 'darwin': @@ -580,7 +594,7 @@ Checksums def write_log_task(options, filename='Changelog'): st = subprocess.Popen( - ['git', 'svn', 'log', '%s..%s' % (LOG_START, LOG_END)], + ['git', 'log', '%s..%s' % (LOG_START, LOG_END)], stdout=subprocess.PIPE) out = st.communicate()[0] diff --git a/release.sh b/release.sh index bb5a375db..5f1f31ebb 100644 --- a/release.sh +++ b/release.sh @@ -5,6 +5,17 @@ # downloads, i.e. two versions for Python 2.7. The Intel 32/64-bit version is # for OS X 10.6+, the other dmg installers are for 10.3+ and are built on 10.5 +# Check we're using the correct g++/c++ for the 32-bit 2.6 version we build for +# the docs and the 64-bit 2.7 dmg installer. +# We do this because for Python 2.6 we use a symlink on the PATH to select +# /usr/bin/g++-4.0, while for Python 2.7 we need the default 4.2 version. +export PATH=~/Code/tmp/gpp40temp/:$PATH +gpp="$(g++ --version | grep "4.0")" +if [ -z "$gpp" ]; then + echo "Wrong g++ version, we need 4.0 to compile scipy with Python 2.6" + exit 1 +fi + # bootstrap needed to ensure we build the docs from the right scipy version paver bootstrap source bootstrap/bin/activate @@ -19,6 +30,14 @@ paver pdf paver sdist export MACOSX_DEPLOYMENT_TARGET=10.6 +# Use GCC 4.2 for 64-bit OS X installer for Python 2.7 +export PATH=~/Code/tmp/gpp42temp/:$PATH +gpp="$(g++ --version | grep "4.2")" +if [ -z "$gpp" ]; then + echo "Wrong g++ version, we need 4.2 for 64-bit binary for Python 2.7" + exit 1 +fi + paver dmg -p 2.7 # 32/64-bit version paver bdist_superpack -p 3.2 |