| #ifdef __cplusplus |
| extern "C" { |
| #endif |
| #include "EXTERN.h" |
| #include "perl.h" |
| #include "XSUB.h" |
| #include <time.h> |
| #ifdef __cplusplus |
| } |
| #endif |
| |
| /* XXX struct tm on some systems (SunOS4/BSD) contains extra (non POSIX) |
| * fields for which we don't have Configure support prior to Perl 5.8.0: |
| * char *tm_zone; -- abbreviation of timezone name |
| * long tm_gmtoff; -- offset from GMT in seconds |
| * To workaround core dumps from the uninitialised tm_zone we get the |
| * system to give us a reasonable struct to copy. This fix means that |
| * strftime uses the tm_zone and tm_gmtoff values returned by |
| * localtime(time()). That should give the desired result most of the |
| * time. But probably not always! |
| * |
| * This is a vestigial workaround for Perls prior to 5.8.0. We now |
| * rely on the initialization (still likely a workaround) in util.c. |
| */ |
| #if !defined(PERL_VERSION) || PERL_VERSION < 8 |
| |
| #if defined(HAS_GNULIBC) |
| # ifndef STRUCT_TM_HASZONE |
| # define STRUCT_TM_HASZONE |
| # else |
| # define USE_TM_GMTOFF |
| # endif |
| #endif |
| |
| #endif /* end of pre-5.8 */ |
| |
| #define DAYS_PER_YEAR 365 |
| #define DAYS_PER_QYEAR (4*DAYS_PER_YEAR+1) |
| #define DAYS_PER_CENT (25*DAYS_PER_QYEAR-1) |
| #define DAYS_PER_QCENT (4*DAYS_PER_CENT+1) |
| #define SECS_PER_HOUR (60*60) |
| #define SECS_PER_DAY (24*SECS_PER_HOUR) |
| /* parentheses deliberately absent on these two, otherwise they don't work */ |
| #define MONTH_TO_DAYS 153/5 |
| #define DAYS_TO_MONTH 5/153 |
| /* offset to bias by March (month 4) 1st between month/mday & year finding */ |
| #define YEAR_ADJUST (4*MONTH_TO_DAYS+1) |
| /* as used here, the algorithm leaves Sunday as day 1 unless we adjust it */ |
| #define WEEKDAY_BIAS 6 /* (1+6)%7 makes Sunday 0 again */ |
| |
| #if !defined(PERL_VERSION) || PERL_VERSION < 8 |
| |
| #ifdef STRUCT_TM_HASZONE |
| static void |
| my_init_tm(struct tm *ptm) /* see mktime, strftime and asctime */ |
| { |
| Time_t now; |
| (void)time(&now); |
| Copy(localtime(&now), ptm, 1, struct tm); |
| } |
| |
| #else |
| # define my_init_tm(ptm) |
| #endif |
| |
| #else |
| /* use core version from util.c in 5.8.0 and later */ |
| # define my_init_tm init_tm |
| #endif |
| |
| #ifdef WIN32 |
| |
| /* |
| * (1) The CRT maintains its own copy of the environment, separate from |
| * the Win32API copy. |
| * |
| * (2) CRT getenv() retrieves from this copy. CRT putenv() updates this |
| * copy, and then calls SetEnvironmentVariableA() to update the Win32API |
| * copy. |
| * |
| * (3) win32_getenv() and win32_putenv() call GetEnvironmentVariableA() and |
| * SetEnvironmentVariableA() directly, bypassing the CRT copy of the |
| * environment. |
| * |
| * (4) The CRT strftime() "%Z" implementation calls __tzset(). That |
| * calls CRT tzset(), but only the first time it is called, and in turn |
| * that uses CRT getenv("TZ") to retrieve the timezone info from the CRT |
| * local copy of the environment and hence gets the original setting as |
| * perl never updates the CRT copy when assigning to $ENV{TZ}. |
| * |
| * Therefore, we need to retrieve the value of $ENV{TZ} and call CRT |
| * putenv() to update the CRT copy of the environment (if it is different) |
| * whenever we're about to call tzset(). |
| * |
| * In addition to all that, when perl is built with PERL_IMPLICIT_SYS |
| * defined: |
| * |
| * (a) Each interpreter has its own copy of the environment inside the |
| * perlhost structure. That allows applications that host multiple |
| * independent Perl interpreters to isolate environment changes from |
| * each other. (This is similar to how the perlhost mechanism keeps a |
| * separate working directory for each Perl interpreter, so that calling |
| * chdir() will not affect other interpreters.) |
| * |
| * (b) Only the first Perl interpreter instantiated within a process will |
| * "write through" environment changes to the process environment. |
| * |
| * (c) Even the primary Perl interpreter won't update the CRT copy of the |
| * the environment, only the Win32API copy (it calls win32_putenv()). |
| * |
| * As with CPerlHost::Getenv() and CPerlHost::Putenv() themselves, it makes |
| * sense to only update the process environment when inside the main |
| * interpreter, but we don't have access to CPerlHost's m_bTopLevel member |
| * from here so we'll just have to check PL_curinterp instead. |
| * |
| * Therefore, we can simply #undef getenv() and putenv() so that those names |
| * always refer to the CRT functions, and explicitly call win32_getenv() to |
| * access perl's %ENV. |
| * |
| * We also #undef malloc() and free() to be sure we are using the CRT |
| * functions otherwise under PERL_IMPLICIT_SYS they are redefined to calls |
| * into VMem::Malloc() and VMem::Free() and all allocations will be freed |
| * when the Perl interpreter is being destroyed so we'd end up with a pointer |
| * into deallocated memory in environ[] if a program embedding a Perl |
| * interpreter continues to operate even after the main Perl interpreter has |
| * been destroyed. |
| * |
| * Note that we don't free() the malloc()ed memory unless and until we call |
| * malloc() again ourselves because the CRT putenv() function simply puts its |
| * pointer argument into the environ[] arrary (it doesn't make a copy of it) |
| * so this memory must otherwise be leaked. |
| */ |
| |
| #undef getenv |
| #undef putenv |
| #undef malloc |
| #undef free |
| |
| static void |
| fix_win32_tzenv(void) |
| { |
| static char* oldenv = NULL; |
| char* newenv; |
| const char* perl_tz_env = win32_getenv("TZ"); |
| const char* crt_tz_env = getenv("TZ"); |
| if (perl_tz_env == NULL) |
| perl_tz_env = ""; |
| if (crt_tz_env == NULL) |
| crt_tz_env = ""; |
| if (strcmp(perl_tz_env, crt_tz_env) != 0) { |
| newenv = (char*)malloc((strlen(perl_tz_env) + 4) * sizeof(char)); |
| if (newenv != NULL) { |
| sprintf(newenv, "TZ=%s", perl_tz_env); |
| putenv(newenv); |
| if (oldenv != NULL) |
| free(oldenv); |
| oldenv = newenv; |
| } |
| } |
| } |
| |
| #endif |
| |
| /* |
| * my_tzset - wrapper to tzset() with a fix to make it work (better) on Win32. |
| * This code is duplicated in the POSIX module, so any changes made here |
| * should be made there too. |
| */ |
| static void |
| my_tzset(pTHX) |
| { |
| #ifdef WIN32 |
| #if defined(USE_ITHREADS) && defined(PERL_IMPLICIT_SYS) |
| if (PL_curinterp == aTHX) |
| #endif |
| fix_win32_tzenv(); |
| #endif |
| tzset(); |
| } |
| |
| /* |
| * my_mini_mktime - normalise struct tm values without the localtime() |
| * semantics (and overhead) of mktime(). Stolen shamelessly from Perl's |
| * Perl_mini_mktime() in util.c - for details on the algorithm, see that |
| * file. |
| */ |
| static void |
| my_mini_mktime(struct tm *ptm) |
| { |
| int yearday; |
| int secs; |
| int month, mday, year, jday; |
| int odd_cent, odd_year; |
| |
| year = 1900 + ptm->tm_year; |
| month = ptm->tm_mon; |
| mday = ptm->tm_mday; |
| /* allow given yday with no month & mday to dominate the result */ |
| if (ptm->tm_yday >= 0 && mday <= 0 && month <= 0) { |
| month = 0; |
| mday = 0; |
| jday = 1 + ptm->tm_yday; |
| } |
| else { |
| jday = 0; |
| } |
| if (month >= 2) |
| month+=2; |
| else |
| month+=14, year--; |
| |
| yearday = DAYS_PER_YEAR * year + year/4 - year/100 + year/400; |
| yearday += month*MONTH_TO_DAYS + mday + jday; |
| /* |
| * Note that we don't know when leap-seconds were or will be, |
| * so we have to trust the user if we get something which looks |
| * like a sensible leap-second. Wild values for seconds will |
| * be rationalised, however. |
| */ |
| if ((unsigned) ptm->tm_sec <= 60) { |
| secs = 0; |
| } |
| else { |
| secs = ptm->tm_sec; |
| ptm->tm_sec = 0; |
| } |
| secs += 60 * ptm->tm_min; |
| secs += SECS_PER_HOUR * ptm->tm_hour; |
| if (secs < 0) { |
| if (secs-(secs/SECS_PER_DAY*SECS_PER_DAY) < 0) { |
| /* got negative remainder, but need positive time */ |
| /* back off an extra day to compensate */ |
| yearday += (secs/SECS_PER_DAY)-1; |
| secs -= SECS_PER_DAY * (secs/SECS_PER_DAY - 1); |
| } |
| else { |
| yearday += (secs/SECS_PER_DAY); |
| secs -= SECS_PER_DAY * (secs/SECS_PER_DAY); |
| } |
| } |
| else if (secs >= SECS_PER_DAY) { |
| yearday += (secs/SECS_PER_DAY); |
| secs %= SECS_PER_DAY; |
| } |
| ptm->tm_hour = secs/SECS_PER_HOUR; |
| secs %= SECS_PER_HOUR; |
| ptm->tm_min = secs/60; |
| secs %= 60; |
| ptm->tm_sec += secs; |
| /* done with time of day effects */ |
| /* |
| * The algorithm for yearday has (so far) left it high by 428. |
| * To avoid mistaking a legitimate Feb 29 as Mar 1, we need to |
| * bias it by 123 while trying to figure out what year it |
| * really represents. Even with this tweak, the reverse |
| * translation fails for years before A.D. 0001. |
| * It would still fail for Feb 29, but we catch that one below. |
| */ |
| jday = yearday; /* save for later fixup vis-a-vis Jan 1 */ |
| yearday -= YEAR_ADJUST; |
| year = (yearday / DAYS_PER_QCENT) * 400; |
| yearday %= DAYS_PER_QCENT; |
| odd_cent = yearday / DAYS_PER_CENT; |
| year += odd_cent * 100; |
| yearday %= DAYS_PER_CENT; |
| year += (yearday / DAYS_PER_QYEAR) * 4; |
| yearday %= DAYS_PER_QYEAR; |
| odd_year = yearday / DAYS_PER_YEAR; |
| year += odd_year; |
| yearday %= DAYS_PER_YEAR; |
| if (!yearday && (odd_cent==4 || odd_year==4)) { /* catch Feb 29 */ |
| month = 1; |
| yearday = 29; |
| } |
| else { |
| yearday += YEAR_ADJUST; /* recover March 1st crock */ |
| month = yearday*DAYS_TO_MONTH; |
| yearday -= month*MONTH_TO_DAYS; |
| /* recover other leap-year adjustment */ |
| if (month > 13) { |
| month-=14; |
| year++; |
| } |
| else { |
| month-=2; |
| } |
| } |
| ptm->tm_year = year - 1900; |
| if (yearday) { |
| ptm->tm_mday = yearday; |
| ptm->tm_mon = month; |
| } |
| else { |
| ptm->tm_mday = 31; |
| ptm->tm_mon = month - 1; |
| } |
| /* re-build yearday based on Jan 1 to get tm_yday */ |
| year--; |
| yearday = year*DAYS_PER_YEAR + year/4 - year/100 + year/400; |
| yearday += 14*MONTH_TO_DAYS + 1; |
| ptm->tm_yday = jday - yearday; |
| /* fix tm_wday if not overridden by caller */ |
| ptm->tm_wday = (jday + WEEKDAY_BIAS) % 7; |
| } |
| |
| # if defined(WIN32) || (defined(__QNX__) && defined(__WATCOMC__)) |
| # define strncasecmp(x,y,n) strnicmp(x,y,n) |
| # endif |
| |
| /* strptime copied from freebsd with the following copyright: */ |
| /* |
| * Copyright (c) 1994 Powerdog Industries. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * 3. All advertising materials mentioning features or use of this |
| * software must display the following acknowledgement: |
| * This product includes software developed by Powerdog Industries. |
| * 4. The name of Powerdog Industries may not be used to endorse or |
| * promote products derived from this software without specific prior |
| * written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY POWERDOG INDUSTRIES ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE POWERDOG INDUSTRIES BE |
| * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR |
| * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
| * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE |
| * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, |
| * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #ifndef lint |
| #ifndef NOID |
| static char copyright[] = |
| "@(#) Copyright (c) 1994 Powerdog Industries. All rights reserved."; |
| static char sccsid[] = "@(#)strptime.c 0.1 (Powerdog) 94/03/27"; |
| #endif /* !defined NOID */ |
| #endif /* not lint */ |
| |
| #include <time.h> |
| #include <ctype.h> |
| #include <string.h> |
| static char * _strptime(pTHX_ const char *, const char *, struct tm *, |
| int *got_GMT); |
| |
| #define asizeof(a) (sizeof (a) / sizeof ((a)[0])) |
| |
| struct lc_time_T { |
| const char * mon[12]; |
| const char * month[12]; |
| const char * wday[7]; |
| const char * weekday[7]; |
| const char * X_fmt; |
| const char * x_fmt; |
| const char * c_fmt; |
| const char * am; |
| const char * pm; |
| const char * date_fmt; |
| const char * alt_month[12]; |
| const char * Ef_fmt; |
| const char * EF_fmt; |
| }; |
| |
| struct lc_time_T _time_localebuf; |
| int _time_using_locale; |
| |
| const struct lc_time_T _C_time_locale = { |
| { |
| "Jan", "Feb", "Mar", "Apr", "May", "Jun", |
| "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" |
| }, { |
| "January", "February", "March", "April", "May", "June", |
| "July", "August", "September", "October", "November", "December" |
| }, { |
| "Sun", "Mon", "Tue", "Wed", |
| "Thu", "Fri", "Sat" |
| }, { |
| "Sunday", "Monday", "Tuesday", "Wednesday", |
| "Thursday", "Friday", "Saturday" |
| }, |
| |
| /* X_fmt */ |
| "%H:%M:%S", |
| |
| /* |
| ** x_fmt |
| ** Since the C language standard calls for |
| ** "date, using locale's date format," anything goes. |
| ** Using just numbers (as here) makes Quakers happier; |
| ** it's also compatible with SVR4. |
| */ |
| "%m/%d/%y", |
| |
| /* |
| ** c_fmt (ctime-compatible) |
| ** Not used, just compatibility placeholder. |
| */ |
| NULL, |
| |
| /* am */ |
| "AM", |
| |
| /* pm */ |
| "PM", |
| |
| /* date_fmt */ |
| "%a %Ef %X %Z %Y", |
| |
| { |
| "January", "February", "March", "April", "May", "June", |
| "July", "August", "September", "October", "November", "December" |
| }, |
| |
| /* Ef_fmt |
| ** To determine short months / day order |
| */ |
| "%b %e", |
| |
| /* EF_fmt |
| ** To determine long months / day order |
| */ |
| "%B %e" |
| }; |
| |
| #define Locale (&_C_time_locale) |
| |
| static char * |
| _strptime(pTHX_ const char *buf, const char *fmt, struct tm *tm, int *got_GMT) |
| { |
| char c; |
| const char *ptr; |
| int i, |
| len; |
| int Ealternative, Oalternative; |
| |
| /* There seems to be a slightly improved version at |
| * http://www.opensource.apple.com/source/Libc/Libc-583/stdtime/strptime-fbsd.c |
| * which we may end up borrowing more from |
| */ |
| ptr = fmt; |
| while (*ptr != 0) { |
| if (*buf == 0) |
| break; |
| |
| c = *ptr++; |
| |
| if (c != '%') { |
| if (isspace((unsigned char)c)) |
| while (*buf != 0 && isspace((unsigned char)*buf)) |
| buf++; |
| else if (c != *buf++) |
| return 0; |
| continue; |
| } |
| |
| Ealternative = 0; |
| Oalternative = 0; |
| label: |
| c = *ptr++; |
| switch (c) { |
| case 0: |
| case '%': |
| if (*buf++ != '%') |
| return 0; |
| break; |
| |
| case '+': |
| buf = _strptime(aTHX_ buf, Locale->date_fmt, tm, got_GMT); |
| if (buf == 0) |
| return 0; |
| break; |
| |
| case 'C': |
| if (!isdigit((unsigned char)*buf)) |
| return 0; |
| |
| /* XXX This will break for 3-digit centuries. */ |
| len = 2; |
| for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) { |
| i *= 10; |
| i += *buf - '0'; |
| len--; |
| } |
| if (i < 19) |
| return 0; |
| |
| tm->tm_year = i * 100 - 1900; |
| break; |
| |
| case 'c': |
| /* NOTE: c_fmt is intentionally ignored */ |
| buf = _strptime(aTHX_ buf, "%a %Ef %T %Y", tm, got_GMT); |
| if (buf == 0) |
| return 0; |
| break; |
| |
| case 'D': |
| buf = _strptime(aTHX_ buf, "%m/%d/%y", tm, got_GMT); |
| if (buf == 0) |
| return 0; |
| break; |
| |
| case 'E': |
| if (Ealternative || Oalternative) |
| break; |
| Ealternative++; |
| goto label; |
| |
| case 'O': |
| if (Ealternative || Oalternative) |
| break; |
| Oalternative++; |
| goto label; |
| |
| case 'F': |
| case 'f': |
| if (!Ealternative) |
| break; |
| buf = _strptime(aTHX_ buf, (c == 'f') ? Locale->Ef_fmt : Locale->EF_fmt, tm, got_GMT); |
| if (buf == 0) |
| return 0; |
| break; |
| |
| case 'R': |
| buf = _strptime(aTHX_ buf, "%H:%M", tm, got_GMT); |
| if (buf == 0) |
| return 0; |
| break; |
| |
| case 'r': |
| buf = _strptime(aTHX_ buf, "%I:%M:%S %p", tm, got_GMT); |
| if (buf == 0) |
| return 0; |
| break; |
| |
| case 'n': /* whitespace */ |
| case 't': |
| if (!isspace((unsigned char)*buf)) |
| return 0; |
| while (isspace((unsigned char)*buf)) |
| buf++; |
| break; |
| |
| case 'T': |
| buf = _strptime(aTHX_ buf, "%H:%M:%S", tm, got_GMT); |
| if (buf == 0) |
| return 0; |
| break; |
| |
| case 'X': |
| buf = _strptime(aTHX_ buf, Locale->X_fmt, tm, got_GMT); |
| if (buf == 0) |
| return 0; |
| break; |
| |
| case 'x': |
| buf = _strptime(aTHX_ buf, Locale->x_fmt, tm, got_GMT); |
| if (buf == 0) |
| return 0; |
| break; |
| |
| case 'j': |
| if (!isdigit((unsigned char)*buf)) |
| return 0; |
| |
| len = 3; |
| for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) { |
| i *= 10; |
| i += *buf - '0'; |
| len--; |
| } |
| if (i < 1 || i > 366) |
| return 0; |
| |
| tm->tm_yday = i - 1; |
| break; |
| |
| case 'M': |
| case 'S': |
| if (*buf == 0 || isspace((unsigned char)*buf)) |
| break; |
| |
| if (!isdigit((unsigned char)*buf)) |
| return 0; |
| |
| len = 2; |
| for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) { |
| i *= 10; |
| i += *buf - '0'; |
| len--; |
| } |
| |
| if (c == 'M') { |
| if (i > 59) |
| return 0; |
| tm->tm_min = i; |
| } else { |
| if (i > 60) |
| return 0; |
| tm->tm_sec = i; |
| } |
| |
| if (*buf != 0 && isspace((unsigned char)*buf)) |
| while (*ptr != 0 && !isspace((unsigned char)*ptr)) |
| ptr++; |
| break; |
| |
| case 'H': |
| case 'I': |
| case 'k': |
| case 'l': |
| /* |
| * Of these, %l is the only specifier explicitly |
| * documented as not being zero-padded. However, |
| * there is no harm in allowing zero-padding. |
| * |
| * XXX The %l specifier may gobble one too many |
| * digits if used incorrectly. |
| */ |
| if (!isdigit((unsigned char)*buf)) |
| return 0; |
| |
| len = 2; |
| for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) { |
| i *= 10; |
| i += *buf - '0'; |
| len--; |
| } |
| if (c == 'H' || c == 'k') { |
| if (i > 23) |
| return 0; |
| } else if (i > 12) |
| return 0; |
| |
| tm->tm_hour = i; |
| |
| if (*buf != 0 && isspace((unsigned char)*buf)) |
| while (*ptr != 0 && !isspace((unsigned char)*ptr)) |
| ptr++; |
| break; |
| |
| case 'p': |
| /* |
| * XXX This is bogus if parsed before hour-related |
| * specifiers. |
| */ |
| len = strlen(Locale->am); |
| if (strncasecmp(buf, Locale->am, len) == 0) { |
| if (tm->tm_hour > 12) |
| return 0; |
| if (tm->tm_hour == 12) |
| tm->tm_hour = 0; |
| buf += len; |
| break; |
| } |
| |
| len = strlen(Locale->pm); |
| if (strncasecmp(buf, Locale->pm, len) == 0) { |
| if (tm->tm_hour > 12) |
| return 0; |
| if (tm->tm_hour != 12) |
| tm->tm_hour += 12; |
| buf += len; |
| break; |
| } |
| |
| return 0; |
| |
| case 'A': |
| case 'a': |
| for (i = 0; i < asizeof(Locale->weekday); i++) { |
| if (c == 'A') { |
| len = strlen(Locale->weekday[i]); |
| if (strncasecmp(buf, |
| Locale->weekday[i], |
| len) == 0) |
| break; |
| } else { |
| len = strlen(Locale->wday[i]); |
| if (strncasecmp(buf, |
| Locale->wday[i], |
| len) == 0) |
| break; |
| } |
| } |
| if (i == asizeof(Locale->weekday)) |
| return 0; |
| |
| tm->tm_wday = i; |
| buf += len; |
| break; |
| |
| case 'U': |
| case 'W': |
| /* |
| * XXX This is bogus, as we can not assume any valid |
| * information present in the tm structure at this |
| * point to calculate a real value, so just check the |
| * range for now. |
| */ |
| if (!isdigit((unsigned char)*buf)) |
| return 0; |
| |
| len = 2; |
| for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) { |
| i *= 10; |
| i += *buf - '0'; |
| len--; |
| } |
| if (i > 53) |
| return 0; |
| |
| if (*buf != 0 && isspace((unsigned char)*buf)) |
| while (*ptr != 0 && !isspace((unsigned char)*ptr)) |
| ptr++; |
| break; |
| |
| case 'w': |
| if (!isdigit((unsigned char)*buf)) |
| return 0; |
| |
| i = *buf - '0'; |
| if (i > 6) |
| return 0; |
| |
| tm->tm_wday = i; |
| |
| if (*buf != 0 && isspace((unsigned char)*buf)) |
| while (*ptr != 0 && !isspace((unsigned char)*ptr)) |
| ptr++; |
| break; |
| |
| case 'd': |
| case 'e': |
| /* |
| * The %e specifier is explicitly documented as not |
| * being zero-padded but there is no harm in allowing |
| * such padding. |
| * |
| * XXX The %e specifier may gobble one too many |
| * digits if used incorrectly. |
| */ |
| if (!isdigit((unsigned char)*buf)) |
| return 0; |
| |
| len = 2; |
| for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) { |
| i *= 10; |
| i += *buf - '0'; |
| len--; |
| } |
| if (i > 31) |
| return 0; |
| |
| tm->tm_mday = i; |
| |
| if (*buf != 0 && isspace((unsigned char)*buf)) |
| while (*ptr != 0 && !isspace((unsigned char)*ptr)) |
| ptr++; |
| break; |
| |
| case 'B': |
| case 'b': |
| case 'h': |
| for (i = 0; i < asizeof(Locale->month); i++) { |
| if (Oalternative) { |
| if (c == 'B') { |
| len = strlen(Locale->alt_month[i]); |
| if (strncasecmp(buf, |
| Locale->alt_month[i], |
| len) == 0) |
| break; |
| } |
| } else { |
| if (c == 'B') { |
| len = strlen(Locale->month[i]); |
| if (strncasecmp(buf, |
| Locale->month[i], |
| len) == 0) |
| break; |
| } else { |
| len = strlen(Locale->mon[i]); |
| if (strncasecmp(buf, |
| Locale->mon[i], |
| len) == 0) |
| break; |
| } |
| } |
| } |
| if (i == asizeof(Locale->month)) |
| return 0; |
| |
| tm->tm_mon = i; |
| buf += len; |
| break; |
| |
| case 'm': |
| if (!isdigit((unsigned char)*buf)) |
| return 0; |
| |
| len = 2; |
| for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) { |
| i *= 10; |
| i += *buf - '0'; |
| len--; |
| } |
| if (i < 1 || i > 12) |
| return 0; |
| |
| tm->tm_mon = i - 1; |
| |
| if (*buf != 0 && isspace((unsigned char)*buf)) |
| while (*ptr != 0 && !isspace((unsigned char)*ptr)) |
| ptr++; |
| break; |
| |
| case 's': |
| { |
| char *cp; |
| int sverrno; |
| long n; |
| time_t t; |
| struct tm mytm; |
| |
| sverrno = errno; |
| errno = 0; |
| n = strtol(buf, &cp, 10); |
| if (errno == ERANGE || (long)(t = n) != n) { |
| errno = sverrno; |
| return 0; |
| } |
| errno = sverrno; |
| buf = cp; |
| memset(&mytm, 0, sizeof(mytm)); |
| my_init_tm(&mytm); /* XXX workaround - see my_init_tm() above */ |
| mytm = *gmtime(&t); |
| tm->tm_sec = mytm.tm_sec; |
| tm->tm_min = mytm.tm_min; |
| tm->tm_hour = mytm.tm_hour; |
| tm->tm_mday = mytm.tm_mday; |
| tm->tm_mon = mytm.tm_mon; |
| tm->tm_year = mytm.tm_year; |
| tm->tm_wday = mytm.tm_wday; |
| tm->tm_yday = mytm.tm_yday; |
| tm->tm_isdst = mytm.tm_isdst; |
| } |
| break; |
| |
| case 'Y': |
| case 'y': |
| if (*buf == 0 || isspace((unsigned char)*buf)) |
| break; |
| |
| if (!isdigit((unsigned char)*buf)) |
| return 0; |
| |
| len = (c == 'Y') ? 4 : 2; |
| for (i = 0; len && *buf != 0 && isdigit((unsigned char)*buf); buf++) { |
| i *= 10; |
| i += *buf - '0'; |
| len--; |
| } |
| if (c == 'Y') |
| i -= 1900; |
| if (c == 'y' && i < 69) |
| i += 100; |
| if (i < 0) |
| return 0; |
| |
| tm->tm_year = i; |
| |
| if (*buf != 0 && isspace((unsigned char)*buf)) |
| while (*ptr != 0 && !isspace((unsigned char)*ptr)) |
| ptr++; |
| break; |
| |
| case 'Z': |
| { |
| const char *cp; |
| char *zonestr; |
| |
| for (cp = buf; *cp && isupper((unsigned char)*cp); ++cp) |
| {/*empty*/} |
| if (cp - buf) { |
| zonestr = (char *)malloc(cp - buf + 1); |
| if (!zonestr) { |
| errno = ENOMEM; |
| return 0; |
| } |
| strncpy(zonestr, buf, cp - buf); |
| zonestr[cp - buf] = '\0'; |
| my_tzset(aTHX); |
| if (0 == strcmp(zonestr, "GMT")) { |
| *got_GMT = 1; |
| } |
| free(zonestr); |
| if (!*got_GMT) return 0; |
| buf += cp - buf; |
| } |
| } |
| break; |
| |
| case 'z': |
| { |
| int sign = 1; |
| |
| if (*buf != '+') { |
| if (*buf == '-') |
| sign = -1; |
| else |
| return 0; |
| } |
| |
| buf++; |
| i = 0; |
| for (len = 4; len > 0; len--) { |
| if (isdigit((int)*buf)) { |
| i *= 10; |
| i += *buf - '0'; |
| buf++; |
| } else |
| return 0; |
| } |
| |
| tm->tm_hour -= sign * (i / 100); |
| tm->tm_min -= sign * (i % 100); |
| *got_GMT = 1; |
| } |
| break; |
| } |
| } |
| return (char *)buf; |
| } |
| |
| |
| char * |
| our_strptime(pTHX_ const char *buf, const char *fmt, struct tm *tm) |
| { |
| char *ret; |
| int got_GMT = 0; |
| |
| return _strptime(aTHX_ buf, fmt, tm, &got_GMT); |
| } |
| |
| MODULE = Time::Piece PACKAGE = Time::Piece |
| |
| PROTOTYPES: ENABLE |
| |
| void |
| _strftime(fmt, sec, min, hour, mday, mon, year, wday = -1, yday = -1, isdst = -1) |
| char * fmt |
| int sec |
| int min |
| int hour |
| int mday |
| int mon |
| int year |
| int wday |
| int yday |
| int isdst |
| CODE: |
| { |
| char tmpbuf[128]; |
| struct tm mytm; |
| int len; |
| memset(&mytm, 0, sizeof(mytm)); |
| my_init_tm(&mytm); /* XXX workaround - see my_init_tm() above */ |
| mytm.tm_sec = sec; |
| mytm.tm_min = min; |
| mytm.tm_hour = hour; |
| mytm.tm_mday = mday; |
| mytm.tm_mon = mon; |
| mytm.tm_year = year; |
| mytm.tm_wday = wday; |
| mytm.tm_yday = yday; |
| mytm.tm_isdst = isdst; |
| my_mini_mktime(&mytm); |
| len = strftime(tmpbuf, sizeof tmpbuf, fmt, &mytm); |
| /* |
| ** The following is needed to handle to the situation where |
| ** tmpbuf overflows. Basically we want to allocate a buffer |
| ** and try repeatedly. The reason why it is so complicated |
| ** is that getting a return value of 0 from strftime can indicate |
| ** one of the following: |
| ** 1. buffer overflowed, |
| ** 2. illegal conversion specifier, or |
| ** 3. the format string specifies nothing to be returned(not |
| ** an error). This could be because format is an empty string |
| ** or it specifies %p that yields an empty string in some locale. |
| ** If there is a better way to make it portable, go ahead by |
| ** all means. |
| */ |
| if ((len > 0 && len < sizeof(tmpbuf)) || (len == 0 && *fmt == '\0')) |
| ST(0) = sv_2mortal(newSVpv(tmpbuf, len)); |
| else { |
| /* Possibly buf overflowed - try again with a bigger buf */ |
| int fmtlen = strlen(fmt); |
| int bufsize = fmtlen + sizeof(tmpbuf); |
| char* buf; |
| int buflen; |
| |
| New(0, buf, bufsize, char); |
| while (buf) { |
| buflen = strftime(buf, bufsize, fmt, &mytm); |
| if (buflen > 0 && buflen < bufsize) |
| break; |
| /* heuristic to prevent out-of-memory errors */ |
| if (bufsize > 100*fmtlen) { |
| Safefree(buf); |
| buf = NULL; |
| break; |
| } |
| bufsize *= 2; |
| Renew(buf, bufsize, char); |
| } |
| if (buf) { |
| ST(0) = sv_2mortal(newSVpv(buf, buflen)); |
| Safefree(buf); |
| } |
| else |
| ST(0) = sv_2mortal(newSVpv(tmpbuf, len)); |
| } |
| } |
| |
| void |
| _tzset() |
| PPCODE: |
| my_tzset(aTHX); |
| |
| |
| void |
| _strptime ( string, format ) |
| char * string |
| char * format |
| PREINIT: |
| struct tm mytm; |
| time_t t; |
| char * remainder; |
| PPCODE: |
| t = 0; |
| mytm = *gmtime(&t); |
| remainder = (char *)our_strptime(aTHX_ string, format, &mytm); |
| if (remainder == NULL) { |
| croak("Error parsing time"); |
| } |
| if (*remainder != '\0') { |
| warn("garbage at end of string in strptime: %s", remainder); |
| } |
| |
| my_mini_mktime(&mytm); |
| |
| /* 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); */ |
| |
| EXTEND(SP, 11); |
| PUSHs(sv_2mortal(newSViv(mytm.tm_sec))); |
| PUSHs(sv_2mortal(newSViv(mytm.tm_min))); |
| PUSHs(sv_2mortal(newSViv(mytm.tm_hour))); |
| PUSHs(sv_2mortal(newSViv(mytm.tm_mday))); |
| PUSHs(sv_2mortal(newSViv(mytm.tm_mon))); |
| PUSHs(sv_2mortal(newSViv(mytm.tm_year))); |
| PUSHs(sv_2mortal(newSViv(mytm.tm_wday))); |
| PUSHs(sv_2mortal(newSViv(mytm.tm_yday))); |
| /* isdst */ |
| PUSHs(sv_2mortal(newSViv(0))); |
| /* epoch */ |
| PUSHs(sv_2mortal(newSViv(0))); |
| /* islocal */ |
| PUSHs(sv_2mortal(newSViv(0))); |
| |
| void |
| _mini_mktime(int sec, int min, int hour, int mday, int mon, int year) |
| PREINIT: |
| struct tm mytm; |
| time_t t; |
| PPCODE: |
| t = 0; |
| mytm = *gmtime(&t); |
| |
| mytm.tm_sec = sec; |
| mytm.tm_min = min; |
| mytm.tm_hour = hour; |
| mytm.tm_mday = mday; |
| mytm.tm_mon = mon; |
| mytm.tm_year = year; |
| |
| my_mini_mktime(&mytm); |
| |
| EXTEND(SP, 11); |
| PUSHs(sv_2mortal(newSViv(mytm.tm_sec))); |
| PUSHs(sv_2mortal(newSViv(mytm.tm_min))); |
| PUSHs(sv_2mortal(newSViv(mytm.tm_hour))); |
| PUSHs(sv_2mortal(newSViv(mytm.tm_mday))); |
| PUSHs(sv_2mortal(newSViv(mytm.tm_mon))); |
| PUSHs(sv_2mortal(newSViv(mytm.tm_year))); |
| PUSHs(sv_2mortal(newSViv(mytm.tm_wday))); |
| PUSHs(sv_2mortal(newSViv(mytm.tm_yday))); |
| /* isdst */ |
| PUSHs(sv_2mortal(newSViv(0))); |
| /* epoch */ |
| PUSHs(sv_2mortal(newSViv(0))); |
| /* islocal */ |
| PUSHs(sv_2mortal(newSViv(0))); |
| |
| void |
| _crt_localtime(time_t sec) |
| PREINIT: |
| struct tm mytm; |
| PPCODE: |
| mytm = *localtime(&sec); |
| /* Need to get: $s,$n,$h,$d,$m,$y */ |
| |
| EXTEND(SP, 9); |
| PUSHs(sv_2mortal(newSViv(mytm.tm_sec))); |
| PUSHs(sv_2mortal(newSViv(mytm.tm_min))); |
| PUSHs(sv_2mortal(newSViv(mytm.tm_hour))); |
| PUSHs(sv_2mortal(newSViv(mytm.tm_mday))); |
| PUSHs(sv_2mortal(newSViv(mytm.tm_mon))); |
| PUSHs(sv_2mortal(newSViv(mytm.tm_year))); |
| PUSHs(sv_2mortal(newSViv(mytm.tm_year))); |
| PUSHs(sv_2mortal(newSViv(mytm.tm_wday))); |
| PUSHs(sv_2mortal(newSViv(mytm.tm_yday))); |
| PUSHs(sv_2mortal(newSViv(mytm.tm_isdst))); |
| |
| void |
| _crt_gmtime(time_t sec) |
| PREINIT: |
| struct tm mytm; |
| PPCODE: |
| mytm = *gmtime(&sec); |
| /* Need to get: $s,$n,$h,$d,$m,$y */ |
| |
| EXTEND(SP, 9); |
| PUSHs(sv_2mortal(newSViv(mytm.tm_sec))); |
| PUSHs(sv_2mortal(newSViv(mytm.tm_min))); |
| PUSHs(sv_2mortal(newSViv(mytm.tm_hour))); |
| PUSHs(sv_2mortal(newSViv(mytm.tm_mday))); |
| PUSHs(sv_2mortal(newSViv(mytm.tm_mon))); |
| PUSHs(sv_2mortal(newSViv(mytm.tm_year))); |
| PUSHs(sv_2mortal(newSViv(mytm.tm_wday))); |
| PUSHs(sv_2mortal(newSViv(mytm.tm_yday))); |
| PUSHs(sv_2mortal(newSViv(mytm.tm_isdst))); |
| |