mirror of
https://github.com/iSoron/uhabits.git
synced 2025-12-06 01:08:50 -06:00
Merge pull request #173 from JotraN/dev
Add exporting scores and checkmarks files with multiple habits (#68).
This commit is contained in:
@@ -75,6 +75,8 @@ public class HabitsCSVExporterTest extends BaseAndroidTest
|
||||
assertPathExists("001 Wake up early/Scores.csv");
|
||||
assertPathExists("002 Meditate/Checkmarks.csv");
|
||||
assertPathExists("002 Meditate/Scores.csv");
|
||||
assertPathExists("Checkmarks.csv");
|
||||
assertPathExists("Scores.csv");
|
||||
}
|
||||
|
||||
private void assertAbsolutePathExists(String s)
|
||||
|
||||
@@ -101,6 +101,30 @@ public class SQLiteScoreListTest extends BaseAndroidTest
|
||||
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()
|
||||
{
|
||||
return new Select()
|
||||
@@ -109,5 +133,4 @@ public class SQLiteScoreListTest extends BaseAndroidTest
|
||||
.orderBy("timestamp desc")
|
||||
.execute();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -41,6 +41,10 @@ public class HabitsCSVExporter
|
||||
private List<String> generateFilenames;
|
||||
|
||||
private String exportDirName;
|
||||
/**
|
||||
* Delimiter used in a CSV file.
|
||||
*/
|
||||
private final String DELIMITER = ",";
|
||||
|
||||
@NonNull
|
||||
private final HabitList allHabits;
|
||||
@@ -102,16 +106,6 @@ public class HabitsCSVExporter
|
||||
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
|
||||
{
|
||||
String filename = "Habits.csv";
|
||||
@@ -134,6 +128,8 @@ public class HabitsCSVExporter
|
||||
writeScores(habitDirName, h.getScores());
|
||||
writeCheckmarks(habitDirName, h.getCheckmarks());
|
||||
}
|
||||
|
||||
writeMultipleHabits();
|
||||
}
|
||||
|
||||
private void writeScores(String habitDirName, ScoreList scores)
|
||||
@@ -146,6 +142,118 @@ public class HabitsCSVExporter
|
||||
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
|
||||
{
|
||||
SimpleDateFormat dateFormat = DateFormats.getCSVDateFormat();
|
||||
|
||||
@@ -107,6 +107,16 @@ public abstract class RepetitionList
|
||||
*/
|
||||
@Nullable
|
||||
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
|
||||
|
||||
@@ -89,6 +89,46 @@ public abstract class ScoreList implements Iterable<Score>
|
||||
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)
|
||||
{
|
||||
computeAll();
|
||||
|
||||
@@ -92,6 +92,26 @@ public class MemoryRepetitionList extends RepetitionList
|
||||
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
|
||||
public void remove(@NonNull Repetition repetition)
|
||||
{
|
||||
|
||||
@@ -43,6 +43,21 @@ public class MemoryScoreList extends ScoreList
|
||||
(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
|
||||
@Override
|
||||
public Score getComputedByTimestamp(long timestamp)
|
||||
|
||||
@@ -125,6 +125,24 @@ public class SQLiteRepetitionList extends RepetitionList
|
||||
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
|
||||
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
|
||||
@Nullable
|
||||
public Score getComputedByTimestamp(long timestamp)
|
||||
@@ -127,11 +150,7 @@ public class SQLiteScoreList extends ScoreList
|
||||
List<ScoreRecord> records = sqlite.query(query, params);
|
||||
for (ScoreRecord record : records) record.habit = habitRecord;
|
||||
|
||||
List<Score> scores = new LinkedList<>();
|
||||
for (ScoreRecord rec : records)
|
||||
scores.add(rec.toScore());
|
||||
|
||||
return scores;
|
||||
return toScores(records);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -177,4 +196,12 @@ public class SQLiteScoreList extends ScoreList
|
||||
record.habit = habitRecord;
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
* 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));
|
||||
}
|
||||
|
||||
@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)
|
||||
{
|
||||
RepetitionList reps = habit.getRepetitions();
|
||||
|
||||
@@ -134,4 +134,14 @@ public class DateUtilsTest extends BaseUnitTest
|
||||
assertThat(DateUtils.truncate(field, t1), 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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user