/*- * Copyright (c) 1999, 2000, 2001, 2002, 2004, 2005 * Lev Walkin . All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Id: strtotime.c,v 1.6 2006/12/11 07:11:52 vlm Exp $ */ #ifndef __EXTENSIONS__ #define __EXTENSIONS__ #endif #include #include #include #include #include #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_TIME_H #include #endif #ifdef HAVE_SYS_TIME_H #include #endif #ifdef HAVE_ERRNO_H #include #else #ifndef EINVAL #define EINVAL 1 #endif #endif #include #include #ifndef HAVE_TM_ZONE #ifndef HAVE_TZNAME #warning "Assume, there is char *tzname[2]" #define HAVE_TZNAME #warning "Please, please report this situation to vlm@lionet.info!" #warning "This iz due to my lazyness in checking the unusual states..." #endif #endif #ifndef HAVE_LOCALTIME_R /* * Emulate localtime_r() using localtime(). */ static struct tm * _sf_localtime_r(const time_t *clock, struct tm *result) { struct tm *tmp; tmp = localtime(clock); if(tmp) /* Assume result is not NULL */ memcpy(result, tmp, sizeof(struct tm)); return result; } #define localtime_r _sf_localtime_r #endif /* * Default date (for parsing something like "23:59"). */ #define NO_VALUE 0x7fffffff static struct { int year; int mon; int mday; int cur_off; } _sf_def = { NO_VALUE, 0, 0, 0 }; static int _sf_chartype_table[256] = { 0, 0, 0, 0, 0, 0, 0, 0, /* 07 */ 0, 1, 1, 1, 0, 1, 0, 0, /* 15 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 23 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 31 */ 1, 0, 0, 0, 0, 0, 0, 0, /* 39 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 47 */ 2, 2, 2, 2, 2, 2, 2, 2, /* 55 */ 2, 2, 0, 0, 0, 0, 0, 0, /* 63 */ }; #define _sf_isspace(foo) (_sf_chartype_table[(foo)] & 1) #define _sf_isdigit(foo) (_sf_chartype_table[(foo)] & 2) static inline int _sf_recognize_month(unsigned char *dts) { switch(*dts & ~32) { case 'A': switch(dts[1]) { case 'p': if(dts[2] == 'r') return 3; return -1; case 'u': if(dts[2] == 'g') return 7; default: return -1; } case 'D': if(dts[1] == 'e' && dts[2] == 'c') return 11; return -1; case 'F': if(dts[1] == 'e' && dts[2] == 'b') return 1; return -1; case 'J': switch(dts[1]) { case 'a': if(dts[2] == 'n') return 0; return -1; case 'u': switch(dts[2]) { case 'l': return 6; case 'n': return 5; } default: return -1; } case 'M': if(dts[1] != 'a') return -1; switch(dts[2]) { case 'r': return 2; case 'y': return 4; default: return -1; } case 'N': if(dts[1] == 'o' && dts[2] == 'v') return 10; return -1; case 'O': if(dts[1] == 'c' && dts[2] == 't') return 9; return -1; case 'S': if(dts[1] == 'e' && dts[2] == 'p') return 8; return -1; default: return -1; } } /* Water-proof atoi() */ static inline int _sf_natoui(unsigned char *str, int num) { int n = 0, na; while(num-- > 0) { na = *str++; if(!_sf_isdigit(na)) return -1; n = (n * 10) + (na - '0'); } return n; } static inline time_t _sf_recognize_tz(unsigned char *dts) { if(*dts == '+' || *dts == '-') { time_t tmp; tmp = _sf_natoui(dts+1, 4); if(tmp < 0) return -1; tmp = (tmp / 100) * 3600 + (tmp % 100) * 60; if(*dts == '-') tmp = -tmp; dts += 5; while(*dts == ' ') dts++; return tmp; } else if(*dts == '(' || (*dts >= 'A' && *dts <= 'Z')) { if(*dts == '(') dts++; if(!*dts || !dts[1] || !dts[2] || (dts[3] >= 'A' && dts[3] <= 'Z')) return 0; /* RFC 822 zones */ switch(*dts) { case 'G': if(dts[1] == 'M' && dts[2] == 'T') return 0; return -1; case 'E': switch(dts[1]) { case 'S': if(dts[2] == 'T') return - 5 * 3600; case 'D': if(dts[2] == 'T') return - 4 * 3600; default: return -1; } case 'C': switch(dts[1]) { case 'S': if(dts[2] == 'T') return - 3 * 3600; case 'D': if(dts[2] == 'T') return - 5 * 3600; default: return -1; } case 'M': switch(dts[1]) { case 'S': if(dts[2] == 'T') return - 7 * 3600; case 'D': if(dts[2] == 'T') return - 6 * 3600; default: return -1; } case 'P': switch(dts[1]) { case 'S': if(dts[2] == 'T') return - 8 * 3600; case 'D': if(dts[2] == 'T') return - 7 * 3600; default: return -1; } default: return -1; } } return -1; } static inline long _sf_recognize_time(unsigned char *dts) { long l = 0; int tmp; if(!*dts) return -1; if((tmp = _sf_natoui(dts, 2)) < 0 || tmp > 24) return -1; l |= tmp << 16; if(dts[2] != ':' && dts[2] != '-') return -1; dts+=3; if((tmp = _sf_natoui(dts, 2)) < 0 || tmp > 60) return -1; l |= tmp << 8; if(dts[2] != ':' && dts[2] != '-') { l |= 5 << 24; return -1; } dts+=3; if((tmp = _sf_natoui(dts, 2)) < 0 || tmp > 60) return -1; l |= tmp; l |= 8 << 24; return l; } /* * Ensures year is within correct range. */ static inline int _sf_year_range_gen(int year) { if(year > 1000) { year -= 1900; } else { /* Dirty hack to convert 80 to 1980. */ if(year < 38) { year += 100; return year; } } if(year < 2 || year > 138) return 0; return year; } static inline int _sf_year_range_big(int year) { year -= 1900; if(year < 2 || year > 138) return 0; return year; } static inline int _sf_year_range_small(int year) { /* Dirty hack to convert 80 to 1980. */ if(year < 38) { year += 100; if(year < 2) return 0; else return year; } if(year < 2 || year > 138) return 0; return year; } static inline int _sf_is_year_leap(int year) { return (year % 400) ? ((year % 100) ? ((year % 4)?0:1) : 0 ) : 1; } /* Fast version of mktime() */ static time_t _sf_mktime(struct tm *tm) { static int mdshift[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; time_t t; t = (tm->tm_year - 70) * 365 + ((tm->tm_year - 69) >> 2) + mdshift[tm->tm_mon]; /* Leap day for this year. */ if ( tm->tm_mon >= 2 && _sf_is_year_leap( tm->tm_year + 1900 ) ) ++t; t = ((((((t + (tm->tm_mday - 1)) * 24) + tm->tm_hour) * 60) + tm->tm_min) * 60) + tm->tm_sec; return t - _sf_def.cur_off; } /* * Zone info cache */ static int _sf_param_stt_zcache; void strtotime_zoneinfo_cache(int on_off) { _sf_param_stt_zcache = on_off; } /* * Convert given time into time_t. * dts (date/time string) can be in numerous representations, * some of them are mentioned in strtotime(3) manual page. */ time_t strtotime(char *dtsp) { static time_t lcupd; /* Last year/zone cache update */ static time_t hour_end_in; unsigned char *dts; struct tm tm; time_t tmp; time_t sugOffset = 0; /* Suggested offset from GMT */ enum { TFORM_ERROR, /* Seems like error */ TFORM_RFC822, /* Seems like RFC 822 */ TFORM_ISO8601, /* Seems like ISO 8601 */ TFORM_CTIME, /* Seems like unix ctime() format */ TFORM_SYSLOGD, /* Seems like syslogd format */ } timespec_form; /* * Cache current year, time offset. */ if(_sf_def.year == NO_VALUE) { time(&tmp); if(localtime_r(&tmp, &tm) == NULL) goto fail; _sf_def.year = tm.tm_year; _sf_def.mon = tm.tm_mon; _sf_def.mday = tm.tm_mday; _sf_def.cur_off = #ifdef HAVE_TM_ZONE tm.tm_gmtoff; #else /* HAVE_TM_ZONE */ - timezone; #endif /* HAVE_TM_ZONE */ hour_end_in = 3600 - (60 * tm.tm_min + tm.tm_sec); lcupd = tmp; } else if(_sf_param_stt_zcache == 0) { time(&tmp); if((tmp - lcupd) > hour_end_in) { _sf_def.year = NO_VALUE; return strtotime(dtsp); } } if(!dtsp) goto fail; /* That's a good start. */ for(; *dtsp == ' '; dtsp++); dts = dtsp; /* ** Forms: ** form = 0: something like error ** form = 1: ? RFC 822 ** form = 2: ? ISO 8601 ** form = 3: ? ctime() ** form = 4: ? syslogd format (Mar 23 18:15:00) */ if(*dts >= 'A') { timespec_form = TFORM_RFC822; } else if(_sf_isdigit(*dts)) { int n; for(n = 0; _sf_isdigit(dts[n]); n++); if(n != 8 /* YYYYMMDD */ && (!dts[n] || (_sf_isspace(dts[n]) && !dts[n+1]))) { if((unsigned long)atol(dts) < 0x7fffffff) return (time_t)atol(dts); } timespec_form = TFORM_ISO8601; } else { /* * Unknown date/time representation, * started neither from digital, * nor from alphabetic character. */ goto fail; } /* * Initialize temporary tm structure with default date. */ memset(&tm, 0, sizeof(struct tm)); tm.tm_isdst = -1; tm.tm_year = _sf_def.year; tm.tm_mon = _sf_def.mon; tm.tm_mday = _sf_def.mday; switch(timespec_form) { case TFORM_RFC822: for(;; dts++) { switch(*dts) { case '\0': goto fail; default: continue; case ' ': /* Maybe, it is just ctime()? */ dts = dtsp; goto tform_ctime; case ',': dts++; } break; } for(; *dts == ' '; dts++); if(*dts) { if(dts[1] == ' ') { tmp = _sf_natoui(dts++, 1); } else { tmp = _sf_natoui(dts, 2); dts+=2; } } else { goto fail; } if(tmp < 1) goto fail; else tm.tm_mday = tmp; for(; *dts == ' ' || *dts == '-'; dts++); if(!*dts) goto fail; tmp = _sf_recognize_month(dts); if(tmp < 0) goto fail; else tm.tm_mon = tmp; while(*dts >= 'A' || *dts == ' ' || *dts == '-') dts++; if(!*dts) goto fail; year: if((tmp = _sf_natoui(dts, 4)) == -1) { if((tmp = _sf_natoui(dts, 2)) < 0) goto fail; if((tm.tm_year = _sf_year_range_small(tmp)) == 0) goto fail; dts+=2; } else { if((tm.tm_year = _sf_year_range_big(tmp)) == 0) goto fail; dts+=4; } while(*dts == ' ') dts++; if((tmp = _sf_recognize_time(dts)) == -1) goto fail; dts += ((unsigned int)tmp) >> 24; tm.tm_hour = (tmp >> 16) & 0xff; tm.tm_min = (tmp >> 8) & 0xff; tm.tm_sec = tmp & 0xff; while(*dts == ' ') dts++; sugOffset = _sf_recognize_tz(dts); break; case TFORM_ISO8601: /* Year */ if((tmp = _sf_natoui(dts, 4)) == -1) { /* hh:mm... form */ if(strlen(dts) >= 5) { if(dts[2] == ':') { /* HH:MM */ goto tme; } else if(dts[2] == '/' || dts[2] == '.') { /* DD.MM */ goto dte; } else if(dts[2] == '-' || dts[2] == ' ') { if(_sf_isdigit(dts[3])) goto dte; tm.tm_mday = _sf_natoui(dts, 2); tm.tm_mon = _sf_recognize_month(dts + 3); if((tm.tm_mday & tm.tm_mon) == -1) goto fail; dts += 6; if(*dts != '-' && *dts != ' ') goto fail; dts++; goto year; } } goto fail; } else { if((tm.tm_year = _sf_year_range_big(tmp)) == 0) goto fail; } dts+=4; if(!*dts) goto fail; if(*dts == '-' || *dts == '/') dts++; else { if(*dts == '+' || *dts == 'Z') goto zone; if(*dts == 'T') { dts++; goto tme; } } dte: /* Month */ if((tmp = _sf_natoui(dts, 2)) == -1) { if(dts[-1] == '-') goto fail; if(!_sf_isspace(*dts)) goto fail; break; } else { if((--tmp) < 0) goto fail; else tm.tm_mon = tmp; } dts+=2; if(!*dts) break; if(*dts == '-' || *dts == '/' || *dts == '.') { dts++; } else { if(*dts == '+' || *dts == 'Z') goto zone; if(*dts == 'T') { dts++; goto tme; } } /* Month Day */ if((tmp = _sf_natoui(dts, 2)) == -1) { if(dts[-1] == '-') goto fail; if(!_sf_isspace(*dts)) goto fail; break; } else if(tmp) { tm.tm_mday = tmp; } else { goto fail; } dts+=2; if(dts[-3] == '.' || (dts[-3] == '/' && dts[0] == '/')) { int __tmp = tm.tm_mday; tm.tm_mday = tm.tm_mon + 1; tm.tm_mon = __tmp - 1; } if(*dts == '.' || *dts == '/') { if((tmp = _sf_natoui(dts + 1, 4)) == -1) { if((tmp = _sf_natoui(dts + 1, 2)) == -1) goto fail; if(_sf_isdigit(dts[3])) goto fail; if((tm.tm_year = _sf_year_range_small(tmp)) == 0) goto fail; dts += 3; } else { if((tm.tm_year = _sf_year_range_big(tmp)) == 0) goto fail; dts += 5; } } lp: if(!*dts) { sugOffset = -1; break; } if(*dts == 'T') { dts++; goto tme; } if(_sf_isdigit(*dts)) goto tme; if(*dts == '+') goto zone; if(*dts == 'Z') break; if(*dts++ == ' ') goto lp; break; tme: if((tmp = _sf_natoui(dts, 2)) == -1) goto fail; else tm.tm_hour = tmp; dts+=2; if(!*dts) break; if(*dts == ':') { dts++; } else { if(*dts == '+' || *dts == '-') goto zone; if(*dts == 'Z') break; } if((tmp = _sf_natoui(dts, 2)) == -1) { if(dts[-1] == ':') goto fail; if(!_sf_isspace(*dts)) goto fail; break; } else { tm.tm_min = tmp; } dts+=2; if(!*dts) goto zone; if(*dts == ':') dts++; else { if(*dts == '+' || *dts == '-') goto zone; if(*dts == 'Z') break; } if((tmp = _sf_natoui(dts, 2)) == -1) { if(dts[-1] == ':') goto fail; if(!_sf_isspace(*dts)) goto fail; break; } else { tm.tm_sec = tmp; } dts+=2; if(!*dts) { /* * Time in this form without the GMT offset * is the LOCAL time */ sugOffset = -1; break; } while(*dts == ' ') dts++; zone: if(!*dts) { /* * Time in this form without the GMT offset * is the LOCAL time */ sugOffset = -1; break; } if(*dts == 'Z') { sugOffset = 0; break; } if(*dts != '+' && *dts != '-') { if(_sf_isspace(*dts)) break; goto fail; } sugOffset = _sf_recognize_tz(dts); break; case TFORM_CTIME: tform_ctime: tmp = _sf_recognize_month(dts); if(tmp != -1) { tm.tm_mon = tmp; goto tform_syslogd; } for(;; dts++) { switch(*dts) { case '\0': goto fail; default: continue; case ' ': dts++; } break; } if((tmp = _sf_recognize_month(dts)) < 0) goto fail; else tm.tm_mon = tmp; while(*dts >= 'A' || *dts == ' ') dts++; if(*dts && dts[1] == ' ') { tmp = _sf_natoui(dts++, 1); } else { tmp = _sf_natoui(dts, 2); dts+=2; } if(tmp < 1) goto fail; else tm.tm_mday = tmp; while(*dts && *dts == ' ') dts++; if((tmp = _sf_recognize_time(dts)) == -1) goto fail; dts += ((unsigned int)tmp) >> 24; tm.tm_hour = (tmp >> 16) & 0xff; tm.tm_min = (tmp >> 8) & 0xff; tm.tm_sec = tmp & 0xff; while(*dts == ' ') dts++; if(!*dts) goto fail; ctime_year: if((tmp = _sf_natoui(dts, 4)) < 0) { if((tmp = _sf_natoui(dts, 2)) < 0) goto fail; if((tm.tm_year = _sf_year_range_small(tmp)) == 0) goto fail; dts+=2; } else { if((tm.tm_year = _sf_year_range_big(tmp)) == 0) goto fail; dts+=4; } while(*dts && *dts == ' ') dts++; sugOffset = _sf_recognize_tz(dts); break; /* syslogd format (Mar 23 18:15:00) */ case TFORM_SYSLOGD: tform_syslogd: while(_sf_isspace(*dts) == 0) /* Skip month */ dts++; while(_sf_isspace(*dts)) /* Skip space before the date */ dts++; if(!*dts) goto fail; if( ((tmp = _sf_natoui(dts, 2)) == -1) && ((tmp = _sf_natoui(dts, 1)) == -1) ) goto fail; else tm.tm_mday = tmp; while(*dts && *dts != ' ') /* Skip date */ dts++; while(*dts && *dts == ' ') /* Skip space before the time */ dts++; if(!*dts) goto fail; if((tmp = _sf_recognize_time(dts)) == -1) goto fail; dts += ((unsigned int)tmp) >> 24; tm.tm_hour = (tmp >> 16) & 0xff; tm.tm_min = (tmp >> 8) & 0xff; tm.tm_sec = tmp & 0xff; if(*dts == ' ' && _sf_isdigit(dts[1])) { dts++; goto ctime_year; } sugOffset = -1; break; case TFORM_ERROR: assert(!"should not happen"); goto fail; } /* switch() */ if(sugOffset == -1) { /* * Time in this form without the GMT offset * is the LOCAL time. */ sugOffset = 0; } else { sugOffset -= _sf_def.cur_off; } return _sf_mktime(&tm) - sugOffset; fail: errno = EINVAL; return 0; }