diff options
Diffstat (limited to 'sql/time.cc')
-rw-r--r-- | sql/time.cc | 690 |
1 files changed, 690 insertions, 0 deletions
diff --git a/sql/time.cc b/sql/time.cc new file mode 100644 index 00000000000..066979d1c9a --- /dev/null +++ b/sql/time.cc @@ -0,0 +1,690 @@ +/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB + + 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; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + + +/* Functions to handle date and time */ + +#include "mysql_priv.h" +#include <m_ctype.h> + +static ulong const days_at_timestart=719528; /* daynr at 1970.01.01 */ +uchar *days_in_month= (uchar*) "\037\034\037\036\037\036\037\037\036\037\036\037"; + + + /* Init some variabels neaded when using my_local_time */ + /* Currently only my_time_zone is inited */ + +static long my_time_zone=0; +pthread_mutex_t LOCK_timezone; + +void init_time(void) +{ + time_t seconds; + struct tm *l_time,tm_tmp;; + TIME my_time; + + seconds= (time_t) time((time_t*) 0); + localtime_r(&seconds,&tm_tmp); + l_time= &tm_tmp; + my_time_zone=0; + my_time.year= (uint) l_time->tm_year+1900; + my_time.month= (uint) l_time->tm_mon+1; + my_time.day= (uint) l_time->tm_mday; + my_time.hour= (uint) l_time->tm_hour; + my_time.minute= (uint) l_time->tm_min; + my_time.second= (uint) l_time->tm_sec; + VOID(my_gmt_sec(&my_time)); /* Init my_time_zone */ +} + +/* + Convert current time to sec. since 1970.01.01 + This code handles also day light saving time. + The idea is to cache the time zone (including daylight saving time) + for the next call to make things faster. + +*/ + +long my_gmt_sec(TIME *t) +{ + uint loop; + time_t tmp; + struct tm *l_time,tm_tmp; + long diff; + + if (t->hour >= 24) + { /* Fix for time-loop */ + t->day+=t->hour/24; + t->hour%=24; + } + pthread_mutex_lock(&LOCK_timezone); + tmp=(time_t) ((calc_daynr((uint) t->year,(uint) t->month,(uint) t->day) - + (long) days_at_timestart)*86400L + (long) t->hour*3600L + + (long) (t->minute*60 + t->second)) + (time_t) my_time_zone; + localtime_r(&tmp,&tm_tmp); + l_time=&tm_tmp; + for (loop=0; + loop < 3 && + (t->hour != (uint) l_time->tm_hour || + t->minute != (uint) l_time->tm_min); + loop++) + { /* One check should be enough ? */ + /* Get difference in days */ + int days= t->day - l_time->tm_mday; + if (days < -1) + days= 1; // Month has wrapped + else if (days > 1) + days= -1; + diff=(3600L*(long) (days*24+((int) t->hour - (int) l_time->tm_hour)) + + (long) (60*((int) t->minute - (int) l_time->tm_min))); + my_time_zone+=diff; + tmp+=(time_t) diff; + localtime_r(&tmp,&tm_tmp); + l_time=&tm_tmp; + } + /* Fix that if we are in the not existing daylight saving time hour + we move the start of the next real hour */ + if (loop == 3 && t->hour != (uint) l_time->tm_hour) + { + int days= t->day - l_time->tm_mday; + if (days < -1) + days=1; // Month has wrapped + else if (days > 1) + days= -1; + diff=(3600L*(long) (days*24+((int) t->hour - (int) l_time->tm_hour))+ + (long) (60*((int) t->minute - (int) l_time->tm_min))); + if (diff == 3600) + tmp+=3600 - t->minute*60 - t->second; // Move to next hour + else if (diff == -3600) + tmp-=t->minute*60 + t->second; // Move to next hour + } + if ((my_time_zone >=0 ? my_time_zone: -my_time_zone) > 3600L*12) + my_time_zone=0; /* Wrong date */ + pthread_mutex_unlock(&LOCK_timezone); + return tmp; +} /* my_gmt_sec */ + + + /* Some functions to calculate dates */ + + /* Calculate nr of day since year 0 in new date-system (from 1615) */ + +long calc_daynr(uint year,uint month,uint day) +{ + long delsum; + int temp; + DBUG_ENTER("calc_daynr"); + + if (year == 0 && month == 0 && day == 0) + DBUG_RETURN(0); /* Skipp errors */ + if (year < 200) + { + if ((year=year+1900) < 1900+YY_PART_YEAR) + year+=100; + } + delsum= (long) (365L * year+ 31*(month-1) +day); + if (month <= 2) + year--; + else + delsum-= (long) (month*4+23)/10; + temp=(int) ((year/100+1)*3)/4; + DBUG_PRINT("exit",("year: %d month: %d day: %d -> daynr: %ld", + year+(month <= 2),month,day,delsum+year/4-temp)); + DBUG_RETURN(delsum+(int) year/4-temp); +} /* calc_daynr */ + + + /* Calc weekday from daynr */ + /* Returns 0 for monday, 1 for tuesday .... */ + +int calc_weekday(long daynr,bool sunday_first_day_of_week) +{ + DBUG_ENTER("calc_weekday"); + DBUG_RETURN ((int) ((daynr + 5L + (sunday_first_day_of_week ? 1L : 0L)) % 7)); +} + + /* 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; +} + +/* Calculate week. If 'with_year' is not set, then return a week 0-53, where + 0 means that it's the last week of the previous year. + If 'with_year' is set then the week will always be in the range 1-53 and + the year out parameter will contain the year for the week */ + +uint calc_week(TIME *l_time, bool with_year, bool sunday_first_day_of_week, + uint *year) +{ + uint days; + ulong daynr=calc_daynr(l_time->year,l_time->month,l_time->day); + ulong first_daynr=calc_daynr(l_time->year,1,1); + uint weekday=calc_weekday(first_daynr,sunday_first_day_of_week); + *year=l_time->year; + if (l_time->month == 1 && weekday >= 4 && l_time->day <= 7-weekday) + { + /* Last week of the previous year */ + if (!with_year) + return 0; + with_year=0; // Don't check the week again + (*year)--; + first_daynr-= (days=calc_days_in_year(*year)); + weekday= (weekday + 53*7- days) % 7; + } + if (weekday >= 4) + days= daynr - (first_daynr+ (7-weekday)); + else + days= daynr - (first_daynr - weekday); + if (with_year && days >= 52*7) + { + /* Check if we are on the first week of the next year (or week 53) */ + weekday= (weekday + calc_days_in_year(*year)) % 7; + if (weekday < 4) + { // We are at first week on next year + (*year)++; + return 1; + } + } + return days/7+1; +} + + /* Change a daynr to year, month and day */ + /* Daynr 0 is returned as date 00.00.00 */ + +void get_date_from_daynr(long daynr,uint *ret_year,uint *ret_month, + uint *ret_day) +{ + uint year,temp,leap_day,day_of_year,days_in_year; + uchar *month_pos; + DBUG_ENTER("get_date_from_daynr"); + + if (daynr <= 365L || daynr >= 3652500) + { /* Fix if wrong daynr */ + *ret_year= *ret_month = *ret_day =0; + } + else + { + year= (uint) (daynr*100 / 36525L); + temp=(((year-1)/100+1)*3)/4; + day_of_year=(uint) (daynr - (long) year * 365L) - (year-1)/4 +temp; + while (day_of_year > (days_in_year= calc_days_in_year(year))) + { + day_of_year-=days_in_year; + (year)++; + } + leap_day=0; + if (days_in_year == 366) + { + if (day_of_year > 31+28) + { + day_of_year--; + if (day_of_year == 31+28) + leap_day=1; /* Handle leapyears leapday */ + } + } + *ret_month=1; + for (month_pos= days_in_month ; + day_of_year > (uint) *month_pos ; + day_of_year-= *(month_pos++), (*ret_month)++) + ; + *ret_year=year; + *ret_day=day_of_year+leap_day; + } + DBUG_VOID_RETURN; +} + +/* find date from string and put it in vektor + Input: pos = "YYMMDD" OR "YYYYMMDD" in any order or + "xxxxx YYxxxMMxxxDD xxxx" where xxx is anything exept + a number. Month or day mustn't exeed 2 digits, year may be 4 digits. +*/ + + +#ifdef NOT_NEEDED + +void find_date(string pos,uint *vek,uint flag) +{ + uint length,value; + string start; + DBUG_ENTER("find_date"); + DBUG_PRINT("enter",("pos: '%s' flag: %d",pos,flag)); + + bzero((char*) vek,sizeof(int)*4); + while (*pos && !isdigit(*pos)) + pos++; + length=strlen(pos); + for (uint i=0 ; i< 3; i++) + { + start=pos; value=0; + while (isdigit(pos[0]) && + ((pos-start) < 2 || ((pos-start) < 4 && length >= 8 && + !(flag & 3)))) + { + value=value*10 + (uint) (uchar) (*pos - '0'); + pos++; + } + vek[flag & 3]=value; flag>>=2; + while (*pos && (ispunct(*pos) || isspace(*pos))) + pos++; + } + DBUG_PRINT("exit",("year: %d month: %d day: %d",vek[0],vek[1],vek[2])); + DBUG_VOID_RETURN; +} /* find_date */ + + + /* Outputs YYMMDD if input year < 100 or YYYYMMDD else */ + +static long calc_daynr_from_week(uint year,uint week,uint day) +{ + long daynr; + int weekday; + + daynr=calc_daynr(year,1,1); + if ((weekday= calc_weekday(daynr,0)) >= 3) + daynr+= (7-weekday); + else + daynr-=weekday; + + return (daynr+week*7+day-8); +} + +void convert_week_to_date(string date,uint flag,uint *res_length) +{ + string format; + uint year,vek[4]; + + find_date(date,vek,(uint) (1*4+2*16)); /* YY-WW-DD */ + year=vek[0]; + + get_date_from_daynr(calc_daynr_from_week(vek[0],vek[1],vek[2]), + &vek[0],&vek[1],&vek[2]); + *res_length=8; + format="%04d%02d%02d"; + if (year < 100) + { + vek[0]= vek[0]%100; + *res_length=6; + format="%02d%02d%02d"; + } + sprintf(date,format,vek[flag & 3],vek[(flag >> 2) & 3], + vek[(flag >> 4) & 3]); + return; +} + + /* returns YYWWDD or YYYYWWDD according to input year */ + /* flag only reflects format of input date */ + +void convert_date_to_week(string date,uint flag,uint *res_length) +{ + uint vek[4],weekday,days,year,week,day; + long daynr,first_daynr; + char buff[256],*format; + + if (! date[0]) + { + get_date(buff,0,0L); /* Use current date */ + find_date(buff+2,vek,(uint) (1*4+2*16)); /* YY-MM-DD */ + } + else + find_date(date,vek,flag); + + year= vek[0]; + daynr= calc_daynr(year,vek[1],vek[2]); + first_daynr=calc_daynr(year,1,1); + + /* Caculate year and first daynr of year */ + if (vek[1] == 1 && (weekday=calc_weekday(first_daynr,0)) >= 3 && + vek[2] <= 7-weekday) + { + if (!year--) + year=99; + first_daynr=first_daynr-calc_days_in_year(year); + } + else if (vek[1] == 12 && + (weekday=calc_weekday(first_daynr+calc_days_in_year(year)),0) < 3 && + vek[2] > 31-weekday) + { + first_daynr=first_daynr+calc_days_in_year(year); + if (year++ == 99) + year=0; + } + + /* Calulate daynr of first day of week 1 */ + if ((weekday= calc_weekday(first_daynr,0)) >= 3) + first_daynr+= (7-weekday); + else + first_daynr-=weekday; + + days=(int) (daynr-first_daynr); + week=days/7+1 ; day=calc_weekday(daynr,0)+1; + + *res_length=8; + format="%04d%02d%02d"; + if (year < 100) + { + *res_length=6; + format="%02d%02d%02d"; + } + sprintf(date,format,year,week,day); + return; +} + +#endif + + /* Functions to handle periods */ + +ulong convert_period_to_month(ulong period) +{ + ulong a,b; + if (period == 0) + return 0L; + if ((a=period/100) < YY_PART_YEAR) + a+=2000; + else if (a < 100) + a+=1900; + b=period%100; + return a*12+b-1; +} + +ulong convert_month_to_period(ulong month) +{ + ulong year; + if (month == 0L) + return 0L; + if ((year=month/12) < 100) + { + year+=(year < YY_PART_YEAR) ? 2000 : 1900; + } + return year*100+month%12+1; +} + +#ifdef NOT_NEEDED + +ulong add_to_period(ulong period,int months) +{ + if (period == 0L) + return 0L; + return convert_month_to_period(convert_period_to_month(period)+months); +} +#endif + + +/***************************************************************************** +** convert a timestamp string to a TIME value. +** At least the following formats are recogniced (based on number of digits) +** YYMMDD, YYYYMMDD, YYMMDDHHMMSS, YYYYMMDDHHMMSS +** YY-MM-DD, YYYY-MM-DD, YY-MM-DD HH.MM.SS +** Returns the type of string +*****************************************************************************/ + +timestamp_type +str_to_TIME(const char *str, uint length, TIME *l_time,bool fuzzy_date) +{ + uint field_length,year_length,digits,i,number_of_fields,date[7]; + bool date_used=0; + const char *pos; + const char *end=str+length; + DBUG_ENTER("str_to_TIME"); + DBUG_PRINT("enter",("str: %.*s",length,str)); + + for (; !isdigit(*str) && str != end ; str++) ; // Skipp garbage + if (str == end) + DBUG_RETURN(TIMESTAMP_NONE); + /* + ** calculate first number of digits. + ** If length= 8 or >= 14 then year is of format YYYY. + (YYYY-MM-DD, YYYYMMDD, YYYYYMMDDHHMMSS) + */ + for (pos=str; pos != end && isdigit(*pos) ; pos++) ; + digits= (uint) (pos-str); + year_length= (digits == 4 || digits == 8 || digits >= 14) ? 4 : 2; + field_length=year_length-1; + for (i=0 ; i < 6 && str != end && isdigit(*str) ; i++) + { + uint tmp_value=(uint) (uchar) (*str++ - '0'); + while (str != end && isdigit(str[0]) && field_length--) + { + tmp_value=tmp_value*10 + (uint) (uchar) (*str - '0'); + str++; + } + if ((date[i]=tmp_value)) + date_used=1; // Found something + if (i == 2 && str != end && *str == 'T') + str++; // ISO8601: CCYYMMDDThhmmss + else + { + while (str != end && (ispunct(*str) || isspace(*str))) + { + // Only allow space between days and hours + if (isspace(*str) && i != 2) + DBUG_RETURN(TIMESTAMP_NONE); + str++; + } + } + field_length=1; // Rest fields can only be 2 + } + /* Handle second fractions */ + if (i == 6 && (uint) (end-str) >= 2 && *str == '.' && isdigit(str[1])) + { + str++; + uint tmp_value=(uint) (uchar) (*str - '0'); + field_length=3; + while (str++ != end && isdigit(str[0]) && field_length--) + tmp_value=tmp_value*10 + (uint) (uchar) (*str - '0'); + date[6]=tmp_value; + } + else + date[6]=0; + + if (year_length == 2) + date[0]+= (date[0] < YY_PART_YEAR ? 2000 : 1900); + number_of_fields=i; + while (i < 6) + date[i++]=0; + if (number_of_fields < 3 || !date_used || date[1] > 12 || + date[2] > 31 || date[3] > 23 || date[4] > 59 || date[5] > 59 || + !fuzzy_date && (date[1] == 0 || date[2] == 0)) + { + current_thd->cuted_fields++; + DBUG_RETURN(TIMESTAMP_NONE); + } + if (str != end && current_thd->count_cuted_fields) + { + for ( ; str != end ; str++) + { + if (!isspace(*str)) + { + current_thd->cuted_fields++; + break; + } + } + } + l_time->year= date[0]; + l_time->month= date[1]; + l_time->day= date[2]; + l_time->hour= date[3]; + l_time->minute=date[4]; + l_time->second=date[5]; + l_time->second_part=date[6]; + DBUG_RETURN(l_time->time_type= + (number_of_fields <= 3 ? TIMESTAMP_DATE : TIMESTAMP_FULL)); +} + + +time_t str_to_timestamp(const char *str,uint length) +{ + TIME l_time; + if (str_to_TIME(str,length,&l_time,0) == TIMESTAMP_NONE) + return(0); + if (l_time.year >= TIMESTAMP_MAX_YEAR || l_time.year < 1900+YY_PART_YEAR) + { + current_thd->cuted_fields++; + return(0); + } + return(my_gmt_sec(&l_time)); +} + + +longlong str_to_datetime(const char *str,uint length,bool fuzzy_date) +{ + TIME l_time; + if (str_to_TIME(str,length,&l_time,fuzzy_date) == TIMESTAMP_NONE) + return(0); + return (longlong) (l_time.year*LL(10000000000) + + l_time.month*LL(100000000)+ + l_time.day*LL(1000000)+ + l_time.hour*LL(10000)+ + (longlong) (l_time.minute*100+l_time.second)); +} + + +/***************************************************************************** +** convert a time string to a (ulong) value. +** Can use all full timestamp formats and +** [-] DAYS [H]H:MM:SS, [H]H:MM:SS, [M]M:SS, [H]HMMSS, [M]MSS or [S]S +** There may be an optional [.second_part] after seconds +*****************************************************************************/ + +bool str_to_time(const char *str,uint length,TIME *l_time) +{ + long date[5],value; + const char *end=str+length; + bool found_days,found_hours; + uint state; + + l_time->neg=0; + for (; !isdigit(*str) && *str != '-' && str != end ; str++) + length--; + if (str != end && *str == '-') + { + l_time->neg=1; + str++; + length--; + } + if (str == end) + return 1; + + /* Check first if this is a full TIMESTAMP */ + if (length >= 12) + { // Probably full timestamp + if (str_to_TIME(str,length,l_time,1) == TIMESTAMP_FULL) + return 0; // Was an ok timestamp + } + + /* Not a timestamp. Try to get this as a DAYS_TO_SECOND string */ + for (value=0; str != end && isdigit(*str) ; str++) + value=value*10L + (long) (*str - '0'); + + if (*str == ' ') + { + while (++str != end && str[0] == ' ') ; + str--; + } + + LINT_INIT(state); + found_days=found_hours=0; + if ((uint) (end-str) > 1 && (*str == ' ' && isdigit(str[1]))) + { // days ! + date[0]=value; + state=1; // Assume next is hours + found_days=1; + str++; // Skipp space; + } + else if ((end-str) > 1 && *str == ':' && isdigit(str[1])) + { + date[0]=0; // Assume we found hours + date[1]=value; + state=2; + found_hours=1; + str++; // skipp ':' + } + else + { + /* String given as one number; assume HHMMSS format */ + date[0]= 0; + date[1]= value/10000; + date[2]= value/100 % 100; + date[3]= value % 100; + state=4; + goto fractional; + } + + /* Read hours, minutes and seconds */ + for (;;) + { + for (value=0; str != end && isdigit(*str) ; str++) + value=value*10L + (long) (*str - '0'); + date[state++]=value; + if (state == 4 || (end-str) < 2 || *str != ':' || !isdigit(str[1])) + break; + str++; // Skipp ':' + } + + if (state != 4) + { // Not HH:MM:SS + /* Fix the date to assume that seconds was given */ + if (!found_hours && !found_days) + { + bmove_upp((char*) (date+4), (char*) (date+state), + sizeof(long)*(state-1)); + bzero((char*) date, sizeof(long)*(4-state)); + } + else + bzero((char*) (date+state), sizeof(long)*(4-state)); + } + + fractional: + /* Get fractional second part */ + if ((end-str) >= 2 && *str == '.' && isdigit(str[1])) + { + uint field_length=3; + str++; value=(uint) (uchar) (*str - '0'); + while (++str != end && isdigit(str[0]) && field_length--) + value=value*10 + (uint) (uchar) (*str - '0'); + date[4]=value; + } + else + date[4]=0; + + /* Some simple checks */ + if (date[2] >= 60 || date[3] >= 60) + { + current_thd->cuted_fields++; + return 1; + } + l_time->month=0; + l_time->day=date[0]; + l_time->hour=date[1]; + l_time->minute=date[2]; + l_time->second=date[3]; + l_time->second_part=date[4]; + + /* Check if there is garbage at end of the TIME specification */ + if (str != end && current_thd->count_cuted_fields) + { + do + { + if (!isspace(*str)) + { + current_thd->cuted_fields++; + break; + } + } while (++str != end); + } + return 0; +} |