/*
    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;
}
Example #2
0
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());
			}
		}
	}
}