Remove DateCalculator

pull/498/head
Alinson S. Xavier 7 years ago
parent 70a79856f2
commit aa6b13f3a6

13
.gitignore vendored

@ -1,9 +1,16 @@
*.iml
*.pbxuser
*.perspective
*.perspectivev3
*.swp
*~.nib
.DS_Store
.externalNativeBuild
.gradle
local.properties
.idea
.DS_Store
build
build/
captures
.externalNativeBuild
local.properties
node_modules
*xcuserdata*

@ -26,7 +26,7 @@ buildscript {
dependencies {
classpath "com.android.tools.build:gradle:3.2.1"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.21"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.11"
}
}
@ -42,17 +42,11 @@ apply plugin:"kotlin-multiplatform"
kotlin {
targets {
def iosPreset = System.getenv('SDK_NAME')?.startsWith("iphoneos") ? presets.iosArm64 : presets.iosX64
fromPreset(presets.jvm, 'jvm')
fromPreset(presets.iosX64, 'iOS') {
fromPreset(iosPreset, 'iOS') {
compilations.main.outputKinds('FRAMEWORK')
}
// Replace the target above by the following one to produce a framework
// which can be installed on a real iPhone:
// fromPreset(presets.iosArm64, 'iOS') {
// compilations.main.outputKinds('FRAMEWORK')
// }
}
sourceSets {

@ -31,48 +31,143 @@ enum class DayOfWeek(val index: Int) {
SATURDAY(6),
}
data class Timestamp(val unixTimeInMillis: Long)
data class Timestamp(val millisSince1970: Long) {
val localDate: LocalDate
get() {
val millisSince2000 = millisSince1970 - 946684800000
val daysSince2000 = millisSince2000 / 86400000
return LocalDate(daysSince2000.toInt())
}
}
data class LocalDate(val daysSince2000: Int) {
var yearCache = -1
var monthCache = -1
var dayCache = -1
init {
if (daysSince2000 < 0)
throw IllegalArgumentException("$daysSince2000 < 0")
}
constructor(year: Int, month: Int, day: Int) :
this(daysSince2000(year, month, day))
val dayOfWeek: DayOfWeek
get() {
return when (daysSince2000 % 7) {
0 -> DayOfWeek.SATURDAY
1 -> DayOfWeek.SUNDAY
2 -> DayOfWeek.MONDAY
3 -> DayOfWeek.TUESDAY
4 -> DayOfWeek.WEDNESDAY
5 -> DayOfWeek.THURSDAY
else -> DayOfWeek.FRIDAY
}
}
data class LocalDate(val year: Int,
val month: Int,
val day: Int) {
val timestamp: Timestamp
get() {
return Timestamp(946684800000 + daysSince2000.toLong() * 86400000)
}
val year: Int
get() {
if (yearCache < 0) updateYearMonthDayCache()
return yearCache
}
val month: Int
get() {
if (monthCache < 0) updateYearMonthDayCache()
return monthCache
}
val day: Int
get() {
if (dayCache < 0) updateYearMonthDayCache()
return dayCache
}
private fun updateYearMonthDayCache() {
var currYear = 2000
var currDay = 0
while (true) {
val currYearLength = if (isLeapYear(currYear)) 366 else 365
if (daysSince2000 < currDay + currYearLength) {
yearCache = currYear
break
} else {
currYear++
currDay += currYearLength
}
}
var currMonth = 1
val monthOffset = if (isLeapYear(currYear)) leapOffset else nonLeapOffset
while (true) {
if (daysSince2000 < currDay + monthOffset[currMonth]) {
monthCache = currMonth
break
} else {
currMonth++
}
}
currDay += monthOffset[currMonth - 1]
dayCache = daysSince2000 - currDay + 1
}
fun isOlderThan(other: LocalDate): Boolean {
if (other.year != year) return other.year > year
if (other.month != month) return other.month > month
return other.day > day
return daysSince2000 < other.daysSince2000
}
fun isNewerThan(other: LocalDate): Boolean {
if (this == other) return false
return other.isOlderThan(this)
return daysSince2000 > other.daysSince2000
}
init {
if ((month <= 0) or (month >= 13)) throw(IllegalArgumentException())
if ((day <= 0) or (day >= 32)) throw(IllegalArgumentException())
fun plus(days: Int): LocalDate {
return LocalDate(daysSince2000 + days)
}
}
interface LocalDateCalculator {
fun plusDays(date: LocalDate, days: Int): LocalDate
fun dayOfWeek(date: LocalDate): DayOfWeek
fun toTimestamp(date: LocalDate): Timestamp
fun fromTimestamp(timestamp: Timestamp): LocalDate
}
fun LocalDateCalculator.distanceInDays(d1: LocalDate, d2: LocalDate): Int {
val t1 = toTimestamp(d1)
val t2 = toTimestamp(d2)
val dayLength = 24 * 60 * 60 * 1000
return abs((t2.unixTimeInMillis - t1.unixTimeInMillis) / dayLength).toInt()
}
fun minus(days: Int): LocalDate {
return LocalDate(daysSince2000 - days)
}
fun LocalDateCalculator.minusDays(date: LocalDate, days: Int): LocalDate {
return plusDays(date, -days)
fun distanceTo(other: LocalDate): Int {
return abs(daysSince2000 - other.daysSince2000)
}
}
interface LocalDateFormatter {
fun shortWeekdayName(date: LocalDate): String
fun shortMonthName(date: LocalDate): String
}
private fun isLeapYear(year: Int): Boolean {
return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0
}
val leapOffset = arrayOf(0, 31, 60, 91, 121, 152, 182,
213, 244, 274, 305, 335, 366)
val nonLeapOffset = arrayOf(0, 31, 59, 90, 120, 151, 181,
212, 243, 273, 304, 334, 365)
private fun daysSince2000(year: Int, month: Int, day: Int): Int {
var result = 365 * (year - 2000)
result += ceil((year - 2000) / 4.0).toInt()
result -= ceil((year - 2000) / 100.0).toInt()
result += ceil((year - 2000) / 400.0).toInt()
if (isLeapYear(year)) {
result += leapOffset[month - 1]
} else {
result += nonLeapOffset[month - 1]
}
result += (day - 1)
return result
}

@ -30,7 +30,6 @@ class Backend(databaseName: String,
databaseOpener: DatabaseOpener,
fileOpener: FileOpener,
val log: Log,
val dateCalculator: LocalDateCalculator,
val taskRunner: TaskRunner) {
val database: Database
@ -56,13 +55,12 @@ class Backend(databaseName: String,
database = databaseOpener.open(dbFile)
database.migrateTo(LOOP_DATABASE_VERSION, fileOpener, log)
habitsRepository = HabitRepository(database)
checkmarkRepository = CheckmarkRepository(database, dateCalculator)
checkmarkRepository = CheckmarkRepository(database)
taskRunner.runInBackground {
habits.putAll(habitsRepository.findAll())
for ((key, habit) in habits) {
val checks = checkmarkRepository.findAll(key)
checkmarks[habit] = CheckmarkList(habit.frequency,
dateCalculator)
checkmarks[habit] = CheckmarkList(habit.frequency)
checkmarks[habit]?.setManualCheckmarks(checks)
}
}
@ -76,7 +74,7 @@ class Backend(databaseName: String,
habit.id = id
habit.position = habits.size
habits[id] = habit
checkmarks[habit] = CheckmarkList(habit.frequency, dateCalculator)
checkmarks[habit] = CheckmarkList(habit.frequency)
habitsRepository.insert(habit)
mainScreenDataSource.requestData()
}

@ -26,7 +26,6 @@ import kotlin.math.*
class CalendarChart(var today: LocalDate,
var color: Color,
var theme: Theme,
var dateCalculator: LocalDateCalculator,
var dateFormatter: LocalDateFormatter) : Component {
var padding = 5.0
@ -47,19 +46,19 @@ class CalendarChart(var today: LocalDate,
canvas.setFontSize(height * 0.06)
val nColumns = floor((width - 2 * padding) / squareSize).toInt() - 2
val todayWeekday = dateCalculator.dayOfWeek(today)
val todayWeekday = today.dayOfWeek
val topLeftOffset = (nColumns - 1 + scrollPosition) * 7 + todayWeekday.index
val topLeftDate = dateCalculator.minusDays(today, topLeftOffset)
val topLeftDate = today.minus(topLeftOffset)
repeat(nColumns) { column ->
val topOffset = topLeftOffset - 7 * column
val topDate = dateCalculator.plusDays(topLeftDate, 7 * column)
val topDate = topLeftDate.plus(7 * column)
drawColumn(canvas, column, topDate, topOffset)
}
canvas.setColor(theme.mediumContrastTextColor)
repeat(7) { row ->
val date = dateCalculator.plusDays(topLeftDate, row)
val date = topLeftDate.plus(row)
canvas.setTextAlign(TextAlign.LEFT)
canvas.drawText(dateFormatter.shortWeekdayName(date),
padding + nColumns * squareSize + padding,
@ -74,7 +73,7 @@ class CalendarChart(var today: LocalDate,
drawHeader(canvas, column, topDate)
repeat(7) { row ->
val offset = topOffset - row
val date = dateCalculator.plusDays(topDate, row)
val date = topDate.plus(row)
if (offset < 0) return
drawSquare(canvas,
padding + column * squareSize,

@ -25,8 +25,7 @@ import org.isoron.platform.time.*
class HabitListHeader(private val today: LocalDate,
private val nButtons: Int,
private val theme: Theme,
private val fmt: LocalDateFormatter,
private val calc: LocalDateCalculator) : Component {
private val fmt: LocalDateFormatter) : Component {
override fun draw(canvas: Canvas) {
val width = canvas.getWidth()
@ -44,7 +43,7 @@ class HabitListHeader(private val today: LocalDate,
canvas.setFontSize(theme.smallTextSize)
repeat(nButtons) { index ->
val date = calc.minusDays(today, nButtons - index - 1)
val date = today.minus(nButtons - index - 1)
val name = fmt.shortWeekdayName(date).toUpperCase()
val number = date.day.toString()

@ -24,8 +24,7 @@ import org.isoron.uhabits.models.Checkmark.Companion.CHECKED_AUTOMATIC
import org.isoron.uhabits.models.Checkmark.Companion.CHECKED_MANUAL
import org.isoron.uhabits.models.Checkmark.Companion.UNCHECKED
class CheckmarkList(private val frequency: Frequency,
private val dateCalculator: LocalDateCalculator) {
class CheckmarkList(private val frequency: Frequency) {
private val manualCheckmarks = mutableListOf<Checkmark>()
private val automaticCheckmarks = mutableListOf<Checkmark>()
@ -39,8 +38,7 @@ class CheckmarkList(private val frequency: Frequency,
automaticCheckmarks.clear()
manualCheckmarks.addAll(checks)
automaticCheckmarks.addAll(computeAutomaticCheckmarks(checks,
frequency,
dateCalculator))
frequency))
}
/**
@ -56,7 +54,7 @@ class CheckmarkList(private val frequency: Frequency,
val result = mutableListOf<Int>()
val newest = automaticCheckmarks.first().date
val distToNewest = dateCalculator.distanceInDays(newest, date)
val distToNewest = newest.distanceTo(date)
var fromIndex = 0
val toIndex = automaticCheckmarks.size
@ -75,13 +73,12 @@ class CheckmarkList(private val frequency: Frequency,
* Computes the list of automatic checkmarks a list of manual ones.
*/
fun computeAutomaticCheckmarks(checks: List<Checkmark>,
frequency: Frequency,
calc: LocalDateCalculator
frequency: Frequency
): MutableList<Checkmark> {
val intervals = buildIntervals(checks, frequency, calc)
snapIntervalsTogether(intervals, calc)
return buildCheckmarksFromIntervals(checks, intervals, calc)
val intervals = buildIntervals(checks, frequency)
snapIntervalsTogether(intervals)
return buildCheckmarksFromIntervals(checks, intervals)
}
/**
@ -92,19 +89,17 @@ class CheckmarkList(private val frequency: Frequency,
* the interval, however, still falls within the interval. The length of
* the intervals are also not modified.
*/
fun snapIntervalsTogether(intervals: MutableList<Interval>,
calc: LocalDateCalculator) {
fun snapIntervalsTogether(intervals: MutableList<Interval>) {
for (i in 1 until intervals.size) {
val (begin, center, end) = intervals[i]
val (_, _, prevEnd) = intervals[i - 1]
val gap = calc.distanceInDays(prevEnd, begin) - 1
val endMinusGap = calc.minusDays(end, gap)
if (gap <= 0 || endMinusGap.isOlderThan(center)) continue
intervals[i] = Interval(calc.minusDays(begin, gap),
val gap = prevEnd.distanceTo(begin) - 1
if (gap <= 0 || end.minus(gap).isOlderThan(center)) continue
intervals[i] = Interval(begin.minus(gap),
center,
calc.minusDays(end, gap))
end.minus(gap))
}
}
@ -119,8 +114,7 @@ class CheckmarkList(private val frequency: Frequency,
* receive unchecked checkmarks.
*/
fun buildCheckmarksFromIntervals(checks: List<Checkmark>,
intervals: List<Interval>,
calc: LocalDateCalculator
intervals: List<Interval>
): MutableList<Checkmark> {
if (checks.isEmpty()) throw IllegalArgumentException()
@ -137,25 +131,25 @@ class CheckmarkList(private val frequency: Frequency,
if (check.date.isNewerThan(newest)) newest = check.date
}
val distance = calc.distanceInDays(oldest, newest)
val distance = oldest.distanceTo(newest)
val checkmarks = mutableListOf<Checkmark>()
for (offset in 0..distance)
checkmarks.add(Checkmark(calc.minusDays(newest, offset),
checkmarks.add(Checkmark(newest.minus(offset),
UNCHECKED))
for (interval in intervals) {
val beginOffset = calc.distanceInDays(newest, interval.begin)
val endOffset = calc.distanceInDays(newest, interval.end)
val beginOffset = newest.distanceTo(interval.begin)
val endOffset = newest.distanceTo(interval.end)
for (offset in endOffset..beginOffset) {
checkmarks.set(offset,
Checkmark(calc.minusDays(newest, offset),
Checkmark(newest.minus(offset),
CHECKED_AUTOMATIC))
}
}
for (check in checks) {
val offset = calc.distanceInDays(newest, check.date)
val offset = newest.distanceTo(check.date)
checkmarks.set(offset, Checkmark(check.date, CHECKED_MANUAL))
}
@ -167,8 +161,7 @@ class CheckmarkList(private val frequency: Frequency,
* checkmarks.
*/
fun buildIntervals(checks: List<Checkmark>,
frequency: Frequency,
calc: LocalDateCalculator): MutableList<Interval> {
frequency: Frequency): MutableList<Interval> {
val num = frequency.numerator
val den = frequency.denominator
@ -178,10 +171,9 @@ class CheckmarkList(private val frequency: Frequency,
val first = checks[i]
val last = checks[i + num - 1]
val distance = calc.distanceInDays(first.date, last.date)
if (distance >= den) continue
if (first.date.distanceTo(last.date) >= den) continue
val end = calc.plusDays(first.date, den - 1)
val end = first.date.plus(den - 1)
intervals.add(Interval(first.date, last.date, end))
}

@ -22,8 +22,7 @@ package org.isoron.uhabits.models
import org.isoron.platform.io.*
import org.isoron.platform.time.*
class CheckmarkRepository(db: Database,
val dateCalculator: LocalDateCalculator) {
class CheckmarkRepository(db: Database) {
private val findStatement = db.prepareStatement("select timestamp, value from Repetitions where habit = ? order by timestamp desc")
private val insertStatement = db.prepareStatement("insert into Repetitions(habit, timestamp, value) values (?, ?, ?)")
@ -33,9 +32,8 @@ class CheckmarkRepository(db: Database,
findStatement.bindInt(0, habitId)
val result = mutableListOf<Checkmark>()
while (findStatement.step() == StepResult.ROW) {
val timestamp = Timestamp(findStatement.getLong(0))
val date = Timestamp(findStatement.getLong(0)).localDate
val value = findStatement.getInt(1)
val date = dateCalculator.fromTimestamp(timestamp)
result.add(Checkmark(date, value))
}
findStatement.reset()
@ -43,18 +41,18 @@ class CheckmarkRepository(db: Database,
}
fun insert(habitId: Int, checkmark: Checkmark) {
val timestamp = dateCalculator.toTimestamp(checkmark.date)
val timestamp = checkmark.date.timestamp
insertStatement.bindInt(0, habitId)
insertStatement.bindLong(1, timestamp.unixTimeInMillis)
insertStatement.bindLong(1, timestamp.millisSince1970)
insertStatement.bindInt(2, checkmark.value)
insertStatement.step()
insertStatement.reset()
}
fun delete(habitId: Int, date: LocalDate) {
val timestamp = dateCalculator.toTimestamp(date)
val timestamp = date.timestamp
deleteStatement.bindInt(0, habitId)
deleteStatement.bindLong(1, timestamp.unixTimeInMillis)
deleteStatement.bindLong(1, timestamp.millisSince1970)
deleteStatement.step()
deleteStatement.reset()
}

@ -19,19 +19,20 @@
package org.isoron.platform.time
import java.lang.Math.*
import java.util.*
import java.util.Calendar.*
fun LocalDate.toGregorianCalendar(): GregorianCalendar {
val cal = GregorianCalendar(TimeZone.getTimeZone("GMT"))
cal.set(Calendar.HOUR_OF_DAY, 0)
cal.set(Calendar.MINUTE, 0)
cal.set(Calendar.SECOND, 0)
cal.set(Calendar.MILLISECOND, 0)
cal.set(Calendar.YEAR, this.year)
cal.set(Calendar.MONTH, this.month - 1)
cal.set(Calendar.DAY_OF_MONTH, this.day)
val cal = GregorianCalendar()
cal.timeZone = TimeZone.getTimeZone("GMT")
cal.set(MILLISECOND, 0)
cal.set(SECOND, 0)
cal.set(MINUTE, 0)
cal.set(HOUR_OF_DAY, 0)
cal.set(YEAR, this.year)
cal.set(MONTH, this.month - 1)
cal.set(DAY_OF_MONTH, this.day)
return cal
}
@ -56,35 +57,3 @@ class JavaLocalDateFormatter(private val locale: Locale) : LocalDateFormatter {
return cal.getDisplayName(DAY_OF_WEEK, SHORT, locale);
}
}
class JavaLocalDateCalculator : LocalDateCalculator {
override fun toTimestamp(date: LocalDate): Timestamp {
val cal = date.toGregorianCalendar()
return Timestamp(cal.timeInMillis)
}
override fun fromTimestamp(timestamp: Timestamp): LocalDate {
val cal = GregorianCalendar(TimeZone.getTimeZone("GMT"))
cal.timeInMillis = timestamp.unixTimeInMillis
return cal.toLocalDate()
}
override fun dayOfWeek(date: LocalDate): DayOfWeek {
val cal = date.toGregorianCalendar()
return when (cal.get(DAY_OF_WEEK)) {
Calendar.SATURDAY -> DayOfWeek.SATURDAY
Calendar.SUNDAY -> DayOfWeek.SUNDAY
Calendar.MONDAY -> DayOfWeek.MONDAY
Calendar.TUESDAY -> DayOfWeek.TUESDAY
Calendar.WEDNESDAY -> DayOfWeek.WEDNESDAY
Calendar.THURSDAY -> DayOfWeek.THURSDAY
else -> DayOfWeek.FRIDAY
}
}
override fun plusDays(date: LocalDate, days: Int): LocalDate {
val cal = date.toGregorianCalendar()
cal.add(Calendar.DAY_OF_MONTH, days)
return cal.toLocalDate()
}
}

@ -23,10 +23,10 @@ import junit.framework.TestCase.*
import org.isoron.platform.time.*
import org.junit.*
import java.util.*
import java.util.Calendar.*
class JavaDatesTest {
private val calc = JavaLocalDateCalculator()
private val d1 = LocalDate(2019, 3, 25)
private val d2 = LocalDate(2019, 4, 4)
private val d3 = LocalDate(2019, 5, 12)
@ -34,46 +34,46 @@ class JavaDatesTest {
@Test
fun plusMinusDays() {
val today = LocalDate(2019, 3, 25)
assertEquals(calc.minusDays(today, 28), LocalDate(2019, 2, 25))
assertEquals(calc.plusDays(today, 7), LocalDate(2019, 4, 1))
assertEquals(calc.plusDays(today, 42), LocalDate(2019, 5, 6))
assertEquals(today.minus(28), LocalDate(2019, 2, 25))
assertEquals(today.plus(7), LocalDate(2019, 4, 1))
assertEquals(today.plus(42), LocalDate(2019, 5, 6))
}
@Test
fun shortMonthName() {
var fmt = JavaLocalDateFormatter(Locale.US)
assertEquals(fmt.shortWeekdayName(d1), "Mon")
assertEquals(fmt.shortWeekdayName(d2), "Thu")
assertEquals(fmt.shortWeekdayName(d3), "Sun")
assertEquals(fmt.shortMonthName(d1), "Mar")
assertEquals(fmt.shortMonthName(d2), "Apr")
assertEquals(fmt.shortMonthName(d3), "May")
assertEquals("Mon", fmt.shortWeekdayName(d1))
assertEquals("Thu", fmt.shortWeekdayName(d2))
assertEquals("Sun", fmt.shortWeekdayName(d3))
assertEquals("Mar", fmt.shortMonthName(d1))
assertEquals("Apr", fmt.shortMonthName(d2))
assertEquals("May", fmt.shortMonthName(d3))
fmt = JavaLocalDateFormatter(Locale.JAPAN)
assertEquals(fmt.shortWeekdayName(d1), "")
assertEquals(fmt.shortWeekdayName(d2), "")
assertEquals(fmt.shortWeekdayName(d3), "")
assertEquals(fmt.shortMonthName(d1), "3月")
assertEquals(fmt.shortMonthName(d2), "4月")
assertEquals(fmt.shortMonthName(d3), "5月")
assertEquals("", fmt.shortWeekdayName(d1))
assertEquals("", fmt.shortWeekdayName(d2))
assertEquals("", fmt.shortWeekdayName(d3))
assertEquals("3月", fmt.shortMonthName(d1))
assertEquals("4月", fmt.shortMonthName(d2))
assertEquals("5月", fmt.shortMonthName(d3))
}
@Test
fun weekDay() {
assertEquals(DayOfWeek.SUNDAY, calc.dayOfWeek(LocalDate(2015, 1, 25)))
assertEquals(DayOfWeek.MONDAY, calc.dayOfWeek(LocalDate(2017, 7, 3)))
assertEquals(DayOfWeek.SUNDAY, LocalDate(2015, 1, 25).dayOfWeek)
assertEquals(DayOfWeek.MONDAY, LocalDate(2017, 7, 3).dayOfWeek)
}
@Test
fun timestamps() {
val timestamps = listOf(Timestamp(1555977600000),
Timestamp(968716800000),
Timestamp(0))
Timestamp(946684800000))
val dates = listOf(LocalDate(2019, 4, 23),
LocalDate(2000, 9, 12),
LocalDate(1970, 1, 1))
assertEquals(timestamps, dates.map { d -> calc.toTimestamp(d) })
assertEquals(dates, timestamps.map { t -> calc.fromTimestamp(t) })
LocalDate(2000, 1, 1))
assertEquals(timestamps, dates.map { d -> d.timestamp })
assertEquals(dates, timestamps.map { t -> t.localDate })
}
@Test
@ -96,10 +96,47 @@ class JavaDatesTest {
val d2 = LocalDate(2019, 5, 30)
val d3 = LocalDate(2019, 6, 5)
assertEquals(0, calc.distanceInDays(d1, d1))
assertEquals(20, calc.distanceInDays(d1, d2))
assertEquals(20, calc.distanceInDays(d2, d1))
assertEquals(26, calc.distanceInDays(d1, d3))
assertEquals(6, calc.distanceInDays(d2, d3))
assertEquals(0, d1.distanceTo(d1))
assertEquals(20, d1.distanceTo(d2))
assertEquals(20, d2.distanceTo(d1))
assertEquals(26, d1.distanceTo(d3))
assertEquals(6, d2.distanceTo(d3))
}
@Test
fun gregorianCalendarConversion() {
fun check(cal: GregorianCalendar, daysSince2000: Int) {
val year = cal.get(YEAR)
val month = cal.get(MONTH) + 1
val day = cal.get(DAY_OF_MONTH)
val weekday = cal.get(DAY_OF_WEEK)
val date = LocalDate(year, month, day)
val millisSince1970 = cal.timeInMillis
val msg = "date=$year-$month-$day offset=$daysSince2000"
assertEquals(msg, daysSince2000, date.daysSince2000)
assertEquals(msg, year, date.year)
assertEquals(msg, month, date.month)
assertEquals(msg, day, date.day)
assertEquals(msg, weekday, date.dayOfWeek.index + 1)
assertEquals(msg, millisSince1970, date.timestamp.millisSince1970)
assertEquals(msg, date, date.timestamp.localDate)
}
val cal = GregorianCalendar()
cal.timeZone = TimeZone.getTimeZone("GMT")
cal.set(MILLISECOND, 0)
cal.set(SECOND, 0)
cal.set(MINUTE, 0)
cal.set(HOUR_OF_DAY, 0)
cal.set(DAY_OF_MONTH, 1)
cal.set(MONTH, 0)
cal.set(YEAR, 2000)
// Check all dates from year 2000 until 2400
for(offset in 0..146097) {
check(cal, offset)
cal.add(DAY_OF_YEAR, 1)
}
}
}

@ -38,8 +38,6 @@ open class BaseTest {
val databaseOpener = JavaDatabaseOpener(log)
val dateCalculator = JavaLocalDateCalculator()
val taskRunner = SequentialTaskRunner()
lateinit var db: Database

@ -40,7 +40,6 @@ class BackendTest : BaseTest() {
databaseOpener,
fileOpener,
log,
dateCalculator,
taskRunner)
}

@ -32,7 +32,6 @@ class CalendarChartTest : BaseViewTest() {
val component = CalendarChart(LocalDate(2015, 1, 25),
theme.color(4),
theme,
JavaLocalDateCalculator(),
JavaLocalDateFormatter(Locale.US))
component.series = listOf(1.0, // today
0.2, 0.5, 0.7, 0.0, 0.3, 0.4, 0.6,

@ -30,8 +30,7 @@ class HabitListHeaderTest : BaseViewTest() {
val header = HabitListHeader(LocalDate(2019, 3, 25),
5,
theme,
JavaLocalDateFormatter(Locale.US),
JavaLocalDateCalculator())
JavaLocalDateFormatter(Locale.US))
assertRenders(1200, 96,
"components/HabitListHeader/light.png",
header)

@ -32,7 +32,7 @@ class CheckmarkListTest : BaseTest() {
private val today = LocalDate(2019, 1, 30)
private fun day(offset: Int): LocalDate {
return dateCalculator.minusDays(today, offset)
return today.minus(offset)
}
@Test
@ -45,8 +45,7 @@ class CheckmarkListTest : BaseTest() {
CheckmarkList.Interval(day(18), day(18), day(12)),
CheckmarkList.Interval(day(8), day(8), day(2)))
val actual = CheckmarkList.buildIntervals(checks,
Frequency.WEEKLY,
dateCalculator)
Frequency.WEEKLY)
assertEquals(expected, actual)
}
@ -60,8 +59,7 @@ class CheckmarkListTest : BaseTest() {
CheckmarkList.Interval(day(18), day(18), day(18)),
CheckmarkList.Interval(day(8), day(8), day(8)))
val actual = CheckmarkList.buildIntervals(checks,
Frequency.DAILY,
dateCalculator)
Frequency.DAILY)
assertEquals(expected, actual)
}
@ -77,8 +75,7 @@ class CheckmarkListTest : BaseTest() {
CheckmarkList.Interval(day(22), day(18), day(16)),
CheckmarkList.Interval(day(18), day(15), day(12)))
val actual = CheckmarkList.buildIntervals(checks,
Frequency.TWO_TIMES_PER_WEEK,
dateCalculator)
Frequency.TWO_TIMES_PER_WEEK)
assertEquals(expected, actual)
}
@ -94,7 +91,7 @@ class CheckmarkListTest : BaseTest() {
CheckmarkList.Interval(day(25), day(25), day(19)),
CheckmarkList.Interval(day(18), day(16), day(12)),
CheckmarkList.Interval(day(11), day(8), day(5)))
CheckmarkList.snapIntervalsTogether(original, dateCalculator)
CheckmarkList.snapIntervalsTogether(original)
assertEquals(expected, original)
}
@ -118,8 +115,7 @@ class CheckmarkListTest : BaseTest() {
Checkmark(day(9), CHECKED_AUTOMATIC),
Checkmark(day(10), CHECKED_MANUAL))
val actual = CheckmarkList.buildCheckmarksFromIntervals(checks,
intervals,
dateCalculator)
intervals)
assertEquals(expected, actual)
}
@ -134,8 +130,7 @@ class CheckmarkListTest : BaseTest() {
Checkmark(day(4), CHECKED_AUTOMATIC),
Checkmark(day(5), CHECKED_AUTOMATIC))
val actual = CheckmarkList.buildCheckmarksFromIntervals(reps,
intervals,
dateCalculator)
intervals)
assertEquals(expected, actual)
}
@ -158,14 +153,13 @@ class CheckmarkListTest : BaseTest() {
Checkmark(day(9), CHECKED_AUTOMATIC),
Checkmark(day(10), CHECKED_MANUAL))
val actual = CheckmarkList.computeAutomaticCheckmarks(checks,
Frequency(1, 3),
dateCalculator)
Frequency(1, 3))
assertEquals(expected, actual)
}
@Test
fun testGetValuesUntil() {
val list = CheckmarkList(Frequency(1, 2), dateCalculator)
val list = CheckmarkList(Frequency(1, 2))
list.setManualCheckmarks(listOf(Checkmark(day(4), CHECKED_MANUAL),
Checkmark(day(7), CHECKED_MANUAL)))
val expected = listOf(UNCHECKED,
@ -188,7 +182,7 @@ class CheckmarkListTest : BaseTest() {
@Test
fun testGetValuesUntil2() {
val list = CheckmarkList(Frequency(1, 2), dateCalculator)
val list = CheckmarkList(Frequency(1, 2))
val expected = listOf<Int>()
assertEquals(expected, list.getValuesUntil(day(0)))
}

@ -39,7 +39,7 @@ class CheckmarkRepositoryTest : BaseTest() {
Checkmark(LocalDate(2019, 1, 25), 450),
Checkmark(LocalDate(2019, 1, 20), 1000))
val repository = CheckmarkRepository(db, JavaLocalDateCalculator())
val repository = CheckmarkRepository(db)
for (c in checkmarksA) repository.insert(habitA, c)
for (c in checkmarksB) repository.insert(habitB, c)

@ -26,7 +26,6 @@ import UIKit
databaseOpener: IosDatabaseOpener(withLog: StandardLog()),
fileOpener: IosFileOpener(),
log: StandardLog(),
dateCalculator: IosLocalDateCalculator(),
taskRunner: SequentialTaskRunner())
func application(_ application: UIApplication,
@ -35,7 +34,7 @@ import UIKit
window = UIWindow(frame: UIScreen.main.bounds)
if let window = window {
let nav = UINavigationController()
nav.viewControllers = [ListHabitsController(withBackend: backend)]
nav.viewControllers = [MainScreenController(withBackend: backend)]
window.backgroundColor = UIColor.white
window.rootViewController = nav
window.makeKeyAndVisible()

@ -19,7 +19,7 @@
import UIKit
class ListHabitsCell : UITableViewCell {
class MainScreenCell : UITableViewCell {
var ring: ComponentView
var label = UILabel()
var buttons: [ComponentView] = []
@ -48,7 +48,7 @@ class ListHabitsCell : UITableViewCell {
label.heightAnchor.constraint(equalToConstant: size).isActive = true
stack.addArrangedSubview(label)
for _ in 1...4 {
for _ in 1...3 {
let btn = ComponentView(frame: frame, component: nil)
btn.backgroundColor = .white
btn.widthAnchor.constraint(equalToConstant: size).isActive = true
@ -88,7 +88,7 @@ class ListHabitsCell : UITableViewCell {
}
}
class ListHabitsController: UITableViewController, MainScreenDataSourceListener {
class MainScreenController: UITableViewController, MainScreenDataSourceListener {
var backend: Backend
var dataSource: MainScreenDataSource
var data: MainScreenDataSource.Data?
@ -119,7 +119,7 @@ class ListHabitsController: UITableViewController, MainScreenDataSourceListener
target: self,
action: #selector(self.onCreateHabitClicked))
]
tableView.register(ListHabitsCell.self, forCellReuseIdentifier: "cell")
tableView.register(MainScreenCell.self, forCellReuseIdentifier: "cell")
tableView.backgroundColor = theme.headerBackgroundColor.uicolor
}
@ -140,7 +140,7 @@ class ListHabitsController: UITableViewController, MainScreenDataSourceListener
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let row = indexPath.row
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ListHabitsCell
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MainScreenCell
let color = theme.color(paletteIndex: data!.colors[row].index)
cell.label.text = data!.names[row]
cell.setColor(color)
@ -149,10 +149,9 @@ class ListHabitsController: UITableViewController, MainScreenDataSourceListener
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let component = HabitListHeader(today: LocalDate(year: 2019, month: 3, day: 24),
nButtons: 4,
nButtons: 3,
theme: theme,
fmt: IosLocalDateFormatter(),
calc: IosLocalDateCalculator())
fmt: IosLocalDateFormatter())
return ComponentView(frame: CGRect(x: 0, y: 0, width: 100, height: CGFloat(theme.checkmarkButtonSize)),
component: component)
}

@ -47,7 +47,6 @@ class ShowHabitController : UITableViewController {
let component = CalendarChart(today: LocalDate(year: 2019, month: 3, day: 15),
color: color,
theme: theme,
dateCalculator: IosLocalDateCalculator(),
dateFormatter: IosLocalDateFormatter())
let cell = UITableViewCell()
let view = ComponentView(frame: cell.frame, component: component)

@ -54,35 +54,3 @@ class IosLocalDateFormatter : NSObject, LocalDateFormatter {
return fmt.string(from: date.iosDate)
}
}
class IosLocalDateCalculator : NSObject, LocalDateCalculator {
func toTimestamp(date: LocalDate) -> Timestamp {
return Timestamp(unixTimeInMillis: Int64(date.iosDate.timeIntervalSince1970 * 1000))
}
func fromTimestamp(timestamp: Timestamp) -> LocalDate {
return Date.init(timeIntervalSince1970: Double(timestamp.unixTimeInMillis / 1000)).localDate
}
let calendar = Calendar(identifier: .gregorian)
func dayOfWeek(date: LocalDate) -> DayOfWeek {
let weekday = calendar.component(.weekday, from: date.iosDate)
switch(weekday) {
case 1: return DayOfWeek.sunday
case 2: return DayOfWeek.monday
case 3: return DayOfWeek.tuesday
case 4: return DayOfWeek.wednesday
case 5: return DayOfWeek.thursday
case 6: return DayOfWeek.friday
default: return DayOfWeek.saturday
}
}
func plusDays(date: LocalDate, days: Int32) -> LocalDate {
let d2 = date.iosDate.addingTimeInterval(24.0 * 60 * 60 * Double(days))
return LocalDate(year: Int32(calendar.component(.year, from: d2)),
month: Int32(calendar.component(.month, from: d2)),
day: Int32(calendar.component(.day, from: d2)))
}
}

@ -9,7 +9,7 @@
/* Begin PBXBuildFile section */
0057EC2B224C4CDB00C49288 /* icons in Resources */ = {isa = PBXBuildFile; fileRef = 0057EC2A224C4CDB00C49288 /* icons */; };
00A5B42822009F590024E00C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00A5B42722009F590024E00C /* AppDelegate.swift */; };
00A5B42A22009F590024E00C /* ListHabitsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00A5B42922009F590024E00C /* ListHabitsController.swift */; };
00A5B42A22009F590024E00C /* MainScreenController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00A5B42922009F590024E00C /* MainScreenController.swift */; };
00A5B42F22009F5A0024E00C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 00A5B42E22009F5A0024E00C /* Assets.xcassets */; };
00C0C6A52246537A003D8AF0 /* IosFilesTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00C0C6A122465365003D8AF0 /* IosFilesTest.swift */; };
00C0C6A62246537E003D8AF0 /* IosDatabaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00C0C6A222465365003D8AF0 /* IosDatabaseTest.swift */; };
@ -59,7 +59,7 @@
0057EC2A224C4CDB00C49288 /* icons */ = {isa = PBXFileReference; lastKnownFileType = folder; path = icons; sourceTree = "<group>"; };
00A5B42422009F590024E00C /* uhabits.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = uhabits.app; sourceTree = BUILT_PRODUCTS_DIR; };
00A5B42722009F590024E00C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
00A5B42922009F590024E00C /* ListHabitsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListHabitsController.swift; sourceTree = "<group>"; };
00A5B42922009F590024E00C /* MainScreenController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainScreenController.swift; sourceTree = "<group>"; };
00A5B42E22009F5A0024E00C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
00A5B43322009F5A0024E00C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
00A5B43822009F5A0024E00C /* uhabitsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = uhabitsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
@ -107,7 +107,7 @@
isa = PBXGroup;
children = (
00D48BD22200AC1600CC4527 /* EditHabitController.swift */,
00A5B42922009F590024E00C /* ListHabitsController.swift */,
00A5B42922009F590024E00C /* MainScreenController.swift */,
00C0C6DE224A35FC003D8AF0 /* ShowHabitController.swift */,
);
path = Frontend;
@ -317,7 +317,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "cd ../core\n./gradlew linkDebugFrameworkIOS\n";
shellScript = "cd ../core\n./gradlew linkIOS\n";
};
/* End PBXShellScriptBuildPhase section */
@ -332,7 +332,7 @@
00C0C6E0224A3602003D8AF0 /* ShowHabitController.swift in Sources */,
00C0C6A8224654A2003D8AF0 /* IosDatabase.swift in Sources */,
00C0C6DB2247E6B0003D8AF0 /* IosDates.swift in Sources */,
00A5B42A22009F590024E00C /* ListHabitsController.swift in Sources */,
00A5B42A22009F590024E00C /* MainScreenController.swift in Sources */,
00A5B42822009F590024E00C /* AppDelegate.swift in Sources */,
00D48BD32200AC1600CC4527 /* EditHabitController.swift in Sources */,
);

@ -83,7 +83,8 @@
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "00A5B42322009F590024E00C"
@ -91,7 +92,7 @@
BlueprintName = "uhabits"
ReferencedContainer = "container:uhabits.xcodeproj">
</BuildableReference>
</MacroExpansion>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">

Loading…
Cancel
Save