diff --git a/app/build.gradle b/app/build.gradle index b5fac63ff..43650a561 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -212,6 +212,7 @@ dependencies { testCompile('org.robolectric:robolectric:3.0', 'junit:junit:4.12', + 'joda-time:joda-time:2.7', 'org.assertj:assertj-core:1.7.1' ) androidTestCompile ('com.android.support:support-annotations:' + androidSupportVersion, diff --git a/app/src/main/java/org/gnucash/android/db/AccountsDbAdapter.java b/app/src/main/java/org/gnucash/android/db/AccountsDbAdapter.java index 3298281c7..1d913177e 100644 --- a/app/src/main/java/org/gnucash/android/db/AccountsDbAdapter.java +++ b/app/src/main/java/org/gnucash/android/db/AccountsDbAdapter.java @@ -37,6 +37,7 @@ import org.gnucash.android.model.Split; import org.gnucash.android.model.Transaction; import org.gnucash.android.model.TransactionType; +import org.gnucash.android.util.TimestampHelper; import java.math.BigDecimal; import java.sql.Timestamp; @@ -182,7 +183,7 @@ protected SQLiteStatement compileReplaceStatement(@NonNull final Account account mReplaceStatement.bindLong(7, account.isFavorite() ? 1 : 0); mReplaceStatement.bindString(8, account.getFullName()); mReplaceStatement.bindLong(9, account.isPlaceholderAccount() ? 1 : 0); - mReplaceStatement.bindString(10, account.getCreatedTimestamp().toString()); + mReplaceStatement.bindString(10, TimestampHelper.getUtcStringForTimestamp(account.getCreatedTimestamp())); mReplaceStatement.bindLong(11, account.isHidden() ? 1 : 0); Commodity commodity = account.getCommodity(); if (commodity == null) @@ -529,7 +530,7 @@ public List getExportableAccounts(Timestamp lastExportTimeStamp){ SplitEntry.COLUMN_ACCOUNT_UID, new String[]{AccountEntry.TABLE_NAME + ".*"}, TransactionEntry.TABLE_NAME + "." + TransactionEntry.COLUMN_MODIFIED_AT + " > ?", - new String[]{lastExportTimeStamp.toString()}, + new String[]{TimestampHelper.getUtcStringForTimestamp(lastExportTimeStamp)}, AccountEntry.TABLE_NAME + "." + AccountEntry.COLUMN_UID, null, null diff --git a/app/src/main/java/org/gnucash/android/db/DatabaseAdapter.java b/app/src/main/java/org/gnucash/android/db/DatabaseAdapter.java index 2e810b569..2f0aa62f3 100644 --- a/app/src/main/java/org/gnucash/android/db/DatabaseAdapter.java +++ b/app/src/main/java/org/gnucash/android/db/DatabaseAdapter.java @@ -29,8 +29,8 @@ import org.gnucash.android.db.DatabaseSchema.TransactionEntry; import org.gnucash.android.model.AccountType; import org.gnucash.android.model.BaseModel; +import org.gnucash.android.util.TimestampHelper; -import java.sql.Timestamp; import java.util.ArrayList; import java.util.List; @@ -301,7 +301,7 @@ public List getAllRecords(){ */ protected ContentValues populateBaseModelAttributes(@NonNull ContentValues contentValues, @NonNull Model model){ contentValues.put(CommonColumns.COLUMN_UID, model.getUID()); - contentValues.put(CommonColumns.COLUMN_CREATED_AT, model.getCreatedTimestamp().toString()); + contentValues.put(CommonColumns.COLUMN_CREATED_AT, TimestampHelper.getUtcStringForTimestamp(model.getCreatedTimestamp())); //there is a trigger in the database for updated the modified_at column /* Due to the use of SQL REPLACE syntax, we insert the created_at values each time * (maintain the original creation time and not the time of creation of the replacement) @@ -321,8 +321,8 @@ protected void populateBaseModelAttributes(Cursor cursor, BaseModel model){ String modified= cursor.getString(cursor.getColumnIndexOrThrow(CommonColumns.COLUMN_MODIFIED_AT)); model.setUID(uid); - model.setCreatedTimestamp(Timestamp.valueOf(created)); - model.setModifiedTimestamp(Timestamp.valueOf(modified)); + model.setCreatedTimestamp(TimestampHelper.getTimestampForUtcString(created)); + model.setModifiedTimestamp(TimestampHelper.getTimestampForUtcString(modified)); } /** diff --git a/app/src/main/java/org/gnucash/android/db/DatabaseSchema.java b/app/src/main/java/org/gnucash/android/db/DatabaseSchema.java index 15d7870cc..7d833a848 100644 --- a/app/src/main/java/org/gnucash/android/db/DatabaseSchema.java +++ b/app/src/main/java/org/gnucash/android/db/DatabaseSchema.java @@ -28,7 +28,7 @@ public class DatabaseSchema { * Database version. * With any change to the database schema, this number must increase */ - public static final int DATABASE_VERSION = 11; + public static final int DATABASE_VERSION = 12; /** * Database version where Splits were introduced diff --git a/app/src/main/java/org/gnucash/android/db/MigrationHelper.java b/app/src/main/java/org/gnucash/android/db/MigrationHelper.java index f8d91f088..87debbebc 100644 --- a/app/src/main/java/org/gnucash/android/db/MigrationHelper.java +++ b/app/src/main/java/org/gnucash/android/db/MigrationHelper.java @@ -24,7 +24,6 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.os.Environment; -import android.preference.PreferenceManager; import android.text.TextUtils; import android.util.Log; @@ -42,6 +41,8 @@ import org.gnucash.android.model.Money; import org.gnucash.android.model.ScheduledAction; import org.gnucash.android.model.Transaction; +import org.gnucash.android.util.PreferencesHelper; +import org.gnucash.android.util.TimestampHelper; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; @@ -59,6 +60,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.TimeZone; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; @@ -645,7 +647,7 @@ static int upgradeDbToVersion8(SQLiteDatabase db) { //================================ END TABLE MIGRATIONS ================================ // String timestamp to be used for all new created entities in migration - String timestamp = (new Timestamp(System.currentTimeMillis())).toString(); + String timestamp = TimestampHelper.getUtcStringForTimestamp(TimestampHelper.getTimestampForNow()); //ScheduledActionDbAdapter scheduledActionDbAdapter = new ScheduledActionDbAdapter(db); //SplitsDbAdapter splitsDbAdapter = new SplitsDbAdapter(db); @@ -696,7 +698,7 @@ static int upgradeDbToVersion8(SQLiteDatabase db) { while (cursor.moveToNext()){ contentValues.clear(); Timestamp timestampT = new Timestamp(cursor.getLong(cursor.getColumnIndexOrThrow(TransactionEntry.COLUMN_TIMESTAMP))); - contentValues.put(TransactionEntry.COLUMN_CREATED_AT, timestampT.toString()); + contentValues.put(TransactionEntry.COLUMN_CREATED_AT, TimestampHelper.getUtcStringForTimestamp(timestampT)); long transactionId = cursor.getLong(cursor.getColumnIndexOrThrow(TransactionEntry._ID)); db.update(TransactionEntry.TABLE_NAME, contentValues, TransactionEntry._ID + "=" + transactionId, null); @@ -1094,11 +1096,9 @@ static int upgradeDbToVersion10(SQLiteDatabase db){ boolean exportAll = Boolean.parseBoolean(tokens[2]); if (exportAll){ - params.setExportStartTime(Timestamp.valueOf(Exporter.TIMESTAMP_ZERO)); + params.setExportStartTime(TimestampHelper.getTimestampForEpochZero()); } else { - String lastExportTimeStamp = PreferenceManager.getDefaultSharedPreferences(GnuCashApplication.getAppContext()) - .getString(Exporter.PREF_LAST_EXPORT_TIME, Exporter.TIMESTAMP_ZERO); - Timestamp timestamp = Timestamp.valueOf(lastExportTimeStamp); + Timestamp timestamp = PreferencesHelper.getLastExportTime(); params.setExportStartTime(timestamp); } @@ -1143,10 +1143,9 @@ static int upgradeDbToVersion11(SQLiteDatabase db){ String tag = cursor.getString(cursor.getColumnIndexOrThrow(ScheduledActionEntry.COLUMN_TAG)); String[] tokens = tag.split(";"); try { - Timestamp timestamp = Timestamp.valueOf(tokens[2]); + Timestamp timestamp = TimestampHelper.getTimestampForUtcString(tokens[2]); } catch (IllegalArgumentException ex) { - tokens[2] = PreferenceManager.getDefaultSharedPreferences(GnuCashApplication.getAppContext()) - .getString(Exporter.PREF_LAST_EXPORT_TIME, Exporter.TIMESTAMP_ZERO); + tokens[2] = TimestampHelper.getUtcStringForTimestamp(PreferencesHelper.getLastExportTime()); } finally { tag = TextUtils.join(";", tokens); } @@ -1170,4 +1169,41 @@ static int upgradeDbToVersion11(SQLiteDatabase db){ } return oldVersion; } + + public static Timestamp subtractTimeZoneOffset(Timestamp timestamp, TimeZone timeZone) { + final long millisecondsToSubtract = Math.abs(timeZone.getOffset(timestamp.getTime())); + return new Timestamp(timestamp.getTime() - millisecondsToSubtract); + } + + /** + * Upgrade database to version 12 + *

+ * Change last_export_time Android preference to current value - N + * where N is the absolute timezone offset for current user time zone. + * For details see #467. + *

+ * @param db SQLite database + * @return 12 if upgrade was successful, 11 otherwise + */ + static int upgradeDbToVersion12(SQLiteDatabase db){ + Log.i(MigrationHelper.LOG_TAG, "Upgrading database to version 12"); + + int oldVersion = 11; + + try { + + final Timestamp currentLastExportTime = PreferencesHelper.getLastExportTime(); + + final Timestamp updatedLastExportTime = subtractTimeZoneOffset( + currentLastExportTime, TimeZone.getDefault()); + PreferencesHelper.setLastExportTime(updatedLastExportTime); + + oldVersion = 12; + + } catch (Exception ignored){ + // Do nothing: here oldVersion = 11. + } + + return oldVersion; + } } diff --git a/app/src/main/java/org/gnucash/android/db/PricesDbAdapter.java b/app/src/main/java/org/gnucash/android/db/PricesDbAdapter.java index 83645f4f5..c4a230f07 100644 --- a/app/src/main/java/org/gnucash/android/db/PricesDbAdapter.java +++ b/app/src/main/java/org/gnucash/android/db/PricesDbAdapter.java @@ -1,17 +1,14 @@ package org.gnucash.android.db; -import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteStatement; import android.support.annotation.NonNull; -import android.util.Log; import android.util.Pair; import org.gnucash.android.app.GnuCashApplication; import org.gnucash.android.model.Price; - -import java.sql.Timestamp; +import org.gnucash.android.util.TimestampHelper; import static org.gnucash.android.db.DatabaseSchema.PriceEntry; @@ -49,7 +46,7 @@ protected SQLiteStatement compileReplaceStatement(@NonNull final Price price) { mReplaceStatement.bindString(1, price.getUID()); mReplaceStatement.bindString(2, price.getCommodityUID()); mReplaceStatement.bindString(3, price.getCurrencyUID()); - mReplaceStatement.bindString(4, price.getDate().toString()); + mReplaceStatement.bindString(4, TimestampHelper.getUtcStringForTimestamp(price.getDate())); if (price.getSource() != null) { mReplaceStatement.bindString(5, price.getSource()); } @@ -73,7 +70,7 @@ public Price buildModelInstance(@NonNull final Cursor cursor) { long valueDenom = cursor.getLong(cursor.getColumnIndexOrThrow(PriceEntry.COLUMN_VALUE_DENOM)); Price price = new Price(commodityUID, currencyUID); - price.setDate(Timestamp.valueOf(dateString)); + price.setDate(TimestampHelper.getTimestampForUtcString(dateString)); price.setSource(source); price.setType(type); price.setValueNum(valueNum); diff --git a/app/src/main/java/org/gnucash/android/db/ScheduledActionDbAdapter.java b/app/src/main/java/org/gnucash/android/db/ScheduledActionDbAdapter.java index 8a98500d2..cecf5ea0c 100644 --- a/app/src/main/java/org/gnucash/android/db/ScheduledActionDbAdapter.java +++ b/app/src/main/java/org/gnucash/android/db/ScheduledActionDbAdapter.java @@ -24,6 +24,7 @@ import org.gnucash.android.app.GnuCashApplication; import org.gnucash.android.model.ScheduledAction; +import org.gnucash.android.util.TimestampHelper; import java.util.ArrayList; import java.util.List; @@ -102,7 +103,7 @@ protected SQLiteStatement compileReplaceStatement(@NonNull final ScheduledAction mReplaceStatement.bindLong(6, schedxAction.getLastRun()); mReplaceStatement.bindLong(7, schedxAction.getPeriod()); mReplaceStatement.bindLong(8, schedxAction.isEnabled() ? 1 : 0); - mReplaceStatement.bindString(9, schedxAction.getCreatedTimestamp().toString()); + mReplaceStatement.bindString(9, TimestampHelper.getUtcStringForTimestamp(schedxAction.getCreatedTimestamp())); if (schedxAction.getTag() == null) mReplaceStatement.bindNull(10); else diff --git a/app/src/main/java/org/gnucash/android/db/SplitsDbAdapter.java b/app/src/main/java/org/gnucash/android/db/SplitsDbAdapter.java index 84215fc81..727db62ce 100644 --- a/app/src/main/java/org/gnucash/android/db/SplitsDbAdapter.java +++ b/app/src/main/java/org/gnucash/android/db/SplitsDbAdapter.java @@ -27,16 +27,14 @@ import android.util.Pair; import org.gnucash.android.app.GnuCashApplication; -import org.gnucash.android.model.AccountType; import org.gnucash.android.model.Commodity; import org.gnucash.android.model.Money; import org.gnucash.android.model.Split; import org.gnucash.android.model.TransactionType; +import org.gnucash.android.util.TimestampHelper; import java.math.BigDecimal; -import java.sql.Timestamp; import java.util.ArrayList; -import java.util.Currency; import java.util.List; import static org.gnucash.android.db.DatabaseSchema.SplitEntry; @@ -80,7 +78,7 @@ public void addRecord(@NonNull final Split split){ //modifying a split means modifying the accompanying transaction as well updateRecord(TransactionEntry.TABLE_NAME, transactionId, - TransactionEntry.COLUMN_MODIFIED_AT, new Timestamp(System.currentTimeMillis()).toString()); + TransactionEntry.COLUMN_MODIFIED_AT, TimestampHelper.getUtcStringForTimestamp(TimestampHelper.getTimestampForNow())); } @Override @@ -109,7 +107,7 @@ protected SQLiteStatement compileReplaceStatement(@NonNull final Split split) { mReplaceStatement.bindLong(5, split.getValue().getDenominator()); mReplaceStatement.bindLong(6, split.getQuantity().getNumerator()); mReplaceStatement.bindLong(7, split.getQuantity().getDenominator()); - mReplaceStatement.bindString(8, split.getCreatedTimestamp().toString()); + mReplaceStatement.bindString(8, TimestampHelper.getUtcStringForTimestamp(split.getCreatedTimestamp())); mReplaceStatement.bindString(9, split.getAccountUID()); mReplaceStatement.bindString(10, split.getTransactionUID()); diff --git a/app/src/main/java/org/gnucash/android/db/TransactionsDbAdapter.java b/app/src/main/java/org/gnucash/android/db/TransactionsDbAdapter.java index 0f9b4bfa6..b4f684340 100644 --- a/app/src/main/java/org/gnucash/android/db/TransactionsDbAdapter.java +++ b/app/src/main/java/org/gnucash/android/db/TransactionsDbAdapter.java @@ -36,6 +36,7 @@ import org.gnucash.android.model.Money; import org.gnucash.android.model.Split; import org.gnucash.android.model.Transaction; +import org.gnucash.android.util.TimestampHelper; import java.sql.Timestamp; import java.util.ArrayList; @@ -187,7 +188,7 @@ protected SQLiteStatement compileReplaceStatement(@NonNull final Transaction tra commodity = CommoditiesDbAdapter.getInstance().getCommodity(transaction.getCurrencyCode()); mReplaceStatement.bindString(7, commodity.getUID()); - mReplaceStatement.bindString(8, transaction.getCreatedTimestamp().toString()); + mReplaceStatement.bindString(8, TimestampHelper.getUtcStringForTimestamp(transaction.getCreatedTimestamp())); if (transaction.getScheduledActionUID() == null) mReplaceStatement.bindNull(9); @@ -643,11 +644,11 @@ public Timestamp getTimestampOfLastModification(){ new String[]{"MAX(" + TransactionEntry.COLUMN_MODIFIED_AT + ")"}, null, null, null, null, null); - Timestamp timestamp = new Timestamp(System.currentTimeMillis()); + Timestamp timestamp = TimestampHelper.getTimestampForNow(); if (cursor.moveToFirst()){ String timeString = cursor.getString(0); if (timeString != null){ //in case there were no transactions in the XML file (account structure only) - timestamp = Timestamp.valueOf(timeString); + timestamp = TimestampHelper.getTimestampForUtcString(timeString); } } cursor.close(); diff --git a/app/src/main/java/org/gnucash/android/export/ExportParams.java b/app/src/main/java/org/gnucash/android/export/ExportParams.java index 97232940e..71002ed2c 100644 --- a/app/src/main/java/org/gnucash/android/export/ExportParams.java +++ b/app/src/main/java/org/gnucash/android/export/ExportParams.java @@ -16,12 +16,8 @@ package org.gnucash.android.export; -import android.preference.PreferenceManager; - -import org.gnucash.android.BuildConfig; -import org.gnucash.android.R; -import org.gnucash.android.app.GnuCashApplication; import org.gnucash.android.ui.export.ExportFormFragment; +import org.gnucash.android.util.TimestampHelper; import java.sql.Timestamp; @@ -50,7 +46,7 @@ public enum ExportTarget {SD_CARD, SHARING, DROPBOX, GOOGLE_DRIVE } /** * All transactions created after this date will be exported */ - private Timestamp mExportStartTime = Timestamp.valueOf(Exporter.TIMESTAMP_ZERO); + private Timestamp mExportStartTime = TimestampHelper.getTimestampForEpochZero(); /** * Flag to determine if all transactions should be deleted after exporting is complete @@ -138,7 +134,7 @@ public void setExportTarget(ExportTarget mExportTarget) { @Override public String toString() { - return "Export all transactions created since " + mExportStartTime.toString() + return "Export all transactions created since " + TimestampHelper.getUtcStringForTimestamp(mExportStartTime) + " UTC" + " as "+ mExportFormat.name() + " to " + mExportTarget.name(); } @@ -151,7 +147,7 @@ public String toCsv(){ String separator = ";"; return mExportFormat.name() + separator + mExportTarget.name() + separator - + mExportStartTime.toString() + separator + + TimestampHelper.getUtcStringForTimestamp(mExportStartTime) + separator + Boolean.toString(mDeleteTransactionsAfterExport); } @@ -164,7 +160,7 @@ public static ExportParams parseCsv(String csvParams){ String[] tokens = csvParams.split(";"); ExportParams params = new ExportParams(ExportFormat.valueOf(tokens[0])); params.setExportTarget(ExportTarget.valueOf(tokens[1])); - params.setExportStartTime(Timestamp.valueOf(tokens[2])); + params.setExportStartTime(TimestampHelper.getTimestampForUtcString(tokens[2])); params.setDeleteTransactionsAfterExport(Boolean.parseBoolean(tokens[3])); return params; diff --git a/app/src/main/java/org/gnucash/android/export/Exporter.java b/app/src/main/java/org/gnucash/android/export/Exporter.java index dc956fd99..8d585531e 100644 --- a/app/src/main/java/org/gnucash/android/export/Exporter.java +++ b/app/src/main/java/org/gnucash/android/export/Exporter.java @@ -35,7 +35,6 @@ import org.gnucash.android.db.TransactionsDbAdapter; import java.io.File; -import java.sql.Timestamp; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; @@ -86,12 +85,6 @@ public abstract class Exporter { private static final SimpleDateFormat EXPORT_FILENAME_DATE_FORMAT = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US); - /** - * last export time in preferences - */ - public static final String PREF_LAST_EXPORT_TIME = "last_export_time"; - - public static final String TIMESTAMP_ZERO = new Timestamp(0).toString(); /** * Adapter for retrieving accounts to export * Subclasses should close this object when they are done with exporting diff --git a/app/src/main/java/org/gnucash/android/export/ofx/OfxExporter.java b/app/src/main/java/org/gnucash/android/export/ofx/OfxExporter.java index c29e426bd..ca71d1db9 100644 --- a/app/src/main/java/org/gnucash/android/export/ofx/OfxExporter.java +++ b/app/src/main/java/org/gnucash/android/export/ofx/OfxExporter.java @@ -29,6 +29,8 @@ import org.gnucash.android.export.Exporter; import org.gnucash.android.model.Account; import org.gnucash.android.model.Transaction; +import org.gnucash.android.util.PreferencesHelper; +import org.gnucash.android.util.TimestampHelper; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -41,7 +43,6 @@ import java.io.OutputStreamWriter; import java.io.StringWriter; import java.io.Writer; -import java.sql.Timestamp; import java.util.ArrayList; import java.util.List; @@ -137,18 +138,16 @@ private String generateOfxExport() throws ExporterException { boolean useXmlHeader = PreferenceManager.getDefaultSharedPreferences(mContext) .getBoolean(mContext.getString(R.string.key_xml_ofx_header), false); - String timeStamp = new Timestamp(System.currentTimeMillis()).toString(); + PreferencesHelper.setLastExportTime(TimestampHelper.getTimestampForNow()); StringWriter stringWriter = new StringWriter(); //if we want SGML OFX headers, write first to string and then prepend header if (useXmlHeader){ write(document, stringWriter, false); - PreferenceManager.getDefaultSharedPreferences(mContext).edit().putString(Exporter.PREF_LAST_EXPORT_TIME, timeStamp).apply(); return stringWriter.toString(); } else { Node ofxNode = document.getElementsByTagName("OFX").item(0); write(ofxNode, stringWriter, true); - PreferenceManager.getDefaultSharedPreferences(mContext).edit().putString(Exporter.PREF_LAST_EXPORT_TIME, timeStamp).apply(); return OfxHelper.OFX_SGML_HEADER + '\n' + stringWriter.toString(); } } diff --git a/app/src/main/java/org/gnucash/android/export/qif/QifExporter.java b/app/src/main/java/org/gnucash/android/export/qif/QifExporter.java index 6c1fcfde6..69946d1c0 100644 --- a/app/src/main/java/org/gnucash/android/export/qif/QifExporter.java +++ b/app/src/main/java/org/gnucash/android/export/qif/QifExporter.java @@ -18,12 +18,13 @@ import android.content.ContentValues; import android.database.Cursor; -import android.preference.PreferenceManager; import org.gnucash.android.db.AccountsDbAdapter; import org.gnucash.android.db.TransactionsDbAdapter; import org.gnucash.android.export.ExportParams; import org.gnucash.android.export.Exporter; +import org.gnucash.android.util.PreferencesHelper; +import org.gnucash.android.util.TimestampHelper; import java.io.BufferedReader; import java.io.BufferedWriter; @@ -34,7 +35,6 @@ import java.io.IOException; import java.io.OutputStreamWriter; import java.math.BigDecimal; -import java.sql.Timestamp; import java.util.ArrayList; import java.util.Currency; import java.util.List; @@ -60,7 +60,7 @@ public List generateExport() throws ExporterException { final String newLine = "\n"; TransactionsDbAdapter transactionsDbAdapter = mTransactionsDbAdapter; try { - String lastExportTimeStamp = mExportParams.getExportStartTime().toString(); + String lastExportTimeStamp = TimestampHelper.getUtcStringForTimestamp(mExportParams.getExportStartTime()); Cursor cursor = transactionsDbAdapter.fetchTransactionsWithSplitsWithTransactionAccount( new String[]{ TransactionEntry.TABLE_NAME + "_" + TransactionEntry.COLUMN_UID + " AS trans_uid", @@ -216,8 +216,7 @@ public List generateExport() throws ExporterException { transactionsDbAdapter.updateTransaction(contentValues, null, null); /// export successful - String timeStamp = new Timestamp(System.currentTimeMillis()).toString(); - PreferenceManager.getDefaultSharedPreferences(mContext).edit().putString(Exporter.PREF_LAST_EXPORT_TIME, timeStamp).apply(); + PreferencesHelper.setLastExportTime(TimestampHelper.getTimestampForNow()); return splitQIF(file); } catch (IOException e) { throw new ExporterException(mExportParams, e); diff --git a/app/src/main/java/org/gnucash/android/export/xml/GncXmlExporter.java b/app/src/main/java/org/gnucash/android/export/xml/GncXmlExporter.java index eecaeafb2..f41aa127d 100644 --- a/app/src/main/java/org/gnucash/android/export/xml/GncXmlExporter.java +++ b/app/src/main/java/org/gnucash/android/export/xml/GncXmlExporter.java @@ -37,6 +37,7 @@ import org.gnucash.android.model.PeriodType; import org.gnucash.android.model.ScheduledAction; import org.gnucash.android.model.TransactionType; +import org.gnucash.android.util.TimestampHelper; import org.xmlpull.v1.XmlPullParserFactory; import org.xmlpull.v1.XmlSerializer; @@ -341,7 +342,7 @@ private void exportTransactions(XmlSerializer xmlSerializer, boolean exportTempl xmlSerializer.endTag(null, GncXmlHelper.TAG_DATE_POSTED); // date entered, time when the transaction was actually created - Timestamp timeEntered = Timestamp.valueOf(cursor.getString(cursor.getColumnIndexOrThrow("trans_date_posted"))); + Timestamp timeEntered = TimestampHelper.getTimestampForUtcString(cursor.getString(cursor.getColumnIndexOrThrow("trans_date_posted"))); String dateEntered = GncXmlHelper.formatDate(timeEntered.getTime()); xmlSerializer.startTag(null, GncXmlHelper.TAG_DATE_ENTERED); xmlSerializer.startTag(null, GncXmlHelper.TAG_TS_DATE); @@ -538,7 +539,7 @@ private void exportScheduledTransactions(XmlSerializer xmlSerializer) throws IOE //start date String createdTimestamp = cursor.getString(cursor.getColumnIndexOrThrow(ScheduledActionEntry.COLUMN_CREATED_AT)); - long scheduleStartTime = Timestamp.valueOf(createdTimestamp).getTime(); + long scheduleStartTime = TimestampHelper.getTimestampForUtcString(createdTimestamp).getTime(); serializeDate(xmlSerializer, GncXmlHelper.TAG_SX_START, scheduleStartTime); long lastRunTime = cursor.getLong(cursor.getColumnIndexOrThrow(ScheduledActionEntry.COLUMN_LAST_RUN)); @@ -658,7 +659,7 @@ private void exportPrices(XmlSerializer xmlSerializer) throws IOException { xmlSerializer.endTag(null, GncXmlHelper.TAG_COMMODITY_ID); xmlSerializer.endTag(null, GncXmlHelper.TAG_PRICE_CURRENCY); // time - String strDate = GncXmlHelper.formatDate(Timestamp.valueOf(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseSchema.PriceEntry.COLUMN_DATE))).getTime()); + String strDate = GncXmlHelper.formatDate(TimestampHelper.getTimestampForUtcString(cursor.getString(cursor.getColumnIndexOrThrow(DatabaseSchema.PriceEntry.COLUMN_DATE))).getTime()); xmlSerializer.startTag(null, GncXmlHelper.TAG_PRICE_TIME); xmlSerializer.startTag(null, GncXmlHelper.TAG_TS_DATE); xmlSerializer.text(strDate); diff --git a/app/src/main/java/org/gnucash/android/importer/GncXmlImporter.java b/app/src/main/java/org/gnucash/android/importer/GncXmlImporter.java index eb9eea9a4..a256ab8cc 100644 --- a/app/src/main/java/org/gnucash/android/importer/GncXmlImporter.java +++ b/app/src/main/java/org/gnucash/android/importer/GncXmlImporter.java @@ -17,12 +17,10 @@ package org.gnucash.android.importer; import android.database.sqlite.SQLiteDatabase; -import android.preference.PreferenceManager; import android.util.Log; -import org.gnucash.android.app.GnuCashApplication; import org.gnucash.android.db.TransactionsDbAdapter; -import org.gnucash.android.export.Exporter; +import org.gnucash.android.util.PreferencesHelper; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; @@ -31,7 +29,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.PushbackInputStream; -import java.sql.Timestamp; import java.util.zip.GZIPInputStream; import javax.xml.parsers.ParserConfigurationException; @@ -94,11 +91,9 @@ public static void parse(InputStream gncXmlInputStream) throws ParserConfigurati xr.parse(new InputSource(bos)); long endTime = System.nanoTime(); - Timestamp timeStamp = TransactionsDbAdapter.getInstance().getTimestampOfLastModification(); - PreferenceManager.getDefaultSharedPreferences(GnuCashApplication.getAppContext()) - .edit() - .putString(Exporter.PREF_LAST_EXPORT_TIME, timeStamp.toString()) - .apply(); + PreferencesHelper.setLastExportTime( + TransactionsDbAdapter.getInstance().getTimestampOfLastModification() + ); Log.d(GncXmlImporter.class.getSimpleName(), String.format("%d ns spent on importing the file", endTime-startTime)); } diff --git a/app/src/main/java/org/gnucash/android/model/BaseModel.java b/app/src/main/java/org/gnucash/android/model/BaseModel.java index cf741495d..38c68802b 100644 --- a/app/src/main/java/org/gnucash/android/model/BaseModel.java +++ b/app/src/main/java/org/gnucash/android/model/BaseModel.java @@ -16,6 +16,8 @@ package org.gnucash.android.model; +import org.gnucash.android.util.TimestampHelper; + import java.sql.Timestamp; import java.util.UUID; @@ -40,8 +42,8 @@ public abstract class BaseModel { * A unique ID will be generated on demand with a call to {@link #getUID()}

*/ public BaseModel(){ - mCreatedTimestamp = new Timestamp(System.currentTimeMillis()); - mModifiedTimestamp = new Timestamp(System.currentTimeMillis()); + mCreatedTimestamp = TimestampHelper.getTimestampForNow(); + mModifiedTimestamp = TimestampHelper.getTimestampForNow(); } /** diff --git a/app/src/main/java/org/gnucash/android/model/Price.java b/app/src/main/java/org/gnucash/android/model/Price.java index 084da5501..b0c2f0cc7 100644 --- a/app/src/main/java/org/gnucash/android/model/Price.java +++ b/app/src/main/java/org/gnucash/android/model/Price.java @@ -1,5 +1,7 @@ package org.gnucash.android.model; +import org.gnucash.android.util.TimestampHelper; + import java.sql.Timestamp; /** @@ -21,7 +23,7 @@ public class Price extends BaseModel { public static final String SOURCE_USER = "user:xfer-dialog"; public Price(){ - mDate = new Timestamp(System.currentTimeMillis()); + mDate = TimestampHelper.getTimestampForNow(); } /** @@ -32,7 +34,7 @@ public Price(){ public Price(String commodityUID, String currencyUID){ this.mCommodityUID = commodityUID; this.mCurrencyUID = currencyUID; - mDate = new Timestamp(System.currentTimeMillis()); + mDate = TimestampHelper.getTimestampForNow(); } public String getCommodityUID() { diff --git a/app/src/main/java/org/gnucash/android/ui/export/ExportFormFragment.java b/app/src/main/java/org/gnucash/android/ui/export/ExportFormFragment.java index 410b48a76..180e4b3f6 100644 --- a/app/src/main/java/org/gnucash/android/ui/export/ExportFormFragment.java +++ b/app/src/main/java/org/gnucash/android/ui/export/ExportFormFragment.java @@ -21,7 +21,6 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; -import android.content.res.Resources; import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; @@ -52,7 +51,6 @@ import com.codetroopers.betterpickers.recurrencepicker.EventRecurrence; import com.codetroopers.betterpickers.recurrencepicker.EventRecurrenceFormatter; import com.codetroopers.betterpickers.recurrencepicker.RecurrencePickerDialog; -import com.crashlytics.android.Crashlytics; import com.dropbox.sync.android.DbxAccountManager; import org.gnucash.android.R; @@ -61,7 +59,6 @@ import org.gnucash.android.export.ExportAsyncTask; import org.gnucash.android.export.ExportFormat; import org.gnucash.android.export.ExportParams; -import org.gnucash.android.export.Exporter; import org.gnucash.android.model.BaseModel; import org.gnucash.android.model.ScheduledAction; import org.gnucash.android.ui.account.AccountsActivity; @@ -69,9 +66,10 @@ import org.gnucash.android.ui.settings.SettingsActivity; import org.gnucash.android.ui.transaction.TransactionFormFragment; import org.gnucash.android.ui.util.RecurrenceParser; +import org.gnucash.android.util.PreferencesHelper; +import org.gnucash.android.util.TimestampHelper; import java.sql.Timestamp; -import java.text.DateFormat; import java.text.ParseException; import java.util.Calendar; import java.util.Date; @@ -267,7 +265,7 @@ private void startExport(){ ExportParams exportParameters = new ExportParams(mExportFormat); if (mExportAllSwitch.isChecked()){ - exportParameters.setExportStartTime(Timestamp.valueOf(Exporter.TIMESTAMP_ZERO)); + exportParameters.setExportStartTime(TimestampHelper.getTimestampForEpochZero()); } else { exportParameters.setExportStartTime(new Timestamp(mExportStartCalendar.getTimeInMillis())); } @@ -350,9 +348,7 @@ public void onNothingSelected(AdapterView parent) { mDestinationSpinner.setSelection(position); //**************** export start time bindings ****************** - String lastExportTimeStamp = PreferenceManager.getDefaultSharedPreferences(getActivity()) - .getString(Exporter.PREF_LAST_EXPORT_TIME, Exporter.TIMESTAMP_ZERO); - Timestamp timestamp = Timestamp.valueOf(lastExportTimeStamp); + Timestamp timestamp = PreferencesHelper.getLastExportTime(); mExportStartCalendar.setTimeInMillis(timestamp.getTime()); Date date = new Date(timestamp.getTime()); diff --git a/app/src/main/java/org/gnucash/android/util/PreferencesHelper.java b/app/src/main/java/org/gnucash/android/util/PreferencesHelper.java new file mode 100644 index 000000000..b48ef7469 --- /dev/null +++ b/app/src/main/java/org/gnucash/android/util/PreferencesHelper.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2015 Alceu Rodrigues Neto + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gnucash.android.util; + +import android.preference.PreferenceManager; +import android.util.Log; + +import org.gnucash.android.app.GnuCashApplication; + +import java.sql.Timestamp; + +/** + * A utility class to deal with Android Preferences in a centralized way. + */ +public final class PreferencesHelper { + + /** + * Should be not instantiated. + */ + private PreferencesHelper() {} + + /** + * Tag for logging + */ + protected static final String LOG_TAG = "PreferencesHelper"; + + + private static final String PREFERENCE_LAST_EXPORT_TIME_DEFAULT_VALUE = "PREFERENCE_LAST_EXPORT_TIME_DEFAULT_VALUE"; + private static final String PREFERENCE_LAST_EXPORT_TIME_KEY = "last_export_time"; + + /** + * Set the last export time in UTC time zone. + * A new export operations will fetch transactions based on this value. + * + * @param lastExportTime the last export time to set. + */ + public static void setLastExportTime(Timestamp lastExportTime) { + final String utcString = TimestampHelper.getUtcStringForTimestamp(lastExportTime); + Log.d(LOG_TAG, "Storing '" + utcString + "' as lastExportTime in Android Preferences."); + PreferenceManager.getDefaultSharedPreferences(GnuCashApplication.getAppContext()) + .edit() + .putString(PREFERENCE_LAST_EXPORT_TIME_KEY, utcString) + .apply(); + } + + /** + * Get the time for the last export operation. + * + * @return A {@link Timestamp} with the time. + */ + public static Timestamp getLastExportTime() { + final String utcString = PreferenceManager.getDefaultSharedPreferences(GnuCashApplication.getAppContext()) + .getString(PREFERENCE_LAST_EXPORT_TIME_KEY, PREFERENCE_LAST_EXPORT_TIME_DEFAULT_VALUE); + Log.d(LOG_TAG, "Retrieving '" + utcString + "' as lastExportTime from Android Preferences."); + return PREFERENCE_LAST_EXPORT_TIME_DEFAULT_VALUE.equals(utcString) ? + TimestampHelper.getTimestampForEpochZero() : TimestampHelper.getTimestampForUtcString(utcString); + } +} \ No newline at end of file diff --git a/app/src/main/java/org/gnucash/android/util/TimestampHelper.java b/app/src/main/java/org/gnucash/android/util/TimestampHelper.java new file mode 100644 index 000000000..ca021ca65 --- /dev/null +++ b/app/src/main/java/org/gnucash/android/util/TimestampHelper.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2016 Alceu Rodrigues Neto + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gnucash.android.util; + +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; + +import java.sql.Timestamp; + +/** + * A utility class to deal with {@link Timestamp} operations in a centralized way. + */ +public final class TimestampHelper { + + /** + * Should be not instantiated. + */ + private TimestampHelper() {} + + /** + * We are using Joda Time classes because they are thread-safe. + */ + private static final DateTimeZone UTC_TIME_ZONE = DateTimeZone.forID("UTC"); + private static final DateTimeFormatter UTC_DATE_FORMAT = + DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss"); + private static final DateTimeFormatter UTC_DATE_WITH_MILLISECONDS_FORMAT = + DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS"); + + /** + * Get a {@link String} representing the {@link Timestamp} + * in UTC time zone and 'yyyy-MM-dd HH:mm:ss.SSS' format. + * + * @param timestamp The {@link Timestamp} to format. + * @return The formatted {@link String}. + */ + public static String getUtcStringForTimestamp(Timestamp timestamp) { + return UTC_DATE_WITH_MILLISECONDS_FORMAT.withZone(UTC_TIME_ZONE).print(timestamp.getTime()); + } + + /** + * @return A {@link Timestamp} with time in milliseconds equals to zero. + */ + public static Timestamp getTimestampForEpochZero() { + return new Timestamp(0); + } + + /** + * Get the {@link Timestamp} with the value of given UTC {@link String}. + * The {@link String} should be a representation in UTC time zone with the following format + * 'yyyy-MM-dd HH:mm:ss.SSS' OR 'yyyy-MM-dd HH:mm:ss' otherwise an IllegalArgumentException + * will be throw. + * + * @param utcString A {@link String} in UTC. + * @return A {@link Timestamp} for given utcString. + */ + public static Timestamp getTimestampForUtcString(String utcString) { + DateTime dateTime; + try { + + dateTime = UTC_DATE_WITH_MILLISECONDS_FORMAT.withZone(UTC_TIME_ZONE).parseDateTime(utcString); + return new Timestamp(dateTime.getMillis()); + + } catch (IllegalArgumentException firstException) { + try { + // In case of parsing of string without milliseconds. + dateTime = UTC_DATE_FORMAT.withZone(UTC_TIME_ZONE).parseDateTime(utcString); + return new Timestamp(dateTime.getMillis()); + + } catch (IllegalArgumentException secondException) { + // If we are here: + // - The utcString has an invalid format OR + // - We are missing some relevant pattern. + throw new IllegalArgumentException("Unknown utcString = '" + utcString + "'.", secondException); + } + } + } + + /** + * @return A {@link Timestamp} initialized with the system current time. + */ + public static Timestamp getTimestampForNow() { + return new Timestamp(System.currentTimeMillis()); + } +} \ No newline at end of file diff --git a/app/src/test/java/org/gnucash/android/test/unit/db/AccountsDbAdapterTest.java b/app/src/test/java/org/gnucash/android/test/unit/db/AccountsDbAdapterTest.java index afb7f1c56..06a8d021e 100644 --- a/app/src/test/java/org/gnucash/android/test/unit/db/AccountsDbAdapterTest.java +++ b/app/src/test/java/org/gnucash/android/test/unit/db/AccountsDbAdapterTest.java @@ -18,9 +18,9 @@ import org.gnucash.android.model.Split; import org.gnucash.android.model.Transaction; import org.gnucash.android.model.TransactionType; -import org.gnucash.android.test.unit.util.GnucashTestRunner; -import org.gnucash.android.test.unit.util.ShadowCrashlytics; -import org.gnucash.android.test.unit.util.ShadowUserVoice; +import org.gnucash.android.test.unit.testutil.GnucashTestRunner; +import org.gnucash.android.test.unit.testutil.ShadowCrashlytics; +import org.gnucash.android.test.unit.testutil.ShadowUserVoice; import org.junit.After; import org.junit.Assert; import org.junit.Before; diff --git a/app/src/test/java/org/gnucash/android/test/unit/db/MigrationHelperTest.java b/app/src/test/java/org/gnucash/android/test/unit/db/MigrationHelperTest.java new file mode 100644 index 000000000..517b4c47c --- /dev/null +++ b/app/src/test/java/org/gnucash/android/test/unit/db/MigrationHelperTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2016 Alceu Rodrigues Neto + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gnucash.android.test.unit.db; + +import org.gnucash.android.BuildConfig; +import org.gnucash.android.db.MigrationHelper; +import org.gnucash.android.test.unit.testutil.GnucashTestRunner; +import org.gnucash.android.test.unit.testutil.ShadowCrashlytics; +import org.gnucash.android.test.unit.testutil.ShadowUserVoice; +import org.gnucash.android.util.TimestampHelper; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +import java.sql.Timestamp; +import java.util.TimeZone; + +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(GnucashTestRunner.class) +@Config(constants = BuildConfig.class, sdk = 21, packageName = "org.gnucash.android", shadows = {ShadowCrashlytics.class, ShadowUserVoice.class}) +public class MigrationHelperTest { + @Test + public void shouldSubtractTimeZoneOffset() { + /** + * The values used here are well known. + * See https://en.wikipedia.org/wiki/Unix_time#Notable_events_in_Unix_time + * for details. + */ + + final long unixBillennium = 1_000_000_000 * 1000L; + final Timestamp unixBillenniumTimestamp = new Timestamp(unixBillennium); + final String unixBillenniumUtcString = "2001-09-09 01:46:40.000"; + final String unixBillenniumUtcStringAfterSubtract = "2001-09-09 00:46:40.000"; + + TimeZone timeZone = TimeZone.getTimeZone("GMT-1:00"); + Timestamp result = MigrationHelper.subtractTimeZoneOffset(unixBillenniumTimestamp, timeZone); + assertThat(TimestampHelper.getUtcStringForTimestamp(result)) + .isEqualTo(unixBillenniumUtcStringAfterSubtract); + + timeZone = TimeZone.getTimeZone("GMT+1:00"); + result = MigrationHelper.subtractTimeZoneOffset(unixBillenniumTimestamp, timeZone); + assertThat(TimestampHelper.getUtcStringForTimestamp(result)) + .isEqualTo(unixBillenniumUtcStringAfterSubtract); + + timeZone = TimeZone.getTimeZone("GMT+0:00"); + result = MigrationHelper.subtractTimeZoneOffset(unixBillenniumTimestamp, timeZone); + assertThat(TimestampHelper.getUtcStringForTimestamp(result)) + .isEqualTo(unixBillenniumUtcString); + } +} \ No newline at end of file diff --git a/app/src/test/java/org/gnucash/android/test/unit/db/SplitsDbAdapterTest.java b/app/src/test/java/org/gnucash/android/test/unit/db/SplitsDbAdapterTest.java index 1685ee242..9f2f1c20d 100644 --- a/app/src/test/java/org/gnucash/android/test/unit/db/SplitsDbAdapterTest.java +++ b/app/src/test/java/org/gnucash/android/test/unit/db/SplitsDbAdapterTest.java @@ -11,9 +11,9 @@ import org.gnucash.android.model.Money; import org.gnucash.android.model.Split; import org.gnucash.android.model.Transaction; -import org.gnucash.android.test.unit.util.GnucashTestRunner; -import org.gnucash.android.test.unit.util.ShadowCrashlytics; -import org.gnucash.android.test.unit.util.ShadowUserVoice; +import org.gnucash.android.test.unit.testutil.GnucashTestRunner; +import org.gnucash.android.test.unit.testutil.ShadowCrashlytics; +import org.gnucash.android.test.unit.testutil.ShadowUserVoice; import org.junit.After; import org.junit.Before; import org.junit.Test; diff --git a/app/src/test/java/org/gnucash/android/test/unit/db/TransactionsDbAdapterTest.java b/app/src/test/java/org/gnucash/android/test/unit/db/TransactionsDbAdapterTest.java index 6b5432c77..caf0fa3b7 100644 --- a/app/src/test/java/org/gnucash/android/test/unit/db/TransactionsDbAdapterTest.java +++ b/app/src/test/java/org/gnucash/android/test/unit/db/TransactionsDbAdapterTest.java @@ -10,9 +10,9 @@ import org.gnucash.android.model.Money; import org.gnucash.android.model.Split; import org.gnucash.android.model.Transaction; -import org.gnucash.android.test.unit.util.GnucashTestRunner; -import org.gnucash.android.test.unit.util.ShadowCrashlytics; -import org.gnucash.android.test.unit.util.ShadowUserVoice; +import org.gnucash.android.test.unit.testutil.GnucashTestRunner; +import org.gnucash.android.test.unit.testutil.ShadowCrashlytics; +import org.gnucash.android.test.unit.testutil.ShadowUserVoice; import org.junit.After; import org.junit.Before; import org.junit.Test; diff --git a/app/src/test/java/org/gnucash/android/test/unit/export/BackupTest.java b/app/src/test/java/org/gnucash/android/test/unit/export/BackupTest.java index 411299b5d..67418f772 100644 --- a/app/src/test/java/org/gnucash/android/test/unit/export/BackupTest.java +++ b/app/src/test/java/org/gnucash/android/test/unit/export/BackupTest.java @@ -21,9 +21,9 @@ import org.gnucash.android.export.Exporter; import org.gnucash.android.export.xml.GncXmlExporter; import org.gnucash.android.test.unit.db.AccountsDbAdapterTest; -import org.gnucash.android.test.unit.util.GnucashTestRunner; -import org.gnucash.android.test.unit.util.ShadowCrashlytics; -import org.gnucash.android.test.unit.util.ShadowUserVoice; +import org.gnucash.android.test.unit.testutil.GnucashTestRunner; +import org.gnucash.android.test.unit.testutil.ShadowCrashlytics; +import org.gnucash.android.test.unit.testutil.ShadowUserVoice; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/app/src/test/java/org/gnucash/android/test/unit/model/AccountTest.java b/app/src/test/java/org/gnucash/android/test/unit/model/AccountTest.java index adbbbb11e..1c378c789 100644 --- a/app/src/test/java/org/gnucash/android/test/unit/model/AccountTest.java +++ b/app/src/test/java/org/gnucash/android/test/unit/model/AccountTest.java @@ -20,9 +20,9 @@ import org.gnucash.android.model.Commodity; import org.gnucash.android.model.Money; import org.gnucash.android.model.Transaction; -import org.gnucash.android.test.unit.util.GnucashTestRunner; -import org.gnucash.android.test.unit.util.ShadowCrashlytics; -import org.gnucash.android.test.unit.util.ShadowUserVoice; +import org.gnucash.android.test.unit.testutil.GnucashTestRunner; +import org.gnucash.android.test.unit.testutil.ShadowCrashlytics; +import org.gnucash.android.test.unit.testutil.ShadowUserVoice; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.annotation.Config; diff --git a/app/src/test/java/org/gnucash/android/test/unit/model/MoneyTest.java b/app/src/test/java/org/gnucash/android/test/unit/model/MoneyTest.java index fd5eb6a81..02408af7b 100644 --- a/app/src/test/java/org/gnucash/android/test/unit/model/MoneyTest.java +++ b/app/src/test/java/org/gnucash/android/test/unit/model/MoneyTest.java @@ -19,9 +19,9 @@ import org.gnucash.android.BuildConfig; import org.gnucash.android.model.Commodity; import org.gnucash.android.model.Money; -import org.gnucash.android.test.unit.util.GnucashTestRunner; -import org.gnucash.android.test.unit.util.ShadowCrashlytics; -import org.gnucash.android.test.unit.util.ShadowUserVoice; +import org.gnucash.android.test.unit.testutil.GnucashTestRunner; +import org.gnucash.android.test.unit.testutil.ShadowCrashlytics; +import org.gnucash.android.test.unit.testutil.ShadowUserVoice; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/app/src/test/java/org/gnucash/android/test/unit/model/SplitTest.java b/app/src/test/java/org/gnucash/android/test/unit/model/SplitTest.java index 35a0af487..262c6cdae 100644 --- a/app/src/test/java/org/gnucash/android/test/unit/model/SplitTest.java +++ b/app/src/test/java/org/gnucash/android/test/unit/model/SplitTest.java @@ -6,15 +6,14 @@ import org.gnucash.android.model.Split; import org.gnucash.android.model.Transaction; import org.gnucash.android.model.TransactionType; -import org.gnucash.android.test.unit.util.GnucashTestRunner; -import org.gnucash.android.test.unit.util.ShadowCrashlytics; -import org.gnucash.android.test.unit.util.ShadowUserVoice; +import org.gnucash.android.test.unit.testutil.GnucashTestRunner; +import org.gnucash.android.test.unit.testutil.ShadowCrashlytics; +import org.gnucash.android.test.unit.testutil.ShadowUserVoice; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.annotation.Config; import java.math.BigDecimal; -import java.util.Currency; import static org.assertj.core.api.Assertions.assertThat; diff --git a/app/src/test/java/org/gnucash/android/test/unit/model/TransactionTest.java b/app/src/test/java/org/gnucash/android/test/unit/model/TransactionTest.java index 486f17931..fa2e9b09c 100644 --- a/app/src/test/java/org/gnucash/android/test/unit/model/TransactionTest.java +++ b/app/src/test/java/org/gnucash/android/test/unit/model/TransactionTest.java @@ -5,9 +5,9 @@ import org.gnucash.android.model.Money; import org.gnucash.android.model.Split; import org.gnucash.android.model.Transaction; -import org.gnucash.android.test.unit.util.GnucashTestRunner; -import org.gnucash.android.test.unit.util.ShadowCrashlytics; -import org.gnucash.android.test.unit.util.ShadowUserVoice; +import org.gnucash.android.test.unit.testutil.GnucashTestRunner; +import org.gnucash.android.test.unit.testutil.ShadowCrashlytics; +import org.gnucash.android.test.unit.testutil.ShadowUserVoice; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.annotation.Config; diff --git a/app/src/test/java/org/gnucash/android/test/unit/util/GnucashTestRunner.java b/app/src/test/java/org/gnucash/android/test/unit/testutil/GnucashTestRunner.java similarity index 94% rename from app/src/test/java/org/gnucash/android/test/unit/util/GnucashTestRunner.java rename to app/src/test/java/org/gnucash/android/test/unit/testutil/GnucashTestRunner.java index 48c754628..635d3ce2f 100644 --- a/app/src/test/java/org/gnucash/android/test/unit/util/GnucashTestRunner.java +++ b/app/src/test/java/org/gnucash/android/test/unit/testutil/GnucashTestRunner.java @@ -1,4 +1,4 @@ -package org.gnucash.android.test.unit.util; +package org.gnucash.android.test.unit.testutil; import com.crashlytics.android.Crashlytics; import com.uservoice.uservoicesdk.UserVoice; diff --git a/app/src/test/java/org/gnucash/android/test/unit/util/ShadowCrashlytics.java b/app/src/test/java/org/gnucash/android/test/unit/testutil/ShadowCrashlytics.java similarity index 91% rename from app/src/test/java/org/gnucash/android/test/unit/util/ShadowCrashlytics.java rename to app/src/test/java/org/gnucash/android/test/unit/testutil/ShadowCrashlytics.java index 5d8dcfafc..44a12c35e 100644 --- a/app/src/test/java/org/gnucash/android/test/unit/util/ShadowCrashlytics.java +++ b/app/src/test/java/org/gnucash/android/test/unit/testutil/ShadowCrashlytics.java @@ -1,4 +1,4 @@ -package org.gnucash.android.test.unit.util; +package org.gnucash.android.test.unit.testutil; import android.content.Context; diff --git a/app/src/test/java/org/gnucash/android/test/unit/util/ShadowUserVoice.java b/app/src/test/java/org/gnucash/android/test/unit/testutil/ShadowUserVoice.java similarity index 90% rename from app/src/test/java/org/gnucash/android/test/unit/util/ShadowUserVoice.java rename to app/src/test/java/org/gnucash/android/test/unit/testutil/ShadowUserVoice.java index 31f3f476c..b655e0c4c 100644 --- a/app/src/test/java/org/gnucash/android/test/unit/util/ShadowUserVoice.java +++ b/app/src/test/java/org/gnucash/android/test/unit/testutil/ShadowUserVoice.java @@ -1,4 +1,4 @@ -package org.gnucash.android.test.unit.util; +package org.gnucash.android.test.unit.testutil; import android.content.Context; diff --git a/app/src/test/java/org/gnucash/android/test/unit/util/PreferencesHelperTest.java b/app/src/test/java/org/gnucash/android/test/unit/util/PreferencesHelperTest.java new file mode 100644 index 000000000..ceb7d0ae9 --- /dev/null +++ b/app/src/test/java/org/gnucash/android/test/unit/util/PreferencesHelperTest.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2016 Alceu Rodrigues Neto + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gnucash.android.test.unit.util; + +import org.gnucash.android.BuildConfig; +import org.gnucash.android.test.unit.testutil.GnucashTestRunner; +import org.gnucash.android.test.unit.testutil.ShadowCrashlytics; +import org.gnucash.android.test.unit.testutil.ShadowUserVoice; +import org.gnucash.android.util.PreferencesHelper; +import org.gnucash.android.util.TimestampHelper; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +import java.sql.Timestamp; + +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(GnucashTestRunner.class) +@Config(constants = BuildConfig.class, sdk = 21, packageName = "org.gnucash.android", shadows = {ShadowCrashlytics.class, ShadowUserVoice.class}) +public class PreferencesHelperTest { + + @Test + public void shouldGetLastExportTimeDefaultValue() { + final Timestamp lastExportTime = PreferencesHelper.getLastExportTime(); + assertThat(lastExportTime).isEqualTo(TimestampHelper.getTimestampForEpochZero()); + } + + @Test + public void shouldGetLastExportTimeCurrentValue() { + final long goldenBoyBirthday = 1190136000L * 1000; + final Timestamp goldenBoyBirthdayTimestamp = new Timestamp(goldenBoyBirthday); + PreferencesHelper.setLastExportTime(goldenBoyBirthdayTimestamp); + assertThat(PreferencesHelper.getLastExportTime()) + .isEqualTo(goldenBoyBirthdayTimestamp); + } +} \ No newline at end of file diff --git a/app/src/test/java/org/gnucash/android/test/unit/util/TimestampHelperTest.java b/app/src/test/java/org/gnucash/android/test/unit/util/TimestampHelperTest.java new file mode 100644 index 000000000..2c9d1091f --- /dev/null +++ b/app/src/test/java/org/gnucash/android/test/unit/util/TimestampHelperTest.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2016 Alceu Rodrigues Neto + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gnucash.android.test.unit.util; + +import org.gnucash.android.BuildConfig; +import org.gnucash.android.test.unit.testutil.GnucashTestRunner; +import org.gnucash.android.test.unit.testutil.ShadowCrashlytics; +import org.gnucash.android.test.unit.testutil.ShadowUserVoice; +import org.gnucash.android.util.TimestampHelper; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +import java.sql.Timestamp; + +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(GnucashTestRunner.class) +@Config(constants = BuildConfig.class, sdk = 21, packageName = "org.gnucash.android", shadows = {ShadowCrashlytics.class, ShadowUserVoice.class}) +public class TimestampHelperTest { + + @Test + public void shouldGetUtcStringForTimestamp() { + /** + * The values used here are well known. + * See https://en.wikipedia.org/wiki/Unix_time#Notable_events_in_Unix_time + * for details. + */ + + final long unixBillennium = 1_000_000_000 * 1000L; + final String unixBillenniumUtcString = "2001-09-09 01:46:40.000"; + final Timestamp unixBillenniumTimestamp = new Timestamp(unixBillennium); + assertThat(TimestampHelper.getUtcStringForTimestamp(unixBillenniumTimestamp)) + .isEqualTo(unixBillenniumUtcString); + + final long the1234567890thSecond = 1234567890 * 1000L; + final String the1234567890thSecondUtcString = "2009-02-13 23:31:30.000"; + final Timestamp the1234567890thSecondTimestamp = new Timestamp(the1234567890thSecond); + assertThat(TimestampHelper.getUtcStringForTimestamp(the1234567890thSecondTimestamp)) + .isEqualTo(the1234567890thSecondUtcString); + } + + @Test + public void shouldGetTimestampForEpochZero() { + Timestamp epochZero = TimestampHelper.getTimestampForEpochZero(); + assertThat(epochZero.getTime()).isZero(); + } + + @Test + public void shouldGetTimestampForUtcString() { + final long unixBillennium = 1_000_000_000 * 1000L; + final String unixBillenniumUtcString = "2001-09-09 01:46:40"; + final String unixBillenniumWithMillisecondsUtcString = "2001-09-09 01:46:40.000"; + final Timestamp unixBillenniumTimestamp = new Timestamp(unixBillennium); + assertThat(TimestampHelper.getTimestampForUtcString(unixBillenniumUtcString)) + .isEqualTo(unixBillenniumTimestamp); + assertThat(TimestampHelper.getTimestampForUtcString(unixBillenniumWithMillisecondsUtcString)) + .isEqualTo(unixBillenniumTimestamp); + + final long the1234567890thSecond = 1234567890 * 1000L; + final String the1234567890thSecondUtcString = "2009-02-13 23:31:30"; + final String the1234567890thSecondWithMillisecondsUtcString = "2009-02-13 23:31:30.000"; + final Timestamp the1234567890thSecondTimestamp = new Timestamp(the1234567890thSecond); + assertThat(TimestampHelper.getTimestampForUtcString(the1234567890thSecondUtcString)) + .isEqualTo(the1234567890thSecondTimestamp); + assertThat(TimestampHelper.getTimestampForUtcString(the1234567890thSecondWithMillisecondsUtcString)) + .isEqualTo(the1234567890thSecondTimestamp); + } + + @Test + public void shouldGetTimestampForNow() { + final long before = System.currentTimeMillis(); + final long now = TimestampHelper.getTimestampForNow().getTime(); + final long after = System.currentTimeMillis(); + assertThat(now).isGreaterThanOrEqualTo(before) + .isLessThanOrEqualTo(after); + } +} \ No newline at end of file