1 /*
2  * Copyright (C) 1995.  Bill Brown <brown@gis.uiuc.edu> & Michael Shapiro
3  *
4  * This program is free software under the GPL (>=v2)
5  * Read the file GPL.TXT coming with GRASS for details.
6  */
7 #include <grass/datetime.h>
8 
9 static int _datetime_add_field(DateTime *, DateTime *, int);
10 static int _datetime_subtract_field(DateTime *, DateTime *, int);
11 
12 /*****************************************************************/
13 #if 0				/* unused */
14 static double _debug_decimal(DateTime * dt)
15 {
16     double dtdec = 0.0;
17 
18     if (dt->mode == DATETIME_RELATIVE) {
19 	if (datetime_in_interval_year_month(dt->from)) {
20 	    dtdec = dt->year + dt->month / 12.;
21 	}
22 	else {
23 	    dtdec = dt->day / 365.25 +
24 		dt->hour / 8766. + dt->minute / 525960.
25 		+ dt->second / 31557600.;
26 	}
27     }
28     if (dt->positive)
29 	return (dtdec);
30     return (-dtdec);
31 }
32 #endif /* unused */
33 
34 /*****************************************************************/
35 
36 /*!
37  * \brief
38  *
39  * This function changes the 'src' date/time data based on the 'incr'
40  * The type (mode/from/to) of the 'src' can be anything.
41  * The mode of the 'incr' must be RELATIVE, and the type (mode/from/to) for
42  * 'incr' must be a valid increment for 'src'. See  <b>datetime_is_valid_increment()</b>,
43  * <b>datetime_check_increment()</b>
44  * Returns:
45  * 0: OK
46  * -1: 'incr' is invalid increment for 'src'
47  * For src.mode ABSOLUTE,
48  * <ul>
49  <li> positive 'incr' moves into the future,
50  </li>
51  <li> negative 'incr' moves into the past.
52  </li>
53  <li> BC implies the year is negative, but all else is positive. Also, year==0
54  * is illegal: adding 1 year to 1[bc] gives 1[ad]
55  </li></ul>
56  * The 'fracsec' in 'src' is preserved.
57  * The 'from/to' of the 'src' is preserved.
58  * A timezone in 'src' is allowed - it's presence is ignored.
59  * NOTE: There is no datetime_decrement() To decrement, set the 'incr' negative.
60 
61  *
62  *  \param src
63  *  \param incr
64  *  \return int
65  */
66 
datetime_increment(DateTime * src,DateTime * incr)67 int datetime_increment(DateTime * src, DateTime * incr)
68 {
69     int i, relfrom;
70     DateTime cpdt, *dt;
71 
72     if (!datetime_is_valid_increment(src, incr))
73 	return datetime_error_code();
74 
75     /* special case - incrementing a relative might try to increment
76        or borrow from a "lower" field than src has,
77        so we use a copy to change from */
78 
79     if (src->mode == DATETIME_RELATIVE) {
80 	datetime_copy(&cpdt, src);
81 	relfrom = datetime_in_interval_day_second(src->from)
82 	    ? DATETIME_DAY : DATETIME_YEAR;
83 	datetime_change_from_to(&cpdt, relfrom, src->to, -1);	/* min. from */
84 	dt = &cpdt;
85     }
86     else
87 	dt = src;
88 
89     /* need to call carry first? (just to make sure?) */
90     /*
91        fprintf (stdout,"DEBUG: INCR %.12lf %.12lf = %.12lf\n",
92        _debug_decimal(dt), _debug_decimal(incr),
93        _debug_decimal(dt)+_debug_decimal(incr));
94      */
95 
96     /* no sign change, just add */
97     if ((dt->positive && incr->positive) ||
98 	(dt->mode == DATETIME_RELATIVE && !dt->positive && !incr->positive)) {
99 
100 	for (i = incr->to; i >= incr->from; i--) {
101 	    _datetime_add_field(dt, incr, i);
102 	}
103     }
104 
105     else if (!incr->positive || dt->mode == DATETIME_RELATIVE) {
106 
107 	for (i = incr->to; i >= incr->from; i--) {
108 	    _datetime_subtract_field(dt, incr, i);
109 	}
110     }
111 
112     /* now only two special cases of bc ABSOLUTE left */
113 
114     else if (!incr->positive) {	/* incr is negative, dt is positive */
115 
116 	for (i = incr->to; i > DATETIME_YEAR; i--) {
117 	    _datetime_subtract_field(dt, incr, i);
118 	}
119 	_datetime_add_field(dt, incr, DATETIME_YEAR);
120     }
121     else {			/* incr is positive, dt is negative */
122 
123 	for (i = incr->to; i > DATETIME_YEAR; i--) {
124 	    _datetime_add_field(dt, incr, i);
125 	}
126 	_datetime_subtract_field(dt, incr, DATETIME_YEAR);
127     }
128     /*
129        fprintf (stdout,"DEBUG: INCR RESULT = %.12lf\n", _debug_decimal(dt));
130      */
131     if (src->mode == DATETIME_RELATIVE) {
132 	datetime_change_from_to(dt, src->from, src->to, -1);
133 
134 	/* copy dt back into src to return */
135 	datetime_copy(src, dt);
136     }
137 
138     return 0;
139 }
140 
141 
142 /*****************************************************************/
143 /*
144    When calling, the field must be
145    in the range of src, but this is not enforced here.
146 
147    The only thing used from the "incr" DateTime is the value of
148    the field being subtracted and the "from" & "to"
149 
150    by the time we get here, if src is RELATIVE, src->from should
151    already be minimized to allow borrowing from "lower" fields
152 
153  */
154 
_datetime_subtract_field(DateTime * src,DateTime * incr,int field)155 static int _datetime_subtract_field(DateTime * src, DateTime * incr,
156 				    int field)
157 {
158 
159     if (src->mode == DATETIME_RELATIVE) {
160 	DateTime srcinc, tinc;
161 	int borrow = 0;
162 
163 	datetime_copy(&tinc, src);
164 	datetime_copy(&srcinc, incr);
165 	switch (field) {
166 	case DATETIME_SECOND:
167 	    /* no "-1" here - remember seconds is floating point */
168 	    /* might result in over borrowing, so have to check */
169 	    if (src->second < incr->second) {
170 		if ((int)(incr->second - src->second) == (incr->second - src->second)) {	/* diff is integer */
171 		    borrow = 1 + (incr->second - src->second - 1) / 60;
172 		}
173 		else
174 		    borrow = 1 + (incr->second - src->second) / 60;
175 		src->second += borrow * 60;
176 	    }
177 	    src->second -= incr->second;
178 	    if (borrow) {
179 		srcinc.minute = borrow;
180 		_datetime_subtract_field(src, &srcinc, DATETIME_MINUTE);
181 	    }
182 	    break;
183 
184 	case DATETIME_MINUTE:
185 	    if (src->minute < incr->minute) {
186 		borrow = 1 + (incr->minute - src->minute - 1) / 60;
187 		src->minute += borrow * 60;
188 	    }
189 	    src->minute -= incr->minute;
190 	    if (borrow) {
191 		srcinc.hour = borrow;
192 		_datetime_subtract_field(src, &srcinc, DATETIME_HOUR);
193 	    }
194 	    break;
195 
196 	case DATETIME_HOUR:
197 	    if (src->hour < incr->hour) {
198 		borrow = 1 + (incr->hour - src->hour - 1) / 24;
199 		src->hour += borrow * 24;
200 	    }
201 	    src->hour -= incr->hour;
202 	    if (borrow) {
203 		srcinc.day = borrow;
204 		_datetime_subtract_field(src, &srcinc, DATETIME_DAY);
205 	    }
206 	    break;
207 
208 	case DATETIME_DAY:
209 	    if (src->day < incr->day) {	/* SIGN CHANGE */
210 		src->day = incr->day - src->day;
211 		datetime_invert_sign(src);
212 		tinc.day = 0;
213 		src->hour = 0;
214 		src->minute = 0;
215 		src->second = 0.0;
216 		datetime_increment(src, &tinc);	/* no sign change */
217 	    }
218 	    else
219 		src->day -= incr->day;
220 	    break;
221 
222 	case DATETIME_MONTH:
223 	    if (src->month < incr->month) {
224 		borrow = 1 + (incr->month - src->month - 1) / 12;
225 		src->month += borrow * 12;
226 	    }
227 	    src->month -= incr->month;
228 	    if (borrow) {
229 		srcinc.year = borrow;
230 		_datetime_subtract_field(src, &srcinc, DATETIME_YEAR);
231 	    }
232 	    break;
233 
234 	case DATETIME_YEAR:
235 	    if (src->year < incr->year) {	/* SIGN CHANGE */
236 		src->year = incr->year - src->year;
237 		datetime_invert_sign(src);
238 		tinc.year = 0;
239 		src->month = 0;
240 		datetime_increment(src, &tinc);	/* no sign change */
241 	    }
242 	    else
243 		src->year -= incr->year;
244 	    break;
245 	}
246     }
247 
248     else if (src->mode == DATETIME_ABSOLUTE) {
249 	DateTime srcinc, tinc, cpsrc;
250 	int i, newdays, borrow = 0;
251 
252 
253 	datetime_copy(&srcinc, incr);	/* makes srcinc valid incr */
254 	switch (field) {
255 	case DATETIME_SECOND:
256 	    if (src->second < incr->second) {
257 		borrow = 1 + (incr->second - src->second - 1) / 60;
258 		src->second += borrow * 60;
259 	    }
260 	    src->second -= incr->second;
261 	    if (borrow) {
262 		srcinc.minute = borrow;
263 		_datetime_subtract_field(src, &srcinc, DATETIME_MINUTE);
264 	    }
265 	    break;
266 
267 	case DATETIME_MINUTE:
268 	    if (src->minute < incr->minute) {
269 		borrow = 1 + (incr->minute - src->minute - 1) / 60;
270 		src->minute += borrow * 60;
271 	    }
272 	    src->minute -= incr->minute;
273 	    if (borrow) {
274 		srcinc.hour = borrow;
275 		_datetime_subtract_field(src, &srcinc, DATETIME_HOUR);
276 	    }
277 	    break;
278 
279 	case DATETIME_HOUR:
280 	    if (src->hour < incr->hour) {
281 		borrow = 1 + (incr->hour - src->hour - 1) / 24;
282 		src->hour += borrow * 24;
283 	    }
284 	    src->hour -= incr->hour;
285 	    if (borrow) {
286 		srcinc.day = borrow;
287 		_datetime_subtract_field(src, &srcinc, DATETIME_DAY);
288 	    }
289 	    break;
290 
291 	case DATETIME_DAY:
292 
293 	    if (src->day <= incr->day) {
294 		datetime_copy(&cpsrc, src);
295 		datetime_change_from_to(&cpsrc, DATETIME_YEAR,
296 					DATETIME_MONTH, -1);
297 		datetime_set_increment_type(&cpsrc, &tinc);
298 		tinc.month = 1;
299 		newdays = src->day;
300 		while (newdays <= incr->day) {
301 		    _datetime_subtract_field(&cpsrc, &tinc, DATETIME_MONTH);
302 		    newdays +=
303 			datetime_days_in_month(cpsrc.year, cpsrc.month,
304 					       cpsrc.positive);
305 		    borrow++;
306 		}
307 		src->day = newdays;
308 	    }
309 	    src->day -= incr->day;
310 	    if (borrow) {
311 		/*
312 		   src->year = cpsrc.year;
313 		   src->month = cpsrc.month;
314 		   src->positive = cpsrc.positive;
315 		 */
316 		/* check here & below - srcinc may be a day-second interval - mess anything up? */
317 		srcinc.month = borrow;
318 		_datetime_subtract_field(src, &srcinc, DATETIME_MONTH);
319 	    }
320 	    break;
321 
322 	case DATETIME_MONTH:
323 	    if (src->month <= incr->month) {
324 		borrow = 1 + (incr->month - src->month) / 12;
325 		src->month += borrow * 12;
326 	    }
327 	    src->month -= incr->month;
328 	    if (borrow) {
329 		srcinc.year = borrow;
330 		_datetime_subtract_field(src, &srcinc, DATETIME_YEAR);
331 	    }
332 	    break;
333 
334 	case DATETIME_YEAR:
335 	    if (src->year <= incr->year) {	/* SIGN CHANGE */
336 		datetime_set_increment_type(src, &tinc);
337 		tinc.positive = src->positive;
338 		if (datetime_in_interval_year_month(tinc.to)) {
339 		    tinc.month = src->month - 1;	/* convert to REL */
340 		    src->year = incr->year - src->year + 1;
341 		    /* +1 to skip 0 */
342 		    datetime_invert_sign(src);
343 		    tinc.year = 0;
344 		    src->month = 1;
345 		    datetime_increment(src, &tinc);	/* no sign change */
346 		}
347 		else {		/* have to convert to days */
348 		    tinc.day = src->day - 1;	/* convert to REL */
349 		    for (i = src->month - 1; i > 0; i--) {
350 			tinc.day +=
351 			    datetime_days_in_month(src->year, i,
352 						   src->positive);
353 		    }
354 		    tinc.hour = src->hour;
355 		    tinc.minute = src->minute;
356 		    tinc.second = src->second;
357 		    src->year = incr->year - src->year + 1;
358 		    /* +1 to skip 0 */
359 		    datetime_invert_sign(src);
360 		    src->month = 1;
361 		    src->day = 1;
362 		    src->hour = src->minute = 0;
363 		    src->second = 0;
364 		    datetime_increment(src, &tinc);	/* no sign change */
365 		}
366 	    }
367 	    else
368 		src->year -= incr->year;
369 	    break;
370 	}
371     }
372 
373     return 0;
374 }
375 
376 /*****************************************************************/
377 
378 /* When absolute is zero, all fields carry toward the future */
379 /* When absolute is one, sign of datetime is ignored */
_datetime_carry(DateTime * dt,int absolute)380 static int _datetime_carry(DateTime * dt, int absolute)
381 {
382     int i, carry;
383 
384     /* normalize day-sec (same for ABSOLUTE & RELATIVE) */
385     for (i = dt->to; i > dt->from && i > DATETIME_DAY; i--) {
386 	switch (i) {
387 	case DATETIME_SECOND:
388 	    if (dt->second >= 60.) {
389 		carry = dt->second / 60.;
390 		dt->minute += carry;
391 		dt->second -= carry * 60;
392 	    }
393 	    break;
394 	case DATETIME_MINUTE:
395 	    if (dt->minute >= 60) {
396 		carry = dt->minute / 60;
397 		dt->hour += carry;
398 		dt->minute -= carry * 60;
399 	    }
400 	    break;
401 	case DATETIME_HOUR:
402 	    if (dt->hour >= 24) {
403 		carry = dt->hour / 24;
404 		dt->day += carry;
405 		dt->hour -= carry * 24;
406 	    }
407 	    break;
408 	}
409     }
410 
411     /* give year a SIGN, temporarily */
412     if (!absolute && !dt->positive && dt->mode == DATETIME_ABSOLUTE) {
413 	dt->year = -dt->year;
414     }
415 
416     if (dt->from == DATETIME_YEAR && dt->to >= DATETIME_MONTH) {
417 
418 	/* normalize yr-mo */
419 	if (dt->mode == DATETIME_ABSOLUTE) {
420 	    if (dt->month > 12) {	/* month will never be zero */
421 		carry = (dt->month - 1) / 12;	/* no carry until 13 */
422 		dt->year += carry;
423 		if (dt->year == 0)
424 		    dt->year = 1;
425 		dt->month -= carry * 12;
426 		/*
427 		   if(dt->month == 0) dt->month = 1;
428 		   shouldn't happen */
429 	    }
430 	}
431 	else {
432 	    if (dt->month >= 12) {
433 		carry = dt->month / 12;
434 		dt->year += carry;
435 		dt->month -= carry * 12;
436 	    }
437 	}
438 
439     }
440 
441     /* normalize yr-day */
442     if (dt->mode == DATETIME_ABSOLUTE && dt->to > DATETIME_MONTH) {
443 
444 	while (dt->day >
445 	       datetime_days_in_month(dt->year, dt->month, dt->positive)) {
446 	    dt->day -=
447 		datetime_days_in_month(dt->year, dt->month, dt->positive);
448 	    if (dt->month == 12) {	/* carry to year */
449 		dt->year++;
450 		if (dt->year == 0)
451 		    dt->year = 1;
452 		dt->month = 1;
453 	    }
454 	    else		/* no carry to year */
455 		dt->month++;
456 
457 	}			/* end while */
458     }				/* end if */
459 
460     /* undo giving year a SIGN, temporarily */
461     if (!absolute && dt->mode == DATETIME_ABSOLUTE) {
462 	if (dt->year < 0) {
463 	    dt->year = -dt->year;
464 	    dt->positive = 0;
465 	}
466 	else
467 	    dt->positive = 1;
468     }
469 
470     return 0;
471 }
472 
_datetime_add_field(DateTime * src,DateTime * incr,int field)473 static int _datetime_add_field(DateTime * src, DateTime * incr, int field)
474 {
475     switch (field) {
476     case DATETIME_SECOND:
477 	src->second += incr->second;
478 	break;
479     case DATETIME_MINUTE:
480 	src->minute += incr->minute;
481 	break;
482     case DATETIME_HOUR:
483 	src->hour += incr->hour;
484 	break;
485     case DATETIME_DAY:
486 	src->day += incr->day;
487 	break;
488     case DATETIME_MONTH:
489 	src->month += incr->month;
490 	break;
491     case DATETIME_YEAR:
492 	src->year += incr->year;
493 	break;
494     }
495     if (src->mode == DATETIME_RELATIVE)
496 	_datetime_carry(src, 1);	/* do carries using absolute values */
497     else
498 	_datetime_carry(src, 0);	/* do carries toward future */
499 
500     return 0;
501 }
502