/***************************************************************************
                             -------------------
    cvs         :
    begin       : Sat Aug 10 2002
    copyright   : (C) 2002, 2003 by Fabian Kaiser and Christian Stimming
    email       : fabian@openhbci.de

 ***************************************************************************
 *                                                                         *
 *   This library is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU Lesser General Public            *
 *   License as published by the Free Software Foundation; either          *
 *   version 2.1 of the License, or (at your option) any later version.    *
 *                                                                         *
 *   This library is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
 *   Lesser General Public License for more details.                       *
 *                                                                         *
 *   You should have received a copy of the GNU Lesser General Public      *
 *   License along with this library; if not, write to the Free Software   *
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston,                 *
 *   MA  02111-1307  USA                                                   *
 *                                                                         *
 ***************************************************************************/

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "ktoblzcheck.h"
#include <fstream>
#include <iostream>
#include <algorithm>
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <cstring> // for strchr()
#include <sqlite3.h>
#ifdef HAVE_DIRENT_H
# include <sys/types.h>
# include <dirent.h>
# include <errno.h>
#endif
#ifdef HAVE_WINDOWS_H
# include <windows.h>
# include <tchar.h>
#endif

// The actual algorithms for number checking are there
#include "algorithms.h"

// This file has the implementations for the class methods. C wrappers
// are in accnum.cc.

#if defined(__MINGW32__) || defined(_WIN32)
# ifndef OS_WIN32
#  define OS_WIN32
# endif
#endif

#ifdef OS_WIN32
# define DIRSEP "\\"
#else
# define DIRSEP "/"
// asctime() doesn't work on win32 for whatever reason
# define HAVE_ASCTIME
#endif

#ifdef ERROR
# undef ERROR
#endif

using namespace std;

// A hard-coded replacement for strptime() because that one is not
// available on windows/mingw.
static void extract_date(const std::string &d, struct tm *p_tm)
{
    char a[5];

    // Make sure the given struct tm is filled with all zero.
    memset(p_tm, 0, sizeof(struct tm));

    // Year
    a[0] = d[0];
    a[1] = d[1];
    a[2] = d[2];
    a[3] = d[3];
    a[4] = '\0';
    p_tm->tm_year = atol(a) - 1900;
    //  cerr << "tm_year=" << p_tm->tm_year << " ";

    // Month
    a[0] = d[4];
    a[1] = d[5];
    a[2] = '\0';
    p_tm->tm_mon = atol(a) - 1;
    //  cerr << "tm_mon=" << p_tm->tm_mon << " ";

    // Day of month
    a[0] = d[6];
    a[1] = d[7];
    p_tm->tm_mday = atol(a);
    //  cerr << "tm_mday=" << p_tm->tm_mday << " ";
}

/// Returns true if the given string starts with the given beginning
static bool starts_with(const std::string &str, const std::string &beginning)
{
    return (str.size() >= beginning.size())
           && (str.substr(0, beginning.size()) == beginning);
}

/// Returns false if the given string ends with the given ending.
static bool ends_with(const std::string &str, const std::string &ending)
{
    return (str.size() >= ending.size())
           && (str.substr(str.size() - ending.size(), str.size()) == ending);
}

/// Predicate for the file names that we accept
static bool accept_filename(const std::string &f)
{
    return (f.size() == std::string("bankdata_20051234.txt").size())
           && starts_with(f, "bankdata_")
           && ends_with(f, ".txt");
}

#ifndef ONLY_EXTRACT_DATE_TEST
const char *AccountNumberCheck::stringEncoding()
{
    return "ISO-8859-15";
}

const char *AccountNumberCheck::libraryVersion()
{
    return VERSION;
}

std::string AccountNumberCheck::bankdata_dir()
{
    return algorithms_get_bankdata_dir();
}

AccountNumberCheck::Record::Record()
{
}

AccountNumberCheck::Record::Record(unsigned long id, const string &meth, const string &name, const string &loc)
    : bankId(std::to_string(id))
    , method(meth)
    , bankName(name)
    , location(loc)
{
}

AccountNumberCheck::Record::Record(const char *id, const char *meth, const char *name, const char *loc)
    : bankId(id ? id : 0)
    , method(meth ? meth : "")
    , bankName(name ? name : "")
    , location(loc ? loc : "")
{
}

