handle date parsing errors, handle real=fake dates
authorDamyan Ivanov <dam+mobileledger@ktnx.net>
Sat, 12 Jan 2019 18:59:00 +0000 (18:59 +0000)
committerDamyan Ivanov <dam+mobileledger@ktnx.net>
Sat, 12 Jan 2019 18:59:00 +0000 (18:59 +0000)
app/src/main/java/net/ktnx/mobileledger/async/RetrieveTransactionsTask.java
app/src/main/java/net/ktnx/mobileledger/async/UpdateTransactionsTask.java
app/src/main/java/net/ktnx/mobileledger/model/LedgerTransaction.java
app/src/main/java/net/ktnx/mobileledger/ui/DatePickerFragment.java
app/src/main/java/net/ktnx/mobileledger/ui/activity/MainActivity.java
app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionActivity.java
app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListFragment.java
app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListViewModel.java
app/src/main/java/net/ktnx/mobileledger/utils/Globals.java
app/src/main/res/values/strings.xml

index 4da02e8..56a2662 100644 (file)
@@ -23,7 +23,6 @@ import android.os.AsyncTask;
 import android.os.OperationCanceledException;
 import android.util.Log;
 
-import net.ktnx.mobileledger.R;
 import net.ktnx.mobileledger.model.Data;
 import net.ktnx.mobileledger.model.LedgerAccount;
 import net.ktnx.mobileledger.model.LedgerTransaction;
@@ -43,6 +42,7 @@ import java.lang.ref.WeakReference;
 import java.net.HttpURLConnection;
 import java.net.MalformedURLException;
 import java.net.URLDecoder;
+import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashMap;
@@ -52,7 +52,7 @@ import java.util.regex.Pattern;
 
 
 public class RetrieveTransactionsTask
-        extends AsyncTask<Void, RetrieveTransactionsTask.Progress, Void> {
+        extends AsyncTask<Void, RetrieveTransactionsTask.Progress, String> {
     private static final int MATCHING_TRANSACTIONS_LIMIT = 50;
     private static final Pattern reComment = Pattern.compile("^\\s*;");
     private static final Pattern reTransactionStart = Pattern.compile("<tr class=\"title\" " +
@@ -65,7 +65,6 @@ public class RetrieveTransactionsTask
     private WeakReference<MainActivity> contextRef;
     private int error;
     // %3A is '='
-    private boolean success;
     private Pattern reAccountName = Pattern.compile("/register\\?q=inacct%3A([a-zA-Z0-9%]+)\"");
     private Pattern reAccountValue = Pattern.compile(
             "<span class=\"[^\"]*\\bamount\\b[^\"]*\">\\s*([-+]?[\\d.,]+)(?:\\s+(\\S+))?</span>");
@@ -90,26 +89,25 @@ public class RetrieveTransactionsTask
         context.onRetrieveStart();
     }
     @Override
-    protected void onPostExecute(Void aVoid) {
-        super.onPostExecute(aVoid);
+    protected void onPostExecute(String error) {
+        super.onPostExecute(error);
         MainActivity context = getContext();
         if (context == null) return;
-        context.onRetrieveDone(success);
+        context.onRetrieveDone(error);
     }
     @Override
     protected void onCancelled() {
         super.onCancelled();
         MainActivity context = getContext();
         if (context == null) return;
-        context.onRetrieveDone(false);
+        context.onRetrieveDone(null);
     }
     @SuppressLint("DefaultLocale")
     @Override
-    protected Void doInBackground(Void... params) {
+    protected String doInBackground(Void... params) {
         MobileLedgerProfile profile = Data.profile.get();
         Progress progress = new Progress();
         int maxTransactionId = Progress.INDETERMINATE;
-        success = false;
         ArrayList<LedgerAccount> accountList = new ArrayList<>();
         HashMap<String, Void> accountNames = new HashMap<>();
         LedgerAccount lastAccount = null;
@@ -248,7 +246,6 @@ public class RetrieveTransactionsTask
                                     m = reEnd.matcher(line);
                                     if (m.find()) {
                                         L("--- transaction value complete ---");
-                                        success = true;
                                         break LINES;
                                     }
                                     break;
@@ -262,13 +259,22 @@ public class RetrieveTransactionsTask
                                                     "Transaction Id is 0 while expecting " +
                                                     "description");
 
-                                        transaction =
-                                                new LedgerTransaction(transactionId, m.group(1),
-                                                        m.group(2));
+                                        String date = m.group(1);
+                                        try {
+                                            int equalsIndex = date.indexOf('=');
+                                            if (equalsIndex >= 0)
+                                                date = date.substring(equalsIndex + 1);
+                                            transaction = new LedgerTransaction(transactionId, date,
+                                                    m.group(2));
+                                        }
+                                        catch (ParseException e) {
+                                            e.printStackTrace();
+                                            return String.format("Error parsing date '%s'", date);
+                                        }
                                         state = ParserState.EXPECTING_TRANSACTION_DETAILS;
                                         L(String.format("transaction %d created for %s (%s) →" +
-                                                        " expecting details", transactionId,
-                                                m.group(1), m.group(2)));
+                                                        " expecting details", transactionId, date,
+                                                m.group(2)));
                                     }
                                     break;
 
@@ -291,7 +297,6 @@ public class RetrieveTransactionsTask
                                                         new Object[]{profile.getUuid(),
                                                                      transaction.getId()
                                                         });
