The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
/*
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

  The GPG signature in this file may be checked with 'gpg --verify format.c'. */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <locale.h>
#include <ctype.h>
#include <config.h>

#ifdef I_LANGINFO
#include <langinfo.h>
#endif

#define unless(cond)  if (!(cond))

static char _VERSION[] = "1.03";

/* format.c, version 1.03

This is part of the Time::Format_XS module.  See the .pm file for documentation.

This code is copyright (c) 2003-2009 by Eric J. Roode -- all rights reserved.

See the Changes file for change history.

*/

#define DEBUG 0
#if DEBUG
#define BUG(args)  fprintf args
#else
#define BUG(args)
#endif

#define TF_INTERNAL "Time::Format_XS internal error: "
typedef struct state_struct
{
    int year, month, day, hour, min, sec, dow;
    int micro, milli;
    char am;
    int h12;
    size_t length;
    const char *start, *fmt;
    char *out, *outptr;
    int modifying;
    int upper, lower, ucnext, lcnext;
    int quoting;
    char tzone[60];
} st_struct, *state;


/* Month and weekday names, and their abbreviations.  Populated by setup_locale. */

#ifdef HAS_NL_LANGINFO
static char *Month_Name[13];
static char *Mon_Name[13];
static char *Weekday_Name[7];
static char *Day_Name[7];

