gnucash master: Multiple changes pushed

John Ralls jralls at code.gnucash.org
Sun Jul 3 12:06:32 EDT 2016


Updated	 via  https://github.com/Gnucash/gnucash/commit/5db6419c (commit)
	 via  https://github.com/Gnucash/gnucash/commit/a1b574af (commit)
	 via  https://github.com/Gnucash/gnucash/commit/344de4eb (commit)
	 via  https://github.com/Gnucash/gnucash/commit/51e29e78 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/6a81738e (commit)
	 via  https://github.com/Gnucash/gnucash/commit/704bc835 (commit)
	from  https://github.com/Gnucash/gnucash/commit/cf735d73 (commit)



commit 5db6419cb6f4360e72a0b3bf8b201f1987eff3dd
Merge: 344de4e a1b574a
Author: John Ralls <jralls at ceridwen.us>
Date:   Sun Jul 3 09:05:01 2016 -0700

    Merge branch 'maint'

diff --cc src/engine/Transaction.c
index deb43f9,9547eb3..95d0cfb
--- a/src/engine/Transaction.c
+++ b/src/engine/Transaction.c
@@@ -2356,17 -2254,13 +2357,13 @@@ xaccTransGetDatePostedGDate (const Tran
          /* Can we look up this value in the kvp slot? If yes, use it
           * from there because it doesn't suffer from time zone
           * shifts. */
 -        const KvpValue* kvp_value =
 -            kvp_frame_get_slot(trans->inst.kvp_data, TRANS_DATE_POSTED);
 -        if (kvp_value)
 -            result = kvp_value_get_gdate(kvp_value);
 -        else
 +	GValue v = G_VALUE_INIT;
 +	qof_instance_get_kvp (QOF_INSTANCE (trans), TRANS_DATE_POSTED, &v);
 +        if (G_VALUE_HOLDS_BOXED (&v))
 +             result = *(GDate*)g_value_get_boxed (&v);
 +	if (! g_date_valid (&result))
              result = timespec_to_gdate(xaccTransRetDatePostedTS(trans));
      }
-     else
-     {
-         g_date_clear(&result, 1);
-     }
      return result;
  }
  

commit a1b574af4f2d2357808534e8c3d6e3b83966d658
Author: John Ralls <jralls at ceridwen.us>
Date:   Sun Jul 3 08:56:52 2016 -0700

    Clear the GDate before use.
    
    If the transaction doesn't have the gdate-posted slot and the random
    junk left on the stack where date lives happens to make a valid gdate
    then timespec_to_gdate won't be called and a bogus date will be returned.

