Add exporting scores and checkmarks files with multiple habits (#68).

Add writeMultipleHabits to write a scores file and a checkmarks file
that contains scores and checkmarks of multiple habits.
Add getTimeframe because it was necessary to get the timeframe between
all habits so that row data could be populated correctly for habits that
started before/after other habits.
Move writeCheckmarks to below writeScores since it wasn't called until
after writeScores in writeHabits.

Add getByInterval to ScoreList, MemoryScoreList, and SQLiteScoreList to
get scores between a given interval - simiarly to CheckmarkList.

Add getValues (ScoreList) to get all values between a given timeframe.

Add getNewest to get the newest repetition in a list and to correspond
with getOldest.

Add getDaysBetween to DateUtils to quickly get the number of days
between two timestamps.

Add tests for the new functions.
pull/173/head
jotran 9 years ago
parent 4d5407a5cc
commit ec34043041

@ -75,6 +75,8 @@ public class HabitsCSVExporterTest extends BaseAndroidTest
assertPathExists("001 Wake up early/Scores.csv"); assertPathExists("001 Wake up early/Scores.csv");
assertPathExists("002 Meditate/Checkmarks.csv"); assertPathExists("002 Meditate/Checkmarks.csv");
assertPathExists("002 Meditate/Scores.csv"); assertPathExists("002 Meditate/Scores.csv");
assertPathExists("Checkmarks.csv");
assertPathExists("Scores.csv");
} }
private void assertAbsolutePathExists(String s) private void assertAbsolutePathExists(String s)

@ -101,6 +101,30 @@ public class SQLiteScoreListTest extends BaseAndroidTest
assertThat(records.get(0).timestamp, equalTo(today)); assertThat(records.get(0).timestamp, equalTo(today));
} }
@Test
public void testGetByInterval()
{
long from = today - 10 * day;
long to = today - 3 * day;
List<Score> list = scores.getByInterval(from, to);
assertThat(list.size(), equalTo(8));
assertThat(list.get(0).getTimestamp(), equalTo(today - 3 * day));
assertThat(list.get(3).getTimestamp(), equalTo(today - 6 * day));
assertThat(list.get(7).getTimestamp(), equalTo(today - 10 * day));
}
@Test
public void testGetByInterval_withLongInterval()
{
long from = today - 200 * day;
long to = today;
List<Score> list = scores.getByInterval(from, to);
assertThat(list.size(), equalTo(201));
}
private List<ScoreRecord> getAllRecords() private List<ScoreRecord> getAllRecords()
{ {
return new Select() return new Select()
@ -109,5 +133,4 @@ public class SQLiteScoreListTest extends BaseAndroidTest
.orderBy("timestamp desc") .orderBy("timestamp desc")
.execute(); .execute();
} }
} }

