krfcdate.cpp
00001 /* 00002 * 00003 * This file is part of the KDE libraries 00004 * Copyright (c) 2000-2002 Waldo Bastian <bastian@kde.org> 00005 * 2002 Rik Hemsley <rik@kde.org> 00006 * 00007 * $Id: krfcdate.cpp 514036 2006-02-27 09:36:44Z osterfeld $ 00008 * 00009 * This library is free software; you can redistribute it and/or 00010 * modify it under the terms of the GNU Library General Public 00011 * License version 2 as published by the Free Software Foundation. 00012 * 00013 * This library is distributed in the hope that it will be useful, 00014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00016 * Library General Public License for more details. 00017 * 00018 * You should have received a copy of the GNU Library General Public License 00019 * along with this library; see the file COPYING.LIB. If not, write to 00020 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00021 * Boston, MA 02110-1301, USA. 00022 **/ 00023 00024 #include <config.h> 00025 00026 #include <sys/param.h> 00027 #include <ctype.h> 00028 #include <stdlib.h> 00029 00030 #include <qstringlist.h> 00031 00032 #include <krfcdate.h> 00033 00034 static unsigned int ymdhms_to_seconds(int year, int mon, int day, int hour, int minute, int second) 00035 { 00036 if (sizeof(time_t) == 4) 00037 { 00038 if ((time_t)-1 < 0) 00039 { 00040 if (year >= 2038) 00041 { 00042 year = 2038; 00043 mon = 0; 00044 day = 1; 00045 hour = 0; 00046 minute = 0; 00047 second = 0; 00048 } 00049 } 00050 else 00051 { 00052 if (year >= 2115) 00053 { 00054 year = 2115; 00055 mon = 0; 00056 day = 1; 00057 hour = 0; 00058 minute = 0; 00059 second = 0; 00060 } 00061 } 00062 } 00063 00064 unsigned int ret = (day - 32075) /* days */ 00065 + 1461L * (year + 4800L + (mon - 14) / 12) / 4 00066 + 367 * (mon - 2 - (mon - 14) / 12 * 12) / 12 00067 - 3 * ((year + 4900L + (mon - 14) / 12) / 100) / 4 00068 - 2440588; 00069 ret = 24*ret + hour; /* hours */ 00070 ret = 60*ret + minute; /* minutes */ 00071 ret = 60*ret + second; /* seconds */ 00072 00073 return ret; 00074 } 00075 00076 static const char haystack[37]="janfebmaraprmayjunjulaugsepoctnovdec"; 00077 00078 // we follow the recommendation of rfc2822 to consider all 00079 // obsolete time zones not listed here equivalent to "-0000" 00080 static const struct { 00081 const char tzName[4]; 00082 int tzOffset; 00083 } known_zones[] = { 00084 { "UT", 0 }, 00085 { "GMT", 0 }, 00086 { "EST", -300 }, 00087 { "EDT", -240 }, 00088 { "CST", -360 }, 00089 { "CDT", -300 }, 00090 { "MST", -420 }, 00091 { "MDT", -360 }, 00092 { "PST", -480 }, 00093 { "PDT", -420 }, 00094 { { 0,0,0,0 }, 0 } 00095 }; 00096 00097 time_t 00098 KRFCDate::parseDate(const QString &_date) 00099 { 00100 if (_date.isEmpty()) 00101 return 0; 00102 00103 // This parse a date in the form: 00104 // Wednesday, 09-Nov-99 23:12:40 GMT 00105 // or 00106 // Sat, 01-Jan-2000 08:00:00 GMT 00107 // or 00108 // Sat, 01 Jan 2000 08:00:00 GMT 00109 // or 00110 // 01 Jan 99 22:00 +0100 (exceptions in rfc822/rfc2822) 00111 // 00112 // We ignore the weekday 00113 // 00114 time_t result = 0; 00115 int offset = 0; 00116 char *newPosStr; 00117 const char *dateString = _date.latin1(); 00118 int day = 0; 00119 char monthStr[4]; 00120 int month = -1; 00121 int year = 0; 00122 int hour = 0; 00123 int minute = 0; 00124 int second = 0; 00125 00126 // Strip leading space 00127 while(*dateString && isspace(*dateString)) 00128 dateString++; 00129 00130 // Strip weekday 00131 while(*dateString && !isdigit(*dateString) && !isspace(*dateString)) 00132 dateString++; 00133 00134 // Strip trailing space 00135 while(*dateString && isspace(*dateString)) 00136 dateString++; 00137 00138 if (!*dateString) 00139 return result; // Invalid date 00140 00141 if (isalpha(*dateString)) 00142 { 00143 // ' Nov 5 1994 18:15:30 GMT' 00144 // Strip leading space 00145 while(*dateString && isspace(*dateString)) 00146 dateString++; 00147 00148 for(int i=0; i < 3;i++) 00149 { 00150 if (!*dateString || (*dateString == '-') || isspace(*dateString)) 00151 return result; // Invalid date 00152 monthStr[i] = tolower(*dateString++); 00153 } 00154 monthStr[3] = '\0'; 00155 00156 newPosStr = (char*)strstr(haystack, monthStr); 00157 00158 if (!newPosStr) 00159 return result; // Invalid date 00160 00161 month = (newPosStr-haystack)/3; // Jan=00, Feb=01, Mar=02, .. 00162 00163 if ((month < 0) || (month > 11)) 00164 return result; // Invalid date 00165 00166 while (*dateString && isalpha(*dateString)) 00167 dateString++; // Skip rest of month-name 00168 } 00169 00170 // ' 09-Nov-99 23:12:40 GMT' 00171 // ' 5 1994 18:15:30 GMT' 00172 day = strtol(dateString, &newPosStr, 10); 00173 dateString = newPosStr; 00174 00175 if ((day < 1) || (day > 31)) 00176 return result; // Invalid date; 00177 00178 if (!*dateString) 00179 return result; // Invalid date 00180 00181 while(*dateString && (isspace(*dateString) || (*dateString == '-'))) 00182 dateString++; 00183 00184 if (month == -1) 00185 { 00186 for(int i=0; i < 3;i++) 00187 { 00188 if (!*dateString || (*dateString == '-') || isspace(*dateString)) 00189 return result; // Invalid date 00190 monthStr[i] = tolower(*dateString++); 00191 } 00192 monthStr[3] = '\0'; 00193 00194 newPosStr = (char*)strstr(haystack, monthStr); 00195 00196 if (!newPosStr) 00197 return result; // Invalid date 00198 00199 month = (newPosStr-haystack)/3; // Jan=00, Feb=01, Mar=02, .. 00200 00201 if ((month < 0) || (month > 11)) 00202 return result; // Invalid date 00203 00204 while (*dateString && isalpha(*dateString)) 00205 dateString++; // Skip rest of month-name 00206 00207 } 00208 00209 // '-99 23:12:40 GMT' 00210 while(*dateString && (isspace(*dateString) || (*dateString == '-'))) 00211 dateString++; 00212 00213 if (!*dateString || !isdigit(*dateString)) 00214 return result; // Invalid date 00215 00216 // '99 23:12:40 GMT' 00217 year = strtol(dateString, &newPosStr, 10); 00218 dateString = newPosStr; 00219 00220 // Y2K: Solve 2 digit years 00221 if ((year >= 0) && (year < 50)) 00222 year += 2000; 00223 00224 if ((year >= 50) && (year < 100)) 00225 year += 1900; // Y2K 00226 00227 if ((year < 1900) || (year > 2500)) 00228 return result; // Invalid date 00229 00230 // Don't fail if the time is missing. 00231 if (*dateString) 00232 { 00233 // ' 23:12:40 GMT' 00234 if (!isspace(*dateString++)) 00235 return result; // Invalid date 00236 00237 hour = strtol(dateString, &newPosStr, 10); 00238 dateString = newPosStr; 00239 00240 if ((hour < 0) || (hour > 23)) 00241 return result; // Invalid date 00242 00243 if (!*dateString) 00244 return result; // Invalid date 00245 00246 // ':12:40 GMT' 00247 if (*dateString++ != ':') 00248 return result; // Invalid date 00249 00250 minute = strtol(dateString, &newPosStr, 10); 00251 dateString = newPosStr; 00252 00253 if ((minute < 0) || (minute > 59)) 00254 return result; // Invalid date 00255 00256 if (!*dateString) 00257 return result; // Invalid date 00258 00259 // ':40 GMT' 00260 if (*dateString != ':' && !isspace(*dateString)) 00261 return result; // Invalid date 00262 00263 // seconds are optional in rfc822 + rfc2822 00264 if (*dateString ==':') { 00265 dateString++; 00266 00267 second = strtol(dateString, &newPosStr, 10); 00268 dateString = newPosStr; 00269 00270 if ((second < 0) || (second > 59)) 00271 return result; // Invalid date 00272 } else { 00273 dateString++; 00274 } 00275 00276 while(*dateString && isspace(*dateString)) 00277 dateString++; 00278 } 00279 00280 // don't fail if the time zone is missing, some 00281 // broken mail-/news-clients omit the time zone 00282 if (*dateString) { 00283 if ((strncasecmp(dateString, "gmt", 3) == 0) || 00284 (strncasecmp(dateString, "utc", 3) == 0)) 00285 { 00286 dateString += 3; 00287 while(*dateString && isspace(*dateString)) 00288 dateString++; 00289 } 00290 00291 if ((*dateString == '+') || (*dateString == '-')) { 00292 offset = strtol(dateString, &newPosStr, 10); 00293 if (abs(offset) < 30) 00294 { 00295 dateString = newPosStr; 00296 00297 offset = offset * 100; 00298 00299 if (*dateString && *(dateString+1)) 00300 { 00301 dateString++; 00302 int minutes = strtol(dateString, &newPosStr, 10); 00303 if (offset > 0) 00304 offset += minutes; 00305 else 00306 offset -= minutes; 00307 } 00308 } 00309 00310 if ((offset < -9959) || (offset > 9959)) 00311 return result; // Invalid date 00312 00313 int sgn = (offset < 0)? -1:1; 00314 offset = abs(offset); 00315 offset = ((offset / 100)*60 + (offset % 100))*sgn; 00316 } else { 00317 for (int i=0; known_zones[i].tzName != 0; i++) { 00318 if (0 == strncasecmp(dateString, known_zones[i].tzName, strlen(known_zones[i].tzName))) { 00319 offset = known_zones[i].tzOffset; 00320 break; 00321 } 00322 } 00323 } 00324 } 00325 00326 result = ymdhms_to_seconds(year, month+1, day, hour, minute, second); 00327 00328 // avoid negative time values 00329 if ((offset > 0) && (offset > result)) 00330 offset = 0; 00331 00332 result -= offset*60; 00333 00334 // If epoch 0 return epoch +1 which is Thu, 01-Jan-70 00:00:01 GMT 00335 // This is so that parse error and valid epoch 0 return values won't 00336 // be the same for sensitive applications... 00337 if (result < 1) result = 1; 00338 00339 return result; 00340 } 00341 00342 time_t 00343 KRFCDate::parseDateISO8601( const QString& input_ ) 00344 { 00345 if (input_.isEmpty()) 00346 return 0; 00347 00348 // These dates look like this: 00349 // YYYY-MM-DDTHH:MM:SS 00350 // But they may also have 0, 1 or 2 suffixes. 00351 // Suffix 1: .secfrac (fraction of second) 00352 // Suffix 2: Either 'Z' or +zone or -zone, where zone is HHMM 00353 00354 unsigned int year = 0; 00355 unsigned int month = 0; 00356 unsigned int mday = 0; 00357 unsigned int hour = 0; 00358 unsigned int min = 0; 00359 unsigned int sec = 0; 00360 00361 int offset = 0; 00362 00363 QString input = input_; 00364 00365 // First find the 'T' separator, if any. 00366 int tPos = input.find('T'); 00367 00368 // If there is no time, no month or no day specified, fill those missing 00369 // fields so that 'input' matches YYYY-MM-DDTHH:MM:SS 00370 if (-1 == tPos) { 00371 const int dashes = input.contains('-'); 00372 if (0 == dashes) { 00373 input += "-01-01"; 00374 } else if (1 == dashes) { 00375 input += "-01"; 00376 } 00377 tPos = input.length(); 00378 input += "T12:00:00"; 00379 } 00380 00381 // Now parse the date part. 00382 00383 QString dateString = input.left(tPos).stripWhiteSpace(); 00384 00385 QString timeString = input.mid(tPos + 1).stripWhiteSpace(); 00386 00387 QStringList l = QStringList::split('-', dateString); 00388 00389 if (l.size() < 3) 00390 return 0; 00391 00392 year = l[0].toUInt(); 00393 month = l[1].toUInt(); 00394 mday = l[2].toUInt(); 00395 00396 // Z suffix means UTC. 00397 if ('Z' == timeString.at(timeString.length() - 1)) { 00398 timeString.remove(timeString.length() - 1, 1); 00399 } 00400 00401 // +zone or -zone suffix (offset from UTC). 00402 00403 int plusPos = timeString.findRev('+'); 00404 00405 if (-1 != plusPos) { 00406 QString offsetString = timeString.mid(plusPos + 1); 00407 00408 offset = offsetString.left(2).toUInt() * 60 + offsetString.right(2).toUInt(); 00409 00410 timeString = timeString.left(plusPos); 00411 } else { 00412 int minusPos = timeString.findRev('-'); 00413 00414 if (-1 != minusPos) { 00415 QString offsetString = timeString.mid(minusPos + 1); 00416 00417 offset = - int(offsetString.left(2).toUInt() * 60 + offsetString.right(2).toUInt()); 00418 00419 timeString = timeString.left(minusPos); 00420 } 00421 } 00422 00423 // secfrac suffix. 00424 int dotPos = timeString.findRev('.'); 00425 00426 if (-1 != dotPos) { 00427 timeString = timeString.left(dotPos); 00428 } 00429 00430 // Now parse the time part. 00431 00432 l = QStringList::split(':', timeString); 00433 00434 if (l.size() < 3) 00435 return 0; 00436 00437 hour = l[0].toUInt(); 00438 min = l[1].toUInt(); 00439 sec = l[2].toUInt(); 00440 00441 time_t result = ymdhms_to_seconds(year, month, mday, hour, min, sec); 00442 00443 // avoid negative time values 00444 if ((offset > 0) && (offset > result)) 00445 offset = 0; 00446 00447 result -= offset*60; 00448 00449 // If epoch 0 return epoch +1 which is Thu, 01-Jan-70 00:00:01 GMT 00450 // This is so that parse error and valid epoch 0 return values won't 00451 // be the same for sensitive applications... 00452 if (result < 1) result = 1; 00453 00454 return result; 00455 } 00456 00457 00458 int KRFCDate::localUTCOffset() 00459 { 00460 time_t timeNow = time((time_t*) 0); 00461 00462 tm *tM = gmtime(&timeNow); 00463 unsigned int timeUTC = ymdhms_to_seconds(tM->tm_year+1900, tM->tm_mon+1, tM->tm_mday, 00464 tM->tm_hour, tM->tm_min, tM->tm_sec); 00465 00466 tM = localtime(&timeNow); 00467 unsigned int timeLocal = ymdhms_to_seconds(tM->tm_year+1900, tM->tm_mon+1, tM->tm_mday, 00468 tM->tm_hour, tM->tm_min, tM->tm_sec); 00469 00470 return ((int)(timeLocal-timeUTC))/60; 00471 } 00472 00473 00474 static const char * const day_names[] = { 00475 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" 00476 }; 00477 00478 static const char * const month_names[] = { 00479 "Jan", "Feb", "Mar", "Apr", "May", "Jun", 00480 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" 00481 }; 00482 00483 00484 QCString KRFCDate::rfc2822DateString(time_t utcTime, int utcOffset) 00485 { 00486 utcTime += utcOffset * 60; 00487 tm *tM = gmtime(&utcTime); 00488 char sgn = (utcOffset < 0) ? '-' : '+'; 00489 int z = (utcOffset < 0) ? -utcOffset : utcOffset; 00490 QCString dateStr; 00491 00492 dateStr.sprintf("%s, %02d %s %04d %02d:%02d:%02d %c%02d%02d", 00493 day_names[tM->tm_wday], tM->tm_mday, 00494 month_names[tM->tm_mon], tM->tm_year+1900, 00495 tM->tm_hour, tM->tm_min, tM->tm_sec, 00496 sgn, z/60%24, z%60); 00497 00498 return dateStr; 00499 } 00500 00501 00502 QCString KRFCDate::rfc2822DateString(time_t utcTime) 00503 { 00504 return rfc2822DateString(utcTime, localUTCOffset()); 00505 }