/* * Map a string time to a time_t time. * * Handles numerous variants. * * SPEC = * WDATE * TIME * WDATE TIME * TIME WDATE * +ssss[.ffff] * www mmm dd hh:mm:ss[.ffff] zzz yyyy * * WDATE = * [www[,]] DATE * * DATE = * mm-dd * yyyy[-/ ]mm[-/ ]dd * yyyy[-/ ]mmm[-/ ]dd * mmm dd[[,] yyyy] * dd mmm[[,] yyyy] * dd-mmm[-yyyy] * ddmmm[yyyy] * * TIME = * hh:mm[:ss[.ffff]] [ZONE [COMMENT]] * * ZONE = * ±nnnn * zzz * * COMMENT = * (ctext) * * ssss = raw time_t seconds value * ffff = fractional seconds value * www = weekday name, case insensitive * mmm = month name, case insensitive * dd = day-of-month number * hh = hours value * mm = minutes value (in SPEC, TIME) or month number (in DATE) * ss = seconds value * zzz = text zone name * yyyy = year number * ±nnnn = four-digit signed timezone offset * ctext = any text not including a ) * * A spec consists of a DATE and a TIME, both of which are optional. A * missing DATE defaults to "today". A missing TIME defaults to * 00:00:00. A missing ZONE defaults to using the local timezone, * except that in this case local DST rules are applied. COMMENT * exists to support email timestamps, which often include timezone * names in comments. This comment syntax does not match RFC2822's, * but is close enough to cover common, and even most uncommon, email * date uses. * * A spec referring to a time that does not exist because it falls in * an hour deleted during summer time changeover produces the value * for the changeover moment. * * A spec referring to a time that exists twice (because it falls in an * hour inserted during summer time changeover) and does not have a * timezone specified may produce either of the two values. * * If a spec contains an explicit timezone reference, the UTC offset it * implies will be obeyed even if the timezone's DST implication * differs from what those who use that time normally observe for the * date given. * * When using the simple parsedate() interface, fractional seconds are * not supported: if they occur in the string a parsing error will * occur. * * This file is in the public domain. */ #include #include #include #include #include "parsedate.h" typedef struct zone ZONE; struct zone { const char *name; int namelen; int offset; } ; static const ZONE zonetbl[] = { /* Would like to use, eg, +0400, -0630, but the leading 0 makes those octal, making, eg, -0800 an error. (We could compensate for octal easily enough, but can't do anything about the errors.) So we convert leading 0s to spaces. :-( */ /* The trailing "Time" is omitted from the comments. */ #define I(name,offset) { #name, sizeof(#name)-1, offset } I(acdt, +1030), /* Australian Central Daylight */ I(acst, + 930), /* Australian Central Standard */ I(adt, - 300), /* Atlantic Daylight */ I(akdt, - 800), /* Alaska Daylight */ I(akst, - 900), /* Alaska Standard */ I(ast, - 400), /* Atlantic Standard */ I(awst, + 800), /* Australian Western Standard */ I(cadt, +1030), /* Central Australian Daylight */ I(cast, + 930), /* Central Australian Standard */ I(cat, + 200), /* Central Africa */ I(cct, + 800), /* China Coast */ I(cdt, - 500), /* Central Daylight */ I(ces, + 200), /* Central European Summer */ I(cest, + 200), /* Central European Summer */ I(cet, + 100), /* Central European */ I(ckt, -1000), /* Cook Islands */ I(clst, - 300), /* Chile Summer */ I(clt, - 400), /* Chile */ I(cot, - 500), /* Colombia */ I(cst, - 600), /* Central Standard */ I(cut, + 0), /* Universal Coordinated */ I(eadt, +1100), /* Eastern Australian Daylight */ I(east, +1000), /* Eastern Australian Standard */ I(edt, - 400), /* Eastern Daylight */ I(eet, + 200), /* Eastern Europe */ I(emt, + 100), /* Norway (??) */ I(est, - 500), /* Eastern Standard */ I(gmt, + 0), /* Greenwich Mean */ I(hadt, - 900), /* Hawaii-Aleutian Daylight */ I(hast, -1000), /* Hawaii-Aleutian Standard */ I(hkt, + 800), /* Hong Kong */ I(hst, -1000), /* Hawaiian Standard */ I(ict, + 700), /* Indochina */ I(jst, + 900), /* Japan Standard */ I(kdt, +1000), /* Korean Daylight */ I(kst, + 900), /* Korean Standard */ I(mdt, - 600), /* Mountain Daylight */ I(met, + 100), /* Middle European */ I(mez, + 100), /* Middle European */ I(mezt, + 200), /* Middle European Summer */ I(mpt, +1000), /* North Mariana Islands */ I(msd, + 400), /* Moscow Summer */ I(msk, + 300), /* Moscow Winter */ I(mst, - 700), /* Mountain Standard */ I(mt, + 830), /* Moluccas */ I(mut, + 400), /* Mauritius */ I(ndt, - 230), /* Newfoundland Daylight */ I(nst, - 330), /* Newfoundland Standard */ I(nzdt, +1300), /* New Zealand Daylight */ I(nzst, +1200), /* New Zealand Standard */ I(pdt, - 700), /* Pacific Daylight */ I(pmt, - 300), /* Pierre and Miquelon Standard */ I(pnt, - 830), /* Pitcairn */ I(pst, - 800), /* Pacific Standard */ I(tmt, + 500), /* Tukmenistan (typo for Turkmenistan?) */ I(tst, + 300), /* Turkish Standard */ I(ut, + 0), /* Universal */ I(utc, + 0), /* Universal Coordinated */ I(wadt, + 900), /* West Australian Daylight */ I(wast, + 800), /* West Australian Standard */ I(wet, + 0), /* Western European */ I(ydt, - 800), /* Yukon Daylight */ I(yst, - 900), /* Yukon Standard */ I(z, + 0), /* Military */ #undef I }; static const int nzones = sizeof(zonetbl) / sizeof(zonetbl[0]); typedef struct state STATE; struct state { const char *sp; unsigned char have; #define HAVE_TIME 0x00000001 #define HAVE_ZONE 0x00000002 #define HAVE_DATE 0x00000004 #define HAVE_YEAR 0x00000008 #define HAVE_WDAY 0x00000010 #define HAVE_TT 0x00000020 #define HAVE_FRAC 0x00000040 unsigned char hr; unsigned char min; unsigned char sec; signed short int zone; unsigned short int year; unsigned char month; unsigned char mday; unsigned char wday; unsigned int tt; unsigned int frac; unsigned int exts; int *fracloc; } ; static const char *skipws(const char *s) { while (1) { switch (*s) { case ' ': case '\t': case '\n': s ++; break; default: return(s); } } } static void ws(STATE *s) { s->sp = skipws(s->sp); } static int parse_www(STATE *s) { const char *fn; int fnl; static const char * const fullnames[] = { [ 0] = "monday", [ 1] = "tuesday", [ 2] = "wednesday", [ 3] = "thursday", [ 4] = "friday", [ 5] = "saturday", [ 6] = "sunday" }; do <"fail"> { switch (*s->sp) { case 'm': case 'M': if (!strncasecmp(s->sp,"mon",3)) s->wday = 0; else break <"fail">; break; case 't': case 'T': if (!strncasecmp(s->sp,"tue",3)) s->wday = 1; else if (!strncasecmp(s->sp,"thu",3)) s->wday = 3; else break <"fail">; break; case 'w': case 'W': if (!strncasecmp(s->sp,"wed",3)) s->wday = 2; else break <"fail">; break; case 'f': case 'F': if (!strncasecmp(s->sp,"fri",3)) s->wday = 4; else break <"fail">; break; case 's': case 'S': if (!strncasecmp(s->sp,"sat",3)) s->wday = 5; else if (!strncasecmp(s->sp,"sun",3)) s->wday = 6; else break <"fail">; break; default: break <"fail">; } fn = fullnames[s->wday]; fnl = strlen(fn); if (!strncasecmp(s->sp,fn,fnl)) s->sp += fnl; else s->sp += 3; return(1); } while (0); return(0); } static int parse_mmm(STATE *s) { const char *fn; int fnl; static const char * const fullnames[] = { [ 1] = "january", [ 2] = "february", [ 3] = "march", [ 4] = "april", [ 5] = "may", [ 6] = "june", [ 7] = "july", [ 8] = "august", [ 9] = "september", [10] = "october", [11] = "november", [12] = "december" }; do <"fail"> { switch (*s->sp) { case 'j': case 'J': /* jan jun jul */ if (!strncasecmp(s->sp,"jan",3)) s->month = 1; else if (!strncasecmp(s->sp,"jun",3)) s->month = 6; else if (!strncasecmp(s->sp,"jul",3)) s->month = 7; else break <"fail">; break; case 'f': case 'F': if (!strncasecmp(s->sp,"feb",3)) s->month = 2; else break <"fail">; break; case 'm': case 'M': if (!strncasecmp(s->sp,"mar",3)) s->month = 3; else if (!strncasecmp(s->sp,"may",3)) s->month = 5; else break <"fail">; break; case 'a': case 'A': if (!strncasecmp(s->sp,"apr",3)) s->month = 4; else if (!strncasecmp(s->sp,"aug",3)) s->month = 8; else break <"fail">; break; case 's': case 'S': if (!strncasecmp(s->sp,"sep",3)) s->month = 9; else break <"fail">; break; case 'o': case 'O': if (!strncasecmp(s->sp,"oct",3)) s->month = 10; else break <"fail">; break; case 'n': case 'N': if (!strncasecmp(s->sp,"nov",3)) s->month = 11; else break <"fail">; break; case 'd': case 'D': if (!strncasecmp(s->sp,"dec",3)) s->month = 12; else break <"fail">; break; default: break <"fail">; } fn = fullnames[s->month]; fnl = strlen(fn); if (!strncasecmp(s->sp,fn,fnl)) s->sp += fnl; else s->sp += 3; return(1); } while (0); return(0); } static int parse_dd(STATE *s) { long int v; char *ep; do <"fail"> { switch (*s->sp) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': v = strtol(s->sp,&ep,10); switch (ep-s->sp) { case 1: case 2: break; default: break <"fail">; } if ((v < 1) || (v > 31)) break <"fail">; s->mday = v; s->sp = ep; return(1); } } while (0); return(0); } static int parse_hh(STATE *s) { long int v; char *ep; switch (*s->sp) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': v = strtol(s->sp,&ep,10); if ( (ep-s->sp == 2) && ((v >= 0) && (v <= 23)) ) { s->hr = v; s->sp = ep; return(1); } break; } return(0); } static int parse_mm_min(STATE *s) { long int v; char *ep; switch (*s->sp) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': v = strtol(s->sp,&ep,10); if ( (ep-s->sp == 2) && ((v >= 0) && (v <= 59)) ) { s->min = v; s->sp = ep; return(1); } break; } return(0); } static int parse_mm_mon(STATE *s) { long int v; char *ep; switch (*s->sp) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': v = strtol(s->sp,&ep,10); if ( (ep-s->sp == 2) && ((v >= 1) && (v <= 12)) ) { s->month = v; s->sp = ep; return(1); } break; } return(0); } /* * I'm not sure allowing 60 is Right, since leap seconds are dealt with * by adjusting the clock rather than inserting seconds into the time * stream. (The latter is the most right way, but is Hard.) */ static int parse_ss(STATE *s) { long int v; char *ep; switch (*s->sp) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': v = strtol(s->sp,&ep,10); if ( (ep-s->sp == 2) && ((v >= 0) && (v <= 60)) ) { s->sec = v; s->sp = ep; return(1); } break; } return(0); } static int namecmp(const char *a, int al, const char *b, int bl) { int l; l = (al < bl) ? al : bl; return(strncasecmp(a,b,l)?:al-bl); } static int parse_zzz(STATE *s) { int i; int j; int m; int l; l = 0; while (1) { switch (s->sp[l]) { case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': l ++; break; default: i = -1; j = nzones; while (j-i > 1) { int c; m = (i + j) >> 1; c = namecmp(s->sp,l,zonetbl[m].name,zonetbl[m].namelen); if (c == 0) { s->zone = zonetbl[m].offset; s->sp += l; return(1); } if (c < 0) j = m; else i = m; } return(0); break; } } } static int parse_yyyy(STATE *s) { long int v; char *ep; switch (*s->sp) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': v = strtol(s->sp,&ep,10); if ( (ep-s->sp == 4) && ((v >= 1969) && (v <= 2038)) ) { s->year = v; s->sp = ep; return(1); } break; } return(0); } /* * Wish we could use strtoul, but that is a poor fit to this task. In * particular: (a) it ignores whitespace; (b) it allows a sign despite * supposedly being for unsigned values(!!); (c) it allows whitespace; * (d) it considers leading zeros irrelevant and trailing 0s * significant. None of these are suitable for our purposes. */ static unsigned int parsefrac(const char *s, const char **ep) { int nd; unsigned int v; int dv; static const int m[7] = { 100000, 10000, 1000, 100, 10, 1, 0 }; nd = 0; v = 0; while (1) { switch (*s) { case '0': dv = 0; if (0) { case '1': dv = 1; } if (0) { case '2': dv = 2; } if (0) { case '3': dv = 3; } if (0) { case '4': dv = 4; } if (0) { case '5': dv = 5; } if (0) { case '6': dv = 6; } if (0) { case '7': dv = 7; } if (0) { case '8': dv = 8; } if (0) { case '9': dv = 9; } v += m[nd] * dv; if (nd < 6) nd ++; s ++; break; default: *ep = s; return(v); } } } static int parse_unixdate(STATE *s) { STATE save; save = *s; if (!( (ws(s),parse_www(s)) && (ws(s),parse_mmm(s)) && (ws(s),parse_dd(s)) && (ws(s),parse_hh(s)) && (*s->sp == ':') && (s->sp++,1) && parse_mm_min(s) && (*s->sp == ':') && (s->sp++,1) && parse_ss(s) )) { *s = save; return(0); } if ((s->exts & PARSEDATE_EXT_ALLOWFRAC) && (*s->sp == '.')) { const char *ep; unsigned int frac; frac = parsefrac(s->sp+1,&ep); if (ep == s->sp+1) { *s = save; return(0); } if (s->exts & PARSEDATE_EXT_STOREFRAC) *s->fracloc = frac; s->sp = ep; } ws(s); if (parse_zzz(s)) { ws(s); if (parse_yyyy(s)) return(1); } *s = save; return(0); } static int parse_zone(STATE *s) { STATE save; int neg; char *ep; long int v; save = *s; switch (*s->sp) { case '-': neg = 1; break; case '+': neg = 0; break; default: return(parse_zzz(s)); break; } switch (*++s->sp) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': v = strtol(s->sp,&ep,10); if ( (ep-s->sp == 4) && (v%100 < 60) && (v/100 < 24) ) { s->zone = neg ? -v : v; s->sp = ep; return(1); } break; } *s = save; return(0); } static int parse_comment(STATE *s) { STATE save; save = *s; ws(s); if (*s->sp != '(') return(1); while (1) { s->sp ++; if (*s->sp == ')') { s->sp ++; return(1); } if (! *s->sp) { *s = save; return(0); } } } static int parse_time(STATE *s) { STATE save; STATE t; save = *s; if ( parse_hh(s) && (*s->sp == ':') && (s->sp++,1) && parse_mm_min(s) ) { s->sec = 0; t = *s; if (*s->sp == ':') { s->sp ++; if (parse_ss(s)) { if ((s->exts & PARSEDATE_EXT_ALLOWFRAC) && (*s->sp == '.')) { const char *ep; unsigned int frac; frac = parsefrac(s->sp+1,&ep); if (ep == s->sp+1) { *s = t; } else { if (s->exts & PARSEDATE_EXT_STOREFRAC) *s->fracloc = frac; s->sp = ep; } } } else { *s = t; } } t = *s; ws(s); if (parse_zone(s) && parse_comment(s)) { s->have |= HAVE_ZONE; } else { *s = t; } return(1); } *s = save; return(0); } static int parse_date(STATE *s) { STATE save; STATE t; save = *s; do <"fail"> { if (parse_mmm(s)) { /* "mmm dd[[,] yyyy]" */ ws(s); if (! parse_dd(s)) break <"fail">; t = *s; if (*s->sp == ',') s->sp ++; ws(s); if (parse_yyyy(s)) s->have |= HAVE_YEAR; else *s = t; s->have |= HAVE_DATE; return(1); } if (parse_yyyy(s)) { /* "yyyy[-/ ]mm[-/ ]dd" or "yyyy[-/ ]mmm[-/ ]dd" */ switch (*s->sp) { case '-': case '/': s->sp ++; break; case ' ': ws(s); break; } if (!parse_mmm(s) && !parse_mm_mon(s)) break <"fail">; switch (*s->sp) { case '-': case '/': s->sp ++; break; case ' ': ws(s); break; } if (! parse_dd(s)) break <"fail">; s->have |= HAVE_DATE | HAVE_YEAR; return(1); } t = *s; if (parse_mm_mon(s) && (*s->sp == '-') && (s->sp++,1) && parse_dd(s)) { /* "mm-dd" */ s->have |= HAVE_DATE; return(1); } *s = t; /* "dd mmm[[,] yyyy]", "dd-mmm-yyyy", or "ddmmmyyyy" */ if (! parse_dd(s)) break <"fail">; switch (*s->sp) { case ' ': /* "dd mmm[[,] yyyy]" */ ws(s); if (! parse_mmm(s)) break <"fail">; t = *s; if (*s->sp == ',') s->sp ++; ws(s); if (parse_yyyy(s)) s->have |= HAVE_YEAR; else *s = t; return(1); break; case '-': /* "dd-mmm[-yyyy]" */ s->sp ++; if (! parse_mmm(s)) break <"fail">; if (*s->sp == '-') { if (! parse_yyyy(s)) break <"fail">; s->have |= HAVE_YEAR; } s->have |= HAVE_DATE; return(1); break; default: /* "ddmmm[yyyy]" */ if (! parse_mmm(s)) break <"fail">; if (parse_yyyy(s)) s->have |= HAVE_YEAR; s->have |= HAVE_DATE; return(1); break; } } while (0); *s = save; return(0); } static int parse_wdate(STATE *s) { STATE save; save = *s; ws(s); if (parse_www(s)) { s->have |= HAVE_WDAY; if (*s->sp == ',') s->sp ++; } ws(s); if (parse_date(s)) return(1); *s = save; return(0); } static void default_time(STATE *s) { s->hr = 0; s->min = 0; s->sec = 0; } static void default_date(STATE *s) { struct timeval nowtv; time_t nowtt; struct tm nowtm; gettimeofday(&nowtv,0); nowtt = nowtv.tv_sec; localtime_r(&nowtt,&nowtm); s->year = nowtm.tm_year + 1900; s->month = nowtm.tm_mon + 1; s->mday = nowtm.tm_mday; } static void default_year(STATE *s) { struct timeval nowtv; time_t nowtt; struct tm nowtm; gettimeofday(&nowtv,0); nowtt = nowtv.tv_sec; localtime_r(&nowtt,&nowtm); s->year = nowtm.tm_year + 1900; } static int parse_spec(STATE *s) { STATE save; if (s->exts & PARSEDATE_EXT_STOREFRAC) *s->fracloc = 0; ws(s); save = *s; if (*s->sp == '+') { /* must be +ssss - nothing else begins with + */ char *ep; unsigned long int v; unsigned int frac; const char *ep2; v = strtoul(s->sp+1,&ep,0); if (ep == s->sp+1) return(0); frac = 0; if ((s->exts & PARSEDATE_EXT_ALLOWFRAC) && (*ep == '.')) { frac = parsefrac(ep+1,&ep2); if (ep2 == ep+1) return(0); } else { ep2 = ep; } s->sp = skipws(ep2); if (*s->sp) return(0); s->tt = v; s->have |= HAVE_TT; if (s->exts & PARSEDATE_EXT_STOREFRAC) *s->fracloc = frac; return(1); } if (parse_unixdate(s)) return(1); *s = save; if (parse_time(s)) { s->have |= HAVE_TIME; save = *s; ws(s); if (parse_wdate(s)) s->have |= HAVE_DATE; else *s = save; } else if (parse_wdate(s)) { s->have |= HAVE_DATE; save = *s; ws(s); if (parse_time(s)) s->have |= HAVE_TIME; else *s = save; } ws(s); if (*s->sp) return(0); if ((s->have & (HAVE_TIME|HAVE_DATE)) == 0) return(0); return(1); } static int cmp_time(time_t tt, struct tm *(*fn)(const time_t *, struct tm *), STATE *s) { struct tm tm; (*fn)(&tt,&tm); if (tm.tm_year+1900 != s->year) return(tm.tm_year+1900-s->year); if (tm.tm_mon+1 != s->month) return(tm.tm_mon+1-s->month); if (tm.tm_mday != s->mday) return(tm.tm_mday-s->mday); if (tm.tm_hour != s->hr) return(tm.tm_hour-s->hr); if (tm.tm_min != s->min) return(tm.tm_min-s->min); if (tm.tm_sec != s->sec) return(tm.tm_sec-s->sec); return(0); } time_t parsedate_ext(const char *str, unsigned int exts, ...) { STATE s; int offset; unsigned int l; unsigned int h; unsigned int m; int c; struct tm *(*fn)(const time_t *, struct tm *); int *fracloc; va_list ap; va_start(ap,exts); if (exts & PARSEDATE_EXT_STOREFRAC) { fracloc = va_arg(ap,int *); } va_end(ap); tzset(); s.sp = str; s.have = 0; s.exts = exts; s.fracloc = fracloc; if (! parse_spec(&s)) return(-1); if (s.have & HAVE_TT) return(s.tt); if (! (s.have & HAVE_TIME)) default_time(&s); if (! (s.have & HAVE_DATE)) default_date(&s); else if (! (s.have & HAVE_YEAR)) default_year(&s); m = (s.year <= 1970) ? 0 : ( ((s.year-1970)*31556952) + ((s.month-1)*2629746) + ((s.mday-1)*86400) + (s.hr*3600) + (s.min*60) + s.sec ); if (s.have & HAVE_ZONE) { offset = (s.zone < 0) ? ( ((((-s.zone)/100)*60)+((-s.zone)%100))*-60 ) : ( ((( s.zone /100)*60)+( s.zone %100))* 60 ); fn = &gmtime_r; } else { offset = 0; fn = &localtime_r; } if (cmp_time(m+offset,fn,&s) < 0) { l = m; m = 1; do { h = l + m; m <<= 1; } while (cmp_time(h+offset,fn,&s) < 0); } else { h = m; m = 1; do { l = h - m; m <<= 1; } while (cmp_time(l+offset,fn,&s) > 0); } while (h-l > 1) { m = (h + l) >> 1; c = cmp_time(m+offset,fn,&s); if (c == 0) return(m); if (c < 0) l = m; else h = m; } return(h); } time_t parsedate(const char *str) { return(parsedate_ext(str,0)); } #ifdef TESTBED #include int main(void); int main(void) { char ibuf[256]; int il; time_t t; unsigned int exts; int frac; int i; while (1) { printf(" > "); if (! fgets(&ibuf[0],sizeof(ibuf),stdin)) break; il = strlen(&ibuf[0]); if ((il > 0) && (ibuf[il-1] = '\n')) ibuf[--il] = '\0'; exts = 0; if (ibuf[0] == '*') { for <"exts"> (i=1;i; break; } } } else { i = 0; } t = parsedate_ext(&ibuf[i],exts,&frac); printf("%lu = %.24s",(unsigned long int)t,ctime(&t)); if (exts & PARSEDATE_EXT_STOREFRAC) { printf(", fraction = %06u",(unsigned int)frac); } printf("\n"); } return(0); } #endif