/* Parse all the date formats that Firefox can. The official format is: expires=ddd(d)?, dd-MMM-yyyy hh:mm:ss GMT But browsers have been supporting a very wide range of date strings. To work on many sites we need to support more then just the official date format. For reference see Firefox's PR_ParseTimeStringToExplodedTime in prtime.c. The Firefox date parser is coded in a very complex way and is slightly over ~700 lines long. While this implementation will be slightly slower for the non standard dates it is smaller, more readable, and maintainable. Or in their own words: "} // else what the hell is this." */ static QDateTime parseDateString(const QByteArray &dateString) { QTime time; // placeholders for values when we are not sure it is a year, month or day int unknown[3] = {-1, -1, -1}; int month = -1; int day = -1; int year = -1; int zoneOffset = -1; // hour:minute:second.ms pm QRegExp timeRx(QLatin1String("(\\d{1,2}):(\\d{1,2})(:(\\d{1,2})|)(\\.(\\d{1,3})|)((\\s{0,}(am|pm))|)")); int at = 0; while (at < dateString.length()) { #ifdef PARSEDATESTRINGDEBUG qDebug() << dateString.mid(at); #endif bool isNum = isNumber(dateString[at]); // Month if (!isNum && checkStaticArray(month, dateString, at, months, sizeof(months)- 1)) { ++month; #ifdef PARSEDATESTRINGDEBUG qDebug() << "Month:" << month; #endif at += 3; continue; } // Zone if (!isNum && zoneOffset == -1 && checkStaticArray(zoneOffset, dateString, at, zones, sizeof(zones)- 1)) { int sign = (at >= 0 && dateString[at - 1] == '-') ? -1 : 1; zoneOffset = sign * zoneOffsets[zoneOffset] * 60 * 60; #ifdef PARSEDATESTRINGDEBUG qDebug() << "Zone:" << month; #endif at += 3; continue; } // Zone offset if (!isNum && (zoneOffset == -1 || zoneOffset == 0) // Can only go after gmt && (dateString[at] == '+' || dateString[at] == '-') && (at == 0 || isWhitespace(dateString[at - 1]) || dateString[at - 1] == ',' || (at >= 3 && (dateString[at - 3] == 'g') && (dateString[at - 2] == 'm') && (dateString[at - 1] == 't')))) { int end = 1; while (end < 5 && dateString.length() > at+end && dateString[at + end] >= '0' && dateString[at + end] <= '9') ++end; int minutes = 0; int hours = 0; switch (end - 1) { case 4: minutes = atoi(dateString.mid(at + 3, 2).constData()); // fall through case 2: hours = atoi(dateString.mid(at + 1, 2).constData()); break; case 1: hours = atoi(dateString.mid(at + 1, 1).constData()); break; default: at += end; continue; } if (end != 1) { int sign = dateString[at] == '-' ? -1 : 1; zoneOffset = sign * ((minutes * 60) + (hours * 60 * 60)); #ifdef PARSEDATESTRINGDEBUG qDebug() << "Zone offset:" << zoneOffset << hours << minutes; #endif at += end; continue; } } // Time if (isNum && time.isNull() && dateString.length() >= at + 3 && (dateString[at + 2] == ':' || dateString[at + 1] == ':')) { // While the date can be found all over the string the format // for the time is set and a nice regexp can be used. int pos = timeRx.indexIn(QLatin1String(dateString), at); if (pos != -1) { QStringList list = timeRx.capturedTexts(); int h = atoi(list.at(1).toLatin1().constData()); int m = atoi(list.at(2).toLatin1().constData()); int s = atoi(list.at(4).toLatin1().constData()); int ms = atoi(list.at(6).toLatin1().constData()); if (h < 12 && !list.at(9).isEmpty()) if (list.at(9) == QLatin1String("pm")) h += 12; time = QTime(h, m, s, ms); #ifdef PARSEDATESTRINGDEBUG qDebug() << "Time:" << list << timeRx.matchedLength(); #endif at += timeRx.matchedLength(); continue; } } // 4 digit Year if (isNum && year == -1 && dateString.length() >= at + 3) { if (isNumber(dateString[at + 1]) && isNumber(dateString[at + 2]) && isNumber(dateString[at + 3])) { year = atoi(dateString.mid(at, 4).constData()); at += 4; #ifdef PARSEDATESTRINGDEBUG qDebug() << "Year:" << year; #endif continue; } } // a one or two digit number // Could be month, day or year if (isNum) { int length = 1; if (dateString.length() > at + 1 && isNumber(dateString[at + 1])) ++length; int x = atoi(dateString.mid(at, length).constData()); if (year == -1 && (x > 31 || x == 0)) { year = x; } else { if (unknown[0] == -1) unknown[0] = x; else if (unknown[1] == -1) unknown[1] = x; else if (unknown[2] == -1) unknown[2] = x; } at += length; #ifdef PARSEDATESTRINGDEBUG qDebug() << "Saving" << x; #endif continue; } // Unknown character, typically a weekday such as 'Mon' ++at; } // Once we are done parsing the string take the digits in unknown // and determine which is the unknown year/month/day int couldBe[3] = { 0, 0, 0 }; int unknownCount = 3; for (int i = 0; i < unknownCount; ++i) { if (unknown[i] == -1) { couldBe[i] = ADAY | AYEAR | AMONTH; unknownCount = i; continue; } if (unknown[i] >= 1) couldBe[i] = ADAY; if (month == -1 && unknown[i] >= 1 && unknown[i] <= 12) couldBe[i] |= AMONTH; if (year == -1) couldBe[i] |= AYEAR; } // For any possible day make sure one of the values that could be a month // can contain that day. // For any possible month make sure one of the values that can be a // day that month can have. // Example: 31 11 06 // 31 can't be a day because 11 and 6 don't have 31 days for (int i = 0; i < unknownCount; ++i) { int currentValue = unknown[i]; bool findMatchingMonth = couldBe[i] & ADAY && currentValue >= 29; bool findMatchingDay = couldBe[i] & AMONTH; if (!findMatchingMonth || !findMatchingDay) continue; for (int j = 0; j < 3; ++j) { if (j == i) continue; for (int k = 0; k < 2; ++k) { if (k == 0 && !(findMatchingMonth && (couldBe[j] & AMONTH))) continue; else if (k == 1 && !(findMatchingDay && (couldBe[j] & ADAY))) continue; int m = currentValue; int d = unknown[j]; if (k == 0) qSwap(m, d); if (m == -1) m = month; bool found = true; switch(m) { case 2: // When we get 29 and the year ends up having only 28 // See date.isValid below // Example: 29 23 Feb if (d <= 29) found = false; break; case 4: case 6: case 9: case 11: if (d <= 30) found = false; break; default: if (d > 0 && d <= 31) found = false; } if (k == 0) findMatchingMonth = found; else if (k == 1) findMatchingDay = found; } } if (findMatchingMonth) couldBe[i] &= ~ADAY; if (findMatchingDay) couldBe[i] &= ~AMONTH; } // First set the year/month/day that have been deduced // and reduce the set as we go along to deduce more for (int i = 0; i < unknownCount; ++i) { int unset = 0; for (int j = 0; j < 3; ++j) { if (couldBe[j] == ADAY && day == -1) { day = unknown[j]; unset |= ADAY; } else if (couldBe[j] == AMONTH && month == -1) { month = unknown[j]; unset |= AMONTH; } else if (couldBe[j] == AYEAR && year == -1) { year = unknown[j]; unset |= AYEAR; } else { // common case break; } couldBe[j] &= ~unset; } } // Now fallback to a standardized order to fill in the rest with for (int i = 0; i < unknownCount; ++i) { if (couldBe[i] & AMONTH && month == -1) month = unknown[i]; else if (couldBe[i] & ADAY && day == -1) day = unknown[i]; else if (couldBe[i] & AYEAR && year == -1) year = unknown[i]; } #ifdef PARSEDATESTRINGDEBUG qDebug() << "Final set" << year << month << day; #endif if (year == -1 || month == -1 || day == -1) { #ifdef PARSEDATESTRINGDEBUG qDebug() << "Parser failure" << year << month << day; #endif return QDateTime(); } // Y2k behavior int y2k = 0; if (year < 70) y2k = 2000; else if (year < 100) y2k = 1900; QDate date(year + y2k, month, day); // When we were given a bad cookie that when parsed // set the day to 29 and the year to one that doesn't // have the 29th of Feb rather then adding the extra // complicated checking earlier just swap here. // Example: 29 23 Feb if (!date.isValid()) date = QDate(day + y2k, month, year); QDateTime dateTime(date, time, Qt::UTC); if (zoneOffset != -1) { dateTime = dateTime.addSecs(zoneOffset); } if (!dateTime.isValid()) return QDateTime(); return dateTime; }
void CLIProcessor::parseCLIArgsPostConfig(const QStringList& argList, QSettings* confSettings) { // Over-ride config file options with command line options // We should catch exceptions from argsGetOptionWithArg... int fullScreen, altitude; float fov; QString landscapeId, homePlanet, longitude, latitude, skyDate, skyTime; QString projectionType, screenshotDir, multiresImage, startupScript; QString lgmode, lgport; int lgoffset; try { bool dumpOpenGLDetails = argsGetOption(argList, "-d", "--dump-opengl-details"); qApp->setProperty("dump_OpenGL_details", dumpOpenGLDetails); fullScreen = argsGetYesNoOption(argList, "-f", "--full-screen", -1); landscapeId = argsGetOptionWithArg(argList, "", "--landscape", "").toString(); homePlanet = argsGetOptionWithArg(argList, "", "--home-planet", "").toString(); altitude = argsGetOptionWithArg(argList, "", "--altitude", -1).toInt(); longitude = argsGetOptionWithArg(argList, "", "--longitude", "").toString(); latitude = argsGetOptionWithArg(argList, "", "--latitude", "").toString(); skyDate = argsGetOptionWithArg(argList, "", "--sky-date", "").toString(); skyTime = argsGetOptionWithArg(argList, "", "--sky-time", "").toString(); fov = argsGetOptionWithArg(argList, "", "--fov", -1.f).toFloat(); projectionType = argsGetOptionWithArg(argList, "", "--projection-type", "").toString(); screenshotDir = argsGetOptionWithArg(argList, "", "--screenshot-dir", "").toString(); multiresImage = argsGetOptionWithArg(argList, "", "--multires-image", "").toString(); startupScript = argsGetOptionWithArg(argList, "", "--startup-script", "").toString(); lgmode = argsGetOptionWithArg(argList, "", "--lg", "").toString(); lgoffset = argsGetOptionWithArg(argList, "", "--lg-offset", 0).toInt(); lgport = argsGetOptionWithArg(argList, "", "--lg-port", "5000").toString(); } catch (std::runtime_error& e) { qCritical() << "ERROR while checking command line options: " << e.what(); exit(0); } // Will be -1 if option is not found, in which case we don't change anything. if (fullScreen==1) confSettings->setValue("video/fullscreen", true); else if (fullScreen==0) confSettings->setValue("video/fullscreen", false); if (!landscapeId.isEmpty()) confSettings->setValue("location_run_once/landscape_name", landscapeId); if (!homePlanet.isEmpty()) confSettings->setValue("location_run_once/home_planet", homePlanet); if (altitude!=-1) confSettings->setValue("location_run_once/altitude", altitude); if (!longitude.isEmpty()) confSettings->setValue("location_run_once/longitude", StelUtils::getDecAngle(longitude)); // Store longitude in radian if (!latitude.isEmpty()) confSettings->setValue("location_run_once/latitude", StelUtils::getDecAngle(latitude)); // Store latitude in radian if (!lgmode.isEmpty()) { confSettings->setValue("lg/mode", lgmode.toUpper()); confSettings->setValue("lg/port", lgport); confSettings->setValue("lg/offset", lgoffset); /// std::cout << "conf values : " << lgmode.toStdString() << " " << lgoffset << std::endl; } else confSettings->setValue("lg/mode", ""); if (!skyDate.isEmpty() || !skyTime.isEmpty()) { // Get the Julian date for the start of the current day // and the extra necessary for the time of day as separate // components. Then if the --sky-date and/or --sky-time flags // are set we over-ride the component, and finally add them to // get the full julian date and set that. // First, lets determine the Julian day number and the part for the time of day QDateTime now = QDateTime::currentDateTime(); double skyDatePart = now.date().toJulianDay(); double skyTimePart = StelUtils::qTimeToJDFraction(now.time()); // Over-ride the skyDatePart if the user specified the date using --sky-date if (!skyDate.isEmpty()) { // validate the argument format, we will tolerate yyyy-mm-dd by removing all -'s QRegExp dateRx("\\d{8}"); if (dateRx.exactMatch(skyDate.remove("-"))) skyDatePart = QDate::fromString(skyDate, "yyyyMMdd").toJulianDay(); else qWarning() << "WARNING: --sky-date argument has unrecognised format (I want yyyymmdd)"; } if (!skyTime.isEmpty()) { QRegExp timeRx("\\d{1,2}:\\d{2}:\\d{2}"); if (timeRx.exactMatch(skyTime)) skyTimePart = StelUtils::qTimeToJDFraction(QTime::fromString(skyTime, "hh:mm:ss")); else qWarning() << "WARNING: --sky-time argument has unrecognised format (I want hh:mm:ss)"; } confSettings->setValue("navigation/startup_time_mode", "preset"); confSettings->setValue("navigation/preset_sky_time", skyDatePart + skyTimePart); } if (!multiresImage.isEmpty()) confSettings->setValue("skylayers/clilayer", multiresImage); else { confSettings->remove("skylayers/clilayer"); } if (!startupScript.isEmpty()) { qApp->setProperty("onetime_startup_script", startupScript); } if (fov>0.0) confSettings->setValue("navigation/init_fov", fov); if (!projectionType.isEmpty()) confSettings->setValue("projection/type", projectionType); if (!screenshotDir.isEmpty()) { try { QString newShotDir = QDir::fromNativeSeparators(argsGetOptionWithArg(argList, "", "--screenshot-dir", "").toString()); if (!newShotDir.isEmpty()) StelFileMgr::setScreenshotDir(newShotDir); } catch (std::runtime_error& e) { qWarning() << "WARNING: problem while setting screenshot directory for --screenshot-dir option: " << e.what(); } } else { const QString& confScreenshotDir = QDir::fromNativeSeparators(confSettings->value("main/screenshot_dir", "").toString()); if (!confScreenshotDir.isEmpty()) { try { StelFileMgr::setScreenshotDir(confScreenshotDir); } catch (std::runtime_error& e) { qWarning() << "WARNING: problem while setting screenshot from config file setting: " << e.what(); } } else { QString screenshotDirSuffix = "/Stellarium"; if (!QStandardPaths::standardLocations(QStandardPaths::PicturesLocation).isEmpty()) screenshotDir = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation)[0].append(screenshotDirSuffix); else screenshotDir = StelFileMgr::getUserDir().append(screenshotDirSuffix); try { StelFileMgr::setScreenshotDir(screenshotDir); confSettings->setValue("main/screenshot_dir", screenshotDir); } catch (std::runtime_error &e) { qDebug("Error: cannot create screenshot directory: %s", e.what()); } } } }