Replace Long by Timestamp

pull/316/head^2
Alinson S. Xavier 8 years ago
parent 882ddba324
commit a8aa6f192c

@ -210,9 +210,8 @@ public class BaseAndroidTest extends TestCase
Debug.stopMethodTracing(); Debug.stopMethodTracing();
} }
protected Long day(int offset) protected Timestamp day(int offset)
{ {
return DateUtils.getStartOfToday() - return DateUtils.getToday().minus(offset);
offset * DateUtils.millisecondsInOneDay;
} }
} }

@ -86,7 +86,7 @@ public class BaseUserInterfaceTest
{ {
prefs.reset(); prefs.reset();
prefs.setFirstRun(false); prefs.setFirstRun(false);
prefs.updateLastHint(100, DateUtils.getStartOfToday()); prefs.updateLastHint(100, DateUtils.getToday());
habitList.removeAll(); habitList.removeAll();
cache.refreshAllHabits(); cache.refreshAllHabits();
Thread.sleep(1000); Thread.sleep(1000);

@ -66,14 +66,13 @@ public class HabitFixtures
habit.setFrequency(new Frequency(3, 7)); habit.setFrequency(new Frequency(3, 7));
habit.setColor(7); habit.setColor(7);
long day = DateUtils.millisecondsInOneDay; Timestamp today = DateUtils.getToday();
long today = DateUtils.getStartOfToday();
int marks[] = { 0, 1, 3, 5, 7, 8, 9, 10, 12, 14, 15, 17, 19, 20, 26, 27, int marks[] = { 0, 1, 3, 5, 7, 8, 9, 10, 12, 14, 15, 17, 19, 20, 26, 27,
28, 50, 51, 52, 53, 54, 58, 60, 63, 65, 70, 71, 72, 73, 74, 75, 80, 28, 50, 51, 52, 53, 54, 58, 60, 63, 65, 70, 71, 72, 73, 74, 75, 80,
81, 83, 89, 90, 91, 95, 102, 103, 108, 109, 120}; 81, 83, 89, 90, 91, 95, 102, 103, 108, 109, 120};
for (int mark : marks) for (int mark : marks)
habit.getRepetitions().toggle(today - mark * day); habit.getRepetitions().toggle(today.minus(mark));
return habit; return habit;
} }
@ -89,12 +88,12 @@ public class HabitFixtures
habit.setUnit("steps"); habit.setUnit("steps");
habitList.add(habit); habitList.add(habit);
long timestamp = DateUtils.getStartOfToday(); Timestamp timestamp = DateUtils.getToday();
for (int value : LONG_NUMERICAL_HABIT_CHECKS) for (int value : LONG_NUMERICAL_HABIT_CHECKS)
{ {
Repetition r = new Repetition(timestamp, value); Repetition r = new Repetition(timestamp, value);
habit.getRepetitions().add(r); habit.getRepetitions().add(r);
timestamp -= DateUtils.millisecondsInOneDay; timestamp = timestamp.minus(1);
} }
return habit; return habit;
@ -108,11 +107,11 @@ public class HabitFixtures
habit.setFrequency(new Frequency(2, 3)); habit.setFrequency(new Frequency(2, 3));
habitList.add(habit); habitList.add(habit);
long timestamp = DateUtils.getStartOfToday(); Timestamp timestamp = DateUtils.getToday();
for (boolean c : LONG_HABIT_CHECKS) for (boolean c : LONG_HABIT_CHECKS)
{ {
if (c) habit.getRepetitions().toggle(timestamp); if (c) habit.getRepetitions().toggle(timestamp);
timestamp -= DateUtils.millisecondsInOneDay; timestamp = timestamp.minus(1);
} }
return habit; return habit;

@ -44,10 +44,9 @@ public class BarChartTest extends BaseViewTest
super.setUp(); super.setUp();
Habit habit = fixtures.createLongNumericalHabit(); Habit habit = fixtures.createLongNumericalHabit();
view = new BarChart(targetContext); view = new BarChart(targetContext);
long today = DateUtils.getStartOfToday(); Timestamp today = DateUtils.getToday();
long day = DateUtils.millisecondsInOneDay;
CheckmarkList checkmarks = habit.getCheckmarks(); CheckmarkList checkmarks = habit.getCheckmarks();
view.setCheckmarks(checkmarks.getByInterval(today - 20 * day, today)); view.setCheckmarks(checkmarks.getByInterval(today.minus(20), today));
view.setColor(PaletteUtils.getColor(targetContext, habit.getColor())); view.setColor(PaletteUtils.getColor(targetContext, habit.getColor()));
view.setTarget(200.0); view.setTarget(200.0);
measureView(view, dpToPixels(300), dpToPixels(200)); measureView(view, dpToPixels(300), dpToPixels(200));

@ -24,6 +24,7 @@ import android.support.test.runner.*
import org.hamcrest.CoreMatchers.* import org.hamcrest.CoreMatchers.*
import org.hamcrest.MatcherAssert.* import org.hamcrest.MatcherAssert.*
import org.isoron.uhabits.* import org.isoron.uhabits.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.models.Checkmark.* import org.isoron.uhabits.core.models.Checkmark.*
import org.isoron.uhabits.utils.* import org.isoron.uhabits.utils.*
import org.junit.* import org.junit.*
@ -89,7 +90,7 @@ class CheckmarkPanelViewTest : BaseViewTest() {
@Test @Test
fun testToggle() { fun testToggle() {
var timestamps = mutableListOf<Long>() var timestamps = mutableListOf<Timestamp>()
view.onToggle = { timestamps.add(it) } view.onToggle = { timestamps.add(it) }
view.buttons[0].performLongClick() view.buttons[0].performLongClick()
view.buttons[2].performLongClick() view.buttons[2].performLongClick()
@ -99,12 +100,12 @@ class CheckmarkPanelViewTest : BaseViewTest() {
@Test @Test
fun testToggle_withOffset() { fun testToggle_withOffset() {
var timestamps = LongArray(0) val timestamps = mutableListOf<Timestamp>()
view.dataOffset = 3 view.dataOffset = 3
view.onToggle = { timestamps += it } view.onToggle = { timestamps += it }
view.buttons[0].performLongClick() view.buttons[0].performLongClick()
view.buttons[2].performLongClick() view.buttons[2].performLongClick()
view.buttons[3].performLongClick() view.buttons[3].performLongClick()
assertThat(timestamps, equalTo(longArrayOf(day(3), day(5), day(6)))) assertThat(timestamps, equalTo(listOf(day(3), day(5), day(6))))
} }
} }

@ -24,6 +24,7 @@ import android.support.test.runner.*
import org.hamcrest.CoreMatchers.* import org.hamcrest.CoreMatchers.*
import org.hamcrest.MatcherAssert.* import org.hamcrest.MatcherAssert.*
import org.isoron.uhabits.* import org.isoron.uhabits.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.utils.* import org.isoron.uhabits.utils.*
import org.junit.* import org.junit.*
import org.junit.runner.* import org.junit.runner.*
@ -84,22 +85,22 @@ class NumberPanelViewTest : BaseViewTest() {
@Test @Test
fun testEdit() { fun testEdit() {
var timestamps = LongArray(0) val timestamps = mutableListOf<Timestamp>()
view.onEdit = { timestamps += it } view.onEdit = { timestamps.plusAssign(it) }
view.buttons[0].performLongClick() view.buttons[0].performLongClick()
view.buttons[2].performLongClick() view.buttons[2].performLongClick()
view.buttons[3].performLongClick() view.buttons[3].performLongClick()
assertThat(timestamps, equalTo(longArrayOf(day(0), day(2), day(3)))) assertThat(timestamps, equalTo(listOf(day(0), day(2), day(3))))
} }
@Test @Test
fun testEdit_withOffset() { fun testEdit_withOffset() {
var timestamps = LongArray(0) val timestamps = mutableListOf<Timestamp>()
view.dataOffset = 3 view.dataOffset = 3
view.onEdit = { timestamps += it } view.onEdit = { timestamps += it }
view.buttons[0].performLongClick() view.buttons[0].performLongClick()
view.buttons[2].performLongClick() view.buttons[2].performLongClick()
view.buttons[3].performLongClick() view.buttons[3].performLongClick()
assertThat(timestamps, equalTo(longArrayOf(day(3), day(5), day(6)))) assertThat(timestamps, equalTo(listOf(day(3), day(5), day(6))))
} }
} }

@ -28,7 +28,7 @@ import org.junit.*;
import org.junit.runner.*; import org.junit.runner.*;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
@LargeTest @MediumTest
public class PerformanceTest extends BaseAndroidTest public class PerformanceTest extends BaseAndroidTest
{ {
private Habit habit; private Habit habit;

@ -115,14 +115,12 @@ public class BarChart extends ScrollableChart
Random random = new Random(); Random random = new Random();
List<Checkmark> checkmarks = new LinkedList<>(); List<Checkmark> checkmarks = new LinkedList<>();
long timestamp = DateUtils.getStartOfToday(); Timestamp today = DateUtils.getToday();
long day = DateUtils.millisecondsInOneDay;
for (int i = 1; i < 100; i++) for (int i = 1; i < 100; i++)
{ {
int value = random.nextInt(1000); int value = random.nextInt(1000);
checkmarks.add(new Checkmark(timestamp, value)); checkmarks.add(new Checkmark(today.minus(i), value));
timestamp -= day;
} }
setCheckmarks(checkmarks); setCheckmarks(checkmarks);
@ -205,7 +203,7 @@ public class BarChart extends ScrollableChart
if (offset >= checkmarks.size()) continue; if (offset >= checkmarks.size()) continue;
double value = checkmarks.get(offset).getValue(); double value = checkmarks.get(offset).getValue();
long timestamp = checkmarks.get(offset).getTimestamp(); Timestamp timestamp = checkmarks.get(offset).getTimestamp();
int height = (int) (columnHeight * value / maxValue); int height = (int) (columnHeight * value / maxValue);
rect.set(0, 0, baseSize, height); rect.set(0, 0, baseSize, height);
@ -286,13 +284,13 @@ public class BarChart extends ScrollableChart
if (isTransparencyEnabled) pGraph.setXfermode(XFERMODE_SRC); if (isTransparencyEnabled) pGraph.setXfermode(XFERMODE_SRC);
} }
private void drawFooter(Canvas canvas, RectF rect, long currentDate) private void drawFooter(Canvas canvas, RectF rect, Timestamp currentDate)
{ {
String yearText = dfYear.format(currentDate); String yearText = dfYear.format(currentDate.toJavaDate());
String monthText = dfMonth.format(currentDate); String monthText = dfMonth.format(currentDate.toJavaDate());
String dayText = dfDay.format(currentDate); String dayText = dfDay.format(currentDate.toJavaDate());
GregorianCalendar calendar = DateUtils.getCalendar(currentDate); GregorianCalendar calendar = currentDate.toCalendar();
pText.setColor(textColor); pText.setColor(textColor);
String text; String text;

@ -26,6 +26,7 @@ import android.util.*;
import org.isoron.androidbase.utils.*; import org.isoron.androidbase.utils.*;
import org.isoron.uhabits.*; import org.isoron.uhabits.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.utils.*; import org.isoron.uhabits.core.utils.*;
import org.isoron.uhabits.utils.*; import org.isoron.uhabits.utils.*;
@ -67,7 +68,7 @@ public class FrequencyChart extends ScrollableChart
private boolean isBackgroundTransparent; private boolean isBackgroundTransparent;
@NonNull @NonNull
private HashMap<Long, Integer[]> frequency; private HashMap<Timestamp, Integer[]> frequency;
private int maxFreq; private int maxFreq;
public FrequencyChart(Context context) public FrequencyChart(Context context)
@ -90,23 +91,21 @@ public class FrequencyChart extends ScrollableChart
postInvalidate(); postInvalidate();
} }
public void setFrequency(HashMap<Long, Integer[]> frequency) public void setFrequency(HashMap<Timestamp, Integer[]> frequency)
{ {
this.frequency = frequency; this.frequency = frequency;
maxFreq = getMaxFreq(frequency); maxFreq = getMaxFreq(frequency);
postInvalidate(); postInvalidate();
} }
private int getMaxFreq(HashMap<Long, Integer[]> frequency) private int getMaxFreq(HashMap<Timestamp, Integer[]> frequency)
{ {
int maxValue = 1; int maxValue = 1;
for (Integer[] values : frequency.values()) for (Integer[] values : frequency.values())
{
for (Integer value : values) for (Integer value : values)
{
maxValue = Math.max(value, maxValue); maxValue = Math.max(value, maxValue);
}
}
return maxValue; return maxValue;
} }
@ -194,7 +193,7 @@ public class FrequencyChart extends ScrollableChart
private void drawColumn(Canvas canvas, RectF rect, GregorianCalendar date) private void drawColumn(Canvas canvas, RectF rect, GregorianCalendar date)
{ {
Integer values[] = frequency.get(date.getTimeInMillis()); Integer values[] = frequency.get(new Timestamp(date));
float rowHeight = rect.height() / 8.0f; float rowHeight = rect.height() / 8.0f;
prevRect.set(rect); prevRect.set(rect);
@ -324,7 +323,7 @@ public class FrequencyChart extends ScrollableChart
for (int j = 0; j < 7; j++) for (int j = 0; j < 7; j++)
values[j] = rand.nextInt(5); values[j] = rand.nextInt(5);
frequency.put(date.getTimeInMillis(), values); frequency.put(new Timestamp(date), values);
date.add(Calendar.MONTH, -1); date.add(Calendar.MONTH, -1);
} }
} }

@ -28,14 +28,15 @@ import android.view.*;
import org.isoron.androidbase.utils.*; import org.isoron.androidbase.utils.*;
import org.isoron.uhabits.*; import org.isoron.uhabits.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.utils.*; import org.isoron.uhabits.core.utils.*;
import org.isoron.uhabits.utils.*; import org.isoron.uhabits.utils.*;
import java.text.*; import java.text.*;
import java.util.*; import java.util.*;
import static org.isoron.uhabits.core.models.Checkmark.*;
import static org.isoron.androidbase.utils.InterfaceUtils.*; import static org.isoron.androidbase.utils.InterfaceUtils.*;
import static org.isoron.uhabits.core.models.Checkmark.*;
public class HistoryChart extends ScrollableChart public class HistoryChart extends ScrollableChart
{ {
@ -135,10 +136,11 @@ public class HistoryChart extends ScrollableChart
return false; return false;
} }
final Long timestamp = positionToTimestamp(x, y); final Timestamp timestamp = positionToTimestamp(x, y);
if (timestamp == null) return false; if (timestamp == null) return false;
int offset = timestampToOffset(timestamp); Timestamp today = DateUtils.getToday();
int offset = timestamp.daysUntil(today);
if (offset < checkmarks.length) if (offset < checkmarks.length)
{ {
boolean isChecked = checkmarks[offset] == CHECKED_EXPLICITLY; boolean isChecked = checkmarks[offset] == CHECKED_EXPLICITLY;
@ -435,7 +437,8 @@ public class HistoryChart extends ScrollableChart
baseLocation = new RectF(); baseLocation = new RectF();
} }
private Long positionToTimestamp(float x, float y) @Nullable
private Timestamp positionToTimestamp(float x, float y)
{ {
int col = (int) (x / columnWidth); int col = (int) (x / columnWidth);
int row = (int) (y / columnWidth); int row = (int) (y / columnWidth);
@ -450,15 +453,7 @@ public class HistoryChart extends ScrollableChart
if (DateUtils.getStartOfDay(date.getTimeInMillis()) > if (DateUtils.getStartOfDay(date.getTimeInMillis()) >
DateUtils.getStartOfToday()) return null; DateUtils.getStartOfToday()) return null;
return date.getTimeInMillis(); return new Timestamp(date.getTimeInMillis());
}
private int timestampToOffset(Long timestamp)
{
Long day = DateUtils.millisecondsInOneDay;
Long today = DateUtils.getStartOfToday();
return (int) ((today - timestamp) / day);
} }
private void updateDate() private void updateDate()
@ -478,6 +473,6 @@ public class HistoryChart extends ScrollableChart
public interface Controller public interface Controller
{ {
default void onToggleCheckmark(long timestamp) {} default void onToggleCheckmark(Timestamp timestamp) {}
} }
} }

