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