1 /* -*- mode: c -*-
2 | Time-stamp: <2007-01-19 01:52:29 jcs>
3 |
4 |  Copyright (C) 2002-2003 Jorg Schuler <jcsjcs at users.sourceforge.net>
5 |  Part of the gtkpod project.
6 |
7 |  URL: http://gtkpod.sourceforge.net/
8 |
9 |  This program is free software; you can redistribute it and/or modify
10 |  it under the terms of the GNU General Public License as published by
11 |  the Free Software Foundation; either version 2 of the License, or
12 |  (at your option) any later version.
13 |
14 |  This program is distributed in the hope that it will be useful,
15 |  but WITHOUT ANY WARRANTY; without even the implied warranty of
16 |  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 |  GNU General Public License for more details.
18 |
19 |  You should have received a copy of the GNU General Public License
20 |  along with this program; if not, write to the Free Software
21 |  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 |
23 |  iTunes and iPod are trademarks of Apple
24 |
25 |  This product is not supported/written/published by Apple!
26 */
27 
28 /* This parser (dp_parse()) will take one date specification (like
29  * 9/6/03, or -5d) and convert it into a timestamp. More details
30  * below. */
31 
32 %{
33 #include <stdlib.h>
34 #include "date_parser.h"
35 #include "misc.h"
36     static gchar *dp_strp = NULL;
37     /* time stamp (result!) */
38     static time_t tstamp;
39     /* already parsed time? */
40     static gboolean parsed_time;
41     /* are we reading a lower bound (TRUE) or upper bound (FALSE) */
42     static gboolean lower;
43     /* did an error occur? */
44     static gboolean dp_error;
45     /* should relative dates be read strictly (TRUE: no changing of seconds
46      * to 0 or 59 when minutes are set) or not. It's only set to FALSE
47      * for the "=..." format. */
48     static gboolean rel_strict;
49     typedef enum {
50 	DP_SEC,
51 	DP_MIN,
52 	DP_HOUR,
53 	DP_DAY,
54 	DP_WEEK,
55 	DP_MONTH,
56 	DP_YEAR,
57 	DP_INF
58     } RelTime;
59     /* Which is the lowest relative time specifier already parsed? */
60     RelTime reltime;
61     static void dp_reltime (gchar *str, gint32 sign);
62 /* We don't read from a stream but from a string buffer. This macro
63    will copy a maximum of @max_size chars from dp_strp to @buf,
64    writing the number of chars copied into @result. If no characters
65    are copied, YY_NULL is written into @result. */
66 #define YY_INPUT(buf,result,max_size) \
67     { \
68     if (!dp_strp || !dp_strp[0]) result = YY_NULL; \
69     else \
70       { \
71         gint i; \
72         for (i=0; (i<max_size && *dp_strp); ++i)  buf[i] = *dp_strp++; \
73         result = i; \
74       } \
75     }
76 %}
77 
78 /* stop parsing after end of string is reached */
79 %option noyywrap
80 /* avoid compiler warning: `yyunput' defined but not used */
81 %option nounput
82 /* We have several parsers in one executable, so we must use the
83    prefix option to avoid name space clashes. Note that this only
84    works with flex, but not with lex. */
85 %option prefix="lexdp"
86 /* Unfortunately, the prefix option also changes the output filename
87    and thereby breaks the automake script. Therefore I define the
88    output filename here. However, this is not portable -- on other
89    systems the default output filename expected by the automake
90    scripts may be lexyy.c or similar. To make the code portable again
91    (hopefully), I also define "LEX_OUTPUT_ROOT = lex.yy" in
92    Makefile.am */
93 %option outfile="lex.yy.c"
94 
95 
96 DIGIT     [0-9]
97 NUM       {DIGIT}+
98 /* definitions for time formats */
99 /* e.g. 8:54               */
100 TIMESHORT {NUM}":"{NUM}
101 /* e.g. 8:54:23            */
102 TIMEFULL  {NUM}":"{NUM}":"{NUM}
103 /* e.g. 9/6 (for June 9th) */
104 DATESHORT {NUM}"/"{NUM}
105 /* e.g. 9/6/3  or 9/6/2003 */
106 DATEFULL  {NUM}"/"{NUM}"/"{NUM}
107 /* e.g. -8:54              */
108 RELTIME_S [+-]{TIMESHORT}
109 /* e.g. +8:54.23           */
110 RELTIME_F [+-]{TIMEFULL}
111 /* e.g. 5d6m3s             */
112 RELTIME   ({NUM}[smhdwMy])+
113 /* e.g. -3M5d              */
114 SRELTIME  [+-]{RELTIME}
115 
116 %%
117 
118 {TIMESHORT}    {
119     gchar *ptr1 = yytext;
120     struct tm *lt = localtime (&tstamp);
121     lt->tm_hour = strtol (ptr1, &ptr1, 10);
122     ++ptr1;
123     lt->tm_min = strtol (ptr1, &ptr1, 10);
124     if (lower)    lt->tm_sec = 0;
125     else          lt->tm_sec = 59;
126     tstamp = mktime (lt);
127     parsed_time = TRUE;
128 #if DP_DEBUG
129     printf ("Time with minutes: '%s'\n", yytext);
130     printf ("tstamp: %d: %s", (int)tstamp, asctime (lt));
131 #endif
132 }
133 
134 {TIMEFULL}	{
135     gchar *ptr1 = yytext;
136     struct tm *lt = localtime (&tstamp);
137     lt->tm_hour = strtol (ptr1, &ptr1, 10);
138     ++ptr1;
139     lt->tm_min = strtol (ptr1, &ptr1, 10);
140     ++ptr1;
141     lt->tm_sec = strtol (ptr1, &ptr1, 10);
142     tstamp = mktime (lt);
143     parsed_time = TRUE;
144 #if DP_DEBUG
145     printf ("Time with seconds: '%s'\n", yytext);
146     printf ("tstamp: %d: %s", (int)tstamp, asctime (lt));
147 #endif
148 }
149 
150 {DATESHORT}	{
151     gchar *ptr1 = yytext;
152     struct tm *lt = localtime (&tstamp);
153     if (!parsed_time)
154     {
155 	if (lower)
156 	{
157 	    lt->tm_hour = 0;
158 	    lt->tm_min = 0;
159 	    lt->tm_sec = 0;
160 	}
161 	else
162 	{
163 	    lt->tm_hour = 23;
164 	    lt->tm_min = 59;
165 	    lt->tm_sec = 59;
166 	}
167     }
168     lt->tm_mday = strtol (ptr1, &ptr1, 10);
169     ++ptr1;
170     lt->tm_mon = strtol (ptr1, &ptr1, 10) - 1;
171     tstamp = mktime (lt);
172 #if DP_DEBUG
173     printf ("Date without year: '%s'\n", yytext);
174     printf ("tstamp: %d: %s", (int)tstamp, asctime (lt));
175 #endif
176 }
177 
178 {DATEFULL}	{
179     gchar *ptr1 = yytext;
180     struct tm *lt = localtime (&tstamp);
181     if (!parsed_time)
182     {
183 	if (lower)
184 	{
185 	    lt->tm_hour = 0;
186 	    lt->tm_min = 0;
187 	    lt->tm_sec = 0;
188 	}
189 	else
190 	{
191 	    lt->tm_hour = 23;
192 	    lt->tm_min = 59;
193 	    lt->tm_sec = 59;
194 	}
195     }
196     lt->tm_mday = strtol (ptr1, &ptr1, 10);
197     ++ptr1;
198     lt->tm_mon = strtol (ptr1, &ptr1, 10) - 1;
199     ++ptr1;
200     lt->tm_year = strtol (ptr1, &ptr1, 10);
201     if (lt->tm_year < 70)
202 	lt->tm_year += 2000;
203     if ((lt->tm_year < 100) && (lt->tm_year >=70))
204 	lt->tm_year += 1900;
205     /* tm_year is years since 1900 */
206     lt->tm_year -= 1900;
207     tstamp = mktime (lt);
208 #if DP_DEBUG
209     printf ("Date with year: '%s'\n", yytext);
210     printf ("tstamp: %d: %s", (int)tstamp, asctime (lt));
211 #endif
212 }
213 
214 {RELTIME_S}     { /* [+-]{TIMESHORT} */
215     gchar *ptr1 = yytext;
216     gint32 hours, mins, sign;
217     if (*ptr1 == '+')  sign = 1;
218     else               sign = -1;
219     ++ptr1;
220     hours = strtol (ptr1, &ptr1, 10);
221     ++ptr1;
222     mins = strtol (ptr1, &ptr1, 10);
223     tstamp += sign * (hours*3600 + mins*60);
224     if (DP_MIN < reltime)   reltime = DP_MIN;
225 #if DP_DEBUG
226     printf ("[+-]{TIMESHORT} '%s'\n", yytext);
227     printf ("tstamp: %d: %s", (int)tstamp, ctime (&tstamp));
228 #endif
229 }
230 
231 {RELTIME_F}     { /* [+-]{TIMEFULL} */
232     gchar *ptr1 = yytext;
233     gint32 hours, mins, secs, sign;
234     if (*ptr1 == '+')  sign = 1;
235     else               sign = -1;
236     ++ptr1;
237     hours = strtol (ptr1, &ptr1, 10);
238     ++ptr1;
239     mins = strtol (ptr1, &ptr1, 10);
240     ++ptr1;
241     secs = strtol (ptr1, &ptr1, 10);
242     tstamp += sign * (hours*3600 + mins*60 + secs);
243     reltime = DP_SEC;
244 #if DP_DEBUG
245     printf ("[+-]{TIMEFULL} '%s'\n", yytext);
246     printf ("tstamp: %d: %s", (int)tstamp, ctime (&tstamp));
247 #endif
248 }
249 
250 {RELTIME}	{ /* ({NUM}[smhdwMy])+ */
251 #if DP_DEBUG
252     printf ("RELTIME: '%s'\n", yytext);
253 #endif
254     /* call reltime with negative sign */
255     dp_reltime (yytext, -1);
256 }
257 
258 {SRELTIME}	{ /* [+-]{RELTIME} */
259 #if DP_DEBUG
260     printf ("SRELTIME: '%s'\n", yytext);
261 #endif
262     if (*yytext == '+')  dp_reltime (yytext+1, +1);
263     else                 dp_reltime (yytext+1, -1);
264 }
265 
266 [ \t]*      /* ignore */
267 
268 .           {
269     gtkpod_warning (_("Date format error: unrecognized character: '%s'\n"), yytext );
270     dp_error = TRUE;
271 }
272 
273 %%
274 
275 /* handle ({NUM}[smhdwMy])+, assuming @sign */
276 static void dp_reltime (gchar *str, gint32 sign)
277 {
278     gchar *ptr1 = str;
279     gint32 arg;
280     time_t secs = 0;
281 
282     while (*ptr1)
283     {
284 	arg = strtol (ptr1, &ptr1, 10);
285 	switch (*ptr1)
286 	{
287 	case 's':
288 	    secs += arg;
289 	    reltime = DP_SEC;
290 	    break;
291 	case 'm':
292 	    secs += 60*arg;
293 	    if (DP_MIN < reltime)   reltime = DP_MIN;
294 	    break;
295 	case 'h':
296 	    secs += 3600*arg;
297 	    if (DP_HOUR < reltime)   reltime = DP_HOUR;
298 	    break;
299 	case 'd':
300 	    secs += 24*3600*arg;
301 	    if (DP_DAY < reltime)   reltime = DP_DAY;
302 	    break;
303 	case 'w':
304 	    secs += 7*24*3600*arg;
305 	    if (DP_WEEK < reltime)   reltime = DP_WEEK;
306 	    break;
307 	case 'M':
308 	    secs += 30*7*24*3600*arg;
309 	    if (DP_MONTH < reltime)   reltime = DP_MONTH;
310 	    break;
311 	case 'y':
312 	    secs += 365*7*24*3600*arg;
313 	    if (DP_YEAR < reltime)   reltime = DP_YEAR;
314 	    break;
315 	}
316 	++ptr1;
317     }
318     tstamp += sign*secs;
319 #if DP_DEBUG
320     printf ("secs: %d, tstamp: %d: %s", (int)secs, (int)tstamp, ctime (&tstamp));
321 #endif
322 }
323 
324 
325 /* after reading the date string check if we should round down the
326    interval (round if reltime is not supposed to be strict and no
327    absolute time string has been parsed */
round_reltime(void)328 static void round_reltime (void)
329 {
330     if (!rel_strict && !parsed_time)
331     {   /* Round this datestamp to make a lower/upper margin */
332 	struct tm *lt = localtime (&tstamp);
333 	switch (reltime)
334 	{
335 	case DP_INF:
336 	    break;
337 	case DP_YEAR:
338 	    if (lower) lt->tm_mon = 0;
339 	    else       lt->tm_mon = 11;
340 	case DP_MONTH:
341 	    if (lower) lt->tm_mday = 1;
342 	    else
343 	    {
344 		switch (lt->tm_mon)
345 		{
346 		case 1:
347 		case 3:
348 		case 5:
349 		case 7:
350 		case 8:
351 		case 10:
352 		case 12:
353 		    lt->tm_mday = 31;
354 		    break;
355 		case 2:
356 		    if ((lt->tm_year % 4) != 0)   lt->tm_mday = 28;
357 		    else
358 		    {
359 			if ((lt->tm_year % 100) != 0) lt->tm_mday = 29;
360 			else
361 			{   /* centuries are only leap years if they
362 			     * are a multiple of 400 */
363 			    if (((lt->tm_year+300) % 400) == 0)
364 				 lt->tm_mday = 29;
365 			    else lt->tm_mday = 28;
366 			}
367 		    }
368 		    break;
369 		case 4:
370 		case 6:
371 		case 9:
372 		case 11:
373 		    lt->tm_mday = 30;
374 		    break;
375 		}
376 	    }
377 	case DP_WEEK:
378 	    if (reltime == DP_WEEK)
379 	    {   /* only do this if week was set manually */
380 		/* tm_wday gives us the number of days since Sunday (0
381 		   to 6). Let's declare Monday to be the start of the
382 		   week and Sunday the end. */
383 		if (lower)
384 		{   /* Round down to Monday */
385 		    if (lt->tm_wday == 0)  lt->tm_mday -= 6;
386 		    else                   lt->tm_mday -= (lt->tm_wday-1);
387 		}
388 		else
389 		{   /* Round up to Sunday */
390 		    if (lt->tm_wday != 0)  lt->tm_mday += (7-lt->tm_wday);
391 		}
392 	    }
393 	case DP_DAY:
394 	    if (lower) lt->tm_hour = 0;
395 	    else       lt->tm_hour = 23;
396 	case DP_HOUR:
397 	    if (lower) lt->tm_min = 0;
398 	    else       lt->tm_min = 59;
399 	case DP_MIN:
400 	    if (lower) lt->tm_sec = 0;
401 	    else       lt->tm_sec = 59;
402 	case DP_SEC:
403 	    break;
404 	}
405 	tstamp = mktime (lt);
406 #if DP_DEBUG
407 	printf ("tstamp: %d: %s", (int)tstamp, ctime (&tstamp));
408 #endif
409     }
410 }
411 
412 /* dp_parse() will take one date specification (like 9/6/03, or -5d,
413  * for details have a look at the definitions above) and convert it
414  * into a timestamp.
415  *
416  * @dp_str: the string to parse
417  *
418  * @result: the parsed timestamp is written into here
419  *
420  * @lower_margin: if TRUE, absolute time or date values are "rounded
421  * down" (e.g. "9/6/03" would compute to "June 6 2003, 0:00:00"),
422  * otherwise the are rounded up (e.g. "9/6/03" would compute to "June
423  * 6 2003, 23:59:59"). Similarily, "8:05" would either compute to
424  * "8:05:00" or "8:05:59" depending on whether the lower or upper
425  * boundary is to be determined.
426  *
427  * @strict: should relative dates be read strictly (TRUE: no changing
428  * of seconds to 0 or 59 when minutes are set, etc.) or not. Actually,
429  * it's only set to FALSE * for the "=..." format.
430  *
431  * Return value:
432  *
433  * TRUE:  no error occurred
434  * FALSE: error occurred
435  */
dp_parse(gchar * dp_str,time_t * result,gboolean lower_margin,gboolean strict)436 gboolean dp_parse (gchar *dp_str, time_t *result,
437 		   gboolean lower_margin, gboolean strict)
438 {
439     dp_strp = dp_str;
440     /* set timestamp to current time */
441     tstamp = time (NULL);
442     /* did not yet parse any absolute time string */
443     parsed_time = FALSE;
444     /* did not yet parse any relative time string */
445     reltime = DP_INF;
446     /* no error occurred (yet) */
447     dp_error = FALSE;
448     /* set parameters */
449     lower = lower_margin;
450     rel_strict = strict;
451     /* call lexer */
452     yylex ();
453     /* round relative time up or down, if necessary */
454     round_reltime ();
455     /* set the result */
456     if (result)  *result = tstamp;
457     /* return the (inverted) error variable */
458     return !dp_error;
459 }
460