/*===========================================================================*
* *
* sflcvsd.c - *
* *
* Copyright (c) 1991-2003 iMatix Corporation *
* *
* ------------------ GPL Licensed Source Code ------------------ *
* iMatix makes this software available under the GNU General *
* Public License (GPL) license for open source projects. For *
* details of the GPL license please see www.gnu.org or read the *
* file license.gpl provided in this package. *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public *
* License along with this program in the file 'license.gpl'; if *
* not, write to the Free Software Foundation, Inc., 59 Temple *
* Place - Suite 330, Boston, MA 02111-1307, USA. *
* *
* You can also license this software under iMatix's General Terms *
* of Business (GTB) for commercial projects. If you have not *
* explicitly licensed this software under the iMatix GTB you may *
* only use it under the terms of the GNU General Public License. *
* *
* For more information, send an email to info@imatix.com. *
* -------------------------------------------------------------- *
*===========================================================================*/
#include "prelude.h" /* Universal header file */
#include "sflconv.h" /* Prototypes for functions */
#include "sfldate.h" /* Date library functions */
#include "sflstr.h" /* String functions */
/* ---------------------------------------------------------------------[<]-
Function: conv_str_date
Synopsis: Converts a string to a date. The supposed format of the
date is defined by the format argument, which can be one of:
DATE_YMD_COMPACT Year month day.
DATE_YMD_DELIM Year month day.
DATE_YMD_SPACE Year month day.
DATE_YMD_COMMA Year month day.
DATE_YM_COMPACT Year and month only; day is zero.
DATE_YM_DELIM Year and month only; day is zero.
DATE_YM_SPACE Year and month only; day is zero.
DATE_MD_COMPACT Month and day only; year is zero.
DATE_MD_DELIM Month and day only; year is zero.
DATE_MD_SPACE Month and day only; year is zero.
The date order must be one of:
DATE_ORDER_YMD Year month day.
DATE_ORDER_DMY Day month year.
DATE_ORDER_MDY Month day year.
You can override the order using these flags:
FLAG_D_ORDER_YMD Year month day.
FLAG_D_ORDER_DMY Day month year.
FLAG_D_ORDER_MDY Month day year.
Returns the date as a long integer, YYYYMMDD. The conversion is pretty
relaxed and allows strings like: 010195, 1-1-95, 1-Jan-95, 1jan95,
01jan95, 1 1 95, etc. The input string must be null-terminated.
Returns -1 in case of an invalid date or format. If the short formats
(DATE_DM_..., DATE_YM_...) are used, the missing field is always set
to 0 and cannot be supplied in the string. If the date was empty, i.e.
contains no usable digits, returns 0.
---------------------------------------------------------------------[>]-*/
long
conv_str_date (
const char *string,
int flags,
int format,
int order)
{
static
char *month_name [] =
{
"jan", "feb", "mar", "apr", "may", "jun",
"jul", "aug", "sep", "oct", "nov", "dec"
};
static byte
month_days [] =
{
31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
},
order_ptr [][3] = /* Year, Month, Day pointers */
{ /* for various orders & formats */
/* YY MM DD YYYY MM DD YY MM YYYY MM MM DD */
{ 0, 2, 4 }, { 0, 4, 6 }, { 0, 2, 99 }, { 0, 4, 99 }, { 99, 0, 2 },
{ 4, 2, 0 }, { 4, 2, 0 }, { 2, 0, 99 }, { 2, 0, 99 }, { 99, 2, 0 },
{ 4, 0, 2 }, { 4, 0, 2 }, { 2, 0, 99 }, { 2, 0, 99 }, { 99, 0, 2 },
};
char
date_digits [9], /* 8 digits of date */
month_letters [4] = "???", /* 3 characters of month */
ch; /* Next character in date */
long
feedback; /* Returned date; -1 = error */
int
digits, /* Number of digits in string */
delimiters, /* Number of delimiters in date */
digitseq, /* Number of digits in sequence */
count, /* Number of month letters */
year, /* Date year value */
month, /* Date month value */
day, /* Date day value */
order_index, /* Index into order table */
y_ptr, /* Where is year in date? */
m_ptr, /* Where is month in date? */
d_ptr, /* Where is day in date? */
date_order = order; /* Actual date order */
Bool
had_month; /* Did we already get a month? */
ASSERT (format >= DATE_FORMAT_FIRST && format <= DATE_FORMAT_LAST);
ASSERT (order >= DATE_ORDER_FIRST && order <= DATE_ORDER_LAST);
conv_reason = 0; /* No conversion errors so far */
if (flags & FLAG_D_ORDER_YMD)
date_order = DATE_ORDER_YMD;
else
if (flags & FLAG_D_ORDER_DMY)
date_order = DATE_ORDER_DMY;
else
if (flags & FLAG_D_ORDER_MDY)
date_order = DATE_ORDER_MDY;
/* Collect date digits */
digits = 0; /* Nothing collected so far */
digitseq = 0; /* No digits in sequence */
feedback = 0; /* No errors so far */
delimiters = 0; /* We allow up to 2 delimiters */
had_month = FALSE; /* True after 3-letter month seen */
do
{
ch = *string++;
if (isdigit (ch))
{
if (digits < 8)
{
digitseq++;
date_digits [digits++] = ch;
}
else
{
conv_reason = CONV_ERR_DATE_SIZE;
feedback = -1; /* Too many digits */
}
}
else
{
/* Fill-up to even number of digits */
if (digits > (digits / 2) * 2)
{
date_digits [digits] = date_digits [digits - 1];
date_digits [digits - 1] = '0';
digits++;
}
/* 3 or 5 in a row is not allowed */
if (digitseq == 3 || digitseq == 5)
{
conv_reason = CONV_ERR_REJECT_3_5;
feedback = -1;
}
digitseq = 0;
/* If a letter, try to match against a month */
if (isalpha (ch))
{
if (had_month)
{
conv_reason = CONV_ERR_MULTIPLE_MONTH;
feedback = -1;
}
else
{
for (count = 0; isalpha (ch); )
{
if (count < 3)
month_letters [count++] = (char) tolower (ch);
ch = *string++;
}
string--; /* Move back to char after month */
if (count < 3)
{
conv_reason = CONV_ERR_BAD_MONTH;
feedback = -1; /* Too few letters */
}
month_letters [3] = 0;
for (count = 0; count < 12; count++)
if (streq (month_letters, month_name [count]))
{
count++;
date_digits [digits++] = (char) (count / 10 + '0');
date_digits [digits++] = (char) (count % 10 + '0');
had_month = TRUE;
break;
}
if (!had_month)
{
conv_reason = CONV_ERR_BAD_MONTH;
feedback = -1; /* Month not found */
}
}
}
else
if (ispunct (ch)) /* Skip any delimiter */
if ((++delimiters > 2)
|| (format > DATE_YMD_LAST && delimiters > 1))
{
conv_reason = CONV_ERR_MULTIPLE_DELIM;
feedback = -1; /* Multiple delimiters */
}
}
}
until (ch == 0);
/* Return zero date if empty */
if (digits == 0)
return (feedback);
/* Calculate offset in date_digits for various date order & formats */
order_index = (date_order - 1) * 5; /* Each row has 5 items */
if (format <= DATE_YMD_LAST)
{
if (digits == 6)
; /* nothing */
else
if (digits == 8)
order_index += 1;
}
else
if (format <= DATE_YM_LAST)
{
date_digits [digits++] = '0';
date_digits [digits++] = '0';
if (digits == 6)
order_index += 2;
else
if (digits == 8)
order_index += 3;
else
{
conv_reason = CONV_ERR_DATE_SIZE;
return (-1); /* Error - bad date size */
}
}
else
if (format <= DATE_MD_LAST)
{
date_digits [digits++] = '0';
date_digits [digits++] = '0';
if (digits == 6)
order_index += 4;
else
{
conv_reason = CONV_ERR_DATE_SIZE;
return (-1); /* Error - bad date size */
}
}
/* Decode order to pick-up offset of day/month/year in date_digits */
y_ptr = order_ptr [order_index][0];
m_ptr = order_ptr [order_index][1];
d_ptr = order_ptr [order_index][2];
# define DIGIT(x) (date_digits [(x)] - '0')
if (y_ptr != 99)
{
year = DIGIT (y_ptr) * 10 + DIGIT (y_ptr + 1);
if (digits == 8)
year = DIGIT (y_ptr + 2) * 10 + DIGIT (y_ptr + 3) + year * 100;
if (year < 50)
year += 2000;
else
if (year < 100)
year += 1900;
}
else
year = 0;
if (m_ptr != 99)
{
month = DIGIT (m_ptr) * 10 + DIGIT (m_ptr + 1);
if (month == 0 || month > 12)
{
conv_reason = CONV_ERR_OUT_OF_RANGE;
feedback = -1;
}
}
else
month = 0;
if (d_ptr != 99)
{
day = DIGIT (d_ptr) * 10 + DIGIT (d_ptr + 1);
if ((day == 0 || day > (int) month_days [month - 1])
|| (month == 2 && day == 29 && !leap_year (year)))
{
conv_reason = CONV_ERR_OUT_OF_RANGE;
feedback = -1;
}
}
else
day = 0;
if (feedback == 0)
feedback = year * 10000L + month * 100 + day;
return (feedback);
}
/* ---------------------------------------------------------------------[<]-
Function: conv_str_day
Synopsis: Converts a string to a day. The string contains a day name
in English. Only the first three letters of the name are significant.
Returns a value 0..6 for Sunday to Saturday, or -1 if the name is not
a valid day.
---------------------------------------------------------------------[>]-*/
int
conv_str_day (
const char *string)
{
static
char *day_name [] =
{ "sun", "mon", "tue", "wed", "thu", "fri", "sat" };
int
day;
char
day_comp [4];
/* Get up to three letters of day name and convert to lower case */
strncpy (day_comp, string, 3);
day_comp [3] = '\0';
strlwc (day_comp);
/* I don't like magic constants, but "7" is pretty safe for now */
for (day = 0; day < 7; day++)
if (streq (day_name [day], day_comp))
break;
return (day >= 7? -1: day);
}