From: Damyan Ivanov Date: Tue, 16 Jun 2020 05:16:39 +0000 (+0000) Subject: rework transaction date handling X-Git-Tag: v0.14.0~14 X-Git-Url: https://git.ktnx.net/?p=mobile-ledger.git;a=commitdiff_plain;h=217da55a224e2ae899d0b50604e2e54f882ec04f rework transaction date handling the surface problem is that storing dates as strings in hledger format (yyyy/mm/dd) doesn't guarantee sort order. An year of 12020 is a typo, but goes unnoticed and sorts earlier than 2020. Splitting the date into separate numeric columns for year, month and day makes sorting work as needed. While there, replace in-memory storage from java's Date to a SimpleDate object which just holds the year, month and day. Simplifies exchanging dates with the database and fulfils the task of holding a date just fine. The extra flexibility of adding intervals to dates or holding also time components is not needed anyway. --- diff --git a/app/src/main/java/net/ktnx/mobileledger/async/SendTransactionTask.java b/app/src/main/java/net/ktnx/mobileledger/async/SendTransactionTask.java index 847e8c2f..0fd4a1b5 100644 --- a/app/src/main/java/net/ktnx/mobileledger/async/SendTransactionTask.java +++ b/app/src/main/java/net/ktnx/mobileledger/async/SendTransactionTask.java @@ -1,5 +1,5 @@ /* - * Copyright © 2019 Damyan Ivanov. + * Copyright © 2020 Damyan Ivanov. * This file is part of MoLe. * MoLe is free software: you can distribute it and/or modify it * under the term of the GNU General Public License as published by @@ -32,6 +32,7 @@ import net.ktnx.mobileledger.model.MobileLedgerProfile; import net.ktnx.mobileledger.utils.Globals; import net.ktnx.mobileledger.utils.Logger; import net.ktnx.mobileledger.utils.NetworkUtil; +import net.ktnx.mobileledger.utils.SimpleDate; import net.ktnx.mobileledger.utils.UrlEncodedFormData; import java.io.BufferedReader; @@ -41,8 +42,6 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.net.HttpURLConnection; import java.nio.charset.StandardCharsets; -import java.util.Date; -import java.util.GregorianCalendar; import java.util.List; import java.util.Locale; import java.util.Map; @@ -176,9 +175,9 @@ public class SendTransactionTask extends AsyncTask { String[] params; if (filterAccName[0] == null) { - sql = "SELECT id, date FROM transactions WHERE profile=? ORDER BY date desc, id " + - "desc"; + sql = "SELECT id, year, month, day FROM transactions WHERE profile=? ORDER BY " + + "year desc, month desc, day desc, id desc"; params = new String[]{profile_uuid}; } else { - sql = "SELECT distinct tr.id, tr.date from transactions tr JOIN " + - "transaction_accounts ta " + + sql = "SELECT distinct tr.id, tr.year, tr.month, tr.day from transactions tr " + + "JOIN " + "transaction_accounts ta " + "ON ta.transaction_id=tr.id AND ta.profile=tr.profile WHERE tr.profile=? " + "and ta.account_name LIKE ?||'%' AND ta" + - ".amount <> 0 ORDER BY tr.date desc, tr.id desc"; + ".amount <> 0 ORDER BY tr.year desc, tr.month desc, tr.day desc, tr.id " + + "desc"; params = new String[]{profile_uuid, filterAccName[0]}; } debug("UTT", sql); SQLiteDatabase db = App.getDatabase(); - String lastDateString = Globals.formatLedgerDate(new Date()); - Calendar lastDate = Globals.parseLedgerDateAsCalendar(lastDateString); boolean odd = true; + SimpleDate lastDate = SimpleDate.today(); try (Cursor cursor = db.rawQuery(sql, params)) { while (cursor.moveToNext()) { if (isCancelled()) return null; int transaction_id = cursor.getInt(0); - String dateString = cursor.getString(1); - Calendar date = Globals.parseLedgerDateAsCalendar(dateString); + SimpleDate date = + new SimpleDate(cursor.getInt(1), cursor.getInt(2), cursor.getInt(3)); - if (!lastDateString.equals(dateString)) { + if (!date.equals(lastDate)) { boolean showMonth = - (date.get(Calendar.MONTH) != lastDate.get(Calendar.MONTH)) || - (date.get(Calendar.YEAR) != lastDate.get(Calendar.YEAR)); - newList.add(new TransactionListItem(date.getTime(), showMonth)); + (date.month != lastDate.month) || (date.year != lastDate.year); + newList.add(new TransactionListItem(date, showMonth)); } newList.add( new TransactionListItem(new LedgerTransaction(transaction_id), odd)); // debug("UTT", String.format("got transaction %d", transaction_id)); lastDate = date; - lastDateString = dateString; odd = !odd; } Data.transactions.setList(newList); @@ -98,9 +93,6 @@ public class UpdateTransactionsTask extends AsyncTask { return null; } - catch (ParseException e) { - return String.format("Error parsing stored date '%s'", e.getMessage()); - } finally { Data.backgroundTaskFinished(); } diff --git a/app/src/main/java/net/ktnx/mobileledger/json/v1_14/ParsedLedgerTransaction.java b/app/src/main/java/net/ktnx/mobileledger/json/v1_14/ParsedLedgerTransaction.java index 926717cc..5b9b399b 100644 --- a/app/src/main/java/net/ktnx/mobileledger/json/v1_14/ParsedLedgerTransaction.java +++ b/app/src/main/java/net/ktnx/mobileledger/json/v1_14/ParsedLedgerTransaction.java @@ -1,5 +1,5 @@ /* - * Copyright © 2019 Damyan Ivanov. + * Copyright © 2020 Damyan Ivanov. * This file is part of MoLe. * MoLe is free software: you can distribute it and/or modify it * under the term of the GNU General Public License as published by @@ -23,11 +23,10 @@ import net.ktnx.mobileledger.model.LedgerTransaction; import net.ktnx.mobileledger.model.LedgerTransactionAccount; import net.ktnx.mobileledger.utils.Globals; import net.ktnx.mobileledger.utils.Misc; +import net.ktnx.mobileledger.utils.SimpleDate; import java.text.ParseException; import java.util.ArrayList; -import java.util.Date; -import java.util.GregorianCalendar; import java.util.List; @JsonIgnoreProperties(ignoreUnknown = true) @@ -60,9 +59,9 @@ public class ParsedLedgerTransaction implements net.ktnx.mobileledger.json.Parse } result.setTpostings(postings); - Date transactionDate = tr.getDate(); + SimpleDate transactionDate = tr.getDate(); if (transactionDate == null) { - transactionDate = new GregorianCalendar().getTime(); + transactionDate = SimpleDate.today(); } result.setTdate(Globals.formatIsoDate(transactionDate)); result.setTdate2(null); @@ -145,7 +144,7 @@ public class ParsedLedgerTransaction implements net.ktnx.mobileledger.json.Parse tpostings.add(posting); } public LedgerTransaction asLedgerTransaction() throws ParseException { - Date date = Globals.parseIsoDate(tdate); + SimpleDate date = Globals.parseIsoDate(tdate); LedgerTransaction tr = new LedgerTransaction(tindex, date, tdescription); tr.setComment(Misc.trim(Misc.emptyIsNull(tcomment))); diff --git a/app/src/main/java/net/ktnx/mobileledger/json/v1_15/ParsedLedgerTransaction.java b/app/src/main/java/net/ktnx/mobileledger/json/v1_15/ParsedLedgerTransaction.java index 305a2ade..225f162b 100644 --- a/app/src/main/java/net/ktnx/mobileledger/json/v1_15/ParsedLedgerTransaction.java +++ b/app/src/main/java/net/ktnx/mobileledger/json/v1_15/ParsedLedgerTransaction.java @@ -1,5 +1,5 @@ /* - * Copyright © 2019 Damyan Ivanov. + * Copyright © 2020 Damyan Ivanov. * This file is part of MoLe. * MoLe is free software: you can distribute it and/or modify it * under the term of the GNU General Public License as published by @@ -23,11 +23,10 @@ import net.ktnx.mobileledger.model.LedgerTransaction; import net.ktnx.mobileledger.model.LedgerTransactionAccount; import net.ktnx.mobileledger.utils.Globals; import net.ktnx.mobileledger.utils.Misc; +import net.ktnx.mobileledger.utils.SimpleDate; import java.text.ParseException; import java.util.ArrayList; -import java.util.Date; -import java.util.GregorianCalendar; import java.util.List; @JsonIgnoreProperties(ignoreUnknown = true) @@ -58,9 +57,9 @@ public class ParsedLedgerTransaction implements net.ktnx.mobileledger.json.Parse } result.setTpostings(postings); - Date transactionDate = tr.getDate(); + SimpleDate transactionDate = tr.getDate(); if (transactionDate == null) { - transactionDate = new GregorianCalendar().getTime(); + transactionDate = SimpleDate.today(); } result.setTdate(Globals.formatIsoDate(transactionDate)); result.setTdate2(null); @@ -143,7 +142,7 @@ public class ParsedLedgerTransaction implements net.ktnx.mobileledger.json.Parse tpostings.add(posting); } public LedgerTransaction asLedgerTransaction() throws ParseException { - Date date = Globals.parseIsoDate(tdate); + SimpleDate date = Globals.parseIsoDate(tdate); LedgerTransaction tr = new LedgerTransaction(tindex, date, tdescription); tr.setComment(Misc.trim(Misc.emptyIsNull(tcomment))); diff --git a/app/src/main/java/net/ktnx/mobileledger/model/LedgerTransaction.java b/app/src/main/java/net/ktnx/mobileledger/model/LedgerTransaction.java index 8417eae1..9b73d2ce 100644 --- a/app/src/main/java/net/ktnx/mobileledger/model/LedgerTransaction.java +++ b/app/src/main/java/net/ktnx/mobileledger/model/LedgerTransaction.java @@ -1,5 +1,5 @@ /* - * Copyright © 2019 Damyan Ivanov. + * Copyright © 2020 Damyan Ivanov. * This file is part of MoLe. * MoLe is free software: you can distribute it and/or modify it * under the term of the GNU General Public License as published by @@ -22,13 +22,13 @@ import android.database.sqlite.SQLiteDatabase; import net.ktnx.mobileledger.utils.Digest; import net.ktnx.mobileledger.utils.Globals; +import net.ktnx.mobileledger.utils.SimpleDate; import java.nio.charset.StandardCharsets; import java.security.NoSuchAlgorithmException; import java.text.ParseException; import java.util.ArrayList; import java.util.Comparator; -import java.util.Date; public class LedgerTransaction { private static final String DIGEST_TYPE = "SHA-256"; @@ -53,7 +53,7 @@ public class LedgerTransaction { }; private String profile; private Integer id; - private Date date; + private SimpleDate date; private String description; private String comment; private ArrayList accounts; @@ -63,7 +63,7 @@ public class LedgerTransaction { throws ParseException { this(id, Globals.parseLedgerDate(dateString), description); } - public LedgerTransaction(Integer id, Date date, String description, + public LedgerTransaction(Integer id, SimpleDate date, String description, MobileLedgerProfile profile) { this.profile = profile.getUuid(); this.id = id; @@ -73,14 +73,14 @@ public class LedgerTransaction { this.dataHash = null; dataLoaded = false; } - public LedgerTransaction(Integer id, Date date, String description) { + public LedgerTransaction(Integer id, SimpleDate date, String description) { this(id, date, description, Data.profile.getValue()); } - public LedgerTransaction(Date date, String description) { + public LedgerTransaction(SimpleDate date, String description) { this(null, date, description); } public LedgerTransaction(int id) { - this(id, (Date) null, null); + this(id, (SimpleDate) null, null); } public LedgerTransaction(int id, String profileUUID) { this.profile = profileUUID; @@ -98,10 +98,10 @@ public class LedgerTransaction { accounts.add(item); dataHash = null; } - public Date getDate() { + public SimpleDate getDate() { return date; } - public void setDate(Date date) { + public void setDate(SimpleDate date) { this.date = date; dataHash = null; } @@ -171,22 +171,13 @@ public class LedgerTransaction { return; try (Cursor cTr = db.rawQuery( - "SELECT date, description, comment from transactions WHERE profile=? AND id=?", - new String[]{profile, String.valueOf(id)})) + "SELECT year, month, day, description, comment from transactions WHERE profile=? " + + "AND id=?", new String[]{profile, String.valueOf(id)})) { if (cTr.moveToFirst()) { - String dateString = cTr.getString(0); - try { - date = Globals.parseLedgerDate(dateString); - } - catch (ParseException e) { - e.printStackTrace(); - throw new RuntimeException( - String.format("Error parsing date '%s' from " + "transaction %d", - dateString, id)); - } - description = cTr.getString(1); - comment = cTr.getString(2); + date = new SimpleDate(cTr.getInt(0), cTr.getInt(1), cTr.getInt(2)); + description = cTr.getString(3); + comment = cTr.getString(4); accounts.clear(); diff --git a/app/src/main/java/net/ktnx/mobileledger/model/MobileLedgerProfile.java b/app/src/main/java/net/ktnx/mobileledger/model/MobileLedgerProfile.java index 9d417955..925e05eb 100644 --- a/app/src/main/java/net/ktnx/mobileledger/model/MobileLedgerProfile.java +++ b/app/src/main/java/net/ktnx/mobileledger/model/MobileLedgerProfile.java @@ -1,5 +1,5 @@ /* - * Copyright © 2019 Damyan Ivanov. + * Copyright © 2020 Damyan Ivanov. * This file is part of MoLe. * MoLe is free software: you can distribute it and/or modify it * under the term of the GNU General Public License as published by @@ -293,11 +293,12 @@ public final class MobileLedgerProfile { new Object[]{uuid, tr.getId()}); db.execSQL( - "INSERT INTO transactions(profile, id, date, description, comment, data_hash, " + - "keep) " + - "values(?,?,?,?,?,?,1)", - new Object[]{uuid, tr.getId(), Globals.formatLedgerDate(tr.getDate()), - tr.getDescription(), tr.getComment(), tr.getDataHash() + "INSERT INTO transactions(profile, id, year, month, day, description, "+ + "comment, data_hash, keep) " + + "values(?,?,?,?,?,?,?,?,1)", + new Object[]{uuid, tr.getId(), tr.getDate().year, tr.getDate().month, + tr.getDate().day, tr.getDescription(), + tr.getComment(), tr.getDataHash() }); for (LedgerTransactionAccount item : tr.getAccounts()) { diff --git a/app/src/main/java/net/ktnx/mobileledger/model/TransactionListItem.java b/app/src/main/java/net/ktnx/mobileledger/model/TransactionListItem.java index e24647f2..ab8bb7ac 100644 --- a/app/src/main/java/net/ktnx/mobileledger/model/TransactionListItem.java +++ b/app/src/main/java/net/ktnx/mobileledger/model/TransactionListItem.java @@ -1,5 +1,5 @@ /* - * Copyright © 2019 Damyan Ivanov. + * Copyright © 2020 Damyan Ivanov. * This file is part of MoLe. * MoLe is free software: you can distribute it and/or modify it * under the term of the GNU General Public License as published by @@ -17,17 +17,17 @@ package net.ktnx.mobileledger.model; -import java.util.Date; - import androidx.annotation.NonNull; +import net.ktnx.mobileledger.utils.SimpleDate; + public class TransactionListItem { private Type type; - private Date date; + private SimpleDate date; private boolean monthShown; private LedgerTransaction transaction; private boolean odd; - public TransactionListItem(Date date, boolean monthShown) { + public TransactionListItem(SimpleDate date, boolean monthShown) { this.type = Type.DELIMITER; this.date = date; this.monthShown = monthShown; @@ -41,7 +41,7 @@ public class TransactionListItem { public Type getType() { return type; } - public Date getDate() { + public SimpleDate getDate() { return date; } public boolean isMonthShown() { diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/DatePickerFragment.java b/app/src/main/java/net/ktnx/mobileledger/ui/DatePickerFragment.java index 3115d477..80c69e59 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/DatePickerFragment.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/DatePickerFragment.java @@ -1,5 +1,5 @@ /* - * Copyright © 2019 Damyan Ivanov. + * Copyright © 2020 Damyan Ivanov. * This file is part of MoLe. * MoLe is free software: you can distribute it and/or modify it * under the term of the GNU General Public License as published by @@ -22,14 +22,15 @@ import android.os.Bundle; import android.widget.CalendarView; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatDialogFragment; import net.ktnx.mobileledger.R; import net.ktnx.mobileledger.model.MobileLedgerProfile; +import net.ktnx.mobileledger.utils.SimpleDate; import java.util.Calendar; import java.util.GregorianCalendar; -import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -40,12 +41,54 @@ public class DatePickerFragment extends AppCompatDialogFragment static final Pattern reD = Pattern.compile("\\s*(\\d+)\\s*$"); private Calendar presentDate = GregorianCalendar.getInstance(); private DatePickedListener onDatePickedListener; - private MobileLedgerProfile.FutureDates futureDates = MobileLedgerProfile.FutureDates.None; - public MobileLedgerProfile.FutureDates getFutureDates() { - return futureDates; + private long minDate = 0; + private long maxDate = Long.MAX_VALUE; + public void setDateRange(@Nullable SimpleDate minDate, @Nullable SimpleDate maxDate) { + if (minDate == null) + this.minDate = 0; + else + this.minDate = minDate.toDate().getTime(); + + if (maxDate == null) + this.maxDate = Long.MAX_VALUE; + else + this.maxDate = maxDate.toDate().getTime(); } public void setFutureDates(MobileLedgerProfile.FutureDates futureDates) { - this.futureDates = futureDates; + if (futureDates == MobileLedgerProfile.FutureDates.All) { + maxDate = Long.MAX_VALUE; + } + else { + final Calendar dateLimit = GregorianCalendar.getInstance(); + switch (futureDates) { + case None: + // already there + break; + case OneWeek: + dateLimit.add(Calendar.DAY_OF_MONTH, 7); + break; + case TwoWeeks: + dateLimit.add(Calendar.DAY_OF_MONTH, 14); + break; + case OneMonth: + dateLimit.add(Calendar.MONTH, 1); + break; + case TwoMonths: + dateLimit.add(Calendar.MONTH, 2); + break; + case ThreeMonths: + dateLimit.add(Calendar.MONTH, 3); + break; + case SixMonths: + dateLimit.add(Calendar.MONTH, 6); + break; + case OneYear: + dateLimit.add(Calendar.YEAR, 1); + break; + } + maxDate = dateLimit.getTime() + .getTime(); + } } public void setCurrentDateFromText(CharSequence present) { final Calendar now = GregorianCalendar.getInstance(); @@ -85,40 +128,8 @@ public class DatePickerFragment extends AppCompatDialogFragment cv.setDate(presentDate.getTime() .getTime()); - if (futureDates == MobileLedgerProfile.FutureDates.All) { - cv.setMaxDate(Long.MAX_VALUE); - } - else { - final Calendar dateLimit = GregorianCalendar.getInstance(); - switch (futureDates) { - case None: - // already there - break; - case OneWeek: - dateLimit.add(Calendar.DAY_OF_MONTH, 7); - break; - case TwoWeeks: - dateLimit.add(Calendar.DAY_OF_MONTH, 14); - break; - case OneMonth: - dateLimit.add(Calendar.MONTH, 1); - break; - case TwoMonths: - dateLimit.add(Calendar.MONTH, 2); - break; - case ThreeMonths: - dateLimit.add(Calendar.MONTH, 3); - break; - case SixMonths: - dateLimit.add(Calendar.MONTH, 6); - break; - case OneYear: - dateLimit.add(Calendar.YEAR, 1); - break; - } - cv.setMaxDate(dateLimit.getTime() - .getTime()); - } + cv.setMinDate(minDate); + cv.setMaxDate(maxDate); cv.setOnDateChangeListener(this); diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionFragment.java b/app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionFragment.java index 2b3adc9f..e4ac7792 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionFragment.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionFragment.java @@ -1,5 +1,5 @@ /* - * Copyright © 2019 Damyan Ivanov. + * Copyright © 2020 Damyan Ivanov. * This file is part of MoLe. * MoLe is free software: you can distribute it and/or modify it * under the term of the GNU General Public License as published by @@ -46,11 +46,10 @@ import net.ktnx.mobileledger.model.LedgerTransactionAccount; import net.ktnx.mobileledger.model.MobileLedgerProfile; import net.ktnx.mobileledger.utils.Logger; import net.ktnx.mobileledger.utils.Misc; +import net.ktnx.mobileledger.utils.SimpleDate; import org.jetbrains.annotations.NotNull; -import java.util.Date; - /** * A simple {@link Fragment} subclass. * Activities that contain this fragment must implement the @@ -197,7 +196,7 @@ public class NewTransactionFragment extends Fragment { fab.setEnabled(false); Misc.hideSoftKeyboard(this); if (mListener != null) { - Date date = viewModel.getDate(); + SimpleDate date = viewModel.getDate(); LedgerTransaction tr = new LedgerTransaction(null, date, viewModel.getDescription(), mProfile); diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionItemHolder.java b/app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionItemHolder.java index 50c321fd..9af8baca 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionItemHolder.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionItemHolder.java @@ -50,11 +50,11 @@ import net.ktnx.mobileledger.utils.DimensionUtils; import net.ktnx.mobileledger.utils.Logger; import net.ktnx.mobileledger.utils.MLDB; import net.ktnx.mobileledger.utils.Misc; +import net.ktnx.mobileledger.utils.SimpleDate; import java.text.DecimalFormatSymbols; -import java.util.Calendar; +import java.text.ParseException; import java.util.Date; -import java.util.GregorianCalendar; import java.util.Locale; import static net.ktnx.mobileledger.ui.activity.NewTransactionModel.ItemType; @@ -77,7 +77,7 @@ class NewTransactionItemHolder extends RecyclerView.ViewHolder private FrameLayout lPadding; private MobileLedgerProfile mProfile; private Date date; - private Observer dateObserver; + private Observer dateObserver; private Observer descriptionObserver; private Observer transactionCommentObserver; private Observer hintObserver; @@ -590,6 +590,9 @@ class NewTransactionItemHolder extends RecyclerView.ViewHolder return true; } + catch (ParseException e) { + throw new RuntimeException("Should not happen", e); + } finally { syncingData = false; } @@ -707,9 +710,7 @@ class NewTransactionItemHolder extends RecyclerView.ViewHolder } @Override public void onDatePicked(int year, int month, int day) { - final Calendar c = GregorianCalendar.getInstance(); - c.set(year, month, day); - item.setDate(c.getTime()); + item.setDate(new SimpleDate(year, month+1, day)); boolean focused = tvDescription.requestFocus(); if (focused) Misc.showSoftKeyboard((NewTransactionActivity) tvAccount.getContext()); diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionModel.java b/app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionModel.java index 83ffbb8f..3350664e 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionModel.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/activity/NewTransactionModel.java @@ -1,5 +1,5 @@ /* - * Copyright © 2019 Damyan Ivanov. + * Copyright © 2020 Damyan Ivanov. * This file is part of MoLe. * MoLe is free software: you can distribute it and/or modify it * under the term of the GNU General Public License as published by @@ -28,24 +28,20 @@ import net.ktnx.mobileledger.model.Currency; import net.ktnx.mobileledger.model.Data; import net.ktnx.mobileledger.model.LedgerTransactionAccount; import net.ktnx.mobileledger.model.MobileLedgerProfile; +import net.ktnx.mobileledger.utils.Globals; +import net.ktnx.mobileledger.utils.SimpleDate; import org.jetbrains.annotations.NotNull; +import java.text.ParseException; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; -import java.util.Date; import java.util.GregorianCalendar; import java.util.Locale; import java.util.concurrent.atomic.AtomicInteger; -import java.util.regex.Matcher; -import java.util.regex.Pattern; public class NewTransactionModel extends ViewModel { - private static final Pattern reYMD = - Pattern.compile("^\\s*(\\d+)\\d*/\\s*(\\d+)\\s*/\\s*(\\d+)\\s*$"); - private static final Pattern reMD = Pattern.compile("^\\s*(\\d+)\\s*/\\s*(\\d+)\\s*$"); - private static final Pattern reD = Pattern.compile("\\s*(\\d+)\\s*$"); final MutableLiveData showCurrency = new MutableLiveData<>(false); final ArrayList items = new ArrayList<>(); final MutableLiveData isSubmittable = new MutableLiveData<>(false); @@ -89,7 +85,7 @@ public class NewTransactionModel extends ViewModel { int getAccountCount() { return items.size(); } - public Date getDate() { + public SimpleDate getDate() { return header.date.getValue(); } public String getDescription() { @@ -232,7 +228,7 @@ public class NewTransactionModel extends ViewModel { static class Item { private ItemType type; - private MutableLiveData date = new MutableLiveData<>(); + private MutableLiveData date = new MutableLiveData<>(); private MutableLiveData description = new MutableLiveData<>(); private LedgerTransactionAccount account; private MutableLiveData amountHint = new MutableLiveData<>(null); @@ -248,7 +244,7 @@ public class NewTransactionModel extends ViewModel { type = ItemType.bottomFiller; editable.setValue(false); } - Item(NewTransactionModel model, Date date, String description) { + Item(NewTransactionModel model, SimpleDate date, String description) { this.model = model; this.type = ItemType.generalData; this.date.setValue(date); @@ -325,58 +321,30 @@ public class NewTransactionModel extends ViewModel { wantedType)); } } - public Date getDate() { + public SimpleDate getDate() { ensureType(ItemType.generalData); return date.getValue(); } - public void setDate(Date date) { + public void setDate(SimpleDate date) { ensureType(ItemType.generalData); this.date.setValue(date); } - public void setDate(String text) { + public void setDate(String text) throws ParseException { if ((text == null) || text.trim() .isEmpty()) { - setDate((Date) null); + setDate((SimpleDate) null); return; } - int year, month, day; - final Calendar c = GregorianCalendar.getInstance(); - Matcher m = reYMD.matcher(text); - 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 { - year = c.get(Calendar.YEAR); - m = reMD.matcher(text); - if (m.matches()) { - month = Integer.parseInt(m.group(1)) - 1; - day = Integer.parseInt(m.group(2)); - } - else { - month = c.get(Calendar.MONTH); - m = reD.matcher(text); - if (m.matches()) { - day = Integer.parseInt(m.group(1)); - } - else { - day = c.get(Calendar.DAY_OF_MONTH); - } - } - } - - c.set(year, month, day); - - this.setDate(c.getTime()); + SimpleDate date = Globals.parseLedgerDate(text); + this.setDate(date); } void observeDate(@NonNull @NotNull androidx.lifecycle.LifecycleOwner owner, - @NonNull androidx.lifecycle.Observer observer) { + @NonNull androidx.lifecycle.Observer observer) { this.date.observe(owner, observer); } - void stopObservingDate(@NonNull androidx.lifecycle.Observer observer) { + void stopObservingDate(@NonNull androidx.lifecycle.Observer observer) { this.date.removeObserver(observer); } public String getDescription() { @@ -426,27 +394,21 @@ public class NewTransactionModel extends ViewModel { String getFormattedDate() { if (date == null) return null; - Date time = date.getValue(); - if (time == null) + SimpleDate d = date.getValue(); + if (d == null) return null; - Calendar c = GregorianCalendar.getInstance(); - c.setTime(time); Calendar today = GregorianCalendar.getInstance(); - final int myYear = c.get(Calendar.YEAR); - final int myMonth = c.get(Calendar.MONTH); - final int myDay = c.get(Calendar.DAY_OF_MONTH); - - if (today.get(Calendar.YEAR) != myYear) { - return String.format(Locale.US, "%d/%02d/%02d", myYear, myMonth + 1, myDay); + if (today.get(Calendar.YEAR) != d.year) { + return String.format(Locale.US, "%d/%02d/%02d", d.year, d.month, d.day); } - if (today.get(Calendar.MONTH) != myMonth) { - return String.format(Locale.US, "%d/%02d", myMonth + 1, myDay); + if (today.get(Calendar.MONTH) != d.month - 1) { + return String.format(Locale.US, "%d/%02d", d.month, d.day); } - return String.valueOf(myDay); + return String.valueOf(d.day); } void observeEditableFlag(NewTransactionActivity activity, Observer observer) { editable.observe(activity, observer); diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListAdapter.java b/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListAdapter.java index 35e68e59..f8b87072 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListAdapter.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/transaction_list/TransactionListAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright © 2019 Damyan Ivanov. + * Copyright © 2020 Damyan Ivanov. * This file is part of MoLe. * MoLe is free software: you can distribute it and/or modify it * under the term of the GNU General Public License as published by @@ -43,9 +43,9 @@ import net.ktnx.mobileledger.model.TransactionListItem; import net.ktnx.mobileledger.utils.Colors; import net.ktnx.mobileledger.utils.Globals; import net.ktnx.mobileledger.utils.Misc; +import net.ktnx.mobileledger.utils.SimpleDate; import java.text.DateFormat; -import java.util.Date; import java.util.GregorianCalendar; import java.util.TimeZone; @@ -84,14 +84,14 @@ public class TransactionListAdapter extends RecyclerView.Adapter. + */ + +package net.ktnx.mobileledger.utils; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.Calendar; +import java.util.Date; + +public class SimpleDate { + public int year; + public int month; + public int day; + public SimpleDate(int y, int m, int d) { + year = y; + month = m; + day = d; + } + public static SimpleDate fromDate(Date date) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + return fromCalendar(calendar); + } + public static SimpleDate fromCalendar(Calendar calendar) { + return new SimpleDate(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH) + 1, + calendar.get(Calendar.DATE)); + } + public static SimpleDate today() { + return fromCalendar(Calendar.getInstance()); + } + public Calendar toCalendar() { + Calendar result = Calendar.getInstance(); + result.set(year, month - 1, day); + return result; + } + public Date toDate() { + return toCalendar().getTime(); + } + public boolean equals(@Nullable SimpleDate date) { + if (date == null) + return false; + + if (year != date.year) + return false; + if (month != date.month) + return false; + if (day != date.day) + return false; + + return true; + } + public boolean earlierThan(@NonNull SimpleDate date) { + if (year < date.year) + return true; + if (year > date.year) + return false; + if (month < date.month) + return true; + if (month > date.month) + return false; + return (day < date.day); + } + public boolean laterThan(@NonNull SimpleDate date) { + if (year > date.year) + return true; + if (year < date.year) + return false; + if (month > date.month) + return true; + if (month < date.month) + return false; + return (day > date.day); + } +} diff --git a/app/src/main/res/raw/create_db.sql b/app/src/main/res/raw/create_db.sql index 175d6574..da3e8d8b 100644 --- a/app/src/main/res/raw/create_db.sql +++ b/app/src/main/res/raw/create_db.sql @@ -1,3 +1,17 @@ +-- Copyright © 2020 Damyan Ivanov. +-- This file is part of MoLe. +-- MoLe is free software: you can distribute it and/or modify it +-- under the term of the GNU General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- (at your opinion), any later version. +-- +-- MoLe 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 terms for details. +-- +-- You should have received a copy of the GNU General Public License +-- along with MoLe. If not, see . create table accounts(profile varchar not null, name varchar not null, name_upper varchar not null, hidden boolean not null default 0, keep boolean not null default 0, level integer not null, parent_name varchar, expanded default 1, amounts_expanded boolean default 0); create unique index un_accounts on accounts(profile, name); create table options(profile varchar not null, name varchar not null, value varchar); @@ -6,10 +20,10 @@ create table account_values(profile varchar not null, account varchar not null, create unique index un_account_values on account_values(profile,account,currency); create table description_history(description varchar not null primary key, keep boolean, description_upper varchar); create table profiles(uuid varchar not null primary key, name not null, url not null, use_authentication boolean not null, auth_user varchar, auth_password varchar, order_no integer, permit_posting boolean default 0, theme integer default -1, preferred_accounts_filter varchar, future_dates integer, api_version integer, show_commodity_by_default boolean default 0, default_commodity varchar, show_comments_by_default boolean default 1); -create table transactions(profile varchar not null, id integer not null, data_hash varchar not null, date varchar not null, description varchar not null, comment varchar, keep boolean not null default 0); +create table transactions(profile varchar not null, id integer not null, data_hash varchar not null, year integer not null, month integer not null, day integer not null, description varchar not null, comment varchar, keep boolean not null default 0); create unique index un_transactions_id on transactions(profile,id); create unique index un_transactions_data_hash on transactions(profile,data_hash); create index idx_transaction_description on transactions(description); create table transaction_accounts(profile varchar not null, transaction_id integer not null, account_name varchar not null, currency varchar not null default '', amount decimal not null, comment varchar, constraint fk_transaction_accounts_acc foreign key(profile,account_name) references accounts(profile,account_name), constraint fk_transaction_accounts_trn foreign key(profile, transaction_id) references transactions(profile,id)); create table currencies(id integer not null primary key, name varchar not null, position varchar not null, has_gap boolean not null); --- updated to revision 33 \ No newline at end of file +-- updated to revision 34 \ No newline at end of file diff --git a/app/src/main/res/raw/sql_34.sql b/app/src/main/res/raw/sql_34.sql new file mode 100644 index 00000000..e03541d4 --- /dev/null +++ b/app/src/main/res/raw/sql_34.sql @@ -0,0 +1,27 @@ +-- Copyright © 2020 Damyan Ivanov. +-- This file is part of MoLe. +-- MoLe is free software: you can distribute it and/or modify it +-- under the term of the GNU General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- (at your opinion), any later version. +-- +-- MoLe 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 terms for details. +-- +-- You should have received a copy of the GNU General Public License +-- along with MoLe. If not, see . +alter table transactions add year integer not null default 0; +alter table transactions add month integer not null default 0; +alter table transactions add day integer not null default 0; +alter table transactions add tmp_md varchar; +update transactions set year= cast(substr(date, 1,instr(date, '/')-1) as integer); +update transactions set tmp_md= substr(date, instr(date, '/')+1); +update transactions set month=cast(substr(tmp_md,1,instr(tmp_md,'/')-1) as integer); +update transactions set day= cast(substr(tmp_md, instr(tmp_md,'/')+1) as integer); +-- alter table transactions drop date +create table transactions_2(profile varchar not null, id integer not null, data_hash varchar not null, year integer not null, month integer not null, day integer not null, description varchar not null, comment varchar, keep boolean not null default 0); +insert into transactions_2(profile, id, data_hash, year, month, day, description, comment, keep) select profile, id, data_hash, year, month, day, description, comment, keep from transactions; +drop table transactions; +alter table transactions_2 rename to transactions;