Subject Re: [IB-Architect] SDF Dates
Author Ann Harrison
At 12:18 PM 7/9/2000 -0400, Jim Starkey wrote:
>At 06:14 AM 7/9/00 +0800, you wrote:
> >Interbase does not recognize the SDF representation for dates which is
> >YYYYMMDD.
>
>Piece of cake once the source is opened.
static void string_to_datetime (
DSC *desc,
GDS_TIMESTAMP *date,
EXPECT_DATETIME expect_type,
FPTR_VOID err)
{
/**************************************
*
* s t r i n g _ t o _ d a t e t i m e
*
**************************************
*
* Functional description
* Convert an arbitrary string to a date and/or time.
*
* String must be formed using ASCII characters only.
* Conversion routine can handle the following input formats
* "now" current date & time
* "today" Today's date 0:0:0.0 time
* "tomorrow" Tomorrow's date 0:0:0.0 time
* "yesterday" Yesterday's date 0:0:0.0 time
* YYYY-MM-DD [HH:[Min:[SS.[Thou]]]]]
* MM:DD[:YY [HH:[Min:[SS.[Thou]]]]]
* DD.MM[:YY [HH:[Min:[SS.[Thou]]]]]
* Where:
* DD = 1 .. 31 (Day of month)
* YY = 00 .. 99 2-digit years are converted to the nearest year
* in a 50 year range. Eg: if this is 1996
* 96 ==> 1996
* 97 ==> 1997
* ...
* 00 ==> 2000
* 01 ==> 2001
* ...
* 44 ==> 2044
* 45 ==> 2045
* 46 ==> 1946
* 47 ==> 1947
* ...
* 95 ==> 1995
* If the current year is 1997, then 46 is converted
* to 2046 (etc).
* = 100.. 5200 (Year)
* MM = 1 .. 12 (Month of year)
* = "JANUARY"... (etc)
* HH = 0 .. 23 (Hour of day)
* Min = 0 .. 59 (Minute of hour)
* SS = 0 .. 59 (Second of minute - LEAP second not supported)
* Thou = 0 .. 9999 (Fraction of second)
* HH, Min, SS, Thou default to 0 if missing.
* YY defaults to current year if missing.
* Note: ANY punctuation can be used instead of : (eg: / - etc)
* Using . (period) in either of the first two separation
* points will cause the date to be parsed in European DMY
* format.
* Arbitrary whitespace (space or TAB) can occur between
* components.
*
**************************************/
TEXT *string, c, *p, *end;
USHORT length, n, i, components [7];
USHORT start_component;
SSHORT description [7];
/* Values inside of description
> 0 is number of digits
0 means missing
ENGLISH_MONTH for the presence of an English month name
SPECIAL for a special date verb */
#define ENGLISH_MONTH -1
#define SPECIAL -2
USHORT position_year = 0;
USHORT position_month = 1;
USHORT position_day = 2;
BOOLEAN have_english_month = FALSE;
BOOLEAN dot_separator_seen = FALSE;
SLONG clock;
struct tm times, times2;
TEXT buffer [100]; /* arbitrarily large */

length = CVT_make_string (desc, ttype_ascii, &string, buffer, sizeof
(buffer), err);
p = string;

end = p + length;

memset (components, 0, sizeof (components));
memset (description, 0, sizeof (description));

/* Parse components */
/* The 7 components are Year, Month, Day, Hours, Minutes, Seconds, Thou */
/* The first 3 can be in any order */

start_component = (expect_type == expect_sql_time) ? 3 : 0;
for (i = start_component; i < 7; i++)
{

/* Skip leading blanks. If we run out of characters, we're done
with parse. */

while (p < end && (*p == ' ' || *p == '\t'))
p++;
if (p == end)
break;

/* Handle digit or character strings */

c = UPPER7 (*p);
if (DIGIT (c))
{
USHORT precision = 0;
n = 0;
while (p < end && DIGIT (*p))
{
n = n * 10 + *p++ - '0';
precision++;
}
description [i] = precision;
}
else if (LETTER7 (c) && !have_english_month)
{
TEXT temp [sizeof(YESTERDAY)+1], *t;
CONST TEXT * CONST *month_ptr;
CONST TEXT *m;

t = temp;
while ((p < end) && (t < &temp[sizeof(temp)-1]))
{
c = UPPER7 (*p);
if (!LETTER7 (c))
break;
*t++ = c;
p++;
}
*t = 0;

/* Insist on at least 3 characters for month names */
if (t - temp < 3)
{
conversion_error (desc, err);
return;
}

month_ptr = months;
while (TRUE)
{
/* Month names are only allowed in first 2 positions */
if (*month_ptr && i < 2)
{
for (t = temp, m = *month_ptr++; *t && *t == *m; t++, m++)
;
if (!*t)
break;
}
else
{
/* it's not a month name, so it's either a magic word or
a non-date string. If there are more characters, it's
bad */

description [i] = SPECIAL;

while (++p < end)
if (*p != ' ' && *p != '\t' && *p != 0)
conversion_error (desc, err);

/* fetch the current time */

clock = time (NULL_PTR);
times2 = *localtime (&clock);

if (strcmp (temp, NOW) == 0)
{
isc_encode_timestamp (&times2, date);
return;
}
if (expect_type == expect_sql_time)
{
conversion_error (desc, err);
return;
}
times2.tm_hour = times2.tm_min = times2.tm_sec = 0;
isc_encode_timestamp (&times2, date);
if (strcmp (temp, TODAY) == 0)
return;
if (strcmp (temp, TOMORROW) == 0)
{
date->timestamp_date++;
return;
}
if (strcmp (temp, YESTERDAY) == 0)
{
date->timestamp_date--;
return;
}
conversion_error (desc, err);
return;
}
}
n = month_ptr - months;
position_month = i;
description [i] = ENGLISH_MONTH;
have_english_month++;
}
else /* Not a digit and not a letter - must be punctuation */
{
conversion_error (desc, err);
return;
}

components [i] = n;

/* Grab whitespace following the number */
while (p < end && (*p == ' ' || *p == '\t'))
p++;

if (p == end)
break;

/* Grab a separator character */
if (*p == '/' || *p == '-' || *p == ',' || *p == ':')
{
p++;
continue;
}
if (*p == '.')
{
if (i <= 1)
dot_separator_seen++;
p++;
continue;
}
}

/* User must provide at least 2 components */
if (i-start_component < 1)
{
conversion_error (desc, err);
return;
}

/* Dates cannot have a Time portion */
if (expect_type == expect_sql_date && i > 2)
{
conversion_error (desc, err);
return;
}

memset (×, 0, sizeof (times));

if (expect_type != expect_sql_time)
{
/* Figure out what format the user typed the date in */

/* A 4 digit number to start implies YYYY-MM-DD */
if (description [0] >= 3)
{
position_year = 0;
position_month = 1;
position_day = 2;
}

/* An English month to start implies MM-DD-YYYY */
else if (description [0] == ENGLISH_MONTH)
{
position_year = 2;
position_month = 0;
position_day = 1;
}

/* An English month in the middle implies DD-MM-YYYY */
else if (description [1] == ENGLISH_MONTH)
{
position_year = 2;
position_month = 1;
position_day = 0;
}

/* A period as a separator implies DD.MM.YYYY */
else if (dot_separator_seen)
{
position_year = 2;
position_month = 1;
position_day = 0;
}

/* Otherwise assume MM-DD-YYYY */
else
{
position_year = 2;
position_month = 0;
position_day = 1;
}

/* Forbid years with more than 4 digits */
/* Forbid months or days with more than 2 digits */
/* Forbid months or days being missing */
if (description [position_year] > 4 ||
description [position_month] > 2 || description [position_month]
== 0 ||
description [position_day] > 2 || description [position_day] <= 0)
{
conversion_error (desc, err);
return;
}

/* Slide things into day, month, year form */

times.tm_year = components [position_year];
times.tm_mon = components [position_month];
times.tm_mday = components [position_day];

/* Handle defaulting of year */

if (description [position_year] == 0)
{
clock = time (NULL_PTR);
times2 = *localtime (&clock);
times.tm_year = times2.tm_year + 1900;
}

/* Handle conversion of 2-digit years */

else if (description [position_year] <= 2)
{
clock = time (NULL_PTR);
times2 = *localtime (&clock);
if (times.tm_year < (times2.tm_year - 50) % 100)
times.tm_year += 2000;
else
times.tm_year += 1900;
}

times.tm_year -= 1900;
times.tm_mon -= 1;
}
else
{
/* The date portion isn't needed for time - but to
keep the conversion in/out of isc_time clean lets
intialize it properly anyway */
times.tm_year = 0;
times.tm_mon = 0;
times.tm_mday = 1;
}

/* Handle time values out of range - note possibility of 60 for
* seconds value due to LEAP second (Not supported in V4.0).
*/
if (((times.tm_hour = components [3]) > 23) ||
((times.tm_min = components [4]) > 59) ||
((times.tm_sec = components [5]) > 59) ||
(description [6] > -ISC_TIME_SECONDS_PRECISION_SCALE))
conversion_error (desc, err);

/* convert day/month/year to Julian and validate result
This catches things like 29-Feb-1995 (not a leap year) */

isc_encode_timestamp (×, date);
if (expect_type != expect_sql_time)
{
isc_decode_timestamp (date, &times2);

if ((times.tm_year+1900) < MIN_YEAR || (times.tm_year)+1900 >
MAX_YEAR)
(*err) (isc_date_range_exceeded, 0);

if (times.tm_year != times2.tm_year ||
times.tm_mon != times2.tm_mon ||
times.tm_mday != times2.tm_mday ||
times.tm_hour != times2.tm_hour ||
times.tm_min != times2.tm_min ||
times.tm_sec != times2.tm_sec)
conversion_error (desc, err);
};

/* Convert fraction of seconds */
while (description [6]++ < -ISC_TIME_SECONDS_PRECISION_SCALE)
components[6] *= 10;

date->timestamp_time += components [6];
}