1 /*
2  * rasqal_datetime.c - Rasqal XSD dateTime and XSD date
3  *
4  * Copyright (C) 2007-2011, David Beckett http://www.dajobe.org/
5  *
6  * Contributions:
7  *   Copyright (C) 2007, Lauri Aalto <laalto@iki.fi>
8  *
9  * This package is Free Software and part of Redland http://librdf.org/
10  *
11  * It is licensed under the following three licenses as alternatives:
12  *   1. GNU Lesser General Public License (LGPL) V2.1 or any newer version
13  *   2. GNU General Public License (GPL) V2 or any newer version
14  *   3. Apache License, V2.0 or any newer version
15  *
16  * You may not use this file except in compliance with at least one of
17  * the above three licenses.
18  *
19  * See LICENSE.html or LICENSE.txt at the top of this package for the
20  * complete terms and further detail along with the license texts for
21  * the licenses in COPYING.LIB, COPYING and LICENSE-2.0.txt respectively.
22  *
23  *
24  */
25 
26 #ifdef HAVE_CONFIG_H
27 #include <rasqal_config.h>
28 #endif
29 
30 #ifdef WIN32
31 #include <win32_rasqal_config.h>
32 #endif
33 
34 #include <stdio.h>
35 #include <string.h>
36 #include <ctype.h>
37 #ifdef TIME_WITH_SYS_TIME
38 # include <sys/time.h>
39 # include <time.h>
40 #else
41 # ifdef HAVE_SYS_TIME_H
42 #  include <sys/time.h>
43 # else
44 #  include <time.h>
45 # endif
46 #endif
47 #ifdef HAVE_STDLIB_H
48 #include <stdlib.h>
49 #endif
50 #include <stdarg.h>
51 #include <limits.h>
52 
53 #include "rasqal.h"
54 #include "rasqal_internal.h"
55 
56 /* Local definitions */
57 
58 static int rasqal_xsd_datetime_parse(const char *datetime_string, rasqal_xsd_datetime *result, int is_dateTime);
59 static unsigned int days_per_month(int month, int year);
60 
61 
62 #ifndef ISNUM
63 #define ISNUM(c) ((c) >= '0' && (c) <= '9')
64 #endif
65 
66 
67 /**
68  * rasqal_xsd_datetime_normalize:
69  * @datetime: date time
70  *
71  * INTERNAL - Normalize a date time into the allowed range
72  *
73  * The result will always give either
74  *   have_tz 'N' with timezone_minutes RASQAL_XSD_DATETIME_NO_TZ
75  *   have_tz 'Z' with timezone_minutes 0
76  *
77  * Return value: zero on success, non zero on failure.
78  */
79 static int
rasqal_xsd_datetime_normalize(rasqal_xsd_datetime * datetime)80 rasqal_xsd_datetime_normalize(rasqal_xsd_datetime *datetime)
81 {
82   int t;
83 
84   if(datetime->have_tz == 'Y') {
85     if(datetime->timezone_minutes) {
86       /* Normalize to Zulu if there was a timezone offset */
87       datetime->hour   = RASQAL_GOOD_CAST(signed char, datetime->hour - (datetime->timezone_minutes / 60));
88       datetime->minute = RASQAL_GOOD_CAST(signed char, datetime->minute - (datetime->timezone_minutes % 60));
89 
90       datetime->timezone_minutes = 0;
91     }
92     datetime->have_tz = 'Z';
93   }
94 
95   /* second & second parts: no need to normalize as they are not
96    * touched after range check
97    */
98 
99   /* minute */
100   if(datetime->minute < 0) {
101     datetime->minute = RASQAL_GOOD_CAST(signed char, datetime->minute + 60);
102     datetime->hour--;
103   } else if(datetime->minute > 59) {
104     datetime->minute = RASQAL_GOOD_CAST(signed char, datetime->minute - 60);
105     datetime->hour++;
106   }
107 
108   /* hour */
109   if(datetime->hour < 0) {
110     datetime->hour = RASQAL_GOOD_CAST(signed char, datetime->hour + 24);
111     datetime->day--;
112   } else if(datetime->hour > 23) {
113     datetime->hour = RASQAL_GOOD_CAST(signed char, datetime->hour - 24);
114     datetime->day++;
115   }
116 
117   /* day */
118   if(datetime->day < 1) {
119     int y2;
120     t = --datetime->month;
121     /* going back beyond year boundary? */
122     if(!t) {
123       t = 12;
124       y2 = datetime->year-1;
125     } else
126       y2 = datetime->year;
127     datetime->day = RASQAL_GOOD_CAST(unsigned char, datetime->day + days_per_month(t, y2));
128   } else {
129     t = RASQAL_GOOD_CAST(int, days_per_month(datetime->month, datetime->year));
130     if(datetime->day > t) {
131       datetime->day = RASQAL_GOOD_CAST(unsigned char, datetime->day - t);
132       datetime->month++;
133     }
134   }
135 
136   /* month & year */
137   if(datetime->month < 1) {
138     datetime->month = RASQAL_GOOD_CAST(unsigned char, datetime->month + 12);
139     datetime->year--;
140     /* there is no year 0 - go backwards to year -1 */
141     if(!datetime->year)
142       datetime->year--;
143   } else if(datetime->month > 12) {
144     datetime->month = RASQAL_GOOD_CAST(unsigned char, datetime->month - 12);
145     datetime->year++;
146     /* there is no year 0 - go forwards to year 1 */
147     if(!datetime->year)
148       datetime->year++;
149   }
150 
151   datetime->time_on_timeline = rasqal_xsd_datetime_get_as_unixtime(datetime);
152 
153   /* success */
154   return 0;
155 }
156 
157 
158 /**
159  * rasqal_xsd_datetime_parse:
160  * @datetime_string: xsd:dateTime as lexical form string
161  * @result: target struct for holding dateTime components
162  * @is_dateTime: is xsd:dateTime and should look for time (hour, mins, secs)
163  *   otherwise is xsd:date and should skip to looking for timezone
164  *
165  * INTERNAL - Parse a xsd:dateTime string into a #rasqal_xsd_datetime struct.
166  *
167  * Does NOT normalize the structure.  Call
168  * rasqal_xsd_datetime_normalize() to do that.
169  *
170  * http://www.w3.org/TR/xmlschema-2/#dt-dateTime
171  *
172  * "The lexical space of dateTime consists of finite-length sequences of
173  * characters of the form:
174  * '-'? yyyy '-' mm '-' dd 'T' hh ':' mm ':' ss ('.' s+)? (zzzzzz)?,
175  * where
176  *
177  * * '-'? yyyy is a four-or-more digit optionally negative-signed numeral that
178  *   represents the year; if more than four digits, leading zeros are
179  *   prohibited, and '0000' is prohibited (see the Note above (3.2.7); also
180  *   note that a plus sign is not permitted);
181  * * the remaining '-'s are separators between parts of the date portion;
182  * * the first mm is a two-digit numeral that represents the month;
183  * * dd is a two-digit numeral that represents the day;
184  * * 'T' is a separator indicating that time-of-day follows;
185  * * hh is a two-digit numeral that represents the hour; '24' is permitted if
186  *   the minutes and seconds represented are zero, and the dateTime value so
187  *   represented is the first instant of the following day (the hour property
188  *   of a dateTime object in the value space cannot have a value greater
189  *   than 23);
190  * * ':' is a separator between parts of the time-of-day portion;
191  * * the second mm is a two-digit numeral that represents the minute;
192  * * ss is a two-integer-digit numeral that represents the whole seconds;
193  * * '.' s+ (if present) represents the fractional seconds;
194  * * zzzzzz (if present) represents the timezone"
195  *
196  *
197  *  http://www.w3.org/TR/xmlschema-2/#dt-date
198  *  lexical space: '-'? yyyy '-' mm '-' dd zzzzzz?
199  *
200  * Return value: zero on success, non zero on failure.
201  */
202 static int
rasqal_xsd_datetime_parse(const char * datetime_string,rasqal_xsd_datetime * result,int is_dateTime)203 rasqal_xsd_datetime_parse(const char *datetime_string,
204                           rasqal_xsd_datetime *result,
205                           int is_dateTime)
206 {
207   const char *p, *q;
208 #define B_SIZE 16
209   char b[B_SIZE];
210   unsigned int l, t, t2, is_neg;
211   unsigned long u;
212 #define MICROSECONDS_MAX_DIGITS 6
213 
214   if(!datetime_string || !result)
215     return -1;
216 
217   p = (const char *)datetime_string;
218   is_neg = 0;
219 
220   /* Parse year */
221 
222   /* negative years permitted */
223   if(*p == '-') {
224     is_neg = 1;
225     p++;
226   }
227   for(q = p; ISNUM(*p); p++)
228     ;
229   l = RASQAL_GOOD_CAST(unsigned int, p - q);
230 
231   /* error if
232      - less than 4 digits in year
233      - more than 4 digits && leading zeros
234      - '-' does not follow numbers
235    */
236   if(l < 4 || (l > 4 && *q=='0') || *p != '-')
237     return -1;
238 
239   if(l >= (B_SIZE - 1))
240     l = RASQAL_GOOD_CAST(unsigned int, B_SIZE - 1);
241 
242   memcpy(b, q, l);
243   b[l] = 0; /* ensure nul termination */
244   u = strtoul(b, 0, 10);
245 
246   /* year "0000" not permitted
247    * restrict to signed int range
248    * >= instead of > to allow for +-1 year adjustment in normalization
249    * (however, these +-INT_MAX years cannot be parsed back in if
250    * converted to string)
251    */
252   if(!u || u >= INT_MAX)
253     return -1;
254 
255   result->year = is_neg ? -RASQAL_GOOD_CAST(int, u) : RASQAL_GOOD_CAST(int, u);
256 
257   /* parse month */
258 
259   for(q = ++p; ISNUM(*p); p++)
260     ;
261   l = RASQAL_GOOD_CAST(unsigned int, p - q);
262 
263   /* error if month is not 2 digits or '-' is not the separator */
264   if(l != 2 || *p != '-')
265     return -2;
266 
267   t = RASQAL_GOOD_CAST(unsigned int, (*q++ - '0')*10);
268   t += RASQAL_GOOD_CAST(unsigned int, *q - '0');
269 
270   /* month must be 1..12 */
271   if(t < 1 || t > 12)
272     return -2;
273 
274   result->month = RASQAL_GOOD_CAST(unsigned char, t);
275 
276   /* parse day */
277 
278   for(q = ++p; ISNUM(*p); p++)
279     ;
280   l = RASQAL_GOOD_CAST(unsigned int, p - q);
281 
282   if(is_dateTime) {
283     /* xsd:dateTime: error if day is not 2 digits or 'T' is not the separator */
284     if(l != 2 || *p != 'T')
285       return -3;
286   } else {
287     /* xsd:date: error if day is not 2 digits or separator is not
288      * 'Z' (utc)
289      * '+' or '-' (timezone offset)
290      * nul (end of string - timezone is optional)
291      */
292     if(l != 2 || (*p && *p != 'Z' && *p != '+' && *p != '-'))
293       return -3;
294   }
295 
296   t = RASQAL_GOOD_CAST(unsigned int, (*q++ - '0') * 10);
297   t += RASQAL_GOOD_CAST(unsigned int, *q - '0');
298 
299   /* day must be 1..days_per_month */
300   if(t < 1 || t > days_per_month(result->month, result->year))
301     return -3;
302 
303   result->day = RASQAL_GOOD_CAST(unsigned char, t);
304 
305   if(is_dateTime) {
306     /* parse hour */
307 
308     for(q = ++p; ISNUM(*p); p++)
309       ;
310     l = RASQAL_GOOD_CAST(unsigned int, p - q);
311 
312     /* error if hour is not 2 digits or ':' is not the separator */
313     if(l != 2 || *p != ':')
314       return -4;
315 
316     t = RASQAL_GOOD_CAST(unsigned int, (*q++ - '0')*10);
317     t += RASQAL_GOOD_CAST(unsigned int, *q - '0');
318 
319     /* hour must be 0..24 - will handle special case 24 later
320      * (no need to check for < 0)
321      */
322     if(t > 24)
323       return -4;
324 
325     result->hour = RASQAL_GOOD_CAST(signed char, t);
326 
327     /* parse minute */
328 
329     for(q = ++p; ISNUM(*p); p++)
330       ;
331     l = RASQAL_GOOD_CAST(unsigned int, p - q);
332 
333     /* error if minute is not 2 digits or ':' is not the separator */
334     if(l != 2 || *p != ':')
335       return -5;
336 
337     t = RASQAL_GOOD_CAST(unsigned int, (*q++ - '0') * 10);
338     t += RASQAL_GOOD_CAST(unsigned int, *q - '0');
339 
340     /* minute must be 0..59
341      * (no need to check for < 0)
342      */
343     if(t > 59)
344       return -5;
345 
346     result->minute = RASQAL_GOOD_CAST(signed char, t);
347 
348     /* parse second whole part */
349 
350     for(q = ++p; ISNUM(*p); p++)
351       ;
352     l = RASQAL_GOOD_CAST(unsigned int, p - q);
353 
354     /* error if second is not 2 digits or separator is not
355      * '.' (second fraction)
356      * 'Z' (utc)
357      * '+' or '-' (timezone offset)
358      * nul (end of string - second fraction and timezone are optional)
359      */
360     if(l != 2 || (*p && *p != '.' && *p != 'Z' && *p != '+' && *p != '-'))
361       return -6;
362 
363     t = RASQAL_GOOD_CAST(unsigned int, (*q++ - '0')*10);
364     t += RASQAL_GOOD_CAST(unsigned int, *q - '0');
365 
366     /* second must be 0..59
367     * (no need to check for < 0)
368     */
369     if(t > 59)
370       return -6;
371 
372     result->second = RASQAL_GOOD_CAST(signed char, t);
373 
374     /* now that we have hour, minute and second, we can check
375      * if hour == 24 -> only 24:00:00 permitted (normalized later)
376      */
377     if(result->hour == 24 && (result->minute || result->second))
378       return -7;
379 
380     /* parse fraction seconds if any */
381     result->microseconds = 0;
382     if(*p == '.') {
383       for(q = ++p; ISNUM(*p); p++)
384         ;
385 
386       /* ignore trailing zeros */
387       while(*--p == '0')
388         ;
389       p++;
390 
391       if(!(*q == '0' && q == p)) {
392         /* allow ".0" */
393         l = RASQAL_GOOD_CAST(unsigned int, p - q);
394 
395         if(l < 1) /* need at least 1 num */
396           return -8;
397 
398         /* support only to microseconds with truncation */
399         if(l > MICROSECONDS_MAX_DIGITS)
400           l = MICROSECONDS_MAX_DIGITS;
401 
402         result->microseconds = 0;
403         for(t2 = 0; t2 < MICROSECONDS_MAX_DIGITS; ++t2) {
404           if(t2 < l)
405             result->microseconds += (*q++ - '0');
406           if(t2 != MICROSECONDS_MAX_DIGITS - 1)
407             result->microseconds *= 10;
408         }
409 
410       }
411 
412       /* skip ignored trailing zeros */
413       while(*p == '0')
414         p++;
415     }
416 
417   } else { /* end if is_dateTime */
418     /* set to center of day interval (noon) */
419     result->hour = 12;
420     result->minute = 0;
421     result->second = 0;
422     result->microseconds = 0;
423   }
424 
425 
426   /* parse & adjust timezone offset */
427   /* result is normalized later */
428   result->timezone_minutes = RASQAL_XSD_DATETIME_NO_TZ;
429   result->have_tz = 'N';
430   if(*p) {
431     if(*p == 'Z') {
432       /* utc timezone - no need to adjust */
433       result->timezone_minutes = 0;
434       result->have_tz = 'Z';
435       p++;
436     } else if(*p == '+' || *p == '-') {
437       result->timezone_minutes = 0;
438       result->have_tz = 'Y';
439 
440       /* work out timezone offsets */
441       is_neg = *p == '-';
442 
443       /* timezone hours */
444       for(q = ++p; ISNUM(*p); p++)
445         ;
446       l = RASQAL_GOOD_CAST(unsigned int, p - q);
447       if(l != 2 || *p!=':')
448         return -9;
449 
450       t2 = RASQAL_GOOD_CAST(unsigned int, (*q++ - '0') * 10);
451       t2 += RASQAL_GOOD_CAST(unsigned int, *q - '0');
452       if(t2 > 14)
453         /* tz offset hours are restricted to 0..14
454          * (no need to check for < 0)
455          */
456         return -9;
457 
458       result->timezone_minutes = RASQAL_GOOD_CAST(short int, (is_neg ? -t2 : t2) * 60);
459 
460       /* timezone minutes */
461       for(q = ++p; ISNUM(*p); p++)
462         ;
463       l = RASQAL_GOOD_CAST(unsigned int, p - q);
464       if(l != 2)
465         return -10;
466 
467       t = RASQAL_GOOD_CAST(unsigned int, (*q++ - '0') * 10);
468       t += RASQAL_GOOD_CAST(unsigned int, *q - '0');
469       if(t > 59 || (t2 == 14 && t != 0)) {
470         /* tz offset minutes are restricted to 0..59
471          * (no need to check for < 0)
472          * or 0 if hour offset is exactly +-14
473          */
474         return -10;
475       }
476 
477       result->timezone_minutes = RASQAL_GOOD_CAST(short int, result->timezone_minutes + RASQAL_GOOD_CAST(short int, (is_neg ? -t : t)));
478     }
479 
480     /* failure if extra chars after the timezone part */
481     if(*p)
482       return -11;
483 
484   }
485 
486   /* Initialise field even though this is not valid before
487    * rasqal_xsd_datetime_normalize() is called on this object.
488    */
489   result->time_on_timeline = 0;
490 
491   return 0;
492 }
493 
494 
495 static int
rasqal_xsd_date_parse(const char * date_string,rasqal_xsd_date * result)496 rasqal_xsd_date_parse(const char *date_string, rasqal_xsd_date *result)
497 {
498   rasqal_xsd_datetime dt_result; /* on stack */
499   int rc;
500 
501   rc = rasqal_xsd_datetime_parse(date_string, &dt_result, 0);
502   if(!rc) {
503     result->year = dt_result.year;
504     result->month = dt_result.month;
505     result->day = dt_result.day;
506     result->time_on_timeline = dt_result.time_on_timeline;
507     result->timezone_minutes = dt_result.timezone_minutes;
508     result->have_tz = dt_result.have_tz; /* This will be N or Z */
509   }
510 
511   return rc;
512 }
513 
514 #ifdef STANDALONE
515 /**
516  * rasqal_xsd_date_normalize:
517  * @date: date
518  *
519  * INTERNAL - Normalize a date into the allowed range
520  *
521  * Return value: zero on success, non zero on failure.
522  */
523 static int
rasqal_xsd_date_normalize(rasqal_xsd_date * date)524 rasqal_xsd_date_normalize(rasqal_xsd_date *date)
525 {
526   rasqal_xsd_datetime dt_result; /* on stack */
527   int rc;
528 
529   memset(&dt_result, '\0', sizeof(dt_result));
530 
531   dt_result.year = date->year;
532   dt_result.month = date->month;
533   dt_result.day = date->day;
534   /* set to center of day interval (noon) */
535   dt_result.hour   = 12;
536   dt_result.minute =  0;
537   dt_result.second =  0;
538   dt_result.microseconds = 0;
539   dt_result.timezone_minutes = date->timezone_minutes;
540   dt_result.have_tz = date->have_tz;
541 
542   rc = rasqal_xsd_datetime_normalize(&dt_result);
543   if(!rc) {
544     date->year = dt_result.year;
545     date->month = dt_result.month;
546     date->day = dt_result.day;
547     date->time_on_timeline = dt_result.time_on_timeline;
548     date->timezone_minutes = dt_result.timezone_minutes;
549     date->have_tz = dt_result.have_tz; /* This will be N or Z */
550   }
551 
552   return rc;
553 }
554 #endif /* STANDALONE */
555 
556 
557 
558 /**
559  * rasqal_new_xsd_datetime:
560  * @world: world object
561  * @datetime_string: XSD Datetime string
562  *
563  * Constructor - make a new XSD datetime object from a string
564  *
565  * Return value: new datetime or NULL on failure
566  */
567 rasqal_xsd_datetime*
rasqal_new_xsd_datetime(rasqal_world * world,const char * datetime_string)568 rasqal_new_xsd_datetime(rasqal_world* world, const char *datetime_string)
569 {
570   rasqal_xsd_datetime* dt;
571   int rc = 0;
572 
573   dt = RASQAL_MALLOC(rasqal_xsd_datetime*, sizeof(*dt));
574   if(!dt)
575     return NULL;
576 
577   rc = rasqal_xsd_datetime_parse(datetime_string, dt, 1);
578   if(!rc) {
579     rasqal_xsd_datetime dt_temp; /* copy on stack to normalize */
580     memcpy(&dt_temp, dt, sizeof(dt_temp));
581 
582     rc = rasqal_xsd_datetime_normalize(&dt_temp);
583     if(!rc)
584       dt->time_on_timeline = dt_temp.time_on_timeline;
585   }
586 
587   if(rc) {
588     rasqal_free_xsd_datetime(dt); dt = NULL;
589   }
590 
591   return dt;
592 }
593 
594 
595 /**
596  * rasqal_new_xsd_datetime_from_unixtime:
597  * @world: world object
598  * @secs: unixtime
599  *
600  * Constructor - make a new XSD datetime object from unixtime seconds
601  *
602  * Return value: new datetime or NULL on failure
603  */
604 rasqal_xsd_datetime*
rasqal_new_xsd_datetime_from_unixtime(rasqal_world * world,time_t secs)605 rasqal_new_xsd_datetime_from_unixtime(rasqal_world* world, time_t secs)
606 {
607   rasqal_xsd_datetime* dt;
608   int rc = 0;
609 
610   dt = RASQAL_MALLOC(rasqal_xsd_datetime*, sizeof(*dt));
611   if(!dt)
612     return NULL;
613 
614   rc = rasqal_xsd_datetime_set_from_unixtime(dt, secs);
615 
616   if(rc) {
617     rasqal_free_xsd_datetime(dt); dt = NULL;
618   }
619 
620   return dt;
621 }
622 
623 
624 /**
625  * rasqal_new_xsd_datetime_from_timeval:
626  * @world: world object
627  * @tv: pointer to struct timeval
628  *
629  * Constructor - make a new XSD datetime object from a timeval
630  *
631  * Return value: new datetime or NULL on failure
632  */
633 rasqal_xsd_datetime*
rasqal_new_xsd_datetime_from_timeval(rasqal_world * world,struct timeval * tv)634 rasqal_new_xsd_datetime_from_timeval(rasqal_world* world, struct timeval *tv)
635 {
636   rasqal_xsd_datetime* dt;
637   int rc = 0;
638 
639   dt = RASQAL_MALLOC(rasqal_xsd_datetime*, sizeof(*dt));
640   if(!dt)
641     return NULL;
642 
643   rc = rasqal_xsd_datetime_set_from_timeval(dt, tv);
644 
645   if(rc) {
646     rasqal_free_xsd_datetime(dt); dt = NULL;
647   }
648 
649   return dt;
650 }
651 
652 
653 /**
654  * rasqal_new_xsd_datetime_from_xsd_date:
655  * @world: world object
656  * @date: pointer to XSD date
657  *
658  * Constructor - make a new XSD datetime object from an XSD date
659  *
660  * Return value: new datetime or NULL on failure
661  */
662 rasqal_xsd_datetime*
rasqal_new_xsd_datetime_from_xsd_date(rasqal_world * world,rasqal_xsd_date * date)663 rasqal_new_xsd_datetime_from_xsd_date(rasqal_world* world, rasqal_xsd_date *date)
664 {
665   rasqal_xsd_datetime* dt;
666 
667   dt = RASQAL_CALLOC(rasqal_xsd_datetime*, 1, sizeof(*dt));
668   if(!dt)
669     return NULL;
670 
671   dt->year = date->year;
672   dt->month = date->month;
673   dt->day = date->day;
674   /* hour, minute, seconds, microseconds are all zero from calloc */
675   dt->timezone_minutes = date->timezone_minutes;
676   dt->time_on_timeline = date->time_on_timeline;
677   dt->have_tz = date->have_tz;
678 
679   return dt;
680 }
681 
682 
683 /**
684  * rasqal_free_xsd_datetime:
685  * @dt: datetime object
686  *
687  * Destroy XSD datetime object.
688  **/
689 void
rasqal_free_xsd_datetime(rasqal_xsd_datetime * dt)690 rasqal_free_xsd_datetime(rasqal_xsd_datetime* dt)
691 {
692   if(!dt)
693     return;
694 
695   RASQAL_FREE(datetime, dt);
696 }
697 
698 
699 #define TIMEZONE_BUFFER_LEN 6
700 
701 /*
702  * rasqal_xsd_timezone_format:
703  * @timezone_minutes: timezone minutes from #rasqal_xsd_datetime or #rasqal_xsd_date
704  * @have_tz: have tz flag from #rasqal_xsd_datetime or #rasqal_xsd_date
705  * @buffer: buffer to write the formatted timezone
706  * @bufsize: length of @buffer; must be 7 or larger
707  *
708  * INTERNAL - format a timezone into the passed in buffer
709  *
710  * Return value: size of buffer or 0 on failure
711  */
712 static int
rasqal_xsd_timezone_format(signed short timezone_minutes,char have_tz,char * buffer,size_t bufsize)713 rasqal_xsd_timezone_format(signed short timezone_minutes,
714                            char have_tz,
715                            char* buffer, size_t bufsize)
716 {
717   size_t tz_len;
718 
719   if(!buffer || !bufsize)
720     return -1;
721 
722   if(have_tz == 'N') {
723     tz_len = 0;
724 
725     buffer[0] = '\0';
726   } else if(have_tz == 'Z') {
727     tz_len = 1;
728     if(bufsize < (tz_len + 1))
729       return -1;
730 
731     buffer[0] = 'Z';
732     buffer[1] = '\0';
733   } else {
734     int mins;
735     int hours;
736     int digit;
737 
738     tz_len = TIMEZONE_BUFFER_LEN;
739 
740     if(bufsize < (tz_len + 1))
741       return -1;
742 
743     mins = abs(timezone_minutes);
744     buffer[0] = (!mins || mins != timezone_minutes ? '-' : '+');
745 
746     hours = (mins / 60);
747     digit = (hours / 10);
748     buffer[1] = RASQAL_GOOD_CAST(char, digit + '0');
749     buffer[2] = RASQAL_GOOD_CAST(char, hours - (digit * 10) + '0');
750     buffer[3] = ':';
751 
752     mins -= hours * 60;
753     buffer[4] = RASQAL_GOOD_CAST(char, (mins / 10) + '0');
754     mins -= mins * 10;
755     buffer[5] = RASQAL_GOOD_CAST(char, mins + '0');
756 
757     buffer[6] = '\0';
758   }
759 
760   return RASQAL_GOOD_CAST(int, tz_len);
761 }
762 
763 
764 static int
rasqal_xsd_format_microseconds(char * buffer,size_t bufsize,unsigned int microseconds)765 rasqal_xsd_format_microseconds(char* buffer, size_t bufsize,
766                                unsigned int microseconds)
767 {
768   int len = 0;
769   char *p;
770   unsigned int value;
771   unsigned int base = 10;
772   unsigned int multiplier;
773 
774   value = microseconds;
775   multiplier = 100000;
776   do {
777     value = value % multiplier;
778     multiplier /= base;
779     len++;
780   } while(value && multiplier);
781 
782   if(!buffer || RASQAL_GOOD_CAST(int, bufsize) < (len + 1)) /* +1 for NUL */
783     return len;
784 
785   value = microseconds;
786   multiplier = 100000;
787   p = buffer;
788   do {
789     unsigned digit = value / multiplier;
790     *p++ = RASQAL_GOOD_CAST(char, '0' + digit);
791     value = value % multiplier;
792     multiplier /= base;
793   } while(value && multiplier);
794   *p = '\0';
795 
796   return len;
797 }
798 
799 
800 /**
801  * rasqal_xsd_datetime_to_counted_string:
802  * @dt: datetime struct
803  * @len_p: output length (or NULL)
804  *
805  * Convert a #rasqal_xsd_datetime struct to a xsd:dateTime lexical form counted string.
806  *
807  * Caller should rasqal_free_memory() the returned string.
808  *
809  * See http://www.w3.org/TR/xmlschema-2/#dateTime-canonical-representation
810  *
811  * Return value: lexical form string or NULL on failure.
812  */
813 char*
rasqal_xsd_datetime_to_counted_string(const rasqal_xsd_datetime * dt,size_t * len_p)814 rasqal_xsd_datetime_to_counted_string(const rasqal_xsd_datetime *dt,
815                                       size_t *len_p)
816 {
817   size_t len;
818   char *buffer = NULL;
819   char *p;
820   /* "[+-]HH:MM\0" */
821   char timezone_string[TIMEZONE_BUFFER_LEN + 1];
822   size_t year_len;
823   int tz_string_len;
824   size_t microseconds_len = 0;
825 
826   /*
827    * http://www.w3.org/TR/xmlschema-2/#dateTime-canonical-representation
828    *
829    * "Except for trailing fractional zero digits in the seconds representation,
830    * '24:00:00' time representations, and timezone (for timezoned values),
831    * the mapping from literals to values is one-to-one.
832    * Where there is more than one possible representation,
833    * the canonical representation is as follows:
834    *    * The 2-digit numeral representing the hour must not be '24';
835    *    * The fractional second string, if present, must not end in '0';
836    *    * for timezoned values, the timezone must be represented with 'Z'
837    *      (All timezoned dateTime values are UTC.)."
838    */
839 
840   if(!dt)
841     return NULL;
842 
843   tz_string_len = rasqal_xsd_timezone_format(dt->timezone_minutes, dt->have_tz,
844                                              timezone_string,
845                                              TIMEZONE_BUFFER_LEN + 1);
846   if(tz_string_len < 0)
847     return NULL;
848 
849   year_len = rasqal_format_integer(NULL, 0, dt->year, 4, '0');
850 
851   len = year_len +
852         RASQAL_GOOD_CAST(size_t, 15) + /* "-MM-DDTHH:MM:SS" = 15 */
853         RASQAL_GOOD_CAST(size_t, tz_string_len);
854   if(dt->microseconds) {
855     microseconds_len = RASQAL_GOOD_CAST(size_t, rasqal_xsd_format_microseconds(NULL, 0,
856                                                                                RASQAL_GOOD_CAST(unsigned int, dt->microseconds)));
857     len += 1 /* . */ + microseconds_len;
858   }
859 
860   if(len_p)
861     *len_p = len;
862 
863   buffer = RASQAL_MALLOC(char*, len + 1);
864   if(!buffer)
865     return NULL;
866 
867   p = buffer;
868   p += rasqal_format_integer(p, year_len + 1, dt->year, 4, '0');
869   *p++ = '-';
870   p += rasqal_format_integer(p, 2 + 1, dt->month, 2, '0');
871   *p++ = '-';
872   p += rasqal_format_integer(p, 2 + 1, dt->day, 2, '0');
873   *p++ = 'T';
874 
875   p += rasqal_format_integer(p, 2 + 1, dt->hour, 2, '0');
876   *p++ = ':';
877   p += rasqal_format_integer(p, 2 + 1, dt->minute, 2, '0');
878   *p++ = ':';
879   p += rasqal_format_integer(p, 2 + 1, dt->second, 2, '0');
880 
881   if(dt->microseconds) {
882     *p++ = '.';
883     p += rasqal_xsd_format_microseconds(p, microseconds_len + 1,
884                                         RASQAL_GOOD_CAST(unsigned int, dt->microseconds));
885   }
886   if(tz_string_len) {
887     memcpy(p, timezone_string, RASQAL_GOOD_CAST(size_t, tz_string_len));
888     p += tz_string_len;
889   }
890 
891   *p = '\0';
892 
893   return buffer;
894 }
895 
896 
897 /**
898  * rasqal_xsd_datetime_to_string:
899  * @dt: datetime struct
900  *
901  * Convert a #rasqal_xsd_datetime struct to a xsd:dateTime lexical form string.
902  *
903  * Caller should rasqal_free_memory() the returned string.
904  *
905  * Return value: lexical form string or NULL on failure.
906  */
907 char*
rasqal_xsd_datetime_to_string(const rasqal_xsd_datetime * dt)908 rasqal_xsd_datetime_to_string(const rasqal_xsd_datetime *dt)
909 {
910   return rasqal_xsd_datetime_to_counted_string(dt, NULL);
911 }
912 
913 
914 /**
915  * rasqal_xsd_datetime_equals2:
916  * @dt1: first XSD dateTime
917  * @dt2: second XSD dateTime
918  * @incomparible_p: address to store incomparable flag (or NULL)
919  *
920  * Compare two XSD dateTimes for equality.
921  *
922  * Return value: non-0 if equal.
923  **/
924 int
rasqal_xsd_datetime_equals2(const rasqal_xsd_datetime * dt1,const rasqal_xsd_datetime * dt2,int * incomparible_p)925 rasqal_xsd_datetime_equals2(const rasqal_xsd_datetime *dt1,
926                             const rasqal_xsd_datetime *dt2,
927                             int *incomparible_p)
928 {
929   int cmp = rasqal_xsd_datetime_compare2(dt1, dt2, incomparible_p);
930   return !cmp;
931 }
932 
933 
934 #ifndef RASQAL_DISABLE_DEPRECATED
935 /**
936  * rasqal_xsd_datetime_equals:
937  * @dt1: first XSD dateTime
938  * @dt2: second XSD dateTime
939  *
940  * Compare two XSD dateTimes for equality.
941  *
942  * @Deprecated: for rasqal_xsd_datetime_equals2 that returns incomparibility.
943  *
944  * Return value: non-0 if equal.
945  **/
946 int
rasqal_xsd_datetime_equals(const rasqal_xsd_datetime * dt1,const rasqal_xsd_datetime * dt2)947 rasqal_xsd_datetime_equals(const rasqal_xsd_datetime *dt1,
948                            const rasqal_xsd_datetime *dt2)
949 {
950   return rasqal_xsd_datetime_equals2(dt1, dt2, NULL);
951 }
952 #endif
953 
954 /*
955  * 3.2.7.4 Order relation on dateTime
956  * http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#dateTime
957  */
958 static int
rasqal_xsd_timeline_compare(time_t dt_timeline1,signed int dt_msec1,signed short tz_minutes1,time_t dt_timeline2,signed int dt_msec2,signed short tz_minutes2,int * incomparible_p)959 rasqal_xsd_timeline_compare(time_t dt_timeline1, signed int dt_msec1,
960                             signed short tz_minutes1,
961                             time_t dt_timeline2, signed int dt_msec2,
962                             signed short tz_minutes2,
963                             int *incomparible_p)
964 {
965   int dt1_has_tz = (tz_minutes1 != RASQAL_XSD_DATETIME_NO_TZ);
966   int dt2_has_tz = (tz_minutes2 != RASQAL_XSD_DATETIME_NO_TZ);
967   int rc;
968 
969 #define SECS_FOR_14_HOURS (14 * 3600)
970 
971   /* Normalize - if there is a timezone that is not Z, convert it to Z
972    *
973    * Already done in rasqal_xsd_datetime_normalize() on construction
974    */
975 
976   if(dt1_has_tz == dt2_has_tz) {
977     /* both are on same timeline */
978     if(dt_timeline1 < dt_timeline2)
979       rc = -1;
980     else if(dt_timeline1 > dt_timeline2)
981       rc = 1;
982     else
983       rc = dt_msec1 - dt_msec2;
984   } else if(dt1_has_tz) {
985     /* dt1 has a tz, dt2 has no tz */
986     if(dt_timeline1 < (dt_timeline2 - SECS_FOR_14_HOURS))
987       rc = -1;
988     else if(dt_timeline1 > (dt_timeline2 + SECS_FOR_14_HOURS))
989       rc = 1;
990     else {
991       if(incomparible_p)
992         *incomparible_p = 1;
993       rc = 2; /* incomparible really */
994     }
995   } else {
996     /* dt1 has no tz, dt2 has a tz */
997     if((dt_timeline1 + SECS_FOR_14_HOURS) < dt_timeline2)
998       rc = -1;
999     else if((dt_timeline1 - SECS_FOR_14_HOURS) > dt_timeline2)
1000       rc = 1;
1001     else {
1002       if(incomparible_p)
1003         *incomparible_p = 1;
1004       rc = 2; /* incomparible really */
1005     }
1006   }
1007 
1008   return rc;
1009 }
1010 
1011 
1012 /**
1013  * rasqal_xsd_datetime_compare2:
1014  * @dt1: first XSD dateTime
1015  * @dt2: second XSD dateTime
1016  * @incomparible_p: address to store incomparable flag (or NULL)
1017  *
1018  * Compare two XSD dateTimes
1019  *
1020  * If the only one of the two dateTimes have timezones, the results
1021  * may be incomparible and that will return >0 and set the
1022  * value of the int point to by @incomparible_p to non-0
1023  *
1024  * Return value: <0 if @dt1 is less than @dt2, 0 if equal, >0 otherwise
1025  **/
1026 int
rasqal_xsd_datetime_compare2(const rasqal_xsd_datetime * dt1,const rasqal_xsd_datetime * dt2,int * incomparible_p)1027 rasqal_xsd_datetime_compare2(const rasqal_xsd_datetime *dt1,
1028                              const rasqal_xsd_datetime *dt2,
1029                              int *incomparible_p)
1030 {
1031   if(incomparible_p)
1032     *incomparible_p = 0;
1033 
1034   /* Handle NULLs */
1035   if(!dt1 || !dt2) {
1036     /* NULLs sort earlier. equal only if both are NULL */
1037     if(!dt1 && !dt2)
1038       return 0;
1039 
1040     return (!dt1) ? -1 : 1;
1041   }
1042 
1043   return rasqal_xsd_timeline_compare(dt1->time_on_timeline, dt1->microseconds,
1044                                      dt1->timezone_minutes,
1045                                      dt2->time_on_timeline, dt2->microseconds,
1046                                      dt2->timezone_minutes,
1047                                      incomparible_p);
1048 }
1049 
1050 #ifndef RASQAL_DISABLE_DEPRECATED
1051 /**
1052  * rasqal_xsd_datetime_compare:
1053  * @dt1: first XSD dateTime
1054  * @dt2: second XSD dateTime
1055  *
1056  * Compare two XSD dateTimes
1057  *
1058  * @Deprecated for rasqal_xsd_datetime_compare2() which can return the incomparible result.
1059  *
1060  * Return value: <0 if @dt1 is less than @dt2, 0 if equal, >0 otherwise
1061  **/
1062 int
rasqal_xsd_datetime_compare(const rasqal_xsd_datetime * dt1,const rasqal_xsd_datetime * dt2)1063 rasqal_xsd_datetime_compare(const rasqal_xsd_datetime *dt1,
1064                             const rasqal_xsd_datetime *dt2)
1065 {
1066   return rasqal_xsd_datetime_compare2(dt1, dt2, NULL);
1067 }
1068 #endif
1069 
1070 /**
1071  * rasqal_xsd_datetime_get_seconds_as_decimal:
1072  * @world: world object
1073  * @dt: XSD dateTime
1074  *
1075  * Get the seconds component of a dateTime as a decimal
1076  *
1077  * Return value: decimal object or NULL on failure
1078  **/
1079 rasqal_xsd_decimal*
rasqal_xsd_datetime_get_seconds_as_decimal(rasqal_world * world,rasqal_xsd_datetime * dt)1080 rasqal_xsd_datetime_get_seconds_as_decimal(rasqal_world* world,
1081                                            rasqal_xsd_datetime* dt)
1082 {
1083   rasqal_xsd_decimal* dec;
1084 
1085   dec = rasqal_new_xsd_decimal(world);
1086   if(!dec)
1087     return NULL;
1088 
1089   if(!dt->microseconds) {
1090     rasqal_xsd_decimal_set_long(dec, (long)dt->second);
1091   } else {
1092     /* Max len 9 "SS.UUUUUU\0" */
1093     char str[10];
1094 
1095     sprintf(str, "%d.%06d", dt->second, dt->microseconds);
1096 
1097     rasqal_xsd_decimal_set_string(dec, str);
1098   }
1099 
1100   return dec;
1101 }
1102 
1103 
1104 /* xsd:date formatted length excluding formatted year length */
1105 #define DATE_BUFFER_LEN_NO_YEAR 6
1106 
1107 /**
1108  * rasqal_xsd_date_to_counted_string:
1109  * @date: date struct
1110  * @len_p: output length (or NULL)
1111  *
1112  * Convert a #rasqal_xsd_date struct to a xsd:date lexical form string.
1113  *
1114  * Caller should rasqal_free_memory() the returned string.
1115  *
1116  * See http://www.w3.org/TR/xmlschema-2/#date-canonical-representation
1117  *
1118  * Return value: lexical form string or NULL on failure.
1119  */
1120 char*
rasqal_xsd_date_to_counted_string(const rasqal_xsd_date * date,size_t * len_p)1121 rasqal_xsd_date_to_counted_string(const rasqal_xsd_date *date, size_t *len_p)
1122 {
1123   char *buffer = NULL;
1124   size_t len;
1125   char *p;
1126   int value;
1127   unsigned int d;
1128   size_t year_len;
1129   /* "[+-]HH:MM\0" */
1130   char timezone_string[TIMEZONE_BUFFER_LEN + 1];
1131   int tz_string_len;
1132 
1133   /* http://www.w3.org/TR/xmlschema-2/#date-canonical-representation
1134    *
1135    * "the date portion of the canonical representation (the entire
1136    * representation for nontimezoned values, and all but the timezone
1137    * representation for timezoned values) is always the date portion of
1138    * the dateTime canonical representation of the interval midpoint
1139    * (the dateTime representation, truncated on the right to eliminate
1140    * 'T' and all following characters). For timezoned values, append
1141    * the canonical representation of the ·recoverable timezone·. "
1142    *
1143    */
1144 
1145   if(!date)
1146     return NULL;
1147 
1148   tz_string_len = rasqal_xsd_timezone_format(date->timezone_minutes,
1149                                              date->have_tz,
1150                                              timezone_string,
1151                                              TIMEZONE_BUFFER_LEN + 1);
1152   if(tz_string_len < 0)
1153     return NULL;
1154 
1155   year_len = rasqal_format_integer(NULL, 0, date->year, -1, '\0');
1156 
1157   len = year_len + DATE_BUFFER_LEN_NO_YEAR + RASQAL_GOOD_CAST(size_t, tz_string_len);
1158 
1159   if(len_p)
1160     *len_p = len;
1161 
1162   buffer = RASQAL_MALLOC(char*, len + 1);
1163   if(!buffer)
1164     return NULL;
1165 
1166   p = buffer;
1167 
1168   /* value is year; length can vary */
1169   p += rasqal_format_integer(p, year_len + 1, date->year, -1, '\0');
1170 
1171   *p++ = '-';
1172 
1173   /* value is 2-digit month */
1174   value = date->month;
1175   d = RASQAL_GOOD_CAST(unsigned int, (value / 10));
1176   *p++ = RASQAL_GOOD_CAST(char, d + '0');
1177   value -= RASQAL_GOOD_CAST(int, d * 10);
1178   *p++ = RASQAL_GOOD_CAST(char, value + '0');
1179 
1180   *p++ = '-';
1181 
1182   /* value is 2-digit day */
1183   value = date->day;
1184   d = RASQAL_GOOD_CAST(unsigned int, (value / 10));
1185   *p++ = RASQAL_GOOD_CAST(char, d + '0');
1186   value -= RASQAL_GOOD_CAST(int, d * 10);
1187   *p++ = RASQAL_GOOD_CAST(char, value + '0');
1188 
1189   if(tz_string_len) {
1190     memcpy(p, timezone_string, RASQAL_GOOD_CAST(size_t, tz_string_len));
1191     p += tz_string_len;
1192   }
1193 
1194   *p = '\0';
1195 
1196   return buffer;
1197 }
1198 
1199 
1200 /**
1201  * rasqal_xsd_date_to_string:
1202  * @d: date struct
1203  *
1204  * Convert a #rasqal_xsd_date struct to a xsd:date lexical form string.
1205  *
1206  * Caller should rasqal_free_memory() the returned string.
1207  *
1208  * Return value: lexical form string or NULL on failure.
1209  */
1210 char*
rasqal_xsd_date_to_string(const rasqal_xsd_date * d)1211 rasqal_xsd_date_to_string(const rasqal_xsd_date *d)
1212 {
1213   return rasqal_xsd_date_to_counted_string(d, NULL);
1214 }
1215 
1216 
1217 /**
1218  * days_per_month:
1219  * @month: month 1-12
1220  * @year: gregorian year
1221  *
1222  * INTERNAL - returns the number of days in given month and year.
1223  *
1224  * Return value: number of days or 0 on invalid arguments
1225  */
1226 static unsigned int
days_per_month(int month,int year)1227 days_per_month(int month, int year) {
1228   switch(month) {
1229     case 1:
1230     case 3:
1231     case 5:
1232     case 7:
1233     case 8:
1234     case 10:
1235     case 12:
1236       return 31;
1237 
1238     case 4:
1239     case 6:
1240     case 9:
1241     case 11:
1242       return 30;
1243 
1244     case 2:
1245       /* any of bottom 2 bits non-zero -> not 0 mod 4 -> not leap year */
1246       if(year & 3)
1247         return 28;
1248 
1249       /* 0 mod 400 and 0 mod 4 -> leap year */
1250       if(!(year % 400))
1251         return 29;
1252 
1253       /* 0 mod 100 and not 0 mod 400 and 0 mod 4 -> not leap year */
1254       if(!(year % 100))
1255         return 28;
1256 
1257       /* other 0 mod 4 years -> leap year */
1258       return 29;
1259 
1260     default:
1261        /* error */
1262       return 0;
1263   }
1264 }
1265 
1266 
1267 int
rasqal_xsd_datetime_check(const char * string)1268 rasqal_xsd_datetime_check(const char* string)
1269 {
1270   rasqal_xsd_datetime d;
1271 
1272   /* This should be correct according to
1273    * http://www.w3.org/TR/xmlschema-2/#dateTime
1274    */
1275   return !rasqal_xsd_datetime_parse(string, &d, 1);
1276 }
1277 
1278 
1279 int
rasqal_xsd_date_check(const char * string)1280 rasqal_xsd_date_check(const char* string)
1281 {
1282   rasqal_xsd_date d;
1283 
1284   /* This should be correct according to
1285    * http://www.w3.org/TR/xmlschema-2/#date
1286    */
1287   return !rasqal_xsd_date_parse(string, &d);
1288 }
1289 
1290 
1291 #define TM_YEAR_ORIGIN 1900
1292 #define TM_MONTH_ORIGIN 1
1293 
1294 /**
1295  * rasqal_xsd_datetime_set_from_timeval:
1296  * @dt: datetime
1297  * @tv: timeval
1298  *
1299  * Set an XSD dateTime from a struct timeval pointer
1300  *
1301  * Returns: non-0 on failure
1302  **/
1303 int
rasqal_xsd_datetime_set_from_timeval(rasqal_xsd_datetime * dt,struct timeval * tv)1304 rasqal_xsd_datetime_set_from_timeval(rasqal_xsd_datetime *dt,
1305                                      struct timeval *tv)
1306 {
1307   struct tm* my_time;
1308 #ifdef HAVE_GMTIME_R
1309   struct tm time_buf;
1310 #endif
1311   time_t sec;
1312 
1313   if(!dt || !tv)
1314     return 1;
1315 
1316   sec = (time_t)tv->tv_sec;
1317 #ifdef HAVE_GMTIME_R
1318   memset(&time_buf, '\0', sizeof(time_buf));
1319   my_time = gmtime_r(&sec, &time_buf);
1320 #else
1321   my_time = gmtime(&sec);
1322 #endif
1323   if(!my_time)
1324     return 1;
1325 
1326   dt->year = my_time->tm_year + TM_YEAR_ORIGIN;
1327   dt->month = RASQAL_GOOD_CAST(unsigned char, my_time->tm_mon + TM_MONTH_ORIGIN);
1328   dt->day = RASQAL_GOOD_CAST(unsigned char, my_time->tm_mday);
1329   dt->hour = RASQAL_GOOD_CAST(signed char, my_time->tm_hour);
1330   dt->minute = RASQAL_GOOD_CAST(signed char, my_time->tm_min);
1331   dt->second = RASQAL_GOOD_CAST(signed char, my_time->tm_sec);
1332   dt->microseconds = RASQAL_GOOD_CAST(int, tv->tv_usec);
1333   dt->timezone_minutes = 0; /* always Zulu time */
1334   dt->have_tz = 'Z';
1335 
1336   return 0;
1337 }
1338 
1339 
1340 /**
1341  * rasqal_xsd_datetime_set_from_unixtime:
1342  * @dt: date time
1343  * @clock: unix time in seconds
1344  *
1345  * Set an XSD dateTime from unixtime seconds
1346  *
1347  * Returns: non-0 on failure
1348  **/
1349 int
rasqal_xsd_datetime_set_from_unixtime(rasqal_xsd_datetime * dt,time_t secs)1350 rasqal_xsd_datetime_set_from_unixtime(rasqal_xsd_datetime* dt,
1351                                       time_t secs)
1352 {
1353   struct timeval tv;
1354 
1355   if(!dt)
1356     return 1;
1357 
1358   tv.tv_sec = secs;
1359   tv.tv_usec = 0;
1360 
1361   return rasqal_xsd_datetime_set_from_timeval(dt, &tv);
1362 }
1363 
1364 
1365 /**
1366  * rasqal_xsd_datetime_get_as_unixtime:
1367  * @dt: datetime
1368  *
1369  * Get a datetime as unix seconds
1370  *
1371  * Returns: unix seconds or 0 if @dt is NULL
1372  **/
1373 time_t
rasqal_xsd_datetime_get_as_unixtime(rasqal_xsd_datetime * dt)1374 rasqal_xsd_datetime_get_as_unixtime(rasqal_xsd_datetime* dt)
1375 {
1376   struct tm time_buf;
1377 
1378   if(!dt)
1379     return 0;
1380 
1381   memset(&time_buf, '\0', sizeof(time_buf));
1382 
1383   time_buf.tm_year = dt->year - TM_YEAR_ORIGIN;
1384   time_buf.tm_mon  = dt->month - TM_MONTH_ORIGIN;
1385   time_buf.tm_mday = dt->day;
1386   time_buf.tm_hour = dt->hour;
1387   time_buf.tm_min  = dt->minute;
1388   time_buf.tm_sec  = dt->second;
1389   time_buf.tm_wday = 0;
1390   time_buf.tm_yday = 0;
1391   time_buf.tm_isdst = -1;
1392 
1393 #ifdef HAVE_TM_GMTOFF
1394   if(dt->timezone_minutes == RASQAL_XSD_DATETIME_NO_TZ)
1395     time_buf.tm_gmtoff = 0;
1396   else
1397     time_buf.tm_gmtoff = dt->timezone_minutes * 60;
1398 #endif
1399 
1400   return rasqal_timegm(&time_buf);
1401 }
1402 
1403 
1404 /**
1405  * rasqal_xsd_datetime_get_as_timeval:
1406  * @dt: datetime
1407  *
1408  * Get a datetime as struct timeval
1409  *
1410  * The returned timeval must be freed by the caller such as using
1411  * rasqal_free_memory().
1412  *
1413  * Returns: pointer to a new timeval structure or NULL on failure
1414  **/
1415 struct timeval*
rasqal_xsd_datetime_get_as_timeval(rasqal_xsd_datetime * dt)1416 rasqal_xsd_datetime_get_as_timeval(rasqal_xsd_datetime *dt)
1417 {
1418   struct timeval *tv;
1419 
1420   if(!dt)
1421     return NULL;
1422 
1423   tv = RASQAL_CALLOC(struct timeval*, 1, sizeof(*tv));
1424   if(!tv)
1425     return NULL;
1426 
1427   tv->tv_sec = rasqal_xsd_datetime_get_as_unixtime(dt);
1428   tv->tv_usec = dt->microseconds;
1429 
1430   return tv;
1431 }
1432 
1433 
1434 /**
1435  * rasqal_xsd_datetime_get_timezone_as_counted_string:
1436  * @dt: datetime
1437  * @len_p: pointer to store returned string length
1438  *
1439  * Get the timezone of a datetime as a duration format string with optional length count
1440  *
1441  * The returned string is owned by the caller and must be freed
1442  * by rasqal_free_memory().
1443  *
1444  * Returns: pointer to a new string or NULL on failure
1445  **/
1446 char*
rasqal_xsd_datetime_get_timezone_as_counted_string(rasqal_xsd_datetime * dt,size_t * len_p)1447 rasqal_xsd_datetime_get_timezone_as_counted_string(rasqal_xsd_datetime *dt,
1448                                                    size_t *len_p)
1449 {
1450   /* timezone duration as implemented here is a signed integer number
1451    * of seconds like +- 14 hours:minutes (no days or larger units, no
1452    * seconds or smaller).
1453    *
1454    * When written in the canonical format, a restricted
1455    * xsd:dayTimeDuration format, it is constraint to a format like -?PThhmm
1456    *
1457    * For example: -PT14H59M PT14H59M and PT0S for a zero timezone offset
1458    */
1459 #define TZ_STR_SIZE 10
1460   char* tz_str;
1461   char* p;
1462   int minutes;
1463   unsigned int hours;
1464 
1465   if(!dt)
1466     return NULL;
1467 
1468   minutes = dt->timezone_minutes;
1469   if(minutes == RASQAL_XSD_DATETIME_NO_TZ)
1470     return NULL;
1471 
1472   tz_str = RASQAL_MALLOC(char*, TZ_STR_SIZE + 1);
1473   if(!tz_str)
1474     return NULL;
1475 
1476   p = tz_str;
1477 
1478   if(minutes < 0) {
1479     *p++ = '-';
1480     minutes = -minutes;
1481   }
1482 
1483   *p++ = 'P';
1484   *p++ = 'T';
1485 
1486   hours = RASQAL_GOOD_CAST(unsigned int, (minutes / 60));
1487   if(hours) {
1488 #if 1
1489     if(hours > 9) {
1490       *p++ = RASQAL_GOOD_CAST(char, '0' + (hours / 10));
1491       hours %= 10;
1492     }
1493     *p++ = RASQAL_GOOD_CAST(char, '0' + hours);
1494     *p++ = 'H';
1495 #else
1496     p += sprintf(p, "%dH", hours);
1497 #endif
1498     minutes -= RASQAL_GOOD_CAST(int, hours * 60);
1499   }
1500 
1501   if(minutes) {
1502 #if 1
1503     if(minutes > 9) {
1504       *p++ = RASQAL_GOOD_CAST(char, '0' + (minutes / 10));
1505       minutes %= 10;
1506     }
1507     *p++ = RASQAL_GOOD_CAST(char, '0' + minutes);
1508     *p++ = 'M';
1509 #else
1510     p += sprintf(p, "%dM", minutes);
1511 #endif
1512   }
1513 
1514   if(!dt->timezone_minutes) {
1515     *p++ = '0';
1516     *p++ = 'S';
1517   }
1518 
1519   *p = '\0';
1520 
1521   if(len_p)
1522     *len_p = RASQAL_GOOD_CAST(size_t, p - tz_str);
1523 
1524   return tz_str;
1525 }
1526 
1527 
1528 /**
1529  * rasqal_xsd_datetime_get_tz_as_counted_string:
1530  * @dt: datetime
1531  * @len_p: pointer to store returned string length
1532  *
1533  * Get the timezone of a datetime as a timezone string
1534  *
1535  * The returned string is owned by the caller and must be freed
1536  * by rasqal_free_memory().
1537  *
1538  * Returns: pointer to a new string or NULL on failure
1539  **/
1540 char*
rasqal_xsd_datetime_get_tz_as_counted_string(rasqal_xsd_datetime * dt,size_t * len_p)1541 rasqal_xsd_datetime_get_tz_as_counted_string(rasqal_xsd_datetime* dt,
1542                                              size_t *len_p)
1543 {
1544   char* s;
1545 
1546   s = RASQAL_MALLOC(char*, TIMEZONE_BUFFER_LEN + 1);
1547   if(!s)
1548     return NULL;
1549 
1550   if(rasqal_xsd_timezone_format(dt->timezone_minutes, dt->have_tz,
1551                                 s, TIMEZONE_BUFFER_LEN + 1) < 0)
1552     goto failed;
1553 
1554   if(len_p)
1555     *len_p = TIMEZONE_BUFFER_LEN;
1556 
1557   return s;
1558 
1559   failed:
1560   RASQAL_FREE(char*, s);
1561   return NULL;
1562 }
1563 
1564 
1565 /**
1566  * rasqal_new_xsd_date:
1567  * @world: world object
1568  * @date_string: XSD date string
1569  *
1570  * Constructor - make a new XSD date object from a string
1571  *
1572  * Return value: new datetime or NULL on failure
1573  */
1574 rasqal_xsd_date*
rasqal_new_xsd_date(rasqal_world * world,const char * date_string)1575 rasqal_new_xsd_date(rasqal_world* world, const char *date_string)
1576 {
1577   rasqal_xsd_datetime dt_result; /* on stack */
1578   rasqal_xsd_date* d;
1579   int rc = 0;
1580 
1581   d = RASQAL_CALLOC(rasqal_xsd_date*, 1, sizeof(*d));
1582   if(!d)
1583     return NULL;
1584 
1585   rc = rasqal_xsd_datetime_parse(date_string, &dt_result, 0);
1586   if(!rc) {
1587     d->year = dt_result.year;
1588     d->month = dt_result.month;
1589     d->day = dt_result.day;
1590     d->timezone_minutes = dt_result.timezone_minutes;
1591     d->have_tz = dt_result.have_tz;
1592 
1593     dt_result.hour   = 12; /* Noon */
1594     dt_result.minute =  0;
1595     dt_result.second =  0;
1596     dt_result.microseconds = 0;
1597 
1598     rc = rasqal_xsd_datetime_normalize(&dt_result);
1599 
1600     /* Track the starting instant as determined by the timezone */
1601     d->time_on_timeline = dt_result.time_on_timeline;
1602     if(d->timezone_minutes != RASQAL_XSD_DATETIME_NO_TZ)
1603       d->time_on_timeline += (60 * dt_result.timezone_minutes);
1604   }
1605 
1606   if(rc) {
1607     rasqal_free_xsd_date(d); d = NULL;
1608   }
1609 
1610   return d;
1611 }
1612 
1613 
1614 /**
1615  * rasqal_free_xsd_date:
1616  * @d: date object
1617  *
1618  * Destroy XSD date object.
1619  **/
1620 void
rasqal_free_xsd_date(rasqal_xsd_date * d)1621 rasqal_free_xsd_date(rasqal_xsd_date* d)
1622 {
1623   if(!d)
1624     return;
1625 
1626   RASQAL_FREE(rasqal_xsd_date, d);
1627 }
1628 
1629 
1630 /**
1631  * rasqal_xsd_date_equals:
1632  * @d1: first XSD date
1633  * @d2: second XSD date
1634  * @incomparible_p: address to store incomparable flag (or NULL)
1635  *
1636  * Compare two XSD dates for equality.
1637  *
1638  * Return value: non-0 if equal.
1639  **/
1640 int
rasqal_xsd_date_equals(const rasqal_xsd_date * d1,const rasqal_xsd_date * d2,int * incomparible_p)1641 rasqal_xsd_date_equals(const rasqal_xsd_date *d1,
1642                        const rasqal_xsd_date *d2,
1643                        int *incomparible_p)
1644 {
1645   int cmp = rasqal_xsd_date_compare(d1, d2, incomparible_p);
1646   return !cmp;
1647 }
1648 
1649 
1650 /**
1651  * rasqal_xsd_date_compare:
1652  * @d1: first XSD date
1653  * @d2: second XSD date
1654  * @incomparible_p: address to store incomparable flag (or NULL)
1655  *
1656  * Compare two XSD dates
1657  *
1658  * If the only one of the two dates have timezones, the results
1659  * may be incomparible and that will return >0 and set the
1660  * value of the int point to by @incomparible_p to non-0
1661  *
1662  * Return value: <0 if @d1 is less than @d2, 0 if equal, >0 otherwise
1663  **/
1664 int
rasqal_xsd_date_compare(const rasqal_xsd_date * d1,const rasqal_xsd_date * d2,int * incomparible_p)1665 rasqal_xsd_date_compare(const rasqal_xsd_date *d1,
1666                         const rasqal_xsd_date *d2,
1667                         int *incomparible_p)
1668 {
1669   if(incomparible_p)
1670     *incomparible_p = 0;
1671 
1672   /* Handle NULLs */
1673   if(!d1 || !d2) {
1674     /* NULLs sort earlier. equal only if both are NULL */
1675     if(!d1 && !d2)
1676       return 0;
1677 
1678     return (!d1) ? -1 : 1;
1679   }
1680 
1681   return rasqal_xsd_timeline_compare(d1->time_on_timeline, 0 /* msec */,
1682                                      d1->timezone_minutes,
1683                                      d2->time_on_timeline, 0 /* msec */,
1684                                      d2->timezone_minutes,
1685                                      incomparible_p);
1686 }
1687 
1688 
1689 #ifdef STANDALONE
1690 #include <stdio.h>
1691 #ifdef HAVE_SYS_TIME_H
1692 #include <sys/time.h>
1693 #endif
1694 
1695 int main(int argc, char *argv[]);
1696 
1697 #define MYASSERT(c) \
1698   if(!(c)) { \
1699     fprintf(stderr, "%s: assertion failed at %s:%d: %s\n", program, __FILE__, __LINE__, #c); \
1700     exit(1); \
1701   }
1702 
1703 static int
test_datetime_parse_and_normalize(const char * datetime_string,rasqal_xsd_datetime * result)1704 test_datetime_parse_and_normalize(const char *datetime_string,
1705                                   rasqal_xsd_datetime *result)
1706 {
1707   if(rasqal_xsd_datetime_parse(datetime_string, result, 1))
1708     return 1;
1709 
1710   return rasqal_xsd_datetime_normalize(result);
1711 }
1712 
1713 static int
test_datetime_parser_tostring(const char * in_str,const char * out_expected)1714 test_datetime_parser_tostring(const char *in_str, const char *out_expected)
1715 {
1716   rasqal_xsd_datetime d; /* allocated on stack */
1717   char const *s = NULL;
1718   int r = 1;
1719 
1720   if(!test_datetime_parse_and_normalize(in_str, &d)) {
1721     s = rasqal_xsd_datetime_to_string(&d);
1722   }
1723 
1724   if(s) {
1725     r = strcmp(RASQAL_GOOD_CAST(char*, s), out_expected);
1726     if(r)
1727       fprintf(stderr, "input dateTime \"%s\" converted to canonical \"%s\", expected \"%s\"\n", in_str, s, out_expected);
1728     RASQAL_FREE(char*, s);
1729   } else
1730     fprintf(stderr, "input dateTime \"%s\" converted to canonical (null), expected \"%s\"\n", in_str, out_expected);
1731   return r;
1732 }
1733 
1734 
1735 static int
test_date_parse_and_normalize(const char * date_string,rasqal_xsd_date * result)1736 test_date_parse_and_normalize(const char *date_string,
1737                               rasqal_xsd_date *result)
1738 {
1739   if(rasqal_xsd_date_parse(date_string, result))
1740     return 1;
1741 
1742   return rasqal_xsd_date_normalize(result);
1743 }
1744 
1745 
1746 static int
test_date_parser_tostring(const char * in_str,const char * out_expected)1747 test_date_parser_tostring(const char *in_str, const char *out_expected)
1748 {
1749   rasqal_xsd_date d; /* allocated on stack */
1750   char const *s = NULL;
1751   int r = 1;
1752 
1753   if(!test_date_parse_and_normalize(in_str, &d)) {
1754     s = rasqal_xsd_date_to_string(&d);
1755   }
1756 
1757   if(s) {
1758     r = strcmp(RASQAL_GOOD_CAST(char*, s), out_expected);
1759     if(r)
1760       fprintf(stderr, "input date \"%s\" converted to canonical \"%s\", expected \"%s\"\n", in_str, s, out_expected);
1761     RASQAL_FREE(char*, s);
1762   } else
1763     fprintf(stderr, "input date \"%s\" converted to canonical (null), expected \"%s\"\n", in_str, out_expected);
1764   return r;
1765 }
1766 
1767 
1768 #define INCOMPARABLE 2
1769 
1770 static int
test_date_equals(rasqal_world * world,const char * value1,const char * value2,int expected_eq)1771 test_date_equals(rasqal_world* world, const char *value1, const char *value2,
1772                  int expected_eq)
1773 {
1774   rasqal_xsd_date* d1;
1775   rasqal_xsd_date* d2;
1776   int r = 1;
1777   int incomparable = 0;
1778   int eq;
1779 
1780   d1 = rasqal_new_xsd_date(world, value1);
1781   d2 = rasqal_new_xsd_date(world, value2);
1782 
1783   eq = rasqal_xsd_date_equals(d1, d2, &incomparable);
1784   if(incomparable)
1785     eq = INCOMPARABLE;
1786   rasqal_free_xsd_date(d1);
1787   rasqal_free_xsd_date(d2);
1788 
1789   if(eq != expected_eq) {
1790     fprintf(stderr, "date equals \"%s\" to \"%s\" returned %d expected %d\n",
1791             value1, value2, eq, expected_eq);
1792     r = 1;
1793   }
1794   return r;
1795 }
1796 
1797 
1798 static int
test_date_not_equals(rasqal_world * world,const char * value1,const char * value2,int expected_neq)1799 test_date_not_equals(rasqal_world* world,
1800                      const char *value1, const char *value2,
1801                      int expected_neq)
1802 {
1803   rasqal_xsd_date* d1;
1804   rasqal_xsd_date* d2;
1805   int r = 1;
1806   int incomparable = 0;
1807   int neq;
1808 
1809   d1 = rasqal_new_xsd_date(world, value1);
1810   d2 = rasqal_new_xsd_date(world, value2);
1811 
1812   neq = !rasqal_xsd_date_equals(d1, d2, &incomparable);
1813   if(incomparable)
1814     neq = INCOMPARABLE;
1815   rasqal_free_xsd_date(d1);
1816   rasqal_free_xsd_date(d2);
1817 
1818   if(neq != expected_neq) {
1819     fprintf(stderr,
1820             "date not equals \"%s\" to \"%s\" returned %d expected %d\n",
1821             value1, value2, neq, expected_neq);
1822     r = 1;
1823   }
1824   return r;
1825 }
1826 
1827 
1828 static int
test_date_compare(rasqal_world * world,const char * value1,const char * value2,int expected_cmp)1829 test_date_compare(rasqal_world* world, const char *value1, const char *value2,
1830                   int expected_cmp)
1831 {
1832   rasqal_xsd_date* d1;
1833   rasqal_xsd_date* d2;
1834   int r = 1;
1835   int incomparable = 0;
1836   int cmp;
1837 
1838   d1 = rasqal_new_xsd_date(world, value1);
1839   d2 = rasqal_new_xsd_date(world, value2);
1840 
1841   cmp = rasqal_xsd_date_compare(d1, d2, &incomparable);
1842   if(incomparable)
1843     cmp = INCOMPARABLE;
1844   else if (cmp < 0)
1845     cmp = -1;
1846   else if (cmp > 0)
1847     cmp = 1;
1848 
1849   rasqal_free_xsd_date(d1);
1850   rasqal_free_xsd_date(d2);
1851 
1852   if(cmp != expected_cmp) {
1853     fprintf(stderr, "date compare \"%s\" to \"%s\" returned %d expected %d\n",
1854             value1, value2, cmp, expected_cmp);
1855     r = 1;
1856   }
1857   return r;
1858 }
1859 
1860 
1861 static int
test_datetime_equals(rasqal_world * world,const char * value1,const char * value2,int expected_eq)1862 test_datetime_equals(rasqal_world* world, const char *value1, const char *value2,
1863                  int expected_eq)
1864 {
1865   rasqal_xsd_datetime* d1;
1866   rasqal_xsd_datetime* d2;
1867   int r = 1;
1868   int incomparable = 0;
1869   int eq;
1870 
1871   d1 = rasqal_new_xsd_datetime(world, value1);
1872   d2 = rasqal_new_xsd_datetime(world, value2);
1873 
1874   eq = rasqal_xsd_datetime_equals2(d1, d2, &incomparable);
1875   if(incomparable)
1876     eq = INCOMPARABLE;
1877   rasqal_free_xsd_datetime(d1);
1878   rasqal_free_xsd_datetime(d2);
1879 
1880   if(eq != expected_eq) {
1881     fprintf(stderr,
1882             "datetime equals \"%s\" to \"%s\" returned %d expected %d\n",
1883             value1, value2, eq, expected_eq);
1884     r = 1;
1885   }
1886   return r;
1887 }
1888 
1889 
1890 static int
test_datetime_compare(rasqal_world * world,const char * value1,const char * value2,int expected_cmp)1891 test_datetime_compare(rasqal_world* world, const char *value1, const char *value2,
1892                   int expected_cmp)
1893 {
1894   rasqal_xsd_datetime* d1;
1895   rasqal_xsd_datetime* d2;
1896   int r = 1;
1897   int incomparable = 0;
1898   int cmp;
1899 
1900   d1 = rasqal_new_xsd_datetime(world, value1);
1901   d2 = rasqal_new_xsd_datetime(world, value2);
1902 
1903   cmp = rasqal_xsd_datetime_compare2(d1, d2, &incomparable);
1904   if(incomparable)
1905     cmp = INCOMPARABLE;
1906   else if (cmp < 0)
1907     cmp = -1;
1908   else if (cmp > 0)
1909     cmp = 1;
1910 
1911   rasqal_free_xsd_datetime(d1);
1912   rasqal_free_xsd_datetime(d2);
1913 
1914   if(cmp != expected_cmp) {
1915     fprintf(stderr,
1916             "datetime compare \"%s\" to \"%s\" returned %d expected %d\n",
1917             value1, value2, cmp, expected_cmp);
1918     r = 1;
1919   }
1920   return r;
1921 }
1922 
1923 
1924 int
main(int argc,char * argv[])1925 main(int argc, char *argv[])
1926 {
1927   char const *program = rasqal_basename(*argv);
1928   rasqal_world* world;
1929   rasqal_xsd_datetime dt;
1930   rasqal_xsd_date d;
1931 
1932   world = rasqal_new_world();
1933 
1934   /* days_per_month */
1935 
1936   MYASSERT(!days_per_month(0,287));
1937 
1938   MYASSERT(days_per_month(1,467) == 31);
1939 
1940   MYASSERT(days_per_month(2,1900) == 28);
1941   MYASSERT(days_per_month(2,1901) == 28);
1942   MYASSERT(days_per_month(2,2000) == 29);
1943   MYASSERT(days_per_month(2,2004) == 29);
1944 
1945   MYASSERT(days_per_month(3,1955) == 31);
1946   MYASSERT(days_per_month(4,3612) == 30);
1947   MYASSERT(days_per_month(5,467) == 31);
1948   MYASSERT(days_per_month(6,398) == 30);
1949   MYASSERT(days_per_month(7,1832) == 31);
1950   MYASSERT(days_per_month(8,8579248) == 31);
1951   MYASSERT(days_per_month(9,843) == 30);
1952   MYASSERT(days_per_month(10,84409) == 31);
1953   MYASSERT(days_per_month(11,398) == 30);
1954   MYASSERT(days_per_month(12,4853) == 31);
1955   MYASSERT(!days_per_month(13,45894));
1956 
1957   /* DATETIME */
1958 
1959   /* rasqal_xsd_datetime_parse_and_normalize,
1960      rasqal_xsd_datetime_to_string and
1961      rasqal_xsd_datetime_string_to_canonical */
1962 
1963   #define PARSE_AND_NORMALIZE_DATETIME(_s,_d) \
1964     test_datetime_parse_and_normalize(_s, _d)
1965 
1966   /* generic */
1967 
1968   MYASSERT(!rasqal_xsd_datetime_to_string(0));
1969 
1970   MYASSERT(PARSE_AND_NORMALIZE_DATETIME(0,0));
1971   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("uhgsufi",0));
1972   MYASSERT(PARSE_AND_NORMALIZE_DATETIME(0 ,&dt));
1973   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("fsdhufhdsuifhidu", &dt));
1974 
1975   /* year */
1976 
1977   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("123-12-12T12:12:12Z", &dt));
1978   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("-123-12-12T12:12:12Z", &dt));
1979   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("0000-12-12T12:12:12Z", &dt));
1980   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("01234-12-12T12:12:12Z", &dt));
1981   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("-01234-12-12T12:12:12Z", &dt));
1982   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("1234a12-12T12:12:12Z", &dt));
1983   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("-1234b12-12T12:12:12Z", &dt));
1984   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("g162-12-12T12:12:12Z", &dt));
1985   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("5476574658746587465874-12-12T12:12:12Z", &dt));
1986 
1987   MYASSERT(test_datetime_parser_tostring("1234-12-12T12:12:12Z", "1234-12-12T12:12:12Z") == 0);
1988   MYASSERT(test_datetime_parser_tostring("-1234-12-12T12:12:12Z", "-1234-12-12T12:12:12Z") == 0);
1989   MYASSERT(test_datetime_parser_tostring("1234567890-12-12T12:12:12Z", "1234567890-12-12T12:12:12Z") == 0);
1990   MYASSERT(test_datetime_parser_tostring("-1234567890-12-12T12:12:12Z", "-1234567890-12-12T12:12:12Z") == 0);
1991 
1992   /* month */
1993 
1994   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-v-12T12:12:12Z", &dt));
1995   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-00-12T12:12:12Z", &dt));
1996   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("PARSE_AND_NORMALIZE-011-12T12:12:12Z", &dt));
1997   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-13-12T12:12:12Z", &dt));
1998   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-12.12T12:12:12Z", &dt));
1999 
2000   MYASSERT(test_datetime_parser_tostring("2004-01-01T12:12:12Z", "2004-01-01T12:12:12Z") == 0);
2001 
2002   /* day */
2003 
2004   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-ffT12:12:12Z", &dt));
2005   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-00T12:12:12Z", &dt));
2006   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-007T12:12:12Z", &dt));
2007   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-32T12:12:12Z", &dt));
2008   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-01t12:12:12Z", &dt));
2009   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01- 1T12:12:12Z", &dt));
2010 
2011   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2005-02-29T12:12:12Z", &dt));
2012   MYASSERT(!PARSE_AND_NORMALIZE_DATETIME("2005-02-28T12:12:12Z", &dt));
2013   MYASSERT(!PARSE_AND_NORMALIZE_DATETIME("2004-02-29T12:12:12Z", &dt));
2014   MYASSERT(!PARSE_AND_NORMALIZE_DATETIME("2000-02-29T12:12:12Z", &dt));
2015   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("1900-02-29T12:12:12Z", &dt));
2016 
2017   MYASSERT(test_datetime_parser_tostring("2012-04-12T12:12:12Z", "2012-04-12T12:12:12Z") == 0);
2018 
2019   /* hour */
2020 
2021   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-01Tew:12:12Z", &dt));
2022   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-01T-1:12:12Z", &dt));
2023   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-01T001:12:12Z", &dt));
2024   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-01T25:12:12Z", &dt));
2025   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-01T01.12:12Z", &dt));
2026 
2027   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-01T24:12:00Z", &dt));
2028   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-01T24:00:34Z", &dt));
2029   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-01T24:12:34Z", &dt));
2030   MYASSERT(!PARSE_AND_NORMALIZE_DATETIME("2004-01-01T24:00:00Z", &dt));
2031 
2032   MYASSERT(test_datetime_parser_tostring("2012-04-12T24:00:00", "2012-04-13T00:00:00") == 0);
2033 
2034   /* minute */
2035 
2036   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-01T12:ij:12Z", &dt));
2037   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-01T12:-1:12Z", &dt));
2038   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-01T12:042:12Z", &dt));
2039   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-01T12:69:12Z", &dt));
2040   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-01T12:12.12Z", &dt));
2041 
2042   /* second */
2043 
2044   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-01T12:12:ijZ", &dt));
2045   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-01T12:12:-1", &dt));
2046   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-01T12:12:054Z", &dt));
2047   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-01T12:12:69Z", &dt));
2048   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-01T12:12:12z", &dt));
2049 
2050   MYASSERT(!PARSE_AND_NORMALIZE_DATETIME("2004-01-01T12:12:12", &dt));
2051 
2052   /* fraction second */
2053 
2054   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-01T12:12:12.", &dt));
2055   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-01T12:12:12.i", &dt));
2056   MYASSERT(!PARSE_AND_NORMALIZE_DATETIME("2004-01-01T12:12:12.0", &dt));
2057   MYASSERT(!PARSE_AND_NORMALIZE_DATETIME("2004-01-01T12:12:12.01", &dt));
2058   MYASSERT(!PARSE_AND_NORMALIZE_DATETIME("2004-01-01T12:12:12.1", &dt));
2059   MYASSERT(!PARSE_AND_NORMALIZE_DATETIME("2004-01-01T12:12:12.100", &dt));
2060   MYASSERT(!PARSE_AND_NORMALIZE_DATETIME("2004-01-01T12:12:12.1000000000000000000000000000000000000000000", &dt));
2061   MYASSERT(!PARSE_AND_NORMALIZE_DATETIME("2004-01-01T12:12:12.5798459847598743987549", &dt));
2062   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-01T12:12:12.1d", &dt));
2063   MYASSERT(!PARSE_AND_NORMALIZE_DATETIME("2004-01-01T12:12:12.1Z", &dt));
2064 
2065   MYASSERT(test_datetime_parser_tostring("2006-05-18T18:36:03.01Z", "2006-05-18T18:36:03.01Z") == 0);
2066   MYASSERT(test_datetime_parser_tostring("2006-05-18T18:36:03.10Z", "2006-05-18T18:36:03.1Z") == 0);
2067   MYASSERT(test_datetime_parser_tostring("2006-05-18T18:36:03.010Z", "2006-05-18T18:36:03.01Z") == 0);
2068   MYASSERT(test_datetime_parser_tostring("2006-05-18T18:36:03.1234Z", "2006-05-18T18:36:03.1234Z") == 0);
2069   MYASSERT(test_datetime_parser_tostring("2006-05-18T18:36:03.1234", "2006-05-18T18:36:03.1234") == 0);
2070   MYASSERT(test_datetime_parser_tostring("2006-05-18T18:36:03.1239Z", "2006-05-18T18:36:03.1239Z") == 0);
2071   MYASSERT(test_datetime_parser_tostring("2006-05-18T18:36:03.1239", "2006-05-18T18:36:03.1239") == 0);
2072 
2073   /* timezones + normalization */
2074 
2075   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-01T12:12:12+", &dt));
2076   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-01T12:12:12-", &dt));
2077   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-01T12:12:12+00.00", &dt));
2078   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-01T12:12:12+aa:bb", &dt));
2079   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-01T12:12:12+15:00", &dt));
2080   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-01T12:12:12+14:01", &dt));
2081   MYASSERT(!PARSE_AND_NORMALIZE_DATETIME("2004-01-01T12:12:12+14:00", &dt));
2082   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-01T12:12:12-14:01", &dt));
2083   MYASSERT(!PARSE_AND_NORMALIZE_DATETIME("2004-01-01T12:12:12-14:00", &dt));
2084   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-01T12:12:12+10:99", &dt));
2085   MYASSERT(!PARSE_AND_NORMALIZE_DATETIME("2004-01-01T12:12:12+10:59", &dt));
2086   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-01T12:12:12+10:059", &dt));
2087   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-01T12:12:12+010:59", &dt));
2088   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-01T12:12:12+10:59a", &dt));
2089   MYASSERT(PARSE_AND_NORMALIZE_DATETIME("2004-01-01T12:12:12+10:059", &dt));
2090 
2091   MYASSERT(test_datetime_parser_tostring("2004-12-31T23:50:22-01:15", "2005-01-01T01:05:22Z") == 0);
2092   MYASSERT(test_datetime_parser_tostring("2005-01-01T01:00:05+02:12", "2004-12-31T22:48:05Z") == 0);
2093   MYASSERT(test_datetime_parser_tostring("0001-01-01T00:00:00+00:01", "-0001-12-31T23:59:00Z") == 0);
2094   MYASSERT(test_datetime_parser_tostring("-0001-12-31T23:59:00-00:01", "0001-01-01T00:00:00Z") == 0);
2095   MYASSERT(test_datetime_parser_tostring("2005-03-01T00:00:00+01:00", "2005-02-28T23:00:00Z") == 0);
2096   MYASSERT(test_datetime_parser_tostring("2004-03-01T00:00:00+01:00", "2004-02-29T23:00:00Z") == 0);
2097   MYASSERT(test_datetime_parser_tostring("2005-02-28T23:00:00-01:00", "2005-03-01T00:00:00Z") == 0);
2098   MYASSERT(test_datetime_parser_tostring("2004-02-29T23:00:00-01:00", "2004-03-01T00:00:00Z") == 0);
2099 
2100 
2101   /* DATE */
2102 
2103   #define PARSE_AND_NORMALIZE_DATE(_s,_d) \
2104     test_date_parse_and_normalize(_s, _d)
2105 
2106   /* generic */
2107 
2108   MYASSERT(!rasqal_xsd_date_to_string(0));
2109 
2110   MYASSERT(PARSE_AND_NORMALIZE_DATE(0,0));
2111   MYASSERT(PARSE_AND_NORMALIZE_DATE("uhgsufi",0));
2112   MYASSERT(PARSE_AND_NORMALIZE_DATE(0 ,&d));
2113   MYASSERT(PARSE_AND_NORMALIZE_DATE("fsdhufhdsuifhidu", &d));
2114 
2115   /* year */
2116 
2117   MYASSERT(PARSE_AND_NORMALIZE_DATE("123-12-12Z", &d));
2118   MYASSERT(PARSE_AND_NORMALIZE_DATE("-123-12-12Z", &d));
2119   MYASSERT(PARSE_AND_NORMALIZE_DATE("0000-12-12Z", &d));
2120   MYASSERT(PARSE_AND_NORMALIZE_DATE("01234-12-12Z", &d));
2121   MYASSERT(PARSE_AND_NORMALIZE_DATE("-01234-12-12Z", &d));
2122   MYASSERT(PARSE_AND_NORMALIZE_DATE("1234a12-12Z", &d));
2123   MYASSERT(PARSE_AND_NORMALIZE_DATE("-1234b12-12Z", &d));
2124   MYASSERT(PARSE_AND_NORMALIZE_DATE("g162-12-12Z", &d));
2125   MYASSERT(PARSE_AND_NORMALIZE_DATE("5476574658746587465874-12-12Z", &d));
2126 
2127   MYASSERT(test_date_parser_tostring("1234-12-12Z", "1234-12-12Z") == 0);
2128   MYASSERT(test_date_parser_tostring("-1234-12-12Z", "-1234-12-12Z") == 0);
2129   MYASSERT(test_date_parser_tostring("1234567890-12-12Z", "1234567890-12-12Z") == 0);
2130   MYASSERT(test_date_parser_tostring("-1234567890-12-12Z", "-1234567890-12-12Z") == 0);
2131 
2132   /* month */
2133 
2134   MYASSERT(PARSE_AND_NORMALIZE_DATE("2004-v-12Z", &d));
2135   MYASSERT(PARSE_AND_NORMALIZE_DATE("2004-00-12Z", &d));
2136   MYASSERT(PARSE_AND_NORMALIZE_DATE("PARSE_AND_NORMALIZE-011-12Z", &d));
2137   MYASSERT(PARSE_AND_NORMALIZE_DATE("2004-13-12Z", &d));
2138   MYASSERT(PARSE_AND_NORMALIZE_DATE("2004-12.12Z", &d));
2139 
2140   MYASSERT(test_date_parser_tostring("2004-01-01Z", "2004-01-01Z") == 0);
2141 
2142   /* day */
2143 
2144   MYASSERT(PARSE_AND_NORMALIZE_DATE("2004-01-ffZ", &d));
2145   MYASSERT(PARSE_AND_NORMALIZE_DATE("2004-01-00Z", &d));
2146   MYASSERT(PARSE_AND_NORMALIZE_DATE("2004-01-007Z", &d));
2147   MYASSERT(PARSE_AND_NORMALIZE_DATE("2004-01-32Z", &d));
2148   MYASSERT(PARSE_AND_NORMALIZE_DATE("2004-01-01t12:12:12Z", &d));
2149   MYASSERT(PARSE_AND_NORMALIZE_DATE("2004-01- 1Z", &d));
2150 
2151   MYASSERT(PARSE_AND_NORMALIZE_DATE("2005-02-29Z", &d));
2152   MYASSERT(!PARSE_AND_NORMALIZE_DATE("2005-02-28Z", &d));
2153   MYASSERT(!PARSE_AND_NORMALIZE_DATE("2004-02-29Z", &d));
2154   MYASSERT(!PARSE_AND_NORMALIZE_DATE("2000-02-29Z", &d));
2155   MYASSERT(PARSE_AND_NORMALIZE_DATE("1900-02-29Z", &d));
2156 
2157   MYASSERT(test_date_parser_tostring("2012-04-12Z", "2012-04-12Z") == 0);
2158 
2159   /* timezones + normalization */
2160 
2161   MYASSERT(PARSE_AND_NORMALIZE_DATE("2004-01-01+", &d));
2162   MYASSERT(PARSE_AND_NORMALIZE_DATE("2004-01-01-", &d));
2163   MYASSERT(PARSE_AND_NORMALIZE_DATE("2004-01-01+00.00", &d));
2164   MYASSERT(PARSE_AND_NORMALIZE_DATE("2004-01-01+aa:bb", &d));
2165   MYASSERT(PARSE_AND_NORMALIZE_DATE("2004-01-01+15:00", &d));
2166   MYASSERT(PARSE_AND_NORMALIZE_DATE("2004-01-01+14:01", &d));
2167   MYASSERT(!PARSE_AND_NORMALIZE_DATE("2004-01-01+14:00", &d));
2168   MYASSERT(PARSE_AND_NORMALIZE_DATE("2004-01-01-14:01", &d));
2169   MYASSERT(!PARSE_AND_NORMALIZE_DATE("2004-01-01-14:00", &d));
2170   MYASSERT(PARSE_AND_NORMALIZE_DATE("2004-01-01+10:99", &d));
2171   MYASSERT(!PARSE_AND_NORMALIZE_DATE("2004-01-01+10:59", &d));
2172   MYASSERT(PARSE_AND_NORMALIZE_DATE("2004-01-01+10:059", &d));
2173   MYASSERT(PARSE_AND_NORMALIZE_DATE("2004-01-01+010:59", &d));
2174   MYASSERT(PARSE_AND_NORMALIZE_DATE("2004-01-01+10:59a", &d));
2175   MYASSERT(PARSE_AND_NORMALIZE_DATE("2004-01-01+10:059", &d));
2176 
2177   MYASSERT(test_date_parser_tostring("2004-12-31-13:00", "2005-01-01Z") == 0);
2178   MYASSERT(test_date_parser_tostring("2005-01-01+13:00", "2004-12-31Z") == 0);
2179   MYASSERT(test_date_parser_tostring("2004-12-31-11:59", "2004-12-31Z") == 0);
2180   MYASSERT(test_date_parser_tostring("2005-01-01+11:59", "2005-01-01Z") == 0);
2181 
2182   /* Date equality */
2183   /* May not be comparible since <14hrs apart */
2184   MYASSERT(test_date_equals(world, "2011-01-02Z", "2011-01-02" , INCOMPARABLE));
2185   MYASSERT(test_date_equals(world, "2011-01-02" , "2011-01-02" , 1));
2186   MYASSERT(test_date_equals(world, "2011-01-02",  "2011-01-02Z", INCOMPARABLE));
2187   MYASSERT(test_date_equals(world, "2011-01-02Z", "2011-01-02Z", 1));
2188 
2189   /* Are comparible across timelines since >14hrs apart */
2190   MYASSERT(test_date_equals(world, "2011-01-02Z", "2011-01-03" , 0));
2191   MYASSERT(test_date_equals(world, "2011-01-02" , "2011-01-03" , 0));
2192   MYASSERT(test_date_equals(world, "2011-01-02",  "2011-01-03Z", 0));
2193   MYASSERT(test_date_equals(world, "2011-01-02Z", "2011-01-03Z", 0));
2194 
2195   MYASSERT(test_date_not_equals(world, "2006-08-23", "2006-08-23", 0));
2196   MYASSERT(test_date_not_equals(world, "2006-08-23", "2006-08-23Z", INCOMPARABLE));
2197   MYASSERT(test_date_not_equals(world, "2006-08-23", "2006-08-23+00:00", INCOMPARABLE));
2198   /* More than 14hrs apart so are comparible */
2199   MYASSERT(test_date_not_equals(world, "2006-08-23", "2001-01-01", 1));
2200   MYASSERT(test_date_not_equals(world, "2006-08-23", "2001-01-01Z", 1));
2201 
2202   /* Date comparisons */
2203   MYASSERT(test_date_compare(world, "2011-01-02Z", "2011-01-02" , INCOMPARABLE));
2204   MYASSERT(test_date_compare(world, "2011-01-02",  "2011-01-02" , 0));
2205   MYASSERT(test_date_compare(world, "2011-01-02",  "2011-01-02Z", INCOMPARABLE));
2206   MYASSERT(test_date_compare(world, "2011-01-02Z", "2011-01-02Z", 0));
2207 
2208   MYASSERT(test_date_compare(world, "2011-01-02Z", "2011-01-03" , -1));
2209   MYASSERT(test_date_compare(world, "2011-01-02",  "2011-01-03" , -1));
2210   MYASSERT(test_date_compare(world, "2011-01-02",  "2011-01-03Z", -1));
2211   MYASSERT(test_date_compare(world, "2011-01-02Z", "2011-01-03Z", -1));
2212 
2213   /* DateTime equality */
2214   MYASSERT(test_datetime_equals(world, "2011-01-02T00:00:00",  "2011-01-02T00:00:00",  1));
2215   MYASSERT(test_datetime_equals(world, "2011-01-02T00:00:00",  "2011-01-02T00:00:00Z", INCOMPARABLE));
2216   MYASSERT(test_datetime_equals(world, "2011-01-02T00:00:00Z", "2011-01-02T00:00:00",  INCOMPARABLE));
2217   MYASSERT(test_datetime_equals(world, "2011-01-02T00:00:00Z", "2011-01-02T00:00:00Z", 1));
2218 
2219   /* DateTime comparisons */
2220   MYASSERT(test_datetime_compare(world, "2011-01-02T00:00:00",  "2011-01-02T00:00:00" , 0));
2221   MYASSERT(test_datetime_compare(world, "2011-01-02T00:00:00",  "2011-01-02T00:00:00Z", INCOMPARABLE));
2222   MYASSERT(test_datetime_compare(world, "2011-01-02T00:00:00Z", "2011-01-02T00:00:00",  INCOMPARABLE));
2223   MYASSERT(test_datetime_compare(world, "2011-01-02T00:00:00Z", "2011-01-02T00:00:00Z", 0));
2224 
2225 
2226   if(1) {
2227     struct timeval my_tv;
2228     time_t secs;
2229     time_t new_secs;
2230     struct timeval* new_tv;
2231 
2232     /* 2010-12-14T06:22:36.868099Z or 2010-12-13T22:22:36.868099+0800
2233      * when I was testing this
2234      */
2235     my_tv.tv_sec = 1292307756;
2236     my_tv.tv_usec = 868099;
2237 
2238     secs = my_tv.tv_sec;
2239 
2240     MYASSERT(rasqal_xsd_datetime_set_from_timeval(&dt, &my_tv) == 0);
2241 
2242     MYASSERT((new_tv = rasqal_xsd_datetime_get_as_timeval(&dt)));
2243     MYASSERT(new_tv->tv_sec == my_tv.tv_sec);
2244     MYASSERT(new_tv->tv_usec == my_tv.tv_usec);
2245 
2246     RASQAL_FREE(timeval, new_tv);
2247 
2248     MYASSERT(rasqal_xsd_datetime_set_from_unixtime(&dt, secs) == 0);
2249 
2250     MYASSERT((new_secs = rasqal_xsd_datetime_get_as_unixtime(&dt)));
2251     MYASSERT(new_secs == secs);
2252   }
2253 
2254   rasqal_free_world(world);
2255 
2256   return 0;
2257 }
2258 
2259 #endif /* STANDALONE */
2260 
2261