std::string AccountNumberCheck::resultToString(Result r)
{
    switch (r) {
    case AccountNumberCheck::OK:
        return "Ok";
    case AccountNumberCheck::ERROR:
        return "ERROR: account and bank do not match";
    case AccountNumberCheck::BANK_NOT_KNOWN:
        return "Bank is unknown";
    default:
    case AccountNumberCheck::UNKNOWN:
        return "Validation algorithm unknown";
    }
}

// ////////////////////////////////////////

AccountNumberCheck::AccountNumberCheck()
    : data() // std::map doesn't take size as argument
{
    std::string filename = "bankdata.de.db";
    if (existDatabase(filename)) {
        try{
            filename = bankdata_dir() + DIRSEP + filename;
            if (isValidDatabase(filename)) {
                readFile(filename);
            } else {
                filename = "bankdata.de.db";
                filename = algorithms_get_default_bankdata_dir() + DIRSEP + filename;
                //std::cerr << "Default: " << filename<< std::endl;
                readFile(filename);
            }
        }
        catch (int i) {
            std::cerr << "Error reading database!" << std::endl;
            exit(1);
        }
    } else {
        std::cerr << "Oops, no bank data file was found in directory \"" << bankdata_dir()
                  << "\"! The ktoblzcheck library will not work." << std::endl;
        exit(1);
    }
}

std::string AccountNumberCheck::getFilenameClosestDateToday() const
{
    std::string filename;
    if (country == "NL") {
        filename = "bankdata.nl.db";
    } else if (country == "CH") {
        filename = "bankdata.ch.db";
    } else {
        filename = "bankdata.de.db";
    }
    filename = AccountNumberCheck::bankdata_dir() + DIRSEP + filename;

    return filename;
}

AccountNumberCheck::AccountNumberCheck(const string &filename)
    : data() // std::map doesn't take size as argument
{
    try{
        readFile(filename);
    }
    catch (int i) {
        std::cerr << "Error reading database!" << std::endl;
        exit(1);
    }
}

AccountNumberCheck *AccountNumberCheck::createChecker(const std::string &country)
{
    if (country == "DE") {
        return new AccountNumberCheck();
    } else if (country == "NL") {
        std::string filename = "bankdata.nl.db";
        if (AccountNumberCheck::existDatabase(filename)) {
            filename = AccountNumberCheck::bankdata_dir() + DIRSEP + filename;
            return new AccountNumberCheck(filename);
        } else {
            std::cerr << "Oops, no bank data file was found in directory \""
                      << AccountNumberCheck::bankdata_dir()
                      << "\"! The ktoblzcheck library will not work." << std::endl;
            exit(1);
        }
    } else if (country == "CH") {
        std::string filename = "bankdata.ch.db";
        if (AccountNumberCheck::existDatabase(filename)) {
            filename = AccountNumberCheck::bankdata_dir() + DIRSEP + filename;
            return new AccountNumberCheck(filename);
        } else {
            std::cerr << "Oops, no bank data file was found in directory \""
                      << AccountNumberCheck::bankdata_dir()
                      << "\"! The ktoblzcheck library will not work." << std::endl;
            exit(1);
        }
    } else {
        std::cerr << country << "Invalid country code! The ktoblzcheck library will not work."
                  << std::endl;
    }
    exit(1);
}

AccountNumberCheck::~AccountNumberCheck()
{
    deleteList();
}

#define BLZ_SIZE    (8+1)
#define METHOD_SIZE (2+1)
#define NAME_SIZE   (58+1)
#define PLACE_SIZE  (35+1)
// From 2006-06-05 onwards the field "PLACE" has 35 instead of 29
// characters!

#define NL_NAME_SIZE 60
#define CH_NAME_SIZE 60

//#define ARRAY_BOUNDS_CHECK 1
#undef ARRAY_BOUNDS_CHECK

#ifdef ARRAY_BOUNDS_CHECK
#  define PLUSONE +1
#else
#  define PLUSONE
#endif