@ -111,17 +111,15 @@ public class ScoreChart extends ScrollableChart
scores = new LinkedList<>(); scores = new LinkedList<>();
double previous = 0.5f; double previous = 0.5f;
long timestamp = DateUtils.getStartOfToday(); Timestamp timestamp = DateUtils.getToday();
long day = DateUtils.millisecondsInOneDay;
for (int i = 1; i < 100; i++) for (int i = 1; i < 100; i++)
{ {
double step = 0.1f; double step = 0.1f;
double current = previous + random.nextDouble() * step * 2 - step; double current = previous + random.nextDouble() * step * 2 - step;
current = Math.max(0, Math.min(1.0f, current)); current = Math.max(0, Math.min(1.0f, current));
scores.add(new Score(timestamp, current)); scores.add(new Score(timestamp.minus(i), current));
previous = current; previous = current;
timestamp -= day;
} }
} }
@ -189,7 +187,7 @@ public class ScoreChart extends ScrollableChart
if (offset >= scores.size()) continue; if (offset >= scores.size()) continue;
double score = scores.get(offset).getValue(); double score = scores.get(offset).getValue();
long timestamp = scores.get(offset).getTimestamp(); Timestamp timestamp = scores.get(offset).getTimestamp();
int height = (int) (columnHeight * score); int height = (int) (columnHeight * score);
@ -258,13 +256,13 @@ public class ScoreChart extends ScrollableChart
if (isTransparencyEnabled) initCache(width, height); if (isTransparencyEnabled) initCache(width, height);
} }
private void drawFooter(Canvas canvas, RectF rect, long currentDate) private void drawFooter(Canvas canvas, RectF rect, Timestamp currentDate)
{ {
String yearText = dfYear.format(currentDate); String yearText = dfYear.format(currentDate.toJavaDate());
String monthText = dfMonth.format(currentDate); String monthText = dfMonth.format(currentDate.toJavaDate());
String dayText = dfDay.format(currentDate); String dayText = dfDay.format(currentDate.toJavaDate());
GregorianCalendar calendar = DateUtils.getCalendar(currentDate); GregorianCalendar calendar = currentDate.toCalendar();
String text; String text;
int year = calendar.get(Calendar.YEAR); int year = calendar.get(Calendar.YEAR);

@ -97,16 +97,15 @@ public class StreakChart extends View
public void populateWithRandomData() public void populateWithRandomData()
{ {
long day = DateUtils.millisecondsInOneDay; Timestamp start = DateUtils.getToday();
long start = DateUtils.getStartOfToday();
LinkedList<Streak> streaks = new LinkedList<>(); LinkedList<Streak> streaks = new LinkedList<>();
for (int i = 0; i < 10; i++) for (int i = 0; i < 10; i++)
{ {
int length = new Random().nextInt(100); int length = new Random().nextInt(100);
long end = start + length * day; Timestamp end = start.plus(length);
streaks.add(new Streak(start, end)); streaks.add(new Streak(start, end));
start = end + day; start = end.plus(1);
} }
setStreaks(streaks); setStreaks(streaks);
@ -215,8 +214,8 @@ public class StreakChart extends View
if (shouldShowLabels) if (shouldShowLabels)
{ {
String startLabel = dateFormat.format(new Date(streak.getStart())); String startLabel = dateFormat.format(streak.getStart().toJavaDate());
String endLabel = dateFormat.format(new Date(streak.getEnd())); String endLabel = dateFormat.format(streak.getEnd().toJavaDate());
paint.setColor(textColor); paint.setColor(textColor);
paint.setTextAlign(Paint.Align.RIGHT); paint.setTextAlign(Paint.Align.RIGHT);
@ -284,9 +283,9 @@ public class StreakChart extends View
minLength = Math.min(minLength, s.getLength()); minLength = Math.min(minLength, s.getLength());
float lw1 = float lw1 =
paint.measureText(dateFormat.format(new Date(s.getStart()))); paint.measureText(dateFormat.format(s.getStart().toJavaDate()));
float lw2 = float lw2 =
paint.measureText(dateFormat.format(new Date(s.getEnd()))); paint.measureText(dateFormat.format(s.getEnd().toJavaDate()));
maxLabelWidth = Math.max(maxLabelWidth, Math.max(lw1, lw2)); maxLabelWidth = Math.max(maxLabelWidth, Math.max(lw1, lw2));
} }

@ -22,6 +22,7 @@ package org.isoron.uhabits.activities.habits.list.views
import android.content.* import android.content.*
import com.google.auto.factory.* import com.google.auto.factory.*
import org.isoron.androidbase.activities.* import org.isoron.androidbase.activities.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.models.Checkmark.* import org.isoron.uhabits.core.models.Checkmark.*
import org.isoron.uhabits.core.preferences.* import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.core.utils.* import org.isoron.uhabits.core.utils.*
@ -45,7 +46,7 @@ class CheckmarkPanelView(
setupButtons() setupButtons()
} }
var onToggle: (Long) -> Unit = {} var onToggle: (Timestamp) -> Unit = {}
set(value) { set(value) {
field = value field = value
setupButtons() setupButtons()
@ -55,11 +56,10 @@ class CheckmarkPanelView(
@Synchronized @Synchronized
override fun setupButtons() { override fun setupButtons() {
val today = DateUtils.getStartOfToday() val today = DateUtils.getToday()
val day = DateUtils.millisecondsInOneDay
buttons.forEachIndexed { index, button -> buttons.forEachIndexed { index, button ->
val timestamp = today - (index + dataOffset) * day val timestamp = today.minus(index + dataOffset)
button.value = when { button.value = when {
index + dataOffset < values.size -> values[index + dataOffset] index + dataOffset < values.size -> values[index + dataOffset]
else -> UNCHECKED else -> UNCHECKED

@ -171,10 +171,9 @@ class HabitCardView(
updateBackground(isSelected) updateBackground(isSelected)
} }
fun triggerRipple(timestamp: Long) { fun triggerRipple(timestamp: Timestamp) {
val today = DateUtils.getStartOfToday() val today = DateUtils.getToday()
val day = DateUtils.millisecondsInOneDay val offset = timestamp.daysUntil(today) - dataOffset
val offset = ((today - timestamp) / day).toInt() - dataOffset
val button = checkmarkPanel.buttons[offset] val button = checkmarkPanel.buttons[offset]
val y = button.height / 2.0f val y = button.height / 2.0f
val x = checkmarkPanel.x + button.x + (button.width / 2).toFloat() val x = checkmarkPanel.x + button.x + (button.width / 2).toFloat()

@ -22,6 +22,7 @@ package org.isoron.uhabits.activities.habits.list.views
import android.content.* import android.content.*
import com.google.auto.factory.* import com.google.auto.factory.*
import org.isoron.androidbase.activities.* import org.isoron.androidbase.activities.*
import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.preferences.* import org.isoron.uhabits.core.preferences.*
import org.isoron.uhabits.core.utils.* import org.isoron.uhabits.core.utils.*
@ -56,7 +57,7 @@ class NumberPanelView(
setupButtons() setupButtons()
} }
var onEdit: (Long) -> Unit = {} var onEdit: (Timestamp) -> Unit = {}
set(value) { set(value) {
field = value field = value
setupButtons() setupButtons()
@ -66,11 +67,10 @@ class NumberPanelView(
@Synchronized @Synchronized
override fun setupButtons() { override fun setupButtons() {
val day = DateUtils.millisecondsInOneDay val today = DateUtils.getToday()
val today = DateUtils.getStartOfToday()
buttons.forEachIndexed { index, button -> buttons.forEachIndexed { index, button ->
val timestamp = today - (index + dataOffset) * day val timestamp = today.minus(index + dataOffset)
button.value = when { button.value = when {
index + dataOffset < values.size -> values[index + dataOffset] index + dataOffset < values.size -> values[index + dataOffset]
else -> 0.0 else -> 0.0

@ -73,7 +73,7 @@ public class ShowHabitScreen extends BaseScreen
} }
@Override @Override
public void onToggleCheckmark(long timestamp) public void onToggleCheckmark(Timestamp timestamp)
{ {
behavior.get().onToggleCheckmark(timestamp); behavior.get().onToggleCheckmark(timestamp);
} }

@ -98,9 +98,9 @@ public class BarCard extends HabitCard
@Override @Override
public void doInBackground() public void doInBackground()
{ {
long today = DateUtils.getStartOfToday(); Timestamp today = DateUtils.getToday();
List<Checkmark> checkmarks = List<Checkmark> checkmarks =
habit.getCheckmarks().getByInterval(0, today); habit.getCheckmarks().getByInterval(Timestamp.ZERO, today);
chart.setCheckmarks(checkmarks); chart.setCheckmarks(checkmarks);
} }

@ -94,7 +94,7 @@ public class FrequencyCard extends HabitCard
public void doInBackground() public void doInBackground()
{ {
RepetitionList reps = getHabit().getRepetitions(); RepetitionList reps = getHabit().getRepetitions();
HashMap<Long, Integer[]> frequency = reps.getWeekdayFrequency(); HashMap<Timestamp, Integer[]> frequency = reps.getWeekdayFrequency();
chart.setFrequency(frequency); chart.setFrequency(frequency);
} }

@ -164,9 +164,9 @@ public class OverviewCard extends HabitCard
ScoreList scores = habit.getScores(); ScoreList scores = habit.getScores();
long today = DateUtils.getStartOfToday(); Timestamp today = DateUtils.getToday();
long lastMonth = today - 30 * DateUtils.millisecondsInOneDay; Timestamp lastMonth = today.minus(30);
long lastYear = today - 365 * DateUtils.millisecondsInOneDay; Timestamp lastYear = today.minus(365);
cache.todayScore = (float) scores.getTodayValue(); cache.todayScore = (float) scores.getTodayValue();
cache.lastMonthScore = (float) scores.getValue(lastMonth); cache.lastMonthScore = (float) scores.getValue(lastMonth);

@ -45,7 +45,7 @@ class FireSettingReceiver : BroadcastReceiver() {
.build() .build()
allHabits = app.component.habitList allHabits = app.component.habitList
val args = parseIntent(intent) ?: return val args = parseIntent(intent) ?: return
val timestamp = DateUtils.getStartOfToday() val timestamp = DateUtils.getToday()
val controller = component.widgetController val controller = component.widgetController
when (args.action) { when (args.action) {

@ -42,16 +42,16 @@ class IntentParser
return habit return habit
} }
private fun parseTimestamp(intent: Intent): Long { private fun parseTimestamp(intent: Intent): Timestamp {
val today = DateUtils.getStartOfToday() val today = DateUtils.getToday().unixTime
var timestamp = intent.getLongExtra("timestamp", today) var timestamp = intent.getLongExtra("timestamp", today)
timestamp = DateUtils.getStartOfDay(timestamp) timestamp = DateUtils.getStartOfDay(timestamp)
if (timestamp < 0 || timestamp > today) if (timestamp < 0 || timestamp > today)
throw IllegalArgumentException("timestamp is not valid") throw IllegalArgumentException("timestamp is not valid")
return timestamp return Timestamp(timestamp)
} }
class CheckmarkIntentData(var habit: Habit, var timestamp: Long) class CheckmarkIntentData(var habit: Habit, var timestamp: Timestamp)
} }

@ -35,13 +35,13 @@ class PendingIntentFactory
@AppContext private val context: Context, @AppContext private val context: Context,
private val intentFactory: IntentFactory) { private val intentFactory: IntentFactory) {
fun addCheckmark(habit: Habit, timestamp: Long?): PendingIntent = fun addCheckmark(habit: Habit, timestamp: Timestamp?): PendingIntent =
PendingIntent.getBroadcast( PendingIntent.getBroadcast(
context, 1, context, 1,
Intent(context, WidgetReceiver::class.java).apply { Intent(context, WidgetReceiver::class.java).apply {
data = Uri.parse(habit.uriString) data = Uri.parse(habit.uriString)
action = WidgetReceiver.ACTION_ADD_REPETITION action = WidgetReceiver.ACTION_ADD_REPETITION
if (timestamp != null) putExtra("timestamp", timestamp) if (timestamp != null) putExtra("timestamp", timestamp.unixTime)
}, },
FLAG_UPDATE_CURRENT) FLAG_UPDATE_CURRENT)

@ -48,7 +48,7 @@ class AndroidNotificationTray
override fun showNotification(habit: Habit, override fun showNotification(habit: Habit,
notificationId: Int, notificationId: Int,
timestamp: Long, timestamp: Timestamp,
reminderTime: Long) { reminderTime: Long) {
val checkAction = Action( val checkAction = Action(

@ -128,7 +128,7 @@ public class PebbleReceiver extends PebbleDataReceiver
Habit habit = habitList.getById(habitId); Habit habit = habitList.getById(habitId);
if (habit == null) return; if (habit == null) return;
long today = DateUtils.getStartOfToday(); Timestamp today = DateUtils.getToday();
commandRunner.execute( commandRunner.execute(
new ToggleRepetitionCommand(habitList, habit, today), habitId); new ToggleRepetitionCommand(habitList, habit, today), habitId);

@ -58,7 +58,7 @@ public class ReminderController
} }
public void onShowReminder(@NonNull Habit habit, public void onShowReminder(@NonNull Habit habit,
long timestamp, Timestamp timestamp,
long reminderTime) long reminderTime)
{ {
notificationTray.show(habit, timestamp, reminderTime); notificationTray.show(habit, timestamp, reminderTime);

@ -79,8 +79,8 @@ public class ReminderReceiver extends BroadcastReceiver
{ {
case ACTION_SHOW_REMINDER: case ACTION_SHOW_REMINDER:
if (habit == null) return; if (habit == null) return;
reminderController.onShowReminder(habit, timestamp, reminderController.onShowReminder(habit,
reminderTime); new Timestamp(timestamp), reminderTime);
break; break;
case ACTION_DISMISS_REMINDER: case ACTION_DISMISS_REMINDER:

@ -80,8 +80,8 @@ public class ReminderControllerTest extends BaseAndroidJVMTest
public void testOnShowReminder() throws Exception public void testOnShowReminder() throws Exception
{ {
Habit habit = mock(Habit.class); Habit habit = mock(Habit.class);
controller.onShowReminder(habit, 123, 456); controller.onShowReminder(habit, Timestamp.ZERO.plus(100), 456);
verify(notificationTray).show(habit, 123, 456); verify(notificationTray).show(habit, Timestamp.ZERO.plus(100), 456);
verify(reminderScheduler).scheduleAll(); verify(reminderScheduler).scheduleAll();
} }

@ -40,7 +40,7 @@ public class WidgetControllerTest extends BaseAndroidJVMTest
private Habit habit; private Habit habit;
private long today; private Timestamp today;
private NotificationTray notificationTray; private NotificationTray notificationTray;
@ -49,7 +49,7 @@ public class WidgetControllerTest extends BaseAndroidJVMTest
{ {
super.setUp(); super.setUp();
today = DateUtils.getStartOfToday(); today = DateUtils.getToday();
habit = fixtures.createEmptyHabit(); habit = fixtures.createEmptyHabit();
commandRunner = mock(CommandRunner.class); commandRunner = mock(CommandRunner.class);
notificationTray = mock(NotificationTray.class); notificationTray = mock(NotificationTray.class);

@ -31,7 +31,7 @@ public class CreateRepetitionCommand extends Command
@NonNull @NonNull
final Habit habit; final Habit habit;
final long timestamp; final Timestamp timestamp;
final int value; final int value;
@ -42,7 +42,7 @@ public class CreateRepetitionCommand extends Command
Repetition newRep; Repetition newRep;
public CreateRepetitionCommand(@NonNull Habit habit, public CreateRepetitionCommand(@NonNull Habit habit,
long timestamp, Timestamp timestamp,
int value) int value)
{ {
this.timestamp = timestamp; this.timestamp = timestamp;
@ -108,7 +108,7 @@ public class CreateRepetitionCommand extends Command
if(habitId == null) throw new RuntimeException("Habit not saved"); if(habitId == null) throw new RuntimeException("Habit not saved");
this.habit = habitId; this.habit = habitId;
this.repTimestamp = command.timestamp; this.repTimestamp = command.timestamp.getUnixTime();
this.value = command.value; this.value = command.value;
} }
@ -118,7 +118,8 @@ public class CreateRepetitionCommand extends Command
if(h == null) throw new HabitNotFoundException(); if(h == null) throw new HabitNotFoundException();
CreateRepetitionCommand command; CreateRepetitionCommand command;
command = new CreateRepetitionCommand(h, repTimestamp, value); command = new CreateRepetitionCommand(
h, new Timestamp(repTimestamp), value);
command.setId(id); command.setId(id);
return command; return command;
} }

@ -98,7 +98,7 @@ public class EditHabitCommand extends Command
habitList.update(habit); habitList.update(habit);
if (hasFrequencyChanged || hasTargetChanged) if (hasFrequencyChanged || hasTargetChanged)
habit.invalidateNewerThan(0); habit.invalidateNewerThan(Timestamp.ZERO);
} }
public static class Record public static class Record

@ -31,14 +31,14 @@ public class ToggleRepetitionCommand extends Command
@NonNull @NonNull
private HabitList list; private HabitList list;
final long timestamp; final Timestamp timestamp;
@NonNull @NonNull
final Habit habit; final Habit habit;
public ToggleRepetitionCommand(@NonNull HabitList list, public ToggleRepetitionCommand(@NonNull HabitList list,
@NonNull Habit habit, @NonNull Habit habit,
long timestamp) Timestamp timestamp)
{ {
super(); super();
this.list = list; this.list = list;
@ -90,7 +90,7 @@ public class ToggleRepetitionCommand extends Command
Long habitId = command.habit.getId(); Long habitId = command.habit.getId();
if (habitId == null) throw new RuntimeException("Habit not saved"); if (habitId == null) throw new RuntimeException("Habit not saved");
this.repTimestamp = command.timestamp; this.repTimestamp = command.timestamp.getUnixTime();
this.habit = habitId; this.habit = habitId;
} }
@ -100,7 +100,8 @@ public class ToggleRepetitionCommand extends Command
if (h == null) throw new HabitNotFoundException(); if (h == null) throw new HabitNotFoundException();
ToggleRepetitionCommand command; ToggleRepetitionCommand command;
command = new ToggleRepetitionCommand(habitList, h, repTimestamp); command = new ToggleRepetitionCommand(
habitList, h, new Timestamp(repTimestamp));
command.setId(id); command.setId(id);
return command; return command;
} }

@ -76,7 +76,7 @@ public class HabitBullCSVImporter extends AbstractImporter
Calendar date = DateUtils.getStartOfTodayCalendar(); Calendar date = DateUtils.getStartOfTodayCalendar();
date.set(year, month - 1, day); date.set(year, month - 1, day);
long timestamp = date.getTimeInMillis(); Timestamp timestamp = new Timestamp(date.getTimeInMillis());
int value = Integer.parseInt(line[4]); int value = Integer.parseInt(line[4]);
if (value != 1) continue; if (value != 1) continue;

@ -173,9 +173,9 @@ public class HabitsCSVExporter
writeMultipleHabitsHeader(scoresWriter); writeMultipleHabitsHeader(scoresWriter);
writeMultipleHabitsHeader(checksWriter); writeMultipleHabitsHeader(checksWriter);
long[] timeframe = getTimeframe(); Timestamp[] timeframe = getTimeframe();
long oldest = timeframe[0]; Timestamp oldest = timeframe[0];
long newest = DateUtils.getStartOfToday(); Timestamp newest = DateUtils.getToday();
List<int[]> checkmarks = new ArrayList<>(); List<int[]> checkmarks = new ArrayList<>();
List<double[]> scores = new ArrayList<>(); List<double[]> scores = new ArrayList<>();
@ -185,11 +185,11 @@ public class HabitsCSVExporter
scores.add(h.getScores().getValues(oldest, newest)); scores.add(h.getScores().getValues(oldest, newest));
} }
int days = DateUtils.getDaysBetween(oldest, newest); int days = oldest.daysUntil(newest);
SimpleDateFormat dateFormat = DateFormats.getCSVDateFormat(); SimpleDateFormat dateFormat = DateFormats.getCSVDateFormat();
for (int i = 0; i <= days; i++) for (int i = 0; i <= days; i++)
{ {
Date day = new Date(newest - i * DateUtils.millisecondsInOneDay); Date day = newest.minus(i).toJavaDate();
String date = dateFormat.format(day); String date = dateFormat.format(day);
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
@ -238,20 +238,20 @@ public class HabitsCSVExporter
* *
* @return the timeframe containing the oldest timestamp and the newest timestamp * @return the timeframe containing the oldest timestamp and the newest timestamp
*/ */
private long[] getTimeframe() private Timestamp[] getTimeframe()
{ {
long oldest = Long.MAX_VALUE; Timestamp oldest = Timestamp.ZERO.plus(1000000);
long newest = -1; Timestamp newest = Timestamp.ZERO;
for (Habit h : selectedHabits) for (Habit h : selectedHabits)
{ {
if(h.getRepetitions().getOldest() == null || h.getRepetitions().getNewest() == null) if(h.getRepetitions().getOldest() == null || h.getRepetitions().getNewest() == null)
continue; continue;
long currOld = h.getRepetitions().getOldest().getTimestamp(); Timestamp currOld = h.getRepetitions().getOldest().getTimestamp();
long currNew = h.getRepetitions().getNewest().getTimestamp(); Timestamp currNew = h.getRepetitions().getNewest().getTimestamp();
oldest = currOld > oldest ? oldest : currOld; oldest = currOld.isOlderThan(oldest) ? oldest : currOld;
newest = currNew < newest ? newest : currNew; newest = currNew.isNewerThan(newest) ? newest : currNew;
} }
return new long[]{oldest, newest}; return new Timestamp[]{oldest, newest};
} }
private String writeZipFile() throws IOException private String writeZipFile() throws IOException

@ -109,7 +109,7 @@ public class LoopDBImporter extends AbstractImporter
habitRecord.id.toString()); habitRecord.id.toString());
for (RepetitionRecord r : reps) for (RepetitionRecord r : reps)
h.getRepetitions().toggle(r.timestamp, r.value); h.getRepetitions().toggle(new Timestamp(r.timestamp), r.value);
} }
} }
} }

@ -165,7 +165,7 @@ public class RewireDBImporter extends AbstractImporter
GregorianCalendar cal = DateUtils.getStartOfTodayCalendar(); GregorianCalendar cal = DateUtils.getStartOfTodayCalendar();
cal.set(year, month - 1, day); cal.set(year, month - 1, day);
habit.getRepetitions().toggle(cal.getTimeInMillis()); habit.getRepetitions().toggle(new Timestamp(cal));
} while (c.moveToNext()); } while (c.moveToNext());
} }
finally finally

@ -100,7 +100,7 @@ public class TickmateDBImporter extends AbstractImporter
GregorianCalendar cal = DateUtils.getStartOfTodayCalendar(); GregorianCalendar cal = DateUtils.getStartOfTodayCalendar();
cal.set(year, month, day); cal.set(year, month, day);
habit.getRepetitions().toggle(cal.getTimeInMillis()); habit.getRepetitions().toggle(new Timestamp(cal));
} while (c.moveToNext()); } while (c.moveToNext());
} }
finally finally

@ -52,7 +52,7 @@ public final class Checkmark
*/ */
public static final int UNCHECKED = 0; public static final int UNCHECKED = 0;
private final long timestamp; private final Timestamp timestamp;
/** /**
* The value of the checkmark. * The value of the checkmark.
@ -65,17 +65,12 @@ public final class Checkmark
*/ */
private final int value; private final int value;
public Checkmark(long timestamp, int value) public Checkmark(Timestamp timestamp, int value)
{ {
this.timestamp = timestamp; this.timestamp = timestamp;
this.value = value; this.value = value;
} }
public int compareNewer(Checkmark other)
{
return Long.signum(this.getTimestamp() - other.getTimestamp());
}
@Override @Override
public boolean equals(Object o) public boolean equals(Object o)
{ {
@ -91,7 +86,7 @@ public final class Checkmark
.isEquals(); .isEquals();
} }
public long getTimestamp() public Timestamp getTimestamp()
{ {
return timestamp; return timestamp;
} }

@ -30,10 +30,7 @@ import java.util.*;
import javax.annotation.concurrent.*; import javax.annotation.concurrent.*;
import static java.lang.Math.min; import static org.isoron.uhabits.core.models.Checkmark.*;
import static org.isoron.uhabits.core.models.Checkmark.CHECKED_EXPLICITLY;
import static org.isoron.uhabits.core.models.Checkmark.CHECKED_IMPLICITLY;
import static org.isoron.uhabits.core.models.Checkmark.UNCHECKED;
/** /**
* The collection of {@link Checkmark}s belonging to a habit. * The collection of {@link Checkmark}s belonging to a habit.
@ -57,32 +54,30 @@ public abstract class CheckmarkList
{ {
if (reps.length == 0) throw new IllegalArgumentException(); if (reps.length == 0) throw new IllegalArgumentException();
long day = DateUtils.millisecondsInOneDay; Timestamp today = DateUtils.getToday();
long today = DateUtils.getStartOfToday(); Timestamp begin = reps[0].getTimestamp();
if (intervals.size() > 0) begin = Timestamp.oldest(begin, intervals.get(0).begin);
long begin = reps[0].getTimestamp();
if (intervals.size() > 0) begin = min(begin, intervals.get(0).begin);
int nDays = (int) ((today - begin) / day) + 1;
int nDays = begin.daysUntil(today) + 1;
List<Checkmark> checkmarks = new ArrayList<>(nDays); List<Checkmark> checkmarks = new ArrayList<>(nDays);
for (int i = 0; i < nDays; i++) for (int i = 0; i < nDays; i++)
checkmarks.add(new Checkmark(today - day * i, UNCHECKED)); checkmarks.add(new Checkmark(today.minus(i), UNCHECKED));
for (Interval interval : intervals) for (Interval interval : intervals)
{ {
for (long date = interval.begin; date <= interval.end; date += day) for (int i = 0; i < interval.length(); i++)
{ {
if (date > today) continue; Timestamp date = interval.begin.plus(i);
int offset = (int) ((today - date) / day); int offset = date.daysUntil(today);
if (offset < 0) continue;
checkmarks.set(offset, new Checkmark(date, CHECKED_IMPLICITLY)); checkmarks.set(offset, new Checkmark(date, CHECKED_IMPLICITLY));
} }
} }
for (Repetition rep : reps) for (Repetition rep : reps)
{ {
long date = rep.getTimestamp(); Timestamp date = rep.getTimestamp();
int offset = (int) ((today - date) / day); int offset = date.daysUntil(today);
checkmarks.set(offset, new Checkmark(date, CHECKED_EXPLICITLY)); checkmarks.set(offset, new Checkmark(date, CHECKED_EXPLICITLY));
} }
@ -104,7 +99,6 @@ public abstract class CheckmarkList
static ArrayList<Interval> buildIntervals(@NonNull Frequency freq, static ArrayList<Interval> buildIntervals(@NonNull Frequency freq,
@NonNull Repetition[] reps) @NonNull Repetition[] reps)
{ {
long day = DateUtils.millisecondsInOneDay;
int num = freq.getNumerator(); int num = freq.getNumerator();
int den = freq.getDenominator(); int den = freq.getDenominator();
@ -114,12 +108,12 @@ public abstract class CheckmarkList
Repetition first = reps[i]; Repetition first = reps[i];
Repetition last = reps[i + num - 1]; Repetition last = reps[i + num - 1];
long distance = (last.getTimestamp() - first.getTimestamp()) / day; long distance = first.getTimestamp().daysUntil(last.getTimestamp());
if (distance >= den) continue; if (distance >= den) continue;
long begin = first.getTimestamp(); Timestamp begin = first.getTimestamp();
long center = last.getTimestamp(); Timestamp center = last.getTimestamp();
long end = begin + (den - 1) * day; Timestamp end = begin.plus(den - 1);
intervals.add(new Interval(begin, center, end)); intervals.add(new Interval(begin, center, end));
} }
@ -134,17 +128,15 @@ public abstract class CheckmarkList
*/ */
static void snapIntervalsTogether(@NonNull ArrayList<Interval> intervals) static void snapIntervalsTogether(@NonNull ArrayList<Interval> intervals)
{ {
long day = DateUtils.millisecondsInOneDay;
for (int i = 1; i < intervals.size(); i++) for (int i = 1; i < intervals.size(); i++)
{ {
Interval curr = intervals.get(i); Interval curr = intervals.get(i);
Interval prev = intervals.get(i - 1); Interval prev = intervals.get(i - 1);
long distance = curr.begin - prev.end - day; int gap = prev.end.daysUntil(curr.begin) - 1;
if (distance <= 0 || curr.end - distance < curr.center) continue; if (gap <= 0 || curr.end.minus(gap).isOlderThan(curr.center)) continue;
intervals.set(i, new Interval(curr.begin - distance, curr.center, intervals.set(i, new Interval(curr.begin.minus(gap), curr.center,
curr.end - distance)); curr.end.minus(gap)));
} }
} }
@ -176,8 +168,8 @@ public abstract class CheckmarkList
Repetition oldestRep = habit.getRepetitions().getOldest(); Repetition oldestRep = habit.getRepetitions().getOldest();
if (oldestRep == null) return new int[0]; if (oldestRep == null) return new int[0];
Long fromTimestamp = oldestRep.getTimestamp(); Timestamp fromTimestamp = oldestRep.getTimestamp();
Long toTimestamp = DateUtils.getStartOfToday(); Timestamp toTimestamp = DateUtils.getToday();
return getValues(fromTimestamp, toTimestamp); return getValues(fromTimestamp, toTimestamp);
} }
@ -195,8 +187,8 @@ public abstract class CheckmarkList
* @return the list of checkmarks within the interval. * @return the list of checkmarks within the interval.
*/ */
@NonNull @NonNull
public abstract List<Checkmark> getByInterval(long fromTimestamp, public abstract List<Checkmark> getByInterval(Timestamp fromTimestamp,
long toTimestamp); Timestamp toTimestamp);
/** /**
* Returns the checkmark for today. * Returns the checkmark for today.
@ -207,7 +199,7 @@ public abstract class CheckmarkList
public synchronized final Checkmark getToday() public synchronized final Checkmark getToday()
{ {
compute(); compute();
long today = DateUtils.getStartOfToday(); Timestamp today = DateUtils.getToday();
return getByInterval(today, today).get(0); return getByInterval(today, today).get(0);
} }
@ -236,9 +228,9 @@ public abstract class CheckmarkList
* @param to timestamp for the newest checkmark * @param to timestamp for the newest checkmark
* @return values for the checkmarks inside the given interval * @return values for the checkmarks inside the given interval
*/ */
public final int[] getValues(long from, long to) public final int[] getValues(Timestamp from, Timestamp to)
{ {
if (from > to) return new int[0]; if (from.isNewerThan(to)) return new int[0];
List<Checkmark> checkmarks = getByInterval(from, to); List<Checkmark> checkmarks = getByInterval(from, to);
int values[] = new int[checkmarks.size()]; int values[] = new int[checkmarks.size()];
@ -257,7 +249,7 @@ public abstract class CheckmarkList
* *
* @param timestamp the timestamp * @param timestamp the timestamp
*/ */
public abstract void invalidateNewerThan(long timestamp); public abstract void invalidateNewerThan(Timestamp timestamp);
/** /**
* Writes the entire list of checkmarks to the given writer, in CSV format. * Writes the entire list of checkmarks to the given writer, in CSV format.
@ -275,14 +267,14 @@ public abstract class CheckmarkList
values = getAllValues(); values = getAllValues();
} }
long timestamp = DateUtils.getStartOfToday(); Timestamp timestamp = DateUtils.getToday();
SimpleDateFormat dateFormat = DateFormats.getCSVDateFormat(); SimpleDateFormat dateFormat = DateFormats.getCSVDateFormat();
for (int value : values) for (int value : values)
{ {
String date = dateFormat.format(new Date(timestamp)); String date = dateFormat.format(timestamp.toJavaDate());
out.write(String.format("%s,%d\n", date, value)); out.write(String.format("%s,%d\n", date, value));
timestamp -= DateUtils.millisecondsInOneDay; timestamp = timestamp.minus(1);
} }
} }
@ -292,15 +284,15 @@ public abstract class CheckmarkList
*/ */
protected final synchronized void compute() protected final synchronized void compute()
{ {
final long today = DateUtils.getStartOfToday(); final Timestamp today = DateUtils.getToday();
Checkmark newest = getNewestComputed(); Checkmark newest = getNewestComputed();
if (newest != null && newest.getTimestamp() == today) return; if (newest != null && newest.getTimestamp().equals(today)) return;
invalidateNewerThan(0); invalidateNewerThan(Timestamp.ZERO);
Repetition oldestRep = habit.getRepetitions().getOldest(); Repetition oldestRep = habit.getRepetitions().getOldest();
if (oldestRep == null) return; if (oldestRep == null) return;
final long from = oldestRep.getTimestamp(); final Timestamp from = oldestRep.getTimestamp();
Repetition reps[] = habit Repetition reps[] = habit
.getRepetitions() .getRepetitions()
@ -331,20 +323,18 @@ public abstract class CheckmarkList
{ {
if (reps.length == 0) throw new IllegalArgumentException(); if (reps.length == 0) throw new IllegalArgumentException();
long day = DateUtils.millisecondsInOneDay; Timestamp today = DateUtils.getToday();
long today = DateUtils.getStartOfToday(); Timestamp begin = reps[0].getTimestamp();
long begin = reps[0].getTimestamp();
int nDays = (int) ((today - begin) / day) + 1; int nDays = begin.daysUntil(today) + 1;
List<Checkmark> checkmarks = new ArrayList<>(nDays); List<Checkmark> checkmarks = new ArrayList<>(nDays);
for (int i = 0; i < nDays; i++) for (int i = 0; i < nDays; i++)
checkmarks.add(new Checkmark(today - day * i, 0)); checkmarks.add(new Checkmark(today.minus(i), 0));
for (Repetition rep : reps) for (Repetition rep : reps)
{ {
long date = rep.getTimestamp(); int offset = rep.getTimestamp().daysUntil(today);
int offset = (int) ((today - date) / day); checkmarks.set(offset, new Checkmark(rep.getTimestamp(), rep.getValue()));
checkmarks.set(offset, new Checkmark(date, rep.getValue()));
} }
add(checkmarks); add(checkmarks);
@ -360,19 +350,23 @@ public abstract class CheckmarkList
static class Interval static class Interval
{ {
final long begin; final Timestamp begin;
final long center; final Timestamp center;
final long end; final Timestamp end;
Interval(long begin, long center, long end) Interval(Timestamp begin, Timestamp center, Timestamp end)
{ {
this.begin = begin; this.begin = begin;
this.center = center; this.center = center;
this.end = end; this.end = end;
} }
public int length() {
return begin.daysUntil(end) + 1;
}
@Override @Override
public boolean equals(Object o) public boolean equals(Object o)
{ {

@ -303,7 +303,7 @@ public class Habit
return data.reminder != null; return data.reminder != null;
} }
public void invalidateNewerThan(long timestamp) public void invalidateNewerThan(Timestamp timestamp)
{ {
getScores().invalidateNewerThan(timestamp); getScores().invalidateNewerThan(timestamp);
getCheckmarks().invalidateNewerThan(timestamp); getCheckmarks().invalidateNewerThan(timestamp);

@ -162,9 +162,9 @@ public abstract class HabitList implements Iterable<Habit>
{ {
for (Habit h : this) for (Habit h : this)
{ {
h.getCheckmarks().invalidateNewerThan(0); h.getCheckmarks().invalidateNewerThan(Timestamp.ZERO);
h.getStreaks().invalidateNewerThan(0); h.getStreaks().invalidateNewerThan(Timestamp.ZERO);
h.getScores().invalidateNewerThan(0); h.getScores().invalidateNewerThan(Timestamp.ZERO);
} }
} }

@ -28,7 +28,7 @@ import org.apache.commons.lang3.builder.*;
public final class Repetition public final class Repetition
{ {
private final long timestamp; private final Timestamp timestamp;
/** /**
* The value of the repetition. * The value of the repetition.
@ -47,7 +47,7 @@ public final class Repetition
* *
* @param timestamp the time this repetition occurred. * @param timestamp the time this repetition occurred.
*/ */
public Repetition(long timestamp, int value) public Repetition(Timestamp timestamp, int value)
{ {
this.timestamp = timestamp; this.timestamp = timestamp;
this.value = value; this.value = value;
@ -67,7 +67,7 @@ public final class Repetition
.isEquals(); .isEquals();
} }
public long getTimestamp() public Timestamp getTimestamp()
{ {
return timestamp; return timestamp;
} }

@ -60,7 +60,7 @@ public abstract class RepetitionList
* @return true if list contains repetition with given timestamp, false * @return true if list contains repetition with given timestamp, false
* otherwise. * otherwise.
*/ */
public boolean containsTimestamp(long timestamp) public boolean containsTimestamp(Timestamp timestamp)
{ {
return (getByTimestamp(timestamp) != null); return (getByTimestamp(timestamp) != null);
} }
@ -77,8 +77,8 @@ public abstract class RepetitionList
* @param toTimestamp timestamp of the end of the interval * @param toTimestamp timestamp of the end of the interval
* @return list of repetitions within given time interval * @return list of repetitions within given time interval
*/ */
public abstract List<Repetition> getByInterval(long fromTimestamp, public abstract List<Repetition> getByInterval(Timestamp fromTimestamp,
long toTimestamp); Timestamp toTimestamp);
/** /**
* Returns the repetition that has the given timestamp, or null if none * Returns the repetition that has the given timestamp, or null if none
@ -88,7 +88,7 @@ public abstract class RepetitionList
* @return the repetition that has the given timestamp. * @return the repetition that has the given timestamp.
*/ */
@Nullable @Nullable
public abstract Repetition getByTimestamp(long timestamp); public abstract Repetition getByTimestamp(Timestamp timestamp);
@NonNull @NonNull
public ModelObservable getObservable() public ModelObservable getObservable()
@ -132,18 +132,19 @@ public abstract class RepetitionList
* @return total number of repetitions by month versus day of week * @return total number of repetitions by month versus day of week
*/ */
@NonNull @NonNull
public HashMap<Long, Integer[]> getWeekdayFrequency() public HashMap<Timestamp, Integer[]> getWeekdayFrequency()
{ {
List<Repetition> reps = getByInterval(0, DateUtils.getStartOfToday()); List<Repetition> reps =
HashMap<Long, Integer[]> map = new HashMap<>(); getByInterval(Timestamp.ZERO, DateUtils.getToday());
HashMap<Timestamp, Integer[]> map = new HashMap<>();
for (Repetition r : reps) for (Repetition r : reps)
{ {
Calendar date = DateUtils.getCalendar(r.getTimestamp()); Calendar date = r.getTimestamp().toCalendar();
int weekday = DateUtils.getWeekday(r.getTimestamp()); int weekday = r.getTimestamp().getWeekday();
date.set(Calendar.DAY_OF_MONTH, 1); date.set(Calendar.DAY_OF_MONTH, 1);
long timestamp = date.getTimeInMillis(); Timestamp timestamp = new Timestamp(date.getTimeInMillis());
Integer[] list = map.get(timestamp); Integer[] list = map.get(timestamp);
if (list == null) if (list == null)
@ -184,14 +185,12 @@ public abstract class RepetitionList
* @return the repetition that has been added or removed. * @return the repetition that has been added or removed.
*/ */
@NonNull @NonNull
public Repetition toggle(long timestamp) public synchronized Repetition toggle(Timestamp timestamp)
{ {
if(habit.isNumerical()) if(habit.isNumerical())
throw new IllegalStateException("habit must NOT be numerical"); throw new IllegalStateException("habit must NOT be numerical");
timestamp = DateUtils.getStartOfDay(timestamp);
Repetition rep = getByTimestamp(timestamp); Repetition rep = getByTimestamp(timestamp);
if (rep != null) remove(rep); if (rep != null) remove(rep);
else else
{ {
@ -211,7 +210,7 @@ public abstract class RepetitionList
@NonNull @NonNull
public abstract long getTotalCount(); public abstract long getTotalCount();
public void toggle(long timestamp, int value) public void toggle(Timestamp timestamp, int value)
{ {
Repetition rep = getByTimestamp(timestamp); Repetition rep = getByTimestamp(timestamp);
if(rep != null) remove(rep); if(rep != null) remove(rep);

@ -32,14 +32,14 @@ public final class Score
* Timestamp of the day to which this score applies. Time of day should be * Timestamp of the day to which this score applies. Time of day should be
* midnight (UTC). * midnight (UTC).
*/ */
private final long timestamp; private final Timestamp timestamp;
/** /**
* Value of the score. * Value of the score.
*/ */
private final double value; private final double value;
public Score(long timestamp, double value) public Score(Timestamp timestamp, double value)
{ {
this.timestamp = timestamp; this.timestamp = timestamp;
this.value = value; this.value = value;
@ -72,10 +72,10 @@ public final class Score
public int compareNewer(Score other) public int compareNewer(Score other)
{ {
return Long.signum(this.getTimestamp() - other.getTimestamp()); return getTimestamp().compare(other.getTimestamp());
} }
public long getTimestamp() public Timestamp getTimestamp()
{ {
return timestamp; return timestamp;
} }

@ -69,7 +69,7 @@ public abstract class ScoreList implements Iterable<Score>
*/ */
public double getTodayValue() public double getTodayValue()
{ {
return getValue(DateUtils.getStartOfToday()); return getValue(DateUtils.getToday());
} }
/** /**
@ -81,11 +81,11 @@ public abstract class ScoreList implements Iterable<Score>
* @param timestamp the timestamp of a day * @param timestamp the timestamp of a day
* @return score value for that day * @return score value for that day
*/ */
public final synchronized double getValue(long timestamp) public final synchronized double getValue(Timestamp timestamp)
{ {
compute(timestamp, timestamp); compute(timestamp, timestamp);
Score s = getComputedByTimestamp(timestamp); Score s = getComputedByTimestamp(timestamp);
if(s == null) throw new IllegalStateException(); if (s == null) throw new IllegalStateException();
return s.getValue(); return s.getValue();
} }
@ -102,8 +102,8 @@ public abstract class ScoreList implements Iterable<Score>
* @return the list of scores within the interval. * @return the list of scores within the interval.
*/ */
@NonNull @NonNull
public abstract List<Score> getByInterval(long fromTimestamp, public abstract List<Score> getByInterval(@NonNull Timestamp fromTimestamp,
long toTimestamp); @NonNull Timestamp toTimestamp);
/** /**
* Returns the values of the scores that fall inside a certain interval * Returns the values of the scores that fall inside a certain interval
@ -118,12 +118,12 @@ public abstract class ScoreList implements Iterable<Score>
* @param to timestamp for the newest score * @param to timestamp for the newest score
* @return values for the scores inside the given interval * @return values for the scores inside the given interval
*/ */
public final double[] getValues(long from, long to) public final double[] getValues(Timestamp from, Timestamp to)
{ {
List<Score> scores = getByInterval(from, to); List<Score> scores = getByInterval(from, to);
double[] values = new double[scores.size()]; double[] values = new double[scores.size()];
for(int i = 0; i < values.length; i++) for (int i = 0; i < values.length; i++)
values[i] = scores.get(i).getValue(); values[i] = scores.get(i).getValue();
return values; return values;
@ -132,7 +132,7 @@ public abstract class ScoreList implements Iterable<Score>
public List<Score> groupBy(DateUtils.TruncateField field) public List<Score> groupBy(DateUtils.TruncateField field)
{ {
computeAll(); computeAll();
HashMap<Long, ArrayList<Double>> groups = getGroupedValues(field); HashMap<Timestamp, ArrayList<Double>> groups = getGroupedValues(field);
List<Score> scores = groupsToAvgScores(groups); List<Score> scores = groupsToAvgScores(groups);
Collections.sort(scores, (s1, s2) -> s2.compareNewer(s1)); Collections.sort(scores, (s1, s2) -> s2.compareNewer(s1));
return scores; return scores;
@ -145,7 +145,7 @@ public abstract class ScoreList implements Iterable<Score>
* *
* @param timestamp the oldest timestamp that should be invalidated * @param timestamp the oldest timestamp that should be invalidated
*/ */
public abstract void invalidateNewerThan(long timestamp); public abstract void invalidateNewerThan(Timestamp timestamp);
@Override @Override
public Iterator<Score> iterator() public Iterator<Score> iterator()
@ -171,9 +171,8 @@ public abstract class ScoreList implements Iterable<Score>
for (Score s : this) for (Score s : this)
{ {
String timestamp = dateFormat.format(s.getTimestamp()); String timestamp = dateFormat.format(s.getTimestamp().getUnixTime());
String score = String score = String.format("%.4f", s.getValue());
String.format("%.4f", s.getValue());
out.write(String.format("%s,%s\n", timestamp, score)); out.write(String.format("%s,%s\n", timestamp, score));
} }
} }
@ -192,25 +191,24 @@ public abstract class ScoreList implements Iterable<Score>
* @param from timestamp of the beginning of the interval * @param from timestamp of the beginning of the interval
* @param to timestamp of the end of the time interval * @param to timestamp of the end of the time interval
*/ */
protected synchronized void compute(long from, long to) protected synchronized void compute(@NonNull Timestamp from,
@NonNull Timestamp to)
{ {
final long day = DateUtils.millisecondsInOneDay;
Score newest = getNewestComputed(); Score newest = getNewestComputed();
Score oldest = getOldestComputed(); Score oldest = getOldestComputed();
if (newest == null) if (newest == null)
{ {
Repetition oldestRep = habit.getRepetitions().getOldest(); Repetition oldestRep = habit.getRepetitions().getOldest();
if (oldestRep != null) if (oldestRep != null) from =
from = Math.min(from, oldestRep.getTimestamp()); Timestamp.oldest(from, oldestRep.getTimestamp());
forceRecompute(from, to, 0); forceRecompute(from, to, 0);
} }
else else
{ {
if (oldest == null) throw new IllegalStateException(); if (oldest == null) throw new IllegalStateException();
forceRecompute(from, oldest.getTimestamp() - day, 0); forceRecompute(from, oldest.getTimestamp().minus(1), 0);
forceRecompute(newest.getTimestamp() + day, to, forceRecompute(newest.getTimestamp().plus(1), to,
newest.getValue()); newest.getValue());
} }
} }
@ -224,7 +222,7 @@ public abstract class ScoreList implements Iterable<Score>
Repetition oldestRep = habit.getRepetitions().getOldest(); Repetition oldestRep = habit.getRepetitions().getOldest();
if (oldestRep == null) return; if (oldestRep == null) return;
long today = DateUtils.getStartOfToday(); Timestamp today = DateUtils.getToday();
compute(oldestRep.getTimestamp(), today); compute(oldestRep.getTimestamp(), today);
} }
@ -236,7 +234,7 @@ public abstract class ScoreList implements Iterable<Score>
* @return the score with given timestamp, or null not yet computed. * @return the score with given timestamp, or null not yet computed.
*/ */
@Nullable @Nullable
protected abstract Score getComputedByTimestamp(long timestamp); protected abstract Score getComputedByTimestamp(Timestamp timestamp);
/** /**
* Returns the most recent score that has already been computed. If no score * Returns the most recent score that has already been computed. If no score
@ -263,11 +261,11 @@ public abstract class ScoreList implements Iterable<Score>
* @param previousValue value of the score on the day immediately before the * @param previousValue value of the score on the day immediately before the
* interval begins * interval begins
*/ */
private void forceRecompute(long from, long to, double previousValue) private void forceRecompute(@NonNull Timestamp from,
@NonNull Timestamp to,
double previousValue)
{ {
if(from > to) return; if (from.isNewerThan(to)) return;
final long day = DateUtils.millisecondsInOneDay;
final double freq = habit.getFrequency().toDouble(); final double freq = habit.getFrequency().toDouble();
final int checkmarkValues[] = habit.getCheckmarks().getValues(from, to); final int checkmarkValues[] = habit.getCheckmarks().getValues(from, to);
@ -278,31 +276,31 @@ public abstract class ScoreList implements Iterable<Score>
{ {
double value = checkmarkValues[checkmarkValues.length - i - 1]; double value = checkmarkValues[checkmarkValues.length - i - 1];
if(habit.isNumerical()) if (habit.isNumerical())
{ {
value /= 1000; value /= 1000;
value /= habit.getTargetValue(); value /= habit.getTargetValue();
value = Math.min(1, value); value = Math.min(1, value);
} }
if(!habit.isNumerical() && value > 0) if (!habit.isNumerical() && value > 0) value = 1;
value = 1;
previousValue = Score.compute(freq, previousValue, value); previousValue = Score.compute(freq, previousValue, value);
scores.add(new Score(from + day * i, previousValue)); scores.add(new Score(from.plus(i), previousValue));
} }
add(scores); add(scores);
} }
@NonNull @NonNull
private HashMap<Long, ArrayList<Double>> getGroupedValues(DateUtils.TruncateField field) private HashMap<Timestamp, ArrayList<Double>> getGroupedValues(DateUtils.TruncateField field)
{ {
HashMap<Long, ArrayList<Double>> groups = new HashMap<>(); HashMap<Timestamp, ArrayList<Double>> groups = new HashMap<>();
for (Score s : this) for (Score s : this)
{ {
long groupTimestamp = DateUtils.truncate(field, s.getTimestamp()); Timestamp groupTimestamp = new Timestamp(
DateUtils.truncate(field, s.getTimestamp().getUnixTime()));
if (!groups.containsKey(groupTimestamp)) if (!groups.containsKey(groupTimestamp))
groups.put(groupTimestamp, new ArrayList<>()); groups.put(groupTimestamp, new ArrayList<>());
@ -314,11 +312,11 @@ public abstract class ScoreList implements Iterable<Score>
} }
@NonNull @NonNull
private List<Score> groupsToAvgScores(HashMap<Long, ArrayList<Double>> groups) private List<Score> groupsToAvgScores(HashMap<Timestamp, ArrayList<Double>> groups)
{ {
List<Score> scores = new LinkedList<>(); List<Score> scores = new LinkedList<>();
for (Long timestamp : groups.keySet()) for (Timestamp timestamp : groups.keySet())
{ {
double meanValue = 0.0; double meanValue = 0.0;
ArrayList<Double> groupValues = groups.get(timestamp); ArrayList<Double> groupValues = groups.get(timestamp);

@ -20,15 +20,14 @@
package org.isoron.uhabits.core.models; package org.isoron.uhabits.core.models;
import org.apache.commons.lang3.builder.*; import org.apache.commons.lang3.builder.*;
import org.isoron.uhabits.core.utils.*;
public final class Streak public final class Streak
{ {
private final long start; private final Timestamp start;
private final long end; private final Timestamp end;
public Streak(long start, long end) public Streak(Timestamp start, Timestamp end)
{ {
this.start = start; this.start = start;
this.end = end; this.end = end;
@ -39,25 +38,25 @@ public final class Streak
if (this.getLength() != other.getLength()) if (this.getLength() != other.getLength())
return Long.signum(this.getLength() - other.getLength()); return Long.signum(this.getLength() - other.getLength());
return Long.signum(this.getEnd() - other.getEnd()); return compareNewer(other);
} }
public int compareNewer(Streak other) public int compareNewer(Streak other)
{ {
return Long.signum(this.getEnd() - other.getEnd()); return end.compare(other.end);
} }
public long getEnd() public Timestamp getEnd()
{ {
return end; return end;
} }
public long getLength() public int getLength()
{ {
return (end - start) / DateUtils.millisecondsInOneDay + 1; return start.daysUntil(end) + 1;
} }
public long getStart() public Timestamp getStart()
{ {
return start; return start;
} }

@ -63,14 +63,13 @@ public abstract class StreakList
return observable; return observable;
} }
public abstract void invalidateNewerThan(long timestamp); public abstract void invalidateNewerThan(Timestamp timestamp);
public synchronized void rebuild() public synchronized void rebuild()
{ {
long today = DateUtils.getStartOfToday(); Timestamp today = DateUtils.getToday();
Timestamp beginning = findBeginning();
Long beginning = findBeginning(); if (beginning == null || beginning.isNewerThan(today)) return;
if (beginning == null || beginning > today) return;
int checks[] = habit.getCheckmarks().getValues(beginning, today); int checks[] = habit.getCheckmarks().getValues(beginning, today);
List<Streak> streaks = checkmarksToStreaks(beginning, checks); List<Streak> streaks = checkmarksToStreaks(beginning, checks);
@ -88,15 +87,15 @@ public abstract class StreakList
* @return the list of streaks. * @return the list of streaks.
*/ */
@NonNull @NonNull
protected List<Streak> checkmarksToStreaks(long beginning, int[] checks) protected List<Streak> checkmarksToStreaks(Timestamp beginning, int[] checks)
{ {
ArrayList<Long> transitions = getTransitions(beginning, checks); ArrayList<Timestamp> transitions = getTransitions(beginning, checks);
List<Streak> streaks = new LinkedList<>(); List<Streak> streaks = new LinkedList<>();
for (int i = 0; i < transitions.size(); i += 2) for (int i = 0; i < transitions.size(); i += 2)
{ {
long start = transitions.get(i); Timestamp start = transitions.get(i);
long end = transitions.get(i + 1); Timestamp end = transitions.get(i + 1);
streaks.add(new Streak(start, end)); streaks.add(new Streak(start, end));
} }
@ -109,14 +108,13 @@ public abstract class StreakList
* @return * @return
*/ */
@Nullable @Nullable
protected Long findBeginning() protected Timestamp findBeginning()
{ {
Streak newestStreak = getNewestComputed(); Streak newestStreak = getNewestComputed();
if (newestStreak != null) return newestStreak.getStart(); if (newestStreak != null) return newestStreak.getStart();
Repetition oldestRep = habit.getRepetitions().getOldest(); Repetition oldestRep = habit.getRepetitions().getOldest();
if (oldestRep != null) return oldestRep.getTimestamp(); if (oldestRep != null) return oldestRep.getTimestamp();
return null; return null;
} }
@ -129,21 +127,19 @@ public abstract class StreakList
* @return the list of transitions * @return the list of transitions
*/ */
@NonNull @NonNull
protected ArrayList<Long> getTransitions(long beginning, int[] checks) protected ArrayList<Timestamp> getTransitions(Timestamp beginning, int[] checks)
{ {
long day = DateUtils.millisecondsInOneDay; ArrayList<Timestamp> list = new ArrayList<>();
long current = beginning; Timestamp current = beginning;
ArrayList<Long> list = new ArrayList<>();
list.add(current); list.add(current);
for (int i = 1; i < checks.length; i++) for (int i = 1; i < checks.length; i++)
{ {
current += day; current = current.plus(1);
int j = checks.length - i - 1; int j = checks.length - i - 1;
if ((checks[j + 1] == 0 && checks[j] > 0)) list.add(current); if ((checks[j + 1] == 0 && checks[j] > 0)) list.add(current);
if ((checks[j + 1] > 0 && checks[j] == 0)) list.add(current - day); if ((checks[j + 1] > 0 && checks[j] == 0)) list.add(current.minus(1));
} }
if (list.size() % 2 == 1) list.add(current); if (list.size() % 2 == 1) list.add(current);

@ -0,0 +1,149 @@
/*
* Copyright (C) 2015-2017 Á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.core.models;
import org.apache.commons.lang3.builder.*;
import java.util.*;
import static java.util.Calendar.DAY_OF_WEEK;
public class Timestamp
{
public static final long DAY_LENGTH = 86400000;
public static final Timestamp ZERO = new Timestamp(0);
private final long unixTime;
public Timestamp(long unixTime)
{
if (unixTime < 0 || unixTime % DAY_LENGTH != 0)
throw new IllegalArgumentException(
"Invalid unix time: " + unixTime);
this.unixTime = unixTime;
}
public Timestamp(GregorianCalendar cal)
{
this(cal.getTimeInMillis());
}
public long getUnixTime()
{
return unixTime;
}
/**
* Returns -1 if this timestamp is older than the given timestamp, 1 if this
* timestamp is newer, or zero if they are equal.
*/
public int compare(Timestamp other)
{
return Long.signum(this.unixTime - other.unixTime);
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Timestamp timestamp = (Timestamp) o;
return new EqualsBuilder()
.append(unixTime, timestamp.unixTime)
.isEquals();
}
@Override
public int hashCode()
{
return new HashCodeBuilder(17, 37).append(unixTime).toHashCode();
}
/**
* Given two timestamps, returns whichever timestamp is the oldest one.
*/
public static Timestamp oldest(Timestamp first, Timestamp second)
{
return first.unixTime < second.unixTime ? first : second;
}
public Timestamp minus(int days)
{
return plus(-days);
}
public Timestamp plus(int days)
{
return new Timestamp(unixTime + DAY_LENGTH * days);
}
/**
* Returns the number of days between this timestamp and the given one. If
* the other timestamp equals this one, returns zero. If the other timestamp
* is older than this one, returns a negative number.
*/
public int daysUntil(Timestamp other)
{
return (int) ((other.unixTime - this.unixTime) / DAY_LENGTH);
}
public boolean isNewerThan(Timestamp other)
{
return compare(other) > 0;
}
public boolean isOlderThan(Timestamp other)
{
return compare(other) < 0;
}
public Date toJavaDate()
{
return new Date(unixTime);
}
public GregorianCalendar toCalendar()
{
GregorianCalendar day =
new GregorianCalendar(TimeZone.getTimeZone("GMT"));
day.setTimeInMillis(unixTime);
return day;
}
@Override
public String toString()
{
return new ToStringBuilder(this)
.append("unixTime", unixTime)
.toString();
}
public int getWeekday()
{
return toCalendar().get(DAY_OF_WEEK) % 7;
}
}

@ -22,12 +22,9 @@ package org.isoron.uhabits.core.models.memory;
import android.support.annotation.*; import android.support.annotation.*;
import org.isoron.uhabits.core.models.*; import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.utils.*;
import java.util.*; import java.util.*;
import static org.isoron.uhabits.core.utils.DateUtils.*;
/** /**
* In-memory implementation of {@link CheckmarkList}. * In-memory implementation of {@link CheckmarkList}.
*/ */
@ -45,42 +42,42 @@ public class MemoryCheckmarkList extends CheckmarkList
public void add(List<Checkmark> checkmarks) public void add(List<Checkmark> checkmarks)
{ {
list.addAll(checkmarks); list.addAll(checkmarks);
Collections.sort(list, (c1, c2) -> c2.compareNewer(c1)); Collections.sort(list,
(c1, c2) -> c2.getTimestamp().compare(c1.getTimestamp()));
} }
@NonNull @NonNull
@Override @Override
public List<Checkmark> getByInterval(long fromTimestamp, long toTimestamp) public synchronized List<Checkmark> getByInterval(Timestamp from,
Timestamp to)
{ {
compute(); compute();
long newestTimestamp = Long.MIN_VALUE; Timestamp newestComputed = new Timestamp(0);
long oldestTimestamp = Long.MAX_VALUE; Timestamp oldestComputed = new Timestamp(0).plus(1000000);
Checkmark newest = getNewestComputed(); Checkmark newest = getNewestComputed();
Checkmark oldest = getOldestComputed(); Checkmark oldest = getOldestComputed();
if(newest != null) newestTimestamp = newest.getTimestamp(); if(newest != null) newestComputed = newest.getTimestamp();
if(oldest != null) oldestTimestamp = oldest.getTimestamp(); if(oldest != null) oldestComputed = oldest.getTimestamp();
long days = (newestTimestamp - oldestTimestamp) /
DateUtils.millisecondsInOneDay; List<Checkmark> filtered = new ArrayList<>(
Math.max(0, oldestComputed.daysUntil(newestComputed) + 1));
List<Checkmark> filtered = new ArrayList<>((int) days); for(int i = 0; i <= from.daysUntil(to); i++)
for(long time = toTimestamp; time >= fromTimestamp; time -= millisecondsInOneDay)
{ {
if(time > newestTimestamp || time < oldestTimestamp) Timestamp t = to.minus(i);
filtered.add(new Checkmark(time, Checkmark.UNCHECKED)); if(t.isNewerThan(newestComputed) || t.isOlderThan(oldestComputed))
filtered.add(new Checkmark(t, Checkmark.UNCHECKED));
else else
{ filtered.add(list.get(t.daysUntil(newestComputed)));
int offset = (int) ((newestTimestamp - time) / millisecondsInOneDay);
filtered.add(list.get(offset));
}
} }
return filtered; return filtered;
} }
@Override @Override
public void invalidateNewerThan(long timestamp) public void invalidateNewerThan(Timestamp timestamp)
{ {
list.clear(); list.clear();
observable.notifyListeners(); observable.notifyListeners();

@ -46,28 +46,29 @@ public class MemoryRepetitionList extends RepetitionList
} }
@Override @Override
public List<Repetition> getByInterval(long fromTimestamp, long toTimestamp) public List<Repetition> getByInterval(Timestamp fromTimestamp, Timestamp toTimestamp)
{ {
ArrayList<Repetition> filtered = new ArrayList<>(); ArrayList<Repetition> filtered = new ArrayList<>();
for (Repetition r : list) for (Repetition r : list)
{ {
long t = r.getTimestamp(); Timestamp t = r.getTimestamp();
if (t >= fromTimestamp && t <= toTimestamp) filtered.add(r); if (t.isOlderThan(fromTimestamp) || t.isNewerThan(toTimestamp)) continue;
filtered.add(r);
} }
Collections.sort(filtered, Collections.sort(filtered,
(r1, r2) -> Long.compare(r1.getTimestamp(), r2.getTimestamp())); (r1, r2) -> r1.getTimestamp().compare(r2.getTimestamp()));
return filtered; return filtered;
} }
@Nullable @Nullable
@Override @Override
public Repetition getByTimestamp(long timestamp) public Repetition getByTimestamp(Timestamp timestamp)
{ {
for (Repetition r : list) for (Repetition r : list)
if (r.getTimestamp() == timestamp) return r; if (r.getTimestamp().equals(timestamp)) return r;
return null; return null;
} }
@ -76,15 +77,15 @@ public class MemoryRepetitionList extends RepetitionList
@Override @Override
public Repetition getOldest() public Repetition getOldest()
{ {
long oldestTime = Long.MAX_VALUE; Timestamp oldestTimestamp = Timestamp.ZERO.plus(1000000);
Repetition oldestRep = null; Repetition oldestRep = null;
for (Repetition rep : list) for (Repetition rep : list)
{ {
if (rep.getTimestamp() < oldestTime) if (rep.getTimestamp().isOlderThan(oldestTimestamp))
{ {
oldestRep = rep; oldestRep = rep;
oldestTime = rep.getTimestamp(); oldestTimestamp = rep.getTimestamp();
} }
} }
@ -95,15 +96,15 @@ public class MemoryRepetitionList extends RepetitionList
@Override @Override
public Repetition getNewest() public Repetition getNewest()
{ {
long newestTime = -1; Timestamp newestTimestamp = Timestamp.ZERO;
Repetition newestRep = null; Repetition newestRep = null;
for (Repetition rep : list) for (Repetition rep : list)
{ {
if (rep.getTimestamp() > newestTime) if (rep.getTimestamp().isNewerThan(newestTimestamp))
{ {
newestRep = rep; newestRep = rep;
newestTime = rep.getTimestamp(); newestTimestamp = rep.getTimestamp();
} }
} }

@ -40,37 +40,41 @@ public class MemoryScoreList extends ScoreList
{ {
list.addAll(scores); list.addAll(scores);
Collections.sort(list, Collections.sort(list,
(s1, s2) -> Long.signum(s2.getTimestamp() - s1.getTimestamp())); (s1, s2) -> s2.getTimestamp().compare(s1.getTimestamp()));
getObservable().notifyListeners(); getObservable().notifyListeners();
} }
@NonNull @NonNull
@Override @Override
public List<Score> getByInterval(long fromTimestamp, long toTimestamp) public List<Score> getByInterval(@NonNull Timestamp fromTimestamp,
@NonNull Timestamp toTimestamp)
{ {
compute(fromTimestamp, toTimestamp); compute(fromTimestamp, toTimestamp);
List<Score> filtered = new LinkedList<>(); List<Score> filtered = new LinkedList<>();
for (Score s : list) for (Score s : list)
if (s.getTimestamp() >= fromTimestamp && {
s.getTimestamp() <= toTimestamp) filtered.add(s); if (s.getTimestamp().isNewerThan(toTimestamp) ||
s.getTimestamp().isOlderThan(fromTimestamp)) continue;
filtered.add(s);
}
return filtered; return filtered;
} }
@Nullable @Nullable
@Override @Override
public Score getComputedByTimestamp(long timestamp) public Score getComputedByTimestamp(Timestamp timestamp)
{ {
for (Score s : list) for (Score s : list)
if (s.getTimestamp() == timestamp) return s; if (s.getTimestamp().equals(timestamp)) return s;
return null; return null;
} }
@Override @Override
public void invalidateNewerThan(long timestamp) public void invalidateNewerThan(Timestamp timestamp)
{ {
list.clear(); list.clear();
getObservable().notifyListeners(); getObservable().notifyListeners();

@ -41,13 +41,14 @@ public class MemoryStreakList extends StreakList
Streak newest = null; Streak newest = null;
for (Streak s : list) for (Streak s : list)
if (newest == null || s.getEnd() > newest.getEnd()) newest = s; if (newest == null || s.getEnd().isNewerThan(newest.getEnd()))
newest = s;
return newest; return newest;
} }
@Override @Override
public void invalidateNewerThan(long timestamp) public void invalidateNewerThan(Timestamp timestamp)
{ {
list.clear(); list.clear();
observable.notifyListeners(); observable.notifyListeners();

@ -79,7 +79,7 @@ public class SQLiteRepetitionList extends RepetitionList
} }
@Override @Override
public List<Repetition> getByInterval(long timeFrom, long timeTo) public List<Repetition> getByInterval(Timestamp timeFrom, Timestamp timeTo)
{ {
loadRecords(); loadRecords();
return list.getByInterval(timeFrom, timeTo); return list.getByInterval(timeFrom, timeTo);
@ -87,7 +87,7 @@ public class SQLiteRepetitionList extends RepetitionList
@Override @Override
@Nullable @Nullable
public Repetition getByTimestamp(long timestamp) public Repetition getByTimestamp(Timestamp timestamp)
{ {
loadRecords(); loadRecords();
return list.getByTimestamp(timestamp); return list.getByTimestamp(timestamp);
@ -115,7 +115,7 @@ public class SQLiteRepetitionList extends RepetitionList
check(habit.getId()); check(habit.getId());
repository.execSQL( repository.execSQL(
"delete from repetitions where habit = ? and timestamp = ?", "delete from repetitions where habit = ? and timestamp = ?",
habit.getId(), repetition.getTimestamp()); habit.getId(), repetition.getTimestamp().getUnixTime());
observable.notifyListeners(); observable.notifyListeners();
} }

@ -46,12 +46,12 @@ public class RepetitionRecord
public void copyFrom(Repetition repetition) public void copyFrom(Repetition repetition)
{ {
timestamp = repetition.getTimestamp(); timestamp = repetition.getTimestamp().getUnixTime();
value = repetition.getValue(); value = repetition.getValue();
} }
public Repetition toRepetition() public Repetition toRepetition()
{ {
return new Repetition(timestamp, value); return new Repetition(new Timestamp(timestamp), value);
} }
} }

@ -94,9 +94,11 @@ public class Preferences
return storage.getInt("last_hint_number", -1); return storage.getInt("last_hint_number", -1);
} }
public long getLastHintTimestamp() public Timestamp getLastHintTimestamp()
{ {
return storage.getLong("last_hint_timestamp", -1); long unixTime = storage.getLong("last_hint_timestamp", -1);
if (unixTime < 0) return null;
else return new Timestamp(unixTime);
} }
public long getLastSync() public long getLastSync()
@ -274,10 +276,10 @@ public class Preferences
return shouldReverseCheckmarks; return shouldReverseCheckmarks;
} }
public void updateLastHint(int number, long timestamp) public void updateLastHint(int number, Timestamp timestamp)
{ {
storage.putInt("last_hint_number", number); storage.putInt("last_hint_number", number);
storage.putLong("last_hint_timestamp", timestamp); storage.putLong("last_hint_timestamp", timestamp.getUnixTime());
} }
public interface Listener public interface Listener

@ -71,7 +71,7 @@ public class ReminderScheduler implements CommandRunner.Listener
sys.scheduleShowReminder(reminderTime, habit, timestamp); sys.scheduleShowReminder(reminderTime, habit, timestamp);
} }
public void scheduleAll() public synchronized void scheduleAll()
{ {
HabitList reminderHabits = HabitList reminderHabits =
habitList.getFiltered(HabitMatcher.WITH_ALARM); habitList.getFiltered(HabitMatcher.WITH_ALARM);
@ -99,7 +99,7 @@ public class ReminderScheduler implements CommandRunner.Listener
Long time = calendar.getTimeInMillis(); Long time = calendar.getTimeInMillis();
if (DateUtils.getLocalTime() > time) if (DateUtils.getLocalTime() > time)
time += DateUtils.millisecondsInOneDay; time += DateUtils.DAY_LENGTH;
return applyTimezone(time); return applyTimezone(time);
} }

@ -57,14 +57,13 @@ public class HabitFixtures
habit.setFrequency(new Frequency(3, 7)); habit.setFrequency(new Frequency(3, 7));
habit.setColor(4); habit.setColor(4);
long day = DateUtils.millisecondsInOneDay; Timestamp today = DateUtils.getToday();
long today = DateUtils.getStartOfToday();
int marks[] = { 0, 1, 3, 5, 7, 8, 9, 10, 12, 14, 15, 17, 19, 20, 26, 27, int marks[] = { 0, 1, 3, 5, 7, 8, 9, 10, 12, 14, 15, 17, 19, 20, 26, 27,
28, 50, 51, 52, 53, 54, 58, 60, 63, 65, 70, 71, 72, 73, 74, 75, 80, 28, 50, 51, 52, 53, 54, 58, 60, 63, 65, 70, 71, 72, 73, 74, 75, 80,
81, 83, 89, 90, 91, 95, 102, 103, 108, 109, 120}; 81, 83, 89, 90, 91, 95, 102, 103, 108, 109, 120};
for (int mark : marks) for (int mark : marks)
habit.getRepetitions().toggle(today - mark * day); habit.getRepetitions().toggle(today.minus(mark));
return habit; return habit;
} }
@ -81,14 +80,13 @@ public class HabitFixtures
habit.setColor(1); habit.setColor(1);
saveIfSQLite(habit); saveIfSQLite(habit);
long day = DateUtils.millisecondsInOneDay; Timestamp today = DateUtils.getToday();
long today = DateUtils.getStartOfToday();
int times[] = { 0, 1, 3, 5, 7, 8, 9, 10 }; int times[] = { 0, 1, 3, 5, 7, 8, 9, 10 };
int values[] = { 100, 200, 300, 400, 500, 600, 700, 800 }; int values[] = { 100, 200, 300, 400, 500, 600, 700, 800 };
for(int i = 0; i < times.length; i++) for(int i = 0; i < times.length; i++)
{ {
long timestamp = today - times[i] * day; Timestamp timestamp = today.minus(times[i]);
habit.getRepetitions().add(new Repetition(timestamp, values[i])); habit.getRepetitions().add(new Repetition(timestamp, values[i]));
} }
@ -103,11 +101,11 @@ public class HabitFixtures
habit.setFrequency(new Frequency(2, 3)); habit.setFrequency(new Frequency(2, 3));
saveIfSQLite(habit); saveIfSQLite(habit);
long timestamp = DateUtils.getStartOfToday(); Timestamp timestamp = DateUtils.getToday();
for (boolean c : NON_DAILY_HABIT_CHECKS) for (boolean c : NON_DAILY_HABIT_CHECKS)
{ {
if (c) habit.getRepetitions().toggle(timestamp); if (c) habit.getRepetitions().toggle(timestamp);
timestamp -= DateUtils.millisecondsInOneDay; timestamp = timestamp.minus(1);
} }
return habit; return habit;

@ -102,7 +102,7 @@ public class NotificationTray
reshowAll(); reshowAll();
} }
public void show(@NonNull Habit habit, long timestamp, long reminderTime) public void show(@NonNull Habit habit, Timestamp timestamp, long reminderTime)
{ {
NotificationData data = new NotificationData(timestamp, reminderTime); NotificationData data = new NotificationData(timestamp, reminderTime);
active.put(habit, data); active.put(habit, data);
@ -143,17 +143,17 @@ public class NotificationTray
void showNotification(Habit habit, void showNotification(Habit habit,
int notificationId, int notificationId,
long timestamp, Timestamp timestamp,
long reminderTime); long reminderTime);
} }
class NotificationData class NotificationData
{ {
public final long timestamp; public final Timestamp timestamp;
public final long reminderTime; public final long reminderTime;
public NotificationData(long timestamp, long reminderTime) public NotificationData(Timestamp timestamp, long reminderTime)
{ {
this.timestamp = timestamp; this.timestamp = timestamp;
this.reminderTime = reminderTime; this.reminderTime = reminderTime;
@ -166,7 +166,7 @@ public class NotificationTray
private final Habit habit; private final Habit habit;
private final long timestamp; private final Timestamp timestamp;
private final long reminderTime; private final long reminderTime;
@ -200,7 +200,7 @@ public class NotificationTray
Reminder reminder = habit.getReminder(); Reminder reminder = habit.getReminder();
boolean reminderDays[] = reminder.getDays().toArray(); boolean reminderDays[] = reminder.getDays().toArray();
int weekday = DateUtils.getWeekday(timestamp); int weekday = timestamp.getWeekday();
return reminderDays[weekday]; return reminderDays[weekday];
} }

@ -304,9 +304,9 @@ public class HabitCardListCache implements CommandRunner.Listener
newData.copyScoresFrom(data); newData.copyScoresFrom(data);
newData.copyCheckmarksFrom(data); newData.copyCheckmarksFrom(data);
long day = DateUtils.millisecondsInOneDay; Timestamp dateTo = new Timestamp(
long dateTo = DateUtils.getStartOfDay(DateUtils.getLocalTime()); DateUtils.getStartOfDay(DateUtils.getLocalTime()));
long dateFrom = dateTo - (checkmarkCount - 1) * day; Timestamp dateFrom = dateTo.minus(checkmarkCount - 1);
runner.publishProgress(this, -1); runner.publishProgress(this, -1);
@ -319,8 +319,9 @@ public class HabitCardListCache implements CommandRunner.Listener
if (targetId != null && !targetId.equals(id)) continue; if (targetId != null && !targetId.equals(id)) continue;
newData.scores.put(id, habit.getScores().getTodayValue()); newData.scores.put(id, habit.getScores().getTodayValue());
newData.checkmarks.put(id, newData.checkmarks.put(id, habit
habit.getCheckmarks().getValues(dateFrom, dateTo)); .getCheckmarks()
.getValues(dateFrom, dateTo));
runner.publishProgress(this, position); runner.publishProgress(this, position);
} }

@ -23,6 +23,7 @@ import android.support.annotation.*;
import com.google.auto.factory.*; import com.google.auto.factory.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.preferences.*; import org.isoron.uhabits.core.preferences.*;
import org.isoron.uhabits.core.utils.*; import org.isoron.uhabits.core.utils.*;
@ -64,7 +65,7 @@ public class HintList
int next = prefs.getLastHintNumber() + 1; int next = prefs.getLastHintNumber() + 1;
if (next >= hints.length) return null; if (next >= hints.length) return null;
prefs.updateLastHint(next, DateUtils.getStartOfToday()); prefs.updateLastHint(next, DateUtils.getToday());
return hints[next]; return hints[next];
} }
@ -75,7 +76,8 @@ public class HintList
*/ */
public boolean shouldShow() public boolean shouldShow()
{ {
long lastHintTimestamp = prefs.getLastHintTimestamp(); Timestamp today = DateUtils.getToday();
return (DateUtils.getStartOfToday() > lastHintTimestamp); Timestamp lastHintTimestamp = prefs.getLastHintTimestamp();
return (lastHintTimestamp.isOlderThan(today));
} }
} }

@ -78,7 +78,7 @@ public class ListHabitsBehavior
screen.showHabitScreen(h); screen.showHabitScreen(h);
} }
public void onEdit(@NonNull Habit habit, long timestamp) public void onEdit(@NonNull Habit habit, Timestamp timestamp)
{ {
CheckmarkList checkmarks = habit.getCheckmarks(); CheckmarkList checkmarks = habit.getCheckmarks();
double oldValue = checkmarks.getValues(timestamp, timestamp)[0]; double oldValue = checkmarks.getValues(timestamp, timestamp)[0];
@ -109,7 +109,7 @@ public class ListHabitsBehavior
public void onFirstRun() public void onFirstRun()
{ {
prefs.setFirstRun(false); prefs.setFirstRun(false);
prefs.updateLastHint(-1, DateUtils.getStartOfToday()); prefs.updateLastHint(-1, DateUtils.getToday());
screen.showIntroScreen(); screen.showIntroScreen();
} }
@ -149,7 +149,7 @@ public class ListHabitsBehavior
if (prefs.isFirstRun()) onFirstRun(); if (prefs.isFirstRun()) onFirstRun();
} }
public void onToggle(@NonNull Habit habit, long timestamp) public void onToggle(@NonNull Habit habit, Timestamp timestamp)
{ {
commandRunner.execute( commandRunner.execute(
new ToggleRepetitionCommand(habitList, habit, timestamp), new ToggleRepetitionCommand(habitList, habit, timestamp),

@ -56,7 +56,7 @@ public class ShowHabitBehavior
screen.showEditHistoryScreen(); screen.showEditHistoryScreen();
} }
public void onToggleCheckmark(long timestamp) public void onToggleCheckmark(Timestamp timestamp)
{ {
commandRunner.execute( commandRunner.execute(
new ToggleRepetitionCommand(habitList, habit, timestamp), null); new ToggleRepetitionCommand(habitList, habit, timestamp), null);

@ -46,7 +46,7 @@ public class WidgetBehavior
this.notificationTray = notificationTray; this.notificationTray = notificationTray;
} }
public void onAddRepetition(@NonNull Habit habit, long timestamp) public void onAddRepetition(@NonNull Habit habit, Timestamp timestamp)
{ {
Repetition rep = habit.getRepetitions().getByTimestamp(timestamp); Repetition rep = habit.getRepetitions().getByTimestamp(timestamp);
if (rep != null) return; if (rep != null) return;
@ -54,19 +54,19 @@ public class WidgetBehavior
notificationTray.cancel(habit); notificationTray.cancel(habit);
} }
public void onRemoveRepetition(@NonNull Habit habit, long timestamp) public void onRemoveRepetition(@NonNull Habit habit, Timestamp timestamp)
{ {
Repetition rep = habit.getRepetitions().getByTimestamp(timestamp); Repetition rep = habit.getRepetitions().getByTimestamp(timestamp);
if (rep == null) return; if (rep == null) return;
performToggle(habit, timestamp); performToggle(habit, timestamp);
} }
public void onToggleRepetition(@NonNull Habit habit, long timestamp) public void onToggleRepetition(@NonNull Habit habit, Timestamp timestamp)
{ {
performToggle(habit, timestamp); performToggle(habit, timestamp);
} }
private void performToggle(@NonNull Habit habit, long timestamp) private void performToggle(@NonNull Habit habit, Timestamp timestamp)
{ {
commandRunner.execute( commandRunner.execute(
new ToggleRepetitionCommand(habitList, habit, timestamp), new ToggleRepetitionCommand(habitList, habit, timestamp),

@ -19,6 +19,10 @@
package org.isoron.uhabits.core.utils; package org.isoron.uhabits.core.utils;
import android.support.annotation.*;
import org.isoron.uhabits.core.models.*;
import java.util.*; import java.util.*;
import static java.util.Calendar.*; import static java.util.Calendar.*;
@ -32,7 +36,7 @@ public abstract class DateUtils
/** /**
* Number of milliseconds in one day. * Number of milliseconds in one day.
*/ */
public static long millisecondsInOneDay = 24 * 60 * 60 * 1000; public static long DAY_LENGTH = 24 * 60 * 60 * 1000;
public static long applyTimezone(long localTimestamp) public static long applyTimezone(long localTimestamp)
{ {
@ -49,7 +53,7 @@ public abstract class DateUtils
return dayOfWeek + "\n" + dayOfMonth; return dayOfWeek + "\n" + dayOfMonth;
} }
public static GregorianCalendar getCalendar(long timestamp) private static GregorianCalendar getCalendar(long timestamp)
{ {
GregorianCalendar day = GregorianCalendar day =
new GregorianCalendar(TimeZone.getTimeZone("GMT")); new GregorianCalendar(TimeZone.getTimeZone("GMT"));
@ -57,7 +61,7 @@ public abstract class DateUtils
return day; return day;
} }
public static String[] getDayNames(int format) private static String[] getDayNames(int format)
{ {
String[] wdays = new String[7]; String[] wdays = new String[7];
@ -130,9 +134,15 @@ public abstract class DateUtils
return getDayNames(SHORT); return getDayNames(SHORT);
} }
@NonNull
public static Timestamp getToday()
{
return new Timestamp(getStartOfToday());
}
public static long getStartOfDay(long timestamp) public static long getStartOfDay(long timestamp)
{ {
return (timestamp / millisecondsInOneDay) * millisecondsInOneDay; return (timestamp / DAY_LENGTH) * DAY_LENGTH;
} }
public static long getStartOfToday() public static long getStartOfToday()
@ -142,7 +152,7 @@ public abstract class DateUtils
public static long millisecondsUntilTomorrow() public static long millisecondsUntilTomorrow()
{ {
return getStartOfToday() + millisecondsInOneDay - getLocalTime(); return getStartOfToday() + DAY_LENGTH - getLocalTime();
} }
public static GregorianCalendar getStartOfTodayCalendar() public static GregorianCalendar getStartOfTodayCalendar()
@ -150,7 +160,7 @@ public abstract class DateUtils
return getCalendar(getStartOfToday()); return getCalendar(getStartOfToday());
} }
public static TimeZone getTimezone() private static TimeZone getTimezone()
{ {
if(fixedTimeZone != null) return fixedTimeZone; if(fixedTimeZone != null) return fixedTimeZone;
return TimeZone.getDefault(); return TimeZone.getDefault();
@ -161,25 +171,6 @@ public abstract class DateUtils
fixedTimeZone = tz; fixedTimeZone = tz;
} }
public static int getWeekday(long timestamp)
{
GregorianCalendar day = getCalendar(timestamp);
return javaWeekdayToLoopWeekday(day.get(DAY_OF_WEEK));
}
/**
* 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 long removeTimezone(long timestamp) public static long removeTimezone(long timestamp)
{ {
TimeZone tz = getTimezone(); TimeZone tz = getTimezone();
@ -230,18 +221,4 @@ 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));
}
} }

@ -57,7 +57,7 @@ public class MidnightTimer
executor = Executors.newSingleThreadScheduledExecutor(); executor = Executors.newSingleThreadScheduledExecutor();
executor.scheduleAtFixedRate(() -> notifyListeners(), executor.scheduleAtFixedRate(() -> notifyListeners(),
DateUtils.millisecondsUntilTomorrow() + 1000, DateUtils.millisecondsUntilTomorrow() + 1000,
DateUtils.millisecondsInOneDay, TimeUnit.MILLISECONDS); DateUtils.DAY_LENGTH, TimeUnit.MILLISECONDS);
} }
public synchronized void removeListener(MidnightListener listener) public synchronized void removeListener(MidnightListener listener)

@ -96,7 +96,7 @@ public class CommandParserTest extends BaseUnitTest
public void testDecodeCreateRepCommand() throws JSONException public void testDecodeCreateRepCommand() throws JSONException
{ {
CreateRepetitionCommand original, decoded; CreateRepetitionCommand original, decoded;
original = new CreateRepetitionCommand(habit, 1000, 5); original = new CreateRepetitionCommand(habit, Timestamp.ZERO.plus(100), 5);
decoded = (CreateRepetitionCommand) parser.parse(original.toJson()); decoded = (CreateRepetitionCommand) parser.parse(original.toJson());
MatcherAssert.assertThat(decoded.getId(), equalTo(original.getId())); MatcherAssert.assertThat(decoded.getId(), equalTo(original.getId()));
@ -140,7 +140,8 @@ public class CommandParserTest extends BaseUnitTest
public void testDecodeToggleCommand() throws JSONException public void testDecodeToggleCommand() throws JSONException
{ {
ToggleRepetitionCommand original, decoded; ToggleRepetitionCommand original, decoded;
original = new ToggleRepetitionCommand(habitList, habit, 1000); original = new ToggleRepetitionCommand(habitList, habit,
Timestamp.ZERO.plus(100));
decoded = (ToggleRepetitionCommand) parser.parse(original.toJson()); decoded = (ToggleRepetitionCommand) parser.parse(original.toJson());
MatcherAssert.assertThat(decoded.getId(), equalTo(original.getId())); MatcherAssert.assertThat(decoded.getId(), equalTo(original.getId()));

@ -35,7 +35,7 @@ public class CreateRepetitionCommandTest extends BaseUnitTest
private Habit habit; private Habit habit;
private long today; private Timestamp today;
@Override @Override
@Before @Before
@ -46,7 +46,7 @@ public class CreateRepetitionCommandTest extends BaseUnitTest
habit = fixtures.createShortHabit(); habit = fixtures.createShortHabit();
habitList.add(habit); habitList.add(habit);
today = DateUtils.getStartOfToday(); today = DateUtils.getToday();
command = new CreateRepetitionCommand(habit, today, 100); command = new CreateRepetitionCommand(habit, today, 100);
} }

@ -33,7 +33,7 @@ public class ToggleRepetitionCommandTest extends BaseUnitTest
private ToggleRepetitionCommand command; private ToggleRepetitionCommand command;
private Habit habit; private Habit habit;
private long today; private Timestamp today;
@Override @Override
@Before @Before
@ -44,7 +44,7 @@ public class ToggleRepetitionCommandTest extends BaseUnitTest
habit = fixtures.createShortHabit(); habit = fixtures.createShortHabit();
habitList.add(habit); habitList.add(habit);
today = DateUtils.getStartOfToday(); today = DateUtils.getToday();
command = new ToggleRepetitionCommand(habitList, habit, today); command = new ToggleRepetitionCommand(habitList, habit, today);
} }

@ -121,7 +121,7 @@ public class ImportTest extends BaseUnitTest
{ {
GregorianCalendar date = DateUtils.getStartOfTodayCalendar(); GregorianCalendar date = DateUtils.getStartOfTodayCalendar();
date.set(year, month - 1, day); date.set(year, month - 1, day);
return h.getRepetitions().containsTimestamp(date.getTimeInMillis()); return h.getRepetitions().containsTimestamp(new Timestamp(date));
} }
private void importFromFile(String assetFilename) throws IOException private void importFromFile(String assetFilename) throws IOException

@ -28,9 +28,7 @@ import java.util.*;
import static org.hamcrest.MatcherAssert.*; import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.core.IsEqual.*; import static org.hamcrest.core.IsEqual.*;
import static org.isoron.uhabits.core.models.Checkmark.CHECKED_EXPLICITLY; import static org.isoron.uhabits.core.models.Checkmark.*;
import static org.isoron.uhabits.core.models.Checkmark.CHECKED_IMPLICITLY;
import static org.isoron.uhabits.core.models.Checkmark.UNCHECKED;
public class CheckmarkListTest extends BaseUnitTest public class CheckmarkListTest extends BaseUnitTest
{ {
@ -49,9 +47,6 @@ public class CheckmarkListTest extends BaseUnitTest
{ {
super.setUp(); super.setUp();
dayLength = DateUtils.millisecondsInOneDay;
today = DateUtils.getStartOfToday();
nonDailyHabit = fixtures.createShortHabit(); nonDailyHabit = fixtures.createShortHabit();
habitList.add(nonDailyHabit); habitList.add(nonDailyHabit);
@ -281,7 +276,9 @@ public class CheckmarkListTest extends BaseUnitTest
@Test @Test
public void test_getValues_withInvalidInterval() public void test_getValues_withInvalidInterval()
{ {
int values[] = nonDailyHabit.getCheckmarks().getValues(100L, -100L); int values[] = nonDailyHabit
.getCheckmarks()
.getValues(new Timestamp(0L).plus(100), new Timestamp(0L));
assertThat(values, equalTo(new int[0])); assertThat(values, equalTo(new int[0]));
} }
@ -305,7 +302,9 @@ public class CheckmarkListTest extends BaseUnitTest
UNCHECKED UNCHECKED
}; };
int[] actualValues = nonDailyHabit.getCheckmarks().getValues(from, to); int[] actualValues = nonDailyHabit
.getCheckmarks()
.getValues(new Timestamp(from), new Timestamp(to));
assertThat(actualValues, equalTo(expectedValues)); assertThat(actualValues, equalTo(expectedValues));
} }
@ -344,15 +343,14 @@ public class CheckmarkListTest extends BaseUnitTest
assertThat(writer.toString(), equalTo(expectedCSV)); assertThat(writer.toString(), equalTo(expectedCSV));
} }
private long day(int offset) private Timestamp day(int offset)
{ {
return DateUtils.getStartOfToday() - return DateUtils.getToday().minus(offset);
offset * DateUtils.millisecondsInOneDay;
} }
private void travelInTime(int days) private void travelInTime(int days)
{ {
DateUtils.setFixedLocalTime( DateUtils.setFixedLocalTime(
FIXED_LOCAL_TIME + days * DateUtils.millisecondsInOneDay); FIXED_LOCAL_TIME + days * Timestamp.DAY_LENGTH);
} }
} }

@ -87,7 +87,7 @@ public class HabitTest extends BaseUnitTest
{ {
Habit h = modelFactory.buildHabit(); Habit h = modelFactory.buildHabit();
assertFalse(h.isCompletedToday()); assertFalse(h.isCompletedToday());
h.getRepetitions().toggle(getStartOfToday()); h.getRepetitions().toggle(getToday());
assertTrue(h.isCompletedToday()); assertTrue(h.isCompletedToday());
} }
@ -100,19 +100,19 @@ public class HabitTest extends BaseUnitTest
h.setTargetValue(100.0); h.setTargetValue(100.0);
assertFalse(h.isCompletedToday()); assertFalse(h.isCompletedToday());
h.getRepetitions().toggle(getStartOfToday(), 200); h.getRepetitions().toggle(getToday(), 200);
assertTrue(h.isCompletedToday()); assertTrue(h.isCompletedToday());
h.getRepetitions().toggle(getStartOfToday(), 100); h.getRepetitions().toggle(getToday(), 100);
assertTrue(h.isCompletedToday()); assertTrue(h.isCompletedToday());
h.getRepetitions().toggle(getStartOfToday(), 50); h.getRepetitions().toggle(getToday(), 50);
assertFalse(h.isCompletedToday()); assertFalse(h.isCompletedToday());
h.setTargetType(Habit.AT_MOST); h.setTargetType(Habit.AT_MOST);
h.getRepetitions().toggle(getStartOfToday(), 200); h.getRepetitions().toggle(getToday(), 200);
assertFalse(h.isCompletedToday()); assertFalse(h.isCompletedToday());
h.getRepetitions().toggle(getStartOfToday(), 100); h.getRepetitions().toggle(getToday(), 100);
assertTrue(h.isCompletedToday()); assertTrue(h.isCompletedToday());
h.getRepetitions().toggle(getStartOfToday(), 50); h.getRepetitions().toggle(getToday(), 50);
assertTrue(h.isCompletedToday()); assertTrue(h.isCompletedToday());
} }

@ -29,7 +29,6 @@ import java.util.*;
import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertFalse;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.*;
import static org.hamcrest.core.IsEqual.*; import static org.hamcrest.core.IsEqual.*;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@ -43,7 +42,7 @@ public class RepetitionListTest extends BaseUnitTest
@NonNull @NonNull
private Habit habit; private Habit habit;
private long today; private Timestamp today;
private long day; private long day;
@ -58,14 +57,13 @@ public class RepetitionListTest extends BaseUnitTest
habit = fixtures.createEmptyHabit(); habit = fixtures.createEmptyHabit();
reps = habit.getRepetitions(); reps = habit.getRepetitions();
today = DateUtils.getStartOfToday(); today = DateUtils.getToday();
day = DateUtils.millisecondsInOneDay;
reps.toggle(today - 3 * day); reps.toggle(today.minus(3));
reps.toggle(today - 2 * day); reps.toggle(today.minus(2));
reps.toggle(today); reps.toggle(today);
reps.toggle(today - 7 * day); reps.toggle(today.minus(7));
reps.toggle(today - 5 * day); reps.toggle(today.minus(5));
listener = mock(ModelObservable.Listener.class); listener = mock(ModelObservable.Listener.class);
reps.getObservable().addListener(listener); reps.getObservable().addListener(listener);
@ -82,19 +80,19 @@ public class RepetitionListTest extends BaseUnitTest
@Test @Test
public void test_contains() public void test_contains()
{ {
assertThat(reps.containsTimestamp(today), is(true)); assertTrue(reps.containsTimestamp(today));
assertThat(reps.containsTimestamp(today - 2 * day), is(true)); assertTrue(reps.containsTimestamp(today.minus(2)));
assertThat(reps.containsTimestamp(today - 3 * day), is(true)); assertTrue(reps.containsTimestamp(today.minus(3)));
assertThat(reps.containsTimestamp(today - day), is(false)); assertFalse(reps.containsTimestamp(today.minus(1)));
assertThat(reps.containsTimestamp(today - 4 * day), is(false)); assertFalse(reps.containsTimestamp(today.minus(4)));
} }
@Test @Test
public void test_getOldest() public void test_getOldest()
{ {
Repetition rep = reps.getOldest(); Repetition rep = reps.getOldest();
assertThat(rep.getTimestamp(), is(equalTo(today - 7 * day))); assertThat(rep.getTimestamp(), equalTo(today.minus(7)));
} }
@Test @Test
@ -134,28 +132,28 @@ public class RepetitionListTest extends BaseUnitTest
weekdayCount[month][week]++; weekdayCount[month][week]++;
monthCount[month]++; monthCount[month]++;
} }
reps.toggle(day.getTimeInMillis()); reps.toggle(new Timestamp(day));
} }
} }
day.add(Calendar.DAY_OF_YEAR, 1); day.add(Calendar.DAY_OF_YEAR, 1);
} }
HashMap<Long, Integer[]> freq = HashMap<Timestamp, Integer[]> freq =
reps.getWeekdayFrequency(); reps.getWeekdayFrequency();
// Repetitions until November should be counted correctly // Repetitions until November should be counted correctly
for (int month = 0; month < 11; month++) for (int month = 0; month < 11; month++)
{ {
day.set(2015, month, 1); day.set(2015, month, 1);
Integer actualCount[] = freq.get(day.getTimeInMillis()); Integer actualCount[] = freq.get(new Timestamp(day));
if (monthCount[month] == 0) assertThat(actualCount, equalTo(null)); if (monthCount[month] == 0) assertThat(actualCount, equalTo(null));
else assertThat(actualCount, equalTo(weekdayCount[month])); else assertThat(actualCount, equalTo(weekdayCount[month]));
} }
// Repetitions in December should be discarded // Repetitions in December should be discarded
day.set(2015, 11, 1); day.set(2015, 11, 1);
assertThat(freq.get(day.getTimeInMillis()), equalTo(null)); assertThat(freq.get(new Timestamp(day)), equalTo(null));
} }
@Test @Test
@ -167,9 +165,9 @@ public class RepetitionListTest extends BaseUnitTest
verify(listener).onModelChange(); verify(listener).onModelChange();
reset(listener); reset(listener);
assertFalse(reps.containsTimestamp(today - day)); assertFalse(reps.containsTimestamp(today.minus(1)));
reps.toggle(today - day); reps.toggle(today.minus(1));
assertTrue(reps.containsTimestamp(today - day)); assertTrue(reps.containsTimestamp(today.minus(1)));
verify(listener).onModelChange(); verify(listener).onModelChange();
reset(listener); reset(listener);

@ -117,11 +117,11 @@ public class ScoreListTest extends BaseUnitTest
}; };
ScoreList scores = habit.getScores(); ScoreList scores = habit.getScores();
long current = DateUtils.getStartOfToday(); Timestamp current = DateUtils.getToday();
for (double expectedValue : expectedValues) for (double expectedValue : expectedValues)
{ {
assertThat(scores.getValue(current), closeTo(expectedValue, E)); assertThat(scores.getValue(current), closeTo(expectedValue, E));
current -= DateUtils.millisecondsInOneDay; current = current.minus(1);
} }
} }
@ -130,11 +130,9 @@ public class ScoreListTest extends BaseUnitTest
{ {
toggleRepetitions(0, 20); toggleRepetitions(0, 20);
long today = DateUtils.getStartOfToday(); Timestamp today = DateUtils.getToday();
long day = DateUtils.millisecondsInOneDay; Timestamp from = today.minus(4);
Timestamp to = today.minus(2);
long from = today - 4 * day;
long to = today - 2 * day;
double[] expected = { double[] expected = {
0.617008, 0.596033, 0.573909, 0.617008, 0.596033, 0.573909,
@ -169,7 +167,7 @@ public class ScoreListTest extends BaseUnitTest
assertThat(habit.getScores().getTodayValue(), closeTo(0.101149, E)); assertThat(habit.getScores().getTodayValue(), closeTo(0.101149, E));
habit.setFrequency(new Frequency(1, 2)); habit.setFrequency(new Frequency(1, 2));
habit.getScores().invalidateNewerThan(0); habit.getScores().invalidateNewerThan(new Timestamp(0));
assertThat(habit.getScores().getTodayValue(), closeTo(0.051922, E)); assertThat(habit.getScores().getTodayValue(), closeTo(0.051922, E));
} }
@ -194,10 +192,9 @@ public class ScoreListTest extends BaseUnitTest
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();
long today = DateUtils.getStartOfToday(); Timestamp today = DateUtils.getToday();
long day = DateUtils.millisecondsInOneDay;
for (int i = from; i < to; i++) for (int i = from; i < to; i++)
reps.toggle(today - i * day); reps.toggle(today.minus(i));
} }
} }

@ -38,7 +38,7 @@ public class StreakListTest extends BaseUnitTest
private long day; private long day;
private long today; private Timestamp today;
private ModelObservable.Listener listener; private ModelObservable.Listener listener;
@ -54,25 +54,23 @@ public class StreakListTest extends BaseUnitTest
listener = mock(ModelObservable.Listener.class); listener = mock(ModelObservable.Listener.class);
streaks.getObservable().addListener(listener); streaks.getObservable().addListener(listener);
today = DateUtils.getToday();
today = DateUtils.getStartOfToday();
day = DateUtils.millisecondsInOneDay;
} }
@Test @Test
public void testFindBeginning_withEmptyHistory() public void testFindBeginning_withEmptyHistory()
{ {
Habit habit2 = fixtures.createEmptyHabit(); Habit habit2 = fixtures.createEmptyHabit();
Long beginning = habit2.getStreaks().findBeginning(); Timestamp beginning = habit2.getStreaks().findBeginning();
assertThat(beginning, is(nullValue())); assertNull(beginning);
} }
@Test @Test
public void testFindBeginning_withLongHistory() public void testFindBeginning_withLongHistory()
{ {
streaks.rebuild(); streaks.rebuild();
streaks.invalidateNewerThan(0); streaks.invalidateNewerThan(new Timestamp(0));
assertThat(streaks.findBeginning(), equalTo(today - 120 * day)); assertThat(streaks.findBeginning(), equalTo(today.minus(120)));
} }
@Test @Test
@ -82,11 +80,11 @@ public class StreakListTest extends BaseUnitTest
assertThat(all.size(), equalTo(22)); assertThat(all.size(), equalTo(22));
assertThat(all.get(3).getEnd(), equalTo(today - 7 * day)); assertThat(all.get(3).getEnd(), equalTo(today.minus(7)));
assertThat(all.get(3).getStart(), equalTo(today - 10 * day)); assertThat(all.get(3).getStart(), equalTo(today.minus(10)));
assertThat(all.get(17).getEnd(), equalTo(today - 89 * day)); assertThat(all.get(17).getEnd(), equalTo(today.minus(89)));
assertThat(all.get(17).getStart(), equalTo(today - 91 * day)); assertThat(all.get(17).getStart(), equalTo(today.minus(91)));
} }
@Test @Test
@ -95,16 +93,16 @@ public class StreakListTest extends BaseUnitTest
List<Streak> best = streaks.getBest(4); List<Streak> best = streaks.getBest(4);
assertThat(best.size(), equalTo(4)); assertThat(best.size(), equalTo(4));
assertThat(best.get(0).getLength(), equalTo(4L)); assertThat(best.get(0).getLength(), equalTo(4));
assertThat(best.get(1).getLength(), equalTo(3L)); assertThat(best.get(1).getLength(), equalTo(3));
assertThat(best.get(2).getLength(), equalTo(5L)); assertThat(best.get(2).getLength(), equalTo(5));
assertThat(best.get(3).getLength(), equalTo(6L)); assertThat(best.get(3).getLength(), equalTo(6));
best = streaks.getBest(2); best = streaks.getBest(2);
assertThat(best.size(), equalTo(2)); assertThat(best.size(), equalTo(2));
assertThat(best.get(0).getLength(), equalTo(5L)); assertThat(best.get(0).getLength(), equalTo(5));
assertThat(best.get(1).getLength(), equalTo(6L)); assertThat(best.get(1).getLength(), equalTo(6));
} }
@Test @Test
@ -113,7 +111,7 @@ public class StreakListTest extends BaseUnitTest
Streak s = streaks.getNewestComputed(); Streak s = streaks.getNewestComputed();
assertThat(s.getEnd(), equalTo(today)); assertThat(s.getEnd(), equalTo(today));
streaks.invalidateNewerThan(today - 8 * day); streaks.invalidateNewerThan(today.minus(8));
verify(listener).onModelChange(); verify(listener).onModelChange();
s = streaks.getNewestComputed(); s = streaks.getNewestComputed();

@ -0,0 +1,68 @@
/*
* Copyright (C) 2015-2017 Á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.core.models;
import org.isoron.uhabits.core.*;
import org.isoron.uhabits.core.utils.*;
import org.junit.*;
import static junit.framework.TestCase.assertFalse;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertTrue;
public class TimestampTest extends BaseUnitTest
{
@Test
public void testCompare() throws Exception
{
Timestamp t1 = DateUtils.getToday();
Timestamp t2 = t1.minus(1);
Timestamp t3 = t1.plus(3);
assertThat(t1.compare(t2), greaterThan(0));
assertThat(t1.compare(t1), equalTo(0));
assertThat(t1.compare(t3), lessThan(0));
assertTrue(t1.isNewerThan(t2));
assertFalse(t1.isNewerThan(t1));
assertFalse(t2.isNewerThan(t1));
assertTrue(t2.isOlderThan(t1));
assertFalse(t1.isOlderThan(t2));
}
@Test
public void testDaysUntil() throws Exception
{
Timestamp t = DateUtils.getToday();
assertThat(t.daysUntil(t), equalTo(0));
assertThat(t.daysUntil(t.plus(1)), equalTo(1));
assertThat(t.daysUntil(t.plus(3)), equalTo(3));
assertThat(t.daysUntil(t.plus(300)), equalTo(300));
assertThat(t.daysUntil(t.minus(1)), equalTo(-1));
assertThat(t.daysUntil(t.minus(3)), equalTo(-3));
assertThat(t.daysUntil(t.minus(300)), equalTo(-300));
}
}

@ -31,17 +31,16 @@ import org.junit.*;
import java.util.*; import java.util.*;
import static junit.framework.TestCase.assertNotNull; import static junit.framework.TestCase.*;
import static junit.framework.TestCase.assertNull;
import static org.hamcrest.MatcherAssert.*; import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.core.IsEqual.*; import static org.hamcrest.core.IsEqual.*;
import static org.isoron.uhabits.core.models.Checkmark.CHECKED_EXPLICITLY; import static org.isoron.uhabits.core.models.Checkmark.*;
public class SQLiteRepetitionListTest extends BaseUnitTest public class SQLiteRepetitionListTest extends BaseUnitTest
{ {
private Habit habit; private Habit habit;
private long today; private Timestamp today;
private RepetitionList repetitions; private RepetitionList repetitions;
@ -62,20 +61,19 @@ public class SQLiteRepetitionListTest extends BaseUnitTest
habit = fixtures.createLongHabit(); habit = fixtures.createLongHabit();
repetitions = habit.getRepetitions(); repetitions = habit.getRepetitions();
today = DateUtils.getStartOfToday(); today = DateUtils.getToday();
day = DateUtils.millisecondsInOneDay;
} }
@Test @Test
public void testAdd() public void testAdd()
{ {
RepetitionRecord record = getByTimestamp(today + day); RepetitionRecord record = getByTimestamp(today.plus(1));
assertNull(record); assertNull(record);
Repetition rep = new Repetition(today + day, CHECKED_EXPLICITLY); Repetition rep = new Repetition(today.plus(1), CHECKED_EXPLICITLY);
habit.getRepetitions().add(rep); habit.getRepetitions().add(rep);
record = getByTimestamp(today + day); record = getByTimestamp(today.plus(1));
assertNotNull(record); assertNotNull(record);
assertThat(record.value, equalTo(CHECKED_EXPLICITLY)); assertThat(record.value, equalTo(CHECKED_EXPLICITLY));
} }
@ -84,12 +82,12 @@ public class SQLiteRepetitionListTest extends BaseUnitTest
public void testGetByInterval() public void testGetByInterval()
{ {
List<Repetition> reps = List<Repetition> reps =
repetitions.getByInterval(today - 10 * day, today); repetitions.getByInterval(today.minus(10), today);
assertThat(reps.size(), equalTo(8)); assertThat(reps.size(), equalTo(8));
assertThat(reps.get(0).getTimestamp(), equalTo(today - 10 * day)); assertThat(reps.get(0).getTimestamp(), equalTo(today.minus(10)));
assertThat(reps.get(4).getTimestamp(), equalTo(today - 5 * day)); assertThat(reps.get(4).getTimestamp(), equalTo(today.minus(5)));
assertThat(reps.get(5).getTimestamp(), equalTo(today - 3 * day)); assertThat(reps.get(5).getTimestamp(), equalTo(today.minus(3)));
} }
@Test @Test
@ -99,7 +97,7 @@ public class SQLiteRepetitionListTest extends BaseUnitTest
assertNotNull(rep); assertNotNull(rep);
assertThat(rep.getTimestamp(), equalTo(today)); assertThat(rep.getTimestamp(), equalTo(today));
rep = repetitions.getByTimestamp(today - 2 * day); rep = repetitions.getByTimestamp(today.minus(2));
assertNull(rep); assertNull(rep);
} }
@ -108,7 +106,7 @@ public class SQLiteRepetitionListTest extends BaseUnitTest
{ {
Repetition rep = repetitions.getOldest(); Repetition rep = repetitions.getOldest();
assertNotNull(rep); assertNotNull(rep);
assertThat(rep.getTimestamp(), equalTo(today - 120 * day)); assertThat(rep.getTimestamp(), equalTo(today.minus(120)));
} }
@Test @Test
@ -133,11 +131,11 @@ public class SQLiteRepetitionListTest extends BaseUnitTest
} }
@Nullable @Nullable
private RepetitionRecord getByTimestamp(long timestamp) private RepetitionRecord getByTimestamp(Timestamp timestamp)
{ {
String query = "where habit = ? and timestamp = ?"; String query = "where habit = ? and timestamp = ?";
String params[] = { String params[] = {
Long.toString(habit.getId()), Long.toString(timestamp) Long.toString(habit.getId()), Long.toString(timestamp.getUnixTime())
}; };
return repository.findFirst(query, params); return repository.findFirst(query, params);

@ -33,7 +33,7 @@ public class RepetitionRecordTest extends BaseUnitTest
@Test @Test
public void testRecord() throws Exception public void testRecord() throws Exception
{ {
Repetition rep = new Repetition(2000L, 50); Repetition rep = new Repetition(Timestamp.ZERO.plus(100), 50);
RepetitionRecord record = new RepetitionRecord(); RepetitionRecord record = new RepetitionRecord();
record.copyFrom(rep); record.copyFrom(rep);
assertThat(rep, equalTo(record.toRepetition())); assertThat(rep, equalTo(record.toRepetition()));

@ -85,7 +85,7 @@ public class HabitCardListCacheTest extends BaseUnitTest
public void testCommandListener_single() public void testCommandListener_single()
{ {
Habit h2 = habitList.getByPosition(2); Habit h2 = habitList.getByPosition(2);
long today = DateUtils.getStartOfToday(); Timestamp today = DateUtils.getToday();
commandRunner.execute(new ToggleRepetitionCommand(habitList, h2, today), commandRunner.execute(new ToggleRepetitionCommand(habitList, h2, today),
h2.getId()); h2.getId());
@ -106,12 +106,10 @@ public class HabitCardListCacheTest extends BaseUnitTest
assertThat(cache.getHabitByPosition(3), equalTo(h)); assertThat(cache.getHabitByPosition(3), equalTo(h));
assertThat(cache.getScore(h.getId()), equalTo(score)); assertThat(cache.getScore(h.getId()), equalTo(score));
long today = DateUtils.getStartOfToday(); Timestamp today = DateUtils.getToday();
long day = DateUtils.millisecondsInOneDay;
int[] actualCheckmarks = cache.getCheckmarks(h.getId()); int[] actualCheckmarks = cache.getCheckmarks(h.getId());
int[] expectedCheckmarks = int[] expectedCheckmarks =
h.getCheckmarks().getValues(today - 9 * day, today); h.getCheckmarks().getValues(today.minus(9), today);
assertThat(actualCheckmarks, equalTo(expectedCheckmarks)); assertThat(actualCheckmarks, equalTo(expectedCheckmarks));
} }

@ -20,6 +20,7 @@
package org.isoron.uhabits.core.ui.screens.habits.list; package org.isoron.uhabits.core.ui.screens.habits.list;
import org.isoron.uhabits.core.*; import org.isoron.uhabits.core.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.preferences.*; import org.isoron.uhabits.core.preferences.*;
import org.isoron.uhabits.core.utils.*; import org.isoron.uhabits.core.utils.*;
import org.junit.*; import org.junit.*;
@ -41,16 +42,16 @@ public class HintListTest extends BaseUnitTest
@Mock @Mock
private Preferences prefs; private Preferences prefs;
private long today; private Timestamp today;
private long yesterday; private Timestamp yesterday;
@Override @Override
public void setUp() throws Exception public void setUp() throws Exception
{ {
super.setUp(); super.setUp();
today = DateUtils.getStartOfToday(); today = DateUtils.getToday();
yesterday = today - DateUtils.millisecondsInOneDay; yesterday = today.minus(1);
hints = new String[]{ "hint1", "hint2", "hint3" }; hints = new String[]{ "hint1", "hint2", "hint3" };
hintList = new HintList(prefs, hints); hintList = new HintList(prefs, hints);

@ -83,7 +83,7 @@ public class ListHabitsBehaviorTest extends BaseUnitTest
@Test @Test
public void testOnEdit() public void testOnEdit()
{ {
behavior.onEdit(habit2, DateUtils.getStartOfToday()); behavior.onEdit(habit2, DateUtils.getToday());
verify(screen).showNumberPicker(eq(0.1), eq("miles"), picker.capture()); verify(screen).showNumberPicker(eq(0.1), eq("miles"), picker.capture());
picker.getValue().onNumberPicked(100); picker.getValue().onNumberPicked(100);
assertThat(habit2.getCheckmarks().getTodayValue(), equalTo(100000)); assertThat(habit2.getCheckmarks().getTodayValue(), equalTo(100000));
@ -173,7 +173,7 @@ public class ListHabitsBehaviorTest extends BaseUnitTest
public void testOnToggle() public void testOnToggle()
{ {
assertTrue(habit1.isCompletedToday()); assertTrue(habit1.isCompletedToday());
behavior.onToggle(habit1, DateUtils.getStartOfToday()); behavior.onToggle(habit1, DateUtils.getToday());
assertFalse(habit1.isCompletedToday()); assertFalse(habit1.isCompletedToday());
} }

@ -134,17 +134,4 @@ 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, JANUARY, 10);
long t3 = timestamp(2016, DECEMBER, 31);
assertThat(DateUtils.getDaysBetween(t1, t1), equalTo(0));
assertThat(DateUtils.getDaysBetween(t1, t2), equalTo(9));
assertThat(DateUtils.getDaysBetween(t1, t3), equalTo(365));
assertThat(DateUtils.getDaysBetween(t3, t1), equalTo(365));
}
} }

Loading…
Cancel
Save