void gnc_tree_util_split_reg_parse_date (GDate *parsed, const char *datestr) { int day, month, year; gboolean use_autoreadonly = qof_book_uses_autoreadonly (gnc_get_current_book ()); if (!parsed) return; if (!datestr) return; if (!qof_scan_date (datestr, &day, &month, &year)) { // Couldn't parse date, use today struct tm tm_today; gnc_tm_get_today_start (&tm_today); day = tm_today.tm_mday; month = tm_today.tm_mon + 1; year = tm_today.tm_year + 1900; } // If we have an auto-read-only threshold, do not accept a date that is // older than the threshold. if (use_autoreadonly) { GDate *d = g_date_new_dmy (day, month, year); GDate *readonly_threshold = qof_book_get_autoreadonly_gdate (gnc_get_current_book()); if (g_date_compare (d, readonly_threshold) < 0) { g_warning("Entered date %s is before the \"auto-read-only threshold\"; resetting to the threshold.", datestr); #if 0 GtkWidget *dialog = gtk_message_dialog_new (NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", _("Cannot store a transaction at this date")); gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), "%s", _("The entered date of the new transaction is older than the \"Read-Only Threshold\" set for this book. " "This setting can be changed in File -> Properties -> Accounts.")); gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); #endif // Reset the date to the threshold date day = g_date_get_day (readonly_threshold); month = g_date_get_month (readonly_threshold); year = g_date_get_year (readonly_threshold); } g_date_free (d); g_date_free (readonly_threshold); } g_date_set_dmy (parsed, day, month, year); }
static void gnc_parse_date (struct tm *parsed, const char * datestr) { int day, month, year; gboolean use_autoreadonly = qof_book_uses_autoreadonly(gnc_get_current_book()); if (!parsed) return; if (!datestr) return; if (!qof_scan_date (datestr, &day, &month, &year)) { // Couldn't parse date, use today struct tm tm_today; memset (&tm_today, 0, sizeof (struct tm)); gnc_tm_get_today_start (&tm_today); day = tm_today.tm_mday; month = tm_today.tm_mon + 1; year = tm_today.tm_year + 1900; } // If we have an auto-read-only threshold, do not accept a date that is // older than the threshold. if (use_autoreadonly) { GDate *d = g_date_new_dmy(day, month, year); if (check_readonly_threshold (datestr, d)) { day = g_date_get_day (d); month = g_date_get_month (d); year = g_date_get_year (d); } g_date_free (d); } parsed->tm_mday = day; parsed->tm_mon = month - 1; parsed->tm_year = year - 1900; gnc_tm_set_day_start(parsed); /* Using gnc_mktime purely for its side effect of filling in the * rest of parsed and to check that it's valid. */ if (gnc_mktime (parsed) == -1) gnc_tm_get_today_start (parsed); gnc_mktime (parsed); }
/* This function converts a string date to a time64 value */ static time64 gcrd_string_dmy2time (const gchar *date_string) { gint year = 0, month = 0, day = 0; if(qof_scan_date (date_string, &day, &month, &year)) { struct tm when; memset (&when, 0, sizeof (when)); when.tm_year = year - 1900; when.tm_mon = month - 1 ; when.tm_mday = day; return gnc_mktime (&when); } else { return gnc_time (NULL); } }
static gboolean gtk_cell_editable_key_press_event (GtkEntry *entry, GdkEventKey *key_event, GncPopupEntry *widget) { const char *date_string; gint year = 0, month = 0, day = 0; struct tm when; if (key_event->keyval == GDK_KEY_Escape) { widget->editing_canceled = TRUE; gtk_cell_editable_editing_done (GTK_CELL_EDITABLE (widget)); gtk_cell_editable_remove_widget (GTK_CELL_EDITABLE (widget)); return TRUE; } date_string = gtk_entry_get_text (entry); memset (&when, 0, sizeof (when)); if (qof_scan_date (date_string, &day, &month, &year)) { when.tm_year = year - 1900; when.tm_mon = month - 1 ; when.tm_mday = day; if (!gnc_handle_date_accelerator (key_event, &when, date_string)) return FALSE; gtk_entry_set_text (entry, qof_print_date (gnc_mktime (&when))); gtk_widget_grab_focus (GTK_WIDGET (entry)); return TRUE; } return FALSE; }
/*********************************************************************** * @todo Maybe invoice checking should be done in gnc_bi_import_fix_bis (...) * rather than in here? But that is more concerned with ensuring the csv is consistent. * @param GtkListStore *store * @param guint *n_invoices_created * @param guint *n_invoices_updated * @return void ***********************************************************************/ void gnc_bi_import_create_bis (GtkListStore * store, QofBook * book, guint * n_invoices_created, guint * n_invoices_updated, gchar * type, gchar * open_mode, GString * info) { gboolean valid; GtkTreeIter iter; gchar *id, *date_opened, *owner_id, *billing_id, *notes; gchar *date, *desc, *action, *account, *quantity, *price, *disc_type, *disc_how, *discount, *taxable, *taxincluded, *tax_table; gchar *date_posted, *due_date, *account_posted, *memo_posted, *accumulatesplits; guint dummy; GncInvoice *invoice; GncEntry *entry; gint day, month, year; gnc_numeric value; GncOwner *owner; Account *acc; enum update {YES = GTK_RESPONSE_YES, NO = GTK_RESPONSE_NO} update; GtkWidget *dialog; Timespec today; InvoiceWindow *iw; gchar *new_id = NULL; gint64 denom = 0; gnc_commodity *currency; // these arguments are needed g_return_if_fail (store && book); // logic of this function only works for bills or invoices g_return_if_fail ((g_ascii_strcasecmp (type, "INVOICE") == 0) || (g_ascii_strcasecmp (type, "BILL") == 0)); // allow to call this function without statistics if (!n_invoices_created) n_invoices_created = &dummy; if (!n_invoices_updated) n_invoices_updated = &dummy; *n_invoices_created = 0; *n_invoices_updated = 0; invoice = NULL; update = NO; valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter); while (valid) { // Walk through the list, reading each row gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, ID, &id, DATE_OPENED, &date_opened, DATE_POSTED, &date_posted, // if autoposting requested DUE_DATE, &due_date, // if autoposting requested ACCOUNT_POSTED, &account_posted, // if autoposting requested MEMO_POSTED, &memo_posted, // if autoposting requested ACCU_SPLITS, &accumulatesplits, // if autoposting requested OWNER_ID, &owner_id, BILLING_ID, &billing_id, NOTES, ¬es, DATE, &date, DESC, &desc, ACTION, &action, ACCOUNT, &account, QUANTITY, &quantity, PRICE, &price, DISC_TYPE, &disc_type, DISC_HOW, &disc_how, DISCOUNT, &discount, TAXABLE, &taxable, TAXINCLUDED, &taxincluded, TAX_TABLE, &tax_table, -1); // TODO: Assign a new invoice number if one is absent. BUT we don't want to assign a new invoice for every line!! // so we'd have to flag this up somehow or add an option in the import GUI. The former implies that we make // an assumption about what the importer (person) wants to do. It seems reasonable that a CSV file full of items with // If an invoice exists then we add to it in this current schema. // no predefined invoice number is a new invoice that's in need of a new number. // This was not designed to satisfy the need for repeat invoices however, so maybe we need a another method for this, after all // It should be easier to copy an invoice with a new ID than to go through all this malarky. if (g_ascii_strcasecmp (type, "BILL") == 0) invoice = gnc_search_bill_on_id (book, id); else if (g_ascii_strcasecmp (type, "INVOICE") == 0) invoice = gnc_search_invoice_on_id (book, id); DEBUG( "Existing %s ID: %s\n", type, gncInvoiceGetID(invoice)); // If the search is empty then there is no existing invoice so make a new one if (invoice == NULL) { DEBUG( "Creating a new : %s\n", type ); // new invoice invoice = gncInvoiceCreate (book); /* Protect against thrashing the DB and trying to write the invoice * record prematurely */ gncInvoiceBeginEdit (invoice); gncInvoiceSetID (invoice, id); owner = gncOwnerNew (); if (g_ascii_strcasecmp (type, "BILL") == 0) gncOwnerInitVendor (owner, gnc_search_vendor_on_id (book, owner_id)); else if (g_ascii_strcasecmp (type, "INVOICE") == 0) gncOwnerInitCustomer (owner, gnc_search_customer_on_id (book, owner_id)); gncInvoiceSetOwner (invoice, owner); gncInvoiceSetCurrency (invoice, gncOwnerGetCurrency (owner)); // Set the invoice currency based on the owner if (strlen (date_opened) != 0) // If a date is specified in CSV { // FIXME: Must check for the return value of qof_scan_date! qof_scan_date (date_opened, &day, &month, &year); gncInvoiceSetDateOpened (invoice, gnc_dmy2timespec (day, month, year)); } else // If no date in CSV { time64 now = gnc_time (NULL); Timespec now_timespec; timespecFromTime64 (&now_timespec, now); gncInvoiceSetDateOpened (invoice, now_timespec); } gncInvoiceSetBillingID (invoice, billing_id ? billing_id : ""); gncInvoiceSetNotes (invoice, notes ? notes : ""); gncInvoiceSetActive (invoice, TRUE); //if (g_ascii_strcasecmp(type,"INVOICE"))gncInvoiceSetBillTo( invoice, billto ); (*n_invoices_created)++; update = YES; // open new bill / invoice in a tab, if requested if (g_ascii_strcasecmp(open_mode, "ALL") == 0 || (g_ascii_strcasecmp(open_mode, "NOT_POSTED") == 0 && strlen(date_posted) == 0)) { iw = gnc_ui_invoice_edit (invoice); gnc_plugin_page_invoice_new (iw); } gncInvoiceCommitEdit (invoice); } // I want to warn the user that an existing billvoice exists, but not every // time. // An import can contain many lines usually referring to the same invoice. // NB: Posted invoices are NEVER updated. else // if invoice exists { if (gncInvoiceIsPosted (invoice)) // Is it already posted? { valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter); continue; // If already posted then never import } if (update != YES) // Pop up a dialog to ask if updates are the expected action { dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_YES_NO, "%s", _("Are you sure you have bills/invoices to update?")); update = gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); if (update == NO) { // Cleanup and leave g_free (id); g_free (date_opened); g_free (owner_id); g_free (billing_id); g_free (notes); g_free (date); g_free (desc); g_free (action); g_free (account); g_free (quantity); g_free (price); g_free (disc_type); g_free (disc_how); g_free (discount); g_free (taxable); g_free (taxincluded); g_free (tax_table); g_free (date_posted); g_free (due_date); g_free (account_posted); g_free (memo_posted); g_free (accumulatesplits); return; } } (*n_invoices_updated)++; } // add entry to invoice/bill entry = gncEntryCreate (book); gncEntryBeginEdit(entry); currency = gncInvoiceGetCurrency(invoice); if (currency) denom = gnc_commodity_get_fraction(currency); // FIXME: Must check for the return value of qof_scan_date! qof_scan_date (date, &day, &month, &year); { GDate *date = g_date_new_dmy(day, month, year); gncEntrySetDateGDate (entry, date); g_date_free (date); } timespecFromTime64 (&today, gnc_time (NULL)); // set today to the current date gncEntrySetDateEntered (entry, today); gncEntrySetDescription (entry, desc); gncEntrySetAction (entry, action); value = gnc_numeric_zero(); gnc_exp_parser_parse (quantity, &value, NULL); gncEntrySetQuantity (entry, value); acc = gnc_account_lookup_for_register (gnc_get_current_root_account (), account); if (g_ascii_strcasecmp (type, "BILL") == 0) { gncEntrySetBillAccount (entry, acc); value = gnc_numeric_zero(); gnc_exp_parser_parse (price, &value, NULL); gncEntrySetBillPrice (entry, value); gncEntrySetBillTaxable (entry, text2bool (taxable)); gncEntrySetBillTaxIncluded (entry, text2bool (taxincluded)); gncEntrySetBillTaxTable (entry, gncTaxTableLookupByName (book, tax_table)); gncEntryCommitEdit(entry); gncBillAddEntry (invoice, entry); } else if (g_ascii_strcasecmp (type, "INVOICE") == 0) { gncEntrySetNotes (entry, notes); gncEntrySetInvAccount (entry, acc); value = gnc_numeric_zero(); gnc_exp_parser_parse (price, &value, NULL); gncEntrySetInvPrice (entry, value); gncEntrySetInvTaxable (entry, text2bool (taxable)); gncEntrySetInvTaxIncluded (entry, text2bool (taxincluded)); gncEntrySetInvTaxTable (entry, gncTaxTableLookupByName (book, tax_table)); value = gnc_numeric_zero(); gnc_exp_parser_parse (discount, &value, NULL); gncEntrySetInvDiscount (entry, value); gncEntrySetInvDiscountType (entry, text2disc_type (disc_type)); gncEntrySetInvDiscountHow (entry, text2disc_how (disc_how)); gncEntryCommitEdit(entry); gncInvoiceAddEntry (invoice, entry); } valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter); // handle auto posting of invoices if (valid) gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, ID, &new_id, -1); if (g_strcmp0 (id, new_id) != 0) { // the next invoice id is different => try to autopost this invoice if (qof_scan_date (date_posted, &day, &month, &year)) { // autopost this invoice gboolean auto_pay; Timespec d1, d2; if (g_ascii_strcasecmp (type, "INVOICE") == 0) auto_pay = gnc_prefs_get_bool (GNC_PREFS_GROUP_INVOICE, GNC_PREF_AUTO_PAY); else auto_pay = gnc_prefs_get_bool (GNC_PREFS_GROUP_BILL, GNC_PREF_AUTO_PAY); d1 = gnc_dmy2timespec (day, month, year); // FIXME: Must check for the return value of qof_scan_date! qof_scan_date (due_date, &day, &month, &year); // obtains the due date, or leaves it at date_posted d2 = gnc_dmy2timespec (day, month, year); acc = gnc_account_lookup_for_register (gnc_get_current_root_account (), account_posted); gncInvoicePostToAccount (invoice, acc, &d1, &d2, memo_posted, text2bool (accumulatesplits), auto_pay); } } } // cleanup g_free (new_id); g_free (id); g_free (date_opened); g_free (owner_id); g_free (billing_id); g_free (notes); g_free (date); g_free (desc); g_free (action); g_free (account); g_free (quantity); g_free (price); g_free (disc_type); g_free (disc_how); g_free (discount); g_free (taxable); g_free (taxincluded); g_free (tax_table); g_free (date_posted); g_free (due_date); g_free (account_posted); g_free (memo_posted); g_free (accumulatesplits); }
/* gnc_parse_time_date parsing tm (+0000) (from %+05d %d:%d:%d then the date) in datestr I hope that github will show differences between my new function gnc_parse_time_date and function qof_scan_date_internal found below, existing in HEAD (as of 25/1/2016) of the branch master of gnucash. Thanks to spaces added to qof_scan_date_internal. ********************************************************** KNOWN BUG: gnucash crashes if a date string starts by ":" and longer thant ":". ********************************************************** */ void gnc_parse_time_date (struct tm *parsed, const char * buff) /* gnc_parse_time_date should call GncDateTime gncdt(std::string) when gnc_print_date_time_buff will be using GncDateTime and TZenv will disappear from everywhere*/ { char *dupe, *tmp, *TZhourtxt, *TZtxt, *hourtxt, *minutetxt, *secondtxt; /* tmp may be the unparsed part of the string. , */ int iday, imonth, iyear; time64 secs; int tz; /* signed timezone in seconds */ hourtxt = NULL;/* will stay NULL if TZ is not in buff */ if (!parsed) return; parsed->tm_year=-1; /* means failure. */ if (!buff) return; dupe = g_strdup (buff); tmp = dupe; TZhourtxt = strtok (tmp, ":"); minutetxt = strtok (NULL, ":"); secs = time(NULL); /* gnc_time(NULL) or time(NULL) ?? */ gnc_localtime_r(&secs, parsed); tz = parsed->tm_gmtoff; if (TZhourtxt || minutetxt) { if (dupe[0] != ':' && minutetxt) {/* "hh:mm date" and "hh:mm:ss date" are parsed. "hh date" is not recognized. "hh:mm" and "hh:mm:ss" imply today. Default value for the timezone is TZenv */ secondtxt = strtok (NULL, " "); tmp = strtok (NULL, "\a");/* hack: \a is the audible bell, hope this one is never used in a date format ; tmp = minutetxt + strlen (strtok (minutetxt, " "))+1 might be better */ TZtxt = strtok (TZhourtxt, " "); hourtxt = strtok (NULL, " "); if (hourtxt) { tz = atoi(TZtxt); parsed->tm_hour = atoi(hourtxt) ; tz = 60 * ( (tz % 100) + 60 * (tz / 100) ) ; if (atoi(TZtxt) < 0) tz = - tz; } else { if (TZtxt) { parsed->tm_hour = atoi(TZtxt) ; } else { parsed->tm_hour = 0 ; /* the case "::" falls here ? */ } } if (tmp) { /* +0200 02:01:03 2016-06-03 * <+0200 02> TZhourtxt * <01> minutetxt * <03> secondtxt * <2016-06-03> tmp * <+0200> TZtxt * <02> hourtxt */ parsed->tm_min = atoi(minutetxt) ; parsed->tm_sec = atoi(secondtxt) ; } else { /* +0200 02:01 2016-06-03 * <+0200 02> TZhourtxt * <01 2016-06-03> minutetxt */ parsed->tm_min = atoi ( strtok (minutetxt, " ") ); parsed->tm_sec = 0 ; tmp = secondtxt ; /* +0200 02:01 2016-06-03 * <+0200 02> TZhourtxt * <2016-06-03> minutetxt * <2016-06-03> tmp */ } parsed->tm_sec = (parsed->tm_sec) - tz + (parsed->tm_gmtoff) ; } } else { parsed->tm_hour = k_reference_time_TZ_hour; /* parsed->tm_gmtoff = 0; does not work */ parsed->tm_min = k_reference_time_TZ_min; parsed->tm_sec = k_reference_time_TZ_sec_of_min + parsed->tm_gmtoff; tmp = TZhourtxt; } if (tmp && qof_scan_date (tmp, &iday, &imonth, &iyear)) { parsed->tm_mday = iday; parsed->tm_mon = imonth - 1; parsed->tm_year = iyear - 1900; } /* else { Couldn't parse date, use today ; } */ g_free (dupe); }
static void gnc_date_edit_popup (GNCDateEdit *gde) { GtkWidget *toplevel; struct tm mtm; gboolean date_was_valid; GdkDevice *device, *keyboard, *pointer; g_return_if_fail (GNC_IS_DATE_EDIT (gde)); ENTER("gde %p", gde); device = gtk_get_current_event_device (); /* This code is pretty much just copied from gtk_date_edit_get_date */ date_was_valid = qof_scan_date (gtk_entry_get_text (GTK_ENTRY (gde->date_entry)), &mtm.tm_mday, &mtm.tm_mon, &mtm.tm_year); if (!date_was_valid) { /* No valid date. Hacky workaround: Instead of crashing we randomly choose today's date. */ gnc_tm_get_today_start(&mtm); } mtm.tm_mon--; /* Hope the user does not actually mean years early in the A.D. days... * This date widget will obviously not work for a history program :-) */ if (mtm.tm_year >= 1900) mtm.tm_year -= 1900; gnc_tm_set_day_start(&mtm); /* Set the calendar. */ gtk_calendar_select_day (GTK_CALENDAR (gde->calendar), 1); gtk_calendar_select_month (GTK_CALENDAR (gde->calendar), mtm.tm_mon, 1900 + mtm.tm_year); gtk_calendar_select_day (GTK_CALENDAR (gde->calendar), mtm.tm_mday); /* Make sure we'll get notified of clicks outside the popup * window so we can properly pop down if that happens. */ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (gde)); if (GTK_IS_WINDOW (toplevel)) { gtk_window_group_add_window ( gtk_window_get_group (GTK_WINDOW (toplevel)), GTK_WINDOW (gde->cal_popup)); gtk_window_set_transient_for (GTK_WINDOW (gde->cal_popup), GTK_WINDOW (toplevel)); } position_popup (gde); gtk_widget_show (gde->cal_popup); gtk_widget_grab_focus (gde->cal_popup); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gde->date_button), TRUE); if (gdk_device_get_source (device) == GDK_SOURCE_KEYBOARD) { keyboard = device; pointer = gdk_device_get_associated_device (device); } else { pointer = device; keyboard = gdk_device_get_associated_device (device); } if (!gtk_widget_has_focus (gde->calendar)) gtk_widget_grab_focus (gde->calendar); if (!popup_grab_on_window (gtk_widget_get_window ((GTK_WIDGET(gde->cal_popup))), keyboard, pointer, GDK_CURRENT_TIME)) { gtk_widget_hide (gde->cal_popup); LEAVE("Failed to grab window"); return; } gtk_grab_add (gde->cal_popup); LEAVE(" "); }
static struct tm gnc_date_edit_get_date_internal (GNCDateEdit *gde) { struct tm tm = {0}; char *str; gchar *flags = NULL; gboolean date_was_valid; /* Assert, because we're just hosed if it's NULL */ g_assert(gde != NULL); g_assert(GNC_IS_DATE_EDIT(gde)); date_was_valid = qof_scan_date (gtk_entry_get_text (GTK_ENTRY (gde->date_entry)), &tm.tm_mday, &tm.tm_mon, &tm.tm_year); if (!date_was_valid) { /* Hm... no valid date. What should we do not? As a hacky workaround we revert to today's date. Alternatively we can return some value that signals that we don't get a valid date, but all callers of this function will have to check this. Alas, I'm too lazy to do this here. */ gnc_tm_get_today_start(&tm); } tm.tm_mon--; tm.tm_year -= 1900; if (gde->flags & GNC_DATE_EDIT_SHOW_TIME) { char *tokp = NULL; gchar *temp; str = g_strdup (gtk_entry_get_text (GTK_ENTRY (gde->time_entry))); temp = gnc_strtok_r (str, ": ", &tokp); if (temp) { tm.tm_hour = atoi (temp); temp = gnc_strtok_r (NULL, ": ", &tokp); if (temp) { if (isdigit (*temp)) { tm.tm_min = atoi (temp); flags = gnc_strtok_r (NULL, ": ", &tokp); if (flags && isdigit (*flags)) { tm.tm_sec = atoi (flags); flags = gnc_strtok_r (NULL, ": ", &tokp); } } else flags = temp; } } if (flags && (strcasecmp (flags, "PM") == 0)) { if (tm.tm_hour < 12) tm.tm_hour += 12; } g_free (str); } else { gnc_tm_set_day_start(&tm); } tm.tm_isdst = -1; return tm; }