-                                                success = true;
                                                 progress.setTotal(progress.getProgress());
                                                 publishProgress(progress);
                                                 break LINES;
@@ -354,6 +359,8 @@ public class RetrieveTransactionsTask
                         profile.setLongOption(MLDB.OPT_LAST_SCRAPE, now.getTime());
                         Data.lastUpdateDate.set(now);
                         TransactionListViewModel.scheduleTransactionListReload();
+
+                        return null;
                     }
                     finally {
                         db.endTransaction();
@@ -362,25 +369,24 @@ public class RetrieveTransactionsTask
             }
         }
         catch (MalformedURLException e) {
-            error = R.string.err_bad_backend_url;
             e.printStackTrace();
+            return "Invalid server URL";
         }
         catch (FileNotFoundException e) {
-            error = R.string.err_bad_auth;
             e.printStackTrace();
+            return "Invalid user name or password";
         }
         catch (IOException e) {
-            error = R.string.err_net_io_error;
             e.printStackTrace();
+            return "Network error";
         }
         catch (OperationCanceledException e) {
-            error = R.string.err_cancelled;
             e.printStackTrace();
+            return "Operation cancelled";
         }
         finally {
             Data.backgroundTaskCount.decrementAndGet();
         }
-        return null;
     }
     private MainActivity getContext() {
         return contextRef.get();
index f58d3e5..dc02fe9 100644 (file)
@@ -28,12 +28,12 @@ import net.ktnx.mobileledger.model.TransactionListItem;
 import net.ktnx.mobileledger.utils.Globals;
 import net.ktnx.mobileledger.utils.MLDB;
 
+import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.Date;
-import java.util.List;
 
-public class UpdateTransactionsTask extends AsyncTask<String, Void, List<TransactionListItem>> {
-    protected List<TransactionListItem> doInBackground(String[] filterAccName) {
+public class UpdateTransactionsTask extends AsyncTask<String, Void, String> {
+    protected String doInBackground(String[] filterAccName) {
         Data.backgroundTaskCount.incrementAndGet();
         String profile_uuid = Data.profile.get().getUuid();
         try {
@@ -88,7 +88,10 @@ public class UpdateTransactionsTask extends AsyncTask<String, Void, List<Transac
                 Log.d("UTT", "transaction list value updated");
             }
 
-            return newList;
+            return null;
+        }
+        catch (ParseException e) {
+            return String.format("Error parsing stored date '%s'", e.getMessage());
         }
         finally {
             Data.backgroundTaskCount.decrementAndGet();
index 7b63a59..03b9c7a 100644 (file)
@@ -26,6 +26,7 @@ import net.ktnx.mobileledger.utils.Globals;
 
 import java.nio.charset.Charset;
 import java.security.NoSuchAlgorithmException;
+import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.Date;
@@ -50,7 +51,8 @@ public class LedgerTransaction {
     private ArrayList<LedgerTransactionAccount> accounts;
     private String dataHash;
     private boolean dataLoaded;
-    public LedgerTransaction(Integer id, String dateString, String description) {
+    public LedgerTransaction(Integer id, String dateString, String description)
+            throws ParseException {
         this(id, Globals.parseLedgerDate(dateString), description);
     }
     public LedgerTransaction(Integer id, Date date, String description) {
@@ -139,7 +141,15 @@ public class LedgerTransaction {
         {
             if (cTr.moveToFirst()) {
                 String dateString = cTr.getString(0);
-                date = Globals.parseLedgerDate(dateString);
+                try {
+                    date = Globals.parseLedgerDate(dateString);
+                }
+                catch (ParseException e) {
+                    e.printStackTrace();
+                    throw new RuntimeException(
+                            String.format("Error parsing date '%s' from " + "transacion %d",
+                                    dateString, id));
+                }
                 description = cTr.getString(1);
 
                 try (Cursor cAcc = db.rawQuery("SELECT account_name, amount, currency FROM " +
index f801bda..d747f34 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2018 Damyan Ivanov.
+ * Copyright © 2019 Damyan Ivanov.
  * This file is part of Mobile-Ledger.
  * Mobile-Ledger is free software: you can distribute it and/or modify it
  * under the term of the GNU General Public License as published by
@@ -37,8 +37,10 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 public class DatePickerFragment extends AppCompatDialogFragment
-implements DatePickerDialog.OnDateSetListener, DatePicker.OnDateChangedListener
-{
+        implements DatePickerDialog.OnDateSetListener, DatePicker.OnDateChangedListener {
+    static final Pattern reYMD = Pattern.compile("^\\s*(\\d+)\\d*/\\s*(\\d+)\\s*/\\s*(\\d+)\\s*$");
+    static final Pattern reMD = Pattern.compile("^\\s*(\\d+)\\s*/\\s*(\\d+)\\s*$");
+    static final Pattern reD = Pattern.compile("\\s*(\\d+)\\s*$");
     @NonNull
     @Override
     public Dialog onCreateDialog(Bundle savedInstanceState) {
@@ -46,26 +48,33 @@ implements DatePickerDialog.OnDateSetListener, DatePicker.OnDateChangedListener
         int year = c.get(GregorianCalendar.YEAR);
         int month = c.get(GregorianCalendar.MONTH);
         int day = c.get(GregorianCalendar.DAY_OF_MONTH);
-        TextView date = Objects.requireNonNull(getActivity()).findViewById(R.id.new_transaction_date);
+        TextView date =
+                Objects.requireNonNull(getActivity()).findViewById(R.id.new_transaction_date);
 
         CharSequence present = date.getText();
 
-        Pattern re_mon_day = Pattern.compile("^\\s*(\\d+)\\s*/\\s*(\\d+)\\s*$");
-        Matcher m_mon_day = re_mon_day.matcher(present);
-
-        if (m_mon_day.matches()) {
-            month = Integer.parseInt(m_mon_day.group(1))-1;
-            day = Integer.parseInt(m_mon_day.group(2));
+        Matcher m = reYMD.matcher(present);
+        if (m.matches()) {
+            year = Integer.parseInt(m.group(1));
+            month = Integer.parseInt(m.group(2)) - 1;   // month is 0-based
+            day = Integer.parseInt(m.group(3));
         }
         else {
-            Pattern re_day = Pattern.compile("^\\s*(\\d{1,2})\\s*$");
-            Matcher m_day = re_day.matcher(present);
-            if (m_day.matches()) {
-                day = Integer.parseInt(m_day.group(1));
+            m = reMD.matcher(present);
+            if (m.matches()) {
+                month = Integer.parseInt(m.group(1)) - 1;
+                day = Integer.parseInt(m.group(2));
+            }
+            else {
+                m = reD.matcher(present);
+                if (m.matches()) {
+                    day = Integer.parseInt(m.group(1));
+                }
             }
         }
 
-        DatePickerDialog dpd =  new DatePickerDialog(Objects.requireNonNull(getActivity()), this, year, month, day);
+        DatePickerDialog dpd =
+                new DatePickerDialog(Objects.requireNonNull(getActivity()), this, year, month, day);
         // quicker date selection available in API 26
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
             DatePicker dp = dpd.getDatePicker();
@@ -77,15 +86,16 @@ implements DatePickerDialog.OnDateSetListener, DatePicker.OnDateChangedListener
 
     @TargetApi(Build.VERSION_CODES.O)
     public void onDateSet(DatePicker view, int year, int month, int day) {
-        TextView date = Objects.requireNonNull(getActivity()).findViewById(R.id.new_transaction_date);
+        TextView date =
+                Objects.requireNonNull(getActivity()).findViewById(R.id.new_transaction_date);
 
         final Calendar c = GregorianCalendar.getInstance();
-        if ( c.get(GregorianCalendar.YEAR) == year && c.get(GregorianCalendar.MONTH) == month) {
-            date.setText(String.format(Locale.US, "%d", day));
-        }
-        else {
-            date.setText(String.format(Locale.US, "%d/%d", month+1, day));
+        if (c.get(GregorianCalendar.YEAR) == year) {
+            if (c.get(GregorianCalendar.MONTH) == month)
+                date.setText(String.format(Locale.US, "%d", day));
+            else date.setText(String.format(Locale.US, "%d/%d", month + 1, day));
         }
+        else date.setText(String.format(Locale.US, "%d/%d/%d", year, month + 1, day));
 
         TextView description = Objects.requireNonNull(getActivity())
                 .findViewById(R.id.new_transaction_description);
@@ -94,15 +104,19 @@ implements DatePickerDialog.OnDateSetListener, DatePicker.OnDateChangedListener
 
     @Override
     public void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
-        TextView date = Objects.requireNonNull(getActivity()).findViewById(R.id.new_transaction_date);
+        TextView date =
+                Objects.requireNonNull(getActivity()).findViewById(R.id.new_transaction_date);
 
         final Calendar c = GregorianCalendar.getInstance();
-        if ( c.get(GregorianCalendar.YEAR) == year && c.get(GregorianCalendar.MONTH) == monthOfYear) {
-            date.setText(String.format(Locale.US, "%d", dayOfMonth));
-        }
-        else {
-            date.setText(String.format(Locale.US, "%d/%d", monthOfYear+1, dayOfMonth));
+        if (c.get(GregorianCalendar.YEAR) == year) {
+            if (c.get(GregorianCalendar.MONTH) == monthOfYear) {
+                date.setText(String.format(Locale.US, "%d", dayOfMonth));
+            }
+            else {
+                date.setText(String.format(Locale.US, "%d/%d", monthOfYear + 1, dayOfMonth));
+            }
         }
+        else date.setText(String.format(Locale.US, "%d/%d/%d", year, monthOfYear + 1, dayOfMonth));
 
         TextView description = Objects.requireNonNull(getActivity())
                 .findViewById(R.id.new_transaction_description);
index e079399..37e6fd2 100644 (file)
@@ -37,6 +37,7 @@ import android.view.View;
 import android.widget.LinearLayout;
 import android.widget.ProgressBar;
 import android.widget.TextView;
+import android.widget.Toast;
 
 import net.ktnx.mobileledger.R;
 import net.ktnx.mobileledger.async.RefreshDescriptionsTask;
@@ -333,11 +334,15 @@ public class MainActivity extends AppCompatActivity {
         if (retrieveTransactionsTask != null) retrieveTransactionsTask.cancel(false);
         bTransactionListCancelDownload.setEnabled(false);
     }
-    public void onRetrieveDone(boolean success) {
+    public void onRetrieveDone(String error) {
         progressLayout.setVisibility(View.GONE);
-        updateLastUpdateTextFromDB();
 
-        new RefreshDescriptionsTask().execute();
+        if (error == null) {
+            updateLastUpdateTextFromDB();
+
+            new RefreshDescriptionsTask().execute();
+        }
+        else Toast.makeText(this, error, Toast.LENGTH_LONG).show();
     }
     public void onRetrieveStart() {
         progressBar.setIndeterminate(true);
index a08f88c..13fc062 100644 (file)
@@ -41,6 +41,7 @@ import android.widget.ProgressBar;
 import android.widget.TableLayout;
 import android.widget.TableRow;
 import android.widget.TextView;
+import android.widget.Toast;
 
 import net.ktnx.mobileledger.R;
 import net.ktnx.mobileledger.async.SaveTransactionTask;
@@ -52,6 +53,7 @@ import net.ktnx.mobileledger.ui.OnSwipeTouchListener;
 import net.ktnx.mobileledger.utils.Globals;
 import net.ktnx.mobileledger.utils.MLDB;
 
+import java.text.ParseException;
 import java.util.Date;
 import java.util.Objects;
 
@@ -133,27 +135,46 @@ public class NewTransactionActivity extends AppCompatActivity implements TaskCal
         if (mSave != null) mSave.setVisible(false);
         toggleAllEditing(false);
         progress.setVisibility(View.VISIBLE);
+        try {
 
-        saver = new SaveTransactionTask(this);
+            saver = new SaveTransactionTask(this);
 
-        String dateString = tvDate.getText().toString();
-        Date date;
-        if (dateString.isEmpty()) date = new Date();
-        else date = Globals.parseLedgerDate(dateString);
-        LedgerTransaction tr = new LedgerTransaction(date, tvDescription.getText().toString());
+            String dateString = tvDate.getText().toString();
+            Date date;
+            if (dateString.isEmpty()) date = new Date();
+            else date = Globals.parseLedgerDate(dateString);
+            LedgerTransaction tr = new LedgerTransaction(date, tvDescription.getText().toString());
 
-        TableLayout table = findViewById(R.id.new_transaction_accounts_table);
-        for (int i = 0; i < table.getChildCount(); i++) {
-            TableRow row = (TableRow) table.getChildAt(i);
-            String acc = ((TextView) row.getChildAt(0)).getText().toString();
-            String amt = ((TextView) row.getChildAt(1)).getText().toString();
-            LedgerTransactionAccount item =
-                    amt.length() > 0 ? new LedgerTransactionAccount(acc, Float.parseFloat(amt))
-                                     : new LedgerTransactionAccount(acc);
+            TableLayout table = findViewById(R.id.new_transaction_accounts_table);
+            for (int i = 0; i < table.getChildCount(); i++) {
+                TableRow row = (TableRow) table.getChildAt(i);
+                String acc = ((TextView) row.getChildAt(0)).getText().toString();
+                String amt = ((TextView) row.getChildAt(1)).getText().toString();
+                LedgerTransactionAccount item =
+                        amt.length() > 0 ? new LedgerTransactionAccount(acc, Float.parseFloat(amt))
+                                         : new LedgerTransactionAccount(acc);
+
+                tr.addAccount(item);
+            }
+            saver.execute(tr);
+        }
+        catch (ParseException e) {
+            Log.d("new-transaction", "Parse error", e);
+            Toast.makeText(this, getResources().getString(R.string.error_invalid_date),
+                    Toast.LENGTH_LONG).show();
+            tvDate.requestFocus();
+
+            progress.setVisibility(View.GONE);
+            toggleAllEditing(true);
+            if (mSave != null) mSave.setVisible(true);
+        }
+        catch (Exception e) {
+            Log.d("new-transaction", "Unknown error", e);
 
-            tr.addAccount(item);
+            progress.setVisibility(View.GONE);
+            toggleAllEditing(true);
+            if (mSave != null) mSave.setVisible(true);
         }
-        saver.execute(tr);
     }
     private void toggleAllEditing(boolean enabled) {
         tvDate.setEnabled(enabled);
index f748ff1..f77580b 100644 (file)
@@ -35,6 +35,7 @@ import android.view.View;
 import android.view.ViewGroup;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.AutoCompleteTextView;
+import android.widget.Toast;
 
 import net.ktnx.mobileledger.R;
 import net.ktnx.mobileledger.model.Data;
@@ -183,7 +184,16 @@ public class TransactionListFragment extends MobileLedgerListFragment {
         TransactionListViewModel.scheduleTransactionListReload();
         TransactionListViewModel.updating.addObserver(
                 (o, arg) -> swiper.setRefreshing(TransactionListViewModel.updating.get()));
+        TransactionListViewModel.updateError.addObserver(new Observer() {
+            @Override
+            public void update(Observable o, Object arg) {
+                String err = TransactionListViewModel.updateError.get();
+                if (err == null) return;
 
+                Toast.makeText(mActivity, err, Toast.LENGTH_SHORT).show();
+                TransactionListViewModel.updateError.set(null);
+            }
+        });
         Data.transactions.addObserver(
                 (o, arg) -> mActivity.runOnUiThread(() -> modelAdapter.notifyDataSetChanged()));
 
index 53f48d3..0cf97bc 100644 (file)
@@ -29,10 +29,11 @@ import java.util.List;
 
 public class TransactionListViewModel extends ViewModel {
     public static ObservableValue<Boolean> updating = new ObservableValue<>();
+    public static ObservableValue<String> updateError = new ObservableValue<>();
 
     public static void scheduleTransactionListReload() {
         String filter = TransactionListFragment.accountFilter.get();
-        AsyncTask<String, Void, List<TransactionListItem>> task = new UTT();
+        AsyncTask<String, Void, String> task = new UTT();
         task.execute(filter);
     }
     public static TransactionListItem getTransactionListItem(int position) {
@@ -47,9 +48,9 @@ public class TransactionListViewModel extends ViewModel {
     }
     private static class UTT extends UpdateTransactionsTask {
         @Override
-        protected void onPostExecute(List<TransactionListItem> list) {
-            super.onPostExecute(list);
-            if (list != null) Data.transactions.set(list);
+        protected void onPostExecute(String error) {
+            super.onPostExecute(error);
+            if (error != null) updateError.set(error);
         }
     }
 }
index cd3f874..87730ca 100644 (file)
@@ -20,13 +20,17 @@ package net.ktnx.mobileledger.utils;
 import android.app.Activity;
 import android.content.Context;
 import android.support.annotation.ColorInt;
+import android.util.Log;
 import android.view.View;
 import android.view.inputmethod.InputMethodManager;
 
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
+import java.util.Calendar;
 import java.util.Date;
 import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 public final class Globals {
     @ColorInt
@@ -38,6 +42,8 @@ public final class Globals {
     public static String[] monthNames;
     private static SimpleDateFormat ledgerDateFormatter =
             new SimpleDateFormat("yyyy/MM/dd", Locale.US);
+    private static Pattern reLedgerDate =
+            Pattern.compile("^(?:(\\d+)/)??(?:(\\d\\d?)/)?(\\d\\d?)$");
     public static void hideSoftKeyboard(Activity act) {
         // hide the keyboard
         View v = act.getCurrentFocus();
@@ -47,13 +53,28 @@ public final class Globals {
             imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
         }
     }
-    public static Date parseLedgerDate(String dateString) {
-        try {
-            return ledgerDateFormatter.parse(dateString);
-        }
-        catch (ParseException e) {
-            throw new RuntimeException(String.format("Error parsing date '%s'", dateString), e);
+    public static Date parseLedgerDate(String dateString) throws ParseException {
+        Matcher m = reLedgerDate.matcher(dateString);
+        if (!m.matches()) throw new ParseException(dateString, 0);
+
+        String year = m.group(1);
+        String month = m.group(2);
+        String day = m.group(3);
+
+        String toParse;
+        if (year == null) {
+            Calendar now = Calendar.getInstance();
+            int thisYear = now.get(Calendar.YEAR);
+            if (month == null) {
+                int thisMonth = now.get(Calendar.MONTH) + 1;
+                toParse = String.format(Locale.US, "%04d/%02d/%s", thisYear, thisMonth, dateString);
+            }
+            else toParse = String.format(Locale.US, "%04d/%s", thisYear, dateString);
         }
+        else toParse = dateString;
+
+        Log.d("globals", String.format("Parsing date '%s'", toParse));
+        return ledgerDateFormatter.parse(toParse);
     }
     public static String formatLedgerDate(Date date) {
         return ledgerDateFormatter.format(date);
index 2390373..74b5484 100644 (file)
     <string name="new_profile_title" type="id">New profile</string>
     <string name="delete_profile">Delete profile</string>
     <string name="delete">Delete</string>
+    <string name="error_invalid_date">Invalid date</string>
     <string-array name="month_names">
         <item>January</item>
         <item>February</item>