@ -41,6 +41,10 @@ public class HabitsCSVExporter
private List<String> generateFilenames; private List<String> generateFilenames;
private String exportDirName; private String exportDirName;
/**
* Delimiter used in a CSV file.
*/
private final String DELIMITER = ",";
@NonNull @NonNull
private final HabitList allHabits; private final HabitList allHabits;
@ -102,16 +106,6 @@ public class HabitsCSVExporter
return s.substring(0, Math.min(s.length(), 100)); return s.substring(0, Math.min(s.length(), 100));
} }
private void writeCheckmarks(String habitDirName, CheckmarkList checkmarks)
throws IOException
{
String filename = habitDirName + "Checkmarks.csv";
FileWriter out = new FileWriter(exportDirName + filename);
generateFilenames.add(filename);
checkmarks.writeCSV(out);
out.close();
}
private void writeHabits() throws IOException private void writeHabits() throws IOException
{ {
String filename = "Habits.csv"; String filename = "Habits.csv";
@ -134,6 +128,8 @@ public class HabitsCSVExporter
writeScores(habitDirName, h.getScores()); writeScores(habitDirName, h.getScores());
writeCheckmarks(habitDirName, h.getCheckmarks()); writeCheckmarks(habitDirName, h.getCheckmarks());
} }
writeMultipleHabits();
} }
private void writeScores(String habitDirName, ScoreList scores) private void writeScores(String habitDirName, ScoreList scores)
@ -146,6 +142,118 @@ public class HabitsCSVExporter
out.close(); out.close();
} }
private void writeCheckmarks(String habitDirName, CheckmarkList checkmarks)
throws IOException
{
String filename = habitDirName + "Checkmarks.csv";
FileWriter out = new FileWriter(exportDirName + filename);
generateFilenames.add(filename);
checkmarks.writeCSV(out);
out.close();
}
/**
* Writes a scores file and a checkmarks file containing scores and checkmarks of every habit.
* The first column corresponds to the date. Subsequent columns correspond to a habit.
* Habits are taken from the list of selected habits.
* Dates are determined from the oldest repetition date to the newest repetition date found in
* the list of habits.
*
* @throws IOException if there was problem writing the files
*/
private void writeMultipleHabits() throws IOException
{
String scoresFileName = "Scores.csv";
String checksFileName = "Checkmarks.csv";
generateFilenames.add(scoresFileName);
generateFilenames.add(checksFileName);
FileWriter scoresWriter = new FileWriter(exportDirName + scoresFileName);
FileWriter checksWriter = new FileWriter(exportDirName + checksFileName);
writeMultipleHabitsHeader(scoresWriter);
writeMultipleHabitsHeader(checksWriter);
long[] timeframe = getTimeframe();
long oldest = timeframe[0];
long newest = DateUtils.getStartOfToday();
List<int[]> checkmarks = new ArrayList<>();
List<int[]> scores = new ArrayList<>();
for (Habit h : selectedHabits)
{
checkmarks.add(h.getCheckmarks().getValues(oldest, newest));
scores.add(h.getScores().getValues(oldest, newest));
}
int days = DateUtils.getDaysBetween(oldest, newest);
SimpleDateFormat dateFormat = DateFormats.getCSVDateFormat();
for (int i = 0; i <= days; i++)
{
Date day = new Date(newest - i * DateUtils.millisecondsInOneDay);
String date = dateFormat.format(day);
StringBuilder sb = new StringBuilder();
sb.append(date).append(DELIMITER);
checksWriter.write(sb.toString());
scoresWriter.write(sb.toString());
for(int j = 0; j < selectedHabits.size(); j++)
{
checksWriter.write(String.valueOf(checkmarks.get(j)[i]));
checksWriter.write(DELIMITER);
String score =
String.format("%.4f", ((float) scores.get(j)[i]) / Score.MAX_VALUE);
scoresWriter.write(score);
scoresWriter.write(DELIMITER);
}
checksWriter.write("\n");
scoresWriter.write("\n");
}
scoresWriter.close();
checksWriter.close();
}
/**
* Writes the first row, containing header information, using the given writer.
* This consists of the date title and the names of the selected habits.
*
* @param out the writer to use
* @throws IOException if there was a problem writing
*/
private void writeMultipleHabitsHeader(Writer out) throws IOException
{
out.write("Date" + DELIMITER);
for (Habit h : selectedHabits) {
out.write(h.getName());
out.write(DELIMITER);
}
out.write("\n");
}
/**
* Gets the overall timeframe of the selected habits.
* The timeframe is an array containing the oldest timestamp among the habits and the
* newest timestamp among the habits.
* Both timestamps are in milliseconds.
*
* @return the timeframe containing the oldest timestamp and the newest timestamp
*/
private long[] getTimeframe()
{
long oldest = Long.MAX_VALUE;
long newest = -1;
for (Habit h : selectedHabits)
{
if(h.getRepetitions().getOldest() == null || h.getRepetitions().getNewest() == null)
continue;
long currOld = h.getRepetitions().getOldest().getTimestamp();
long currNew = h.getRepetitions().getNewest().getTimestamp();
oldest = currOld > oldest ? oldest : currOld;
newest = currNew < newest ? newest : currNew;
}
return new long[]{oldest, newest};
}
private String writeZipFile() throws IOException private String writeZipFile() throws IOException
{ {
SimpleDateFormat dateFormat = DateFormats.getCSVDateFormat(); SimpleDateFormat dateFormat = DateFormats.getCSVDateFormat();

@ -107,6 +107,16 @@ public abstract class RepetitionList
*/ */
@Nullable @Nullable
public abstract Repetition getOldest(); public abstract Repetition getOldest();
@Nullable
/**
* Returns the newest repetition in the list.
* <p>
* If the list is empty, returns null. Repetitions in the past are
* discarded.
*
* @return newest repetition in the list, or null if list is empty.
*/
public abstract Repetition getNewest();
/** /**
* Returns the total number of repetitions for each month, from the first * Returns the total number of repetitions for each month, from the first

@ -89,6 +89,46 @@ public abstract class ScoreList implements Iterable<Score>
return s.getValue(); return s.getValue();
} }
/**
* Returns the list of scores that fall within the given interval.
* <p>
* There is exactly one score per day in the interval. The endpoints of
* the interval are included. The list is ordered by timestamp (decreasing).
* That is, the first score corresponds to the newest timestamp, and the
* last score corresponds to the oldest timestamp.
*
* @param fromTimestamp timestamp of the beginning of the interval.
* @param toTimestamp timestamp of the end of the interval.
* @return the list of scores within the interval.
*/
@NonNull
public abstract List<Score> getByInterval(long fromTimestamp,
long toTimestamp);
/**
* Returns the values of the scores that fall inside a certain interval
* of time.
* <p>
* The values are returned in an array containing one integer value for each
* day of the interval. The first entry corresponds to the most recent day
* in the interval. Each subsequent entry corresponds to one day older than
* the previous entry. The boundaries of the time interval are included.
*
* @param from timestamp for the oldest score
* @param to timestamp for the newest score
* @return values for the scores inside the given interval
*/
public final int[] getValues(long from, long to)
{
List<Score> scores = getByInterval(from, to);
int[] values = new int[scores.size()];
for(int i = 0; i < values.length; i++)
values[i] = scores.get(i).getValue();
return values;
}
public List<Score> groupBy(DateUtils.TruncateField field) public List<Score> groupBy(DateUtils.TruncateField field)
{ {
computeAll(); computeAll();

@ -92,6 +92,26 @@ public class MemoryRepetitionList extends RepetitionList
return oldestRep; return oldestRep;
} }
@Nullable
@Override
public Repetition getNewest()
{
long newestTime = -1;
Repetition newestRep = null;
for (Repetition rep : list)
{
if (rep.getTimestamp() > newestTime)
{
newestRep = rep;
newestTime = rep.getTimestamp();
}
}
return newestRep;
}
@Override @Override
public void remove(@NonNull Repetition repetition) public void remove(@NonNull Repetition repetition)
{ {

@ -43,6 +43,21 @@ public class MemoryScoreList extends ScoreList
(s1, s2) -> Long.signum(s2.getTimestamp() - s1.getTimestamp())); (s1, s2) -> Long.signum(s2.getTimestamp() - s1.getTimestamp()));
} }
@NonNull
@Override
public List<Score> getByInterval(long fromTimestamp, long toTimestamp)
{
compute(fromTimestamp, toTimestamp);
List<Score> filtered = new LinkedList<>();
for (Score s : list)
if (s.getTimestamp() >= fromTimestamp &&
s.getTimestamp() <= toTimestamp) filtered.add(s);
return filtered;
}
@Nullable @Nullable
@Override @Override
public Score getComputedByTimestamp(long timestamp) public Score getComputedByTimestamp(long timestamp)

