Re: [PATCH] MAD fix p55 $[
[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 #if defined(__BORLANDC__)
249 void * __cdecl _EXPFUNC alloca(_SIZE_T __size);
250 #else
251 #define alloca _alloca
252 #endif
253 #endif
254
255 #include <time.h>
256 #include <ctype.h>
257 #include <string.h>
258 #ifdef _THREAD_SAFE
259 #include <pthread.h>
260 #include "pthread_private.h"
261 #endif /* _THREAD_SAFE */
262
263 static char * _strptime(const char *, const char *, struct tm *);
264
265 #ifdef _THREAD_SAFE
266 static struct pthread_mutex     _gotgmt_mutexd = PTHREAD_MUTEX_STATIC_INITIALIZER;
267 static pthread_mutex_t          gotgmt_mutex   = &_gotgmt_mutexd;
268 #endif
269 static int got_GMT;
270
271 #define asizeof(a)      (sizeof (a) / sizeof ((a)[0]))
272
273 struct lc_time_T {
274     const char *    mon[12];
275     const char *    month[12];
276     const char *    wday[7];
277     const char *    weekday[7];
278     const char *    X_fmt;     
279     const char *    x_fmt;
280     const char *    c_fmt;
281     const char *    am;
282     const char *    pm;
283     const char *    date_fmt;
284     const char *    alt_month[12];
285     const char *    Ef_fmt;
286     const char *    EF_fmt;
287 };
288
289 struct lc_time_T _time_localebuf;
290 int _time_using_locale;
291
292 const struct lc_time_T  _C_time_locale = {
293         {
294                 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
295                 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
296         }, {
297                 "January", "February", "March", "April", "May", "June",
298                 "July", "August", "September", "October", "November", "December"
299         }, {
300                 "Sun", "Mon", "Tue", "Wed",
301                 "Thu", "Fri", "Sat"
302         }, {
303                 "Sunday", "Monday", "Tuesday", "Wednesday",
304                 "Thursday", "Friday", "Saturday"
305         },
306
307         /* X_fmt */
308         "%H:%M:%S",
309
310         /*
311         ** x_fmt
312         ** Since the C language standard calls for
313         ** "date, using locale's date format," anything goes.
314         ** Using just numbers (as here) makes Quakers happier;
315         ** it's also compatible with SVR4.
316         */
317         "%m/%d/%y",
318
319         /*
320         ** c_fmt (ctime-compatible)
321         ** Not used, just compatibility placeholder.
322         */
323         NULL,
324
325         /* am */
326         "AM",
327
328         /* pm */
329         "PM",
330
331         /* date_fmt */
332         "%a %Ef %X %Z %Y",
333         
334         {
335                 "January", "February", "March", "April", "May", "June",
336                 "July", "August", "September", "October", "November", "December"
337         },
338
339         /* Ef_fmt
340         ** To determine short months / day order
341         */
342         "%b %e",
343
344         /* EF_fmt
345         ** To determine long months / day order
346         */
347         "%B %e"
348 };
349
350 #define Locale (&_C_time_locale)
351
352 static char *
353 _strptime(const char *buf, const char *fmt, struct tm *tm)
354 {
355         char c;
356         const char *ptr;
357         int i,
358                 len;
359         int Ealternative, Oalternative;
360
361         ptr = fmt;
362         while (*ptr != 0) {
363                 if (*buf == 0)
364                         break;
365
366                 c = *ptr++;
367
368                 if (c != '%') {
369                         if (isspace((unsigned char)c))
370                                 while (*buf != 0 && isspace((unsigned char)*buf))
371                                         buf++;
372                         else if (c != *buf++)
373                                 return 0;
374                         continue;
375                 }
376
377                 Ealternative = 0;
378                 Oalternative = 0;
379 label:
380                 c = *ptr++;
381                 switch (c) {
382                 case 0:
383                 case '%':
384                         if (*buf++ != '%')
385                                 return 0;
386                         break;
387
388                 case '+':
389                         buf = _strptime(buf, Locale->date_fmt, tm);
390                         if (buf == 0)
391                                 return 0;
392                         break;
393
394                 case 'C':
395                         if (!isdigit((unsigned char)*buf))
396                                 return 0;
397
398                         /* XXX This will break for 3-digit centuries. */
399                         len = 2;
400                         for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
401                                 i *= 10;
402                                 i += *buf - '0';
403                                 len--;
404                         }
405                         if (i < 19)
406                                 return 0;
407
408                         tm->tm_year = i * 100 - 1900;
409                         break;
410
411                 case 'c':
412                         /* NOTE: c_fmt is intentionally ignored */
413                         buf = _strptime(buf, "%a %Ef %T %Y", tm);
414                         if (buf == 0)
415                                 return 0;
416                         break;
417
418                 case 'D':
419                         buf = _strptime(buf, "%m/%d/%y", tm);
420                         if (buf == 0)
421                                 return 0;
422                         break;
423
424                 case 'E':
425                         if (Ealternative || Oalternative)
426                                 break;
427                         Ealternative++;
428                         goto label;
429
430                 case 'O':
431                         if (Ealternative || Oalternative)
432                                 break;
433                         Oalternative++;
434                         goto label;
435
436                 case 'F':
437                 case 'f':
438                         if (!Ealternative)
439                                 break;
440                         buf = _strptime(buf, (c == 'f') ? Locale->Ef_fmt : Locale->EF_fmt, tm);
441                         if (buf == 0)
442                                 return 0;
443                         break;
444
445                 case 'R':
446                         buf = _strptime(buf, "%H:%M", tm);
447                         if (buf == 0)
448                                 return 0;
449                         break;
450
451                 case 'r':
452                         buf = _strptime(buf, "%I:%M:%S %p", tm);
453                         if (buf == 0)
454                                 return 0;
455                         break;
456
457                 case 'T':
458                         buf = _strptime(buf, "%H:%M:%S", tm);
459                         if (buf == 0)
460                                 return 0;
461                         break;
462
463                 case 'X':
464                         buf = _strptime(buf, Locale->X_fmt, tm);
465                         if (buf == 0)
466                                 return 0;
467                         break;
468
469                 case 'x':
470                         buf = _strptime(buf, Locale->x_fmt, tm);
471                         if (buf == 0)
472                                 return 0;
473                         break;
474
475                 case 'j':
476                         if (!isdigit((unsigned char)*buf))
477                                 return 0;
478
479                         len = 3;
480                         for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
481                                 i *= 10;
482                                 i += *buf - '0';
483                                 len--;
484                         }
485                         if (i < 1 || i > 366)
486                                 return 0;
487
488                         tm->tm_yday = i - 1;
489                         break;
490
491                 case 'M':
492                 case 'S':
493                         if (*buf == 0 || isspace((unsigned char)*buf))
494                                 break;
495
496                         if (!isdigit((unsigned char)*buf))
497                                 return 0;
498
499                         len = 2;
500                         for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
501                                 i *= 10;
502                                 i += *buf - '0';
503                                 len--;
504                         }
505
506                         if (c == 'M') {
507                                 if (i > 59)
508                                         return 0;
509                                 tm->tm_min = i;
510                         } else {
511                                 if (i > 60)
512                                         return 0;
513                                 tm->tm_sec = i;
514                         }
515
516                         if (*buf != 0 && isspace((unsigned char)*buf))
517                                 while (*ptr != 0 && !isspace((unsigned char)*ptr))
518                                         ptr++;
519                         break;
520
521                 case 'H':
522                 case 'I':
523                 case 'k':
524                 case 'l':
525                         /*
526                          * Of these, %l is the only specifier explicitly
527                          * documented as not being zero-padded.  However,
528                          * there is no harm in allowing zero-padding.
529                          *
530                          * XXX The %l specifier may gobble one too many
531                          * digits if used incorrectly.
532                          */
533                         if (!isdigit((unsigned char)*buf))
534                                 return 0;
535
536                         len = 2;
537                         for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
538                                 i *= 10;
539                                 i += *buf - '0';
540                                 len--;
541                         }
542                         if (c == 'H' || c == 'k') {
543                                 if (i > 23)
544                                         return 0;
545                         } else if (i > 12)
546                                 return 0;
547
548                         tm->tm_hour = i;
549
550                         if (*buf != 0 && isspace((unsigned char)*buf))
551                                 while (*ptr != 0 && !isspace((unsigned char)*ptr))
552                                         ptr++;
553                         break;
554
555                 case 'p':
556                         /*
557                          * XXX This is bogus if parsed before hour-related
558                          * specifiers.
559                          */
560                         len = strlen(Locale->am);
561                         if (strncasecmp(buf, Locale->am, len) == 0) {
562                                 if (tm->tm_hour > 12)
563                                         return 0;
564                                 if (tm->tm_hour == 12)
565                                         tm->tm_hour = 0;
566                                 buf += len;
567                                 break;
568                         }
569
570                         len = strlen(Locale->pm);
571                         if (strncasecmp(buf, Locale->pm, len) == 0) {
572                                 if (tm->tm_hour > 12)
573                                         return 0;
574                                 if (tm->tm_hour != 12)
575                                         tm->tm_hour += 12;
576                                 buf += len;
577                                 break;
578                         }
579
580                         return 0;
581
582                 case 'A':
583                 case 'a':
584                         for (i = 0; i < asizeof(Locale->weekday); i++) {
585                                 if (c == 'A') {
586                                         len = strlen(Locale->weekday[i]);
587                                         if (strncasecmp(buf,
588                                                         Locale->weekday[i],
589                                                         len) == 0)
590                                                 break;
591                                 } else {
592                                         len = strlen(Locale->wday[i]);
593                                         if (strncasecmp(buf,
594                                                         Locale->wday[i],
595                                                         len) == 0)
596                                                 break;
597                                 }
598                         }
599                         if (i == asizeof(Locale->weekday))
600                                 return 0;
601
602                         tm->tm_wday = i;
603                         buf += len;
604                         break;
605
606                 case 'U':
607                 case 'W':
608                         /*
609                          * XXX This is bogus, as we can not assume any valid
610                          * information present in the tm structure at this
611                          * point to calculate a real value, so just check the
612                          * range for now.
613                          */
614                         if (!isdigit((unsigned char)*buf))
615                                 return 0;
616
617                         len = 2;
618                         for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
619                                 i *= 10;
620                                 i += *buf - '0';
621                                 len--;
622                         }
623                         if (i > 53)
624                                 return 0;
625
626                         if (*buf != 0 && isspace((unsigned char)*buf))
627                                 while (*ptr != 0 && !isspace((unsigned char)*ptr))
628                                         ptr++;
629                         break;
630
631                 case 'w':
632                         if (!isdigit((unsigned char)*buf))
633                                 return 0;
634
635                         i = *buf - '0';
636                         if (i > 6)
637                                 return 0;
638
639                         tm->tm_wday = i;
640
641                         if (*buf != 0 && isspace((unsigned char)*buf))
642                                 while (*ptr != 0 && !isspace((unsigned char)*ptr))
643                                         ptr++;
644                         break;
645
646                 case 'd':
647                 case 'e':
648                         /*
649                          * The %e specifier is explicitly documented as not
650                          * being zero-padded but there is no harm in allowing
651                          * such padding.
652                          *
653                          * XXX The %e specifier may gobble one too many
654                          * digits if used incorrectly.
655                          */
656                         if (!isdigit((unsigned char)*buf))
657                                 return 0;
658
659                         len = 2;
660                         for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
661                                 i *= 10;
662                                 i += *buf - '0';
663                                 len--;
664                         }
665                         if (i > 31)
666                                 return 0;
667
668                         tm->tm_mday = i;
669
670                         if (*buf != 0 && isspace((unsigned char)*buf))
671                                 while (*ptr != 0 && !isspace((unsigned char)*ptr))
672                                         ptr++;
673                         break;
674
675                 case 'B':
676                 case 'b':
677                 case 'h':
678                         for (i = 0; i < asizeof(Locale->month); i++) {
679                                 if (Oalternative) {
680                                         if (c == 'B') {
681                                                 len = strlen(Locale->alt_month[i]);
682                                                 if (strncasecmp(buf,
683                                                                 Locale->alt_month[i],
684                                                                 len) == 0)
685                                                         break;
686                                         }
687                                 } else {
688                                         if (c == 'B') {
689                                                 len = strlen(Locale->month[i]);
690                                                 if (strncasecmp(buf,
691                                                                 Locale->month[i],
692                                                                 len) == 0)
693                                                         break;
694                                         } else {
695                                                 len = strlen(Locale->mon[i]);
696                                                 if (strncasecmp(buf,
697                                                                 Locale->mon[i],
698                                                                 len) == 0)
699                                                         break;
700                                         }
701                                 }
702                         }
703                         if (i == asizeof(Locale->month))
704                                 return 0;
705
706                         tm->tm_mon = i;
707                         buf += len;
708                         break;
709
710                 case 'm':
711                         if (!isdigit((unsigned char)*buf))
712                                 return 0;
713
714                         len = 2;
715                         for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
716                                 i *= 10;
717                                 i += *buf - '0';
718                                 len--;
719                         }
720                         if (i < 1 || i > 12)
721                                 return 0;
722
723                         tm->tm_mon = i - 1;
724
725                         if (*buf != 0 && isspace((unsigned char)*buf))
726                                 while (*ptr != 0 && !isspace((unsigned char)*ptr))
727                                         ptr++;
728                         break;
729
730                 case 'Y':
731                 case 'y':
732                         if (*buf == 0 || isspace((unsigned char)*buf))
733                                 break;
734
735                         if (!isdigit((unsigned char)*buf))
736                                 return 0;
737
738                         len = (c == 'Y') ? 4 : 2;
739                         for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
740                                 i *= 10;
741                                 i += *buf - '0';
742                                 len--;
743                         }
744                         if (c == 'Y')
745                                 i -= 1900;
746                         if (c == 'y' && i < 69)
747                                 i += 100;
748                         if (i < 0)
749                                 return 0;
750
751                         tm->tm_year = i;
752
753                         if (*buf != 0 && isspace((unsigned char)*buf))
754                                 while (*ptr != 0 && !isspace((unsigned char)*ptr))
755                                         ptr++;
756                         break;
757
758                 case 'Z':
759                         {
760                         const char *cp;
761                         char *zonestr;
762
763                         for (cp = buf; *cp && isupper((unsigned char)*cp); ++cp) 
764                             {/*empty*/}
765                         if (cp - buf) {
766                                 zonestr = (char *)alloca(cp - buf + 1);
767                                 strncpy(zonestr, buf, cp - buf);
768                                 zonestr[cp - buf] = '\0';
769                                 tzset();
770                                 if (0 == strcmp(zonestr, "GMT")) {
771                                     got_GMT = 1;
772                                 } else {
773                                     return 0;
774                                 }
775                                 buf += cp - buf;
776                         }
777                         }
778                         break;
779                 }
780         }
781         return (char *)buf;
782 }
783
784
785 char *
786 strptime(const char *buf, const char *fmt, struct tm *tm)
787 {
788         char *ret;
789
790 #ifdef _THREAD_SAFE
791 pthread_mutex_lock(&gotgmt_mutex);
792 #endif
793
794         got_GMT = 0;
795         ret = _strptime(buf, fmt, tm);
796
797 #ifdef _THREAD_SAFE
798         pthread_mutex_unlock(&gotgmt_mutex);
799 #endif
800
801         return ret;
802 }
803
804 #endif /* Mac OS X */
805
806 MODULE = Time::Piece     PACKAGE = Time::Piece
807
808 PROTOTYPES: ENABLE
809
810 void
811 _strftime(fmt, sec, min, hour, mday, mon, year, wday = -1, yday = -1, isdst = -1)
812     char *        fmt
813     int        sec
814     int        min
815     int        hour
816     int        mday
817     int        mon
818     int        year
819     int        wday
820     int        yday
821     int        isdst
822     CODE:
823     {
824         char tmpbuf[128];
825         struct tm mytm;
826         int len;
827         memset(&mytm, 0, sizeof(mytm));
828         my_init_tm(&mytm);    /* XXX workaround - see my_init_tm() above */
829         mytm.tm_sec = sec;
830         mytm.tm_min = min;
831         mytm.tm_hour = hour;
832         mytm.tm_mday = mday;
833         mytm.tm_mon = mon;
834         mytm.tm_year = year;
835         mytm.tm_wday = wday;
836         mytm.tm_yday = yday;
837         mytm.tm_isdst = isdst;
838         my_mini_mktime(&mytm);
839         len = strftime(tmpbuf, sizeof tmpbuf, fmt, &mytm);
840         /*
841         ** The following is needed to handle to the situation where 
842         ** tmpbuf overflows.  Basically we want to allocate a buffer
843         ** and try repeatedly.  The reason why it is so complicated
844         ** is that getting a return value of 0 from strftime can indicate
845         ** one of the following:
846         ** 1. buffer overflowed,
847         ** 2. illegal conversion specifier, or
848         ** 3. the format string specifies nothing to be returned(not
849         **      an error).  This could be because format is an empty string
850         **    or it specifies %p that yields an empty string in some locale.
851         ** If there is a better way to make it portable, go ahead by
852         ** all means.
853         */
854         if ((len > 0 && len < sizeof(tmpbuf)) || (len == 0 && *fmt == '\0'))
855         ST(0) = sv_2mortal(newSVpv(tmpbuf, len));
856         else {
857         /* Possibly buf overflowed - try again with a bigger buf */
858         int     fmtlen = strlen(fmt);
859         int    bufsize = fmtlen + sizeof(tmpbuf);
860         char*     buf;
861         int    buflen;
862
863         New(0, buf, bufsize, char);
864         while (buf) {
865             buflen = strftime(buf, bufsize, fmt, &mytm);
866             if (buflen > 0 && buflen < bufsize)
867             break;
868             /* heuristic to prevent out-of-memory errors */
869             if (bufsize > 100*fmtlen) {
870             Safefree(buf);
871             buf = NULL;
872             break;
873             }
874             bufsize *= 2;
875             Renew(buf, bufsize, char);
876         }
877         if (buf) {
878             ST(0) = sv_2mortal(newSVpv(buf, buflen));
879             Safefree(buf);
880         }
881         else
882             ST(0) = sv_2mortal(newSVpv(tmpbuf, len));
883         }
884     }
885
886 void
887 _tzset()
888   PPCODE:
889     tzset();
890
891
892 void
893 _strptime ( string, format )
894         char * string
895         char * format
896   PREINIT:
897        struct tm mytm;
898        time_t t;
899        char * remainder;
900   PPCODE:
901        t = 0;
902        mytm = *gmtime(&t);
903        
904        remainder = (char *)strptime(string, format, &mytm);
905        
906        if (remainder == NULL) {
907           croak("Error parsing time");
908        }
909
910        if (*remainder != '\0') {
911            warn("garbage at end of string in strptime: %s", remainder);
912        }
913           
914        my_mini_mktime(&mytm);
915
916   /* 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); */
917           
918        EXTEND(SP, 11);
919        PUSHs(sv_2mortal(newSViv(mytm.tm_sec)));
920        PUSHs(sv_2mortal(newSViv(mytm.tm_min)));
921        PUSHs(sv_2mortal(newSViv(mytm.tm_hour)));
922        PUSHs(sv_2mortal(newSViv(mytm.tm_mday)));
923        PUSHs(sv_2mortal(newSViv(mytm.tm_mon)));
924        PUSHs(sv_2mortal(newSViv(mytm.tm_year)));
925        PUSHs(sv_2mortal(newSViv(mytm.tm_wday)));
926        PUSHs(sv_2mortal(newSViv(mytm.tm_yday)));
927        /* isdst */
928        PUSHs(sv_2mortal(newSViv(0)));
929        /* epoch */
930        PUSHs(sv_2mortal(newSViv(0)));
931        /* islocal */
932        PUSHs(sv_2mortal(newSViv(0)));