Bump version of File::Copy
[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 prior to Perl 5.8.0:
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 vestigial workaround for Perls prior to 5.8.0.  We now
23  * rely on the initialization (still likely a workaround) in util.c.
24  */
25 #if !defined(PERL_VERSION) || PERL_VERSION < 8
26
27 #if defined(HAS_GNULIBC)
28 # ifndef STRUCT_TM_HASZONE
29 #    define STRUCT_TM_HASZONE
30 # else
31 #    define USE_TM_GMTOFF
32 # endif
33 #endif
34
35 #endif /* end of pre-5.8 */
36
37 #define    DAYS_PER_YEAR    365
38 #define    DAYS_PER_QYEAR    (4*DAYS_PER_YEAR+1)
39 #define    DAYS_PER_CENT    (25*DAYS_PER_QYEAR-1)
40 #define    DAYS_PER_QCENT    (4*DAYS_PER_CENT+1)
41 #define    SECS_PER_HOUR    (60*60)
42 #define    SECS_PER_DAY    (24*SECS_PER_HOUR)
43 /* parentheses deliberately absent on these two, otherwise they don't work */
44 #define    MONTH_TO_DAYS    153/5
45 #define    DAYS_TO_MONTH    5/153
46 /* offset to bias by March (month 4) 1st between month/mday & year finding */
47 #define    YEAR_ADJUST    (4*MONTH_TO_DAYS+1)
48 /* as used here, the algorithm leaves Sunday as day 1 unless we adjust it */
49 #define    WEEKDAY_BIAS    6    /* (1+6)%7 makes Sunday 0 again */
50
51 #if !defined(PERL_VERSION) || PERL_VERSION < 8
52
53 #ifdef STRUCT_TM_HASZONE
54 static void
55 my_init_tm(struct tm *ptm)        /* see mktime, strftime and asctime    */
56 {
57     Time_t now;
58     (void)time(&now);
59     Copy(localtime(&now), ptm, 1, struct tm);
60 }
61
62 #else
63 # define my_init_tm(ptm)
64 #endif
65
66 #else
67 /* use core version from util.c in 5.8.0 and later */
68 # define my_init_tm init_tm
69 #endif 
70
71 #ifdef WIN32
72
73 /*
74  * (1) The CRT maintains its own copy of the environment, separate from
75  * the Win32API copy.
76  *
77  * (2) CRT getenv() retrieves from this copy. CRT putenv() updates this
78  * copy, and then calls SetEnvironmentVariableA() to update the Win32API
79  * copy.
80  *
81  * (3) win32_getenv() and win32_putenv() call GetEnvironmentVariableA() and
82  * SetEnvironmentVariableA() directly, bypassing the CRT copy of the
83  * environment.
84  *
85  * (4) The CRT strftime() "%Z" implementation calls __tzset(). That
86  * calls CRT tzset(), but only the first time it is called, and in turn
87  * that uses CRT getenv("TZ") to retrieve the timezone info from the CRT
88  * local copy of the environment and hence gets the original setting as
89  * perl never updates the CRT copy when assigning to $ENV{TZ}.
90  *
91  * Therefore, we need to retrieve the value of $ENV{TZ} and call CRT
92  * putenv() to update the CRT copy of the environment whenever we're about
93  * to call tzset().
94  */
95
96 static const char*
97 win32_crt_getenv(const char* name)
98 {
99 #undef getenv
100     const char* value = getenv(name);
101 #define getenv win32_getenv
102     return value;
103 }
104
105 static void
106 win32_crt_putenv(const char* name, const char* value)
107 {
108     char* envstr =
109         (char*)malloc((strlen(name) + strlen(value) + 2) * sizeof(char));
110     if (envstr != NULL) {
111         sprintf(envstr, "%s=%s", name, value);
112 #undef putenv
113         putenv(envstr);
114 #define putenv win32_putenv
115         free(envstr);
116     }
117 }
118
119 static void
120 fix_win32_tzenv(void)
121 {
122     const char* perl_tz_env = getenv("TZ");
123     const char* crt_tz_env = win32_crt_getenv("TZ");
124     if (perl_tz_env != NULL && crt_tz_env != NULL) {
125         if (strcmp(perl_tz_env, crt_tz_env) != 0)
126             win32_crt_putenv("TZ", perl_tz_env);
127     }
128     else if (perl_tz_env != NULL && crt_tz_env == NULL)
129         win32_crt_putenv("TZ", perl_tz_env);
130     else if (perl_tz_env == NULL && crt_tz_env != NULL)
131         win32_crt_putenv("TZ", "");
132 }
133
134 #endif
135
136 static void
137 my_tzset(void)
138 {
139 #ifdef WIN32
140     fix_win32_tzenv();
141 #endif
142     tzset();
143 }
144
145 /*
146  * my_mini_mktime - normalise struct tm values without the localtime()
147  * semantics (and overhead) of mktime(). Stolen shamelessly from Perl's
148  * Perl_mini_mktime() in util.c - for details on the algorithm, see that
149  * file.
150  */
151 static void
152 my_mini_mktime(struct tm *ptm)
153 {
154     int yearday;
155     int secs;
156     int month, mday, year, jday;
157     int odd_cent, odd_year;
158
159     year = 1900 + ptm->tm_year;
160     month = ptm->tm_mon;
161     mday = ptm->tm_mday;
162     /* allow given yday with no month & mday to dominate the result */
163     if (ptm->tm_yday >= 0 && mday <= 0 && month <= 0) {
164         month = 0;
165         mday = 0;
166         jday = 1 + ptm->tm_yday;
167     }
168     else {
169         jday = 0;
170     }
171     if (month >= 2)
172         month+=2;
173     else
174         month+=14, year--;
175
176     yearday = DAYS_PER_YEAR * year + year/4 - year/100 + year/400;
177     yearday += month*MONTH_TO_DAYS + mday + jday;
178     /*
179      * Note that we don't know when leap-seconds were or will be,
180      * so we have to trust the user if we get something which looks
181      * like a sensible leap-second.  Wild values for seconds will
182      * be rationalised, however.
183      */
184     if ((unsigned) ptm->tm_sec <= 60) {
185         secs = 0;
186     }
187     else {
188         secs = ptm->tm_sec;
189         ptm->tm_sec = 0;
190     }
191     secs += 60 * ptm->tm_min;
192     secs += SECS_PER_HOUR * ptm->tm_hour;
193     if (secs < 0) {
194         if (secs-(secs/SECS_PER_DAY*SECS_PER_DAY) < 0) {
195             /* got negative remainder, but need positive time */
196             /* back off an extra day to compensate */
197             yearday += (secs/SECS_PER_DAY)-1;
198             secs -= SECS_PER_DAY * (secs/SECS_PER_DAY - 1);
199         }
200         else {
201             yearday += (secs/SECS_PER_DAY);
202             secs -= SECS_PER_DAY * (secs/SECS_PER_DAY);
203         }
204     }
205     else if (secs >= SECS_PER_DAY) {
206         yearday += (secs/SECS_PER_DAY);
207         secs %= SECS_PER_DAY;
208     }
209     ptm->tm_hour = secs/SECS_PER_HOUR;
210     secs %= SECS_PER_HOUR;
211     ptm->tm_min = secs/60;
212     secs %= 60;
213     ptm->tm_sec += secs;
214     /* done with time of day effects */
215     /*
216      * The algorithm for yearday has (so far) left it high by 428.
217      * To avoid mistaking a legitimate Feb 29 as Mar 1, we need to
218      * bias it by 123 while trying to figure out what year it
219      * really represents.  Even with this tweak, the reverse
220      * translation fails for years before A.D. 0001.
221      * It would still fail for Feb 29, but we catch that one below.
222      */
223     jday = yearday;    /* save for later fixup vis-a-vis Jan 1 */
224     yearday -= YEAR_ADJUST;
225     year = (yearday / DAYS_PER_QCENT) * 400;
226     yearday %= DAYS_PER_QCENT;
227     odd_cent = yearday / DAYS_PER_CENT;
228     year += odd_cent * 100;
229     yearday %= DAYS_PER_CENT;
230     year += (yearday / DAYS_PER_QYEAR) * 4;
231     yearday %= DAYS_PER_QYEAR;
232     odd_year = yearday / DAYS_PER_YEAR;
233     year += odd_year;
234     yearday %= DAYS_PER_YEAR;
235     if (!yearday && (odd_cent==4 || odd_year==4)) { /* catch Feb 29 */
236         month = 1;
237         yearday = 29;
238     }
239     else {
240         yearday += YEAR_ADJUST;    /* recover March 1st crock */
241         month = yearday*DAYS_TO_MONTH;
242         yearday -= month*MONTH_TO_DAYS;
243         /* recover other leap-year adjustment */
244         if (month > 13) {
245             month-=14;
246             year++;
247         }
248         else {
249             month-=2;
250         }
251     }
252     ptm->tm_year = year - 1900;
253     if (yearday) {
254       ptm->tm_mday = yearday;
255       ptm->tm_mon = month;
256     }
257     else {
258       ptm->tm_mday = 31;
259       ptm->tm_mon = month - 1;
260     }
261     /* re-build yearday based on Jan 1 to get tm_yday */
262     year--;
263     yearday = year*DAYS_PER_YEAR + year/4 - year/100 + year/400;
264     yearday += 14*MONTH_TO_DAYS + 1;
265     ptm->tm_yday = jday - yearday;
266     /* fix tm_wday if not overridden by caller */
267     ptm->tm_wday = (jday + WEEKDAY_BIAS) % 7;
268 }
269
270 /* No strptime on Win32 or QNX4 */
271 #if defined(WIN32) || (defined(__QNX__) && defined(__WATCOMC__))
272 #define strncasecmp(x,y,n) strnicmp(x,y,n)
273
274 #if defined(WIN32)
275 #if defined(__BORLANDC__)
276 void * __cdecl _EXPFUNC alloca(_SIZE_T __size);
277 #else
278 #define alloca _alloca
279 #endif
280 #endif
281
282 /* strptime copied from freebsd with the following copyright: */
283 /*
284  * Copyright (c) 1994 Powerdog Industries.  All rights reserved.
285  *
286  * Redistribution and use in source and binary forms, with or without
287  * modification, are permitted provided that the following conditions
288  * are met:
289  * 1. Redistributions of source code must retain the above copyright
290  *    notice, this list of conditions and the following disclaimer.
291  * 2. Redistributions in binary form must reproduce the above copyright
292  *    notice, this list of conditions and the following disclaimer
293  *    in the documentation and/or other materials provided with the
294  *    distribution.
295  * 3. All advertising materials mentioning features or use of this
296  *    software must display the following acknowledgement:
297  *      This product includes software developed by Powerdog Industries.
298  * 4. The name of Powerdog Industries may not be used to endorse or
299  *    promote products derived from this software without specific prior
300  *    written permission.
301  *
302  * THIS SOFTWARE IS PROVIDED BY POWERDOG INDUSTRIES ``AS IS'' AND ANY
303  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
304  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
305  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE POWERDOG INDUSTRIES BE
306  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
307  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
308  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
309  * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
310  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
311  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
312  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
313  */
314  
315 #ifndef lint
316 #ifndef NOID
317 static char copyright[] =
318 "@(#) Copyright (c) 1994 Powerdog Industries.  All rights reserved.";
319 static char sccsid[] = "@(#)strptime.c  0.1 (Powerdog) 94/03/27";
320 #endif /* !defined NOID */
321 #endif /* not lint */
322
323 #include <time.h>
324 #include <ctype.h>
325 #include <string.h>
326 #ifdef _THREAD_SAFE
327 #include <pthread.h>
328 #include "pthread_private.h"
329 #endif /* _THREAD_SAFE */
330
331 static char * _strptime(const char *, const char *, struct tm *);
332
333 #ifdef _THREAD_SAFE
334 static struct pthread_mutex     _gotgmt_mutexd = PTHREAD_MUTEX_STATIC_INITIALIZER;
335 static pthread_mutex_t          gotgmt_mutex   = &_gotgmt_mutexd;
336 #endif
337 static int got_GMT;
338
339 #define asizeof(a)      (sizeof (a) / sizeof ((a)[0]))
340
341 struct lc_time_T {
342     const char *    mon[12];
343     const char *    month[12];
344     const char *    wday[7];
345     const char *    weekday[7];
346     const char *    X_fmt;     
347     const char *    x_fmt;
348     const char *    c_fmt;
349     const char *    am;
350     const char *    pm;
351     const char *    date_fmt;
352     const char *    alt_month[12];
353     const char *    Ef_fmt;
354     const char *    EF_fmt;
355 };
356
357 struct lc_time_T _time_localebuf;
358 int _time_using_locale;
359
360 const struct lc_time_T  _C_time_locale = {
361         {
362                 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
363                 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
364         }, {
365                 "January", "February", "March", "April", "May", "June",
366                 "July", "August", "September", "October", "November", "December"
367         }, {
368                 "Sun", "Mon", "Tue", "Wed",
369                 "Thu", "Fri", "Sat"
370         }, {
371                 "Sunday", "Monday", "Tuesday", "Wednesday",
372                 "Thursday", "Friday", "Saturday"
373         },
374
375         /* X_fmt */
376         "%H:%M:%S",
377
378         /*
379         ** x_fmt
380         ** Since the C language standard calls for
381         ** "date, using locale's date format," anything goes.
382         ** Using just numbers (as here) makes Quakers happier;
383         ** it's also compatible with SVR4.
384         */
385         "%m/%d/%y",
386
387         /*
388         ** c_fmt (ctime-compatible)
389         ** Not used, just compatibility placeholder.
390         */
391         NULL,
392
393         /* am */
394         "AM",
395
396         /* pm */
397         "PM",
398
399         /* date_fmt */
400         "%a %Ef %X %Z %Y",
401         
402         {
403                 "January", "February", "March", "April", "May", "June",
404                 "July", "August", "September", "October", "November", "December"
405         },
406
407         /* Ef_fmt
408         ** To determine short months / day order
409         */
410         "%b %e",
411
412         /* EF_fmt
413         ** To determine long months / day order
414         */
415         "%B %e"
416 };
417
418 #define Locale (&_C_time_locale)
419
420 static char *
421 _strptime(const char *buf, const char *fmt, struct tm *tm)
422 {
423         char c;
424         const char *ptr;
425         int i,
426                 len;
427         int Ealternative, Oalternative;
428
429         ptr = fmt;
430         while (*ptr != 0) {
431                 if (*buf == 0)
432                         break;
433
434                 c = *ptr++;
435
436                 if (c != '%') {
437                         if (isspace((unsigned char)c))
438                                 while (*buf != 0 && isspace((unsigned char)*buf))
439                                         buf++;
440                         else if (c != *buf++)
441                                 return 0;
442                         continue;
443                 }
444
445                 Ealternative = 0;
446                 Oalternative = 0;
447 label:
448                 c = *ptr++;
449                 switch (c) {
450                 case 0:
451                 case '%':
452                         if (*buf++ != '%')
453                                 return 0;
454                         break;
455
456                 case '+':
457                         buf = _strptime(buf, Locale->date_fmt, tm);
458                         if (buf == 0)
459                                 return 0;
460                         break;
461
462                 case 'C':
463                         if (!isdigit((unsigned char)*buf))
464                                 return 0;
465
466                         /* XXX This will break for 3-digit centuries. */
467                         len = 2;
468                         for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
469                                 i *= 10;
470                                 i += *buf - '0';
471                                 len--;
472                         }
473                         if (i < 19)
474                                 return 0;
475
476                         tm->tm_year = i * 100 - 1900;
477                         break;
478
479                 case 'c':
480                         /* NOTE: c_fmt is intentionally ignored */
481                         buf = _strptime(buf, "%a %Ef %T %Y", tm);
482                         if (buf == 0)
483                                 return 0;
484                         break;
485
486                 case 'D':
487                         buf = _strptime(buf, "%m/%d/%y", tm);
488                         if (buf == 0)
489                                 return 0;
490                         break;
491
492                 case 'E':
493                         if (Ealternative || Oalternative)
494                                 break;
495                         Ealternative++;
496                         goto label;
497
498                 case 'O':
499                         if (Ealternative || Oalternative)
500                                 break;
501                         Oalternative++;
502                         goto label;
503
504                 case 'F':
505                 case 'f':
506                         if (!Ealternative)
507                                 break;
508                         buf = _strptime(buf, (c == 'f') ? Locale->Ef_fmt : Locale->EF_fmt, tm);
509                         if (buf == 0)
510                                 return 0;
511                         break;
512
513                 case 'R':
514                         buf = _strptime(buf, "%H:%M", tm);
515                         if (buf == 0)
516                                 return 0;
517                         break;
518
519                 case 'r':
520                         buf = _strptime(buf, "%I:%M:%S %p", tm);
521                         if (buf == 0)
522                                 return 0;
523                         break;
524
525                 case 'T':
526                         buf = _strptime(buf, "%H:%M:%S", tm);
527                         if (buf == 0)
528                                 return 0;
529                         break;
530
531                 case 'X':
532                         buf = _strptime(buf, Locale->X_fmt, tm);
533                         if (buf == 0)
534                                 return 0;
535                         break;
536
537                 case 'x':
538                         buf = _strptime(buf, Locale->x_fmt, tm);
539                         if (buf == 0)
540                                 return 0;
541                         break;
542
543                 case 'j':
544                         if (!isdigit((unsigned char)*buf))
545                                 return 0;
546
547                         len = 3;
548                         for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
549                                 i *= 10;
550                                 i += *buf - '0';
551                                 len--;
552                         }
553                         if (i < 1 || i > 366)
554                                 return 0;
555
556                         tm->tm_yday = i - 1;
557                         break;
558
559                 case 'M':
560                 case 'S':
561                         if (*buf == 0 || isspace((unsigned char)*buf))
562                                 break;
563
564                         if (!isdigit((unsigned char)*buf))
565                                 return 0;
566
567                         len = 2;
568                         for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
569                                 i *= 10;
570                                 i += *buf - '0';
571                                 len--;
572                         }
573
574                         if (c == 'M') {
575                                 if (i > 59)
576                                         return 0;
577                                 tm->tm_min = i;
578                         } else {
579                                 if (i > 60)
580                                         return 0;
581                                 tm->tm_sec = i;
582                         }
583
584                         if (*buf != 0 && isspace((unsigned char)*buf))
585                                 while (*ptr != 0 && !isspace((unsigned char)*ptr))
586                                         ptr++;
587                         break;
588
589                 case 'H':
590                 case 'I':
591                 case 'k':
592                 case 'l':
593                         /*
594                          * Of these, %l is the only specifier explicitly
595                          * documented as not being zero-padded.  However,
596                          * there is no harm in allowing zero-padding.
597                          *
598                          * XXX The %l specifier may gobble one too many
599                          * digits if used incorrectly.
600                          */
601                         if (!isdigit((unsigned char)*buf))
602                                 return 0;
603
604                         len = 2;
605                         for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
606                                 i *= 10;
607                                 i += *buf - '0';
608                                 len--;
609                         }
610                         if (c == 'H' || c == 'k') {
611                                 if (i > 23)
612                                         return 0;
613                         } else if (i > 12)
614                                 return 0;
615
616                         tm->tm_hour = i;
617
618                         if (*buf != 0 && isspace((unsigned char)*buf))
619                                 while (*ptr != 0 && !isspace((unsigned char)*ptr))
620                                         ptr++;
621                         break;
622
623                 case 'p':
624                         /*
625                          * XXX This is bogus if parsed before hour-related
626                          * specifiers.
627                          */
628                         len = strlen(Locale->am);
629                         if (strncasecmp(buf, Locale->am, len) == 0) {
630                                 if (tm->tm_hour > 12)
631                                         return 0;
632                                 if (tm->tm_hour == 12)
633                                         tm->tm_hour = 0;
634                                 buf += len;
635                                 break;
636                         }
637
638                         len = strlen(Locale->pm);
639                         if (strncasecmp(buf, Locale->pm, len) == 0) {
640                                 if (tm->tm_hour > 12)
641                                         return 0;
642                                 if (tm->tm_hour != 12)
643                                         tm->tm_hour += 12;
644                                 buf += len;
645                                 break;
646                         }
647
648                         return 0;
649
650                 case 'A':
651                 case 'a':
652                         for (i = 0; i < asizeof(Locale->weekday); i++) {
653                                 if (c == 'A') {
654                                         len = strlen(Locale->weekday[i]);
655                                         if (strncasecmp(buf,
656                                                         Locale->weekday[i],
657                                                         len) == 0)
658                                                 break;
659                                 } else {
660                                         len = strlen(Locale->wday[i]);
661                                         if (strncasecmp(buf,
662                                                         Locale->wday[i],
663                                                         len) == 0)
664                                                 break;
665                                 }
666                         }
667                         if (i == asizeof(Locale->weekday))
668                                 return 0;
669
670                         tm->tm_wday = i;
671                         buf += len;
672                         break;
673
674                 case 'U':
675                 case 'W':
676                         /*
677                          * XXX This is bogus, as we can not assume any valid
678                          * information present in the tm structure at this
679                          * point to calculate a real value, so just check the
680                          * range for now.
681                          */
682                         if (!isdigit((unsigned char)*buf))
683                                 return 0;
684
685                         len = 2;
686                         for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
687                                 i *= 10;
688                                 i += *buf - '0';
689                                 len--;
690                         }
691                         if (i > 53)
692                                 return 0;
693
694                         if (*buf != 0 && isspace((unsigned char)*buf))
695                                 while (*ptr != 0 && !isspace((unsigned char)*ptr))
696                                         ptr++;
697                         break;
698
699                 case 'w':
700                         if (!isdigit((unsigned char)*buf))
701                                 return 0;
702
703                         i = *buf - '0';
704                         if (i > 6)
705                                 return 0;
706
707                         tm->tm_wday = i;
708
709                         if (*buf != 0 && isspace((unsigned char)*buf))
710                                 while (*ptr != 0 && !isspace((unsigned char)*ptr))
711                                         ptr++;
712                         break;
713
714                 case 'd':
715                 case 'e':
716                         /*
717                          * The %e specifier is explicitly documented as not
718                          * being zero-padded but there is no harm in allowing
719                          * such padding.
720                          *
721                          * XXX The %e specifier may gobble one too many
722                          * digits if used incorrectly.
723                          */
724                         if (!isdigit((unsigned char)*buf))
725                                 return 0;
726
727                         len = 2;
728                         for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
729                                 i *= 10;
730                                 i += *buf - '0';
731                                 len--;
732                         }
733                         if (i > 31)
734                                 return 0;
735
736                         tm->tm_mday = i;
737
738                         if (*buf != 0 && isspace((unsigned char)*buf))
739                                 while (*ptr != 0 && !isspace((unsigned char)*ptr))
740                                         ptr++;
741                         break;
742
743                 case 'B':
744                 case 'b':
745                 case 'h':
746                         for (i = 0; i < asizeof(Locale->month); i++) {
747                                 if (Oalternative) {
748                                         if (c == 'B') {
749                                                 len = strlen(Locale->alt_month[i]);
750                                                 if (strncasecmp(buf,
751                                                                 Locale->alt_month[i],
752                                                                 len) == 0)
753                                                         break;
754                                         }
755                                 } else {
756                                         if (c == 'B') {
757                                                 len = strlen(Locale->month[i]);
758                                                 if (strncasecmp(buf,
759                                                                 Locale->month[i],
760                                                                 len) == 0)
761                                                         break;
762                                         } else {
763                                                 len = strlen(Locale->mon[i]);
764                                                 if (strncasecmp(buf,
765                                                                 Locale->mon[i],
766                                                                 len) == 0)
767                                                         break;
768                                         }
769                                 }
770                         }
771                         if (i == asizeof(Locale->month))
772                                 return 0;
773
774                         tm->tm_mon = i;
775                         buf += len;
776                         break;
777
778                 case 'm':
779                         if (!isdigit((unsigned char)*buf))
780                                 return 0;
781
782                         len = 2;
783                         for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
784                                 i *= 10;
785                                 i += *buf - '0';
786                                 len--;
787                         }
788                         if (i < 1 || i > 12)
789                                 return 0;
790
791                         tm->tm_mon = i - 1;
792
793                         if (*buf != 0 && isspace((unsigned char)*buf))
794                                 while (*ptr != 0 && !isspace((unsigned char)*ptr))
795                                         ptr++;
796                         break;
797
798                 case 'Y':
799                 case 'y':
800                         if (*buf == 0 || isspace((unsigned char)*buf))
801                                 break;
802
803                         if (!isdigit((unsigned char)*buf))
804                                 return 0;
805
806                         len = (c == 'Y') ? 4 : 2;
807                         for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) {
808                                 i *= 10;
809                                 i += *buf - '0';
810                                 len--;
811                         }
812                         if (c == 'Y')
813                                 i -= 1900;
814                         if (c == 'y' && i < 69)
815                                 i += 100;
816                         if (i < 0)
817                                 return 0;
818
819                         tm->tm_year = i;
820
821                         if (*buf != 0 && isspace((unsigned char)*buf))
822                                 while (*ptr != 0 && !isspace((unsigned char)*ptr))
823                                         ptr++;
824                         break;
825
826                 case 'Z':
827                         {
828                         const char *cp;
829                         char *zonestr;
830
831                         for (cp = buf; *cp && isupper((unsigned char)*cp); ++cp) 
832                             {/*empty*/}
833                         if (cp - buf) {
834                                 zonestr = (char *)alloca(cp - buf + 1);
835                                 strncpy(zonestr, buf, cp - buf);
836                                 zonestr[cp - buf] = '\0';
837                                 my_tzset();
838                                 if (0 == strcmp(zonestr, "GMT")) {
839                                     got_GMT = 1;
840                                 } else {
841                                     return 0;
842                                 }
843                                 buf += cp - buf;
844                         }
845                         }
846                         break;
847                 }
848         }
849         return (char *)buf;
850 }
851
852
853 char *
854 strptime(const char *buf, const char *fmt, struct tm *tm)
855 {
856         char *ret;
857
858 #ifdef _THREAD_SAFE
859 pthread_mutex_lock(&gotgmt_mutex);
860 #endif
861
862         got_GMT = 0;
863         ret = _strptime(buf, fmt, tm);
864
865 #ifdef _THREAD_SAFE
866         pthread_mutex_unlock(&gotgmt_mutex);
867 #endif
868
869         return ret;
870 }
871
872 #endif /* Mac OS X */
873
874 MODULE = Time::Piece     PACKAGE = Time::Piece
875
876 PROTOTYPES: ENABLE
877
878 void
879 _strftime(fmt, sec, min, hour, mday, mon, year, wday = -1, yday = -1, isdst = -1)
880     char *        fmt
881     int        sec
882     int        min
883     int        hour
884     int        mday
885     int        mon
886     int        year
887     int        wday
888     int        yday
889     int        isdst
890     CODE:
891     {
892         char tmpbuf[128];
893         struct tm mytm;
894         int len;
895         memset(&mytm, 0, sizeof(mytm));
896         my_init_tm(&mytm);    /* XXX workaround - see my_init_tm() above */
897         mytm.tm_sec = sec;
898         mytm.tm_min = min;
899         mytm.tm_hour = hour;
900         mytm.tm_mday = mday;
901         mytm.tm_mon = mon;
902         mytm.tm_year = year;
903         mytm.tm_wday = wday;
904         mytm.tm_yday = yday;
905         mytm.tm_isdst = isdst;
906         my_mini_mktime(&mytm);
907         len = strftime(tmpbuf, sizeof tmpbuf, fmt, &mytm);
908         /*
909         ** The following is needed to handle to the situation where 
910         ** tmpbuf overflows.  Basically we want to allocate a buffer
911         ** and try repeatedly.  The reason why it is so complicated
912         ** is that getting a return value of 0 from strftime can indicate
913         ** one of the following:
914         ** 1. buffer overflowed,
915         ** 2. illegal conversion specifier, or
916         ** 3. the format string specifies nothing to be returned(not
917         **      an error).  This could be because format is an empty string
918         **    or it specifies %p that yields an empty string in some locale.
919         ** If there is a better way to make it portable, go ahead by
920         ** all means.
921         */
922         if ((len > 0 && len < sizeof(tmpbuf)) || (len == 0 && *fmt == '\0'))
923         ST(0) = sv_2mortal(newSVpv(tmpbuf, len));
924         else {
925         /* Possibly buf overflowed - try again with a bigger buf */
926         int     fmtlen = strlen(fmt);
927         int    bufsize = fmtlen + sizeof(tmpbuf);
928         char*     buf;
929         int    buflen;
930
931         New(0, buf, bufsize, char);
932         while (buf) {
933             buflen = strftime(buf, bufsize, fmt, &mytm);
934             if (buflen > 0 && buflen < bufsize)
935             break;
936             /* heuristic to prevent out-of-memory errors */
937             if (bufsize > 100*fmtlen) {
938             Safefree(buf);
939             buf = NULL;
940             break;
941             }
942             bufsize *= 2;
943             Renew(buf, bufsize, char);
944         }
945         if (buf) {
946             ST(0) = sv_2mortal(newSVpv(buf, buflen));
947             Safefree(buf);
948         }
949         else
950             ST(0) = sv_2mortal(newSVpv(tmpbuf, len));
951         }
952     }
953
954 void
955 _tzset()
956   PPCODE:
957     my_tzset();
958
959
960 void
961 _strptime ( string, format )
962         char * string
963         char * format
964   PREINIT:
965        struct tm mytm;
966        time_t t;
967        char * remainder;
968   PPCODE:
969        t = 0;
970        mytm = *gmtime(&t);
971        
972        remainder = (char *)strptime(string, format, &mytm);
973        
974        if (remainder == NULL) {
975           croak("Error parsing time");
976        }
977
978        if (*remainder != '\0') {
979            warn("garbage at end of string in strptime: %s", remainder);
980        }
981           
982        my_mini_mktime(&mytm);
983
984   /* 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); */
985           
986        EXTEND(SP, 11);
987        PUSHs(sv_2mortal(newSViv(mytm.tm_sec)));
988        PUSHs(sv_2mortal(newSViv(mytm.tm_min)));
989        PUSHs(sv_2mortal(newSViv(mytm.tm_hour)));
990        PUSHs(sv_2mortal(newSViv(mytm.tm_mday)));
991        PUSHs(sv_2mortal(newSViv(mytm.tm_mon)));
992        PUSHs(sv_2mortal(newSViv(mytm.tm_year)));
993        PUSHs(sv_2mortal(newSViv(mytm.tm_wday)));
994        PUSHs(sv_2mortal(newSViv(mytm.tm_yday)));
995        /* isdst */
996        PUSHs(sv_2mortal(newSViv(0)));
997        /* epoch */
998        PUSHs(sv_2mortal(newSViv(0)));
999        /* islocal */
1000        PUSHs(sv_2mortal(newSViv(0)));
1001
1002 void
1003 _mini_mktime(int sec, int min, int hour, int mday, int mon, int year)
1004   PREINIT:
1005        struct tm mytm;
1006        time_t t;
1007   PPCODE:
1008        t = 0;
1009        mytm = *gmtime(&t);
1010
1011        mytm.tm_sec = sec;
1012        mytm.tm_min = min;
1013        mytm.tm_hour = hour;
1014        mytm.tm_mday = mday;
1015        mytm.tm_mon = mon;
1016        mytm.tm_year = year;
1017        
1018        my_mini_mktime(&mytm);
1019
1020        EXTEND(SP, 11);
1021        PUSHs(sv_2mortal(newSViv(mytm.tm_sec)));
1022        PUSHs(sv_2mortal(newSViv(mytm.tm_min)));
1023        PUSHs(sv_2mortal(newSViv(mytm.tm_hour)));
1024        PUSHs(sv_2mortal(newSViv(mytm.tm_mday)));
1025        PUSHs(sv_2mortal(newSViv(mytm.tm_mon)));
1026        PUSHs(sv_2mortal(newSViv(mytm.tm_year)));
1027        PUSHs(sv_2mortal(newSViv(mytm.tm_wday)));
1028        PUSHs(sv_2mortal(newSViv(mytm.tm_yday)));
1029        /* isdst */
1030        PUSHs(sv_2mortal(newSViv(0)));
1031        /* epoch */
1032        PUSHs(sv_2mortal(newSViv(0)));
1033        /* islocal */
1034        PUSHs(sv_2mortal(newSViv(0)));