Time::Piece & matherr on qnx4
[p5sagit/p5-mst-13.2.git] / ext / Time / Piece / Piece.xs
1 #ifdef __cplusplus
2 extern "C" {
3 #endif
4 #include "EXTERN.h"
5 #include "perl.h"
6 #include "XSUB.h"
7 #include <time.h>
8 #ifdef __cplusplus
9 }
10 #endif
11
12 /* XXX struct tm on some systems (SunOS4/BSD) contains extra (non POSIX)
13  * fields for which we don't have Configure support yet:
14  *   char *tm_zone;   -- abbreviation of timezone name
15  *   long tm_gmtoff;  -- offset from GMT in seconds
16  * To workaround core dumps from the uninitialised tm_zone we get the
17  * system to give us a reasonable struct to copy.  This fix means that
18  * strftime uses the tm_zone and tm_gmtoff values returned by
19  * localtime(time()). That should give the desired result most of the
20  * time. But probably not always!
21  *
22  * This is a temporary workaround to be removed once Configure
23  * support is added and NETaa14816 is considered in full.
24  * It does not address tzname aspects of NETaa14816.
25  */
26 #if !defined(HAS_GNULIBC)
27 # ifndef STRUCT_TM_HASZONE
28 #    define STRUCT_TM_HASZONE
29 # else
30 #    define USE_TM_GMTOFF
31 # endif
32 #endif
33
34 #define    DAYS_PER_YEAR    365
35 #define    DAYS_PER_QYEAR    (4*DAYS_PER_YEAR+1)
36 #define    DAYS_PER_CENT    (25*DAYS_PER_QYEAR-1)
37 #define    DAYS_PER_QCENT    (4*DAYS_PER_CENT+1)
38 #define    SECS_PER_HOUR    (60*60)
39 #define    SECS_PER_DAY    (24*SECS_PER_HOUR)
40 /* parentheses deliberately absent on these two, otherwise they don't work */
41 #define    MONTH_TO_DAYS    153/5
42 #define    DAYS_TO_MONTH    5/153
43 /* offset to bias by March (month 4) 1st between month/mday & year finding */
44 #define    YEAR_ADJUST    (4*MONTH_TO_DAYS+1)
45 /* as used here, the algorithm leaves Sunday as day 1 unless we adjust it */
46 #define    WEEKDAY_BIAS    6    /* (1+6)%7 makes Sunday 0 again */
47
48 #ifdef STRUCT_TM_HASZONE
49 static void
50 my_init_tm(struct tm *ptm)        /* see mktime, strftime and asctime    */
51 {
52     Time_t now;
53     (void)time(&now);
54     Copy(localtime(&now), ptm, 1, struct tm);
55 }
56
57 #else
58 # define my_init_tm(ptm)
59 #endif
60
61 /*
62  * my_mini_mktime - normalise struct tm values without the localtime()
63  * semantics (and overhead) of mktime().
64  */
65 static void
66 my_mini_mktime(struct tm *ptm)
67 {
68     int yearday;
69     int secs;
70     int month, mday, year, jday;
71     int odd_cent, odd_year;
72
73 /*
74  * Year/day algorithm notes:
75  *
76  * With a suitable offset for numeric value of the month, one can find
77  * an offset into the year by considering months to have 30.6 (153/5) days,
78  * using integer arithmetic (i.e., with truncation).  To avoid too much
79  * messing about with leap days, we consider January and February to be
80  * the 13th and 14th month of the previous year.  After that transformation,
81  * we need the month index we use to be high by 1 from 'normal human' usage,
82  * so the month index values we use run from 4 through 15.
83  *
84  * Given that, and the rules for the Gregorian calendar (leap years are those
85  * divisible by 4 unless also divisible by 100, when they must be divisible
86  * by 400 instead), we can simply calculate the number of days since some
87  * arbitrary 'beginning of time' by futzing with the (adjusted) year number,
88  * the days we derive from our month index, and adding in the day of the
89  * month.  The value used here is not adjusted for the actual origin which
90  * it normally would use (1 January A.D. 1), since we're not exposing it.
91  * We're only building the value so we can turn around and get the
92  * normalised values for the year, month, day-of-month, and day-of-year.
93  *
94  * For going backward, we need to bias the value we're using so that we find
95  * the right year value.  (Basically, we don't want the contribution of
96  * March 1st to the number to apply while deriving the year).  Having done
97  * that, we 'count up' the contribution to the year number by accounting for
98  * full quadracenturies (400-year periods) with their extra leap days, plus
99  * the contribution from full centuries (to avoid counting in the lost leap
100  * days), plus the contribution from full quad-years (to count in the normal
101  * leap days), plus the leftover contribution from any non-leap years.
102  * At this point, if we were working with an actual leap day, we'll have 0
103  * days left over.  This is also true for March 1st, however.  So, we have
104  * to special-case that result, and (earlier) keep track of the 'odd'
105  * century and year contributions.  If we got 4 extra centuries in a qcent,
106  * or 4 extra years in a qyear, then it's a leap day and we call it 29 Feb.
107  * Otherwise, we add back in the earlier bias we removed (the 123 from
108  * figuring in March 1st), find the month index (integer division by 30.6),
109  * and the remainder is the day-of-month.  We then have to convert back to
110  * 'real' months (including fixing January and February from being 14/15 in
111  * the previous year to being in the proper year).  After that, to get
112  * tm_yday, we work with the normalised year and get a new yearday value for
113  * January 1st, which we subtract from the yearday value we had earlier,
114  * representing the date we've re-built.  This is done from January 1
115  * because tm_yday is 0-origin.
116  *
117  * Since POSIX time routines are only guaranteed to work for times since the
118  * UNIX epoch (00:00:00 1 Jan 1970 UTC), the fact that this algorithm
119  * applies Gregorian calendar rules even to dates before the 16th century
120  * doesn't bother me.  Besides, you'd need cultural context for a given
121  * date to know whether it was Julian or Gregorian calendar, and that's
122  * outside the scope for this routine.  Since we convert back based on the
123  * same rules we used to build the yearday, you'll only get strange results
124  * for input which needed normalising, or for the 'odd' century years which
125  * were leap years in the Julian calander but not in the Gregorian one.
126  * I can live with that.
127  *
128  * This algorithm also fails to handle years before A.D. 1 gracefully, but
129  * that's still outside the scope for POSIX time manipulation, so I don't
130  * care.
131  */
132
133     year = 1900 + ptm->tm_year;
134     month = ptm->tm_mon;
135     mday = ptm->tm_mday;
136     /* allow given yday with no month & mday to dominate the result */
137     if (ptm->tm_yday >= 0 && mday <= 0 && month <= 0) {
138         month = 0;
139         mday = 0;
140         jday = 1 + ptm->tm_yday;
141     }
142     else {
143         jday = 0;
144     }
145     if (month >= 2)
146         month+=2;
147     else
148         month+=14, year--;
149
150     yearday = DAYS_PER_YEAR * year + year/4 - year/100 + year/400;
151     yearday += month*MONTH_TO_DAYS + mday + jday;
152     /*
153      * Note that we don't know when leap-seconds were or will be,
154      * so we have to trust the user if we get something which looks
155      * like a sensible leap-second.  Wild values for seconds will
156      * be rationalised, however.
157      */
158     if ((unsigned) ptm->tm_sec <= 60) {
159         secs = 0;
160     }
161     else {
162         secs = ptm->tm_sec;
163         ptm->tm_sec = 0;
164     }
165     secs += 60 * ptm->tm_min;
166     secs += SECS_PER_HOUR * ptm->tm_hour;
167     if (secs < 0) {
168         if (secs-(secs/SECS_PER_DAY*SECS_PER_DAY) < 0) {
169             /* got negative remainder, but need positive time */
170             /* back off an extra day to compensate */
171             yearday += (secs/SECS_PER_DAY)-1;
172             secs -= SECS_PER_DAY * (secs/SECS_PER_DAY - 1);
173         }
174         else {
175             yearday += (secs/SECS_PER_DAY);
176             secs -= SECS_PER_DAY * (secs/SECS_PER_DAY);
177         }
178     }
179     else if (secs >= SECS_PER_DAY) {
180         yearday += (secs/SECS_PER_DAY);
181         secs %= SECS_PER_DAY;
182     }
183     ptm->tm_hour = secs/SECS_PER_HOUR;
184     secs %= SECS_PER_HOUR;
185     ptm->tm_min = secs/60;
186     secs %= 60;
187     ptm->tm_sec += secs;
188     /* done with time of day effects */
189     /*
190      * The algorithm for yearday has (so far) left it high by 428.
191      * To avoid mistaking a legitimate Feb 29 as Mar 1, we need to
192      * bias it by 123 while trying to figure out what year it
193      * really represents.  Even with this tweak, the reverse
194      * translation fails for years before A.D. 0001.
195      * It would still fail for Feb 29, but we catch that one below.
196      */
197     jday = yearday;    /* save for later fixup vis-a-vis Jan 1 */
198     yearday -= YEAR_ADJUST;
199     year = (yearday / DAYS_PER_QCENT) * 400;
200     yearday %= DAYS_PER_QCENT;
201     odd_cent = yearday / DAYS_PER_CENT;
202     year += odd_cent * 100;
203     yearday %= DAYS_PER_CENT;
204     year += (yearday / DAYS_PER_QYEAR) * 4;
205     yearday %= DAYS_PER_QYEAR;
206     odd_year = yearday / DAYS_PER_YEAR;
207     year += odd_year;
208     yearday %= DAYS_PER_YEAR;
209     if (!yearday && (odd_cent==4 || odd_year==4)) { /* catch Feb 29 */
210         month = 1;
211         yearday = 29;
212     }
213     else {
214         yearday += YEAR_ADJUST;    /* recover March 1st crock */
215         month = yearday*DAYS_TO_MONTH;
216         yearday -= month*MONTH_TO_DAYS;
217         /* recover other leap-year adjustment */
218         if (month > 13) {
219             month-=14;
220             year++;
221         }
222         else {
223             month-=2;
224         }
225     }
226     ptm->tm_year = year - 1900;
227     if (yearday) {
228       ptm->tm_mday = yearday;
229       ptm->tm_mon = month;
230     }
231     else {
232       ptm->tm_mday = 31;
233       ptm->tm_mon = month - 1;
234     }
235     /* re-build yearday based on Jan 1 to get tm_yday */
236     year--;
237     yearday = year*DAYS_PER_YEAR + year/4 - year/100 + year/400;
238     yearday += 14*MONTH_TO_DAYS + 1;
239     ptm->tm_yday = jday - yearday;
240     /* fix tm_wday if not overridden by caller */
241     ptm->tm_wday = (jday + WEEKDAY_BIAS) % 7;
242 }
243
244 #if defined(WIN32) || (defined(__QNX__) && defined(__WATCOMC__)) /* No strptime on Win32 or QNX4 */
245 #define strncasecmp(x,y,n) strnicmp(x,y,n)
246
247 #if defined(WIN32)
248 #define alloca _alloca
249 #endif
250
251 #include <time.h>
252 #include <ctype.h>
253 #include <string.h>
254 #ifdef _THREAD_SAFE
255 #include <pthread.h>
256 #include "pthread_private.h"
257 #endif /* _THREAD_SAFE */
258
259 static char * _strptime(const char *, const char *, struct tm *);
260
261 #ifdef _THREAD_SAFE
262 static struct pthread_mutex     _gotgmt_mutexd = PTHREAD_MUTEX_STATIC_INITIALIZER;
263 static pthread_mutex_t          gotgmt_mutex   = &_gotgmt_mutexd;
264 #endif
265 static int got_GMT;
266
267 #define asizeof(a)      (sizeof (a) / sizeof ((a)[0]))
268
269 struct lc_time_T {
270     const char *    mon[12];
271     const char *    month[12];
272     const char *    wday[7];
273     const char *    weekday[7];
274     const char *    X_fmt;     
275     const char *    x_fmt;
276     const char *    c_fmt;
277     const char *    am;
278     const char *    pm;
279     const char *    date_fmt;
280     const char *    alt_month[12];
281     const char *    Ef_fmt;
282     const char *    EF_fmt;
283 };
284
285 struct lc_time_T _time_localebuf;
286 int _time_using_locale;
287
288 const struct lc_time_T  _C_time_locale = {
289         {
290                 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
291                 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
292         }, {
293                 "January", "February", "March", "April", "May", "June",
294                 "July", "August", "September", "October", "November", "December"
295         }, {
296                 "Sun", "Mon", "Tue", "Wed",
297                 "Thu", "Fri", "Sat"
298         }, {
299                 "Sunday", "Monday", "Tuesday", "Wednesday",
300                 "Thursday", "Friday", "Saturday"
301         },
302
303         /* X_fmt */
304         "%H:%M:%S",
305
306         /*
307         ** x_fmt
308         ** Since the C language standard calls for
309         ** "date, using locale's date format," anything goes.
310         ** Using just numbers (as here) makes Quakers happier;
311         ** it's also compatible with SVR4.
312         */
313         "%m/%d/%y",
314
315         /*
316         ** c_fmt (ctime-compatible)
317         ** Not used, just compatibility placeholder.
318         */
319         NULL,
320
321         /* am */
322         "AM",
323
324         /* pm */
325         "PM",
326
327         /* date_fmt */
328         "%a %Ef %X %Z %Y",
329         
330         {
331                 "January", "February", "March", "April", "May", "June",
332                 "July", "August", "September", "October", "November", "December"
333         },
334
335         /* Ef_fmt
336         ** To determine short months / day order
337         */
338         "%b %e",
339
340         /* EF_fmt
341         ** To determine long months / day order
342         */
343         "%B %e"
344 };
345
346 #define Locale (&_C_time_locale)
347
348 static char *
349 _strptime(const char *buf, const char *fmt, struct tm *tm)
350 {
351         char c;
352         const char *ptr;
353         int i,
354                 len;
355         int Ealternative, Oalternative;
356
357         ptr = fmt;
358         while (*ptr != 0) {
359                 if (*buf == 0)
360                         break;
361
362                 c = *ptr++;
363
364                 if (c != '%') {
365                         if (isspace((unsigned char)c))
366                                 while (*buf != 0 && isspace((unsigned char)*buf))
367                                         buf++;
368                         else if (c != *buf++)
369                                 return 0;
370                         continue;
371                 }
372
373                 Ealternative = 0;
374                 Oalternative = 0;
375 label:
376                 c = *ptr++;
377                 switch (c) {
378                 case 0:
379                 case '%':
380                         if (*buf++ != '%')
381                                 return 0;
382                         break;
383
384                 case '+':
385                         buf = _strptime(buf, Locale->date_fmt, tm);
386                         if (buf == 0)
387                                 return 0;
388                         break;
389
390                 case 'C':
391                         if (!isdigit((unsigned char)*buf))
392                                 return 0;
393
394                         /* XXX This will break for 3-digit centuries. */
395                         len = 2;
396                         for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
397                                 i *= 10;
398                                 i += *buf - '0';
399                                 len--;
400                         }
401                         if (i < 19)
402                                 return 0;
403
404                         tm->tm_year = i * 100 - 1900;
405                         break;
406
407                 case 'c':
408                         /* NOTE: c_fmt is intentionally ignored */
409                         buf = _strptime(buf, "%a %Ef %T %Y", tm);
410                         if (buf == 0)
411                                 return 0;
412                         break;
413
414                 case 'D':
415                         buf = _strptime(buf, "%m/%d/%y", tm);
416                         if (buf == 0)
417                                 return 0;
418                         break;
419
420                 case 'E':
421                         if (Ealternative || Oalternative)
422                                 break;
423                         Ealternative++;
424                         goto label;
425
426                 case 'O':
427                         if (Ealternative || Oalternative)
428                                 break;
429                         Oalternative++;
430                         goto label;
431
432                 case 'F':
433                 case 'f':
434                         if (!Ealternative)
435                                 break;
436                         buf = _strptime(buf, (c == 'f') ? Locale->Ef_fmt : Locale->EF_fmt, tm);
437                         if (buf == 0)
438                                 return 0;
439                         break;
440
441                 case 'R':
442                         buf = _strptime(buf, "%H:%M", tm);
443                         if (buf == 0)
444                                 return 0;
445                         break;
446
447                 case 'r':
448                         buf = _strptime(buf, "%I:%M:%S %p", tm);
449                         if (buf == 0)
450                                 return 0;
451                         break;
452
453                 case 'T':
454                         buf = _strptime(buf, "%H:%M:%S", tm);
455                         if (buf == 0)
456                                 return 0;
457                         break;
458
459                 case 'X':
460                         buf = _strptime(buf, Locale->X_fmt, tm);
461                         if (buf == 0)
462                                 return 0;
463                         break;
464
465                 case 'x':
466                         buf = _strptime(buf, Locale->x_fmt, tm);
467                         if (buf == 0)
468                                 return 0;
469                         break;
470
471                 case 'j':
472                         if (!isdigit((unsigned char)*buf))
473                                 return 0;
474
475                         len = 3;
476                         for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
477                                 i *= 10;
478                                 i += *buf - '0';
479                                 len--;
480                         }
481                         if (i < 1 || i > 366)
482                                 return 0;
483
484                         tm->tm_yday = i - 1;
485                         break;
486
487                 case 'M':
488                 case 'S':
489                         if (*buf == 0 || isspace((unsigned char)*buf))
490                                 break;
491
492                         if (!isdigit((unsigned char)*buf))
493                                 return 0;
494
495                         len = 2;
496                         for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
497                                 i *= 10;
498                                 i += *buf - '0';
499                                 len--;
500                         }
501
502                         if (c == 'M') {
503                                 if (i > 59)
504                                         return 0;
505                                 tm->tm_min = i;
506                         } else {
507                                 if (i > 60)
508                                         return 0;
509                                 tm->tm_sec = i;
510                         }
511
512                         if (*buf != 0 && isspace((unsigned char)*buf))
513                                 while (*ptr != 0 && !isspace((unsigned char)*ptr))
514                                         ptr++;
515                         break;
516
517                 case 'H':
518                 case 'I':
519                 case 'k':
520                 case 'l':
521                         /*
522                          * Of these, %l is the only specifier explicitly
523                          * documented as not being zero-padded.  However,
524                          * there is no harm in allowing zero-padding.
525                          *
526                          * XXX The %l specifier may gobble one too many
527                          * digits if used incorrectly.
528                          */
529                         if (!isdigit((unsigned char)*buf))
530                                 return 0;
531
532                         len = 2;
533                         for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
534                                 i *= 10;
535                                 i += *buf - '0';
536                                 len--;
537                         }
538                         if (c == 'H' || c == 'k') {
539                                 if (i > 23)
540                                         return 0;
541                         } else if (i > 12)
542                                 return 0;
543
544                         tm->tm_hour = i;
545
546                         if (*buf != 0 && isspace((unsigned char)*buf))
547                                 while (*ptr != 0 && !isspace((unsigned char)*ptr))
548                                         ptr++;
549                         break;
550
551                 case 'p':
552                         /*
553                          * XXX This is bogus if parsed before hour-related
554                          * specifiers.
555                          */
556                         len = strlen(Locale->am);
557                         if (strncasecmp(buf, Locale->am, len) == 0) {
558                                 if (tm->tm_hour > 12)
559                                         return 0;
560                                 if (tm->tm_hour == 12)
561                                         tm->tm_hour = 0;
562                                 buf += len;
563                                 break;
564                         }
565
566                         len = strlen(Locale->pm);
567                         if (strncasecmp(buf, Locale->pm, len) == 0) {
568                                 if (tm->tm_hour > 12)
569                                         return 0;
570                                 if (tm->tm_hour != 12)
571                                         tm->tm_hour += 12;
572                                 buf += len;
573                                 break;
574                         }
575
576                         return 0;
577
578                 case 'A':
579                 case 'a':
580                         for (i = 0; i < asizeof(Locale->weekday); i++) {
581                                 if (c == 'A') {
582                                         len = strlen(Locale->weekday[i]);
583                                         if (strncasecmp(buf,
584                                                         Locale->weekday[i],
585                                                         len) == 0)
586                                                 break;
587                                 } else {
588                                         len = strlen(Locale->wday[i]);
589                                         if (strncasecmp(buf,
590                                                         Locale->wday[i],
591                                                         len) == 0)
592                                                 break;
593                                 }
594                         }
595                         if (i == asizeof(Locale->weekday))
596                                 return 0;
597
598                         tm->tm_wday = i;
599                         buf += len;
600                         break;
601
602                 case 'U':
603                 case 'W':
604                         /*
605                          * XXX This is bogus, as we can not assume any valid
606                          * information present in the tm structure at this
607                          * point to calculate a real value, so just check the
608                          * range for now.
609                          */
610                         if (!isdigit((unsigned char)*buf))
611                                 return 0;
612
613                         len = 2;
614                         for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
615                                 i *= 10;
616                                 i += *buf - '0';
617                                 len--;
618                         }
619                         if (i > 53)
620                                 return 0;
621
622                         if (*buf != 0 && isspace((unsigned char)*buf))
623                                 while (*ptr != 0 && !isspace((unsigned char)*ptr))
624                                         ptr++;
625                         break;
626
627                 case 'w':
628                         if (!isdigit((unsigned char)*buf))
629                                 return 0;
630
631                         i = *buf - '0';
632                         if (i > 6)
633                                 return 0;
634
635                         tm->tm_wday = i;
636
637                         if (*buf != 0 && isspace((unsigned char)*buf))
638                                 while (*ptr != 0 && !isspace((unsigned char)*ptr))
639                                         ptr++;
640                         break;
641
642                 case 'd':
643                 case 'e':
644                         /*
645                          * The %e specifier is explicitly documented as not
646                          * being zero-padded but there is no harm in allowing
647                          * such padding.
648                          *
649                          * XXX The %e specifier may gobble one too many
650                          * digits if used incorrectly.
651                          */
652                         if (!isdigit((unsigned char)*buf))
653                                 return 0;
654
655                         len = 2;
656                         for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
657                                 i *= 10;
658                                 i += *buf - '0';
659                                 len--;
660                         }
661                         if (i > 31)
662                                 return 0;
663
664                         tm->tm_mday = i;
665
666                         if (*buf != 0 && isspace((unsigned char)*buf))
667                                 while (*ptr != 0 && !isspace((unsigned char)*ptr))
668                                         ptr++;
669                         break;
670
671                 case 'B':
672                 case 'b':
673                 case 'h':
674                         for (i = 0; i < asizeof(Locale->month); i++) {
675                                 if (Oalternative) {
676                                         if (c == 'B') {
677                                                 len = strlen(Locale->alt_month[i]);
678                                                 if (strncasecmp(buf,
679                                                                 Locale->alt_month[i],
680                                                                 len) == 0)
681                                                         break;
682                                         }
683                                 } else {
684                                         if (c == 'B') {
685                                                 len = strlen(Locale->month[i]);
686                                                 if (strncasecmp(buf,
687                                                                 Locale->month[i],
688                                                                 len) == 0)
689                                                         break;
690                                         } else {
691                                                 len = strlen(Locale->mon[i]);
692                                                 if (strncasecmp(buf,
693                                                                 Locale->mon[i],
694                                                                 len) == 0)
695                                                         break;
696                                         }
697                                 }
698                         }
699                         if (i == asizeof(Locale->month))
700                                 return 0;
701
702                         tm->tm_mon = i;
703                         buf += len;
704                         break;
705
706                 case 'm':
707                         if (!isdigit((unsigned char)*buf))
708                                 return 0;
709
710                         len = 2;
711                         for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
712                                 i *= 10;
713                                 i += *buf - '0';
714                                 len--;
715                         }
716                         if (i < 1 || i > 12)
717                                 return 0;
718
719                         tm->tm_mon = i - 1;
720
721                         if (*buf != 0 && isspace((unsigned char)*buf))
722                                 while (*ptr != 0 && !isspace((unsigned char)*ptr))
723                                         ptr++;
724                         break;
725
726                 case 'Y':
727                 case 'y':
728                         if (*buf == 0 || isspace((unsigned char)*buf))
729                                 break;
730
731                         if (!isdigit((unsigned char)*buf))
732                                 return 0;
733
734                         len = (c == 'Y') ? 4 : 2;
735                         for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
736                                 i *= 10;
737                                 i += *buf - '0';
738                                 len--;
739                         }
740                         if (c == 'Y')
741                                 i -= 1900;
742                         if (c == 'y' && i < 69)
743                                 i += 100;
744                         if (i < 0)
745                                 return 0;
746
747                         tm->tm_year = i;
748
749                         if (*buf != 0 && isspace((unsigned char)*buf))
750                                 while (*ptr != 0 && !isspace((unsigned char)*ptr))
751                                         ptr++;
752                         break;
753
754                 case 'Z':
755                         {
756                         const char *cp;
757                         char *zonestr;
758
759                         for (cp = buf; *cp && isupper((unsigned char)*cp); ++cp) 
760                             {/*empty*/}
761                         if (cp - buf) {
762                                 zonestr = (char *)alloca(cp - buf + 1);
763                                 strncpy(zonestr, buf, cp - buf);
764                                 zonestr[cp - buf] = '\0';
765                                 tzset();
766                                 if (0 == strcmp(zonestr, "GMT")) {
767                                     got_GMT = 1;
768                                 } else {
769                                     return 0;
770                                 }
771                                 buf += cp - buf;
772                         }
773                         }
774                         break;
775                 }
776         }
777         return (char *)buf;
778 }
779
780
781 char *
782 strptime(const char *buf, const char *fmt, struct tm *tm)
783 {
784         char *ret;
785
786 #ifdef _THREAD_SAFE
787 pthread_mutex_lock(&gotgmt_mutex);
788 #endif
789
790         got_GMT = 0;
791         ret = _strptime(buf, fmt, tm);
792
793 #ifdef _THREAD_SAFE
794         pthread_mutex_unlock(&gotgmt_mutex);
795 #endif
796
797         return ret;
798 }
799
800 #endif /* Mac OS X */
801
802 MODULE = Time::Piece     PACKAGE = Time::Piece
803
804 PROTOTYPES: ENABLE
805
806 void
807 _strftime(fmt, sec, min, hour, mday, mon, year, wday = -1, yday = -1, isdst = -1)
808     char *        fmt
809     int        sec
810     int        min
811     int        hour
812     int        mday
813     int        mon
814     int        year
815     int        wday
816     int        yday
817     int        isdst
818     CODE:
819     {
820         char tmpbuf[128];
821         struct tm mytm;
822         int len;
823         memset(&mytm, 0, sizeof(mytm));
824         my_init_tm(&mytm);    /* XXX workaround - see my_init_tm() above */
825         mytm.tm_sec = sec;
826         mytm.tm_min = min;
827         mytm.tm_hour = hour;
828         mytm.tm_mday = mday;
829         mytm.tm_mon = mon;
830         mytm.tm_year = year;
831         mytm.tm_wday = wday;
832         mytm.tm_yday = yday;
833         mytm.tm_isdst = isdst;
834         my_mini_mktime(&mytm);
835         len = strftime(tmpbuf, sizeof tmpbuf, fmt, &mytm);
836         /*
837         ** The following is needed to handle to the situation where 
838         ** tmpbuf overflows.  Basically we want to allocate a buffer
839         ** and try repeatedly.  The reason why it is so complicated
840         ** is that getting a return value of 0 from strftime can indicate
841         ** one of the following:
842         ** 1. buffer overflowed,
843         ** 2. illegal conversion specifier, or
844         ** 3. the format string specifies nothing to be returned(not
845         **      an error).  This could be because format is an empty string
846         **    or it specifies %p that yields an empty string in some locale.
847         ** If there is a better way to make it portable, go ahead by
848         ** all means.
849         */
850         if ((len > 0 && len < sizeof(tmpbuf)) || (len == 0 && *fmt == '\0'))
851         ST(0) = sv_2mortal(newSVpv(tmpbuf, len));
852         else {
853         /* Possibly buf overflowed - try again with a bigger buf */
854         int     fmtlen = strlen(fmt);
855         int    bufsize = fmtlen + sizeof(tmpbuf);
856         char*     buf;
857         int    buflen;
858
859         New(0, buf, bufsize, char);
860         while (buf) {
861             buflen = strftime(buf, bufsize, fmt, &mytm);
862             if (buflen > 0 && buflen < bufsize)
863             break;
864             /* heuristic to prevent out-of-memory errors */
865             if (bufsize > 100*fmtlen) {
866             Safefree(buf);
867             buf = NULL;
868             break;
869             }
870             bufsize *= 2;
871             Renew(buf, bufsize, char);
872         }
873         if (buf) {
874             ST(0) = sv_2mortal(newSVpv(buf, buflen));
875             Safefree(buf);
876         }
877         else
878             ST(0) = sv_2mortal(newSVpv(tmpbuf, len));
879         }
880     }
881
882 void
883 _tzset()
884   PPCODE:
885     tzset();
886
887
888 void
889 _strptime ( string, format )
890         char * string
891         char * format
892   PREINIT:
893        struct tm mytm;
894        time_t t;
895        char * remainder;
896   PPCODE:
897        t = 0;
898        mytm = *gmtime(&t);
899        
900        remainder = (char *)strptime(string, format, &mytm);
901        
902        if (remainder == NULL) {
903           croak("Error parsing time");
904        }
905
906        if (*remainder != '\0') {
907            warn("garbage at end of string in strptime: %s", remainder);
908        }
909           
910        my_mini_mktime(&mytm);
911
912   /* warn("tm: %d-%d-%d %d:%d:%d\n", mytm.tm_year, mytm.tm_mon, mytm.tm_mday, mytm.tm_hour, mytm.tm_min, mytm.tm_sec); */
913           
914        EXTEND(SP, 11);
915        PUSHs(sv_2mortal(newSViv(mytm.tm_sec)));
916        PUSHs(sv_2mortal(newSViv(mytm.tm_min)));
917        PUSHs(sv_2mortal(newSViv(mytm.tm_hour)));
918        PUSHs(sv_2mortal(newSViv(mytm.tm_mday)));
919        PUSHs(sv_2mortal(newSViv(mytm.tm_mon)));
920        PUSHs(sv_2mortal(newSViv(mytm.tm_year)));
921        PUSHs(sv_2mortal(newSViv(mytm.tm_wday)));
922        PUSHs(sv_2mortal(newSViv(mytm.tm_yday)));
923        /* isdst */
924        PUSHs(sv_2mortal(newSViv(0)));
925        /* epoch */
926        PUSHs(sv_2mortal(newSViv(0)));
927        /* islocal */
928        PUSHs(sv_2mortal(newSViv(0)));