mirror of
https://github.com/iSoron/uhabits.git
synced 2025-12-06 09:08:52 -06:00
Move remaining model tests to JVM; simplify SQLite implementation
This commit is contained in:
@@ -35,6 +35,17 @@ android {
|
||||
targetCompatibility 1.8
|
||||
sourceCompatibility 1.8
|
||||
}
|
||||
|
||||
testOptions {
|
||||
unitTests.all {
|
||||
testLogging {
|
||||
events "passed", "skipped", "failed", "standardOut", "standardError"
|
||||
outputs.upToDateWhen {false}
|
||||
showStandardStreams = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -23,174 +23,11 @@ import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import org.isoron.uhabits.BaseAndroidTest;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.utils.DatabaseUtils;
|
||||
import org.isoron.uhabits.utils.DateUtils;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class ScoreListTest extends BaseAndroidTest
|
||||
{
|
||||
private Habit habit;
|
||||
|
||||
@Before
|
||||
public void setUp()
|
||||
{
|
||||
super.setUp();
|
||||
|
||||
habitFixtures.purgeHabits(habitList);
|
||||
habit = habitFixtures.createEmptyHabit();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown()
|
||||
{
|
||||
DateUtils.setFixedLocalTime(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_getAllValues_withGroups()
|
||||
{
|
||||
toggleRepetitions(0, 20);
|
||||
|
||||
int expectedValues[] = {11434978, 7894999, 3212362};
|
||||
|
||||
int actualValues[] = habit.getScores().getAllValues(7);
|
||||
assertThat(actualValues, equalTo(expectedValues));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_getAllValues_withoutGroups()
|
||||
{
|
||||
toggleRepetitions(0, 20);
|
||||
|
||||
int expectedValues[] = {
|
||||
12629351,
|
||||
12266245,
|
||||
11883254,
|
||||
11479288,
|
||||
11053198,
|
||||
10603773,
|
||||
10129735,
|
||||
9629735,
|
||||
9102352,
|
||||
8546087,
|
||||
7959357,
|
||||
7340494,
|
||||
6687738,
|
||||
5999234,
|
||||
5273023,
|
||||
4507040,
|
||||
3699107,
|
||||
2846927,
|
||||
1948077,
|
||||
1000000
|
||||
};
|
||||
|
||||
int actualValues[] = habit.getScores().getAllValues(1);
|
||||
assertThat(actualValues, equalTo(expectedValues));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_getTodayValue()
|
||||
{
|
||||
toggleRepetitions(0, 20);
|
||||
assertThat(habit.getScores().getTodayValue(), equalTo(12629351));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_getValue()
|
||||
{
|
||||
toggleRepetitions(0, 20);
|
||||
|
||||
int expectedValues[] = {
|
||||
12629351,
|
||||
12266245,
|
||||
11883254,
|
||||
11479288,
|
||||
11053198,
|
||||
10603773,
|
||||
10129735,
|
||||
9629735,
|
||||
9102352,
|
||||
8546087,
|
||||
7959357,
|
||||
7340494,
|
||||
6687738,
|
||||
5999234,
|
||||
5273023,
|
||||
4507040,
|
||||
3699107,
|
||||
2846927,
|
||||
1948077,
|
||||
1000000
|
||||
};
|
||||
|
||||
long current = DateUtils.getStartOfToday();
|
||||
for (int expectedValue : expectedValues)
|
||||
{
|
||||
assertThat(habit.getScores().getValue(current),
|
||||
equalTo(expectedValue));
|
||||
current -= DateUtils.millisecondsInOneDay;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_invalidateNewerThan()
|
||||
{
|
||||
assertThat(habit.getScores().getTodayValue(), equalTo(0));
|
||||
|
||||
toggleRepetitions(0, 2);
|
||||
assertThat(habit.getScores().getTodayValue(), equalTo(1948077));
|
||||
|
||||
habit.setFreqNum(1);
|
||||
habit.setFreqDen(2);
|
||||
habit.getScores().invalidateNewerThan(0);
|
||||
|
||||
assertThat(habit.getScores().getTodayValue(), equalTo(1974654));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_writeCSV() throws IOException
|
||||
{
|
||||
habitFixtures.purgeHabits(habitList);
|
||||
Habit habit = habitFixtures.createShortHabit();
|
||||
|
||||
String expectedCSV = "2015-01-16,0.0519\n" +
|
||||
"2015-01-17,0.1021\n" +
|
||||
"2015-01-18,0.0986\n" +
|
||||
"2015-01-19,0.0952\n" +
|
||||
"2015-01-20,0.1439\n" +
|
||||
"2015-01-21,0.1909\n" +
|
||||
"2015-01-22,0.2364\n" +
|
||||
"2015-01-23,0.2283\n" +
|
||||
"2015-01-24,0.2205\n" +
|
||||
"2015-01-25,0.2649\n";
|
||||
|
||||
StringWriter writer = new StringWriter();
|
||||
habit.getScores().writeCSV(writer);
|
||||
|
||||
assertThat(writer.toString(), equalTo(expectedCSV));
|
||||
}
|
||||
|
||||
private void toggleRepetitions(final int from, final int to)
|
||||
{
|
||||
DatabaseUtils.executeAsTransaction(() -> {
|
||||
long today = DateUtils.getStartOfToday();
|
||||
for (int i = from; i < to; i++)
|
||||
habit
|
||||
.getRepetitions()
|
||||
.toggleTimestamp(today - i * DateUtils.millisecondsInOneDay);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,8 +57,6 @@ public interface BaseComponent
|
||||
|
||||
void inject(ToggleRepetitionTask toggleRepetitionTask);
|
||||
|
||||
void inject(BaseDialogFragment baseDialogFragment);
|
||||
|
||||
void inject(HabitCardListCache habitCardListCache);
|
||||
|
||||
void inject(HabitBroadcastReceiver habitBroadcastReceiver);
|
||||
@@ -100,4 +98,6 @@ public interface BaseComponent
|
||||
void inject(AbstractImporter abstractImporter);
|
||||
|
||||
void inject(HabitsCSVExporter habitsCSVExporter);
|
||||
|
||||
void inject(BaseDialogFragment baseDialogFragment);
|
||||
}
|
||||
|
||||
@@ -49,11 +49,11 @@ public class Checkmark
|
||||
*/
|
||||
public static final int UNCHECKED = 0;
|
||||
|
||||
final Habit habit;
|
||||
private final Habit habit;
|
||||
|
||||
final long timestamp;
|
||||
private final long timestamp;
|
||||
|
||||
final int value;
|
||||
private final int value;
|
||||
|
||||
public Checkmark(Habit habit, long timestamp, int value)
|
||||
{
|
||||
@@ -62,6 +62,11 @@ public class Checkmark
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Habit getHabit()
|
||||
{
|
||||
return habit;
|
||||
}
|
||||
|
||||
public long getTimestamp()
|
||||
{
|
||||
return timestamp;
|
||||
|
||||
@@ -55,7 +55,7 @@ public abstract class HabitList
|
||||
*
|
||||
* @param habit the habit to be inserted
|
||||
*/
|
||||
public abstract void add(Habit habit);
|
||||
public abstract void add(@NonNull Habit habit);
|
||||
|
||||
/**
|
||||
* Returns the total number of unarchived habits.
|
||||
@@ -87,6 +87,7 @@ public abstract class HabitList
|
||||
* @param id the id of the habit
|
||||
* @return the habit, or null if none exist
|
||||
*/
|
||||
@Nullable
|
||||
public abstract Habit getById(long id);
|
||||
|
||||
/**
|
||||
@@ -136,7 +137,7 @@ public abstract class HabitList
|
||||
* @param h the habit
|
||||
* @return the index of the habit, or -1 if not in the list
|
||||
*/
|
||||
public abstract int indexOf(Habit h);
|
||||
public abstract int indexOf(@NonNull Habit h);
|
||||
|
||||
/**
|
||||
* Removes the given habit from the list.
|
||||
@@ -173,7 +174,7 @@ public abstract class HabitList
|
||||
*
|
||||
* @param habit the habit that has been modified.
|
||||
*/
|
||||
public void update(Habit habit)
|
||||
public void update(@NonNull Habit habit)
|
||||
{
|
||||
update(Collections.singletonList(habit));
|
||||
}
|
||||
@@ -187,7 +188,7 @@ public abstract class HabitList
|
||||
* @param out the writer that will receive the result
|
||||
* @throws IOException if write operations fail
|
||||
*/
|
||||
public void writeCSV(Writer out) throws IOException
|
||||
public void writeCSV(@NonNull Writer out) throws IOException
|
||||
{
|
||||
String header[] = {
|
||||
"Position",
|
||||
|
||||
@@ -189,9 +189,9 @@ public abstract class RepetitionList
|
||||
add(rep);
|
||||
}
|
||||
|
||||
// habit.getScores().invalidateNewerThan(timestamp);
|
||||
// habit.getCheckmarks().invalidateNewerThan(timestamp);
|
||||
// habit.getStreaks().invalidateNewerThan(timestamp);
|
||||
habit.getScores().invalidateNewerThan(timestamp);
|
||||
habit.getCheckmarks().invalidateNewerThan(timestamp);
|
||||
habit.getStreaks().invalidateNewerThan(timestamp);
|
||||
return rep;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,12 +35,12 @@ public class Score
|
||||
* Timestamp of the day to which this score applies. Time of day should be
|
||||
* midnight (UTC).
|
||||
*/
|
||||
private Long timestamp;
|
||||
private final Long timestamp;
|
||||
|
||||
/**
|
||||
* Value of the score.
|
||||
*/
|
||||
private Integer value;
|
||||
private final Integer value;
|
||||
|
||||
/**
|
||||
* Maximum score value attainable by any habit.
|
||||
@@ -86,6 +86,11 @@ public class Score
|
||||
return score;
|
||||
}
|
||||
|
||||
public int compareNewer(Score other)
|
||||
{
|
||||
return Long.signum(this.getTimestamp() - other.getTimestamp());
|
||||
}
|
||||
|
||||
public Habit getHabit()
|
||||
{
|
||||
return habit;
|
||||
|
||||
@@ -19,19 +19,17 @@
|
||||
|
||||
package org.isoron.uhabits.models;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.activeandroid.Cache;
|
||||
|
||||
import org.isoron.uhabits.utils.DateUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -55,39 +53,7 @@ public abstract class ScoreList
|
||||
observable = new ModelObservable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the values of all the scores, from day of the first repetition
|
||||
* until today, grouped in chunks of specified size.
|
||||
* <p>
|
||||
* If the group size is one, then the value of each score is returned
|
||||
* individually. If the group is, for example, seven, then the days are
|
||||
* grouped in groups of seven consecutive days.
|
||||
* <p>
|
||||
* The values are returned in an array of integers, with one entry for each
|
||||
* group of days in the interval. This value corresponds to the average of
|
||||
* the scores for the days inside the group. The first entry corresponds to
|
||||
* the ending of the interval (that is, the most recent group of days). The
|
||||
* last entry corresponds to the beginning of the interval. As usual, the
|
||||
* time of the day for the timestamps should be midnight (UTC). The
|
||||
* endpoints of the interval are included.
|
||||
* <p>
|
||||
* The values are returned in an integer array. There is one entry for each
|
||||
* day inside the interval. The first entry corresponds to today, while the
|
||||
* last entry corresponds to the day of the oldest repetition.
|
||||
*
|
||||
* @param divisor the size of the groups
|
||||
* @return array of values, with one entry for each group of days
|
||||
*/
|
||||
@NonNull
|
||||
public int[] getAllValues(long divisor)
|
||||
{
|
||||
Repetition oldestRep = habit.getRepetitions().getOldest();
|
||||
if (oldestRep == null) return new int[0];
|
||||
|
||||
long fromTimestamp = oldestRep.getTimestamp();
|
||||
long toTimestamp = DateUtils.getStartOfToday();
|
||||
return getValues(fromTimestamp, toTimestamp, divisor);
|
||||
}
|
||||
public abstract List<Score> getAll();
|
||||
|
||||
public ModelObservable getObservable()
|
||||
{
|
||||
@@ -112,6 +78,14 @@ public abstract class ScoreList
|
||||
*/
|
||||
public abstract int getValue(long timestamp);
|
||||
|
||||
public List<Score> groupBy(DateUtils.TruncateField field)
|
||||
{
|
||||
HashMap<Long, ArrayList<Long>> groups = getGroupedValues(field);
|
||||
List<Score> scores = groupsToAvgScores(groups);
|
||||
Collections.sort(scores, (s1, s2) -> s2.compareNewer(s1));
|
||||
return scores;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks all scores that have timestamp equal to or newer than the given
|
||||
* timestamp as invalid. Any following getValue calls will trigger the
|
||||
@@ -124,29 +98,15 @@ public abstract class ScoreList
|
||||
public void writeCSV(Writer out) throws IOException
|
||||
{
|
||||
computeAll();
|
||||
|
||||
SimpleDateFormat dateFormat = DateUtils.getCSVDateFormat();
|
||||
|
||||
String query =
|
||||
"select timestamp, score from score where habit = ? order by timestamp";
|
||||
String params[] = {habit.getId().toString()};
|
||||
|
||||
SQLiteDatabase db = Cache.openDatabase();
|
||||
Cursor cursor = db.rawQuery(query, params);
|
||||
|
||||
if (!cursor.moveToFirst()) return;
|
||||
|
||||
do
|
||||
for (Score s : getAll())
|
||||
{
|
||||
String timestamp = dateFormat.format(new Date(cursor.getLong(0)));
|
||||
String score = String.format("%.4f",
|
||||
((float) cursor.getInt(1)) / Score.MAX_VALUE);
|
||||
String timestamp = dateFormat.format(s.getTimestamp());
|
||||
String score =
|
||||
String.format("%.4f", ((float) s.getValue()) / Score.MAX_VALUE);
|
||||
out.write(String.format("%s,%s\n", timestamp, score));
|
||||
|
||||
} while (cursor.moveToNext());
|
||||
|
||||
cursor.close();
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void add(List<Score> scores);
|
||||
@@ -175,7 +135,7 @@ public abstract class ScoreList
|
||||
long newestTimestamp = 0;
|
||||
|
||||
Score newest = getNewestComputed();
|
||||
if(newest != null)
|
||||
if (newest != null)
|
||||
{
|
||||
newestValue = newest.getValue();
|
||||
newestTimestamp = newest.getTimestamp();
|
||||
@@ -218,10 +178,29 @@ public abstract class ScoreList
|
||||
* @param timestamp the timestamp for the day
|
||||
* @return the score for the day
|
||||
*/
|
||||
@Nullable
|
||||
protected abstract Score get(long timestamp);
|
||||
|
||||
@NonNull
|
||||
private HashMap<Long, ArrayList<Long>> getGroupedValues(DateUtils.TruncateField field)
|
||||
{
|
||||
HashMap<Long, ArrayList<Long>> groups = new HashMap<>();
|
||||
|
||||
for (Score s : getAll())
|
||||
{
|
||||
long groupTimestamp = DateUtils.truncate(field, s.getTimestamp());
|
||||
|
||||
if (!groups.containsKey(groupTimestamp))
|
||||
groups.put(groupTimestamp, new ArrayList<>());
|
||||
|
||||
groups.get(groupTimestamp).add((long) s.getValue());
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the most recent score that was already computed.
|
||||
* Returns the most recent score that has already been computed.
|
||||
* <p>
|
||||
* If no score has been computed yet, returns null.
|
||||
*
|
||||
@@ -230,13 +209,23 @@ public abstract class ScoreList
|
||||
@Nullable
|
||||
protected abstract Score getNewestComputed();
|
||||
|
||||
/**
|
||||
* Same as getAllValues(long), but using a specified interval.
|
||||
*
|
||||
* @param from beginning of the interval (included)
|
||||
* @param to end of the interval (included)
|
||||
* @param divisor size of the groups
|
||||
* @return array of values, with one entry for each group of days
|
||||
*/
|
||||
protected abstract int[] getValues(long from, long to, long divisor);
|
||||
|
||||
@NonNull
|
||||
private List<Score> groupsToAvgScores(HashMap<Long, ArrayList<Long>> groups)
|
||||
{
|
||||
List<Score> scores = new LinkedList<>();
|
||||
|
||||
for (Long timestamp : groups.keySet())
|
||||
{
|
||||
long meanValue = 0L;
|
||||
ArrayList<Long> groupValues = groups.get(timestamp);
|
||||
|
||||
for (Long v : groupValues) meanValue += v;
|
||||
meanValue /= groupValues.size();
|
||||
|
||||
scores.add(new Score(habit, timestamp, (int) meanValue));
|
||||
}
|
||||
|
||||
return scores;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ public abstract class StreakList
|
||||
|
||||
public abstract List<Streak> getAll();
|
||||
|
||||
@NonNull
|
||||
public List<Streak> getBest(int limit)
|
||||
{
|
||||
List<Streak> streaks = getAll();
|
||||
@@ -57,8 +58,10 @@ public abstract class StreakList
|
||||
return streaks;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public abstract Streak getNewestComputed();
|
||||
|
||||
@NonNull
|
||||
public ModelObservable getObservable()
|
||||
{
|
||||
return observable;
|
||||
@@ -89,7 +92,7 @@ public abstract class StreakList
|
||||
* @return the list of streaks.
|
||||
*/
|
||||
@NonNull
|
||||
protected List<Streak> checkmarksToStreaks(Long beginning, int[] checks)
|
||||
protected List<Streak> checkmarksToStreaks(long beginning, int[] checks)
|
||||
{
|
||||
ArrayList<Long> transitions = getTransitions(beginning, checks);
|
||||
|
||||
@@ -130,7 +133,7 @@ public abstract class StreakList
|
||||
* @return the list of transitions
|
||||
*/
|
||||
@NonNull
|
||||
protected ArrayList<Long> getTransitions(Long beginning, int[] checks)
|
||||
protected ArrayList<Long> getTransitions(long beginning, int[] checks)
|
||||
{
|
||||
long day = DateUtils.millisecondsInOneDay;
|
||||
long current = beginning;
|
||||
@@ -152,7 +155,7 @@ public abstract class StreakList
|
||||
return list;
|
||||
}
|
||||
|
||||
protected abstract void insert(List<Streak> streaks);
|
||||
protected abstract void insert(@NonNull List<Streak> streaks);
|
||||
|
||||
protected abstract void removeNewestComputed();
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ public class MemoryModelFactory implements ModelFactory
|
||||
@Override
|
||||
public ScoreList buildScoreList(Habit habit)
|
||||
{
|
||||
return null;
|
||||
return new MemoryScoreList(habit);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker 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 for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.models.memory;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.models.Score;
|
||||
import org.isoron.uhabits.models.ScoreList;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class MemoryScoreList extends ScoreList
|
||||
{
|
||||
List<Score> list;
|
||||
|
||||
public MemoryScoreList(Habit habit)
|
||||
{
|
||||
super(habit);
|
||||
list = new LinkedList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getValue(long timestamp)
|
||||
{
|
||||
Score s = get(timestamp);
|
||||
if (s != null) return s.getValue();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateNewerThan(long timestamp)
|
||||
{
|
||||
List<Score> discard = new LinkedList<>();
|
||||
|
||||
for (Score s : list)
|
||||
if (s.getTimestamp() >= timestamp) discard.add(s);
|
||||
|
||||
list.removeAll(discard);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public List<Score> getAll()
|
||||
{
|
||||
computeAll();
|
||||
return new LinkedList<>(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void add(List<Score> scores)
|
||||
{
|
||||
list.addAll(scores);
|
||||
Collections.sort(list,
|
||||
(s1, s2) -> Long.signum(s2.getTimestamp() - s1.getTimestamp()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
protected Score get(long timestamp)
|
||||
{
|
||||
computeAll();
|
||||
for (Score s : list)
|
||||
if (s.getTimestamp() == timestamp) return s;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Score getNewestComputed()
|
||||
{
|
||||
if(list.isEmpty()) return null;
|
||||
return list.get(0);
|
||||
}
|
||||
}
|
||||
@@ -106,6 +106,7 @@ public class SQLiteCheckmarkList extends CheckmarkList
|
||||
.limit(1)
|
||||
.executeSingle();
|
||||
|
||||
if(record == null) return null;
|
||||
return record.toCheckmark();
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ public class SQLiteHabitList extends HabitList
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(Habit habit)
|
||||
public void add(@NonNull Habit habit)
|
||||
{
|
||||
if(cache.containsValue(habit))
|
||||
throw new RuntimeException("habit already in cache");
|
||||
@@ -132,7 +132,7 @@ public class SQLiteHabitList extends HabitList
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOf(Habit h)
|
||||
public int indexOf(@NonNull Habit h)
|
||||
{
|
||||
HabitRecord record = HabitRecord.get(h.getId());
|
||||
if (record == null) return -1;
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
|
||||
package org.isoron.uhabits.models.sqlite;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteStatement;
|
||||
import android.support.annotation.NonNull;
|
||||
@@ -32,11 +31,10 @@ import com.activeandroid.query.Select;
|
||||
import com.activeandroid.util.SQLiteUtils;
|
||||
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.models.Repetition;
|
||||
import org.isoron.uhabits.models.Score;
|
||||
import org.isoron.uhabits.models.ScoreList;
|
||||
import org.isoron.uhabits.utils.DateUtils;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -73,11 +71,25 @@ public class SQLiteScoreList extends ScoreList
|
||||
.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public List<Score> getAll()
|
||||
{
|
||||
List<ScoreRecord> records = select().execute();
|
||||
List<Score> scores = new LinkedList<>();
|
||||
|
||||
for(ScoreRecord rec : records)
|
||||
scores.add(rec.toScore());
|
||||
|
||||
return scores;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Score getNewestComputed()
|
||||
{
|
||||
ScoreRecord record = select().limit(1).executeSingle();
|
||||
if(record == null) return null;
|
||||
return record.toScore();
|
||||
}
|
||||
|
||||
@@ -85,55 +97,15 @@ public class SQLiteScoreList extends ScoreList
|
||||
@Nullable
|
||||
protected Score get(long timestamp)
|
||||
{
|
||||
Repetition oldestRep = habit.getRepetitions().getOldest();
|
||||
if (oldestRep == null) return null;
|
||||
compute(oldestRep.getTimestamp(), timestamp);
|
||||
computeAll();
|
||||
|
||||
ScoreRecord record =
|
||||
select().where("timestamp = ?", timestamp).executeSingle();
|
||||
|
||||
if(record == null) return null;
|
||||
return record.toScore();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
protected int[] getValues(long from, long to, long divisor)
|
||||
{
|
||||
compute(from, to);
|
||||
|
||||
divisor *= DateUtils.millisecondsInOneDay;
|
||||
Long offset = to + divisor;
|
||||
|
||||
String query =
|
||||
"select ((timestamp - ?) / ?) as time, avg(score) from Score " +
|
||||
"where habit = ? and timestamp >= ? and timestamp <= ? " +
|
||||
"group by time order by time desc";
|
||||
|
||||
String params[] = {
|
||||
offset.toString(),
|
||||
Long.toString(divisor),
|
||||
habit.getId().toString(),
|
||||
Long.toString(from),
|
||||
Long.toString(to)
|
||||
};
|
||||
|
||||
SQLiteDatabase db = Cache.openDatabase();
|
||||
Cursor cursor = db.rawQuery(query, params);
|
||||
|
||||
if (!cursor.moveToFirst()) return new int[0];
|
||||
|
||||
int k = 0;
|
||||
int[] scores = new int[cursor.getCount()];
|
||||
|
||||
do
|
||||
{
|
||||
scores[k++] = (int) cursor.getFloat(1);
|
||||
} while (cursor.moveToNext());
|
||||
|
||||
cursor.close();
|
||||
return scores;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void add(List<Score> scores)
|
||||
{
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
|
||||
package org.isoron.uhabits.models.sqlite;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.activeandroid.query.Delete;
|
||||
import com.activeandroid.query.Select;
|
||||
|
||||
@@ -57,8 +60,9 @@ public class SQLiteStreakList extends StreakList
|
||||
@Override
|
||||
public Streak getNewestComputed()
|
||||
{
|
||||
rebuild();
|
||||
return getNewestRecord().toStreak();
|
||||
StreakRecord newestRecord = getNewestRecord();
|
||||
if(newestRecord == null) return null;
|
||||
return newestRecord.toStreak();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -73,6 +77,7 @@ public class SQLiteStreakList extends StreakList
|
||||
observable.notifyListeners();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private StreakRecord getNewestRecord()
|
||||
{
|
||||
return new Select()
|
||||
@@ -84,7 +89,7 @@ public class SQLiteStreakList extends StreakList
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void insert(List<Streak> streaks)
|
||||
protected void insert(@NonNull List<Streak> streaks)
|
||||
{
|
||||
DatabaseUtils.executeAsTransaction(() -> {
|
||||
for (Streak streak : streaks)
|
||||
@@ -96,6 +101,7 @@ public class SQLiteStreakList extends StreakList
|
||||
});
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private List<Streak> recordsToStreaks(List<StreakRecord> records)
|
||||
{
|
||||
LinkedList<Streak> streaks = new LinkedList<>();
|
||||
|
||||
@@ -36,6 +36,7 @@ import org.isoron.uhabits.HabitsApplication;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.commands.CommandRunner;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.models.HabitList;
|
||||
import org.isoron.uhabits.utils.ColorUtils;
|
||||
import org.isoron.uhabits.utils.DateUtils;
|
||||
import org.isoron.uhabits.utils.Preferences;
|
||||
@@ -50,17 +51,23 @@ import butterknife.OnItemSelected;
|
||||
|
||||
public abstract class BaseDialogFragment extends AppCompatDialogFragment
|
||||
{
|
||||
@Nullable
|
||||
protected Habit originalHabit;
|
||||
|
||||
@Nullable
|
||||
protected Habit modifiedHabit;
|
||||
|
||||
@Nullable
|
||||
protected BaseDialogHelper helper;
|
||||
|
||||
@Inject
|
||||
Preferences prefs;
|
||||
protected Preferences prefs;
|
||||
|
||||
@Inject
|
||||
CommandRunner commandRunner;
|
||||
protected CommandRunner commandRunner;
|
||||
|
||||
@Inject
|
||||
protected HabitList habitList;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
|
||||
@@ -21,20 +21,13 @@ package org.isoron.uhabits.ui.habits.edit;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.isoron.uhabits.HabitsApplication;
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.commands.Command;
|
||||
import org.isoron.uhabits.commands.EditHabitCommand;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.models.HabitList;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class EditHabitDialogFragment extends BaseDialogFragment
|
||||
{
|
||||
@Inject
|
||||
HabitList habitList;
|
||||
|
||||
public static EditHabitDialogFragment newInstance(long habitId)
|
||||
{
|
||||
EditHabitDialogFragment frag = new EditHabitDialogFragment();
|
||||
@@ -53,8 +46,6 @@ public class EditHabitDialogFragment extends BaseDialogFragment
|
||||
@Override
|
||||
protected void initializeHabits()
|
||||
{
|
||||
HabitsApplication.getComponent().inject(this);
|
||||
|
||||
Long habitId = (Long) getArguments().get("habitId");
|
||||
if (habitId == null)
|
||||
throw new IllegalArgumentException("habitId must be specified");
|
||||
|
||||
@@ -27,8 +27,10 @@ import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.RectF;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
@@ -42,6 +44,8 @@ import org.isoron.uhabits.utils.InterfaceUtils;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class HabitScoreView extends ScrollableDataView
|
||||
@@ -86,7 +90,7 @@ public class HabitScoreView extends ScrollableDataView
|
||||
private int gridColor;
|
||||
|
||||
@Nullable
|
||||
private int[] scores;
|
||||
private List<Score> scores;
|
||||
|
||||
private int primaryColor;
|
||||
|
||||
@@ -134,7 +138,11 @@ public class HabitScoreView extends ScrollableDataView
|
||||
else
|
||||
{
|
||||
if (habit == null) return;
|
||||
scores = habit.getScores().getAllValues(bucketSize);
|
||||
if (bucketSize == 1)
|
||||
scores = habit.getScores().getAll();
|
||||
else
|
||||
scores = habit.getScores().groupBy(getTruncateField());
|
||||
|
||||
createColors();
|
||||
}
|
||||
|
||||
@@ -285,14 +293,20 @@ public class HabitScoreView extends ScrollableDataView
|
||||
private void generateRandomData()
|
||||
{
|
||||
Random random = new Random();
|
||||
scores = new int[100];
|
||||
scores[0] = Score.MAX_VALUE / 2;
|
||||
scores = new LinkedList<>();
|
||||
|
||||
int previous = Score.MAX_VALUE / 2;
|
||||
long timestamp = DateUtils.getStartOfToday();
|
||||
long day = DateUtils.millisecondsInOneDay;
|
||||
|
||||
for (int i = 1; i < 100; i++)
|
||||
{
|
||||
int step = Score.MAX_VALUE / 10;
|
||||
scores[i] = scores[i - 1] + random.nextInt(step * 2) - step;
|
||||
scores[i] = Math.max(0, Math.min(Score.MAX_VALUE, scores[i]));
|
||||
int current = previous + random.nextInt(step * 2) - step;
|
||||
current = Math.max(0, Math.min(Score.MAX_VALUE, current));
|
||||
scores.add(new Score(habit, timestamp, current));
|
||||
previous = current;
|
||||
timestamp -= day;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,6 +340,38 @@ public class HabitScoreView extends ScrollableDataView
|
||||
return maxMonthWidth;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private DateUtils.TruncateField getTruncateField()
|
||||
{
|
||||
DateUtils.TruncateField field;
|
||||
|
||||
switch (bucketSize)
|
||||
{
|
||||
case 7:
|
||||
field = DateUtils.TruncateField.WEEK_NUMBER;
|
||||
break;
|
||||
|
||||
case 365:
|
||||
field = DateUtils.TruncateField.YEAR;
|
||||
break;
|
||||
|
||||
case 92:
|
||||
field = DateUtils.TruncateField.QUARTER;
|
||||
break;
|
||||
|
||||
default:
|
||||
Log.e("HabitScoreView",
|
||||
String.format("Unknown bucket size: %d", bucketSize));
|
||||
// continue to case 31
|
||||
|
||||
case 31:
|
||||
field = DateUtils.TruncateField.MONTH;
|
||||
break;
|
||||
}
|
||||
|
||||
return field;
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
createPaints();
|
||||
@@ -413,7 +459,7 @@ public class HabitScoreView extends ScrollableDataView
|
||||
{
|
||||
int score = 0;
|
||||
int offset = nColumns - k - 1 + getDataOffset();
|
||||
if (offset < scores.length) score = scores[offset];
|
||||
if (offset < scores.size()) score = scores.get(offset).getValue();
|
||||
|
||||
double relativeScore = ((double) score) / Score.MAX_VALUE;
|
||||
int height = (int) (columnHeight * relativeScore);
|
||||
|
||||
@@ -33,20 +33,233 @@ import java.util.TimeZone;
|
||||
|
||||
public abstract class DateUtils
|
||||
{
|
||||
public static long millisecondsInOneDay = 24 * 60 * 60 * 1000;
|
||||
public static int ALL_WEEK_DAYS = 127;
|
||||
|
||||
private static Long fixedLocalTime = null;
|
||||
|
||||
/**
|
||||
* Number of milliseconds in one day.
|
||||
*/
|
||||
public static long millisecondsInOneDay = 24 * 60 * 60 * 1000;
|
||||
|
||||
public static String formatHeaderDate(GregorianCalendar day)
|
||||
{
|
||||
String dayOfMonth =
|
||||
Integer.toString(day.get(GregorianCalendar.DAY_OF_MONTH));
|
||||
String dayOfWeek = day.getDisplayName(GregorianCalendar.DAY_OF_WEEK,
|
||||
GregorianCalendar.SHORT, Locale.getDefault());
|
||||
|
||||
return dayOfWeek + "\n" + dayOfMonth;
|
||||
}
|
||||
|
||||
public static String formatTime(Context context, int hours, int minutes)
|
||||
{
|
||||
int reminderMilliseconds = (hours * 60 + minutes) * 60 * 1000;
|
||||
|
||||
Date date = new Date(reminderMilliseconds);
|
||||
java.text.DateFormat df = DateFormat.getTimeFormat(context);
|
||||
df.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
|
||||
return df.format(date);
|
||||
}
|
||||
|
||||
public static String formatWeekdayList(Context context, boolean weekday[])
|
||||
{
|
||||
String shortDayNames[] = getShortDayNames();
|
||||
String longDayNames[] = getLongDayNames();
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
||||
int count = 0;
|
||||
int first = 0;
|
||||
boolean isFirst = true;
|
||||
for (int i = 0; i < 7; i++)
|
||||
{
|
||||
if (weekday[i])
|
||||
{
|
||||
if (isFirst) first = i;
|
||||
else buffer.append(", ");
|
||||
|
||||
buffer.append(shortDayNames[i]);
|
||||
isFirst = false;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 1) return longDayNames[first];
|
||||
if (count == 2 && weekday[0] && weekday[1])
|
||||
return context.getString(R.string.weekends);
|
||||
if (count == 5 && !weekday[0] && !weekday[1])
|
||||
return context.getString(R.string.any_weekday);
|
||||
if (count == 7) return context.getString(R.string.any_day);
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
public static SimpleDateFormat getBackupDateFormat()
|
||||
{
|
||||
SimpleDateFormat dateFormat =
|
||||
new SimpleDateFormat("yyyy-MM-dd HHmmss", Locale.US);
|
||||
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
|
||||
return dateFormat;
|
||||
}
|
||||
|
||||
public static SimpleDateFormat getCSVDateFormat()
|
||||
{
|
||||
SimpleDateFormat dateFormat =
|
||||
new SimpleDateFormat("yyyy-MM-dd", Locale.US);
|
||||
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
|
||||
return dateFormat;
|
||||
}
|
||||
|
||||
public static GregorianCalendar getCalendar(long timestamp)
|
||||
{
|
||||
GregorianCalendar day =
|
||||
new GregorianCalendar(TimeZone.getTimeZone("GMT"));
|
||||
day.setTimeInMillis(timestamp);
|
||||
return day;
|
||||
}
|
||||
|
||||
public static SimpleDateFormat getDateFormat(String skeleton)
|
||||
{
|
||||
String pattern;
|
||||
Locale locale = Locale.getDefault();
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT >=
|
||||
android.os.Build.VERSION_CODES.JELLY_BEAN_MR2)
|
||||
pattern = DateFormat.getBestDateTimePattern(locale, skeleton);
|
||||
else pattern = skeleton;
|
||||
|
||||
SimpleDateFormat format = new SimpleDateFormat(pattern, locale);
|
||||
format.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
|
||||
return format;
|
||||
}
|
||||
|
||||
public static String[] getDayNames(int format)
|
||||
{
|
||||
String[] wdays = new String[7];
|
||||
|
||||
Calendar day = new GregorianCalendar();
|
||||
day.set(GregorianCalendar.DAY_OF_WEEK, Calendar.SATURDAY);
|
||||
|
||||
for (int i = 0; i < wdays.length; i++)
|
||||
{
|
||||
wdays[i] = day.getDisplayName(GregorianCalendar.DAY_OF_WEEK, format,
|
||||
Locale.getDefault());
|
||||
day.add(GregorianCalendar.DAY_OF_MONTH, 1);
|
||||
}
|
||||
|
||||
return wdays;
|
||||
}
|
||||
|
||||
public static long getLocalTime()
|
||||
{
|
||||
if(fixedLocalTime != null) return fixedLocalTime;
|
||||
if (fixedLocalTime != null) return fixedLocalTime;
|
||||
|
||||
TimeZone tz = TimeZone.getDefault();
|
||||
long now = new Date().getTime();
|
||||
return now + tz.getOffset(now);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array with weekday names starting according to locale settings,
|
||||
* e.g. [Mo,Di,Mi,Do,Fr,Sa,So] in Europe
|
||||
*/
|
||||
public static String[] getLocaleDayNames(int format)
|
||||
{
|
||||
String[] days = new String[7];
|
||||
|
||||
Calendar calendar = new GregorianCalendar();
|
||||
calendar.set(GregorianCalendar.DAY_OF_WEEK,
|
||||
calendar.getFirstDayOfWeek());
|
||||
for (int i = 0; i < days.length; i++)
|
||||
{
|
||||
days[i] =
|
||||
calendar.getDisplayName(GregorianCalendar.DAY_OF_WEEK, format,
|
||||
Locale.getDefault());
|
||||
calendar.add(GregorianCalendar.DAY_OF_MONTH, 1);
|
||||
}
|
||||
|
||||
return days;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array with week days numbers starting according to locale
|
||||
* settings, e.g. [2,3,4,5,6,7,1] in Europe
|
||||
*/
|
||||
public static Integer[] getLocaleWeekdayList()
|
||||
{
|
||||
Integer[] dayNumbers = new Integer[7];
|
||||
Calendar calendar = new GregorianCalendar();
|
||||
calendar.set(GregorianCalendar.DAY_OF_WEEK,
|
||||
calendar.getFirstDayOfWeek());
|
||||
for (int i = 0; i < dayNumbers.length; i++)
|
||||
{
|
||||
dayNumbers[i] = calendar.get(GregorianCalendar.DAY_OF_WEEK);
|
||||
calendar.add(GregorianCalendar.DAY_OF_MONTH, 1);
|
||||
}
|
||||
return dayNumbers;
|
||||
}
|
||||
|
||||
public static String[] getLongDayNames()
|
||||
{
|
||||
return getDayNames(GregorianCalendar.LONG);
|
||||
}
|
||||
|
||||
public static String[] getShortDayNames()
|
||||
{
|
||||
return getDayNames(GregorianCalendar.SHORT);
|
||||
}
|
||||
|
||||
public static long getStartOfDay(long timestamp)
|
||||
{
|
||||
return (timestamp / millisecondsInOneDay) * millisecondsInOneDay;
|
||||
}
|
||||
|
||||
public static long getStartOfToday()
|
||||
{
|
||||
return getStartOfDay(DateUtils.getLocalTime());
|
||||
}
|
||||
|
||||
public static GregorianCalendar getStartOfTodayCalendar()
|
||||
{
|
||||
return getCalendar(getStartOfToday());
|
||||
}
|
||||
|
||||
public static int getWeekday(long timestamp)
|
||||
{
|
||||
GregorianCalendar day = getCalendar(timestamp);
|
||||
return day.get(GregorianCalendar.DAY_OF_WEEK) % 7;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throughout the code, it is assumed that the weekdays are numbered from 0
|
||||
* (Saturday) to 6 (Friday). In the Java Calendar they are numbered from 1
|
||||
* (Sunday) to 7 (Saturday). This function converts from Java to our
|
||||
* internal representation.
|
||||
*
|
||||
* @return weekday number in the internal interpretation
|
||||
*/
|
||||
public static int javaWeekdayToLoopWeekday(int number)
|
||||
{
|
||||
return number % 7;
|
||||
}
|
||||
|
||||
public static Integer packWeekdayList(boolean weekday[])
|
||||
{
|
||||
int list = 0;
|
||||
int current = 1;
|
||||
|
||||
for (int i = 0; i < 7; i++)
|
||||
{
|
||||
if (weekday[i]) list |= current;
|
||||
current = current << 1;
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public static void setFixedLocalTime(Long timestamp)
|
||||
{
|
||||
fixedLocalTime = timestamp;
|
||||
@@ -59,210 +272,38 @@ public abstract class DateUtils
|
||||
return now + tz.getOffset(now);
|
||||
}
|
||||
|
||||
public static long getStartOfDay(long timestamp)
|
||||
public static Long truncate(TruncateField field, long timestamp)
|
||||
{
|
||||
return (timestamp / millisecondsInOneDay) * millisecondsInOneDay;
|
||||
}
|
||||
GregorianCalendar cal = DateUtils.getCalendar(timestamp);
|
||||
|
||||
public static GregorianCalendar getStartOfTodayCalendar()
|
||||
{
|
||||
return getCalendar(getStartOfToday());
|
||||
}
|
||||
|
||||
public static GregorianCalendar getCalendar(long timestamp)
|
||||
{
|
||||
GregorianCalendar day = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
|
||||
day.setTimeInMillis(timestamp);
|
||||
return day;
|
||||
}
|
||||
|
||||
public static int getWeekday(long timestamp)
|
||||
{
|
||||
GregorianCalendar day = getCalendar(timestamp);
|
||||
return day.get(GregorianCalendar.DAY_OF_WEEK) % 7;
|
||||
}
|
||||
|
||||
public static long getStartOfToday()
|
||||
{
|
||||
return getStartOfDay(DateUtils.getLocalTime());
|
||||
}
|
||||
|
||||
public static String formatTime(Context context, int hours, int minutes)
|
||||
{
|
||||
int reminderMilliseconds = (hours * 60 + minutes) * 60 * 1000;
|
||||
|
||||
Date date = new Date(reminderMilliseconds);
|
||||
java.text.DateFormat df = DateFormat.getTimeFormat(context);
|
||||
df.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
|
||||
return df.format(date);
|
||||
}
|
||||
|
||||
public static SimpleDateFormat getDateFormat(String skeleton)
|
||||
{
|
||||
String pattern;
|
||||
Locale locale = Locale.getDefault();
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2)
|
||||
pattern = DateFormat.getBestDateTimePattern(locale, skeleton);
|
||||
else
|
||||
pattern = skeleton;
|
||||
|
||||
SimpleDateFormat format = new SimpleDateFormat(pattern, locale);
|
||||
format.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
|
||||
return format;
|
||||
}
|
||||
|
||||
public static SimpleDateFormat getCSVDateFormat()
|
||||
{
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
|
||||
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
|
||||
return dateFormat;
|
||||
}
|
||||
|
||||
public static SimpleDateFormat getBackupDateFormat()
|
||||
{
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HHmmss", Locale.US);
|
||||
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
|
||||
return dateFormat;
|
||||
}
|
||||
|
||||
public static String formatHeaderDate(GregorianCalendar day)
|
||||
{
|
||||
String dayOfMonth = Integer.toString(day.get(GregorianCalendar.DAY_OF_MONTH));
|
||||
String dayOfWeek = day.getDisplayName(GregorianCalendar.DAY_OF_WEEK,
|
||||
GregorianCalendar.SHORT, Locale.getDefault());
|
||||
|
||||
return dayOfWeek + "\n" + dayOfMonth;
|
||||
}
|
||||
|
||||
public static int differenceInDays(Date from, Date to)
|
||||
{
|
||||
long milliseconds = getStartOfDay(to.getTime()) - getStartOfDay(from.getTime());
|
||||
return (int) (milliseconds / millisecondsInOneDay);
|
||||
}
|
||||
|
||||
public static String[] getShortDayNames()
|
||||
{
|
||||
return getDayNames(GregorianCalendar.SHORT);
|
||||
}
|
||||
|
||||
public static String[] getLongDayNames()
|
||||
{
|
||||
return getDayNames(GregorianCalendar.LONG);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Throughout the code, it is assumed that the weekdays are numbered from 0 (Saturday) to 6
|
||||
* (Friday). In the Java Calendar they are numbered from 1 (Sunday) to 7 (Saturday). This
|
||||
* function converts from Java to our internal representation.
|
||||
*
|
||||
* @return weekday number in the internal interpretation
|
||||
*/
|
||||
public static int javaWeekdayToLoopWeekday(int number)
|
||||
{
|
||||
return number % 7;
|
||||
}
|
||||
|
||||
public static String[] getDayNames(int format)
|
||||
{
|
||||
String[] wdays = new String[7];
|
||||
|
||||
Calendar day = new GregorianCalendar();
|
||||
day.set(GregorianCalendar.DAY_OF_WEEK, Calendar.SATURDAY);
|
||||
|
||||
for (int i = 0; i < wdays.length; i++)
|
||||
switch (field)
|
||||
{
|
||||
wdays[i] = day.getDisplayName(GregorianCalendar.DAY_OF_WEEK, format,
|
||||
Locale.getDefault());
|
||||
day.add(GregorianCalendar.DAY_OF_MONTH, 1);
|
||||
case MONTH:
|
||||
cal.set(Calendar.DAY_OF_MONTH, 1);
|
||||
return cal.getTimeInMillis();
|
||||
|
||||
case WEEK_NUMBER:
|
||||
int firstWeekday = cal.getFirstDayOfWeek();
|
||||
int weekday = cal.get(Calendar.DAY_OF_WEEK);
|
||||
int delta = weekday - firstWeekday;
|
||||
if (delta < 0) delta += 7;
|
||||
cal.add(Calendar.DAY_OF_YEAR, -delta);
|
||||
return cal.getTimeInMillis();
|
||||
|
||||
case QUARTER:
|
||||
int quarter = cal.get(Calendar.MONTH) / 3;
|
||||
cal.set(Calendar.DAY_OF_MONTH, 1);
|
||||
cal.set(Calendar.MONTH, quarter * 3);
|
||||
return cal.getTimeInMillis();
|
||||
|
||||
case YEAR:
|
||||
cal.set(Calendar.MONTH, Calendar.JANUARY);
|
||||
cal.set(Calendar.DAY_OF_MONTH, 1);
|
||||
return cal.getTimeInMillis();
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
return wdays;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array with weekday names starting according to locale settings,
|
||||
* e.g. [Mo,Di,Mi,Do,Fr,Sa,So] in Europe
|
||||
*/
|
||||
public static String[] getLocaleDayNames(int format)
|
||||
{
|
||||
String[] days = new String[7];
|
||||
|
||||
Calendar calendar = new GregorianCalendar();
|
||||
calendar.set(GregorianCalendar.DAY_OF_WEEK, calendar.getFirstDayOfWeek());
|
||||
for (int i = 0; i < days.length; i++)
|
||||
{
|
||||
days[i] = calendar.getDisplayName(GregorianCalendar.DAY_OF_WEEK, format,
|
||||
Locale.getDefault());
|
||||
calendar.add(GregorianCalendar.DAY_OF_MONTH, 1);
|
||||
}
|
||||
|
||||
return days;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array with week days numbers starting according to locale settings,
|
||||
* e.g. [2,3,4,5,6,7,1] in Europe
|
||||
*/
|
||||
public static Integer[] getLocaleWeekdayList()
|
||||
{
|
||||
Integer[] dayNumbers = new Integer[7];
|
||||
Calendar calendar = new GregorianCalendar();
|
||||
calendar.set(GregorianCalendar.DAY_OF_WEEK, calendar.getFirstDayOfWeek());
|
||||
for (int i = 0; i < dayNumbers.length; i++)
|
||||
{
|
||||
dayNumbers[i] = calendar.get(GregorianCalendar.DAY_OF_WEEK);
|
||||
calendar.add(GregorianCalendar.DAY_OF_MONTH, 1);
|
||||
}
|
||||
return dayNumbers;
|
||||
}
|
||||
|
||||
public static String formatWeekdayList(Context context, boolean weekday[])
|
||||
{
|
||||
String shortDayNames[] = getShortDayNames();
|
||||
String longDayNames[] = getLongDayNames();
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
||||
int count = 0;
|
||||
int first = 0;
|
||||
boolean isFirst = true;
|
||||
for(int i = 0; i < 7; i++)
|
||||
{
|
||||
if(weekday[i])
|
||||
{
|
||||
if(isFirst) first = i;
|
||||
else buffer.append(", ");
|
||||
|
||||
buffer.append(shortDayNames[i]);
|
||||
isFirst = false;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if(count == 1) return longDayNames[first];
|
||||
if(count == 2 && weekday[0] && weekday[1]) return context.getString(R.string.weekends);
|
||||
if(count == 5 && !weekday[0] && !weekday[1]) return context.getString(R.string.any_weekday);
|
||||
if(count == 7) return context.getString(R.string.any_day);
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
public static Integer packWeekdayList(boolean weekday[])
|
||||
{
|
||||
int list = 0;
|
||||
int current = 1;
|
||||
|
||||
for(int i = 0; i < 7; i++)
|
||||
{
|
||||
if(weekday[i]) list |= current;
|
||||
current = current << 1;
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public static boolean[] unpackWeekdayList(int list)
|
||||
@@ -270,12 +311,17 @@ public abstract class DateUtils
|
||||
boolean[] weekday = new boolean[7];
|
||||
int current = 1;
|
||||
|
||||
for(int i = 0; i < 7; i++)
|
||||
for (int i = 0; i < 7; i++)
|
||||
{
|
||||
if((list & current) != 0) weekday[i] = true;
|
||||
if ((list & current) != 0) weekday[i] = true;
|
||||
current = current << 1;
|
||||
}
|
||||
|
||||
return weekday;
|
||||
}
|
||||
|
||||
public enum TruncateField
|
||||
{
|
||||
MONTH, WEEK_NUMBER, YEAR, QUARTER
|
||||
}
|
||||
}
|
||||
|
||||
195
app/src/test/java/org/isoron/uhabits/models/ScoreListTest.java
Normal file
195
app/src/test/java/org/isoron/uhabits/models/ScoreListTest.java
Normal file
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker 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 for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.models;
|
||||
|
||||
import org.isoron.uhabits.BaseUnitTest;
|
||||
import org.isoron.uhabits.utils.DateUtils;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.MatcherAssert.*;
|
||||
|
||||
public class ScoreListTest extends BaseUnitTest
|
||||
{
|
||||
private Habit habit;
|
||||
|
||||
@Override
|
||||
@Before
|
||||
public void setUp()
|
||||
{
|
||||
super.setUp();
|
||||
habit = fixtures.createEmptyHabit();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_getAll()
|
||||
{
|
||||
toggleRepetitions(0, 20);
|
||||
|
||||
int expectedValues[] = {
|
||||
12629351,
|
||||
12266245,
|
||||
11883254,
|
||||
11479288,
|
||||
11053198,
|
||||
10603773,
|
||||
10129735,
|
||||
9629735,
|
||||
9102352,
|
||||
8546087,
|
||||
7959357,
|
||||
7340494,
|
||||
6687738,
|
||||
5999234,
|
||||
5273023,
|
||||
4507040,
|
||||
3699107,
|
||||
2846927,
|
||||
1948077,
|
||||
1000000
|
||||
};
|
||||
|
||||
int actualValues[] = new int[expectedValues.length];
|
||||
|
||||
int i = 0;
|
||||
for (Score s : habit.getScores().getAll())
|
||||
actualValues[i++] = s.getValue();
|
||||
|
||||
assertThat(actualValues, equalTo(expectedValues));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_getTodayValue()
|
||||
{
|
||||
toggleRepetitions(0, 20);
|
||||
assertThat(habit.getScores().getTodayValue(), equalTo(12629351));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_getValue()
|
||||
{
|
||||
toggleRepetitions(0, 20);
|
||||
|
||||
int expectedValues[] = {
|
||||
12629351,
|
||||
12266245,
|
||||
11883254,
|
||||
11479288,
|
||||
11053198,
|
||||
10603773,
|
||||
10129735,
|
||||
9629735,
|
||||
9102352,
|
||||
8546087,
|
||||
7959357,
|
||||
7340494,
|
||||
6687738,
|
||||
5999234,
|
||||
5273023,
|
||||
4507040,
|
||||
3699107,
|
||||
2846927,
|
||||
1948077,
|
||||
1000000
|
||||
};
|
||||
|
||||
long current = DateUtils.getStartOfToday();
|
||||
for (int expectedValue : expectedValues)
|
||||
{
|
||||
assertThat(habit.getScores().getValue(current),
|
||||
equalTo(expectedValue));
|
||||
current -= DateUtils.millisecondsInOneDay;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_groupBy()
|
||||
{
|
||||
Habit habit = fixtures.createLongHabit();
|
||||
List<Score> list =
|
||||
habit.getScores().groupBy(DateUtils.TruncateField.MONTH);
|
||||
|
||||
assertThat(list.size(), equalTo(5));
|
||||
assertThat(list.get(0).getValue(), equalTo(14634077));
|
||||
assertThat(list.get(1).getValue(), equalTo(12969133));
|
||||
assertThat(list.get(2).getValue(), equalTo(10595391));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_invalidateNewerThan()
|
||||
{
|
||||
assertThat(habit.getScores().getTodayValue(), equalTo(0));
|
||||
|
||||
toggleRepetitions(0, 2);
|
||||
assertThat(habit.getScores().getTodayValue(), equalTo(1948077));
|
||||
|
||||
habit.setFreqNum(1);
|
||||
habit.setFreqDen(2);
|
||||
habit.getScores().invalidateNewerThan(0);
|
||||
|
||||
assertThat(habit.getScores().getTodayValue(), equalTo(1974654));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_writeCSV() throws IOException
|
||||
{
|
||||
Habit habit = fixtures.createShortHabit();
|
||||
|
||||
String expectedCSV = "2015-01-25,0.2649\n" +
|
||||
"2015-01-24,0.2205\n" +
|
||||
"2015-01-23,0.2283\n" +
|
||||
"2015-01-22,0.2364\n" +
|
||||
"2015-01-21,0.1909\n" +
|
||||
"2015-01-20,0.1439\n" +
|
||||
"2015-01-19,0.0952\n" +
|
||||
"2015-01-18,0.0986\n" +
|
||||
"2015-01-17,0.1021\n" +
|
||||
"2015-01-16,0.0519\n";
|
||||
|
||||
StringWriter writer = new StringWriter();
|
||||
habit.getScores().writeCSV(writer);
|
||||
|
||||
assertThat(writer.toString(), equalTo(expectedCSV));
|
||||
}
|
||||
|
||||
private void log(List<Score> list)
|
||||
{
|
||||
SimpleDateFormat df = DateUtils.getCSVDateFormat();
|
||||
for (Score s : list)
|
||||
log("%s %d", df.format(new Date(s.getTimestamp())), s.getValue());
|
||||
}
|
||||
|
||||
private void toggleRepetitions(final int from, final int to)
|
||||
{
|
||||
RepetitionList reps = habit.getRepetitions();
|
||||
long today = DateUtils.getStartOfToday();
|
||||
long day = DateUtils.millisecondsInOneDay;
|
||||
|
||||
for (int i = from; i < to; i++)
|
||||
reps.toggleTimestamp(today - i * day);
|
||||
}
|
||||
}
|
||||
@@ -17,24 +17,16 @@
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.unit.models;
|
||||
package org.isoron.uhabits.models;
|
||||
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
|
||||
import org.isoron.uhabits.BaseAndroidTest;
|
||||
import org.isoron.uhabits.models.Checkmark;
|
||||
import org.isoron.uhabits.models.Score;
|
||||
import org.isoron.uhabits.BaseUnitTest;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class ScoreTest extends BaseAndroidTest
|
||||
public class ScoreTest extends BaseUnitTest
|
||||
{
|
||||
@Override
|
||||
@Before
|
||||
@@ -50,20 +42,22 @@ public class ScoreTest extends BaseAndroidTest
|
||||
assertThat(Score.compute(1, 0, checkmark), equalTo(0));
|
||||
assertThat(Score.compute(1, 5000000, checkmark), equalTo(4740387));
|
||||
assertThat(Score.compute(1, 10000000, checkmark), equalTo(9480775));
|
||||
assertThat(Score.compute(1, Score.MAX_VALUE, checkmark), equalTo(18259478));
|
||||
assertThat(Score.compute(1, Score.MAX_VALUE, checkmark),
|
||||
equalTo(18259478));
|
||||
|
||||
checkmark = Checkmark.CHECKED_IMPLICITLY;
|
||||
assertThat(Score.compute(1, 0, checkmark), equalTo(0));
|
||||
assertThat(Score.compute(1, 5000000, checkmark), equalTo(4740387));
|
||||
assertThat(Score.compute(1, 10000000, checkmark), equalTo(9480775));
|
||||
assertThat(Score.compute(1, Score.MAX_VALUE, checkmark), equalTo(18259478));
|
||||
assertThat(Score.compute(1, Score.MAX_VALUE, checkmark),
|
||||
equalTo(18259478));
|
||||
|
||||
checkmark = Checkmark.CHECKED_EXPLICITLY;
|
||||
assertThat(Score.compute(1, 0, checkmark), equalTo(1000000));
|
||||
assertThat(Score.compute(1, 5000000, checkmark), equalTo(5740387));
|
||||
assertThat(Score.compute(1, 10000000, checkmark), equalTo(10480775));
|
||||
assertThat(Score.compute(1, Score.MAX_VALUE, checkmark), equalTo(
|
||||
Score.MAX_VALUE));
|
||||
assertThat(Score.compute(1, Score.MAX_VALUE, checkmark),
|
||||
equalTo(Score.MAX_VALUE));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -71,15 +65,19 @@ public class ScoreTest extends BaseAndroidTest
|
||||
{
|
||||
int checkmark = Checkmark.CHECKED_EXPLICITLY;
|
||||
assertThat(Score.compute(1 / 3.0, 0, checkmark), equalTo(1000000));
|
||||
assertThat(Score.compute(1 / 3.0, 5000000, checkmark), equalTo(5916180));
|
||||
assertThat(Score.compute(1 / 3.0, 10000000, checkmark), equalTo(10832360));
|
||||
assertThat(Score.compute(1 / 3.0, Score.MAX_VALUE, checkmark), equalTo(
|
||||
Score.MAX_VALUE));
|
||||
assertThat(Score.compute(1 / 3.0, 5000000, checkmark),
|
||||
equalTo(5916180));
|
||||
assertThat(Score.compute(1 / 3.0, 10000000, checkmark),
|
||||
equalTo(10832360));
|
||||
assertThat(Score.compute(1 / 3.0, Score.MAX_VALUE, checkmark),
|
||||
equalTo(Score.MAX_VALUE));
|
||||
|
||||
assertThat(Score.compute(1 / 7.0, 0, checkmark), equalTo(1000000));
|
||||
assertThat(Score.compute(1 / 7.0, 5000000, checkmark), equalTo(5964398));
|
||||
assertThat(Score.compute(1 / 7.0, 10000000, checkmark), equalTo(10928796));
|
||||
assertThat(Score.compute(1 / 7.0, Score.MAX_VALUE, checkmark), equalTo(
|
||||
Score.MAX_VALUE));
|
||||
assertThat(Score.compute(1 / 7.0, 5000000, checkmark),
|
||||
equalTo(5964398));
|
||||
assertThat(Score.compute(1 / 7.0, 10000000, checkmark),
|
||||
equalTo(10928796));
|
||||
assertThat(Score.compute(1 / 7.0, Score.MAX_VALUE, checkmark),
|
||||
equalTo(Score.MAX_VALUE));
|
||||
}
|
||||
}
|
||||
146
app/src/test/java/org/isoron/uhabits/utils/DateUtilsTest.java
Normal file
146
app/src/test/java/org/isoron/uhabits/utils/DateUtilsTest.java
Normal file
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker 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 for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.utils;
|
||||
|
||||
import org.isoron.uhabits.BaseUnitTest;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.MatcherAssert.*;
|
||||
|
||||
public class DateUtilsTest extends BaseUnitTest
|
||||
{
|
||||
@Test
|
||||
public void testTruncate_dayOfWeek()
|
||||
{
|
||||
DateUtils.TruncateField field = DateUtils.TruncateField.WEEK_NUMBER;
|
||||
|
||||
long expected = timestamp(2015, Calendar.JANUARY, 11);
|
||||
long t0 = timestamp(2015, Calendar.JANUARY, 11);
|
||||
long t1 = timestamp(2015, Calendar.JANUARY, 16);
|
||||
long t2 = timestamp(2015, Calendar.JANUARY, 17);
|
||||
|
||||
assertThat(DateUtils.truncate(field, t0), equalTo(expected));
|
||||
assertThat(DateUtils.truncate(field, t1), equalTo(expected));
|
||||
assertThat(DateUtils.truncate(field, t2), equalTo(expected));
|
||||
|
||||
expected = timestamp(2015, Calendar.JANUARY, 18);
|
||||
t0 = timestamp(2015, Calendar.JANUARY, 18);
|
||||
t1 = timestamp(2015, Calendar.JANUARY, 19);
|
||||
t2 = timestamp(2015, Calendar.JANUARY, 24);
|
||||
|
||||
assertThat(DateUtils.truncate(field, t0), equalTo(expected));
|
||||
assertThat(DateUtils.truncate(field, t1), equalTo(expected));
|
||||
assertThat(DateUtils.truncate(field, t2), equalTo(expected));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTruncate_month()
|
||||
{
|
||||
long expected = timestamp(2016, Calendar.JUNE, 1);
|
||||
long t0 = timestamp(2016, Calendar.JUNE, 1);
|
||||
long t1 = timestamp(2016, Calendar.JUNE, 15);
|
||||
long t2 = timestamp(2016, Calendar.JUNE, 20);
|
||||
|
||||
DateUtils.TruncateField field = DateUtils.TruncateField.MONTH;
|
||||
|
||||
assertThat(DateUtils.truncate(field, t0), equalTo(expected));
|
||||
assertThat(DateUtils.truncate(field, t1), equalTo(expected));
|
||||
assertThat(DateUtils.truncate(field, t2), equalTo(expected));
|
||||
|
||||
expected = timestamp(2016, Calendar.DECEMBER, 1);
|
||||
t0 = timestamp(2016, Calendar.DECEMBER, 1);
|
||||
t1 = timestamp(2016, Calendar.DECEMBER, 15);
|
||||
t2 = timestamp(2016, Calendar.DECEMBER, 31);
|
||||
|
||||
assertThat(DateUtils.truncate(field, t0), equalTo(expected));
|
||||
assertThat(DateUtils.truncate(field, t1), equalTo(expected));
|
||||
assertThat(DateUtils.truncate(field, t2), equalTo(expected));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTruncate_quarter()
|
||||
{
|
||||
DateUtils.TruncateField field = DateUtils.TruncateField.QUARTER;
|
||||
|
||||
long expected = timestamp(2016, Calendar.JANUARY, 1);
|
||||
long t0 = timestamp(2016, Calendar.JANUARY, 20);
|
||||
long t1 = timestamp(2016, Calendar.FEBRUARY, 15);
|
||||
long t2 = timestamp(2016, Calendar.MARCH, 30);
|
||||
|
||||
assertThat(DateUtils.truncate(field, t0), equalTo(expected));
|
||||
assertThat(DateUtils.truncate(field, t1), equalTo(expected));
|
||||
assertThat(DateUtils.truncate(field, t2), equalTo(expected));
|
||||
|
||||
expected = timestamp(2016, Calendar.APRIL, 1);
|
||||
t0 = timestamp(2016, Calendar.APRIL, 1);
|
||||
t1 = timestamp(2016, Calendar.MAY, 30);
|
||||
t2 = timestamp(2016, Calendar.JUNE, 20);
|
||||
|
||||
assertThat(DateUtils.truncate(field, t0), equalTo(expected));
|
||||
assertThat(DateUtils.truncate(field, t1), equalTo(expected));
|
||||
assertThat(DateUtils.truncate(field, t2), equalTo(expected));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTruncate_year()
|
||||
{
|
||||
DateUtils.TruncateField field = DateUtils.TruncateField.YEAR;
|
||||
|
||||
long expected = timestamp(2016, Calendar.JANUARY, 1);
|
||||
long t0 = timestamp(2016, Calendar.JANUARY, 1);
|
||||
long t1 = timestamp(2016, Calendar.FEBRUARY, 25);
|
||||
long t2 = timestamp(2016, Calendar.DECEMBER, 31);
|
||||
|
||||
assertThat(DateUtils.truncate(field, t0), equalTo(expected));
|
||||
assertThat(DateUtils.truncate(field, t1), equalTo(expected));
|
||||
assertThat(DateUtils.truncate(field, t2), equalTo(expected));
|
||||
|
||||
expected = timestamp(2017, Calendar.JANUARY, 1);
|
||||
t0 = timestamp(2017, Calendar.JANUARY, 1);
|
||||
t1 = timestamp(2017, Calendar.MAY, 30);
|
||||
t2 = timestamp(2017, Calendar.DECEMBER, 31);
|
||||
|
||||
assertThat(DateUtils.truncate(field, t0), equalTo(expected));
|
||||
assertThat(DateUtils.truncate(field, t1), equalTo(expected));
|
||||
assertThat(DateUtils.truncate(field, t2), equalTo(expected));
|
||||
}
|
||||
|
||||
private void log(long timestamp)
|
||||
{
|
||||
DateFormat df = SimpleDateFormat.getDateTimeInstance();
|
||||
df.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
log("%s", df.format(new Date(timestamp)));
|
||||
}
|
||||
|
||||
public long timestamp(int year, int month, int day)
|
||||
{
|
||||
GregorianCalendar cal = DateUtils.getStartOfTodayCalendar();
|
||||
cal.set(year, month, day);
|
||||
return cal.getTimeInMillis();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user