@ -125,6 +125,24 @@ public class SQLiteRepetitionList extends RepetitionList
return record.toRepetition(); return record.toRepetition();
} }
@Override
public Repetition getNewest()
{
check(habit.getId());
String query = "select habit, timestamp " +
"from Repetitions " +
"where habit = ? " +
"order by timestamp desc " +
"limit 1";
String params[] = { Long.toString(habit.getId()) };
RepetitionRecord record = sqlite.querySingle(query, params);
if (record == null) return null;
record.habit = habitRecord;
return record.toRepetition();
}
@Override @Override
public void remove(@NonNull Repetition repetition) public void remove(@NonNull Repetition repetition)
{ {

@ -84,6 +84,29 @@ public class SQLiteScoreList extends ScoreList
} }
} }
@NonNull
@Override
public List<Score> getByInterval(long fromTimestamp, long toTimestamp)
{
check(habit.getId());
compute(fromTimestamp, toTimestamp);
String query = "select habit, timestamp, score " +
"from Score " +
"where habit = ? and timestamp >= ? and timestamp <= ? " +
"order by timestamp desc";
String params[] = {
Long.toString(habit.getId()),
Long.toString(fromTimestamp),
Long.toString(toTimestamp)
};
List<ScoreRecord> records = sqlite.query(query, params);
for (ScoreRecord record : records) record.habit = habitRecord;
return toScores(records);
}
@Override @Override
@Nullable @Nullable
public Score getComputedByTimestamp(long timestamp) public Score getComputedByTimestamp(long timestamp)
@ -127,11 +150,7 @@ public class SQLiteScoreList extends ScoreList
List<ScoreRecord> records = sqlite.query(query, params); List<ScoreRecord> records = sqlite.query(query, params);
for (ScoreRecord record : records) record.habit = habitRecord; for (ScoreRecord record : records) record.habit = habitRecord;
List<Score> scores = new LinkedList<>(); return toScores(records);
for (ScoreRecord rec : records)
scores.add(rec.toScore());
return scores;
} }
@Nullable @Nullable
@ -177,4 +196,12 @@ public class SQLiteScoreList extends ScoreList
record.habit = habitRecord; record.habit = habitRecord;
return record.toScore(); return record.toScore();
} }
@NonNull
private List<Score> toScores(@NonNull List<ScoreRecord> records)
{
List<Score> scores = new LinkedList<>();
for (ScoreRecord r : records) scores.add(r.toScore());
return scores;
}
} }

