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