void
AccountNumberCheck::readFile(const string &filename)
{
    // First clear existing data
    if (data.size() > 0) {
        deleteList();
    }

    sqlite3 *db;
    int retcode;
    char *zErrorMsg;
    retcode = sqlite3_open(filename.c_str(), &db);
    if (retcode) {
        std::cerr << "Cannot open database: " << sqlite3_errmsg(db)
                  << std::endl;
        throw -1;
    }
    // else{
    //     std::cerr << "Opened database successfully!\n";
    // }
    std::string sql;
    sql = "SELECT country FROM 'institutions' LIMIT 1";
    retcode
        = sqlite3_exec(db, sql.c_str(), AccountNumberCheck::setCountryCallback, this, &zErrorMsg);
    if (retcode) {
        std::cerr <<zErrorMsg << std::endl;
        throw -1;
    }

    if (country.empty()) {
        throw -1;
    }

    if (country == "DE") {
        sql
            =
                "SELECT country,bankcode,bic,method,name,location,date(valid_upto) FROM 'institutions'";
    } else if (country == "NL") {
        sql = "SELECT country,bankcode,bic,name FROM 'institutions'";
    } else if (country == "CH") {
        sql = "SELECT country,bankcode,bic,name FROM 'institutions'";
    }

    retcode = sqlite3_exec(db, sql.c_str(), AccountNumberCheck::callback, this, &zErrorMsg);
    if (retcode) {
        std::cerr <<zErrorMsg << std::endl;
        throw -1;
    }
    sqlite3_close(db);
}

void
AccountNumberCheck::deleteList()
{
    for (banklist_type::iterator iter = data.begin(); iter != data.end(); iter++) {
        delete iter->second;
    }
    data.clear();
}

unsigned int AccountNumberCheck::bankCount() const
{
    return data.size();
}

void AccountNumberCheck::createIndex()
{
    // not yet implemented; for std::map this isn't necessary anyway.
}

// Function object for the predicate of matching a job result
#if 0
// currently unused; was only used for std::vector et al
class MatchBlz
{
    unsigned long blz;
public:
    MatchBlz(unsigned long bankId)
        : blz(bankId)
    {
    }

    bool operator()(const AccountNumberCheck::Record *r)
    {
        return r && (r->bankId == blz);
    }
};
#endif

const AccountNumberCheck::Record &
AccountNumberCheck::findBank(const string &bankId) const
{
    //unsigned long lbankId = atol(bankId.c_str());
    banklist_type::const_iterator iter;

    // Lookup the object
    iter = data.find(bankId);

    // Did we find it? Yes, return the reference.
    if (iter != data.end()) {
        return *(iter->second);
    } else {
        throw -1;
    }
}

#define NL_MAX_ACCOUNT_ID_SIZE 10
#define CH_MAX_ACCOUNT_ID_SIZE 12
#define DE_MAX_ACCOUNT_ID_SIZE 10

#define NL_BANK_ID_SIZE         4
#define CH_BANK_ID_SIZE         5
#define DE_MAX_BANK_ID_SIZE     8