nl_item NL_MONTH_IX[13] = {  MON_1,  MON_1,   MON_2,   MON_3,   MON_4,   MON_5,   MON_6,   MON_7,   MON_8,   MON_9,   MON_10,   MON_11,   MON_12};
nl_item NL_MON_IX  [13] = {ABMON_1, ABMON_1, ABMON_2, ABMON_3, ABMON_4, ABMON_5, ABMON_6, ABMON_7, ABMON_8, ABMON_9, ABMON_10, ABMON_11, ABMON_12};
nl_item NL_WKDAY_IX[ 7] = {  DAY_1,   DAY_2,   DAY_3,   DAY_4,   DAY_5,   DAY_6,   DAY_7};
nl_item NL_DAY_IX  [ 7] = {ABDAY_1, ABDAY_2, ABDAY_3, ABDAY_4, ABDAY_5, ABDAY_6, ABDAY_7};
#else
char *ENGLISH_MONTH_NAME[13] = {"n/a", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"};
char *ENGLISH_MON_NAME[13] = {"n/a", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
char *ENGLISH_WKD_NAME[7] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
char *ENGLISH_DAY_NAME[7] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
#endif

/* Croak.  Calls Time::Format_XS::_croak, which calls Carp::croak */
void c_croak (const char *str)
{
    STRLEN len=strlen(str);
    dSP;
    ENTER;
    SAVETMPS;
    PUSHMARK(SP);
    XPUSHs(sv_2mortal(newSVpv(str,len)));
    PUTBACK;
    call_pv("Time::Format_XS::_croak", G_DISCARD);
    FREETMPS;
    LEAVE;
}

/* setup_locale
   Populate day/month names based on current locale.
   Alas, I don't know how portable this code is.
*/
static void setup_locale(void)
{
    static int checked_locale = 0;
    int i;

    char *cur_locale;
    static char prev_locale[40];

    /* have we checked the locale yet? */
    if (checked_locale)
    {
        /* Yes.  Has it changed? */
        cur_locale = setlocale(LC_TIME, NULL);
        if (NULL != cur_locale  &&  !strcmp(cur_locale, prev_locale))
            /* No, it's the same */
            return;
    }
    else
    {
        cur_locale = setlocale(LC_TIME, "");
        checked_locale = 1;
    }

    /* Locale either was never set, or has just changed.  Store it. */
    strncpy(prev_locale, cur_locale, 39);
    prev_locale[39] = '\0';

#ifdef HAS_NL_LANGINFO
    /* Zero out the month names; they'll be filled in as needed */
    for (i=0; i<13; i++)
        Month_Name[i] = Mon_Name[i] = "";
    for (i=0; i<7; i++)
        Weekday_Name[i] = Day_Name[i] = "";
#endif
}

#ifdef HAS_NL_LANGINFO
// Delay populating the names until we actually need one of them.
char *Get_Month_Name(int m)
{
    if (! Month_Name[m][0])
        Month_Name[m] = nl_langinfo(NL_MONTH_IX[m]);
    return Month_Name[m];
}

char *Get_Mon_Name(int m)
{
    if (! Mon_Name[m][0])
        Mon_Name[m] = nl_langinfo(NL_MON_IX[m]);
    return Mon_Name[m];
}
char *Get_Weekday_Name(int w)
{
    if (! Weekday_Name[w][0])
        Weekday_Name[w] = nl_langinfo(NL_WKDAY_IX[w]);
    return Weekday_Name[w];
}
char *Get_Day_Name(int w)
{
    if (! Day_Name[w][0])
        Day_Name[w] = nl_langinfo(NL_DAY_IX[w]);
    return Day_Name[w];
}
#else
// No NL_LANGINFO; use english names
#define Get_Month_Name(m)    ENGLISH_MONTH_NAME[m]
#define Get_Mon_Name(m)      ENGLISH_MON_NAME[m]
#define Get_Weekday_Name(m)  ENGLISH_WKD_NAME[m]
#define Get_Day_Name(m)      ENGLISH_DAY_NAME[m]
#endif


int is_leap (int year)
{
    return !(year%4) && ( (year%100) || !(year%400) );
}
int days_in (int month, int year)
{
    switch (month)
    {
    case 1: case 3: case 5: case 7: case 8: case 10: case 12:
        return 31;
    case 4: case 6: case 9: case 11:
        return 30;
    case 2:
        return is_leap(year)? 29 : 28;
    default:
        croak (TF_INTERNAL "invalid call to days_in");
    }
}
int dow (int yr, int mo, int dy)
{
    int dow;
    if (mo < 3)
    {
        mo += 12;
        --yr;
    }

    /* Zeller's congruence - or at least, some form of it. */
    dow = dy + (13 * mo - 27) / 5 + yr + yr/4 - yr/100 + yr/400;
    while (dow < 0) dow += 7;
    dow %= 7;
    return dow;
}


#define RESET_UCLC       self->ucnext = self->lcnext = 0
#define OUTPUSH(c)       *(self->outptr)++ = (c)
#define OUT_FIRST_UPPER(c)     *(self->outptr)++ = (self->lcnext||(self->lower&&!self->ucnext))? tolower(c) : toupper(c)
#define OUT_FIRST_LOWER(c)     *(self->outptr)++ = (self->ucnext||(self->upper&&!self->lcnext))? toupper(c) : tolower(c)
#define OUT_FIRST_MIXED(c)     *(self->outptr)++ = self->ucnext? toupper(c) : self->lcnext? tolower(c) : self->upper? toupper(c) : self->lower? tolower(c) : (c)
#define OUT_REST_UPPER(c)      *(self->outptr)++ = self->lower? tolower(c) : toupper(c)
#define OUT_REST_LOWER(c)      *(self->outptr)++ = self->upper? toupper(c) : tolower(c)
#define OUT_REST_MIXED(c)      *(self->outptr)++ = self->upper? toupper(c) : self->lower? tolower(c) : (c)

static int pack_02d (char *out, int num)
{
    int t = num / 10;    /* tens position */
    *out++ = t + '0';
    num -= t*10;
    *out = num + '0';
    return 2;
}
static int pack_2d (char *out, int num)
{
    int t = num/10;
    if (t)
    {
        *out++ = t + '0';
        num -= t*10;
    }
    else
        *out++ = ' ';
    *out = num + '0';
    return 2;
}
static int pack_d (char *out, int num)
{
    int t = num/10;
    int rv = 1;
    if (t)
    {
        rv++;
        *out++ = t + '0';
        num -= t*10;
    }
    *out = num + '0';
    return rv;
}

static void standard_x (state self, int num)
{
    if (self->modifying)
        self->outptr += pack_d (self->outptr, num);
    else
        self->length += num>9? 2 : 1;
    self->fmt += 1;
    RESET_UCLC;
}
static void standard_xx (state self, int num)
{
    if (self->modifying)
        self->outptr += pack_02d (self->outptr, num);
    else
        self->length += 2;
    self->fmt += 2;
    RESET_UCLC;
}
static void standard__x (state self, int num)   /* ?x */
{
    if (self->modifying)
        self->outptr += pack_2d (self->outptr, num);
    else
        self->length += 2;
    self->fmt += 2;
    RESET_UCLC;
}


static void yyyy (state self)
{
    if (self->modifying)
    {
        int c = self->year/100;
        int y = self->year%100;
        self->outptr += pack_02d(self->outptr, c);
        self->outptr += pack_02d(self->outptr, y);
    }
    else
        self->length += 4;
    self->fmt += 4;
    RESET_UCLC;
}
#define yy(self) standard_xx(self, self->year%100)

#define mm_on_(self) do{standard_xx(self, self->month); self->fmt += 4;} while(0)
#define  m_on_(self) do{standard_x (self, self->month); self->fmt += 4;} while(0)
#define _m_on_(self) do{standard__x(self, self->month); self->fmt += 4;} while(0)

#define dd(self) standard_xx (self, self->day)
#define  d(self) standard_x  (self, self->day)
#define _d(self) standard__x (self, self->day)

#define hh(self) standard_xx (self, self->hour)
#define  h(self) standard_x  (self, self->hour)
#define _h(self) standard__x (self, self->hour)

static void get_h12(state self)
{
    if (self->h12) return;
    self->h12 = self->hour % 12;
    if (self->h12 == 0) self->h12 = 12;
    self->am = self->hour<12? 'a' : 'p';
}
#define HH(self)  do{get_h12(self); standard_xx(self, self->h12); } while(0)
#define  H(self)  do{get_h12(self); standard_x (self, self->h12); } while(0)
#define _H(self)  do{get_h12(self); standard__x(self, self->h12); } while(0)

#define mm_in_(self) do{standard_xx(self, self->min); self->fmt += 4;} while(0)
#define  m_in_(self) do{standard_x (self, self->min); self->fmt += 4;} while(0)
#define _m_in_(self) do{standard__x(self, self->min); self->fmt += 4;} while(0)

#define ss(self) standard_xx (self, self->sec)
#define  s(self) standard_x  (self, self->sec)
#define _s(self) standard__x (self, self->sec)

static void mmm (state self)
{
    self->fmt += 3;
    if (!self->modifying)
    {
        self->length += 3;
        return;
    }
    RESET_UCLC;
    if (self->milli == 0)
    {
        OUTPUSH('0');
        OUTPUSH('0');
        OUTPUSH('0');
    }
    else
    {
        int h  = self->milli / 100;
        int to = self->milli % 100;
        OUTPUSH(h + '0');
        self->outptr += pack_02d (self->outptr, to);
    }
}

static void uuuuuu (state self)
{
    self->fmt += 6;
    if (!self->modifying)
    {
        self->length += 6;
        return;
    }
    RESET_UCLC;
    if (self->micro == 0)
    {
        OUTPUSH('0');
        OUTPUSH('0');
        OUTPUSH('0');
        OUTPUSH('0');
        OUTPUSH('0');
        OUTPUSH('0');
    }
    else
    {
        int u  = self->micro/100;
        int u3 = self->micro % 100;
        int u2 = u % 100;
        int u1 = u / 100;
        self->outptr += pack_02d (self->outptr, u1);
        self->outptr += pack_02d (self->outptr, u2);
        self->outptr += pack_02d (self->outptr, u3);
    }
}

/* Ambiguous mm, ?m, m codes */
static void mm (state self)
{
    if (!self->modifying)
    {
        self->length += 2;
        self->fmt    += 2;
        return;
    }
    if (month_context (self, 2))
        return standard_xx(self, self->month);

    if (minute_context(self, 2))
        return standard_xx(self, self->min);

    OUT_FIRST_LOWER('m');
    OUT_REST_LOWER('m');
    self->fmt += 2;
    RESET_UCLC;
}
static void m (state self)
{
    if (month_context (self, 2))
    {
        standard_x(self, self->month);
        return;
    }

    if (minute_context(self, 2))
    {
        standard_x(self, self->min);
        return;
    }

    if (!self->modifying)
    {
        self->length += 1;
        self->fmt    += 1;
        return;
    }

    OUT_FIRST_LOWER('m');
    self->fmt += 1;
    RESET_UCLC;
}
static void _m (state self)
{
    if (!self->modifying)
    {
        self->length += 2;
        self->fmt    += 2;
        return;
    }

    if (month_context (self, 2))
    {
        standard__x(self, self->month);
        return;
    }

    if (minute_context(self, 2))
    {
        standard__x(self, self->min);
        return;
    }

    OUTPUSH('?');
    OUT_REST_LOWER('m');
    self->fmt += 2;
    RESET_UCLC;
}

static char *suffix[] = {"th", "st", "nd", "rd"};
static void th (state self)
{
    int ones, tens;
    self->fmt += 2;
    if (!self->modifying)
    {
        self->length += 2;
        return;
    }
    ones = self->day % 10;
    tens = self->day / 10;
    if (tens == 1  ||  ones > 3) ones = 0;

    OUT_FIRST_LOWER(suffix[ones][0]);
    OUT_REST_LOWER (suffix[ones][1]);
    RESET_UCLC;
    return;
}
static void TH (state self)
{
    int ones, tens;
    self->fmt += 2;
    if (!self->modifying)
    {
        self->length += 2;
        return;
    }
    ones = self->day % 10;
    tens = self->day / 10;
    if (tens == 1  ||  ones > 3) ones = 0;

    OUT_FIRST_UPPER(suffix[ones][0]);
    OUT_REST_UPPER (suffix[ones][1]);
    RESET_UCLC;
}

static void am (state self)
{
    self->fmt += 2;
    if (!self->modifying)
    {
        self->length += 2;
        return;
    }
    get_h12(self);
    OUT_FIRST_LOWER(self->am);
    OUT_REST_LOWER('m');
    RESET_UCLC;
}

static void AM (state self)
{
    self->fmt += 2;
    if (!self->modifying)
    {
        self->length += 2;
        return;
    }
    get_h12(self);
    OUT_FIRST_UPPER(self->am);
    OUT_REST_UPPER('M');
    RESET_UCLC;
}

static void a_m_ (state self)
{
    self->fmt += 4;
    if (!self->modifying)
    {
        self->length += 4;
        return;
    }
    get_h12(self);
    OUT_FIRST_LOWER(self->am);
    OUTPUSH('.');
    OUT_REST_LOWER('m');
    OUTPUSH('.');
    RESET_UCLC;
}

static void A_M_ (state self)
{
    self->fmt += 4;
    if (!self->modifying)
    {
        self->length += 4;
        return;
    }
    get_h12(self);
    OUT_FIRST_UPPER(self->am);
    OUTPUSH('.');
    OUT_REST_UPPER('M');
    OUTPUSH('.');
    RESET_UCLC;
}

#define pm(self)   am(self)
#define PM(self)   AM(self)
#define p_m_(self) a_m_(self)
#define P_M_(self) A_M_(self)

static void packstr_mc(state self, int fmtlen, const char *name)
{
    int ch;
    self->fmt += fmtlen;
    if (!self->modifying)
    {
        self->length += strlen(name);
        return;
    }

    OUT_FIRST_MIXED(*name);
    while (*++name)
        OUT_REST_MIXED(*name);
    RESET_UCLC;
}
static void packstr_uc(state self, int fmtlen, const char *name)
{
    int ch;
    self->fmt += fmtlen;
    if (!self->modifying)
    {
        self->length += strlen(name);
        return;
    }

    OUT_FIRST_UPPER(*name);
    while (*++name)
        OUT_REST_UPPER(*name);
    RESET_UCLC;
}
static void packstr_lc(state self, int fmtlen, const char *name)
{
    int ch;
    self->fmt += fmtlen;
    if (!self->modifying)
    {
        self->length += strlen(name);
        return;
    }

    OUT_FIRST_LOWER(*name);
    while (*++name)
        OUT_REST_LOWER(*name);
    RESET_UCLC;
}
static void packstr_mc_limit(state self, int fmtlen, const char *name, size_t limit)
{
    int ch;
    self->fmt += fmtlen;
    if (!limit)  return;   /* output length zero */

    if (!self->modifying)
    {
        self->length += limit;
        return;
    }

    OUT_FIRST_MIXED(*name);
    while (*++name  &&  --limit)
        OUT_REST_MIXED(*name);
    RESET_UCLC;
}

#define Month(self)   do {setup_locale(); packstr_mc(self, 5, Get_Month_Name(self->month));} while(0)
#define MONTH(self)   do {setup_locale(); packstr_uc(self, 5, Get_Month_Name(self->month));} while(0)
#define month(self)   do {setup_locale(); packstr_lc(self, 5, Get_Month_Name(self->month));} while(0)

#define Mon(self)     do {setup_locale(); packstr_mc(self, 3, Get_Mon_Name(self->month));}   while(0)
#define MON(self)     do {setup_locale(); packstr_uc(self, 3, Get_Mon_Name(self->month));}   while(0)
#define mon(self)     do {setup_locale(); packstr_lc(self, 3, Get_Mon_Name(self->month));}   while(0)

#define Weekday(self) do {setup_locale(); packstr_mc(self, 7, Get_Weekday_Name(self->dow));} while(0)
#define WEEKDAY(self) do {setup_locale(); packstr_uc(self, 7, Get_Weekday_Name(self->dow));} while(0)
#define weekday(self) do {setup_locale(); packstr_lc(self, 7, Get_Weekday_Name(self->dow));} while(0)

#define Day(self)     do {setup_locale(); packstr_mc(self, 3, Get_Day_Name(self->dow));}     while(0)
#define DAY(self)     do {setup_locale(); packstr_uc(self, 3, Get_Day_Name(self->dow));}     while(0)
#define day(self)     do {setup_locale(); packstr_lc(self, 3, Get_Day_Name(self->dow));}     while(0)

#if HAVE_TZNAME && !HAVE_DECL_TZNAME
extern char *tzname[2];
#endif
static void tz (state self)
{
    if (strlen(self->tzone) == 0)
    {
        tzset();
        strcpy (self->tzone, tzname[0]);
    }
    packstr_mc(self, 2, self->tzone);
}

static void literal (state self)
{
    if (!self->modifying)
    {
        self->length++;
        self->fmt++;
        return;
    }
    *(self->outptr)++ = *(self->fmt++);
}

#define bs_literal(self) do{self->fmt++; literal(self);} while(0)
#define bs_Q(self) do{self->fmt+=2; self->quoting = 1;} while(0)
#define bs_E(self) do{self->fmt += 2; self->quoting = self->upper = self->lower = self->lcnext = self->ucnext = 0;} while(0)
#define bs_U(self) do{self->fmt += 2; self->upper = 1;} while(0)
#define bs_L(self) do{self->fmt += 2; self->lower = 1;} while(0)
#define bs_u(self) do{self->fmt += 2; self->lcnext = 0; self->ucnext = 1;} while(0)
#define bs_l(self) do{self->fmt += 2; self->ucnext = 0; self->lcnext = 1;} while(0)


/* forward
   Returns true if the beginning of fmt matches the whole of pat.
*/
#define forward(fmt,pat) (!strncmp(fmt, pat, strlen(pat)))

/* backward
   Returns true if fmt ends with pat, and is not preceded by an odd backslash.
   <start> is a pointer to the beginning of fmt, so we know not to go back too far.
*/
static int backward(const char *start, const char *fmt, const char *pat)
{
    size_t patlen = strlen(pat);
    int bs = 1;
    if (fmt - start < patlen)  return 0;
    fmt -= patlen;
    if (strncmp(fmt, pat, patlen))   return 0;

    /* we have a match; check that it's not preceded by an odd number of backslashes */
    while (fmt >= start  &&  *fmt-- == '\\')
        bs = !bs;
    return bs;
}

/* bool = month_context(start, fmt, patlen);
   Returns TRUE if the current format (delimited by fmt and patlen)
   is in a "month" context; that is, it is followed or preceeded by
   a year or a day.
   We check immediately following and immediately preceeding, and
   also we check one character away in either direction.  So mm/dd
   will work (because there's one character in between).
*/
int month_context(state self, size_t patlen)
{
    const char *backskip = self->fmt-2;
    const char *fwdskip  = self->fmt + patlen + 1;
    if (*backskip != '\\') backskip++;
    if (*fwdskip == '\\') fwdskip++;

    return  forward(self->fmt+patlen, "?d")
        ||  forward(self->fmt+patlen , "d")
        ||  forward(fwdskip,          "?d")
        ||  forward(fwdskip,           "d")
        ||  forward(self->fmt+patlen, "yy")
        ||  forward(fwdskip,          "yy")
        ||  backward(self->start, self->fmt, "yy")
        ||  backward(self->start, backskip,  "yy")
        ||  backward(self->start, self->fmt,  "d")
        ||  backward(self->start, backskip,   "d");
}

/* bool = minute_context(start, fmt, patlen);
   Returns TRUE if the current format is in a "minute" context.
   That is, if it's preceeded by an hour and/or followed by a second.
*/
int minute_context(state self, size_t patlen)
{
    const char *backskip = self->fmt-1;
    const char *fwdskip  = self->fmt+patlen+1;
    if (*backskip == '\\') backskip--;
    if (*fwdskip == '\\')  fwdskip++;

    return  forward(self->fmt+patlen, "?s")
        ||  forward(self->fmt+patlen,  "s")
        ||  forward(fwdskip,    "?s")
        ||  forward(fwdskip,     "s")
        ||  backward(self->start, self->fmt, "h")
        ||  backward(self->start, backskip,  "h")
        ||  backward(self->start, self->fmt, "H")
        ||  backward(self->start, backskip,  "H");
}

int get_2_digits(const char *str)
{
    if (isDIGIT(str[0])  &&  isDIGIT(str[1]))
        return 10 * (str[0] - '0') + (str[1] - '0');
    return -1;
}
int get_4_digits(const char *str)
{
    if (isDIGIT(str[0])  &&  isDIGIT(str[1])  &&  isDIGIT(str[2])  &&  isDIGIT(str[3]))
        return 100 * get_2_digits(str) + get_2_digits(str + 2);
    return -1;
}
int is_date_sep(char ch)
{
    return (ch == '-' || ch == '/' || ch == '.');
}
int is_time_sep(char ch)
{
    return (ch == ':' || ch == '.');
}
int is_datetime_sep(char ch)
{
    return (ch == '_' || ch == 'T' || ch == ' ');
}

/* Calls a DateTime method that takes NO arguments and returns ONE integer. */
int _datetime_method_int (SV *dt_obj, const char *method)
{
    dSP;
    int retval, retval_count;

    ENTER;
    SAVETMPS;

    PUSHMARK(SP);
    XPUSHs(dt_obj);  /* object */
    PUTBACK;
    retval_count = call_method(method, G_SCALAR);   /* $datetime->$method */
    SPAGAIN;
    if (retval_count != 1)
    {
        char msg[99];
        sprintf(msg, TF_INTERNAL "confusion in DateTime->%s method call, retval_count=%d", method, retval_count);
        croak (msg);
    }
    retval = POPi;

    FREETMPS;
    LEAVE;

    return retval;
}

/* Calls a DateTime method that takes NO arguments and returns ONE string. */
char * _datetime_method_str (SV *dt_obj, const char *method)
{
    dSP;
    STRLEN n_a;
    int retval_count;
    char *retval;

    ENTER;
    SAVETMPS;

    PUSHMARK(SP);
    XPUSHs(dt_obj);  /* object */
    PUTBACK;
    retval_count = call_method(method, G_SCALAR);   /* $datetime->$method */
    SPAGAIN;
    if (retval_count != 1)
    {
        char msg[99];
        sprintf(msg, TF_INTERNAL "confusion in DateTime->%s method call, retval_count=%d", method, retval_count);
        croak (msg);
    }
    retval = POPpx;

    FREETMPS;
    LEAVE;

    return retval;
}

/* Returns true if data successfully parsed. */
int parse_datetime_obj  (SV *time_value, state st)
{
    int retval_count;
    char *cptr;

    BUG((stderr, "parse_datetime_obj: starts!\n"));

    /* This routine applies only to DateTime objects. */
    unless (SvROK(time_value)  &&  sv_derived_from(time_value, "DateTime"))
        return 0;

    /* Basic date/time elements */
    st->year  = _datetime_method_int(time_value, "year");
    st->month = _datetime_method_int(time_value, "month");
    st->day   = _datetime_method_int(time_value, "day");
    st->hour  = _datetime_method_int(time_value, "hour");
    st->min   = _datetime_method_int(time_value, "minute");
    st->sec   = _datetime_method_int(time_value, "second");
    st->dow   = _datetime_method_int(time_value, "day_of_week");
    cptr = _datetime_method_str(time_value, "time_zone_short_name");;
    strncpy (st->tzone, cptr, 60);
    st->tzone[59] = '\0';

    // Setting h12 to zero means "calculate on demand" */
    st->h12   = 0;

    /* microseconds and milliseconds */
    st->micro = _datetime_method_int(time_value, "microsecond");
    st->milli = (int) (st->micro / 1000);

    return 1;
}


void _validate_date(int yr, int mo, int dy)
{
    char msg[99];

    if (mo < 1  ||  mo > 12)
    {
        sprintf(msg, "Invalid month \"%02d\" in iso8601 string", mo);
        c_croak(msg);
    }

    if (dy < 1  ||  dy > 31)
    {
        sprintf(msg, "Invalid day \"%02d\" in iso8601 string", dy);
        c_croak(msg);
    }

    if (dy > days_in(mo, yr))
    {
        if (dy == 29  &&  mo == 2)
            sprintf(msg, "Invalid day \"29\" for 02/%04d in iso8601 string", yr);
        else
            sprintf(msg, "Invalid day \"%02d\" for month %02d in iso8601 string", dy, mo);
        c_croak(msg);
    }
}

void _validate_time(int hr, int mn, int sc)
{
    char msg[99];

    if (hr > 23)
    {
        sprintf(msg, "Invalid hour \"%02d\" in iso8601 string", hr);
        c_croak(msg);
    }

    if (mn > 59)
    {
        sprintf(msg, "Invalid minute \"%02d\" in iso8601 string", mn);
        c_croak(msg);
    }

    if (sc > 61)
    {
        sprintf(msg, "Invalid second \"%02d\" in iso8601 string", sc);
        c_croak(msg);
    }
}

/* Datetime string: YYYY-MM-DD HH:MM:SS */
/* ISO 8601, and DateTime/Date::Manip stringification */
int parse_iso8601_str(SV *timeval, state st)
{
    STRLEN len;
    char *str;
    char sep;
    int got_date=0;

    str = SvPV(timeval, len);
    if (NULL == str)
        return 0;

    /* Year */
    if ((st->year = get_4_digits(str)) >= 0)
    {
        str += 4;

        /* Date separator */
        if (is_date_sep(sep = *str))
            ++str;
        else
            sep = '\0';

        /* Month */
        if ((st->month = get_2_digits(str)) < 0)
            return 0;
        str += 2;
        BUG ((stderr, "_is_iso: month= [%02d]\n", st->month));

        /* Date separator. Should match previous one. */
        if (sep)
            if (sep == *str)
                ++str;
            else
                return 0;

        /* Day */
        if ((st->day = get_2_digits(str)) < 0)
            return 0;
        str += 2;
        BUG ((stderr, "_is_iso: day= [%02d]\n", st->day));

        st->dow = dow(st->year, st->month, st->day);

        /* If there was a date separator, it's okay for the string (date-only) to end here */
        if (sep)
            if (*str == '\0')
            {
                _validate_date(st->year, st->month, st->day);
                st->hour = st->min = st->sec = st->h12 = st->milli = st->micro = 0;
                BUG ((stderr, "_is_iso: Success!  date-only.\n"));
                return 1;
            }

        got_date = 1;

        /* Date-Time separator */
        if (is_datetime_sep(*str))
            ++str;
    }
    else   /* No date. */
    {
        st->year  = 1969;
        st->month =   12;
        st->day   =   31;
        st->dow   =    3;
    }

    /* Hour */
    if ((st->hour = get_2_digits(str)) < 0)
        return 0;
    str += 2;
    st->h12 = 0;    /* means: compute it later */
    BUG ((stderr, "_is_iso: hour= [%02d]\n", st->hour));

    /* MUST have a separator if this is a time-only string */
    if (is_time_sep(sep = *str))
        str++;
    else if (!got_date)
        return 0;
    else
        sep = '\0';

    /* Minute */
    if ((st->min = get_2_digits(str)) < 0)
        return 0;
    str += 2;

    /* Separator must match earlier */
    if (sep)
        if (sep == *str)
            ++str;
        else
            return 0;

    /* second */
    if ((st->sec = get_2_digits(str)) < 0)
        return 0;
    str += 2;

    /* fractional part is optional. */
    if (*str  &&  *str == '.'  &&  isDIGIT(str[1]))
    {
        int micro = 0;
        int ndig  = 0;
        ++str;

        while (isDIGIT(*str) && ndig++ < 6)
            micro = micro * 10  +  *str-'0';
        while (ndig++ < 6)
            micro *= 10;
        while (isDIGIT(*str))
            ++str;

        st->micro = micro;
        st->milli = micro / 1000;
    }
    else
        st->milli = st->micro = 0;

    /* Schmutz after the time */
    if (*str)
        return 0;

    _validate_date(st->year, st->month, st->day);
    _validate_time(st->hour, st->min  , st->sec);
    BUG((stderr, "_is_iso: success!\n"));
    return 1;
}

int parse_time_num    (SV *time_value, state st)
{
    STRLEN len=0;
    char *str;
    time_t epoch = 0;
    struct tm *tmstruct;

    BUG((stderr, "parse_time_num: starts!\n"));

    /* We should have been passed a numeric value */
    str = SvPV(time_value, len);
    if (NULL == str)
        return 0;

    /* Get integer portion */
    while (isDIGIT(*str))
        epoch = 10 * epoch + *str++ - '0';

    /* get fractional part, if any */
    if (*str == '.')
    {
        int micro = 0;
        int ndig  = 0;
        ++str;

        while (isDIGIT(*str) && ndig++ < 6)
            micro = micro * 10  +  *str++ - '0';
        while (ndig++ < 6)
            micro *= 10;
        while (isDIGIT(*str))
            ++str;

        st->micro = micro;
        st->milli = micro / 1000;
    }
    else
        st->milli = st->micro = 0;

    /* Any schmutz after the time? */
    if (*str)
        return 0;

    tmstruct  =  localtime(&epoch);
    st->year  = tmstruct->tm_year + 1900;
    st->month = tmstruct->tm_mon + 1;
    st->day   = tmstruct->tm_mday;
    st->hour  = tmstruct->tm_hour;
    st->min   = tmstruct->tm_min;
    st->sec   = tmstruct->tm_sec;
    st->dow   = tmstruct->tm_wday;
    st->h12   = 0;    /* Compute later */
    st->tzone[0] = '\0';

    return 1;
}

/* This function exists in case the clueless user typed "time", which
   isn't too hard to do as the second argument to the tied hash */
int parse_time_literal (SV *time_value, state st)
{
    STRLEN len=0;
    char *str;
    time_t epoch;
    struct tm *tmstruct;

    str = SvPV(time_value, len);
    if (NULL == str)
        return 0;

    if (strcmp(str, "time"))
        return 0;

    epoch = time(NULL);
    tmstruct  =  localtime(&epoch);
    st->year  = tmstruct->tm_year + 1900;
    st->month = tmstruct->tm_mon + 1;
    st->day   = tmstruct->tm_mday;
    st->hour  = tmstruct->tm_hour;
    st->min   = tmstruct->tm_min;
    st->sec   = tmstruct->tm_sec;
    st->dow   = tmstruct->tm_wday;
    st->h12   = 0;    /* Compute later */
    st->tzone[0] = '\0';

    return 1;
}


void in_parse (SV *in_time, state time_state)
{
    /* in_time may be:
       A time value (floating-point or integer)
       A DateTime object
       A stringified DateTime
       A Date::Manip string
       An ISO-8601 date, time, or datetime string.

       time_state is assumed to already be allocated.
    */

    if (! (
           parse_datetime_obj(in_time, time_state)
        || parse_iso8601_str (in_time, time_state)
        || parse_time_num    (in_time, time_state)
        || parse_time_literal(in_time, time_state)
           ))
    {
        char msg[99];
        char *in_str;
        STRLEN len;

        in_str = SvPV(in_time, len);
        if (NULL == in_str)
            sprintf(msg, "Can't understand time value");
        else
            sprintf(msg, "Can't understand time value \"%.50s\"", in_str);
        c_croak(msg);
    }
}

#define THISCHAR      (st->fmt[0])
#define NEXTCHAR      (st->fmt[1])
#define CHARPLUS2     (st->fmt[2])
#define FORMATCODE(f) (forward(st->fmt, (f)))

/* time_format
   Given a format, and a string that represents a time number, returns a malloc'd output string.
   The time input is a string, because it could be ten digits plus six or more decimals, which
   exceeds the precision of most double-precision floats.  So we parse it into a long and a double.
   See the documentation for the Time::Format module on what formats are expanded.
*/
char *time_format(const char *fmt, SV *in_time)
{
    struct state_struct mystate;
    state st = &mystate;
    BUG ((stderr, "time_format: begins\n"));

    /* Parse the in_time parameter into the state structure */
    in_parse(in_time, st);

    BUG((stderr, "tf: st->year  = %d\n", st->year));
    BUG((stderr, "tf: st->month = %d\n", st->month));
    BUG((stderr, "tf: st->day   = %d\n", st->day));
    BUG((stderr, "tf: st->hour  = %d\n", st->hour));
    BUG((stderr, "tf: st->min   = %d\n", st->min));
    BUG((stderr, "tf: st->sec   = %d\n", st->sec));
    BUG((stderr, "tf: st->dow   = %d\n", st->dow));
    BUG((stderr, "tf: st->milli = %d\n", st->milli));
    BUG((stderr, "tf: st->micro = %d\n", st->micro));
    BUG((stderr, "tf: st->h12   = %d\n", st->h12));
    BUG((stderr, "tf: st->tzone = [%s]\n", st->tzone));

    /* other intialization */
    st->length = 0;
    st->fmt    = st->start = fmt;
    st->out = st->outptr = NULL;

    /* First, compute length of result string.  Then actually populate it. */
    for (st->modifying=0; st->modifying<=1; st->modifying++)
    {
        st->quoting = st->upper = st->lower = st->ucnext = st->lcnext = 0;

        while (THISCHAR)
        {
            char *jump;

            if (st->quoting)
                jump = strstr(st->fmt, "\\E");    /* look for end of literal-quote */
            else
                jump = strpbrk(st->fmt, "\\dDy?hHsaApPMmWwutT");  /* jump to one of these */

            if (NULL == jump)
            {
                packstr_mc (st, strlen(st->fmt), st->fmt);
                break;
            }
            else if (jump > st->fmt)    /* skip over the section that does not contain codes */
            {
                packstr_mc_limit (st, jump - st->fmt, st->fmt, jump - st->fmt);
            }

            switch (THISCHAR)
            {
            case '\\':        /* escape character */
                switch (NEXTCHAR)
                {
                case 'Q':  bs_Q(st);    break;
                case 'E':  bs_E(st);    break;
                case 'U':  bs_U(st);    break;
                case 'L':  bs_L(st);    break;
                case 'u':  bs_u(st);    break;
                case 'l':  bs_l(st);    break;
                default :  bs_literal(st); break;
                }
                break;

            case 'd':        /* dd, day, d */

                if      (NEXTCHAR == 'd')    dd(st);
                else if (FORMATCODE("day"))  day(st);
                else                         d(st);
                break;

            case 'y':        /* yyyy, yy */

                if      (FORMATCODE("yyyy"))  yyyy(st);
                else if (FORMATCODE("yy"))    yy(st);
                else                          literal(st);
                break;

            case 'h':        /* hh, h */

                if (NEXTCHAR == 'h')  hh(st);
                else                  h(st);
                break;

            case 'H':        /* HH, H */

                if (NEXTCHAR == 'H')  HH(st);
                else                   H(st);
                break;

            case 's':        /* ss, s */

                if (NEXTCHAR == 's')  ss(st);
                else                  s(st);
                break;

            case 'm':        /* month, mon, mm{on}, m{on}, mm{in}, m{in}, mmm, mm, m */

                if      (FORMATCODE("month"))   month(st);
                else if (FORMATCODE("mon"))     mon(st);
                else if (FORMATCODE("mm{on}"))  mm_on_(st);
                else if (FORMATCODE("m{on}"))   m_on_(st);
                else if (FORMATCODE("mm{in}"))  mm_in_(st);
                else if (FORMATCODE("m{in}"))   m_in_(st);
                else if (FORMATCODE("mmm"))     mmm(st);
                else if (NEXTCHAR == 'm')       mm(st);
                else                            m(st);
                break;

            case 'M':        /* Month, MONTH, Mon, MON */

                if      (FORMATCODE("Month"))  Month(st);
                else if (FORMATCODE("MONTH"))  MONTH(st);
                else if (FORMATCODE("Mon"))    Mon(st);
                else if (FORMATCODE("MON"))    MON(st);
                else                           literal(st);
                break;

            case 'W':        /* Weekday, WEEKDAY */

                if      (FORMATCODE("Weekday"))  Weekday(st);
                else if (FORMATCODE("WEEKDAY"))  WEEKDAY(st);
                else                             literal(st);
                break;

            case 'w':        /* weekday */

                if      (FORMATCODE("weekday"))   weekday(st);
                else                              literal(st);
                break;

            case 'D':        /* Day, DAY */

                if      (FORMATCODE("Day"))  Day(st);
                else if (FORMATCODE("DAY"))  DAY(st);
                else                         literal(st);
                break;

            case 'a':        /* am, a.m. */

                if      (FORMATCODE("am"))    am(st);
                else if (FORMATCODE("a.m."))  a_m_(st);
                else                          literal(st);
                break;

            case 'p':        /* pm, p.m. */

                if      (FORMATCODE("pm"))    pm(st);
                else if (FORMATCODE("p.m."))  p_m_(st);
                else                          literal(st);
                break;

            case 'A':        /* AM, A.M. */

                if      (FORMATCODE("AM"))    AM(st);
                else if (FORMATCODE("A.M."))  A_M_(st);
                else                          literal(st);
                break;

            case 'P':        /* PM, P.M. */

                if      (FORMATCODE("PM"))    PM(st);
                else if (FORMATCODE("P.M."))  P_M_(st);
                else                          literal(st);
                break;

            case '?':        /* ?d, ?h, ?H, ?s, ?m{on}, ?m{in}, ?m */

                switch (NEXTCHAR)
                {
                case 'd':  _d(st);  break;
                case 'h':  _h(st);  break;
                case 'H':  _H(st);  break;
                case 's':  _s(st);  break;
                case 'm':
                    if      (FORMATCODE("?m{on}"))  _m_on_(st);
                    else if (FORMATCODE("?m{in}"))  _m_in_(st);
                    else                            _m(st);
                    break;

                default:  literal(st);   /* just a question mark */
                }
                break;

            case 'u':        /* uuuuuu (microseconds) */

                if (FORMATCODE("uuuuuu"))  uuuuuu(st);
                else
                    literal(st);
                break;

            case 't':        /* th, tz */
                if      (NEXTCHAR == 'h')  th(st);
                else if (NEXTCHAR == 'z')  tz(st);
                else                       literal(st);
                break;

            case 'T':        /* TH */

                if      (NEXTCHAR == 'H')  TH(st);
                else                       literal(st);
                break;

            default:
                literal(st);
                break;
            }

        }
        if (st->modifying)
            *(st->outptr) = '\0';
        else
        {
            st->out = st->outptr = malloc(st->length+1);
            if (NULL == st->out) return st->out;  /* Yikes */
            st->fmt = st->start;    /* Start over! */
        }
    }

    return st->out;
}

/*
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)

iEYEARECAAYFAko671oACgkQwoSYc5qQVqrGTgCfbXD94tpfPSRMv7KLmnx4eDWe
ZXkAoJiyBxkQ0sLJ7/NOYMCdjW6wfT88
=lQyf
-----END PGP SIGNATURE-----
*/