@@ -120,6 +120,104 @@ func NewDatetime(t time.Time) (*Datetime, error) {
120120 return dt , nil
121121}
122122
123+ func intervalFromDatetime (dtime * Datetime ) (ival Interval ) {
124+ ival .Year = int64 (dtime .time .Year ())
125+ ival .Month = int64 (dtime .time .Month ())
126+ ival .Day = int64 (dtime .time .Day ())
127+ ival .Hour = int64 (dtime .time .Hour ())
128+ ival .Min = int64 (dtime .time .Minute ())
129+ ival .Sec = int64 (dtime .time .Second ())
130+ ival .Nsec = int64 (dtime .time .Nanosecond ())
131+ ival .Adjust = NoneAdjust
132+
133+ return ival
134+ }
135+
136+ func daysInMonth (year int64 , month int64 ) int64 {
137+ if month == 12 {
138+ year ++
139+ month = 1
140+ } else {
141+ month += 1
142+ }
143+
144+ // We use the fact that time.Date accepts values outside their usual
145+ // ranges - the values are normalized during the conversion.
146+ //
147+ // So we got a day (year, month - 1, last day of the month) before
148+ // (year, month, 1) because we pass (year, month, 0).
149+ return int64 (time .Date (int (year ), time .Month (month ), 0 , 0 , 0 , 0 , 0 , time .UTC ).Day ())
150+ }
151+
152+ // C imlementation:
153+ // https://github.com/tarantool/c-dt/blob/cec6acebb54d9e73ea0b99c63898732abd7683a6/dt_arithmetic.c#L74-L98
154+ func addMonth (ival * Interval , delta int64 , adjust Adjust ) {
155+ oldYear := ival .Year
156+ oldMonth := ival .Month
157+
158+ ival .Month += delta
159+ if ival .Month < 1 || ival .Month > 12 {
160+ ival .Year += ival .Month / 12
161+ ival .Month %= 12
162+ if ival .Month < 1 {
163+ ival .Year --
164+ ival .Month += 12
165+ }
166+ }
167+ if adjust == ExcessAdjust || ival .Day < 28 {
168+ return
169+ }
170+
171+ dim := daysInMonth (ival .Year , ival .Month )
172+ if ival .Day > dim || (adjust == LastAdjust && ival .Day == daysInMonth (oldYear , oldMonth )) {
173+ ival .Day = dim
174+ }
175+ }
176+
177+ func (dtime * Datetime ) add (ival Interval , positive bool ) (* Datetime , error ) {
178+ newVal := intervalFromDatetime (dtime )
179+
180+ var direction int64
181+ if positive {
182+ direction = 1
183+ } else {
184+ direction = - 1
185+ }
186+
187+ addMonth (& newVal , direction * ival .Year * 12 + direction * ival .Month , ival .Adjust )
188+ newVal .Day += direction * 7 * ival .Week
189+ newVal .Day += direction * ival .Day
190+ newVal .Hour += direction * ival .Hour
191+ newVal .Min += direction * ival .Min
192+ newVal .Sec += direction * ival .Sec
193+ newVal .Nsec += direction * ival .Nsec
194+
195+ tm := time .Date (int (newVal .Year ), time .Month (newVal .Month ),
196+ int (newVal .Day ), int (newVal .Hour ), int (newVal .Min ),
197+ int (newVal .Sec ), int (newVal .Nsec ), dtime .time .Location ())
198+
199+ return NewDatetime (tm )
200+ }
201+
202+ // Add creates a new Datetime as addition of the Datetime and Interval. It may
203+ // return an error if a new Datetime is out of supported range.
204+ func (dtime * Datetime ) Add (ival Interval ) (* Datetime , error ) {
205+ return dtime .add (ival , true )
206+ }
207+
208+ // Sub creates a new Datetime as subtraction of the Datetime and Interval. It
209+ // may return an error if a new Datetime is out of supported range.
210+ func (dtime * Datetime ) Sub (ival Interval ) (* Datetime , error ) {
211+ return dtime .add (ival , false )
212+ }
213+
214+ // Interval returns an Interval value to a next Datetime value.
215+ func (dtime * Datetime ) Interval (next * Datetime ) Interval {
216+ curIval := intervalFromDatetime (dtime )
217+ nextIval := intervalFromDatetime (next )
218+ return nextIval .Sub (curIval )
219+ }
220+
123221// ToTime returns a time.Time that Datetime contains.
124222func (dtime * Datetime ) ToTime () time.Time {
125223 return dtime .time
0 commit comments