AccountNumberCheck::Result
AccountNumberCheck::check(const string &bankId, const string &accountId, const string &given_method) const
{
    // Initialize method map, if it has been empty
    if (method_map.empty()) {
        const_cast<AccountNumberCheck *>(this)->initMethodMap();
    }

    if (country == "NL") {
        try{
            Record rec = findBank(bankId);
        } catch (int) {
            return BANK_NOT_KNOWN;
        }

        if (accountId.size() > NL_MAX_ACCOUNT_ID_SIZE || bankId.size() != NL_BANK_ID_SIZE) {
            return ERROR;
        }

        method_map2_t::const_iterator iter = method_map2.find("4!a10!n");
        if (iter != method_map2.end()) {
            return (iter->second)(0, 0, accountId, bankId);
        }

        std::cerr << "AccountNumberCheck::check: Specified method '" << "4!a10!n"
                  << "' is unknown." << std::endl;
        return UNKNOWN;
    }

    if (country == "CH") {
        try{
            Record rec = findBank(bankId);
        } catch (int) {
            return BANK_NOT_KNOWN;
        }

        if (accountId.size() > CH_MAX_ACCOUNT_ID_SIZE || bankId.size() != CH_BANK_ID_SIZE) {
            return ERROR;
        }

        method_map2_t::const_iterator iter = method_map2.find("5!n12!c");
        if (iter != method_map2.end()) {
            return (iter->second)(0, 0, accountId, bankId);
        }

        std::cerr << "AccountNumberCheck::check: Specified method '" << "5!n12!c"
                  << "' is unknown." << std::endl;
        return UNKNOWN;
    }

    int account[10] = {9, 1, 3, 0, 0, 0, 0, 2, 0, 1};
    int weight[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

    string method = given_method;

    if (method.empty()) {
        try {
            Record rec = findBank(bankId);
            method = rec.method;
        } catch (int) {
            // bank was not found, return error if not forced to use a spec. method
            return BANK_NOT_KNOWN;
        }
    }

    // Check account-id length
    if (accountId.size() > DE_MAX_ACCOUNT_ID_SIZE || bankId.size() > DE_MAX_BANK_ID_SIZE) {
        return ERROR;
    }

    // Convert string to array of integers
    number2Array(accountId, account);

    // Note: Originally, the program code for the method was chosen by
    // the "giant if-statement". I thought that this is quite a
    // performance bottleneck, so I changed the code to make one small
    // function for each method, and its lookup is done through a
    // std::map hash table. However, this sped up the number checking
    // only by roughly 15%, so it probably wasn't worth the effort :-(
    // -- cstim, 2005-01-16

    // Find method function
    method_map_t::const_iterator iter = method_map.find(method);
    // Did we find it? Yes, use the returned function.
    if (iter != method_map.end()) {
        return (iter->second)(account, weight);
    }

    // If it hasnt been found, try the second map for methods that also
    // want the accountId and bankId string.
    method_map2_t::const_iterator iter2 = method_map2.find(method);
    // Did we find it? Yes, use the returned function.
    if (iter2 != method_map2.end()) {
        return (iter2->second)(account, weight, accountId, bankId);
    }

    std::cerr << "AccountNumberCheck::check: Specified method '" << method
              << "' is unknown." << std::endl;
    return UNKNOWN;
}

// ////////////////////////////////////////
// Bankdata date validity

typedef std::vector<std::string> StringList;

static StringList all_filenames_from_dir(const std::string &dir, bool print_errormsgs)
{
    StringList result;
#ifdef HAVE_WINDOWS_H
    HANDLE hFind = INVALID_HANDLE_VALUE;
    WIN32_FIND_DATA ffd;
    std::string mydir = dir + "\\*";
    hFind = FindFirstFile(mydir.c_str(), &ffd);
    // List all the files in the directory with some info about them.

    do {
        if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
            continue;
        }
        result.push_back(ffd.cFileName);
    } while (FindNextFile(hFind, &ffd) != 0);

    FindClose(hFind);

#else // HAVE_WINDOWS_H
# ifdef HAVE_DIRENT_H
    DIR *dp = opendir(dir.c_str());
    if (dp) {
        struct dirent *ep;
        while ((ep = readdir(dp)))
        {
            result.push_back(ep->d_name);
            //cerr << "Found dir entry name " << result.back() << std::endl;
        }
        closedir(dp);
    } else {
        int errsv = errno;
        std::string errstr = strerror(errsv);
        if (print_errormsgs) {
            cerr << "Could not read directory \"" << dir << "\": " << errstr << std::endl;
        }
    }
# else // HAVE_DIRENT_H
#  error \
    "OS is not yet supported: Neither POSIX <dirent.h> nor MS windows <windows.h> is available!"
# endif // HAVE_DIRENT_H
#endif // HAVE_WINDOWS_H
    return result;
}

static StringList
lookup_files_from_dir(const std::string &dir, bool (*filename_predicate)(
                          const std::string &), bool print_errormsgs)
{
    // there is surely a STL algorithm for this, but I forgot which
    // one it is (such as copy_if)
    StringList raw = all_filenames_from_dir(dir, print_errormsgs);
    StringList result;
    for (StringList::const_iterator iter = raw.begin();
         iter != raw.end(); ++iter) {
        if (filename_predicate(*iter)) {
            result.push_back(*iter);
            //cerr << "Adding entry name " << result.back() << std::endl;
        }
    }
    return result;
}

bool AccountNumberCheck::isDataValidForDate(time_t date) const
{
    return date >= data_valid_start && date <= data_valid_end;
}