diff --git a/src/engine/Transaction.c b/src/engine/Transaction.c
index 0e2a4fc..9547eb3 100644
--- a/src/engine/Transaction.c
+++ b/src/engine/Transaction.c
@@ -2248,6 +2248,7 @@ GDate
 xaccTransGetDatePostedGDate (const Transaction *trans)
 {
     GDate result;
+    g_date_clear (&result, 1);
     if (trans)
     {
         /* Can we look up this value in the kvp slot? If yes, use it
@@ -2260,10 +2261,6 @@ xaccTransGetDatePostedGDate (const Transaction *trans)
         else
             result = timespec_to_gdate(xaccTransRetDatePostedTS(trans));
     }
-    else
-    {
-        g_date_clear(&result, 1);
-    }
     return result;
 }
 

commit 344de4eb378bd088488babd21398964ee25667f1
Merge: cf735d7 51e29e7
Author: John Ralls <jralls at ceridwen.us>
Date:   Sun Jul 3 08:53:04 2016 -0700

    Merge branch 'maint'

diff --cc CMakeLists.txt
index 0664d9f,913834e..a626cf1
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@@ -375,8 -348,8 +375,8 @@@ SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS
  
  
  IF (UNIX)
 -  SET( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror -Wdeclaration-after-statement -Wno-pointer-sign -D_FORTIFY_SOURCE=2 -Wall -Wunused -Wmissing-prototypes -Wmissing-declarations -Wno-unused -std=gnu99")
 -  SET( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-error=deprecated-declarations")
 +  SET( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror -Wdeclaration-after-statement -Wno-pointer-sign -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 -Wall -Wunused -Wmissing-prototypes -Wmissing-declarations  -Wno-unused")
-   SET( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-error=deprecated-declarations -std=gnu99 -O2")
++  SET( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-error=deprecated-declarations -std=gnu99")
  ENDIF (UNIX)
  IF (MINGW)
    SET( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror -Wdeclaration-after-statement -Wno-pointer-sign -Wall -Wunused -Wmissing-prototypes -Wmissing-declarations  -Wno-unused -std=gnu99")
diff --cc src/backend/sql/gnc-transaction-sql.cpp
index a3d93dc,0000000..5d6f6d7
mode 100644,000000..100644
--- a/src/backend/sql/gnc-transaction-sql.cpp
+++ b/src/backend/sql/gnc-transaction-sql.cpp
@@@ -1,1548 -1,0 +1,1550 @@@
 +/********************************************************************
 + * gnc-transaction-sql.c: load and save data to SQL                 *
 + *                                                                  *
 + * This program is free software; you can redistribute it and/or    *
 + * modify it under the terms of the GNU General Public License as   *
 + * published by the Free Software Foundation; either version 2 of   *
 + * the License, or (at your option) any later version.              *
 + *                                                                  *
 + * This program 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 General Public License for more details.                     *
 + *                                                                  *
 + * You should have received a copy of the GNU General Public License*
 + * along with this program; if not, contact:                        *
 + *                                                                  *
 + * Free Software Foundation           Voice:  +1-617-542-5942       *
 + * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
 + * Boston, MA  02110-1301,  USA       gnu at gnu.org                   *
 +\********************************************************************/
 +/** @file gnc-transaction-sql.c
 + *  @brief load and save data to SQL
 + *  @author Copyright (c) 2006-2008 Phil Longstaff <plongstaff at rogers.com>
 + *
 + * This file implements the top-level QofBackend API for saving/
 + * restoring data to/from an SQL db
 + */
 +#include <guid.hpp>
 +extern "C"
 +{
 +#include "config.h"
 +
 +#include <glib/gi18n.h>
 +
 +#include "qof.h"
 +#include "qofquery-p.h"
 +#include "qofquerycore-p.h"
 +
 +#include "Account.h"
 +#include "Transaction.h"
++#include <Scrub.h>
 +#include "gnc-lot.h"
 +#include "engine-helpers.h"
 +#include "gnc-commodity.h"
 +#include "gnc-engine.h"
 +
 +#ifdef S_SPLINT_S
 +#include "splint-defs.h"
 +#endif
 +}
 +
 +#include "escape.h"
 +
 +#include "gnc-backend-sql.h"
 +#include "gnc-transaction-sql.h"
 +#include "gnc-commodity-sql.h"
 +#include "gnc-slots-sql.h"
 +
 +#define SIMPLE_QUERY_COMPILATION 1
 +#define LOAD_TRANSACTIONS_AS_NEEDED 0
 +
 +static QofLogModule log_module = G_LOG_DOMAIN;
 +
 +#define TRANSACTION_TABLE "transactions"
 +#define TX_TABLE_VERSION 3
 +#define SPLIT_TABLE "splits"
 +#define SPLIT_TABLE_VERSION 4
 +
 +typedef struct
 +{
 +    GncSqlBackend* be;
 +    const GncGUID* guid;
 +    gboolean is_ok;
 +} split_info_t;
 +
 +#define TX_MAX_NUM_LEN 2048
 +#define TX_MAX_DESCRIPTION_LEN 2048
 +
 +static const GncSqlColumnTableEntry tx_col_table[] =
 +{
 +    { "guid",          CT_GUID,           0,                      COL_NNUL | COL_PKEY, "guid" },
 +    { "currency_guid", CT_COMMODITYREF,   0,                      COL_NNUL,          "currency" },
 +    { "num",           CT_STRING,         TX_MAX_NUM_LEN,         COL_NNUL,          "num" },
 +    { "post_date",     CT_TIMESPEC,       0,                      0,                 "post-date" },
 +    { "enter_date",    CT_TIMESPEC,       0,                      0,                 "enter-date" },
 +    { "description",   CT_STRING,         TX_MAX_DESCRIPTION_LEN, 0,                 "description" },
 +    { NULL }
 +};
 +
 +static  gpointer get_split_reconcile_state (gpointer pObject);
 +static void set_split_reconcile_state (gpointer pObject,  gpointer pValue);
 +static void set_split_lot (gpointer pObject,  gpointer pLot);
 +
 +#define SPLIT_MAX_MEMO_LEN 2048
 +#define SPLIT_MAX_ACTION_LEN 2048
 +
 +static const GncSqlColumnTableEntry split_col_table[] =
 +{
 +    { "guid",            CT_GUID,         0,                    COL_NNUL | COL_PKEY, "guid" },
 +    { "tx_guid",         CT_TXREF,        0,                    COL_NNUL,          "transaction" },
 +    { "account_guid",    CT_ACCOUNTREF,   0,                    COL_NNUL,          "account" },
 +    { "memo",            CT_STRING,       SPLIT_MAX_MEMO_LEN,   COL_NNUL,          "memo" },
 +    { "action",          CT_STRING,       SPLIT_MAX_ACTION_LEN, COL_NNUL,          "action" },
 +    {
 +        "reconcile_state", CT_STRING,       1,                    COL_NNUL,          NULL, NULL,
 +        (QofAccessFunc)get_split_reconcile_state, set_split_reconcile_state
 +    },
 +    { "reconcile_date",  CT_TIMESPEC,     0,                    0,                 "reconcile-date" },
 +    { "value",           CT_NUMERIC,      0,                    COL_NNUL,          "value" },
 +    { "quantity",        CT_NUMERIC,      0,                    COL_NNUL,          "amount" },
 +    {
 +        "lot_guid",        CT_LOTREF,       0,                    0,                 NULL, NULL,
 +        (QofAccessFunc)xaccSplitGetLot, set_split_lot
 +    },
 +    { NULL }
 +};
 +
 +static const GncSqlColumnTableEntry post_date_col_table[] =
 +{
 +    { "post_date", CT_TIMESPEC, 0, 0, "post-date" },
 +    { NULL }
 +};
 +
 +static const GncSqlColumnTableEntry account_guid_col_table[] =
 +{
 +    { "account_guid", CT_ACCOUNTREF, 0, COL_NNUL, "account" },
 +    { NULL }
 +};
 +
 +static const GncSqlColumnTableEntry tx_guid_col_table[] =
 +{
 +    { "tx_guid", CT_GUID, 0, 0, "guid" },
 +    { NULL }
 +};
 +
 +/* ================================================================= */
 +
 +static  gpointer
 +get_split_reconcile_state (gpointer pObject)
 +{
 +    static gchar c[2];
 +
 +    g_return_val_if_fail (pObject != NULL, NULL);
 +    g_return_val_if_fail (GNC_IS_SPLIT (pObject), NULL);
 +
 +    c[0] = xaccSplitGetReconcile (GNC_SPLIT (pObject));
 +    c[1] = '\0';
 +    return (gpointer)c;
 +}
 +
 +static void
 +set_split_reconcile_state (gpointer pObject,  gpointer pValue)
 +{
 +    const gchar* s = (const gchar*)pValue;
 +
 +    g_return_if_fail (pObject != NULL);
 +    g_return_if_fail (GNC_IS_SPLIT (pObject));
 +    g_return_if_fail (pValue != NULL);
 +
 +    xaccSplitSetReconcile (GNC_SPLIT (pObject), s[0]);
 +}
 +
 +static void
 +set_split_lot (gpointer pObject,  gpointer pLot)
 +{
 +    GNCLot* lot;
 +    Split* split;
 +
 +    g_return_if_fail (pObject != NULL);
 +    g_return_if_fail (GNC_IS_SPLIT (pObject));
 +
 +    if (pLot == NULL) return;
 +
 +    g_return_if_fail (GNC_IS_LOT (pLot));
 +
 +    split = GNC_SPLIT (pObject);
 +    lot = GNC_LOT (pLot);
 +    gnc_lot_add_split (lot, split);
 +}
 +
 +static  Split*
 +load_single_split (GncSqlBackend* be, GncSqlRow* row)
 +{
 +    const GncGUID* guid;
 +    GncGUID split_guid;
 +    Split* pSplit = NULL;
 +    gboolean bad_guid = FALSE;
 +
 +    g_return_val_if_fail (be != NULL, NULL);
 +    g_return_val_if_fail (row != NULL, NULL);
 +
 +    guid = gnc_sql_load_guid (be, row);
 +    if (guid == NULL) return NULL;
 +    if (guid_equal (guid, guid_null ()))
 +    {
 +        PWARN ("Bad GUID, creating new");
 +        bad_guid = TRUE;
 +        split_guid = guid_new_return ();
 +    }
 +    else
 +    {
 +        split_guid = *guid;
 +        pSplit = xaccSplitLookup (&split_guid, be->book);
 +    }
 +
 +    if (pSplit == NULL)
 +    {
 +        pSplit = xaccMallocSplit (be->book);
 +    }
 +
 +    /* If the split is dirty, don't overwrite it */
 +    if (!qof_instance_is_dirty (QOF_INSTANCE (pSplit)))
 +    {
 +        gnc_sql_load_object (be, row, GNC_ID_SPLIT, pSplit, split_col_table);
 +    }
 +
 +    /*# -ifempty */
 +    if (pSplit != xaccSplitLookup (&split_guid, be->book))
 +    {
 +        gchar guidstr[GUID_ENCODING_LENGTH + 1];
 +        guid_to_string_buff (qof_instance_get_guid (pSplit), guidstr);
 +        PERR ("A malformed split with id %s was found in the dataset.", guidstr);
 +        qof_backend_set_error (&be->be, ERR_BACKEND_DATA_CORRUPT);
 +        pSplit = NULL;
 +    }
 +    return pSplit;
 +}
 +
 +static void
 +load_splits_for_tx_list (GncSqlBackend* be, GList* list)
 +{
 +    GString* sql;
 +    GncSqlResult* result;
 +
 +    g_return_if_fail (be != NULL);
 +
 +    if (list == NULL) return;
 +
 +    sql = g_string_sized_new (40 + (GUID_ENCODING_LENGTH + 3) * g_list_length (
 +                                  list));
 +    g_string_append_printf (sql, "SELECT * FROM %s WHERE %s IN (", SPLIT_TABLE,
 +                            tx_guid_col_table[0].col_name);
 +    (void)gnc_sql_append_guid_list_to_sql (sql, list, G_MAXUINT);
 +    (void)g_string_append (sql, ")");
 +
 +    // Execute the query and load the splits
 +    result = gnc_sql_execute_select_sql (be, sql->str);
 +    if (result != NULL)
 +    {
 +        GList* split_list = NULL;
 +        GncSqlRow* row;
 +
 +        row = gnc_sql_result_get_first_row (result);
 +        while (row != NULL)
 +        {
 +            Split* s;
 +            s = load_single_split (be, row);
 +            if (s != NULL)
 +            {
 +                split_list = g_list_prepend (split_list, s);
 +            }
 +            row = gnc_sql_result_get_next_row (result);
 +        }
 +
 +        if (split_list != NULL)
 +        {
 +            gnc_sql_slots_load_for_list (be, split_list);
 +            g_list_free (split_list);
 +        }
 +
 +        gnc_sql_result_dispose (result);
 +    }
 +    (void)g_string_free (sql, TRUE);
 +}
 +
 +static  Transaction*
 +load_single_tx (GncSqlBackend* be, GncSqlRow* row)
 +{
 +    const GncGUID* guid;
 +    GncGUID tx_guid;
 +    Transaction* pTx;
 +
 +    g_return_val_if_fail (be != NULL, NULL);
 +    g_return_val_if_fail (row != NULL, NULL);
 +
 +    guid = gnc_sql_load_guid (be, row);
 +    if (guid == NULL) return NULL;
 +    tx_guid = *guid;
 +
 +    // Don't overwrite the transaction if it's already been loaded (and possibly modified).
 +    pTx = xaccTransLookup (&tx_guid, be->book);
 +    if (pTx != NULL)
 +    {
 +        return NULL;
 +    }
 +
 +    pTx = xaccMallocTransaction (be->book);
 +    xaccTransBeginEdit (pTx);
 +    gnc_sql_load_object (be, row, GNC_ID_TRANS, pTx, tx_col_table);
 +
 +    if (pTx != xaccTransLookup (&tx_guid, be->book))
 +    {
 +        gchar guidstr[GUID_ENCODING_LENGTH + 1];
 +        guid_to_string_buff (qof_instance_get_guid (pTx), guidstr);
 +        PERR ("A malformed transaction with id %s was found in the dataset.", guidstr);
 +        qof_backend_set_error (&be->be, ERR_BACKEND_DATA_CORRUPT);
 +        pTx = NULL;
 +    }
 +
 +    return pTx;
 +}
 +
 +/**
 + * Structure to hold start/end balances for each account.  The values are
 + * saved before splits are loaded, and then used to adjust the start balances
 + * so that the end balances (which are calculated and correct on initial load)
 + * are unchanged.
 + */
 +typedef struct
 +{
 +    Account* acc;
 +    gnc_numeric start_bal;
 +    gnc_numeric end_bal;
 +    gnc_numeric start_cleared_bal;
 +    gnc_numeric end_cleared_bal;
 +    gnc_numeric start_reconciled_bal;
 +    gnc_numeric end_reconciled_bal;
 +} full_acct_balances_t;
 +
 +/**
 + * Executes a transaction query statement and loads the transactions and all
 + * of the splits.
 + *
 + * @param be SQL backend
 + * @param stmt SQL statement
 + */
 +static void
 +query_transactions (GncSqlBackend* be, GncSqlStatement* stmt)
 +{
 +    GncSqlResult* result;
 +
 +    g_return_if_fail (be != NULL);
 +    g_return_if_fail (stmt != NULL);
 +
 +    result = gnc_sql_execute_select_statement (be, stmt);
 +    if (result != NULL)
 +    {
 +        GList* tx_list = NULL;
 +        GList* node;
 +        GncSqlRow* row;
 +        Transaction* tx;
 +#if LOAD_TRANSACTIONS_AS_NEEDED
 +        GSList* bal_list = NULL;
 +        GSList* nextbal;
 +        Account* root = gnc_book_get_root_account (be->book);
 +
 +        qof_event_suspend ();
 +        xaccAccountBeginEdit (root);
 +
 +        // Save the start/ending balances (balance, cleared and reconciled) for
 +        // every account.
 +        gnc_account_foreach_descendant (gnc_book_get_root_account (be->primary_book),
 +                                        save_account_balances,
 +                                        &bal_list);
 +#endif
 +
 +        // Load the transactions
 +        row = gnc_sql_result_get_first_row (result);
 +        while (row != NULL)
 +        {
 +            tx = load_single_tx (be, row);
 +            if (tx != NULL)
 +            {
 +                tx_list = g_list_prepend (tx_list, tx);
++                xaccTransScrubPostedDate (tx);
 +            }
 +            row = gnc_sql_result_get_next_row (result);
 +        }
 +        gnc_sql_result_dispose (result);
 +
 +        // Load all splits and slots for the transactions
 +        if (tx_list != NULL)
 +        {
 +            gnc_sql_slots_load_for_list (be, tx_list);
 +            load_splits_for_tx_list (be, tx_list);
 +        }
 +
 +        // Commit all of the transactions
 +        for (node = tx_list; node != NULL; node = node->next)
 +        {
 +            Transaction* pTx = GNC_TRANSACTION (node->data);
 +            xaccTransCommitEdit (pTx);
 +        }
 +        g_list_free (tx_list);
 +
 +#if LOAD_TRANSACTIONS_AS_NEEDED
 +        // Update the account balances based on the loaded splits.  If the end
 +        // balance has changed, update the start balance so that the end
 +        // balance is the same as it was before the splits were loaded.
 +        // Repeat for cleared and reconciled balances.
 +        for (nextbal = bal_list; nextbal != NULL; nextbal = nextbal->next)
 +        {
 +            full_acct_balances_t* balns = (full_acct_balances_t*)nextbal->data;
 +            gnc_numeric* pnew_end_bal;
 +            gnc_numeric* pnew_end_c_bal;
 +            gnc_numeric* pnew_end_r_bal;
 +            gnc_numeric adj;
 +
 +            g_object_get (balns->acc,
 +                          "end-balance", &pnew_end_bal,
 +                          "end-cleared-balance", &pnew_end_c_bal,
 +                          "end-reconciled-balance", &pnew_end_r_bal,
 +                          NULL);
 +
 +            qof_instance_increase_editlevel (balns - acc);
 +            if (!gnc_numeric_eq (*pnew_end_bal, balns->end_bal))
 +            {
 +                adj = gnc_numeric_sub (balns->end_bal, *pnew_end_bal,
 +                                       GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
 +                balns->start_bal = gnc_numeric_add (balns->start_bal, adj,
 +                                                    GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
 +                g_object_set (balns->acc, "start-balance", &balns->start_bal, NULL);
 +                qof_instance_decrease_editlevel (balns - acc);
 +            }
 +            if (!gnc_numeric_eq (*pnew_end_c_bal, balns->end_cleared_bal))
 +            {
 +                adj = gnc_numeric_sub (balns->end_cleared_bal, *pnew_end_c_bal,
 +                                       GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
 +                balns->start_cleared_bal = gnc_numeric_add (balns->start_cleared_bal, adj,
 +                                                            GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
 +                g_object_set (balns->acc, "start-cleared-balance", &balns->start_cleared_bal,
 +                              NULL);
 +            }
 +            if (!gnc_numeric_eq (*pnew_end_r_bal, balns->end_reconciled_bal))
 +            {
 +                adj = gnc_numeric_sub (balns->end_reconciled_bal, *pnew_end_r_bal,
 +                                       GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
 +                balns->start_reconciled_bal = gnc_numeric_add (balns->start_reconciled_bal,
 +                                                               adj,
 +                                                               GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
 +                g_object_set (balns->acc, "start-reconciled-balance",
 +                              &balns->start_reconciled_bal, NULL);
 +            }
 +            qof_instance_decrease_editlevel (balns - acc);
 +            xaccAccountRecomputeBalance (balns->acc);
 +            g_free (pnew_end_bal);
 +            g_free (pnew_end_c_bal);
 +            g_free (pnew_end_r_bal);
 +            g_free (balns);
 +        }
 +        if (bal_list != NULL)
 +        {
 +            g_slist_free (bal_list);
 +        }
 +
 +        xaccAccountCommitEdit (root);
 +        qof_event_resume ();
 +#endif
 +    }
 +}
 +
 +/* ================================================================= */
 +/**
 + * Creates the transaction and split tables.
 + *
 + * @param be SQL backend
 + */
 +static void
 +create_transaction_tables (GncSqlBackend* be)
 +{
 +    gint version;
 +    gboolean ok;
 +
 +    g_return_if_fail (be != NULL);
 +
 +    version = gnc_sql_get_table_version (be, TRANSACTION_TABLE);
 +    if (version == 0)
 +    {
 +        (void)gnc_sql_create_table (be, TRANSACTION_TABLE, TX_TABLE_VERSION,
 +                                    tx_col_table);
 +        ok = gnc_sql_create_index (be, "tx_post_date_index", TRANSACTION_TABLE,
 +                                   post_date_col_table);
 +        if (!ok)
 +        {
 +            PERR ("Unable to create index\n");
 +        }
 +    }
 +    else if (version < TX_TABLE_VERSION)
 +    {
 +        /* Upgrade:
 +            1->2: 64 bit int handling
 +            2->3: allow dates to be NULL
 +        */
 +        gnc_sql_upgrade_table (be, TRANSACTION_TABLE, tx_col_table);
 +        (void)gnc_sql_set_table_version (be, TRANSACTION_TABLE, TX_TABLE_VERSION);
 +        PINFO ("Transactions table upgraded from version %d to version %d\n", version,
 +               TX_TABLE_VERSION);
 +    }
 +
 +    version = gnc_sql_get_table_version (be, SPLIT_TABLE);
 +    if (version == 0)
 +    {
 +        (void)gnc_sql_create_table (be, SPLIT_TABLE, SPLIT_TABLE_VERSION,
 +                                    split_col_table);
 +        ok = gnc_sql_create_index (be, "splits_tx_guid_index", SPLIT_TABLE,
 +                                   tx_guid_col_table);
 +        if (!ok)
 +        {
 +            PERR ("Unable to create index\n");
 +        }
 +        ok = gnc_sql_create_index (be, "splits_account_guid_index", SPLIT_TABLE,
 +                                   account_guid_col_table);
 +        if (!ok)
 +        {
 +            PERR ("Unable to create index\n");
 +        }
 +    }
 +    else if (version < SPLIT_TABLE_VERSION)
 +    {
 +
 +        /* Upgrade:
 +           1->2: 64 bit int handling
 +           3->4: Split reconcile date can be NULL */
 +        gnc_sql_upgrade_table (be, SPLIT_TABLE, split_col_table);
 +        ok = gnc_sql_create_index (be, "splits_tx_guid_index", SPLIT_TABLE,
 +                                   tx_guid_col_table);
 +        if (!ok)
 +        {
 +            PERR ("Unable to create index\n");
 +        }
 +        ok = gnc_sql_create_index (be, "splits_account_guid_index", SPLIT_TABLE,
 +                                   account_guid_col_table);
 +        if (!ok)
 +        {
 +            PERR ("Unable to create index\n");
 +        }
 +        (void)gnc_sql_set_table_version (be, SPLIT_TABLE, SPLIT_TABLE_VERSION);
 +        PINFO ("Splits table upgraded from version %d to version %d\n", version,
 +               SPLIT_TABLE_VERSION);
 +    }
 +}
 +/* ================================================================= */
 +/**
 + * Callback function to delete slots for a split
 + *
 + * @param data Split
 + * @param user_data split_info_t structure contain operation info
 + */
 +static void
 +delete_split_slots_cb (gpointer data, gpointer user_data)
 +{
 +    split_info_t* split_info = (split_info_t*)user_data;
 +    Split* pSplit = GNC_SPLIT (data);
 +
 +    g_return_if_fail (data != NULL);
 +    g_return_if_fail (GNC_IS_SPLIT (data));
 +    g_return_if_fail (user_data != NULL);
 +
 +    if (split_info->is_ok)
 +    {
 +        split_info->is_ok = gnc_sql_slots_delete (split_info->be,
 +                                                  qof_instance_get_guid (QOF_INSTANCE (pSplit)));
 +    }
 +}
 +
 +/**
 + * Deletes all of the splits for a transaction
 + *
 + * @param be SQL backend
 + * @param pTx Transaction
 + * @return TRUE if successful, FALSE if unsuccessful
 + */
 +static gboolean
 +delete_splits (GncSqlBackend* be, Transaction* pTx)
 +{
 +    split_info_t split_info;
 +
 +    g_return_val_if_fail (be != NULL, FALSE);
 +    g_return_val_if_fail (pTx != NULL, FALSE);
 +
 +    if (!gnc_sql_do_db_operation (be, OP_DB_DELETE, SPLIT_TABLE,
 +                                  SPLIT_TABLE, pTx, tx_guid_col_table))
 +    {
 +        return FALSE;
 +    }
 +    split_info.be = be;
 +    split_info.is_ok = TRUE;
 +
 +    g_list_foreach (xaccTransGetSplitList (pTx), delete_split_slots_cb,
 +                    &split_info);
 +
 +    return split_info.is_ok;
 +}
 +
 +/**
 + * Commits a split to the database
 + *
 + * @param be SQL backend
 + * @param inst Split
 + * @return TRUE if successful, FALSE if error
 + */
 +static gboolean
 +commit_split (GncSqlBackend* be, QofInstance* inst)
 +{
 +    E_DB_OPERATION op;
 +    gboolean is_infant;
 +    gboolean is_ok;
 +    GncGUID* guid = (GncGUID*)qof_instance_get_guid (inst);
 +
 +    g_return_val_if_fail (inst != NULL, FALSE);
 +    g_return_val_if_fail (be != NULL, FALSE);
 +
 +    is_infant = qof_instance_get_infant (inst);
 +    if (qof_instance_get_destroying (inst))
 +    {
 +        op = OP_DB_DELETE;
 +    }
 +    else if (be->is_pristine_db || is_infant)
 +    {
 +        op = OP_DB_INSERT;
 +    }
 +    else
 +    {
 +        op = OP_DB_UPDATE;
 +    }
 +
 +    if (guid_equal (guid, guid_null ()))
 +    {
 +        *guid = guid_new_return ();
 +        qof_instance_set_guid (inst, guid);
 +    }
 +
 +    is_ok = gnc_sql_do_db_operation (be, op, SPLIT_TABLE, GNC_ID_SPLIT,
 +                                     inst, split_col_table);
 +
 +    if (is_ok && !qof_instance_get_destroying (inst))
 +    {
 +        is_ok = gnc_sql_slots_save (be, guid, is_infant, inst);
 +    }
 +
 +    return is_ok;
 +}
 +
 +static void
 +save_split_cb (gpointer data, gpointer user_data)
 +{
 +    split_info_t* split_info = (split_info_t*)user_data;
 +    Split* pSplit = GNC_SPLIT (data);
 +
 +    g_return_if_fail (data != NULL);
 +    g_return_if_fail (GNC_IS_SPLIT (data));
 +    g_return_if_fail (user_data != NULL);
 +
 +    if (split_info->is_ok)
 +    {
 +        split_info->is_ok = commit_split (split_info->be, QOF_INSTANCE (pSplit));
 +    }
 +}
 +
 +static gboolean
 +save_splits (GncSqlBackend* be, const GncGUID* tx_guid, SplitList* pSplitList)
 +{
 +    split_info_t split_info;
 +
 +    g_return_val_if_fail (be != NULL, FALSE);
 +    g_return_val_if_fail (tx_guid != NULL, FALSE);
 +    g_return_val_if_fail (pSplitList != NULL, FALSE);
 +
 +    split_info.be = be;
 +    split_info.guid = tx_guid;
 +    split_info.is_ok = TRUE;
 +    g_list_foreach (pSplitList, save_split_cb, &split_info);
 +
 +    return split_info.is_ok;
 +}
 +
 +static gboolean
 +save_transaction (GncSqlBackend* be, Transaction* pTx, gboolean do_save_splits)
 +{
 +    const GncGUID* guid;
 +    E_DB_OPERATION op;
 +    gboolean is_infant;
 +    QofInstance* inst;
 +    gboolean is_ok = TRUE;
 +    const char* err = NULL;
 +
 +    g_return_val_if_fail (be != NULL, FALSE);
 +    g_return_val_if_fail (pTx != NULL, FALSE);
 +
 +    inst = QOF_INSTANCE (pTx);
 +    is_infant = qof_instance_get_infant (inst);
 +    if (qof_instance_get_destroying (inst))
 +    {
 +        op = OP_DB_DELETE;
 +    }
 +    else if (be->is_pristine_db || is_infant)
 +    {
 +        op = OP_DB_INSERT;
 +    }
 +    else
 +    {
 +        op = OP_DB_UPDATE;
 +    }
 +
 +    if (op != OP_DB_DELETE)
 +    {
 +        gnc_commodity* commodity = xaccTransGetCurrency (pTx);
 +        // Ensure the commodity is in the db
 +        is_ok = gnc_sql_save_commodity (be, commodity);
 +        if (! is_ok)
 +        {
 +            err = "Commodity save failed: Probably an invalid or missing currency";
 +            qof_backend_set_error (&be->be, ERR_BACKEND_DATA_CORRUPT);
 +        }
 +    }
 +
 +    if (is_ok)
 +    {
 +        is_ok = gnc_sql_do_db_operation (be, op, TRANSACTION_TABLE, GNC_ID_TRANS, pTx,
 +                                         tx_col_table);
 +        if (! is_ok)
 +        {
 +            err = "Transaction header save failed. Check trace log for SQL errors";
 +        }
 +    }
 +
 +    if (is_ok)
 +    {
 +        // Commit slots and splits
 +        guid = qof_instance_get_guid (inst);
 +        if (!qof_instance_get_destroying (inst))
 +        {
 +            is_ok = gnc_sql_slots_save (be, guid, is_infant, inst);
 +            if (! is_ok)
 +            {
 +                err = "Slots save failed. Check trace log for SQL errors";
 +            }
 +            if (is_ok && do_save_splits)
 +            {
 +                is_ok = save_splits (be, guid, xaccTransGetSplitList (pTx));
 +                if (! is_ok)
 +                {
 +                    err = "Split save failed. Check trace log for SQL errors";
 +                }
 +            }
 +        }
 +        else
 +        {
 +            is_ok = gnc_sql_slots_delete (be, guid);
 +            if (! is_ok)
 +            {
 +                err = "Slots delete failed. Check trace log for SQL errors";
 +            }
 +            if (is_ok)
 +            {
 +                is_ok = delete_splits (be, pTx);
 +                if (! is_ok)
 +                {
 +                    err = "Split delete failed. Check trace log for SQL errors";
 +                }
 +            }
 +        }
 +    }
 +    if (! is_ok)
 +    {
 +        Split* split = xaccTransGetSplit (pTx, 0);
 +        Account* acc = xaccSplitGetAccount (split);
 +        /* FIXME: This needs to be implemented
 +        const char *message1 = "Transaction %s dated %s in account %s not saved due to %s.%s";
 +        const char *message2 = "\nDatabase may be corrupted, check your data carefully.";
 +        qof_error_format_secondary_text( GTK_MESSAGE_DIALOG( msg ),
 +                              message1,
 +                             xaccTransGetDescription( pTx ),
 +                              qof_print_date( xaccTransGetDate( pTx ) ),
 +                              xaccAccountGetName( acc ),
 +                              err,
 +                              message2 );
 +        */
 +        PERR ("Transaction %s dated %s in account %s not saved due to %s.\n",
 +              xaccTransGetDescription (pTx),
 +              qof_print_date (xaccTransGetDate (pTx)),
 +              xaccAccountGetName (acc),
 +              err);
 +    }
 +    return is_ok;
 +}
 +
 +gboolean
 +gnc_sql_save_transaction (GncSqlBackend* be, QofInstance* inst)
 +{
 +    g_return_val_if_fail (be != NULL, FALSE);
 +    g_return_val_if_fail (inst != NULL, FALSE);
 +    g_return_val_if_fail (GNC_IS_TRANS (inst), FALSE);
 +
 +    return save_transaction (be, GNC_TRANS (inst), /* do_save_splits */TRUE);
 +}
 +
 +static gboolean
 +commit_transaction (GncSqlBackend* be, QofInstance* inst)
 +{
 +    g_return_val_if_fail (be != NULL, FALSE);
 +    g_return_val_if_fail (inst != NULL, FALSE);
 +    g_return_val_if_fail (GNC_IS_TRANS (inst), FALSE);
 +
 +    return save_transaction (be, GNC_TRANS (inst), /* do_save_splits */FALSE);
 +}
 +
 +/* ================================================================= */
 +/**
 + * Loads all transactions for an account.
 + *
 + * @param be SQL backend
 + * @param account Account
 + */
 +void gnc_sql_transaction_load_tx_for_account (GncSqlBackend* be,
 +                                              Account* account)
 +{
 +    const GncGUID* guid;
 +    gchar guid_buf[GUID_ENCODING_LENGTH + 1];
 +    gchar* query_sql;
 +    GncSqlStatement* stmt;
 +
 +    g_return_if_fail (be != NULL);
 +    g_return_if_fail (account != NULL);
 +
 +    guid = qof_instance_get_guid (QOF_INSTANCE (account));
 +    (void)guid_to_string_buff (guid, guid_buf);
 +    query_sql = g_strdup_printf (
 +                    "SELECT DISTINCT t.* FROM %s AS t, %s AS s WHERE s.tx_guid=t.guid AND s.account_guid ='%s'",
 +                    TRANSACTION_TABLE, SPLIT_TABLE, guid_buf);
 +    stmt = gnc_sql_create_statement_from_sql (be, query_sql);
 +    g_free (query_sql);
 +    if (stmt != NULL)
 +    {
 +        query_transactions (be, stmt);
 +        gnc_sql_statement_dispose (stmt);
 +    }
 +}
 +
 +/**
 + * Loads all transactions.  This might be used during a save-as operation to ensure that
 + * all data is in memory and ready to be saved.
 + *
 + * @param be SQL backend
 + */
 +void gnc_sql_transaction_load_all_tx (GncSqlBackend* be)
 +{
 +    gchar* query_sql;
 +    GncSqlStatement* stmt;
 +
 +    g_return_if_fail (be != NULL);
 +
 +    query_sql = g_strdup_printf ("SELECT * FROM %s", TRANSACTION_TABLE);
 +    stmt = gnc_sql_create_statement_from_sql (be, query_sql);
 +    g_free (query_sql);
 +    if (stmt != NULL)
 +    {
 +        query_transactions (be, stmt);
 +        gnc_sql_statement_dispose (stmt);
 +    }
 +}
 +
 +static void
 +convert_query_comparison_to_sql (QofQueryPredData* pPredData,
 +                                 gboolean isInverted, GString* sql)
 +{
 +    if (pPredData->how == QOF_COMPARE_LT
 +        || (isInverted && pPredData->how == QOF_COMPARE_GTE))
 +    {
 +        g_string_append (sql, "<");
 +    }
 +    else if (pPredData->how == QOF_COMPARE_LTE
 +             || (isInverted && pPredData->how == QOF_COMPARE_GT))
 +    {
 +        g_string_append (sql, "<=");
 +    }
 +    else if (pPredData->how == QOF_COMPARE_EQUAL
 +             || (isInverted && pPredData->how == QOF_COMPARE_NEQ))
 +    {
 +        g_string_append (sql, "=");
 +    }
 +    else if (pPredData->how == QOF_COMPARE_GT
 +             || (isInverted && pPredData->how == QOF_COMPARE_LTE))
 +    {
 +        g_string_append (sql, ">");
 +    }
 +    else if (pPredData->how == QOF_COMPARE_GTE
 +             || (isInverted && pPredData->how == QOF_COMPARE_LT))
 +    {
 +        g_string_append (sql, ">=");
 +    }
 +    else if (pPredData->how == QOF_COMPARE_NEQ
 +             || (isInverted && pPredData->how == QOF_COMPARE_EQUAL))
 +    {
 +        g_string_append (sql, "~=");
 +    }
 +    else
 +    {
 +        PERR ("Unknown comparison type\n");
 +        g_string_append (sql, "??");
 +    }
 +}
 +
 +static void
 +convert_query_term_to_sql (const GncSqlBackend* be, const gchar* fieldName,
 +                           QofQueryTerm* pTerm, GString* sql)
 +{
 +    QofQueryPredData* pPredData;
 +    gboolean isInverted;
 +
 +    g_return_if_fail (pTerm != NULL);
 +    g_return_if_fail (sql != NULL);
 +
 +    pPredData = qof_query_term_get_pred_data (pTerm);
 +    isInverted = qof_query_term_is_inverted (pTerm);
 +
 +    if (g_strcmp0 (pPredData->type_name, QOF_TYPE_GUID) == 0)
 +    {
 +        query_guid_t guid_data = (query_guid_t)pPredData;
 +        GList* guid_entry;
 +
 +        g_string_append (sql, "(");
 +        g_string_append (sql, fieldName);
 +
 +        switch (guid_data->options)
 +        {
 +        case QOF_GUID_MATCH_ANY:
 +            if (isInverted) g_string_append (sql, " NOT IN (");
 +            else g_string_append (sql, " IN (");
 +            break;
 +
 +        case QOF_GUID_MATCH_NONE:
 +            if (isInverted) g_string_append (sql, " IN (");
 +            else g_string_append (sql, " NOT IN (");
 +            break;
 +
 +        default:
 +            PERR ("Unexpected GncGUID match type: %d\n", guid_data->options);
 +        }
 +
 +        for (guid_entry = guid_data->guids; guid_entry != NULL;
 +             guid_entry = guid_entry->next)
 +        {
 +            gchar guid_buf[GUID_ENCODING_LENGTH + 1];
 +
 +            if (guid_entry != guid_data->guids) g_string_append (sql, ",");
 +            (void)guid_to_string_buff (static_cast<GncGUID*> (guid_entry->data),
 +                                       guid_buf);
 +            g_string_append_printf (sql, "'%s'", guid_buf);
 +        }
 +        g_string_append (sql, "))");
 +
 +    }
 +    else if (g_strcmp0 (pPredData->type_name, QOF_TYPE_CHAR) == 0)
 +    {
 +        query_char_t char_data = (query_char_t)pPredData;
 +        int i;
 +
 +        if (isInverted)
 +        {
 +            g_string_append (sql, "NOT(");
 +        }
 +        if (char_data->options == QOF_CHAR_MATCH_NONE)
 +        {
 +            g_string_append (sql, "NOT ");
 +        }
 +        g_string_append (sql, "(");
 +        for (i = 0; char_data->char_list[i] != '\0'; i++)
 +        {
 +            if (i != 0)
 +            {
 +                g_string_append (sql, " OR ");
 +            }
 +            g_string_append (sql, fieldName);
 +            g_string_append (sql, " = '");
 +            g_string_append_c (sql, char_data->char_list[i]);
 +            g_string_append (sql, "'");
 +        }
 +        g_string_append (sql, ") ");
 +        if (isInverted)
 +        {
 +            g_string_append (sql, ") ");
 +        }
 +
 +    }
 +    else if (g_strcmp0 (pPredData->type_name, QOF_TYPE_STRING) == 0)
 +    {
 +        query_string_t string_data = (query_string_t)pPredData;
 +        sqlEscape* escape = sqlEscape_new ();
 +
 +        if (isInverted)
 +        {
 +            g_string_append (sql, "NOT(");
 +        }
 +        if (pPredData->how == QOF_COMPARE_NEQ)
 +        {
 +            g_string_append (sql, "NOT(");
 +        }
 +        g_string_append (sql, fieldName);
 +        if (string_data->is_regex ||
 +            string_data->options == QOF_STRING_MATCH_CASEINSENSITIVE)
 +        {
 +            PWARN ("String is_regex || option = QOF_STRING_MATCH_INSENSITIVE\n");
 +        }
 +//          g_string_append( sql, " ~" );
 +//      } else {
 +        g_string_append (sql, " =");
 +//      }
 +//      if( string_data->options == QOF_STRING_MATCH_CASEINSENSITIVE ) {
 +//          g_string_append( sql, "*" );
 +//      }
 +        g_string_append (sql, "'");
 +        g_string_append (sql, sqlEscapeString (escape, string_data->matchstring));
 +        g_string_append (sql, "'");
 +        if (pPredData->how == QOF_COMPARE_NEQ)
 +        {
 +            g_string_append (sql, ")");
 +        }
 +        if (isInverted)
 +        {
 +            g_string_append (sql, ")");
 +        }
 +        sqlEscape_destroy (escape);
 +
 +    }
 +    else
 +    {
 +        g_string_append (sql, "(");
 +        g_string_append (sql, fieldName);
 +        convert_query_comparison_to_sql (pPredData, isInverted, sql);
 +
 +        if (strcmp (pPredData->type_name, QOF_TYPE_NUMERIC) == 0)
 +        {
 +            query_numeric_t pData = (query_numeric_t)pPredData;
 +            double d = gnc_numeric_to_double (pData->amount);
 +
 +            g_string_append_printf (sql, "%f", d);
 +
 +        }
 +        else if (g_strcmp0 (pPredData->type_name, QOF_TYPE_DATE) == 0)
 +        {
 +            query_date_t date_data = (query_date_t)pPredData;
 +            gchar* datebuf;
 +
 +            datebuf = gnc_sql_convert_timespec_to_string (be, date_data->date);
 +            g_string_append_printf (sql, "'%s'", datebuf);
 +
 +        }
 +        else if (strcmp (pPredData->type_name, QOF_TYPE_INT32) == 0)
 +        {
 +            query_int32_t pData = (query_int32_t)pPredData;
 +
 +            g_string_append_printf (sql, "%d", pData->val);
 +
 +        }
 +        else if (strcmp (pPredData->type_name, QOF_TYPE_INT64) == 0)
 +        {
 +            query_int64_t pData = (query_int64_t)pPredData;
 +
 +            g_string_append_printf (sql, "%" G_GINT64_FORMAT, pData->val);
 +
 +        }
 +        else if (strcmp (pPredData->type_name, QOF_TYPE_DOUBLE) == 0)
 +        {
 +            query_double_t pData = (query_double_t)pPredData;
 +
 +            g_string_append_printf (sql, "%f", pData->val);
 +
 +        }
 +        else if (strcmp (pPredData->type_name, QOF_TYPE_BOOLEAN) == 0)
 +        {
 +            query_boolean_t pData = (query_boolean_t)pPredData;
 +
 +            g_string_append_printf (sql, "%d", pData->val);
 +
 +        }
 +        else
 +        {
 +            PERR ("Unknown query predicate type: %s\n", pPredData->type_name);
 +        }
 +
 +        g_string_append (sql, ")");
 +    }
 +}
 +
 +typedef struct
 +{
 +    GncSqlStatement* stmt;
 +    gboolean has_been_run;
 +} split_query_info_t;
 +
 +#define TX_GUID_CHECK 0
 +
 +G_GNUC_UNUSED static  gpointer
 +compile_split_query (GncSqlBackend* be, QofQuery* query)
 +{
 +    split_query_info_t* query_info = NULL;
 +    gchar* query_sql;
 +
 +    g_return_val_if_fail (be != NULL, NULL);
 +    g_return_val_if_fail (query != NULL, NULL);
 +
 +    query_info = static_cast<decltype (query_info)> (
 +                     g_malloc (sizeof (split_query_info_t)));
 +    g_assert (query_info != NULL);
 +    query_info->has_been_run = FALSE;
 +
 +    if (qof_query_has_terms (query))
 +    {
 +        GList* orterms = qof_query_get_terms (query);
 +        GList* orTerm;
 +        GString* sql = g_string_new ("");
 +        gboolean need_OR = FALSE;
 +
 +        for (orTerm = orterms; orTerm != NULL; orTerm = orTerm->next)
 +        {
 +            GList* andterms = (GList*)orTerm->data;
 +            GList* andTerm;
 +            gboolean need_AND = FALSE;
 +#if TX_GUID_CHECK
 +            gboolean has_tx_guid_check = FALSE;
 +#endif
 +            if (need_OR)
 +            {
 +                g_string_append (sql, " OR ");
 +            }
 +            g_string_append (sql, "(");
 +            for (andTerm = andterms; andTerm != NULL; andTerm = andTerm->next)
 +            {
 +                QofQueryTerm* term;
 +                GSList* paramPath;
 +                gboolean unknownPath = FALSE;
 +
 +                term = (QofQueryTerm*)andTerm->data;
 +                paramPath = qof_query_term_get_param_path (term);
 +                const char* path = static_cast<decltype (path)> (paramPath->data);
 +                const char* next_path =
 +                    static_cast<decltype (next_path)> (paramPath->next->data);
 +                if (strcmp (path, QOF_PARAM_BOOK) == 0) continue;
 +
 +#if SIMPLE_QUERY_COMPILATION
 +                if (strcmp (path, SPLIT_ACCOUNT) != 0 ||
 +                    strcmp (next_path, QOF_PARAM_GUID) != 0) continue;
 +#endif
 +
 +                if (need_AND) g_string_append (sql, " AND ");
 +
 +                if (strcmp (path, SPLIT_ACCOUNT) == 0 &&
 +                    strcmp (next_path, QOF_PARAM_GUID) == 0)
 +                {
 +                    convert_query_term_to_sql (be, "s.account_guid", term, sql);
 +#if SIMPLE_QUERY_COMPILATION
 +                    goto done_compiling_query;
 +#endif
 +
 +                }
 +                else if (strcmp (path, SPLIT_RECONCILE) == 0)
 +                {
 +                    convert_query_term_to_sql (be, "s.reconcile_state", term, sql);
 +
 +                }
 +                else if (strcmp (path, SPLIT_TRANS) == 0)
 +                {
 +#if TX_GUID_CHECK
 +                    if (!has_tx_guid_check)
 +                    {
 +                        g_string_append (sql, "(splits.tx_guid = transactions.guid) AND ");
 +                        has_tx_guid_check = TRUE;
 +                    }
 +#endif
 +                    if (strcmp (next_path, TRANS_DATE_POSTED) == 0)
 +                    {
 +                        convert_query_term_to_sql (be, "t.post_date", term, sql);
 +                    }
 +                    else if (strcmp (next_path, TRANS_DESCRIPTION) == 0)
 +                    {
 +                        convert_query_term_to_sql (be, "t.description", term, sql);
 +                    }
 +                    else
 +                    {
 +                        unknownPath = TRUE;
 +                    }
 +
 +                }
 +                else if (strcmp (path, SPLIT_VALUE) == 0)
 +                {
 +                    convert_query_term_to_sql (be, "s.value_num/s.value_denom", term, sql);
 +
 +                }
 +                else
 +                {
 +                    unknownPath = TRUE;
 +                }
 +
 +                if (unknownPath)
 +                {
 +                    GString* name = g_string_new ((gchar*)paramPath->data);
 +                    while (paramPath->next != NULL)
 +                    {
 +                        g_string_append (name, ".");
 +                        g_string_append (name, next_path);
 +                        paramPath = paramPath->next;
 +                    }
 +                    PERR ("Unknown SPLIT query field: %s\n", name->str);
 +                    g_string_free (name, TRUE);
 +                }
 +                need_AND = TRUE;
 +            }
 +
 +            /* If the last char in the string is a '(', then for some reason, there were
 +               no terms added to the SQL.  If so, remove it and ignore the OR term. */
 +            if (sql->str[sql->len - 1] == '(')
 +            {
 +                g_string_truncate (sql, sql->len - 1);
 +                need_OR = FALSE;
 +            }
 +            else
 +            {
 +                g_string_append (sql, ")");
 +                need_OR = TRUE;
 +            }
 +        }
 +
 +#if SIMPLE_QUERY_COMPILATION
 +done_compiling_query:
 +#endif
 +        if (sql->len != 0)
 +        {
 +#if SIMPLE_QUERY_COMPILATION
 +            g_string_append (sql, ")");
 +#endif
 +            query_sql = g_strdup_printf (
 +                            "SELECT DISTINCT t.* FROM %s AS t, %s AS s WHERE s.tx_guid=t.guid AND %s",
 +                            TRANSACTION_TABLE, SPLIT_TABLE, sql->str);
 +        }
 +        else
 +        {
 +            query_sql = g_strdup_printf ("SELECT * FROM %s", TRANSACTION_TABLE);
 +        }
 +        query_info->stmt = gnc_sql_create_statement_from_sql (be, query_sql);
 +
 +        g_string_free (sql, TRUE);
 +        g_free (query_sql);
 +
 +    }
 +    else
 +    {
 +        query_sql = g_strdup_printf ("SELECT * FROM %s", TRANSACTION_TABLE);
 +        query_info->stmt = gnc_sql_create_statement_from_sql (be, query_sql);
 +        g_free (query_sql);
 +    }
 +
 +    return query_info;
 +}
 +
 +G_GNUC_UNUSED static void
 +run_split_query (GncSqlBackend* be, gpointer pQuery)
 +{
 +    split_query_info_t* query_info = (split_query_info_t*)pQuery;
 +
 +    g_return_if_fail (be != NULL);
 +    g_return_if_fail (pQuery != NULL);
 +
 +    if (!query_info->has_been_run)
 +    {
 +        query_transactions (be, query_info->stmt);
 +        query_info->has_been_run = TRUE;
 +        gnc_sql_statement_dispose (query_info->stmt);
 +        query_info->stmt = NULL;
 +    }
 +}
 +
 +G_GNUC_UNUSED static void
 +free_split_query (GncSqlBackend* be, gpointer pQuery)
 +{
 +    g_return_if_fail (be != NULL);
 +    g_return_if_fail (pQuery != NULL);
 +
 +    g_free (pQuery);
 +}
 +
 +/* ----------------------------------------------------------------- */
 +typedef struct
 +{
 +    const GncSqlBackend* be;
 +    Account* acct;
 +    char reconcile_state;
 +    gnc_numeric balance;
 +} single_acct_balance_t;
 +
 +static void
 +set_acct_bal_account_from_guid (gpointer pObject, gpointer pValue)
 +{
 +    single_acct_balance_t* bal = (single_acct_balance_t*)pObject;
 +    const GncGUID* guid = (const GncGUID*)pValue;
 +
 +    g_return_if_fail (pObject != NULL);
 +    g_return_if_fail (pValue != NULL);
 +
 +    bal->acct = xaccAccountLookup (guid, bal->be->book);
 +}
 +
 +static void
 +set_acct_bal_reconcile_state (gpointer pObject, gpointer pValue)
 +{
 +    single_acct_balance_t* bal = (single_acct_balance_t*)pObject;
 +    const gchar* s = (const gchar*)pValue;
 +
 +    g_return_if_fail (pObject != NULL);
 +    g_return_if_fail (pValue != NULL);
 +
 +    bal->reconcile_state = s[0];
 +}
 +
 +static void
 +set_acct_bal_balance (gpointer pObject, gnc_numeric value)
 +{
 +    single_acct_balance_t* bal = (single_acct_balance_t*)pObject;
 +
 +    g_return_if_fail (pObject != NULL);
 +
 +    bal->balance = value;
 +}
 +
 +static const GncSqlColumnTableEntry acct_balances_col_table[] =
 +{
 +    { "account_guid",    CT_GUID,    0, 0, NULL, NULL, NULL, (QofSetterFunc)set_acct_bal_account_from_guid },
 +    { "reconcile_state", CT_STRING,  1, 0, NULL, NULL, NULL, (QofSetterFunc)set_acct_bal_reconcile_state },
 +    { "quantity",        CT_NUMERIC, 0, 0, NULL, NULL, NULL, (QofSetterFunc)set_acct_bal_balance },
 +    { NULL }
 +};
 +
 +G_GNUC_UNUSED static  single_acct_balance_t*
 +load_single_acct_balances (const GncSqlBackend* be, GncSqlRow* row)
 +{
 +    single_acct_balance_t* bal = NULL;
 +
 +    g_return_val_if_fail (be != NULL, NULL);
 +    g_return_val_if_fail (row != NULL, NULL);
 +
 +    bal = static_cast<decltype (bal)> (g_malloc (sizeof (single_acct_balance_t)));
 +    g_assert (bal != NULL);
 +
 +    bal->be = be;
 +    gnc_sql_load_object (be, row, NULL, bal, acct_balances_col_table);
 +
 +    return bal;
 +}
 +
 +GSList*
 +gnc_sql_get_account_balances_slist (GncSqlBackend* be)
 +{
 +#if LOAD_TRANSACTIONS_AS_NEEDED
 +    GncSqlResult* result;
 +    GncSqlStatement* stmt;
 +    gchar* buf;
 +    GSList* bal_slist = NULL;
 +
 +    g_return_val_if_fail (be != NULL, NULL);
 +
 +    buf = g_strdup_printf ("SELECT account_guid, reconcile_state, sum(quantity_num) as quantity_num, quantity_denom FROM %s GROUP BY account_guid, reconcile_state, quantity_denom ORDER BY account_guid, reconcile_state",
 +                           SPLIT_TABLE);
 +    stmt = gnc_sql_create_statement_from_sql (be, buf);
 +    g_assert (stmt != NULL);
 +    g_free (buf);
 +    result = gnc_sql_execute_select_statement (be, stmt);
 +    gnc_sql_statement_dispose (stmt);
 +    if (result != NULL)
 +    {
 +        GncSqlRow* row;
 +        acct_balances_t* bal = NULL;
 +
 +        row = gnc_sql_result_get_first_row (result);
 +        while (row != NULL)
 +        {
 +            single_acct_balance_t* single_bal;
 +
 +            // Get the next reconcile state balance and merge with other balances
 +            single_bal = load_single_acct_balances (be, row);
 +            if (single_bal != NULL)
 +            {
 +                if (bal != NULL && bal->acct != single_bal->acct)
 +                {
 +                    bal->cleared_balance = gnc_numeric_add (bal->cleared_balance,
 +                                                            bal->reconciled_balance,
 +                                                            GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
 +                    bal->balance = gnc_numeric_add (bal->balance, bal->cleared_balance,
 +                                                    GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
 +                    bal_slist = g_slist_append (bal_slist, bal);
 +                    bal = NULL;
 +                }
 +                if (bal == NULL)
 +                {
 +                    bal = g_malloc ((gsize)sizeof (acct_balances_t));
 +                    g_assert (bal != NULL);
 +
 +                    bal->acct = single_bal->acct;
 +                    bal->balance = gnc_numeric_zero ();
 +                    bal->cleared_balance = gnc_numeric_zero ();
 +                    bal->reconciled_balance = gnc_numeric_zero ();
 +                }
 +                if (single_bal->reconcile_state == 'n')
 +                {
 +                    bal->balance = gnc_numeric_add (bal->balance, single_bal->balance,
 +                                                    GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
 +                }
 +                else if (single_bal->reconcile_state == 'c')
 +                {
 +                    bal->cleared_balance = gnc_numeric_add (bal->cleared_balance,
 +                                                            single_bal->balance,
 +                                                            GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
 +                }
 +                else if (single_bal->reconcile_state == 'y')
 +                {
 +                    bal->reconciled_balance = gnc_numeric_add (bal->reconciled_balance,
 +                                                               single_bal->balance,
 +                                                               GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
 +                }
 +                g_free (single_bal);
 +            }
 +            row = gnc_sql_result_get_next_row (result);
 +        }
 +
 +        // Add the final balance
 +        if (bal != NULL)
 +        {
 +            bal->cleared_balance = gnc_numeric_add (bal->cleared_balance,
 +                                                    bal->reconciled_balance,
 +                                                    GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
 +            bal->balance = gnc_numeric_add (bal->balance, bal->cleared_balance,
 +                                            GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD);
 +            bal_slist = g_slist_append (bal_slist, bal);
 +        }
 +        gnc_sql_result_dispose (result);
 +    }
 +
 +    return bal_slist;
 +#else
 +    return NULL;
 +#endif
 +}
 +
 +/* ----------------------------------------------------------------- */
 +static void
 +load_tx_guid (const GncSqlBackend* be, GncSqlRow* row,
 +              QofSetterFunc setter, gpointer pObject,
 +              const GncSqlColumnTableEntry* table_row)
 +{
 +    const GValue* val;
 +    GncGUID guid;
 +    Transaction* tx;
 +    const gchar* guid_str;
 +
 +    g_return_if_fail (be != NULL);
 +    g_return_if_fail (row != NULL);
 +    g_return_if_fail (pObject != NULL);
 +    g_return_if_fail (table_row != NULL);
 +
 +    val = gnc_sql_row_get_value_at_col_name (row, table_row->col_name);
 +    g_assert (val != NULL);
 +    guid_str = g_value_get_string (val);
 +    if (guid_str != NULL)
 +    {
 +        (void)string_to_guid (guid_str, &guid);
 +        tx = xaccTransLookup (&guid, be->book);
 +
 +        // If the transaction is not found, try loading it
 +        if (tx == NULL)
 +        {
 +            gchar* buf;
 +            GncSqlStatement* stmt;
 +
 +            buf = g_strdup_printf ("SELECT * FROM %s WHERE guid='%s'",
 +                                   TRANSACTION_TABLE, guid_str);
 +            stmt = gnc_sql_create_statement_from_sql ((GncSqlBackend*)be, buf);
 +            g_free (buf);
 +            query_transactions ((GncSqlBackend*)be, stmt);
 +            tx = xaccTransLookup (&guid, be->book);
 +        }
 +
 +        if (tx != NULL)
 +        {
 +            if (table_row->gobj_param_name != NULL)
 +            {
 +                qof_instance_increase_editlevel (pObject);
 +                g_object_set (pObject, table_row->gobj_param_name, tx, NULL);
 +                qof_instance_decrease_editlevel (pObject);
 +            }
 +            else
 +            {
 +                g_return_if_fail (setter != NULL);
 +                (*setter) (pObject, (const gpointer)tx);
 +            }
 +        }
 +    }
 +}
 +
 +static GncSqlColumnTypeHandler tx_guid_handler
 += { load_tx_guid,
 +    gnc_sql_add_objectref_guid_col_info_to_list,
 +    gnc_sql_add_colname_to_list,
 +    gnc_sql_add_gvalue_objectref_guid_to_slist
 +  };
 +/* ================================================================= */
 +void
 +gnc_sql_init_transaction_handler (void)
 +{
 +    static GncSqlObjectBackend be_data_tx =
 +    {
 +        GNC_SQL_BACKEND_VERSION,
 +        GNC_ID_TRANS,
 +        commit_transaction,          /* commit */
 +#if LOAD_TRANSACTIONS_AS_NEEDED
 +        NULL,                        /* initial load */
 +#else
 +        gnc_sql_transaction_load_all_tx,
 +#endif
 +        create_transaction_tables,   /* create tables */
 +        NULL,                        /* compile_query */
 +        NULL,                        /* run_query */
 +        NULL,                        /* free_query */
 +        NULL                         /* write */
 +    };
 +    static GncSqlObjectBackend be_data_split =
 +    {
 +        GNC_SQL_BACKEND_VERSION,
 +        GNC_ID_SPLIT,
 +        commit_split,                /* commit */
 +        NULL,                        /* initial_load */
 +        NULL,                        /* create tables */
 +#if LOAD_TRANSACTIONS_AS_NEEDED
 +        compile_split_query,
 +        run_split_query,
 +        free_split_query,
 +#else
 +        NULL,                        /* compile_query */
 +        NULL,                        /* run_query */
 +        NULL,                        /* free_query */
 +#endif
 +        NULL                         /* write */
 +    };
 +
 +    (void)qof_object_register_backend (GNC_ID_TRANS, GNC_SQL_BACKEND, &be_data_tx);
 +    (void)qof_object_register_backend (GNC_ID_SPLIT, GNC_SQL_BACKEND,
 +                                       &be_data_split);
 +
 +    gnc_sql_register_col_type_handler (CT_TXREF, &tx_guid_handler);
 +}
 +
 +/* ========================== END OF FILE ===================== */
diff --cc src/backend/xml/io-gncxml-v2.cpp
index 61de9d2,0000000..920fe8d
mode 100644,000000..100644
--- a/src/backend/xml/io-gncxml-v2.cpp
+++ b/src/backend/xml/io-gncxml-v2.cpp
@@@ -1,2204 -1,0 +1,2205 @@@
 +/********************************************************************\
 + * Copyright (C) 2000,2001 Gnumatic Inc.                            *
 + *                                                                  *
 + * This program is free software; you can redistribute it and/or    *
 + * modify it under the terms of the GNU General Public License as   *
 + * published by the Free Software Foundation; either version 2 of   *
 + * the License, or (at your option) any later version.              *
 + *                                                                  *
 + * This program 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 General Public License for more details.                     *
 + *                                                                  *
 + * You should have received a copy of the GNU General Public License*
 + * along with this program; if not, contact:                        *
 + *                                                                  *
 + * Free Software Foundation           Voice:  +1-617-542-5942       *
 + * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
 + * Boston, MA  02110-1301,  USA       gnu at gnu.org                   *
 +\********************************************************************/
 +#include <guid.hpp>
 +extern "C"
 +{
 +#include "config.h"
 +
 +#include <platform.h>
 +#if PLATFORM(WINDOWS)
 +#ifdef __STRICT_ANSI__
 +#undef __STRICT_ANSI__
 +#define __STRICT_ANSI_UNSET__ 1
 +#endif
 +#ifdef _NO_OLDNAMES
 +#undef _NO_OLDNAMES
 +#endif
 +#ifdef _UWIN
 +#undef _UWIN
 +#endif
 +#include <windows.h>
 +#endif
 +#include <glib.h>
 +#include <glib/gstdio.h>
 +#include <fcntl.h>
 +#include <string.h>
 +#ifdef HAVE_UNISTD_H
 +# include <unistd.h>
 +#endif
 +#include <zlib.h>
 +#include <errno.h>
 +
 +#include "gnc-engine.h"
 +#include "gnc-pricedb-p.h"
 +#include "Scrub.h"
 +#include "SX-book.h"
 +#include "SX-book-p.h"
 +#include "Transaction.h"
 +#include "TransactionP.h"
 +#include "TransLog.h"
 +#if PLATFORM(WINDOWS)
 +#ifdef __STRICT_ANSI_UNSET__
 +#undef __STRICT_ANSI_UNSET__
 +#define __STRICT_ANSI__ 1
 +#endif
 +#endif
 +#if COMPILER(MSVC)
 +# define g_fopen fopen
 +# define g_open _open
 +#endif
 +}
 +
 +#include "sixtp.h"
 +#include "sixtp-parsers.h"
 +#include "sixtp-utils.h"
 +#include "gnc-xml.h"
 +#include "io-utils.h"
 +#include "sixtp-dom-parsers.h"
 +#include "io-gncxml-v2.h"
 +#include "io-gncxml-gen.h"
 +
 +/* Do not treat -Wstrict-aliasing warnings as errors because of problems of the
 + * G_LOCK* macros as declared by glib.  See
 + * http://bugzilla.gnome.org/show_bug.cgi?id=316221 for additional information.
 + */
 +#if (__GNUC__ >= 4 && __GNUC_MINOR__ >= 2)
 +#    pragma GCC diagnostic warning "-Wstrict-aliasing"
 +#endif
 +
 +static QofLogModule log_module = GNC_MOD_IO;
 +
 +/* map pointers, e.g. of type FILE*, to GThreads */
 +static GHashTable* threads = NULL;
 +G_LOCK_DEFINE_STATIC (threads);
 +
 +typedef struct
 +{
 +    gint fd;
 +    gchar* filename;
 +    gchar* perms;
 +    gboolean compress;
 +} gz_thread_params_t;
 +
 +/* Callback structure */
 +struct file_backend
 +{
 +    gboolean        ok;
 +    gpointer        data;
 +    sixtp_gdv2*     gd;
 +    const char*     tag;
 +    sixtp*          parser;
 +    FILE*           out;
 +    QofBook*        book;
 +};
 +
 +#define GNC_V2_STRING "gnc-v2"
 +/* non-static because they are used in sixtp.c */
 +const gchar* gnc_v2_xml_version_string = GNC_V2_STRING;
 +extern const gchar*
 +gnc_v2_book_version_string;        /* see gnc-book-xml-v2 */
 +
 +/* Forward declarations */
 +static FILE* try_gz_open (const char* filename, const char* perms,
 +                          gboolean use_gzip,
 +                          gboolean compress);
 +static gboolean is_gzipped_file (const gchar* name);
 +static gboolean wait_for_gzip (FILE* file);
 +
 +static void
 +clear_up_account_commodity (
 +    gnc_commodity_table* tbl, Account* act,
 +    gnc_commodity * (*getter) (const Account* account),
 +    void (*setter) (Account* account, gnc_commodity* comm),
 +    int (*scu_getter) (const Account* account),
 +    void (*scu_setter) (Account* account, int scu))
 +{
 +    gnc_commodity* gcom;
 +    gnc_commodity* com = getter (act);
 +    int old_scu;
 +
 +    if (scu_getter)
 +        old_scu = scu_getter (act);
 +    else
 +        old_scu = 0;
 +
 +    if (!com)
 +    {
 +        return;
 +    }
 +
 +    gcom = gnc_commodity_table_lookup (tbl, gnc_commodity_get_namespace (com),
 +                                       gnc_commodity_get_mnemonic (com));
 +
 +    if (gcom == com)
 +    {
 +        return;
 +    }
 +    else if (!gcom)
 +    {
 +        PWARN ("unable to find global commodity for %s adding new",
 +               gnc_commodity_get_unique_name (com));
 +        gnc_commodity_table_insert (tbl, com);
 +    }
 +    else
 +    {
 +        setter (act, gcom);
 +        if (old_scu != 0 && scu_setter)
 +            scu_setter (act, old_scu);
 +        gnc_commodity_destroy (com);
 +    }
 +}
 +
 +static void
 +clear_up_transaction_commodity (
 +    gnc_commodity_table* tbl, Transaction* trans,
 +    gnc_commodity * (*getter) (const Transaction* trans),
 +    void (*setter) (Transaction* trans, gnc_commodity* comm))
 +{
 +    gnc_commodity* gcom;
 +    gnc_commodity* com = getter (trans);
 +
 +    if (!com)
 +    {
 +        return;
 +    }
 +
 +    gcom = gnc_commodity_table_lookup (tbl, gnc_commodity_get_namespace (com),
 +                                       gnc_commodity_get_mnemonic (com));
 +
 +    if (gcom == com)
 +    {
 +        return;
 +    }
 +    else if (!gcom)
 +    {
 +        PWARN ("unable to find global commodity for %s adding new",
 +               gnc_commodity_get_unique_name (com));
 +        gnc_commodity_table_insert (tbl, com);
 +    }
 +    else
 +    {
 +        xaccTransBeginEdit (trans);
 +        setter (trans, gcom);
 +        xaccTransCommitEdit (trans);
 +        gnc_commodity_destroy (com);
 +    }
 +}
 +
 +static gboolean
 +add_account_local (sixtp_gdv2* data, Account* act)
 +{
 +    gnc_commodity_table* table;
 +    Account* parent, *root;
 +    int type;
 +
 +    table = gnc_commodity_table_get_table (data->book);
 +
 +    clear_up_account_commodity (table, act,
 +                                DxaccAccountGetCurrency,
 +                                DxaccAccountSetCurrency,
 +                                NULL, NULL);
 +
 +    clear_up_account_commodity (table, act,
 +                                xaccAccountGetCommodity,
 +                                xaccAccountSetCommodity,
 +                                xaccAccountGetCommoditySCUi,
 +                                xaccAccountSetCommoditySCU);
 +
 +    xaccAccountScrubCommodity (act);
 +    xaccAccountScrubKvp (act);
 +
 +    /* Backwards compatability.  If there's no parent, see if this
 +     * account is of type ROOT.  If not, find or create a ROOT
 +     * account and make that the parent. */
 +    type = xaccAccountGetType (act);
 +    if (type == ACCT_TYPE_ROOT)
 +    {
 +        gnc_book_set_root_account (data->book, act);
 +    }
 +    else
 +    {
 +        parent = gnc_account_get_parent (act);
 +        if (parent == NULL)
 +        {
 +            root = gnc_book_get_root_account (data->book);
 +            gnc_account_append_child (root, act);
 +        }
 +    }
 +
 +    data->counter.accounts_loaded++;
 +    sixtp_run_callback (data, "account");
 +
 +    return FALSE;
 +}
 +
 +static gboolean
 +add_book_local (sixtp_gdv2* data, QofBook* book)
 +{
 +    data->counter.books_loaded++;
 +    sixtp_run_callback (data, "book");
 +
 +    return FALSE;
 +}
 +
 +static gboolean
 +add_commodity_local (sixtp_gdv2* data, gnc_commodity* com)
 +{
 +    gnc_commodity_table* table;
 +
 +    table = gnc_commodity_table_get_table (data->book);
 +
 +    gnc_commodity_table_insert (table, com);
 +
 +    data->counter.commodities_loaded++;
 +    sixtp_run_callback (data, "commodities");
 +
 +    return TRUE;
 +}
 +
 +static gboolean
 +add_transaction_local (sixtp_gdv2* data, Transaction* trn)
 +{
 +    gnc_commodity_table* table;
 +
 +    table = gnc_commodity_table_get_table (data->book);
 +
 +    xaccTransBeginEdit (trn);
 +    clear_up_transaction_commodity (table, trn,
 +                                    xaccTransGetCurrency,
 +                                    xaccTransSetCurrency);
 +
 +    xaccTransScrubCurrency (trn);
++    xaccTransScrubPostedDate (trn);
 +    xaccTransCommitEdit (trn);
 +
 +    data->counter.transactions_loaded++;
 +    sixtp_run_callback (data, "transaction");
 +    return TRUE;
 +}
 +
 +static gboolean
 +add_schedXaction_local (sixtp_gdv2* data, SchedXaction* sx)
 +{
 +    SchedXactions* sxes;
 +    sxes = gnc_book_get_schedxactions (data->book);
 +    gnc_sxes_add_sx (sxes, sx);
 +    data->counter.schedXactions_loaded++;
 +    sixtp_run_callback (data, "schedXactions");
 +    return TRUE;
 +}
 +
 +static gboolean
 +add_template_transaction_local (sixtp_gdv2* data,
 +                                gnc_template_xaction_data* txd)
 +{
 +    GList* n;
 +    Account* acctRoot = NULL;
 +    QofBook* book;
 +
 +    book = data->book;
 +
 +    /* expect a struct of: */
 +    /* . template accounts. */
 +    /* . transactions in those accounts. */
 +    for (n = txd->accts; n; n = n->next)
 +    {
 +        if (gnc_account_get_parent ((Account*)n->data) == NULL)
 +        {
 +            if (xaccAccountGetType ((Account*)n->data) == ACCT_TYPE_ROOT)
 +            {
 +                /* replace the gnc_book_init-created root account */
 +                gnc_book_set_template_root (book, (Account*)n->data);
 +            }
 +            else
 +            {
 +                /* This is an old data file that doesn't have a template root
 +                   account and this is a top level account.  Make it a child
 +                   of the template root account. */
 +                acctRoot = gnc_book_get_template_root (book);
 +                gnc_account_append_child (acctRoot, (Account*)n->data);
 +            }
 +        }
 +
 +    }
 +
 +    for (n = txd->transactions; n; n = n->next)
 +    {
 +        /* insert transactions into accounts */
 +        add_transaction_local (data, (Transaction*)n->data);
 +    }
 +
 +    return TRUE;
 +}
 +
 +static gboolean
 +add_pricedb_local (sixtp_gdv2* data, GNCPriceDB* db)
 +{
 +    /* gnc_pricedb_print_contents(db, stdout); */
 +    return TRUE;
 +}
 +
 +static void
 +do_counter_cb (const char* type, gpointer data_p, gpointer be_data_p)
 +{
 +    GncXmlDataType_t* data = static_cast<decltype (data)> (data_p);
 +    struct file_backend* be_data = static_cast<decltype (be_data)> (be_data_p);
 +
 +    g_return_if_fail (type && data && be_data);
 +    g_return_if_fail (data->version == GNC_FILE_BACKEND_VERS);
 +
 +    if (be_data->ok == TRUE)
 +        return;
 +
 +    if (!g_strcmp0 (be_data->tag, data->type_name))
 +        be_data->ok = TRUE;
 +
 +    /* XXX: should we do anything with this counter? */
 +}
 +
 +static gboolean
 +gnc_counter_end_handler (gpointer data_for_children,
 +                         GSList* data_from_children, GSList* sibling_data,
 +                         gpointer parent_data, gpointer global_data,
 +                         gpointer* result, const gchar* tag)
 +{
 +    char* strval;
 +    gint64 val;
 +    char* type;
 +    xmlNodePtr tree = (xmlNodePtr)data_for_children;
 +    gxpf_data* gdata = (gxpf_data*)global_data;
 +    sixtp_gdv2* sixdata = (sixtp_gdv2*)gdata->parsedata;
 +    gboolean ret = TRUE;
 +
 +    if (parent_data)
 +        return TRUE;
 +
 +    /* OK.  For some messed up reason this is getting called again with a
 +       NULL tag.  So we ignore those cases */
 +    if (!tag)
 +        return TRUE;
 +
 +    g_return_val_if_fail (tree, FALSE);
 +
 +    /* Note: BADXML.
 +     *
 +     * This is invalid xml because the namespace isn't declared in the
 +     * tag itself. This should be changed to 'type' at some point. */
 +    type = (char*)xmlGetProp (tree, BAD_CAST "cd:type");
 +    strval = dom_tree_to_text (tree);
 +    if (!string_to_gint64 (strval, &val))
 +    {
 +        PERR ("string_to_gint64 failed with input: %s",
 +              strval ? strval : "(null)");
 +        ret = FALSE;
 +    }
 +    else if (g_strcmp0 (type, "transaction") == 0)
 +    {
 +        sixdata->counter.transactions_total = val;
 +    }
 +    else if (g_strcmp0 (type, "account") == 0)
 +    {
 +        sixdata->counter.accounts_total = val;
 +    }
 +    else if (g_strcmp0 (type, "book") == 0)
 +    {
 +        sixdata->counter.books_total = val;
 +    }
 +    else if (g_strcmp0 (type, "commodity") == 0)
 +    {
 +        sixdata->counter.commodities_total = val;
 +    }
 +    else if (g_strcmp0 (type, "schedxaction") == 0)
 +    {
 +        sixdata->counter.schedXactions_total = val;
 +    }
 +    else if (g_strcmp0 (type, "budget") == 0)
 +    {
 +        sixdata->counter.budgets_total = val;
 +    }
 +    else if (g_strcmp0 (type, "price") == 0)
 +    {
 +        sixdata->counter.prices_total = val;
 +    }
 +    else
 +    {
 +        struct file_backend be_data;
 +
 +        be_data.ok = FALSE;
 +        be_data.tag = type;
 +
 +        qof_object_foreach_backend (GNC_FILE_BACKEND, do_counter_cb, &be_data);
 +
 +        if (be_data.ok == FALSE)
 +        {
 +            PERR ("Unknown type: %s", type ? type : "(null)");
 +            /* Do *NOT* flag this as an error. Gnucash 1.8 writes invalid
 +             * xml by writing the 'cd:type' attribute without providing
 +             * the namespace in the gnc:count-data tag.  The parser is
 +             * entirely within its rights to refuse to read this bad
 +             * attribute. Gnucash will function correctly without the data
 +             * in this tag, so just let the error pass. */
 +            ret = TRUE;
 +        }
 +    }
 +
 +    g_free (strval);
 +    xmlFree (type);
 +    xmlFreeNode (tree);
 +    return ret;
 +}
 +
 +static sixtp*
 +gnc_counter_sixtp_parser_create (void)
 +{
 +    return sixtp_dom_parser_new (gnc_counter_end_handler, NULL, NULL);
 +}
 +
 +static void
 +debug_print_counter_data (load_counter* data)
 +{
 +    DEBUG ("Transactions: Total: %d, Loaded: %d",
 +           data->transactions_total, data->transactions_loaded);
 +    DEBUG ("Accounts: Total: %d, Loaded: %d",
 +           data->accounts_total, data->accounts_loaded);
 +    DEBUG ("Books: Total: %d, Loaded: %d",
 +           data->books_total, data->books_loaded);
 +    DEBUG ("Commodities: Total: %d, Loaded: %d",
 +           data->commodities_total, data->commodities_loaded);
 +    DEBUG ("Scheduled Transactions: Total: %d, Loaded: %d",
 +           data->schedXactions_total, data->schedXactions_loaded);
 +    DEBUG ("Budgets: Total: %d, Loaded: %d",
 +           data->budgets_total, data->budgets_loaded);
 +}
 +
 +static void
 +file_rw_feedback (sixtp_gdv2* gd, const char* type)
 +{
 +    load_counter* counter;
 +    int loaded, total, percentage;
 +
 +    g_assert (gd != NULL);
 +    if (!gd->gui_display_fn)
 +        return;
 +
 +    counter = &gd->counter;
 +    loaded = counter->transactions_loaded + counter->accounts_loaded +
 +             counter->books_loaded + counter->commodities_loaded +
 +             counter->schedXactions_loaded + counter->budgets_loaded +
 +             counter->prices_loaded;
 +    total = counter->transactions_total + counter->accounts_total +
 +            counter->books_total + counter->commodities_total +
 +            counter->schedXactions_total + counter->budgets_total +
 +            counter->prices_total;
 +    if (total == 0)
 +        total = 1;
 +
 +    percentage = (loaded * 100) / total;
 +    if (percentage > 100)
 +    {
 +        /* FIXME: Perhaps the below should be replaced by:
 +        print_counter_data(counter); */
 +//      printf("Transactions: Total: %d, Loaded: %d\n",
 +//             counter->transactions_total, counter->transactions_loaded);
 +//      printf("Accounts: Total: %d, Loaded: %d\n",
 +//             counter->accounts_total, counter->accounts_loaded);
 +//      printf("Books: Total: %d, Loaded: %d\n",
 +//             counter->books_total, counter->books_loaded);
 +//      printf("Commodities: Total: %d, Loaded: %d\n",
 +//             counter->commodities_total, counter->commodities_loaded);
 +//      printf("Scheduled Transactions: Total: %d, Loaded: %d\n",
 +//             counter->schedXactions_total, counter->schedXactions_loaded);
 +//      printf("Budgets: Total: %d, Loaded: %d\n",
 +//       counter->budgets_total, counter->budgets_loaded);
 +    }
 +    gd->gui_display_fn (NULL, percentage);
 +}
 +
 +static const char* BOOK_TAG = "gnc:book";
 +static const char* BOOK_ID_TAG = "book:id";
 +static const char* BOOK_SLOTS_TAG = "book:slots";
 +static const char* ACCOUNT_TAG = "gnc:account";
 +static const char* PRICEDB_TAG = "gnc:pricedb";
 +static const char* COMMODITY_TAG = "gnc:commodity";
 +static const char* COUNT_DATA_TAG = "gnc:count-data";
 +static const char* TRANSACTION_TAG = "gnc:transaction";
 +static const char* SCHEDXACTION_TAG = "gnc:schedxaction";
 +static const char* TEMPLATE_TRANSACTION_TAG = "gnc:template-transactions";
 +static const char* BUDGET_TAG = "gnc:budget";
 +
 +static void
 +add_item_cb (const char* type, gpointer data_p, gpointer be_data_p)
 +{
 +    GncXmlDataType_t* data = static_cast<decltype (data)> (data_p);
 +    struct file_backend* be_data = static_cast<decltype (be_data)> (be_data_p);
 +
 +    g_return_if_fail (type && data && be_data);
 +    g_return_if_fail (data->version == GNC_FILE_BACKEND_VERS);
 +
 +    if (be_data->ok)
 +        return;
 +
 +    if (!g_strcmp0 (be_data->tag, data->type_name))
 +    {
 +        if (data->add_item)
 +            (data->add_item) (be_data->gd, be_data->data);
 +
 +        be_data->ok = TRUE;
 +    }
 +}
 +
 +static gboolean
 +book_callback (const char* tag, gpointer globaldata, gpointer data)
 +{
 +    sixtp_gdv2* gd = (sixtp_gdv2*)globaldata;
 +
 +    if (g_strcmp0 (tag, ACCOUNT_TAG) == 0)
 +    {
 +        add_account_local (gd, (Account*)data);
 +    }
 +    else if (g_strcmp0 (tag, PRICEDB_TAG) == 0)
 +    {
 +        add_pricedb_local (gd, (GNCPriceDB*)data);
 +    }
 +    else if (g_strcmp0 (tag, COMMODITY_TAG) == 0)
 +    {
 +        add_commodity_local (gd, (gnc_commodity*)data);
 +    }
 +    else if (g_strcmp0 (tag, TRANSACTION_TAG) == 0)
 +    {
 +        add_transaction_local (gd, (Transaction*)data);
 +    }
 +    else if (g_strcmp0 (tag, SCHEDXACTION_TAG) == 0)
 +    {
 +        add_schedXaction_local (gd, (SchedXaction*)data);
 +    }
 +    else if (g_strcmp0 (tag, TEMPLATE_TRANSACTION_TAG) == 0)
 +    {
 +        add_template_transaction_local (gd, (gnc_template_xaction_data*)data);
 +    }
 +    else if (g_strcmp0 (tag, BUDGET_TAG) == 0)
 +    {
 +        // Nothing needed here.
 +    }
 +    else
 +    {
 +        struct file_backend be_data;
 +
 +        be_data.ok = FALSE;
 +        be_data.tag = tag;
 +        be_data.gd = gd;
 +        be_data.data = data;
 +
 +        qof_object_foreach_backend (GNC_FILE_BACKEND, add_item_cb, &be_data);
 +
 +        if (be_data.ok == FALSE)
 +        {
 +            PWARN ("unexpected tag %s", tag);
 +        }
 +    }
 +    return TRUE;
 +}
 +
 +static gboolean
 +generic_callback (const char* tag, gpointer globaldata, gpointer data)
 +{
 +    sixtp_gdv2* gd = (sixtp_gdv2*)globaldata;
 +
 +    if (g_strcmp0 (tag, BOOK_TAG) == 0)
 +    {
 +        add_book_local (gd, (QofBook*)data);
 +        book_callback (tag, globaldata, data);
 +    }
 +    else
 +    {
 +        // PWARN ("importing pre-book-style XML data file");
 +        book_callback (tag, globaldata, data);
 +    }
 +    return TRUE;
 +}
 +
 +static void
 +add_parser_cb (const char* type, gpointer data_p, gpointer be_data_p)
 +{
 +    GncXmlDataType_t* data = static_cast<decltype (data)> (data_p);
 +    struct file_backend* be_data = static_cast<decltype (be_data)> (be_data_p);
 +
 +    g_return_if_fail (type && data && be_data);
 +    g_return_if_fail (data->version == GNC_FILE_BACKEND_VERS);
 +
 +    if (be_data->ok == FALSE)
 +        return;
 +
 +    if (data->create_parser)
 +        if (!sixtp_add_some_sub_parsers (
 +                be_data->parser, TRUE,
 +                data->type_name, (data->create_parser) (),
 +                NULL, NULL))
 +            be_data->ok = FALSE;
 +}
 +
 +static void
 +scrub_cb (const char* type, gpointer data_p, gpointer be_data_p)
 +{
 +    GncXmlDataType_t* data = static_cast<decltype (data)> (data_p);
 +    struct file_backend* be_data = static_cast<decltype (be_data)> (be_data_p);
 +
 +    g_return_if_fail (type && data && be_data);
 +    g_return_if_fail (data->version == GNC_FILE_BACKEND_VERS);
 +
 +    if (data->scrub)
 +        (data->scrub) (be_data->book);
 +}
 +
 +static sixtp_gdv2*
 +gnc_sixtp_gdv2_new (
 +    QofBook* book,
 +    gboolean exporting,
 +    countCallbackFn countcallback,
 +    QofBePercentageFunc gui_display_fn)
 +{
 +    sixtp_gdv2* gd = g_new0 (sixtp_gdv2, 1);
 +
 +    if (gd == NULL) return NULL;
 +
 +    gd->book = book;
 +    gd->counter.accounts_loaded = 0;
 +    gd->counter.accounts_total = 0;
 +    gd->counter.books_loaded = 0;
 +    gd->counter.books_total = 0;
 +    gd->counter.commodities_loaded = 0;
 +    gd->counter.commodities_total = 0;
 +    gd->counter.transactions_loaded = 0;
 +    gd->counter.transactions_total = 0;
 +    gd->counter.prices_loaded = 0;
 +    gd->counter.prices_total = 0;
 +    gd->counter.schedXactions_loaded = 0;
 +    gd->counter.schedXactions_total = 0;
 +    gd->counter.budgets_loaded = 0;
 +    gd->counter.budgets_total = 0;
 +    gd->exporting = exporting;
 +    gd->countCallback = countcallback;
 +    gd->gui_display_fn = gui_display_fn;
 +    return gd;
 +}
 +
 +static gboolean
 +qof_session_load_from_xml_file_v2_full (
 +    FileBackend* fbe, QofBook* book,
 +    sixtp_push_handler push_handler, gpointer push_user_data,
 +    QofBookFileType type)
 +{
 +    Account* root;
 +    QofBackend* be = &fbe->be;
 +    sixtp_gdv2* gd;
 +    sixtp* top_parser;
 +    sixtp* main_parser;
 +    sixtp* book_parser;
 +    struct file_backend be_data;
 +    gboolean retval;
 +    char* v2type = NULL;
 +
 +    gd = gnc_sixtp_gdv2_new (book, FALSE, file_rw_feedback, be->percentage);
 +
 +    top_parser = sixtp_new ();
 +    main_parser = sixtp_new ();
 +    book_parser = sixtp_new ();
 +
 +    if (type == GNC_BOOK_XML2_FILE)
 +        v2type = g_strdup (GNC_V2_STRING);
 +
 +    if (!sixtp_add_some_sub_parsers (
 +            top_parser, TRUE,
 +            v2type, main_parser,
 +            NULL, NULL))
 +    {
 +        g_free (v2type);
 +        goto bail;
 +    }
 +
 +    g_free (v2type);
 +
 +    if (!sixtp_add_some_sub_parsers (
 +            main_parser, TRUE,
 +            COUNT_DATA_TAG, gnc_counter_sixtp_parser_create (),
 +            BOOK_TAG, book_parser,
 +
 +            /* the following are present here only to support
 +             * the older, pre-book format.  Basically, the top-level
 +             * book is implicit. */
 +            PRICEDB_TAG, gnc_pricedb_sixtp_parser_create (),
 +            COMMODITY_TAG, gnc_commodity_sixtp_parser_create (),
 +            ACCOUNT_TAG, gnc_account_sixtp_parser_create (),
 +            TRANSACTION_TAG, gnc_transaction_sixtp_parser_create (),
 +            SCHEDXACTION_TAG, gnc_schedXaction_sixtp_parser_create (),
 +            TEMPLATE_TRANSACTION_TAG, gnc_template_transaction_sixtp_parser_create (),
 +            NULL, NULL))
 +    {
 +        goto bail;
 +    }
 +
 +    if (!sixtp_add_some_sub_parsers (
 +            book_parser, TRUE,
 +            BOOK_ID_TAG, gnc_book_id_sixtp_parser_create (),
 +            BOOK_SLOTS_TAG, gnc_book_slots_sixtp_parser_create (),
 +            COUNT_DATA_TAG, gnc_counter_sixtp_parser_create (),
 +            PRICEDB_TAG, gnc_pricedb_sixtp_parser_create (),
 +            COMMODITY_TAG, gnc_commodity_sixtp_parser_create (),
 +            ACCOUNT_TAG, gnc_account_sixtp_parser_create (),
 +            BUDGET_TAG, gnc_budget_sixtp_parser_create (),
 +            TRANSACTION_TAG, gnc_transaction_sixtp_parser_create (),
 +            SCHEDXACTION_TAG, gnc_schedXaction_sixtp_parser_create (),
 +            TEMPLATE_TRANSACTION_TAG, gnc_template_transaction_sixtp_parser_create (),
 +            NULL, NULL))
 +    {
 +        goto bail;
 +    }
 +
 +    be_data.ok = TRUE;
 +    be_data.parser = book_parser;
 +    qof_object_foreach_backend (GNC_FILE_BACKEND, add_parser_cb, &be_data);
 +    if (be_data.ok == FALSE)
 +        goto bail;
 +
 +    /* stop logging while we load */
 +    xaccLogDisable ();
 +    xaccDisableDataScrubbing ();
 +
 +    if (push_handler)
 +    {
 +        gpointer parse_result = NULL;
 +        gxpf_data gpdata;
 +
 +        gpdata.cb = generic_callback;
 +        gpdata.parsedata = gd;
 +        gpdata.bookdata = book;
 +
 +        retval = sixtp_parse_push (top_parser, push_handler, push_user_data,
 +                                   NULL, &gpdata, &parse_result);
 +    }
 +    else
 +    {
 +        /* Even though libxml2 knows how to decompress zipped files, we
 +         * do it ourself since as of version 2.9.1 it has a bug that
 +         * causes it to fail to decompress certain files. See
 +         * https://bugzilla.gnome.org/show_bug.cgi?id=712528 for more
 +         * info.
 +         */
 +        gchar* filename = fbe->fullpath;
 +        FILE* file;
 +        gboolean is_compressed = is_gzipped_file (filename);
 +        file = try_gz_open (filename, "r", is_compressed, FALSE);
 +        if (file == NULL)
 +        {
 +            PWARN ("Unable to open file %s", filename);
 +            retval = FALSE;
 +        }
 +        else
 +        {
 +            retval = gnc_xml_parse_fd (top_parser, file,
 +                                       generic_callback, gd, book);
 +            fclose (file);
 +            if (is_compressed)
 +                wait_for_gzip (file);
 +        }
 +    }
 +
 +    if (!retval)
 +    {
 +        sixtp_destroy (top_parser);
 +        xaccLogEnable ();
 +        xaccEnableDataScrubbing ();
 +        goto bail;
 +    }
 +    debug_print_counter_data (&gd->counter);
 +
 +    /* destroy the parser */
 +    sixtp_destroy (top_parser);
 +    g_free (gd);
 +
 +    xaccEnableDataScrubbing ();
 +
 +    /* Mark the session as saved */
 +    qof_book_mark_session_saved (book);
 +
 +    /* Call individual scrub functions */
 +    memset (&be_data, 0, sizeof (be_data));
 +    be_data.book = book;
 +    qof_object_foreach_backend (GNC_FILE_BACKEND, scrub_cb, &be_data);
 +
 +    /* fix price quote sources */
 +    root = gnc_book_get_root_account (book);
 +    xaccAccountTreeScrubQuoteSources (root, gnc_commodity_table_get_table (book));
 +
 +    /* Fix account and transaction commodities */
 +    xaccAccountTreeScrubCommodities (root);
 +
 +    /* Fix split amount/value */
 +    xaccAccountTreeScrubSplits (root);
 +
 +    /* commit all groups, this completes the BeginEdit started when the
 +     * account_end_handler finished reading the account.
 +     */
 +    gnc_account_foreach_descendant (root,
 +                                    (AccountCb) xaccAccountCommitEdit,
 +                                    NULL);
 +
 +    /* start logging again */
 +    xaccLogEnable ();
 +
 +    return TRUE;
 +
 +bail:
 +    g_free (gd);
 +    return FALSE;
 +}
 +
 +gboolean
 +qof_session_load_from_xml_file_v2 (FileBackend* fbe, QofBook* book,
 +                                   QofBookFileType type)
 +{
 +    return qof_session_load_from_xml_file_v2_full (fbe, book, NULL, NULL, type);
 +}
 +
 +/***********************************************************************/
 +
 +static gboolean
 +write_counts (FILE* out, ...)
 +{
 +    va_list ap;
 +    char* type;
 +    gboolean success = TRUE;
 +
 +    va_start (ap, out);
 +    type = g_strdup (va_arg (ap, char*));
 +
 +    while (success && type)
 +    {
 +        int amount = va_arg (ap, int);
 +
 +        if (amount != 0)
 +        {
 +#if GNUCASH_REALLY_BUILD_AN_XML_TREE_ON_OUTPUT
 +            char* val;
 +            xmlNodePtr node;
 +
 +            val = g_strdup_printf ("%d", amount);
 +
 +            node = xmlNewNode (NULL, BAD_CAST COUNT_DATA_TAG);
 +            /* Note: BADXML.
 +             *
 +             * This is invalid xml because the namespace isn't
 +             * declared in the tag itself. This should be changed to
 +             * 'type' at some point. */
 +            xmlSetProp (node, BAD_CAST "cd:type", checked_char_cast (type));
 +            xmlNodeAddContent (node, checked_char_cast (val));
 +            g_free (val);
 +            g_free (type);
 +
 +            xmlElemDump (out, NULL, node);
 +            xmlFreeNode (node);
 +
 +            if (ferror (out) || fprintf (out, "\n") < 0)
 +            {
 +                success = FALSE;
 +                break;
 +            }
 +#else
 +            if (fprintf (out, "<%s %s=\"%s\">%d</%s>\n",
 +                         COUNT_DATA_TAG, "cd:type", type, amount, COUNT_DATA_TAG) < 0)
 +            {
 +                success = FALSE;
 +                break;
 +            }
 +#endif
 +
 +        }
 +
 +        type = va_arg (ap, char*);
 +    }
 +
 +    va_end (ap);
 +    return success;
 +}
 +
 +static gint
 +compare_namespaces (gconstpointer a, gconstpointer b)
 +{
 +    const gchar* sa = (const gchar*) a;
 +    const gchar* sb = (const gchar*) b;
 +    return (g_strcmp0 (sa, sb));
 +}
 +
 +static gint
 +compare_commodity_ids (gconstpointer a, gconstpointer b)
 +{
 +    const gnc_commodity* ca = (const gnc_commodity*) a;
 +    const gnc_commodity* cb = (const gnc_commodity*) b;
 +    return (g_strcmp0 (gnc_commodity_get_mnemonic (ca),
 +                       gnc_commodity_get_mnemonic (cb)));
 +}
 +
 +static gboolean write_pricedb (FILE* out, QofBook* book, sixtp_gdv2* gd);
 +static gboolean write_transactions (FILE* out, QofBook* book, sixtp_gdv2* gd);
 +static gboolean write_template_transaction_data (FILE* out, QofBook* book,
 +                                                 sixtp_gdv2* gd);
 +static gboolean write_schedXactions (FILE* out, QofBook* book, sixtp_gdv2* gd);
 +static void write_budget (QofInstance* ent, gpointer data);
 +
 +static void
 +write_counts_cb (const char* type, gpointer data_p, gpointer be_data_p)
 +{
 +    GncXmlDataType_t* data = static_cast<decltype (data)> (data_p);
 +    struct file_backend* be_data = static_cast<decltype (be_data)> (be_data_p);
 +
 +    g_return_if_fail (type && data && be_data);
 +    g_return_if_fail (data->version == GNC_FILE_BACKEND_VERS);
 +
 +    if (data->get_count)
 +        write_counts (be_data->out, data->type_name,
 +                      (data->get_count) (be_data->book),
 +                      NULL);
 +}
 +
 +static void
 +write_data_cb (const char* type, gpointer data_p, gpointer be_data_p)
 +{
 +    GncXmlDataType_t* data = static_cast<decltype (data)> (data_p);
 +    struct file_backend* be_data = static_cast<decltype (be_data)> (be_data_p);
 +
 +    g_return_if_fail (type && data && be_data);
 +    g_return_if_fail (data->version == GNC_FILE_BACKEND_VERS);
 +
 +    if (data->write && !ferror (be_data->out))
 +        (data->write) (be_data->out, be_data->book);
 +}
 +
 +static gboolean
 +write_book (FILE* out, QofBook* book, sixtp_gdv2* gd)
 +{
 +    struct file_backend be_data;
 +
 +#ifdef IMPLEMENT_BOOK_DOM_TREES_LATER
 +    /* We can't just blast out the dom tree, because the dom tree
 +     * doesn't have the books, transactions, etc underneath it.
 +     * But that is just as well, since I think the performance
 +     * will be much better if we write out as we go along
 +     */
 +    xmlNodePtr node;
 +
 +    node = gnc_book_dom_tree_create (book);
 +
 +    if (!node)
 +    {
 +        return FALSE;
 +    }
 +
 +    xmlElemDump (out, NULL, node);
 +    xmlFreeNode (node);
 +
 +    if (ferror (out) || fprintf (out, "\n") < 0)
 +    {
 +        return FALSE;
 +    }
 +
 +#endif
 +
 +    be_data.out = out;
 +    be_data.book = book;
 +    be_data.gd = gd;
 +    if (fprintf (out, "<%s version=\"%s\">\n", BOOK_TAG,
 +                 gnc_v2_book_version_string) < 0)
 +        return FALSE;
 +    if (!write_book_parts (out, book))
 +        return FALSE;
 +
 +    /* gd->counter.{foo}_total fields should have all these totals
 +       already collected.  I don't know why we're re-calling all these
 +       functions.  */
 +    if (!write_counts (out,
 +                       "commodity",
 +                       gnc_commodity_table_get_size (
 +                           gnc_commodity_table_get_table (book)),
 +                       "account",
 +                       1 + gnc_account_n_descendants (gnc_book_get_root_account (book)),
 +                       "transaction",
 +                       gnc_book_count_transactions (book),
 +                       "schedxaction",
 +                       g_list_length (gnc_book_get_schedxactions (book)->sx_list),
 +                       "budget", qof_collection_count (
 +                           qof_book_get_collection (book, GNC_ID_BUDGET)),
 +                       "price", gnc_pricedb_get_num_prices (gnc_pricedb_get_db (book)),
 +                       NULL))
 +        return FALSE;
 +
 +    qof_object_foreach_backend (GNC_FILE_BACKEND, write_counts_cb, &be_data);
 +
 +    if (ferror (out)
 +        || !write_commodities (out, book, gd)
 +        || !write_pricedb (out, book, gd)
 +        || !write_accounts (out, book, gd)
 +        || !write_transactions (out, book, gd)
 +        || !write_template_transaction_data (out, book, gd)
 +        || !write_schedXactions (out, book, gd))
 +
 +        return FALSE;
 +
 +    qof_collection_foreach (qof_book_get_collection (book, GNC_ID_BUDGET),
 +                            write_budget, &be_data);
 +    if (ferror (out))
 +        return FALSE;
 +
 +    qof_object_foreach_backend (GNC_FILE_BACKEND, write_data_cb, &be_data);
 +    if (ferror (out))
 +        return FALSE;
 +
 +    if (fprintf (out, "</%s>\n", BOOK_TAG) < 0)
 +        return FALSE;
 +
 +    return TRUE;
 +}
 +
 +gboolean
 +write_commodities (FILE* out, QofBook* book, sixtp_gdv2* gd)
 +{
 +    gnc_commodity_table* tbl;
 +    GList* namespaces;
 +    GList* lp;
 +    gboolean success = TRUE;
 +
 +    tbl = gnc_commodity_table_get_table (book);
 +
 +    namespaces = gnc_commodity_table_get_namespaces (tbl);
 +    if (namespaces)
 +    {
 +        namespaces = g_list_sort (namespaces, compare_namespaces);
 +    }
 +
 +    for (lp = namespaces; success && lp; lp = lp->next)
 +    {
 +        GList* comms, *lp2;
 +        xmlNodePtr comnode;
 +
 +        comms = gnc_commodity_table_get_commodities (tbl,
 +                                                     static_cast<const char*> (lp->data));
 +        comms = g_list_sort (comms, compare_commodity_ids);
 +
 +        for (lp2 = comms; lp2; lp2 = lp2->next)
 +        {
 +            comnode = gnc_commodity_dom_tree_create (static_cast<const gnc_commodity*>
 +                                                     (lp2->data));
 +            if (comnode == NULL)
 +                continue;
 +
 +            xmlElemDump (out, NULL, comnode);
 +            if (ferror (out) || fprintf (out, "\n") < 0)
 +            {
 +                success = FALSE;
 +                break;
 +            }
 +
 +            xmlFreeNode (comnode);
 +            gd->counter.commodities_loaded++;
 +            sixtp_run_callback (gd, "commodities");
 +        }
 +
 +        g_list_free (comms);
 +    }
 +
 +    if (namespaces) g_list_free (namespaces);
 +
 +    return success;
 +}
 +
 +static gboolean
 +write_pricedb (FILE* out, QofBook* book, sixtp_gdv2* gd)
 +{
 +    xmlNodePtr node;
 +    xmlNodePtr parent;
 +    xmlOutputBufferPtr outbuf;
 +
 +    parent = gnc_pricedb_dom_tree_create (gnc_pricedb_get_db (book));
 +
 +    if (!parent)
 +    {
 +        return TRUE;
 +    }
 +
 +    /* Write out the parent pricedb tag then loop to write out each price.
 +       We do it this way instead of just calling xmlElemDump so that we can
 +       increment the progress bar as we go. */
 +
 +    if (fprintf (out, "<%s version=\"%s\">\n", parent->name,
 +                 xmlGetProp (parent, BAD_CAST "version")) < 0)
 +        return FALSE;
 +
 +    /* We create our own output buffer so we can call xmlNodeDumpOutput to get
 +       the indendation correct. */
 +    outbuf = xmlOutputBufferCreateFile (out, NULL);
 +    if (outbuf == NULL)
 +    {
 +        xmlFreeNode (parent);
 +        return FALSE;
 +    }
 +
 +    for (node = parent->children; node; node = node->next)
 +    {
 +        /* Write two spaces since xmlNodeDumpOutput doesn't indent the first line */
 +        xmlOutputBufferWrite (outbuf, 2, "  ");
 +        xmlNodeDumpOutput (outbuf, NULL, node, 1, 1, NULL);
 +        /* It also doesn't terminate the last line */
 +        xmlOutputBufferWrite (outbuf, 1, "\n");
 +        if (ferror (out))
 +            break;
 +        gd->counter.prices_loaded += 1;
 +        sixtp_run_callback (gd, "prices");
 +    }
 +
 +    xmlOutputBufferClose (outbuf);
 +
 +    if (ferror (out) || fprintf (out, "</%s>\n", parent->name) < 0)
 +    {
 +        xmlFreeNode (parent);
 +        return FALSE;
 +    }
 +
 +    xmlFreeNode (parent);
 +    return TRUE;
 +}
 +
 +static int
 +xml_add_trn_data (Transaction* t, gpointer data)
 +{
 +    struct file_backend* be_data = static_cast<decltype (be_data)> (data);
 +    xmlNodePtr node;
 +
 +    node = gnc_transaction_dom_tree_create (t);
 +
 +    xmlElemDump (be_data->out, NULL, node);
 +    xmlFreeNode (node);
 +
 +    if (ferror (be_data->out) || fprintf (be_data->out, "\n") < 0)
 +        return -1;
 +
 +    be_data->gd->counter.transactions_loaded++;
 +    sixtp_run_callback (be_data->gd, "transaction");
 +    return 0;
 +}
 +
 +static gboolean
 +write_transactions (FILE* out, QofBook* book, sixtp_gdv2* gd)
 +{
 +    struct file_backend be_data;
 +
 +    be_data.out = out;
 +    be_data.gd = gd;
 +    return 0 ==
 +           xaccAccountTreeForEachTransaction (gnc_book_get_root_account (book),
 +                                              xml_add_trn_data,
 +                                              (gpointer) &be_data);
 +}
 +
 +static gboolean
 +write_template_transaction_data (FILE* out, QofBook* book, sixtp_gdv2* gd)
 +{
 +    Account* ra;
 +    struct file_backend be_data;
 +
 +    be_data.out = out;
 +    be_data.gd = gd;
 +
 +    ra = gnc_book_get_template_root (book);
 +    if (gnc_account_n_descendants (ra) > 0)
 +    {
 +        if (fprintf (out, "<%s>\n", TEMPLATE_TRANSACTION_TAG) < 0
 +            || !write_account_tree (out, ra, gd)
 +            || xaccAccountTreeForEachTransaction (ra, xml_add_trn_data, (gpointer)&be_data)
 +            || fprintf (out, "</%s>\n", TEMPLATE_TRANSACTION_TAG) < 0)
 +
 +            return FALSE;
 +    }
 +
 +    return TRUE;
 +}
 +
 +static gboolean
 +write_schedXactions (FILE* out, QofBook* book, sixtp_gdv2* gd)
 +{
 +    GList* schedXactions;
 +    SchedXaction* tmpSX;
 +    xmlNodePtr node;
 +
 +    schedXactions = gnc_book_get_schedxactions (book)->sx_list;
 +
 +    if (schedXactions == NULL)
 +        return TRUE;
 +
 +    do
 +    {
 +        tmpSX = static_cast<decltype (tmpSX)> (schedXactions->data);
 +        node = gnc_schedXaction_dom_tree_create (tmpSX);
 +        xmlElemDump (out, NULL, node);
 +        xmlFreeNode (node);
 +        if (ferror (out) || fprintf (out, "\n") < 0)
 +            return FALSE;
 +        gd->counter.schedXactions_loaded++;
 +        sixtp_run_callback (gd, "schedXactions");
 +    }
 +    while ((schedXactions = schedXactions->next));
 +
 +    return TRUE;
 +}
 +
 +static void
 +write_budget (QofInstance* ent, gpointer data)
 +{
 +    xmlNodePtr node;
 +    struct file_backend* be = static_cast<decltype (be)> (data);
 +
 +    GncBudget* bgt = GNC_BUDGET (ent);
 +
 +    if (ferror (be->out))
 +        return;
 +
 +    node = gnc_budget_dom_tree_create (bgt);
 +    xmlElemDump (be->out, NULL, node);
 +    xmlFreeNode (node);
 +    if (ferror (be->out) || fprintf (be->out, "\n") < 0)
 +        return;
 +
 +    be->gd->counter.budgets_loaded++;
 +    sixtp_run_callback (be->gd, "budgets");
 +}
 +
 +gboolean
 +gnc_xml2_write_namespace_decl (FILE* out, const char* name_space)
 +{
 +    g_return_val_if_fail (name_space, FALSE);
 +    return fprintf (out, "\n     xmlns:%s=\"http://www.gnucash.org/XML/%s\"",
 +                    name_space, name_space) >= 0;
 +}
 +
 +static void
 +do_write_namespace_cb (const char* type, gpointer data_p, gpointer file_p)
 +{
 +    GncXmlDataType_t* data = static_cast<decltype (data)> (data_p);
 +    FILE* out = static_cast<decltype (out)> (file_p);
 +
 +    g_return_if_fail (type && data && out);
 +    g_return_if_fail (data->version == GNC_FILE_BACKEND_VERS);
 +
 +    if (data->ns && !ferror (out))
 +        (data->ns) (out);
 +}
 +
 +static gboolean
 +write_v2_header (FILE* out)
 +{
 +    if (fprintf (out, "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n") < 0
 +        || fprintf (out, "<" GNC_V2_STRING) < 0
 +
 +        || !gnc_xml2_write_namespace_decl (out, "gnc")
 +        || !gnc_xml2_write_namespace_decl (out, "act")
 +        || !gnc_xml2_write_namespace_decl (out, "book")
 +        || !gnc_xml2_write_namespace_decl (out, "cd")
 +        || !gnc_xml2_write_namespace_decl (out, "cmdty")
 +        || !gnc_xml2_write_namespace_decl (out, "price")
 +        || !gnc_xml2_write_namespace_decl (out, "slot")
 +        || !gnc_xml2_write_namespace_decl (out, "split")
 +        || !gnc_xml2_write_namespace_decl (out, "sx")
 +        || !gnc_xml2_write_namespace_decl (out, "trn")
 +        || !gnc_xml2_write_namespace_decl (out, "ts")
 +        || !gnc_xml2_write_namespace_decl (out, "fs")
 +        || !gnc_xml2_write_namespace_decl (out, "bgt")
 +        || !gnc_xml2_write_namespace_decl (out, "recurrence")
 +        || !gnc_xml2_write_namespace_decl (out, "lot"))
 +
 +        return FALSE;
 +
 +    /* now cope with the plugins */
 +    qof_object_foreach_backend (GNC_FILE_BACKEND, do_write_namespace_cb, out);
 +
 +    if (ferror (out) || fprintf (out, ">\n") < 0)
 +        return FALSE;
 +
 +    return TRUE;
 +}
 +
 +gboolean
 +gnc_book_write_to_xml_filehandle_v2 (QofBook* book, FILE* out)
 +{
 +    QofBackend* be;
 +    sixtp_gdv2* gd;
 +    gboolean success = TRUE;
 +
 +    if (!out) return FALSE;
 +
 +    if (!write_v2_header (out)
 +        || !write_counts (out, "book", 1, NULL))
 +        return FALSE;
 +
 +    be = qof_book_get_backend (book);
 +    gd = gnc_sixtp_gdv2_new (book, FALSE, file_rw_feedback, be->percentage);
 +    gd->counter.commodities_total =
 +        gnc_commodity_table_get_size (gnc_commodity_table_get_table (book));
 +    gd->counter.accounts_total = 1 +
 +                                 gnc_account_n_descendants (gnc_book_get_root_account (book));
 +    gd->counter.transactions_total = gnc_book_count_transactions (book);
 +    gd->counter.schedXactions_total =
 +        g_list_length (gnc_book_get_schedxactions (book)->sx_list);
 +    gd->counter.budgets_total = qof_collection_count (
 +                                    qof_book_get_collection (book, GNC_ID_BUDGET));
 +    gd->counter.prices_total = gnc_pricedb_get_num_prices (gnc_pricedb_get_db (
 +                                                               book));
 +
 +    if (!write_book (out, book, gd)
 +        || fprintf (out, "</" GNC_V2_STRING ">\n\n") < 0)
 +        success = FALSE;
 +
 +    g_free (gd);
 +    return success;
 +}
 +
 +/*
 + * This function is called by the "export" code.
 + */
 +gboolean
 +gnc_book_write_accounts_to_xml_filehandle_v2 (QofBackend* be, QofBook* book,
 +                                              FILE* out)
 +{
 +    gnc_commodity_table* table;
 +    Account* root;
 +    int ncom, nacc;
 +    sixtp_gdv2* gd;
 +    gboolean success = TRUE;
 +
 +    if (!out) return FALSE;
 +
 +    root = gnc_book_get_root_account (book);
 +    nacc = 1 + gnc_account_n_descendants (root);
 +
 +    table = gnc_commodity_table_get_table (book);
 +    ncom = gnc_commodity_table_get_size (table);
 +
 +    if (!write_v2_header (out)
 +        || !write_counts (out, "commodity", ncom, "account", nacc, NULL))
 +        return FALSE;
 +
 +    gd = gnc_sixtp_gdv2_new (book, TRUE, file_rw_feedback, be->percentage);
 +    gd->counter.commodities_total = ncom;
 +    gd->counter.accounts_total = nacc;
 +
 +    if (!write_commodities (out, book, gd)
 +        || !write_accounts (out, book, gd)
 +        || fprintf (out, "</" GNC_V2_STRING ">\n\n") < 0)
 +        success = FALSE;
 +
 +    g_free (gd);
 +    return success;
 +}
 +
 +#define BUFLEN 4096
 +
 +/* Compress or decompress function that is to be run in a separate thread.
 + * Returns 1 on success or 0 otherwise, stuffed into a pointer type. */
 +static gpointer
 +gz_thread_func (gz_thread_params_t* params)
 +{
 +    gchar buffer[BUFLEN];
 +    gssize bytes;
 +    gint gzval;
 +    gzFile file;
 +    gint success = 1;
 +
 +#ifdef G_OS_WIN32
 +    {
 +        gchar* conv_name = g_win32_locale_filename_from_utf8 (params->filename);
 +        gchar* perms;
 +
 +        if (!conv_name)
 +        {
 +            g_warning ("Could not convert '%s' to system codepage",
 +                       params->filename);
 +            success = 0;
 +            goto cleanup_gz_thread_func;
 +        }
 +
 +        if (strchr (params->perms, 'b'))
 +            perms = g_strdup (params->perms);
 +        else
 +            perms = g_strdup_printf ("%cb%s", *params->perms, params->perms + 1);
 +
 +        file = gzopen (conv_name, perms);
 +        g_free (perms);
 +        g_free (conv_name);
 +    }
 +#else /* !G_OS_WIN32 */
 +    file = gzopen (params->filename, params->perms);
 +#endif /* G_OS_WIN32 */
 +
 +    if (file == NULL)
 +    {
 +        g_warning ("Child threads gzopen failed");
 +        success = 0;
 +        goto cleanup_gz_thread_func;
 +    }
 +
 +    if (params->compress)
 +    {
 +        while (success)
 +        {
 +            bytes = read (params->fd, buffer, BUFLEN);
 +            if (bytes > 0)
 +            {
 +                if (gzwrite (file, buffer, bytes) <= 0)
 +                {
 +                    gint errnum;
 +                    const gchar* error = gzerror (file, &errnum);
 +                    g_warning ("Could not write the compressed file '%s'. The error is: '%s' (%d)",
 +                               params->filename, error, errnum);
 +                    success = 0;
 +                }
 +            }
 +            else if (bytes == 0)
 +            {
 +                break;
 +            }
 +            else
 +            {
 +                g_warning ("Could not read from pipe. The error is '%s' (errno %d)",
 +                           g_strerror (errno) ? g_strerror (errno) : "", errno);
 +                success = 0;
 +            }
 +        }
 +    }
 +    else
 +    {
 +        while (success)
 +        {
 +            gzval = gzread (file, buffer, BUFLEN);
 +            if (gzval > 0)
 +            {
 +                if (
 +#if COMPILER(MSVC)
 +                    _write
 +#else
 +                    write
 +#endif
 +                    (params->fd, buffer, gzval) < 0)
 +                {
 +                    g_warning ("Could not write to pipe. The error is '%s' (%d)",
 +                               g_strerror (errno) ? g_strerror (errno) : "", errno);
 +                    success = 0;
 +                }
 +            }
 +            else if (gzval == 0)
 +            {
 +                break;
 +            }
 +            else
 +            {
 +                gint errnum;
 +                const gchar* error = gzerror (file, &errnum);
 +                g_warning ("Could not read from compressed file '%s'. The error is: '%s' (%d)",
 +                           params->filename, error, errnum);
 +                success = 0;
 +            }
 +        }
 +    }
 +
 +    if ((gzval = gzclose (file)) != Z_OK)
 +    {
 +        g_warning ("Could not close the compressed file '%s' (errnum %d)",
 +                   params->filename, gzval);
 +        success = 0;
 +    }
 +
 +cleanup_gz_thread_func:
 +    close (params->fd);
 +    g_free (params->filename);
 +    g_free (params->perms);
 +    g_free (params);
 +
 +    return GINT_TO_POINTER (success);
 +}
 +
 +static FILE*
 +try_gz_open (const char* filename, const char* perms, gboolean use_gzip,
 +             gboolean compress)
 +{
 +    if (strstr (filename, ".gz.") != NULL) /* its got a temp extension */
 +        use_gzip = TRUE;
 +
 +    if (!use_gzip)
 +        return g_fopen (filename, perms);
 +
 +    {
 +        int filedes[2];
 +        GThread* thread;
 +        GError* error = NULL;
 +        gz_thread_params_t* params;
 +        FILE* file;
 +
 +#ifdef G_OS_WIN32
 +        if (_pipe (filedes, 4096, _O_BINARY) < 0)
 +        {
 +#else
 +        if (pipe (filedes) < 0)
 +        {
 +#endif
 +            g_warning ("Pipe call failed. Opening uncompressed file.");
 +            return g_fopen (filename, perms);
 +        }
 +
 +        params = g_new (gz_thread_params_t, 1);
 +        params->fd = filedes[compress ? 0 : 1];
 +        params->filename = g_strdup (filename);
 +        params->perms = g_strdup (perms);
 +        params->compress = compress;
 +
 +#ifndef HAVE_GLIB_2_32
 +        thread = g_thread_create ((GThreadFunc) gz_thread_func, params,
 +                                  TRUE, &error);
 +#else
 +        thread = g_thread_new ("xml_thread", (GThreadFunc) gz_thread_func,
 +                               params);
 +#endif
 +        if (!thread)
 +        {
 +            g_warning ("Could not create thread for (de)compression: %s",
 +                       error->message);
 +            g_error_free (error);
 +            g_free (params->filename);
 +            g_free (params->perms);
 +            g_free (params);
 +            close (filedes[0]);
 +            close (filedes[1]);
 +
 +            return g_fopen (filename, perms);
 +        }
 +
 +        if (compress)
 +            file = fdopen (filedes[1], "w");
 +        else
 +            file = fdopen (filedes[0], "r");
 +
 +        G_LOCK (threads);
 +        if (!threads)
 +            threads = g_hash_table_new (g_direct_hash, g_direct_equal);
 +
 +        g_hash_table_insert (threads, file, thread);
 +        G_UNLOCK (threads);
 +
 +        return file;
 +    }
 +}
 +
 +static gboolean
 +wait_for_gzip (FILE* file)
 +{
 +    gboolean retval = TRUE;
 +
 +    G_LOCK (threads);
 +    if (threads)
 +    {
 +        GThread* thread = static_cast<decltype (thread)> (g_hash_table_lookup (threads,
 +                                                          file));
 +        if (thread)
 +        {
 +            g_hash_table_remove (threads, file);
 +            retval = GPOINTER_TO_INT (g_thread_join (thread));
 +        }
 +    }
 +    G_UNLOCK (threads);
 +
 +    return retval;
 +}
 +
 +gboolean
 +gnc_book_write_to_xml_file_v2 (
 +    QofBook* book,
 +    const char* filename,
 +    gboolean compress)
 +{
 +    FILE* out;
 +    gboolean success = TRUE;
 +
 +    out = try_gz_open (filename, "w", compress, TRUE);
 +
 +    /* Try to write as much as possible */
 +    if (!out
 +        || !gnc_book_write_to_xml_filehandle_v2 (book, out)
 +        || !write_emacs_trailer (out))
 +        success = FALSE;
 +
 +    /* Close the output stream */
 +    if (out && fclose (out))
 +        success = FALSE;
 +
 +    /* Optionally wait for parallel compression threads */
 +    if (out && compress)
 +        if (!wait_for_gzip (out))
 +            success = FALSE;
 +
 +    return success;
 +}
 +
 +/*
 + * Have to pass in the backend as this routine needs the temporary
 + * backend for file export, not the real backend which could be
 + * postgress or anything else.
 + */
 +gboolean
 +gnc_book_write_accounts_to_xml_file_v2 (
 +    QofBackend* be,
 +    QofBook* book,
 +    const char* filename)
 +{
 +    FILE* out;
 +    gboolean success = TRUE;
 +
 +    out = g_fopen (filename, "w");
 +
 +    /* Try to write as much as possible */
 +    if (!out
 +        || !gnc_book_write_accounts_to_xml_filehandle_v2 (be, book, out)
 +        || !write_emacs_trailer (out))
 +        success = FALSE;
 +
 +    /* Close the output stream */
 +    if (out && fclose (out))
 +        success = FALSE;
 +
 +    if (!success && !qof_backend_check_error (be))
 +    {
 +
 +        /* Use a generic write error code */
 +        qof_backend_set_error (be, ERR_FILEIO_WRITE_ERROR);
 +    }
 +
 +    return success;
 +}
 +
 +/***********************************************************************/
 +static gboolean
 +is_gzipped_file (const gchar* name)
 +{
 +    unsigned char buf[2];
 +    int fd = g_open (name, O_RDONLY, 0);
 +
 +    if (fd == -1)
 +    {
 +        return FALSE;
 +    }
 +
 +    if (read (fd, buf, 2) != 2)
 +    {
 +        close (fd);
 +        return FALSE;
 +    }
 +    close (fd);
 +
 +    if (buf[0] == 037 && buf[1] == 0213)
 +    {
 +        return TRUE;
 +    }
 +
 +    return FALSE;
 +}
 +
 +QofBookFileType
 +gnc_is_xml_data_file_v2 (const gchar* name, gboolean* with_encoding)
 +{
 +    if (is_gzipped_file (name))
 +    {
 +        gzFile file = NULL;
 +        char first_chunk[256];
 +        int num_read;
 +
 +#ifdef G_OS_WIN32
 +        {
 +            gchar* conv_name = g_win32_locale_filename_from_utf8 (name);
 +            if (!conv_name)
 +                g_warning ("Could not convert '%s' to system codepage", name);
 +            else
 +            {
 +                file = gzopen (conv_name, "rb");
 +                g_free (conv_name);
 +            }
 +        }
 +#else
 +        file = gzopen (name, "r");
 +#endif
 +        if (file == NULL)
 +            return GNC_BOOK_NOT_OURS;
 +
 +        num_read = gzread (file, first_chunk, sizeof (first_chunk) - 1);
 +        gzclose (file);
 +
 +        if (num_read < 1)
 +            return GNC_BOOK_NOT_OURS;
 +
 +        return gnc_is_our_first_xml_chunk (first_chunk, with_encoding);
 +    }
 +
 +    return (gnc_is_our_xml_file (name, with_encoding));
 +}
 +
 +
 +static void
 +replace_character_references (gchar* string)
 +{
 +    gchar* cursor, *semicolon, *tail;
 +    glong number;
 +
 +    for (cursor = strstr (string, "&#");
 +         cursor && *cursor;
 +         cursor = strstr (cursor, "&#"))
 +    {
 +        semicolon = strchr (cursor, ';');
 +        if (semicolon && *semicolon)
 +        {
 +
 +            /* parse number */
 +            errno = 0;
 +            if (* (cursor + 2) == 'x')
 +            {
 +                number = strtol (cursor + 3, &tail, 16);
 +            }
 +            else
 +            {
 +                number = strtol (cursor + 2, &tail, 10);
 +            }
 +            if (errno || tail != semicolon || number < 0 || number > 255)
 +            {
 +                PWARN ("Illegal character reference");
 +                return;
 +            }
 +
 +            /* overwrite '&' with the specified character */
 +            *cursor = (gchar) number;
 +            cursor++;
 +            if (* (semicolon + 1))
 +            {
 +                /* move text after semicolon the the left */
 +                tail = g_strdup (semicolon + 1);
 +                strcpy (cursor, tail);
 +                g_free (tail);
 +            }
 +            else
 +            {
 +                /* cut here */
 +                *cursor = '\0';
 +            }
 +
 +        }
 +        else
 +        {
 +            PWARN ("Unclosed character reference");
 +            return;
 +        }
 +    }
 +}
 +
 +static void
 +conv_free (conv_type* conv)
 +{
 +    if (conv)
 +    {
 +        g_free (conv->utf8_string);
 +        g_free (conv);
 +    }
 +}
 +
 +static void
 +conv_list_free (GList* conv_list)
 +{
 +    g_list_foreach (conv_list, (GFunc) conv_free, NULL);
 +    g_list_free (conv_list);
 +}
 +
 +typedef struct
 +{
 +    GQuark encoding;
 +    GIConv iconv;
 +} iconv_item_type;
 +
 +gint
 +gnc_xml2_find_ambiguous (const gchar* filename, GList* encodings,
 +                         GHashTable** unique, GHashTable** ambiguous,
 +                         GList** impossible)
 +{
 +    FILE* file = NULL;
 +    GList* iconv_list = NULL, *conv_list = NULL, *iter;
 +    iconv_item_type* iconv_item = NULL, *ascii = NULL;
 +    const gchar* enc;
 +    GHashTable* processed = NULL;
 +    gint n_impossible = 0;
 +    GError* error = NULL;
 +    gboolean is_compressed;
 +    gboolean clean_return = FALSE;
 +
 +    is_compressed = is_gzipped_file (filename);
 +    file = try_gz_open (filename, "r", is_compressed, FALSE);
 +    if (file == NULL)
 +    {
 +        PWARN ("Unable to open file %s", filename);
 +        goto cleanup_find_ambs;
 +    }
 +
 +    /* we need ascii */
 +    ascii = g_new (iconv_item_type, 1);
 +    ascii->encoding = g_quark_from_string ("ASCII");
 +    ascii->iconv = g_iconv_open ("UTF-8", "ASCII");
 +    if (ascii->iconv == (GIConv) - 1)
 +    {
 +        PWARN ("Unable to open ASCII ICONV conversion descriptor");
 +        goto cleanup_find_ambs;
 +    }
 +
 +    /* call iconv_open on encodings */
 +    for (iter = encodings; iter; iter = iter->next)
 +    {
 +        iconv_item = g_new (iconv_item_type, 1);
 +        iconv_item->encoding = GPOINTER_TO_UINT (iter->data);
 +        if (iconv_item->encoding == ascii->encoding)
 +        {
 +            continue;
 +        }
 +
 +        enc = g_quark_to_string (iconv_item->encoding);
 +        iconv_item->iconv = g_iconv_open ("UTF-8", enc);
 +        if (iconv_item->iconv == (GIConv) - 1)
 +        {
 +            PWARN ("Unable to open IConv conversion descriptor for '%s'", enc);
 +            goto cleanup_find_ambs;
 +        }
 +        else
 +        {
 +            iconv_list = g_list_prepend (iconv_list, iconv_item);
 +        }
 +    }
 +
 +    /* prepare data containers */
 +    if (unique)
 +        *unique = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
 +                                         (GDestroyNotify) conv_free);
 +    if (ambiguous)
 +        *ambiguous = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
 +                                            (GDestroyNotify) conv_list_free);
 +    if (impossible)
 +        *impossible = NULL;
 +    processed = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
 +
 +    /* loop through lines */
 +    while (1)
 +    {
 +        gchar line[256], *word, *utf8;
 +        gchar** word_array, **word_cursor;
 +        conv_type* conv = NULL;
 +
 +        if (!fgets (line, sizeof (line) - 1, file))
 +        {
 +            if (feof (file))
 +            {
 +                break;
 +            }
 +            else
 +            {
 +                goto cleanup_find_ambs;
 +            }
 +        }
 +
 +        g_strchomp (line);
 +        replace_character_references (line);
 +        word_array = g_strsplit_set (line, "> <", 0);
 +
 +        /* loop through words */
 +        for (word_cursor = word_array; *word_cursor; word_cursor++)
 +        {
 +            word = *word_cursor;
 +            if (!word)
 +                continue;
 +
 +            utf8 = g_convert_with_iconv (word, -1, ascii->iconv,
 +                                         NULL, NULL, &error);
 +            if (utf8)
 +            {
 +                /* pure ascii */
 +                g_free (utf8);
 +                continue;
 +            }
 +            g_error_free (error);
 +            error = NULL;
 +
 +            if (g_hash_table_lookup_extended (processed, word, NULL, NULL))
 +            {
 +                /* already processed */
 +                continue;
 +            }
 +
 +            /* loop through encodings */
 +            conv_list = NULL;
 +            for (iter = iconv_list; iter; iter = iter->next)
 +            {
 +                iconv_item = static_cast<decltype (iconv_item)> (iter->data);
 +                utf8 = g_convert_with_iconv (word, -1, iconv_item->iconv,
 +                                             NULL, NULL, &error);
 +                if (utf8)
 +                {
 +                    conv = g_new (conv_type, 1);
 +                    conv->encoding = iconv_item->encoding;
 +                    conv->utf8_string = utf8;
 +                    conv_list = g_list_prepend (conv_list, conv);
 +                }
 +                else
 +                {
 +                    g_error_free (error);
 +                    error = NULL;
 +                }
 +            }
 +
 +            /* no successful conversion */
 +            if (!conv_list)
 +            {
 +                if (impossible)
 +                    *impossible = g_list_append (*impossible, g_strdup (word));
 +                n_impossible++;
 +            }
 +
 +            /* more than one successful conversion */
 +            else if (conv_list->next)
 +            {
 +                if (ambiguous)
 +                {
 +                    g_hash_table_insert (*ambiguous, g_strdup (word), conv_list);
 +                }
 +                else
 +                {
 +                    conv_list_free (conv_list);
 +                }
 +            }
 +
 +            /* only one successful conversion */
 +            else
 +            {
 +                if (unique)
 +                {
 +                    g_hash_table_insert (*unique, g_strdup (word), conv);
 +                }
 +                else
 +                {
 +                    conv_free (conv);
 +                }
 +                g_list_free (conv_list);
 +            }
 +
 +            g_hash_table_insert (processed, g_strdup (word), NULL);
 +        }
 +        g_strfreev (word_array);
 +    }
 +
 +    clean_return = TRUE;
 +
 +cleanup_find_ambs:
 +
 +    if (iconv_list)
 +    {
 +        for (iter = iconv_list; iter; iter = iter->next)
 +        {
 +            if (iter->data)
 +            {
 +                g_iconv_close (((iconv_item_type*) iter->data)->iconv);
 +                g_free (iter->data);
 +            }
 +        }
 +        g_list_free (iconv_list);
 +    }
 +    if (processed)
 +        g_hash_table_destroy (processed);
 +    if (ascii)
 +        g_free (ascii);
 +    if (file)
 +    {
 +        fclose (file);
 +        if (is_compressed)
 +            wait_for_gzip (file);
 +    }
 +
 +    return (clean_return) ? n_impossible : -1;
 +}
 +
 +typedef struct
 +{
 +    gchar* filename;
 +    GHashTable* subst;
 +} push_data_type;
 +
 +static void
 +parse_with_subst_push_handler (xmlParserCtxtPtr xml_context,
 +                               push_data_type* push_data)
 +{
 +    const gchar* filename;
 +    FILE* file = NULL;
 +    GIConv ascii = (GIConv) - 1;
 +    GString* output = NULL;
 +    GError* error = NULL;
 +    gboolean is_compressed;
 +
 +    filename = push_data->filename;
 +    is_compressed = is_gzipped_file (filename);
 +    file = try_gz_open (filename, "r", is_compressed, FALSE);
 +    if (file == NULL)
 +    {
 +        PWARN ("Unable to open file %s", filename);
 +        goto cleanup_push_handler;
 +    }
 +
 +    ascii = g_iconv_open ("UTF-8", "ASCII");
 +    if (ascii == (GIConv) - 1)
 +    {
 +        PWARN ("Unable to open ASCII ICONV conversion descriptor");
 +        goto cleanup_push_handler;
 +    }
 +
 +    /* loop through lines */
 +    while (1)
 +    {
 +        gchar line[256], *word, *repl, *utf8;
 +        gint pos, len;
 +        gchar* start, *cursor;
 +
 +        if (!fgets (line, sizeof (line) - 1, file))
 +        {
 +            if (feof (file))
 +            {
 +                break;
 +            }
 +            else
 +            {
 +                goto cleanup_push_handler;
 +            }
 +        }
 +
 +        replace_character_references (line);
 +        output = g_string_new (line);
 +
 +        /* loop through words */
 +        cursor = output->str;
 +        pos = 0;
 +        while (1)
 +        {
 +            /* ignore delimiters */
 +            while (*cursor == '>' || *cursor == ' ' || *cursor == '<' ||
 +                   *cursor == '\n')
 +            {
 +                cursor++;
 +                pos += 1;
 +            }
 +
 +            if (!*cursor)
 +                /* welcome to EOL */
 +                break;
 +
 +            /* search for a delimiter */
 +            start = cursor;
 +            len = 0;
 +            while (*cursor && *cursor != '>' && *cursor != ' ' && *cursor != '<' &&
 +                   *cursor != '\n')
 +            {
 +                cursor++;
 +                len++;
 +            }
 +
 +            utf8 = g_convert_with_iconv (start, len, ascii, NULL, NULL, &error);
 +
 +            if (utf8)
 +            {
 +                /* pure ascii */
 +                g_free (utf8);
 +                pos += len;
 +            }
 +            else
 +            {
 +                g_error_free (error);
 +                error = NULL;
 +
 +                word = g_strndup (start, len);
 +                repl = static_cast<decltype (repl)> (g_hash_table_lookup (push_data->subst,
 +                                                                          word));
 +                g_free (word);
 +                if (repl)
 +                {
 +                    /* there is a replacement */
 +                    output = g_string_insert (g_string_erase (output, pos, len),
 +                                              pos, repl);
 +                    pos += strlen (repl);
 +                    cursor = output->str + pos;
 +                }
 +                else
 +                {
 +                    /* there is no replacement, return immediately */
 +                    goto cleanup_push_handler;
 +                }
 +            }
 +        }
 +
 +        if (xmlParseChunk (xml_context, output->str, output->len, 0) != 0)
 +        {
 +            goto cleanup_push_handler;
 +        }
 +    }
 +
 +    /* last chunk */
 +    xmlParseChunk (xml_context, "", 0, 1);
 +
 +cleanup_push_handler:
 +
 +    if (output)
 +        g_string_free (output, TRUE);
 +    if (ascii != (GIConv) - 1)
 +        g_iconv_close (ascii);
 +    if (file)
 +    {
 +        fclose (file);
 +        if (is_compressed)
 +            wait_for_gzip (file);
 +    }
 +}
 +
 +gboolean
 +gnc_xml2_parse_with_subst (FileBackend* fbe, QofBook* book, GHashTable* subst)
 +{
 +    push_data_type* push_data;
 +    gboolean success;
 +
 +    push_data = g_new (push_data_type, 1);
 +    push_data->filename = fbe->fullpath;
 +    push_data->subst = subst;
 +
 +    success = qof_session_load_from_xml_file_v2_full (
 +                  fbe, book, (sixtp_push_handler) parse_with_subst_push_handler,
 +                  push_data, GNC_BOOK_XML2_FILE);
 +
 +    if (success)
 +        qof_instance_set_dirty (QOF_INSTANCE (book));
 +
 +    return success;
 +}
diff --cc src/business/business-ledger/gncEntryLedgerLoad.c
index eeac11f,ef099d1..c277836
--- a/src/business/business-ledger/gncEntryLedgerLoad.c
+++ b/src/business/business-ledger/gncEntryLedgerLoad.c
@@@ -458,9 -447,9 +459,10 @@@ void gnc_entry_ledger_load (GncEntryLed
                  }
                  else
                  {
+                     gncEntrySetBillTaxable (blank_entry, table != NULL);
                      gncEntrySetBillTaxTable (blank_entry, table);
                      gncEntrySetBillTaxIncluded (blank_entry, taxincluded);
 +                    gncEntrySetBillPrice (blank_entry, price);
                  }
              }
  
diff --cc src/libqof/qof/gnc-date.cpp
index 18f7405,3da2eca..dd0fa56
--- a/src/libqof/qof/gnc-date.cpp
+++ b/src/libqof/qof/gnc-date.cpp
@@@ -217,22 -368,31 +217,21 @@@ gnc_mktime (struct tm* time
  time64
  gnc_timegm (struct tm* time)
  {
 -     GDateTime *gdt;
 -     time64 secs;
 -     normalize_struct_tm (time);
 -     gdt = g_date_time_new_utc (time->tm_year + 1900, time->tm_mon,
 -				time->tm_mday, time->tm_hour, time->tm_min,
 -				(gdouble)(time->tm_sec));
 -     if (gdt == NULL)
 -     {
 -         PERR("Failed to get valid GDateTime with struct tm: %d-%d-%d %d:%d:%d",
 -              time->tm_year + 1900, time->tm_mon, time->tm_mday, time->tm_hour,
 -              time->tm_min, time->tm_sec);
 -         return 0;
 -     }
 -     secs = g_date_time_to_unix (gdt);
 -     g_date_time_unref (gdt);
 -     return secs;
 +    try
 +    {
 +	normalize_struct_tm(time);
 +	return static_cast<time64>(GncDateTime(*time));
 +    }
 +    catch(std::invalid_argument)
 +    {
 +	return 0;
 +    }
- 
  }
  
 -gchar*
 +char*
  gnc_ctime (const time64 *secs)
  {
 -     GDateTime *gdt = gnc_g_date_time_new_from_unix_local (*secs);
 -     gchar *string = g_date_time_format (gdt, "%a %b %e %H:%M:%S %Y");
 -     g_date_time_unref (gdt);
 -     return string;
 +    return gnc_print_time64(*secs, "%a %b %d %H:%M:%S %Y");
  }
  
  time64
@@@ -1229,8 -1567,58 +1229,29 @@@ gnc_dmy2timespec_end (int day, int mont
      return gnc_dmy2timespec_internal (day, month, year, FALSE);
  }
  
+ Timespec
+ gnc_dmy2timespec_neutral (int day, int month, int year)
+ {
+     struct tm date;
 -    Timespec ts = {0, 0};
 -    GTimeZone *zone = gnc_g_time_zone_new_local();
 -    GDateTime *gdt = gnc_g_date_time_new_local (year, month, day, 12, 0, 0.0);
 -    int interval = g_time_zone_find_interval (zone, G_TIME_TYPE_STANDARD,
 -                                              g_date_time_to_unix(gdt));
 -    int offset = g_time_zone_get_offset(gnc_g_time_zone_new_local(),
 -                                        interval) / 3600;
 -    g_date_time_unref (gdt);
+     memset (&date, 0, sizeof(struct tm));
+     date.tm_year = year - 1900;
+     date.tm_mon = month - 1;
+     date.tm_mday = day;
 -    date.tm_hour = offset < -11 ? -offset : offset > 13 ? 24 - offset : 11;
++    date.tm_hour = 11;
+     date.tm_min = 0;
+     date.tm_sec = 0;
+ 
 -    ts.tv_sec = gnc_timegm(&date);
 -    return ts;
++    GncDateTime gncdt(date);
++    auto offset = gncdt.offset() / 3600;
++    if (offset < -11)
++        date.tm_hour = -offset;
++    if (offset > 13)
++        date.tm_hour = 24 - offset;
++
++    return {gnc_timegm(&date), 0};
+ }
  /********************************************************************\
  \********************************************************************/
 -
 -long int
 -gnc_timezone (const struct tm *tm)
 -{
 -    g_return_val_if_fail (tm != NULL, 0);
 -
 -#ifdef HAVE_STRUCT_TM_GMTOFF
 -    /* tm_gmtoff is seconds *east* of UTC and is
 -     * already adjusted for daylight savings time. */
 -    return -(tm->tm_gmtoff);
 -#else
 -    {
 -        long tz_seconds;
 -        /* timezone is seconds *west* of UTC and is
 -         * not adjusted for daylight savings time.
 -         * In Spring, we spring forward, wheee! */
 -# if COMPILER(MSVC)
 -        _get_timezone(&tz_seconds);
 -# else
 -        tz_seconds = timezone;
 -# endif
 -        return (long int)(tz_seconds - (tm->tm_isdst > 0 ? 3600 : 0));
 -    }
 -#endif
 -}
 -
 -
  void
  timespecFromTime64 ( Timespec *ts, time64 t )
  {
@@@ -1283,9 -1674,10 +1304,9 @@@ GDate* gnc_g_date_new_today (
  
  Timespec gdate_to_timespec (GDate d)
  {
-     return gnc_dmy2timespec(g_date_get_day(&d),
-                             g_date_get_month(&d),
-                             g_date_get_year(&d));
 -    gnc_gdate_range_check (&d);
+     return gnc_dmy2timespec_neutral (g_date_get_day(&d),
+                                      g_date_get_month(&d),
+                                      g_date_get_year(&d));
  }
  
  static void
diff --cc src/libqof/qof/test/test-gnc-date.c
index 712a8f2,4210177..037ade6
--- a/src/libqof/qof/test/test-gnc-date.c
+++ b/src/libqof/qof/test/test-gnc-date.c
@@@ -64,75 -46,18 +64,84 @@@ typedef struc
  
  typedef struct
  {
 -    GDateTime *(*new_local)(gint, gint, gint, gint, gint, gdouble);
 -    GDateTime *(*new_utc)(gint, gint, gint, gint, gint, gdouble);
 -    GDateTime *(*adjust_for_dst)(GDateTime *, GTimeZone *);
 -    GDateTime *(*new_from_unix_local)(time64);
 -    GDateTime *(*new_from_timeval_local)(GTimeVal *);
 -    GDateTime *(*new_now_local)(void);
 -    GDateTime *(*to_local)(GDateTime *);
 -} _GncDateTime;
 +    TZOffset off_zulu;
 +    TZOffset off_05w;
 +    TZOffset off_0840e;
 +    Timespec ts0;
 +    Timespec ts1;
 +    Timespec ts2;
 +    Timespec ts3;
 +    Timespec ts4;
 +    Timespec ts5;
 +} FixtureA;
 +
 +static int
 +offset_secs (TZOffset tz)
 +{
 +    return 3600 * tz.hours + 60 * tz.minutes;
 +}
 +
 +static char*
 +offset_string (TZOffset tz)
 +{
 +    return g_strdup_printf("%+02d%02d", tz.hours, tz.minutes);
 +}
 +
 +static void setup (FixtureA *f, gconstpointer pData)
 +{
 +    f->ts0 = (Timespec){gnc_time(NULL), 0};
 +    f->off_zulu = (TZOffset){0, 0};
 +    f->off_05w = (TZOffset){-5, 0};
 +    f->off_0840e = (TZOffset){8, 40};
 +    f->ts1 = (Timespec){607009407, 0}; //1989-3-27 13:43:27 Z
 +    f->ts2 = (Timespec){1604748079, 0}; //2020-11-7 06:21:19 -05:00
 +    f->ts3 = (Timespec){1341398864, 0}; //2012-07-04 19:27:44 +08:40
 +    f->ts4 = (Timespec){-261104801, 0}; //1961-09-22 17:53:19 -05:00
 +    f->ts5 = (Timespec){2873938879LL, 0}; //2061-01-25 23:21:19 -05:00
 +}
  
 -static _GncDateTime gncdt;
 -extern void _gnc_date_time_init (_GncDateTime *);
 +typedef struct
 +{
 +    int yr;
 +    int mon;
 +    int day;
 +    time64 secs;
 +} TimeMap;
 +
 +typedef struct
 +{
 +    TimeMap test[4];
 +} FixtureB;
 +
 +static void
 +setup_begin(FixtureB *f, gconstpointer pData)
 +{
 +    f->test[0] = (TimeMap){1999, 7, 21, INT64_C(932515200)};
 +    f->test[1] = (TimeMap){1918, 3, 31, INT64_C(-1633305600)};
 +    f->test[2] = (TimeMap){1918, 4, 1, INT64_C(-1633219200)};
 +    f->test[3] = (TimeMap){2057, 11, 20, INT64_C(2773440000)};
 +}
 +
 +static void
++setup_neutral(FixtureB *f, gconstpointer pData)
++{
++    f->test[0] = (TimeMap){1999, 7, 21, INT64_C(932554800)};
++    f->test[1] = (TimeMap){1918, 3, 31, INT64_C(-1633266000)};
++    f->test[2] = (TimeMap){1918, 4, 1, INT64_C(-1633179600)};
++    f->test[3] = (TimeMap){2057, 11, 20, INT64_C(2773479600)};
++}
++
++static void
 +setup_end(FixtureB *f, gconstpointer pData)
 +{
 +    f->test[0] = (TimeMap){1999, 7, 21, INT64_C(932601599)};
 +    f->test[1] = (TimeMap){1918, 3, 31, INT64_C(-1633219201)};
 +    f->test[2] = (TimeMap){1918, 4, 1, INT64_C(-1633132801)};
 +    f->test[3] = (TimeMap){2057, 11, 20, INT64_C(2773526399)};
 +}
  
 +void test_suite_gnc_date ( void );
 +static GTimeZone *tz;
  /* gnc_localtime just creates a tm on the heap and calls
   * gnc_localtime_r with it, so this suffices to test both.
   */
@@@ -1807,24 -1929,87 +1816,37 @@@ Timespe
  gnc_dmy2timespec_end (int day, int month, int year)// C: 1  Local: 0:0:0
  */
  static void
 -test_gnc_dmy2timespec_end (void)
 -{
 -    GDateTime *gdt1 = gncdt.new_local (1999, 7, 21,23,59, 59);
 -    GDateTime *gdt2 = gncdt.new_local (1918, 3, 30, 23, 59, 59);
 -    GDateTime *gdt3 = gncdt.new_local (1918, 3, 31, 23, 59, 59);
 -    GDateTime *gdt4 = gncdt.new_local (2057, 11, 20, 23, 59, 59);
 -
 -    gint day, mon, yr;
 -    Timespec t, r_t;
 -
 -    t = g_date_time_to_timespec (gdt1);
 -    g_date_time_get_ymd (gdt1, &yr, &mon, &day);
 -    r_t = gnc_dmy2timespec_end (day, mon, yr);
 -    g_assert_cmpint (r_t.tv_sec, ==, t.tv_sec);
 -    g_assert_cmpint (r_t.tv_nsec, ==, t.tv_nsec);
 -
 -    t = g_date_time_to_timespec (gdt2);
 -    g_date_time_get_ymd (gdt2, &yr, &mon, &day);
 -    r_t = gnc_dmy2timespec_end (day, mon, yr);
 -    g_assert_cmpint (r_t.tv_sec, ==, t.tv_sec);
 -    g_assert_cmpint (r_t.tv_nsec, ==, t.tv_nsec);
 -
 -    t = g_date_time_to_timespec (gdt3);
 -    g_date_time_get_ymd (gdt3, &yr, &mon, &day);
 -    r_t = gnc_dmy2timespec_end (day, mon, yr);
 -    g_assert_cmpint (r_t.tv_sec, ==, t.tv_sec);
 -    g_assert_cmpint (r_t.tv_nsec, ==, t.tv_nsec);
 -
 -    t = g_date_time_to_timespec (gdt4);
 -    g_date_time_get_ymd (gdt4, &yr, &mon, &day);
 -    r_t = gnc_dmy2timespec_end (day, mon, yr);
 -    g_assert_cmpint (r_t.tv_sec, ==, t.tv_sec);
 -    g_assert_cmpint (r_t.tv_nsec, ==, t.tv_nsec);
 -
 -    g_date_time_unref (gdt1);
 -    g_date_time_unref (gdt2);
 -    g_date_time_unref (gdt3);
 -    g_date_time_unref (gdt4);
 +test_gnc_dmy2timespec_end (FixtureB *f, gconstpointer pData)
 +{
 +    for (int i = 0; i < sizeof(f->test)/sizeof(TimeMap); ++i)
 +    {
 +#ifdef HAVE_STRUCT_TM_GMTOFF
 +        struct tm tm = {59, 59, 23, f->test[i].day, f->test[i].mon - 1,
 +                        f->test[i].yr - 1900, 0, 0, -1, 0, NULL};
 +#else
 +        struct tm tm = {59, 59, 23, f->test[i].day, f->test[i].mon - 1,
 +                        f->test[i].yr - 1900, 0, 0, -1};
 +#endif
 +        Timespec r_t = gnc_dmy2timespec_end (f->test[i].day, f->test[i].mon,
 +                                             f->test[i].yr);
 +        int offset = gnc_mktime(&tm) - gnc_timegm(&tm);
 +        g_assert_cmpint (r_t.tv_sec, ==, f->test[i].secs + offset);
 +    }
  }
  
+ /*gnc_dmy2timespec_neutral*/
+ static void
 -test_gnc_dmy2timespec_neutral (void)
 -{
 -    GDateTime *gdt1 = gncdt.new_utc (1999, 7, 21, 11, 0, 0);
 -    GDateTime *gdt2 = gncdt.new_utc (1918, 3, 31, 11, 0, 0);
 -    GDateTime *gdt3 = gncdt.new_utc (1918, 4, 1, 11, 0, 0);
 -    GDateTime *gdt4 = gncdt.new_utc (2057, 11, 20, 11, 0, 0);
 -
 -    gint day, mon, yr;
 -    Timespec t, r_t;
 -
 -    t = g_date_time_to_timespec (gdt1);
 -    g_date_time_get_ymd (gdt1, &yr, &mon, &day);
 -    r_t = gnc_dmy2timespec_neutral (day, mon, yr);
 -    g_assert_cmpint (r_t.tv_sec, ==, t.tv_sec);
 -    g_assert_cmpint (r_t.tv_nsec, ==, t.tv_nsec);
 -
 -    t = g_date_time_to_timespec (gdt2);
 -    g_date_time_get_ymd (gdt2, &yr, &mon, &day);
 -    r_t = gnc_dmy2timespec_neutral (day, mon, yr);
 -    g_assert_cmpint (r_t.tv_sec, ==, t.tv_sec);
 -    g_assert_cmpint (r_t.tv_nsec, ==, t.tv_nsec);
 -
 -    t = g_date_time_to_timespec (gdt3);
 -    g_date_time_get_ymd (gdt3, &yr, &mon, &day);
 -    r_t = gnc_dmy2timespec_neutral (day, mon, yr);
 -    g_assert_cmpint (r_t.tv_sec, ==, t.tv_sec);
 -    g_assert_cmpint (r_t.tv_nsec, ==, t.tv_nsec);
 -
 -    t = g_date_time_to_timespec (gdt4);
 -    g_date_time_get_ymd (gdt4, &yr, &mon, &day);
 -    r_t = gnc_dmy2timespec_neutral (day, mon, yr);
 -    g_assert_cmpint (r_t.tv_sec, ==, t.tv_sec);
 -    g_assert_cmpint (r_t.tv_nsec, ==, t.tv_nsec);
 -
 -    g_date_time_unref (gdt1);
 -    g_date_time_unref (gdt2);
 -    g_date_time_unref (gdt3);
 -    g_date_time_unref (gdt4);
++test_gnc_dmy2timespec_neutral (FixtureB *f, gconstpointer pData)
++{
++    for (int i = 0; i < sizeof(f->test)/sizeof(TimeMap); ++i)
++    {
++        Timespec r_t = gnc_dmy2timespec_neutral (f->test[i].day, f->test[i].mon,
++                                             f->test[i].yr);
++
++        g_assert_cmpint (r_t.tv_sec, ==, f->test[i].secs);
++    }
+ }
++
  /* gnc_timezone
  long int
  gnc_timezone (const struct tm *tm)// C: 5 in 2  Local: 2:0:0
@@@ -1909,25 -2127,51 +1931,17 @@@ test_timespec_to_gdate (FixtureA *f, gc
  Timespec gdate_to_timespec (GDate d)// C: 7 in 6  Local: 0:0:0
  */
  static void
 -test_gdate_to_timespec (void)
 -{
 -    GDateTime *gdt1 = gncdt.new_utc (1999, 7, 21, 11, 0, 0);
 -    GDateTime *gdt2 = gncdt.new_utc (1918, 3, 31, 11, 0, 0);
 -    GDateTime *gdt3 = gncdt.new_utc (1918, 4, 1, 11, 0, 0);
 -    GDateTime *gdt4 = gncdt.new_utc (2057, 11, 20, 11, 0, 0);
 -
 -    gint day, mon, yr;
 -    Timespec t, r_t;
 -    GDate gd;
 -
 -    g_date_clear (&gd, 1);
 -
 -    t = g_date_time_to_timespec (gdt1);
 -    g_date_time_get_ymd (gdt1, &yr, &mon, &day);
 -    g_date_set_dmy (&gd, day, mon, yr);
 -    r_t = gdate_to_timespec (gd);
 -    g_assert_cmpint (r_t.tv_sec, ==, t.tv_sec);
 -    g_assert_cmpint (r_t.tv_nsec, ==, t.tv_nsec);
 -
 -    t = g_date_time_to_timespec (gdt2);
 -    g_date_time_get_ymd (gdt2, &yr, &mon, &day);
 -    g_date_set_dmy (&gd, day, mon, yr);
 -    r_t = gdate_to_timespec (gd);
 -    g_assert_cmpint (r_t.tv_sec, ==, t.tv_sec);
 -    g_assert_cmpint (r_t.tv_nsec, ==, t.tv_nsec);
 -
 -    t = g_date_time_to_timespec (gdt3);
 -    g_date_time_get_ymd (gdt3, &yr, &mon, &day);
 -    g_date_set_dmy (&gd, day, mon, yr);
 -    r_t = gdate_to_timespec (gd);
 -    g_assert_cmpint (r_t.tv_sec, ==, t.tv_sec);
 -    g_assert_cmpint (r_t.tv_nsec, ==, t.tv_nsec);
 -
 -    t = g_date_time_to_timespec (gdt4);
 -    g_date_time_get_ymd (gdt4, &yr, &mon, &day);
 -    g_date_set_dmy (&gd, day, mon, yr);
 -    r_t = gdate_to_timespec (gd);
 -    g_assert_cmpint (r_t.tv_sec, ==, t.tv_sec);
 -    g_assert_cmpint (r_t.tv_nsec, ==, t.tv_nsec);
 -
 -    g_date_time_unref (gdt1);
 -    g_date_time_unref (gdt2);
 -    g_date_time_unref (gdt3);
 -    g_date_time_unref (gdt4);
 +test_gdate_to_timespec (FixtureB *f, gconstpointer pData)
 +{
 +    for (int i = 0; i < sizeof(f->test)/sizeof(TimeMap); ++i)
 +    {
- #ifdef HAVE_STRUCT_TM_GMTOFF
-         struct tm tm = {0, 0, 0, f->test[i].day, f->test[i].mon - 1,
-                         f->test[i].yr - 1900, 0, 0, -1, 0, NULL};
- #else
-         struct tm tm = {0, 0, 0, f->test[i].day, f->test[i].mon - 1,
-                         f->test[i].yr - 1900, 0, 0, -1};
- #endif
 +        GDate gd;
 +        Timespec r_t;
-         int offset = gnc_mktime(&tm) - gnc_timegm(&tm);
 +        g_date_clear(&gd, 1);
 +        g_date_set_dmy(&gd, f->test[i].day, f->test[i].mon, f->test[i].yr);
 +        r_t = gdate_to_timespec(gd);
-         g_assert_cmpint (r_t.tv_sec, ==, f->test[i].secs + offset);
++        g_assert_cmpint (r_t.tv_sec, ==, f->test[i].secs);
 +    }
  }
  /* gnc_tm_get_day_start
  static void
@@@ -2154,22 -2448,23 +2168,24 @@@ test_suite_gnc_date (void
  // GNC_TEST_ADD_FUNC (suitename, "qof format time", test_qof_format_time);
  // GNC_TEST_ADD_FUNC (suitename, "qof strftime", test_qof_strftime);
      GNC_TEST_ADD_FUNC (suitename, "gnc_date_timestamp", test_gnc_date_timestamp);
 -    GNC_TEST_ADD_FUNC (suitename, "gnc iso8601 to timespec gmt", test_gnc_iso8601_to_timespec_gmt);
 -    GNC_TEST_ADD_FUNC (suitename, "gnc timespec to iso8601 buff", test_gnc_timespec_to_iso8601_buff);
 -    GNC_TEST_ADD_FUNC (suitename, "gnc timespec2dmy", test_gnc_timespec2dmy);
 +    GNC_TEST_ADD (suitename, "gnc iso8601 to timespec gmt", FixtureA, NULL, setup, test_gnc_iso8601_to_timespec_gmt, NULL);
 +    GNC_TEST_ADD (suitename, "gnc timespec to iso8601 buff", FixtureA, NULL, setup, test_gnc_timespec_to_iso8601_buff, NULL);
 +    GNC_TEST_ADD (suitename, "gnc timespec2dmy", FixtureA, NULL, setup, test_gnc_timespec2dmy, NULL);
  // GNC_TEST_ADD_FUNC (suitename, "gnc dmy2timespec internal", test_gnc_dmy2timespec_internal);
 -    GNC_TEST_ADD_FUNC (suitename, "gnc dmy2timespec", test_gnc_dmy2timespec);
 -    GNC_TEST_ADD_FUNC (suitename, "gnc dmy2timespec end", test_gnc_dmy2timespec_end);
 -    GNC_TEST_ADD_FUNC (suitename, "gnc dmy2timespec Neutral", test_gnc_dmy2timespec_neutral);
++
 +    GNC_TEST_ADD (suitename, "gnc dmy2timespec", FixtureB, NULL, setup_begin, test_gnc_dmy2timespec, NULL);
 +    GNC_TEST_ADD (suitename, "gnc dmy2timespec end", FixtureB, NULL, setup_end, test_gnc_dmy2timespec_end, NULL);
++    GNC_TEST_ADD (suitename, "gnc dmy2timespec Neutral", FixtureB, NULL, setup_neutral, test_gnc_dmy2timespec_neutral, NULL);
  // GNC_TEST_ADD_FUNC (suitename, "gnc timezone", test_gnc_timezone);
  // GNC_TEST_ADD_FUNC (suitename, "timespecFromTime t", test_timespecFromtime64);
  // GNC_TEST_ADD_FUNC (suitename, "timespec now", test_timespec_now);
  // GNC_TEST_ADD_FUNC (suitename, "timespecToTime t", test_timespecTotime64);
 -    GNC_TEST_ADD_FUNC (suitename, "timespec to gdate", test_timespec_to_gdate);
 -    GNC_TEST_ADD_FUNC (suitename, "gdate to timespec", test_gdate_to_timespec);
 +    GNC_TEST_ADD (suitename, "timespec to gdate", FixtureA, NULL, setup, test_timespec_to_gdate, NULL);
-     GNC_TEST_ADD (suitename, "gdate to timespec", FixtureB, NULL, setup_begin, test_gdate_to_timespec, NULL);
++    GNC_TEST_ADD (suitename, "gdate to timespec", FixtureB, NULL, setup_neutral, test_gdate_to_timespec, NULL);
  // GNC_TEST_ADD_FUNC (suitename, "gnc tm get day start", test_gnc_tm_get_day_start);
  // GNC_TEST_ADD_FUNC (suitename, "gnc tm get day end", test_gnc_tm_get_day_end);
 -    GNC_TEST_ADD_FUNC (suitename, "gnc time64 get day start", test_gnc_time64_get_day_start);
 -    GNC_TEST_ADD_FUNC (suitename, "gnc time64 get day end", test_gnc_time64_get_day_end);
 +    GNC_TEST_ADD (suitename, "gnc time64 get day start", FixtureA, NULL, setup, test_gnc_time64_get_day_start, NULL);
 +    GNC_TEST_ADD (suitename, "gnc time64 get day end", FixtureA, NULL, setup, test_gnc_time64_get_day_end, NULL);
  // GNC_TEST_ADD_FUNC (suitename, "gnc tm get today start", test_gnc_tm_get_today_start);
  // GNC_TEST_ADD_FUNC (suitename, "gnc timet get today start", test_gnc_time64_get_today_start);
  // GNC_TEST_ADD_FUNC (suitename, "gnc timet get today end", test_gnc_time64_get_today_end);

commit 51e29e7836af814868f51161cb3263465a5e951f
Author: John Ralls <jralls at ceridwen.us>
Date:   Sat Jul 2 16:03:55 2016 -0700

    Bug 137017 - date of transaction change with time zone change
    
    First step: Change the timestamp to 11:00 UTC instead of midnight local,
    adjusting by an hour or two if the local timezone is one near the
    International Date Line to keep the date from flipping around.
    
    Scrub all old entries to make current files correct.
    
    Note: This effectively disposes of the distinction of close-book transactions
    having a noon instead of midnight timestamp as a way to distinguish them
    from regular transactions, but that distinction doesn't seem to be used;
    xaccTransIsCloseBook() is used instead.

diff --git a/src/backend/sql/gnc-transaction-sql.c b/src/backend/sql/gnc-transaction-sql.c
index 0ea2189..b228c61 100644
--- a/src/backend/sql/gnc-transaction-sql.c
+++ b/src/backend/sql/gnc-transaction-sql.c
@@ -36,6 +36,7 @@
 
 #include "Account.h"
 #include "Transaction.h"
+#include <Scrub.h>
 #include "gnc-lot.h"
 #include "engine-helpers.h"
 
@@ -376,6 +377,7 @@ query_transactions( GncSqlBackend* be, GncSqlStatement* stmt )
             if ( tx != NULL )
             {
                 tx_list = g_list_prepend( tx_list, tx );
+                xaccTransScrubPostedDate (tx);
             }
             row = gnc_sql_result_get_next_row( result );
         }
diff --git a/src/backend/xml/io-gncxml-v2.c b/src/backend/xml/io-gncxml-v2.c
index 051c94c..0531984 100644
--- a/src/backend/xml/io-gncxml-v2.c
+++ b/src/backend/xml/io-gncxml-v2.c
@@ -278,6 +278,7 @@ add_transaction_local(sixtp_gdv2 *data, Transaction *trn)
                                    xaccTransSetCurrency);
 
     xaccTransScrubCurrency (trn);
+    xaccTransScrubPostedDate (trn);
     xaccTransCommitEdit (trn);
 
     data->counter.transactions_loaded++;
diff --git a/src/engine/Scrub.c b/src/engine/Scrub.c
index dc22b20..4146f9b 100644
--- a/src/engine/Scrub.c
+++ b/src/engine/Scrub.c
@@ -1373,4 +1373,17 @@ xaccScrubUtilityGetOrMakeAccount (Account *root, gnc_commodity * currency,
     return acc;
 }
 
+void
+xaccTransScrubPostedDate (Transaction *trans)
+{
+    time64 orig = xaccTransGetDate(trans);
+    GDate date = xaccTransGetDatePostedGDate(trans);
+    Timespec ts = gdate_to_timespec(date);
+    if (orig && orig != ts.tv_sec)
+    {
+        /* xaccTransSetDatePostedTS handles committing the change. */
+        xaccTransSetDatePostedTS(trans, &ts);
+    }
+}
+
 /* ==================== END OF FILE ==================== */
diff --git a/src/engine/Scrub.h b/src/engine/Scrub.h
index 82d5e2d..efdad1b 100644
--- a/src/engine/Scrub.h
+++ b/src/engine/Scrub.h
@@ -142,8 +142,18 @@ void xaccAccountTreeScrubCommodities (Account *acc);
  */
 void xaccAccountTreeScrubQuoteSources (Account *root, gnc_commodity_table *table);
 
+/** Removes empty "notes", "placeholder", and "hbci" KVP slots from Accounts. */
 void xaccAccountScrubKvp (Account *account);
 
+/** Changes Transaction date_posted timestamps from 00:00 local to 11:00 UTC.
+ * 11:00 UTC is the same day local time in almost all timezones, the exceptions
+ * being the -12, +13, and +14 timezones along the International Date Line. If
+ * Local time is set to one of these timezones then the new date_posted time
+ * will be adjusted as needed to ensure that the date doesn't change there. This
+ * change was made for v2.6.14 to partially resolve bug 137017.
+ */
+void xaccTransScrubPostedDate (Transaction *trans);
+
 #endif /* XACC_SCRUB_H */
 /** @} */
 /** @} */
diff --git a/src/libqof/qof/gnc-date.c b/src/libqof/qof/gnc-date.c
index 50cd248..3da2eca 100644
--- a/src/libqof/qof/gnc-date.c
+++ b/src/libqof/qof/gnc-date.c
@@ -183,6 +183,7 @@ gnc_g_date_time_to_local (GDateTime* gdt)
 typedef struct
 {
     GDateTime *(*new_local)(gint, gint, gint, gint, gint, gdouble);
+    GDateTime *(*new_utc)(gint, gint, gint, gint, gint, gdouble);
     GDateTime *(*adjust_for_dst)(GDateTime *, GTimeZone *);
     GDateTime *(*new_from_unix_local)(time64);
     GDateTime *(*new_from_timeval_local)(const GTimeVal *);
@@ -195,6 +196,7 @@ void
 _gnc_date_time_init (_GncDateTime *gncdt)
 {
     gncdt->new_local = gnc_g_date_time_new_local;
+    gncdt->new_utc = g_date_time_new_utc;
     gncdt->adjust_for_dst = gnc_g_date_time_adjust_for_dst;
     gncdt->new_from_unix_local = gnc_g_date_time_new_from_unix_local;
     gncdt->new_from_timeval_local = gnc_g_date_time_new_from_timeval_local;
@@ -372,12 +374,13 @@ gnc_timegm (struct tm* time)
      gdt = g_date_time_new_utc (time->tm_year + 1900, time->tm_mon,
 				time->tm_mday, time->tm_hour, time->tm_min,
 				(gdouble)(time->tm_sec));
-     time->tm_mon = time->tm_mon > 0 ? time->tm_mon - 1 : 11;
-     // Watch out: struct tm has wday=0..6 with Sunday=0, but GDateTime has wday=1..7 with Sunday=7.
-     time->tm_wday = g_date_time_get_day_of_week (gdt) % 7;
-     time->tm_yday = g_date_time_get_day_of_year (gdt);
-     time->tm_isdst = g_date_time_is_daylight_savings (gdt);
-
+     if (gdt == NULL)
+     {
+         PERR("Failed to get valid GDateTime with struct tm: %d-%d-%d %d:%d:%d",
+              time->tm_year + 1900, time->tm_mon, time->tm_mday, time->tm_hour,
+              time->tm_min, time->tm_sec);
+         return 0;
+     }
      secs = g_date_time_to_unix (gdt);
      g_date_time_unref (gdt);
      return secs;
@@ -1551,6 +1554,7 @@ gnc_dmy2timespec_internal (int day, int month, int year, gboolean start_of_day)
     return result;
 }
 
+
 Timespec
 gnc_dmy2timespec (int day, int month, int year)
 {
@@ -1563,6 +1567,29 @@ gnc_dmy2timespec_end (int day, int month, int year)
     return gnc_dmy2timespec_internal (day, month, year, FALSE);
 }
 
+Timespec
+gnc_dmy2timespec_neutral (int day, int month, int year)
+{
+    struct tm date;
+    Timespec ts = {0, 0};
+    GTimeZone *zone = gnc_g_time_zone_new_local();
+    GDateTime *gdt = gnc_g_date_time_new_local (year, month, day, 12, 0, 0.0);
+    int interval = g_time_zone_find_interval (zone, G_TIME_TYPE_STANDARD,
+                                              g_date_time_to_unix(gdt));
+    int offset = g_time_zone_get_offset(gnc_g_time_zone_new_local(),
+                                        interval) / 3600;
+    g_date_time_unref (gdt);
+    memset (&date, 0, sizeof(struct tm));
+    date.tm_year = year - 1900;
+    date.tm_mon = month - 1;
+    date.tm_mday = day;
+    date.tm_hour = offset < -11 ? -offset : offset > 13 ? 24 - offset : 11;
+    date.tm_min = 0;
+    date.tm_sec = 0;
+
+    ts.tv_sec = gnc_timegm(&date);
+    return ts;
+}
 /********************************************************************\
 \********************************************************************/
 
@@ -1648,9 +1675,9 @@ GDate* gnc_g_date_new_today ()
 Timespec gdate_to_timespec (GDate d)
 {
     gnc_gdate_range_check (&d);
-    return gnc_dmy2timespec(g_date_get_day(&d),
-                            g_date_get_month(&d),
-                            g_date_get_year(&d));
+    return gnc_dmy2timespec_neutral (g_date_get_day(&d),
+                                     g_date_get_month(&d),
+                                     g_date_get_year(&d));
 }
 
 static void
diff --git a/src/libqof/qof/gnc-date.h b/src/libqof/qof/gnc-date.h
index 56fed84..51fa353 100644
--- a/src/libqof/qof/gnc-date.h
+++ b/src/libqof/qof/gnc-date.h
@@ -351,6 +351,15 @@ Timespec gnc_dmy2timespec (gint day, gint month, gint year);
 /** Same as gnc_dmy2timespec, but last second of the day */
 Timespec gnc_dmy2timespec_end (gint day, gint month, gint year);
 
+/** Converts a day, month, and year to a Timespec representing 11:00:00 UTC
+ *  11:00:00 UTC falls on the same time in almost all timezones, the exceptions
+ *  being the +13, +14, and -12 timezones used by countries along the
+ *  International Date Line. Since users in those timezones would see dates
+ *  immediately change by one day, the function checks the current timezone for
+ *  those changes and adjusts the UTC time so that the date will be consistent.
+ */
+Timespec gnc_dmy2timespec_neutral (gint day, gint month, gint year);
+
 /** The gnc_iso8601_to_timespec_gmt() routine converts an ISO-8601 style
  *    date/time string to Timespec.  Please note that ISO-8601 strings
  *    are a representation of Universal Time (UTC), and as such, they
diff --git a/src/libqof/qof/test/test-gnc-date.c b/src/libqof/qof/test/test-gnc-date.c
index a610beb..4210177 100644
--- a/src/libqof/qof/test/test-gnc-date.c
+++ b/src/libqof/qof/test/test-gnc-date.c
@@ -47,6 +47,7 @@ void test_suite_gnc_date ( void );
 typedef struct
 {
     GDateTime *(*new_local)(gint, gint, gint, gint, gint, gdouble);
+    GDateTime *(*new_utc)(gint, gint, gint, gint, gint, gdouble);
     GDateTime *(*adjust_for_dst)(GDateTime *, GTimeZone *);
     GDateTime *(*new_from_unix_local)(time64);
     GDateTime *(*new_from_timeval_local)(GTimeVal *);
@@ -1967,6 +1968,48 @@ test_gnc_dmy2timespec_end (void)
     g_date_time_unref (gdt3);
     g_date_time_unref (gdt4);
 }
+
+/*gnc_dmy2timespec_neutral*/
+static void
+test_gnc_dmy2timespec_neutral (void)
+{
+    GDateTime *gdt1 = gncdt.new_utc (1999, 7, 21, 11, 0, 0);
+    GDateTime *gdt2 = gncdt.new_utc (1918, 3, 31, 11, 0, 0);
+    GDateTime *gdt3 = gncdt.new_utc (1918, 4, 1, 11, 0, 0);
+    GDateTime *gdt4 = gncdt.new_utc (2057, 11, 20, 11, 0, 0);
+
+    gint day, mon, yr;
+    Timespec t, r_t;
+
+    t = g_date_time_to_timespec (gdt1);
+    g_date_time_get_ymd (gdt1, &yr, &mon, &day);
+    r_t = gnc_dmy2timespec_neutral (day, mon, yr);
+    g_assert_cmpint (r_t.tv_sec, ==, t.tv_sec);
+    g_assert_cmpint (r_t.tv_nsec, ==, t.tv_nsec);
+
+    t = g_date_time_to_timespec (gdt2);
+    g_date_time_get_ymd (gdt2, &yr, &mon, &day);
+    r_t = gnc_dmy2timespec_neutral (day, mon, yr);
+    g_assert_cmpint (r_t.tv_sec, ==, t.tv_sec);
+    g_assert_cmpint (r_t.tv_nsec, ==, t.tv_nsec);
+
+    t = g_date_time_to_timespec (gdt3);
+    g_date_time_get_ymd (gdt3, &yr, &mon, &day);
+    r_t = gnc_dmy2timespec_neutral (day, mon, yr);
+    g_assert_cmpint (r_t.tv_sec, ==, t.tv_sec);
+    g_assert_cmpint (r_t.tv_nsec, ==, t.tv_nsec);
+
+    t = g_date_time_to_timespec (gdt4);
+    g_date_time_get_ymd (gdt4, &yr, &mon, &day);
+    r_t = gnc_dmy2timespec_neutral (day, mon, yr);
+    g_assert_cmpint (r_t.tv_sec, ==, t.tv_sec);
+    g_assert_cmpint (r_t.tv_nsec, ==, t.tv_nsec);
+
+    g_date_time_unref (gdt1);
+    g_date_time_unref (gdt2);
+    g_date_time_unref (gdt3);
+    g_date_time_unref (gdt4);
+}
 /* gnc_timezone
 long int
 gnc_timezone (const struct tm *tm)// C: 5 in 2  Local: 2:0:0
@@ -2086,10 +2129,10 @@ Timespec gdate_to_timespec (GDate d)// C: 7 in 6  Local: 0:0:0
 static void
 test_gdate_to_timespec (void)
 {
-    GDateTime *gdt1 = gncdt.new_local (1999, 7, 21, 0, 0, 0);
-    GDateTime *gdt2 = gncdt.new_local (1918, 3, 31, 0, 0, 0);
-    GDateTime *gdt3 = gncdt.new_local (1918, 4, 1, 0, 0, 0);
-    GDateTime *gdt4 = gncdt.new_local (2057, 11, 20, 0, 0, 0);
+    GDateTime *gdt1 = gncdt.new_utc (1999, 7, 21, 11, 0, 0);
+    GDateTime *gdt2 = gncdt.new_utc (1918, 3, 31, 11, 0, 0);
+    GDateTime *gdt3 = gncdt.new_utc (1918, 4, 1, 11, 0, 0);
+    GDateTime *gdt4 = gncdt.new_utc (2057, 11, 20, 11, 0, 0);
 
     gint day, mon, yr;
     Timespec t, r_t;
@@ -2411,6 +2454,7 @@ test_suite_gnc_date (void)
 // GNC_TEST_ADD_FUNC (suitename, "gnc dmy2timespec internal", test_gnc_dmy2timespec_internal);
     GNC_TEST_ADD_FUNC (suitename, "gnc dmy2timespec", test_gnc_dmy2timespec);
     GNC_TEST_ADD_FUNC (suitename, "gnc dmy2timespec end", test_gnc_dmy2timespec_end);
+    GNC_TEST_ADD_FUNC (suitename, "gnc dmy2timespec Neutral", test_gnc_dmy2timespec_neutral);
 // GNC_TEST_ADD_FUNC (suitename, "gnc timezone", test_gnc_timezone);
 // GNC_TEST_ADD_FUNC (suitename, "timespecFromTime t", test_timespecFromtime64);
 // GNC_TEST_ADD_FUNC (suitename, "timespec now", test_timespec_now);

commit 6a81738e969cd56556f3dbaf1ee08521ee86a855
Author: John Ralls <jralls at ceridwen.us>
Date:   Sat Jul 2 14:08:14 2016 -0700

    Don't override optimization flags in CMakeLists.txt.

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0cb86eb..913834e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -348,7 +348,7 @@ SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")  # FIXME: should be -std=
 
 
 IF (UNIX)
-  SET( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror -Wdeclaration-after-statement -Wno-pointer-sign -D_FORTIFY_SOURCE=2 -Wall -Wunused -Wmissing-prototypes -Wmissing-declarations -O2 -Wno-unused -std=gnu99")
+  SET( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror -Wdeclaration-after-statement -Wno-pointer-sign -D_FORTIFY_SOURCE=2 -Wall -Wunused -Wmissing-prototypes -Wmissing-declarations -Wno-unused -std=gnu99")
   SET( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-error=deprecated-declarations")
 ENDIF (UNIX)
 IF (MINGW)

commit 704bc8352dd2e9bf25e73fcecfd3710858cf609f
Author: Geert Janssens <janssens-geert at telenet.be>
Date:   Sat Jul 2 17:38:33 2016 +0200

    When default tax table is 'None', set a new invoice/bill entry as not taxabl by default as well.
    
    This slightly changes the semantics of a 'None' default tax table
    from 'taxable but no tax details given'
    to 'not taxable'.
    While not exactly the same the former is a highly unlikely
    situation, where the latter is more common and hence makes
    more sense. In the rare case a user does want to express
    the old semantics this is still possible by explicitly
    creating a tax table without any details.
    Note this only influences newly entered entries. It has
    no influence on existing ones.

diff --git a/src/business/business-ledger/gncEntryLedgerLoad.c b/src/business/business-ledger/gncEntryLedgerLoad.c
index e96eff7..ef099d1 100644
--- a/src/business/business-ledger/gncEntryLedgerLoad.c
+++ b/src/business/business-ledger/gncEntryLedgerLoad.c
@@ -440,12 +440,14 @@ void gnc_entry_ledger_load (GncEntryLedger *ledger, GList *entry_list)
 
                 if (ledger->is_cust_doc)
                 {
+                    gncEntrySetInvTaxable (blank_entry, table != NULL);
                     gncEntrySetInvTaxTable (blank_entry, table);
                     gncEntrySetInvTaxIncluded (blank_entry, taxincluded);
                     gncEntrySetInvDiscount (blank_entry, discount);
                 }
                 else
                 {
+                    gncEntrySetBillTaxable (blank_entry, table != NULL);
                     gncEntrySetBillTaxTable (blank_entry, table);
                     gncEntrySetBillTaxIncluded (blank_entry, taxincluded);
                 }



Summary of changes:
 CMakeLists.txt                                    |  2 +-
 src/backend/sql/gnc-transaction-sql.cpp           |  2 ++
 src/backend/xml/io-gncxml-v2.cpp                  |  1 +
 src/business/business-ledger/gncEntryLedgerLoad.c |  2 ++
 src/engine/Scrub.c                                | 13 ++++++++
 src/engine/Scrub.h                                | 10 +++++++
 src/engine/Transaction.c                          |  5 +---
 src/libqof/qof/gnc-date.cpp                       | 29 +++++++++++++++---
 src/libqof/qof/gnc-date.h                         |  9 ++++++
 src/libqof/qof/test/test-gnc-date.c               | 36 ++++++++++++++++-------
 10 files changed, 90 insertions(+), 19 deletions(-)



More information about the gnucash-changes mailing list