@@ -22,6 +22,7 @@ cimport ccalendar
22
22
from conversion import tz_localize_to_utc, normalize_i8_timestamps
23
23
from conversion cimport (tz_convert_single, _TSObject,
24
24
convert_to_tsobject, convert_datetime_to_tsobject)
25
+ import enum
25
26
from fields import get_start_end_field, get_date_name_field
26
27
from nattype import NaT
27
28
from nattype cimport NPY_NAT
@@ -57,50 +58,56 @@ cdef inline object create_timestamp_from_ts(int64_t value,
57
58
return ts_base
58
59
59
60
60
- def round_ns (values , rounder , freq ):
61
+ @enum.unique
62
+ class RoundTo (enum .Enum ):
63
+ MINUS_INFTY = 0
64
+ PLUS_INFTY = 1
65
+ NEAREST_HALF_EVEN = 2
66
+ NEAREST_HALF_PLUS_INFTY = 3
67
+ NEAREST_HALF_MINUS_INFTY = 4
68
+
69
+
70
+ def round_nsint64 (values , mode: RoundTo , freq ):
61
71
"""
62
- Applies rounding function at given frequency
72
+ Applies rounding mode at given frequency
63
73
64
74
Parameters
65
75
----------
66
76
values : :obj:`ndarray`
67
- rounder : function, eg. 'ceil', 'floor', 'round'
77
+ mode : instance of `RoundTo` enumeration
68
78
freq : str, obj
69
79
70
80
Returns
71
81
-------
72
82
:obj:`ndarray`
73
83
"""
84
+
85
+ if not isinstance (mode, RoundTo):
86
+ raise ValueError (' mode should be a RoundTo member' )
87
+
74
88
unit = to_offset(freq).nanos
75
89
76
- # GH21262 If the Timestamp is multiple of the freq str
77
- # don't apply any rounding
78
- mask = values % unit == 0
79
- if mask.all():
80
- return values
81
- r = values.copy()
82
-
83
- if unit < 1000 :
84
- # for nano rounding, work with the last 6 digits separately
85
- # due to float precision
86
- buff = 1000000
87
- r[~ mask] = (buff * (values[~ mask] // buff) +
88
- unit * (rounder((values[~ mask] % buff) *
89
- (1 / float (unit)))).astype(' i8' ))
90
- else :
91
- if unit % 1000 != 0 :
92
- msg = ' Precision will be lost using frequency: {}'
93
- warnings.warn(msg.format(freq))
94
- # GH19206
95
- # to deal with round-off when unit is large
96
- if unit >= 1e9 :
97
- divisor = 10 ** int (np.log10(unit / 1e7 ))
98
- else :
99
- divisor = 10
100
- r[~ mask] = (unit * rounder((values[~ mask] *
101
- (divisor / float (unit))) / divisor)
102
- .astype(' i8' ))
103
- return r
90
+ if mode is RoundTo.MINUS_INFTY:
91
+ return values - (values % unit)
92
+ elif mode is RoundTo.PLUS_INFTY:
93
+ return values + (- values % unit)
94
+ elif mode is RoundTo.NEAREST_HALF_MINUS_INFTY:
95
+ return round_nsint64(values - unit// 2 , RoundTo.PLUS_INFTY, freq)
96
+ elif mode is RoundTo.NEAREST_HALF_PLUS_INFTY:
97
+ return round_nsint64(values + unit// 2 , RoundTo.MINUS_INFTY, freq)
98
+ elif mode is RoundTo.NEAREST_HALF_EVEN:
99
+ # for odd unit there is n need of a tie break
100
+ if unit % 2 :
101
+ return round_nsint64(values, RoundTo.NEAREST_HALF_MINUS_INFTY, freq)
102
+ d, r = np.divmod(values, unit)
103
+ mask = np.logical_or(
104
+ r > (unit // 2 ),
105
+ np.logical_and(r == (unit // 2 ), d % 2 )
106
+ )
107
+ d[mask] += 1
108
+ return d * unit
109
+
110
+ raise NotImplementedError (mode)
104
111
105
112
106
113
# This is PITA. Because we inherit from datetime, which has very specific
@@ -656,7 +663,7 @@ class Timestamp(_Timestamp):
656
663
657
664
return create_timestamp_from_ts(ts.value, ts.dts, ts.tzinfo, freq)
658
665
659
- def _round (self , freq , rounder ):
666
+ def _round (self , freq , mode ):
660
667
if self .tz is not None :
661
668
value = self .tz_localize(None ).value
662
669
else :
@@ -665,7 +672,7 @@ class Timestamp(_Timestamp):
665
672
value = np.array([value], dtype = np.int64)
666
673
667
674
# Will only ever contain 1 element for timestamp
668
- r = round_ns (value, rounder , freq)[0 ]
675
+ r = round_nsint64 (value, mode , freq)[0 ]
669
676
result = Timestamp(r, unit = ' ns' )
670
677
if self .tz is not None :
671
678
result = result.tz_localize(self .tz)
@@ -687,7 +694,7 @@ class Timestamp(_Timestamp):
687
694
------
688
695
ValueError if the freq cannot be converted
689
696
"""
690
- return self ._round(freq, np.round )
697
+ return self ._round(freq, RoundTo.NEAREST_HALF_EVEN )
691
698
692
699
def floor (self , freq ):
693
700
"""
@@ -697,7 +704,7 @@ class Timestamp(_Timestamp):
697
704
----------
698
705
freq : a freq string indicating the flooring resolution
699
706
"""
700
- return self ._round(freq, np.floor )
707
+ return self ._round(freq, RoundTo.MINUS_INFTY )
701
708
702
709
def ceil (self , freq ):
703
710
"""
@@ -707,7 +714,7 @@ class Timestamp(_Timestamp):
707
714
----------
708
715
freq : a freq string indicating the ceiling resolution
709
716
"""
710
- return self ._round(freq, np.ceil )
717
+ return self ._round(freq, RoundTo.PLUS_INFTY )
711
718
712
719
@property
713
720
def tz (self ):
0 commit comments