bool AccountNumberCheck::isValidDataAvailable(time_t date) const
{
    if (isDataValidForDate(date)) {
        return true;
    }

    return false;
}

time_t AccountNumberCheck::closestValidData(time_t date) const
{
    if (isDataValidForDate(date)) {
        return date;
    }

    return data_valid_end;
}

bool AccountNumberCheck::loadDataForDate(time_t date)
{
    // Since the method is used to read the closest available datafile,
    // now it doesn't need to do anything as the file is already loaded
    return true;
}

static bool accept_dbname(const std::string &db)
{
    return (db.size() == std::string("bankdata.de.db").size())
           && starts_with(db, "bankdata")
           && ends_with(db, ".db");
}

// Returns true if dbname exists and is a valid SQLite file
bool AccountNumberCheck::isValidDatabase(const std::string &filename)
{
    char buffer[100];
    std::ifstream db(filename, ios::in | ios::binary);
    if (db) {
        db.seekg(0, db.end);
        long length = db.tellg();
        if (length < 100) {
            db.close();
            return false;
        }

        db.seekg(0, db.beg);
        db.read(buffer, 100);

        if (starts_with(buffer, "SQLite format 3")) {
            return true;
        }
    }
    return false;
}

bool AccountNumberCheck::existDatabase(const std::string &dbname)
{
    StringList file_names = lookup_files_from_dir(bankdata_dir(), &accept_dbname,
                                                  false);

    if (file_names.empty()) {
        std::cerr << "Didn't find updated bankdata files! Trying with default installed files.\n" << std::endl;
        file_names = lookup_files_from_dir(algorithms_get_default_bankdata_dir(), &accept_dbname, false);
        if (file_names.empty()) {
            std::cerr << "Oops, no bank data file was found in default directory \"" << algorithms_get_default_bankdata_dir()
                      << "\"! Something is wrong with the installation.\nThe ktoblzcheck library will not work." << std::endl;
            return false;
        }
    }
    bool db_found = false;
    std::string filename = "";
    StringList::const_iterator iter = file_names.begin();
    while (iter != file_names.end())
    {
        filename = *iter;
        ++iter;
        if (filename == dbname) {
            return true;
        }
    }

    std::cerr << "Didn't find updated file! Trying with default installed file.\n" << std::endl;
    file_names = lookup_files_from_dir(algorithms_get_default_bankdata_dir(), &accept_dbname, false);
    if (file_names.empty()) {
        std::cerr << "Oops, no bank data file was found in default directory \"" << algorithms_get_default_bankdata_dir()
                  << "\"! Something is wrong with the installation.\nThe ktoblzcheck library will not work." << std::endl;
        return false;
    }

    db_found = false;
    filename = "";
    iter = file_names.begin();
    while (iter != file_names.end())
    {
        filename = *iter;
        ++iter;
        if (filename == dbname) {
            return true;
        }
    }
    return false;
}

int
AccountNumberCheck::callback(void *objPtr, int argc, char **argv, char **azColName)
{
    return ((AccountNumberCheck *)objPtr)->readDatabase(argc, argv);
}

int
AccountNumberCheck::setCountryCallback(void *objPtr, int argc, char **argv, char **azColName)
{
    return ((AccountNumberCheck *)objPtr)->setCountry(argc, argv);
}

int
AccountNumberCheck::setCountry(int argc, char **argv)
{
    //argc is no. of columns and argv contains the row
    if (argc > 0) {
        std::string dbCountry = argv[0];

        if (dbCountry == "DE" || dbCountry == "NL" || dbCountry == "CH") {
            country = dbCountry;
            return 0;
        }

        std::cerr << "Database of this country is not supported!" << std::endl;
        return 1;
    }

    std::cerr << "No country column in database!" << std::endl;
    return 1;
}

static bool isEntryValidToday(char *valid_upto)
{
    if (valid_upto == NULL) {
        return true;
    }

    std::time_t now = time(0);
    struct tm t_struct;
    char today[20];
    t_struct = *localtime(&now);
    strftime(today, sizeof(today), "%Y-%m-%d", &t_struct);

    // Since both dates are in format YYYY-MM-DD, they can be compared using strcmp.
    if (strcmp(today, valid_upto) > 0) {
        return false;
    }

    return true;
}