@ -272,4 +272,18 @@ public abstract class DateUtils
{ {
MONTH, WEEK_NUMBER, YEAR, QUARTER MONTH, WEEK_NUMBER, YEAR, QUARTER
} }
/**
* Gets the number of days between two timestamps (exclusively).
*
* @param t1 the first timestamp to use in milliseconds
* @param t2 the second timestamp to use in milliseconds
* @return the number of days between the two timestamps
*/
public static int getDaysBetween(long t1, long t2)
{
Date d1 = new Date(t1);
Date d2 = new Date(t2);
return (int) (Math.abs((d2.getTime() - d1.getTime()) / millisecondsInOneDay));
}
} }

@ -174,6 +174,27 @@ public class ScoreListTest extends BaseUnitTest
assertThat(writer.toString(), equalTo(expectedCSV)); assertThat(writer.toString(), equalTo(expectedCSV));
} }
@Test
public void test_getValues()
{
toggleRepetitions(0, 20);
long today = DateUtils.getStartOfToday();
long day = DateUtils.millisecondsInOneDay;
long from = today - 4 * day;
long to = today - 2 * day;
int[] expected = {
11883254,
11479288,
11053198,
};
int[] actual = habit.getScores().getValues(from, to);
assertThat(actual, equalTo(expected));
}
private void toggleRepetitions(final int from, final int to) private void toggleRepetitions(final int from, final int to)
{ {
RepetitionList reps = habit.getRepetitions(); RepetitionList reps = habit.getRepetitions();

@ -134,4 +134,14 @@ public class DateUtilsTest extends BaseUnitTest
assertThat(DateUtils.truncate(field, t1), equalTo(expected)); assertThat(DateUtils.truncate(field, t1), equalTo(expected));
assertThat(DateUtils.truncate(field, t2), equalTo(expected)); assertThat(DateUtils.truncate(field, t2), equalTo(expected));
} }
@Test
public void test_getDaysBetween()
{
long t1 = timestamp(2016, JANUARY, 1);
long t2 = timestamp(2016, DECEMBER, 31);
int expected = 365;
assertThat(DateUtils.getDaysBetween(t1, t2), equalTo(expected));
assertThat(DateUtils.getDaysBetween(t2, t1), equalTo(expected));
}
} }

Loading…
Cancel
Save