summaryrefslogtreecommitdiff
path: root/sql-common/my_time.c
diff options
context:
space:
mode:
Diffstat (limited to 'sql-common/my_time.c')
-rw-r--r--sql-common/my_time.c233
1 files changed, 228 insertions, 5 deletions
diff --git a/sql-common/my_time.c b/sql-common/my_time.c
index 8dd4801b562..1078259f15d 100644
--- a/sql-common/my_time.c
+++ b/sql-common/my_time.c
@@ -47,6 +47,43 @@ uchar days_in_month[]= {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0};
static long my_time_zone=0;
+/* Calc days in one year. works with 0 <= year <= 99 */
+
+uint calc_days_in_year(uint year)
+{
+ return ((year & 3) == 0 && (year%100 || (year%400 == 0 && year)) ?
+ 366 : 365);
+}
+
+/*
+ check date.
+
+ SYNOPOSIS
+ bool check_date()
+ time Date to check.
+
+ NOTES
+ Here we assume that year and month is ok !
+ If month is 0 we allow any date. (This only happens if we allow zero
+ date parts in str_to_datetime())
+
+ RETURN
+ 0 ok
+ 1 errro
+*/
+
+bool check_date(MYSQL_TIME *ltime)
+{
+ if (ltime->month && ltime->day > days_in_month[ltime->month-1])
+ {
+ if (ltime->month != 2 || calc_days_in_year(ltime->year) != 366 ||
+ ltime->day != 29)
+ return 1;
+ }
+ return 0;
+}
+
+
/*
Convert a timestamp string to a MYSQL_TIME value.
@@ -58,8 +95,12 @@ static long my_time_zone=0;
flags Bitmap of following items
TIME_FUZZY_DATE Set if we should allow partial dates
TIME_DATETIME_ONLY Set if we only allow full datetimes.
- was_cut Set to 1 if value was cut during conversion or to 0
- otherwise.
+ TIME_NO_ZERO_IN_DATE Don't allow partial dates
+ TIME_NO_ZERO_DATE Don't allow 0000-00-00 date
+ TIME_INVALID_DATES Allow 2000-02-31
+ was_cut 0 Value ok
+ 1 If value was cut during conversion
+ 2 Date part was withing ranges but date was wrong
DESCRIPTION
At least the following formats are recogniced (based on number of digits)
@@ -127,6 +168,8 @@ str_to_datetime(const char *str, uint length, MYSQL_TIME *l_time,
*was_cut= 1;
DBUG_RETURN(MYSQL_TIMESTAMP_NONE);
}
+ if (flags & TIME_NO_ZERO_IN_DATE)
+ flags&= ~TIME_FUZZY_DATE;
is_internal_format= 0;
/* This has to be changed if want to activate different timestamp formats */
@@ -342,11 +385,22 @@ str_to_datetime(const char *str, uint length, MYSQL_TIME *l_time,
if (year_length == 2 && not_zero_date)
l_time->year+= (l_time->year < YY_PART_YEAR ? 2000 : 1900);
+ if (!not_zero_date && (flags & TIME_NO_ZERO_DATE))
+ {
+ /*
+ We don't set *was_cut here to signal that the problem was a zero date
+ and not an invalid date
+ */
+ goto err;
+ }
+
if (number_of_fields < 3 ||
l_time->year > 9999 || l_time->month > 12 ||
l_time->day > 31 || l_time->hour > 23 ||
l_time->minute > 59 || l_time->second > 59 ||
- (!(flags & TIME_FUZZY_DATE) && (l_time->month == 0 || l_time->day == 0)))
+ (!(flags & TIME_FUZZY_DATE) && (l_time->month == 0 ||
+ l_time->day == 0) &&
+ not_zero_date))
{
/* Only give warning for a zero date if there is some garbage after */
if (!not_zero_date) /* If zero date */
@@ -360,14 +414,19 @@ str_to_datetime(const char *str, uint length, MYSQL_TIME *l_time,
}
}
}
- if (not_zero_date)
- *was_cut= 1;
+ *was_cut= test(not_zero_date);
goto err;
}
l_time->time_type= (number_of_fields <= 3 ?
MYSQL_TIMESTAMP_DATE : MYSQL_TIMESTAMP_DATETIME);
+ if (not_zero_date && !(flags & TIME_INVALID_DATES) && check_date(l_time))
+ {
+ *was_cut= 2; /* Not correct date */
+ goto err;
+ }
+
for (; str != end ; str++)
{
if (!my_isspace(&my_charset_latin1,*str))
@@ -812,3 +871,167 @@ int my_TIME_to_str(const MYSQL_TIME *l_time, char *to)
return 0;
}
}
+
+
+/*
+ Convert datetime value specified as number to broken-down TIME
+ representation and form value of DATETIME type as side-effect.
+
+ SYNOPSIS
+ number_to_datetime()
+ nr - datetime value as number
+ time_res - pointer for structure for broken-down representation
+ fuzzy_date - indicates whenever we allow fuzzy dates
+ was_cut - set ot 1 if there was some kind of error during
+ conversion or to 0 if everything was OK.
+
+ DESCRIPTION
+ Convert a datetime value of formats YYMMDD, YYYYMMDD, YYMMDDHHMSS,
+ YYYYMMDDHHMMSS to broken-down TIME representation. Return value in
+ YYYYMMDDHHMMSS format as side-effect.
+
+ This function also checks if datetime value fits in DATETIME range.
+
+ RETURN VALUE
+ Datetime value in YYYYMMDDHHMMSS format.
+ If input value is not valid datetime value then 0 is returned.
+*/
+
+longlong number_to_datetime(longlong nr, MYSQL_TIME *time_res,
+ my_bool fuzzy_date, int *was_cut)
+{
+ long part1,part2;
+
+ *was_cut= 0;
+
+ if (nr == LL(0) || nr >= LL(10000101000000))
+ goto ok;
+ if (nr < 101)
+ goto err;
+ if (nr <= (YY_PART_YEAR-1)*10000L+1231L)
+ {
+ nr= (nr+20000000L)*1000000L; /* YYMMDD, year: 2000-2069 */
+ goto ok;
+ }
+ if (nr < (YY_PART_YEAR)*10000L+101L)
+ goto err;
+ if (nr <= 991231L)
+ {
+ nr= (nr+19000000L)*1000000L; /* YYMMDD, year: 1970-1999 */
+ goto ok;
+ }
+ if (nr < 10000101L)
+ goto err;
+ if (nr <= 99991231L)
+ {
+ nr= nr*1000000L;
+ goto ok;
+ }
+ if (nr < 101000000L)
+ goto err;
+ if (nr <= (YY_PART_YEAR-1)*LL(10000000000)+LL(1231235959))
+ {
+ nr= nr+LL(20000000000000); /* YYMMDDHHMMSS, 2000-2069 */
+ goto ok;
+ }
+ if (nr < YY_PART_YEAR*LL(10000000000)+ LL(101000000))
+ goto err;
+ if (nr <= LL(991231235959))
+ nr= nr+LL(19000000000000); /* YYMMDDHHMMSS, 1970-1999 */
+
+ ok:
+ part1=(long) (nr/LL(1000000));
+ part2=(long) (nr - (longlong) part1*LL(1000000));
+ time_res->year= (int) (part1/10000L); part1%=10000L;
+ time_res->month= (int) part1 / 100;
+ time_res->day= (int) part1 % 100;
+ time_res->hour= (int) (part2/10000L); part2%=10000L;
+ time_res->minute=(int) part2 / 100;
+ time_res->second=(int) part2 % 100;
+
+ if (time_res->year <= 9999 && time_res->month <= 12 &&
+ time_res->day <= 31 && time_res->hour <= 23 &&
+ time_res->minute <= 59 && time_res->second <= 59 &&
+ (fuzzy_date || (time_res->month != 0 && time_res->day != 0) || nr==0))
+ return nr;
+
+ err:
+
+ *was_cut= 1;
+ return LL(0);
+}
+
+
+/* Convert time value to integer in YYYYMMDDHHMMSS format */
+
+ulonglong TIME_to_ulonglong_datetime(const MYSQL_TIME *time)
+{
+ return ((ulonglong) (time->year * 10000UL +
+ time->month * 100UL +
+ time->day) * ULL(1000000) +
+ (ulonglong) (time->hour * 10000UL +
+ time->minute * 100UL +
+ time->second));
+}
+
+
+/* Convert TIME value to integer in YYYYMMDD format */
+
+ulonglong TIME_to_ulonglong_date(const MYSQL_TIME *time)
+{
+ return (ulonglong) (time->year * 10000UL + time->month * 100UL + time->day);
+}
+
+
+/*
+ Convert TIME value to integer in HHMMSS format.
+ This function doesn't take into account time->day member:
+ it's assumed that days have been converted to hours already.
+*/
+
+ulonglong TIME_to_ulonglong_time(const MYSQL_TIME *time)
+{
+ return (ulonglong) (time->hour * 10000UL +
+ time->minute * 100UL +
+ time->second);
+}
+
+
+/*
+ Convert struct TIME (date and time split into year/month/day/hour/...
+ to a number in format YYYYMMDDHHMMSS (DATETIME),
+ YYYYMMDD (DATE) or HHMMSS (TIME).
+
+ SYNOPSIS
+ TIME_to_ulonglong()
+
+ DESCRIPTION
+ The function is used when we need to convert value of time item
+ to a number if it's used in numeric context, i. e.:
+ SELECT NOW()+1, CURDATE()+0, CURTIMIE()+0;
+ SELECT ?+1;
+
+ NOTE
+ This function doesn't check that given TIME structure members are
+ in valid range. If they are not, return value won't reflect any
+ valid date either.
+*/
+
+ulonglong TIME_to_ulonglong(const MYSQL_TIME *time)
+{
+ switch (time->time_type) {
+ case MYSQL_TIMESTAMP_DATETIME:
+ return TIME_to_ulonglong_datetime(time);
+ case MYSQL_TIMESTAMP_DATE:
+ return TIME_to_ulonglong_date(time);
+ case MYSQL_TIMESTAMP_TIME:
+ return TIME_to_ulonglong_time(time);
+ case MYSQL_TIMESTAMP_NONE:
+ case MYSQL_TIMESTAMP_ERROR:
+ return ULL(0);
+ default:
+ DBUG_ASSERT(0);
+ }
+ return 0;
+}
+