int
AccountNumberCheck::readDatabase(int argc, char **argv)
{
    if (country == "DE") {
        if (argc < 7) {
            std::cerr << "Error while reading database!" << std::endl;
            return 1;
        }

        if (isEntryValidToday(argv[6])) {
            //Heap-allocated as some weird stack corruption has been observed in the past in Debian systems
            char *blz = new char[BLZ_SIZE PLUSONE];
            char *method = new char[METHOD_SIZE PLUSONE];
            char *name = new char[NAME_SIZE PLUSONE];
            char *place = new char[PLACE_SIZE PLUSONE];

            memcpy(blz, argv[1], BLZ_SIZE);
            memcpy(method, argv[3], METHOD_SIZE);
            memcpy(name, argv[4], NAME_SIZE);
            memcpy(place, argv[5], PLACE_SIZE);

            Record *newRecord = new Record(blz, method, name, place); // Create new record
            data.insert(data.end(), banklist_type::value_type(newRecord->bankId, newRecord));

            delete[] method;
            delete[] blz;
            delete[] name;
            delete[] place;
        }
    } else if (country == "NL") {
        if (argc < 4) {
            std::cerr << "Error while reading database!" << std::endl;
            return 1;
        }

        char *bankId = new char[NL_BANK_ID_SIZE + 1];
        char *name = new char[NL_NAME_SIZE + 1];

        memcpy(bankId, argv[1], NL_BANK_ID_SIZE + 1);
        memcpy(name, argv[3], NL_NAME_SIZE + 1);

        Record *newRecord = new Record(bankId, "", name, "");
        data.insert(data.end(), banklist_type::value_type(newRecord->bankId, newRecord));

        delete[] bankId;
        delete[] name;
    } else if (country == "CH") {
        if (argc < 4) {
            std::cerr << "Error while reading database!" << std::endl;
            return 1;
        }

        char *bankId = new char[CH_BANK_ID_SIZE + 1];
        char *name = new char[CH_NAME_SIZE + 1];

        memcpy(bankId, argv[1], CH_BANK_ID_SIZE + 1);
        memcpy(name, argv[3], CH_NAME_SIZE + 1);

        Record *newRecord = new Record(bankId, "", name, "");
        data.insert(data.end(), banklist_type::value_type(newRecord->bankId, newRecord));

        delete[] bankId;
        delete[] name;
    }

    return 0;
}

// ////////////////////////////////////////////////////////////

#else // ONLY_EXTRACT_DATE_TEST

int rv = 0;

// This is a small unittest for the extract_date() function at the
// top, because this seems to fail on Mac OS X and this should at
// least be discovered by make check.
int main(int argc, char **argv)
{
    std::string filename = "bankdata_20090102.txt";

    if (!starts_with(filename, "bankdata_")) {
        rv = -1;
    }
    if (!ends_with(filename, ".txt")) {
        rv = -1;
    }
    if (!accept_filename(filename)) {
        rv = -1;
    }

    // Use the part directly after the '_' character as the date
    struct tm tmp_tm;
    extract_date(strchr(filename.c_str(), '_')+1, &tmp_tm);

    // Point to the beginning of day (don't forget this as tmp_tm is
    // uninitialized so far!)
    tmp_tm.tm_hour = 0;
    tmp_tm.tm_min = 0;
    tmp_tm.tm_sec = 0;
#ifdef HAVE_ASCTIME
    // asctime() doesn't work on win32 for whatever reason
    cerr << "Result of extract_date: " << asctime(&tmp_tm) << endl;
#endif
    time_t start_date = mktime(&tmp_tm);
    cerr << "Result of mktime: " << ctime(&start_date) << endl;

    if (start_date == -1) {
        cerr << "extract_date and/or mktime() failed. From "
#ifdef HAVE_ASCTIME
        // asctime() doesn't work on win32 for whatever reason
        "extract_date we got \"" << asctime(&tmp_tm) << "\" and from "
#endif
        "mktime() we got " << ctime(&start_date) << endl;
        rv = -1;
    }
    return rv;
}

#endif // ONLY_EXTRACT_DATE_TEST

// The code for the actual methods is now in methods.cc. For adding a
// method, see the comment at the beginning of that file.
