1 /* ======================================================= *
2  * Copyright 1998-2006 Stephen C. Grubb                    *
3  * http://ploticus.sourceforge.net                         *
4  * Covered by GPL; see the file ./Copyright for details.   *
5  * ======================================================= */
6 
7 #include "pl.h"
8 
9 /* Given smin, smax, and nearest, determine minval and maxval.
10    Smin and Smax are string reps of numerics, dates, times, etc.
11    Pass identical smin and smax to get a $dategroup() or $numgroup() operation.
12 
13    Function returns 1 if the 'nearest' mode was recognized and a nearest range computed.
14    Function returns 0 otherwise.
15  */
16 int
PLP_findnearest(smin,smax,axis,nearest,minval,maxval)17 PLP_findnearest( smin, smax, axis, nearest, minval, maxval )
18 char *smin, *smax, axis, *nearest, *minval, *maxval;
19 {
20 int stat;
21 char datepart[40], timepart[40], unittyp[40];
22 
23 Egetunits( axis, unittyp );
24 
25 if( strncmp( nearest, "month", 5 )== 0 || strncmp( nearest, "quarter", 7 )==0 || strncmp( nearest, "3month", 6 )==0 ) {
26 	/* nearest month boundary / quarter-year boundary.. */
27 	int mon, day, yr, newmon;
28 	long l;
29 	if( !GL_smember( unittyp, "date datetime" ))
30 		Eerr( 2892, "autorange 'nearest=month' or 'nearest=quarter' only valid with date or datetime scaletype", unittyp );
31 	/* min */
32 	stat = DT_jdate( smin, &l );
33         DT_getmdy( &mon, &day, &yr );
34 	if( nearest[0] == 'q' || nearest[0] == '3' ) {
35 		if( mon >= 10 ) mon = 10;
36 		else if( mon >= 7 ) mon = 7;
37 		else if( mon >= 4 ) mon = 4;
38 		else if( mon >= 1 ) mon = 1;
39 		}
40 	DT_makedate( yr, mon, 1, "", datepart );
41 	if( strcmp( unittyp, "datetime" )==0 ) {
42 		DT_maketime( 0, 0, 0.0, timepart );
43 		DT_build_dt( datepart, timepart, minval );
44 		}
45 	else strcpy( minval, datepart );
46 
47 	/* max */
48 	stat = DT_jdate( smax, &l );
49         DT_getmdy( &mon, &day, &yr );
50 	if( nearest[0] == 'q' || nearest[0] == '3' ) {
51 		if( mon <= 3 ) mon = 4;
52 		else if( mon <= 6 ) mon = 7;
53 		else if( mon <= 9 ) mon = 10;
54 		else if( mon <= 12 ) mon = 13;
55 		}
56 	else mon ++;
57 
58         /* wrap around year.. */
59         newmon = ((mon-1) % 12 ) +1;
60         yr += ((mon-1) / 12);
61         mon = newmon;
62 	DT_makedate( yr, mon, 1, "", datepart );
63 	if( strcmp( unittyp, "datetime" )==0 ) {
64 		DT_maketime( 0, 0, 0.0, timepart );
65 		DT_build_dt( datepart, timepart, maxval );
66 		}
67 	else strcpy( maxval, datepart );
68 	return( 1 );
69 	}
70 
71 
72 else if( strncmp( nearest, "year", 4 )== 0 || strncmp( nearest, "2year", 5 )==0 ||
73 	strncmp( nearest, "5year", 5 )==0 || strncmp( nearest, "10year", 6 )==0 ) {
74 	int mon, day, yr;
75 	long l;
76 	int yearsblock; /* 0 5 or 10 */
77 
78 	if( !GL_smember( unittyp, "date datetime" ))
79 		Eerr( 2892, "autorange 'nearest=year' only valid with date or datetime scaletype", unittyp );
80 
81 	if( nearest[0] != 'y' ) {			/* this section scg 1/28/05 */
82 		yearsblock = nearest[0] - '0';
83 		if( yearsblock == 1 ) yearsblock = 10;
84 		}
85 	else yearsblock = 0;
86 
87 	/* min */
88 	stat = DT_jdate( smin, &l );
89         DT_getmdy( &mon, &day, &yr );
90 	if( yearsblock ) yr = (yr / yearsblock) * yearsblock; 		/* scg 1/28/05 */
91 	DT_makedate( yr, 1, 1, "", datepart );
92 	if( strcmp( unittyp, "datetime" )==0 ) {
93 		DT_maketime( 0, 0, 0.0, timepart );
94 		DT_build_dt( datepart, timepart, minval );
95 		}
96 	else strcpy( minval, datepart );
97 
98 	/* max */
99 	stat = DT_jdate( smax, &l );
100         DT_getmdy( &mon, &day, &yr );
101 	if( yearsblock ) yr = ((yr / yearsblock)+1) * yearsblock; 		/* scg 1/28/05 */
102 	else yr++;
103 	DT_makedate( yr, 1, 1, "", datepart );
104 	if( strcmp( unittyp, "datetime" )==0 ) {
105 		DT_maketime( 0, 0, 0.0, timepart );
106 		DT_build_dt( datepart, timepart, maxval );
107 		}
108 	else strcpy( maxval, datepart );
109 	return( 1 );
110 	}
111 
112 
113 else if( strncmp( nearest, "day", 3 )== 0 || strcmp( nearest, "monday" )==0 || strcmp( nearest, "sunday" )==0 ) {
114 	int mon, day, yr;
115 	double days, mins;
116 
117 	if( !GL_smember( unittyp, "date datetime" ))
118 		Eerr( 2892, "autorange 'nearest=day' only valid with date or datetime scaletype", unittyp );
119 
120 	/* min */
121 	if( strcmp( unittyp, "datetime" )==0 ) DT_getdtparts( smin, datepart, timepart );
122 	else strcpy( datepart, smin ); /* if and else added scg 8/10/05 */
123 
124 	if( nearest[0] == 'm' || nearest[0] == 's' ) {  /* adjust datepart back to a monday or sunday */
125 		int iwk;  char rbuf[40];
126 		DT_weekday( datepart, rbuf, &iwk ); /* rbuf not used */
127 		if( nearest[0] == 'm' ) { if( iwk == 1 ) iwk = 8; DT_dateadd( datepart, 2 - iwk, rbuf ); }
128 		else if( nearest[0] == 's' ) DT_dateadd( datepart, 1 - iwk, rbuf );
129 		strcpy( datepart, rbuf );
130 		}
131 
132 	/* this is just a way to get the dt parts (?) ...
133 	 * DT_datetime2days( smin, &days );
134          * DT_getmdy( &mon, &day, &yr );
135 	 * DT_makedate( yr, mon, day, "", datepart );
136 	 */
137 
138 	if( strcmp( unittyp, "date" )==0 )
139 	/* check for biz day window.. scg 7/21/04 */
140 	mins = 0.0;
141 	DT_frame_mins( &mins ); /* adjust to any biz day window.. */
142 	DT_frommin( mins, timepart );
143 	if( strcmp( unittyp, "date" )==0 ) strcpy( minval, datepart );
144 	else DT_build_dt( datepart, timepart, minval );
145 
146 	/* max */
147 	if( strcmp( unittyp, "datetime" )==0 ) DT_getdtparts( smax, datepart, timepart );
148 	else strcpy( datepart, smax );  /* if and else added scg 8/10/05 */
149 
150 	if( nearest[0] == 'm' || nearest[0] == 's' ) {  /* adjust datepart to next monday or sunday */
151 		int iwk;  char rbuf[40];
152 		DT_weekday( datepart, rbuf, &iwk ); /* rbuf not used */
153 		if( nearest[0] == 'm' ) { if( iwk == 1 ) iwk = 8; DT_dateadd( datepart, 9 - iwk, rbuf ); }
154 		else if( nearest[0] == 's' ) DT_dateadd( datepart, 8 - iwk, rbuf );
155 		DT_build_dt( rbuf, timepart, smax );
156 		}
157 
158 	DT_datetime2days( smax, &days );
159 	if( fabs( days - floor( days )) < 0.0001 ) ; /* avoid spurious extra day when data max is on date boundary added scg 7/21/04 */
160 	else days++;
161 	DT_days2datetime( days, smax );
162 	DT_datetime2days( smax, &days ); /* set next day's date for getmdy below */
163         DT_getmdy( &mon, &day, &yr );
164 	DT_makedate( yr, mon, day, "", datepart );
165 	DT_maketime( 0, 0, 0.0, timepart );
166 	if( strcmp( unittyp, "date" )==0 ) strcpy( maxval, datepart );
167 	else DT_build_dt( datepart, timepart, maxval );
168 	return( 1 );
169 	}
170 
171 else if( strncmp( nearest, "hour", 4 )== 0 || strncmp( nearest, "3hour", 5 )==0 ||
172 	 strncmp( nearest, "6hour", 5 )==0 || strncmp( nearest, "12hour", 6 )==0 ) {
173 	int hour, minute;
174 	double sec;
175 	int hoursblock; /* 0, 3, 6, or 12 */
176 	if( !GL_smember( unittyp, "time datetime" ))
177 		Eerr( 2892, "autorange 'nearest=hour' is incompatible with scaletype", unittyp );
178 
179 	if( nearest[0]!= 'h' ) {				/* this section scg 1/28/05 */
180 		hoursblock = nearest[0] - '0';
181 		if( hoursblock == 1 ) hoursblock = 12;
182 		}
183 	else hoursblock = 0;
184 
185 	if( strcmp( unittyp, "time" )==0 ) {
186 		/* min */
187 		DT_tomin( smin, &sec ); /* sec not used */
188 		DT_gethms( &hour, &minute, &sec );
189 		if( hoursblock ) hour = (hour / hoursblock) * hoursblock; 		/* scg 1/28/05 */
190 		DT_maketime( hour, 0, 0.0, minval );
191 		/* max */
192 		DT_tomin( smax, &sec ); /* sec not used */
193 		DT_gethms( &hour, &minute, &sec );
194 		if( hoursblock ) hour = ((hour / hoursblock)+1) * hoursblock; 		/* scg 1/28/05 */
195 		if( minute != 0 || sec != 0.0 ) hour++; 				/* bug, scg 12/13/05 */
196 		DT_maketime( hour, 0, 0.0, maxval );
197 		}
198 	else if( strcmp( unittyp, "datetime" )==0 ) {
199 		double days;
200 		int mon, day, yr;
201 
202 		/* min */
203 		DT_datetime2days( smin, &days );
204 		/* time part */
205 		DT_gethms( &hour, &minute, &sec );
206 		if( hoursblock ) hour = (hour / hoursblock) * hoursblock; 		/* scg 1/28/05 */
207 		DT_maketime( hour, 0, 0.0, timepart );
208 		/* date part */
209         	DT_getmdy( &mon, &day, &yr );
210 		DT_makedate( yr, mon, day, "", datepart );
211 		DT_build_dt( datepart, timepart, minval );
212 
213 		/* max */
214 		DT_datetime2days( smax, &days );
215 		/* time part */
216 		DT_gethms( &hour, &minute, &sec );
217 		if( hour == 23 ) {
218 			DT_days2datetime( days+1.0, smax ); /* set next day's date for getmdy below*/
219 			DT_datetime2days( smax, &days ); /* set next day's date for getmdy below*/
220 			DT_maketime( 0, 0, 0.0, timepart );					/* ok for any hoursblock */
221 			}
222 		else 	{
223 			if( hoursblock ) hour = ((hour / hoursblock)+1) * hoursblock; 		/* scg 1/28/05 */
224 			else hour++;
225 			DT_maketime( hour, 0, 0.0, timepart );
226 			}
227 		/* date part */
228         	DT_getmdy( &mon, &day, &yr );
229 		DT_makedate( yr, mon, day, "", datepart );
230 		DT_build_dt( datepart, timepart, maxval );
231 		}
232 	return( 1 );
233 	}
234 
235 /* else if( strcmp( nearest, "minute" )==0 || strcmp( nearest, "10minute" )==0 ||
236  *	strcmp( nearest, "20minute" )==0 || strcmp( nearest, "30minute" )==0 ) {
237  */
238 else if( stricmp( nearest, "minute" )==0 ||	/* handles  minute  or  Nminute or NNminute, contributed by Chris Demetriou 1/9/08 */
239          ( nearest[0] >= '0' && nearest[0] <= '9' && stricmp( nearest + 1, "minute" )==0 ) ||
240          ( nearest[0] >= '0' && nearest[0] <= '9' && nearest[1] >= '0' && nearest[1] <= '9' && stricmp( nearest + 2, "minute" )==0 ) ) {
241 
242 	int hour, minute, minblock;
243 	double sec;
244 
245 	if( strcmp( unittyp, "time" )!= 0 ) Eerr( 2892, "autorange 'nearest=minute' is incompatible with scaletype", unittyp );
246 
247 	/* if( nearest[0] != 'm' ) minblock = (nearest[0] - '0') * 10;  // changed 1/9/08 */
248         if( tolower( (int) nearest[0] ) != 'm' ) {
249                minblock = atoi(nearest);
250                if( minblock < 0 || minblock >= 60 || ( 60 % minblock )!=0 ) {
251                        Eerr( 2892 /*???*/, "invalid number of minutes in 'nearest' autorange specification", nearest );
252                        }
253                }
254 	else minblock = 0;
255 
256 	/* min */
257 	DT_tomin( smin, &sec ); /* sec not used */
258 	DT_gethms( &hour, &minute, &sec );
259 	if( minblock ) minute = (minute / minblock) * minblock;
260 	DT_maketime( hour, minute, 0.0, minval );
261 
262 	/* max */
263 	DT_tomin( smax, &sec ); /* sec not used */
264 	DT_gethms( &hour, &minute, &sec );
265 	if( minblock ) minute = ((minute / minblock)+1) * minblock;
266 	else minute++;
267 	if( minute >= 60 ) { minute = minute % 60; hour++; }
268 	DT_maketime( hour, minute, 0.0, maxval );
269 	return( 1 );
270 	}
271 
272 
273 else if( stricmp( nearest, "second" )==0 ||   /* handles  second or  Nsecond or NNsecond, contributed by Chris Demetriou 1/9/08 */
274          ( nearest[0] >= '0' && nearest[0] <= '9' && stricmp( nearest + 1, "second" )==0 ) ||
275          ( nearest[0] >= '0' && nearest[0] <= '9' && nearest[1] >= '0' && nearest[1] <= '9' && stricmp( nearest + 2, "second" )==0 ) ) {
276        int hour, minute, isec, secblock;
277        double sec;
278 
279        if( strcmp( unittyp, "time" )!= 0 ) Eerr( 2892, "autorange 'nearest=second' is incompatible with scaletype", unittyp );
280 
281        if( tolower( (int) nearest[0] ) != 's' ) {
282                secblock = atoi(nearest);
283                if( secblock < 0 || secblock >= 60 || ( 60 % secblock )!=0 ) {
284                        Eerr( 2892 /*???*/, "invalid number of minutes in 'nearest' autorange specification", nearest );
285                        }
286                }
287        else secblock = 0;
288 
289        /* min */
290        DT_tomin( smin, &sec );
291        DT_gethms( &hour, &minute, &sec );
292        isec = (int)floor(sec);
293        if( secblock ) isec = (isec / secblock) * secblock;
294        DT_maketime( hour, minute, isec * 1.0, minval );
295 
296        /* max */
297        DT_tomin( smax, &sec );
298        DT_gethms( &hour, &minute, &sec );
299        isec = (int)ceil(sec); /* will round up to "60" at most. */
300        if( secblock ) isec = ((isec + secblock - 1) / secblock) * secblock;
301        if( isec >= 60 ) { minute++; isec = 0; }
302        if( minute >= 60 ) { minute = minute % 60; hour++; }
303        DT_maketime( hour, minute, isec * 1.0, maxval );
304        return( 1 );
305        }
306 
307 
308 return( 0 );
309 }
310