From b58af03a7ce86fd68d497e96ba37ac1d099cfe26 Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Sat, 13 Feb 2021 18:00:08 -0600 Subject: [PATCH] HabitBullCSVImporter: Accept multiple date formats Fixes 762 --- uhabits-core/assets/test/habitbull2.csv | 82 +++++++++++++++++++ .../uhabits/core/io/HabitBullCSVImporter.kt | 69 ++++++++++++---- .../org/isoron/uhabits/core/io/ImportTest.kt | 16 +++- 3 files changed, 152 insertions(+), 15 deletions(-) create mode 100644 uhabits-core/assets/test/habitbull2.csv diff --git a/uhabits-core/assets/test/habitbull2.csv b/uhabits-core/assets/test/habitbull2.csv new file mode 100644 index 000000000..890632bbd --- /dev/null +++ b/uhabits-core/assets/test/habitbull2.csv @@ -0,0 +1,82 @@ +HabitName,HabitDescription,HabitCategory,CalendarDate,Value,CommentText +H1,,C1,11/5/2020,1, +H2,,C2,11/5/2020,-2150000000, +H3,Habit 3,C3,4/11/2019,1, +H3,Habit 3,C3,4/12/2019,1, +H3,Habit 3,C3,4/13/2019,0, +H3,Habit 3,C3,4/14/2019,1, +H3,Habit 3,C3,4/15/2019,1, +H3,Habit 3,C3,4/16/2019,1, +H3,Habit 3,C3,4/17/2019,1, +H3,Habit 3,C3,4/18/2019,1, +H3,Habit 3,C3,4/19/2019,0, +H3,Habit 3,C3,4/20/2019,1, +H3,Habit 3,C3,4/21/2019,1, +H3,Habit 3,C3,4/22/2019,1, +H3,Habit 3,C3,4/23/2019,0, +H3,Habit 3,C3,4/24/2019,1, +H3,Habit 3,C3,4/25/2019,1, +H3,Habit 3,C3,4/26/2019,1, +H3,Habit 3,C3,4/27/2019,0, +H3,Habit 3,C3,4/28/2019,1, +H3,Habit 3,C3,4/29/2019,0, +H3,Habit 3,C3,4/30/2019,1, +H3,Habit 3,C3,5/1/2019,1, +H3,Habit 3,C3,5/2/2019,1, +H3,Habit 3,C3,5/3/2019,1, +H3,Habit 3,C3,5/4/2019,1, +H3,Habit 3,C3,5/5/2019,1, +H3,Habit 3,C3,5/6/2019,0, +H3,Habit 3,C3,5/7/2019,1, +H3,Habit 3,C3,5/8/2019,1, +H3,Habit 3,C3,5/9/2019,1, +H3,Habit 3,C3,5/10/2019,1, +H3,Habit 3,C3,5/11/2019,1, +H3,Habit 3,C3,5/12/2019,1, +H3,Habit 3,C3,5/13/2019,1, +H3,Habit 3,C3,5/14/2019,1, +H3,Habit 3,C3,5/15/2019,1, +H3,Habit 3,C3,5/16/2019,1, +H3,Habit 3,C3,5/17/2019,1, +H3,Habit 3,C3,5/18/2019,0, +H3,Habit 3,C3,5/19/2019,1, +H3,Habit 3,C3,5/20/2019,1, +H3,Habit 3,C3,5/21/2019,1, +H3,Habit 3,C3,5/22/2019,1, +H3,Habit 3,C3,5/23/2019,1, +H3,Habit 3,C3,5/24/2019,1, +H3,Habit 3,C3,5/25/2019,1, +H3,Habit 3,C3,5/26/2019,1, +H3,Habit 3,C3,5/27/2019,1, +H3,Habit 3,C3,5/28/2019,1, +H3,Habit 3,C3,5/29/2019,1, +H3,Habit 3,C3,5/30/2019,0, +H3,Habit 3,C3,5/31/2019,1, +H3,Habit 3,C3,6/1/2019,1, +H3,Habit 3,C3,6/2/2019,1, +H3,Habit 3,C3,6/3/2019,1, +H3,Habit 3,C3,6/4/2019,1, +H3,Habit 3,C3,6/5/2019,1, +H3,Habit 3,C3,6/6/2019,1, +H3,Habit 3,C3,6/7/2019,1, +H3,Habit 3,C3,6/8/2019,1, +H3,Habit 3,C3,6/9/2019,1, +H3,Habit 3,C3,6/10/2019,1, +H3,Habit 3,C3,6/11/2019,1, +H3,Habit 3,C3,6/12/2019,1, +H3,Habit 3,C3,6/13/2019,1, +H3,Habit 3,C3,6/14/2019,0, +H3,Habit 3,C3,6/15/2019,1, +H4,Habit 4,C4,11/6/2020,1, +H4,Habit 4,C4,11/9/2020,1, +H4,Habit 4,C4,11/11/2020,1, +H4,Habit 4,C4,12/30/2020,-2150000000, +H5,Habit 5,C4,11/5/2020,1, +H5,Habit 5,C4,11/6/2020,1, +H5,Habit 5,C4,11/9/2020,1, +H5,Habit 5,C4,11/11/2020,1, +H6,Habit 6,C5,11/5/2020,1, +H6,Habit 6,C5,11/6/2020,1, +H6,Habit 6,C5,11/9/2020,1, +H6,Habit 6,C5,11/11/2020,1, +H6,Habit 6,C5,11/12/2020,1, diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/io/HabitBullCSVImporter.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/io/HabitBullCSVImporter.kt index cb47ae492..bdf7b70c9 100644 --- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/io/HabitBullCSVImporter.kt +++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/io/HabitBullCSVImporter.kt @@ -25,11 +25,19 @@ import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.HabitList import org.isoron.uhabits.core.models.ModelFactory import org.isoron.uhabits.core.models.Timestamp -import org.isoron.uhabits.core.utils.DateUtils import java.io.BufferedReader import java.io.File import java.io.FileReader +import java.text.DateFormat +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.Calendar.DAY_OF_MONTH +import java.util.Calendar.MONTH +import java.util.Calendar.YEAR +import java.util.Date +import java.util.GregorianCalendar import java.util.HashMap +import java.util.Locale import javax.inject.Inject /** @@ -39,8 +47,11 @@ class HabitBullCSVImporter @Inject constructor( private val habitList: HabitList, private val modelFactory: ModelFactory, + logging: Logging, ) : AbstractImporter() { + val logger = logging.getLogger("HabitBullCSVImporter") + override fun canHandle(file: File): Boolean { val reader = BufferedReader(FileReader(file)) val line = reader.readLine() @@ -50,19 +61,11 @@ class HabitBullCSVImporter override fun importHabitsFromFile(file: File) { val reader = CSVReader(FileReader(file)) val map = HashMap() - for (line in reader) { - val name = line[0] + for (cols in reader) { + val name = cols[0] if (name == "HabitName") continue - val description = line[1] - val dateString = line[3].split("-").toTypedArray() - val year = dateString[0].toInt() - val month = dateString[1].toInt() - val day = dateString[2].toInt() - val date = DateUtils.getStartOfTodayCalendar() - date[year, month - 1] = day - val timestamp = Timestamp(date.timeInMillis) - val value = line[4].toInt() - if (value != 1) continue + val description = cols[1] + val timestamp = parseTimestamp(cols[3]) var h = map[name] if (h == null) { h = modelFactory.buildHabit() @@ -71,8 +74,46 @@ class HabitBullCSVImporter h.frequency = Frequency.DAILY habitList.add(h) map[name] = h + logger.info("Creating habit: $name") + } + if (parseInt(cols[4]) == 1) { + h.originalEntries.add(Entry(timestamp, Entry.YES_MANUAL)) + } + } + } + + private fun parseTimestamp(rawValue: String): Timestamp { + val formats = listOf( + DateFormat.getDateInstance(DateFormat.SHORT), + SimpleDateFormat("yyyy-MM-dd", Locale.US), + SimpleDateFormat("MM/dd/yyyy", Locale.US), + ) + var parsedDate: Date? = null + for (fmt in formats) { + try { + parsedDate = fmt.parse(rawValue) + } catch (e: ParseException) { + // ignored } - h.originalEntries.add(Entry(timestamp, Entry.YES_MANUAL)) + } + if (parsedDate == null) { + throw Exception("Unrecognized date format: $rawValue") + } + val parsedCalendar = GregorianCalendar() + parsedCalendar.time = parsedDate + return Timestamp.from( + parsedCalendar[YEAR], + parsedCalendar[MONTH], + parsedCalendar[DAY_OF_MONTH], + ) + } + + private fun parseInt(rawValue: String): Int { + return try { + rawValue.toInt() + } catch (e: NumberFormatException) { + logger.error("Could not parse int: $rawValue. Replacing by zero.") + 0 } } } diff --git a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/io/ImportTest.kt b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/io/ImportTest.kt index 0be326074..6a8bd0c4f 100644 --- a/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/io/ImportTest.kt +++ b/uhabits-core/src/jvmTest/java/org/isoron/uhabits/core/io/ImportTest.kt @@ -56,6 +56,20 @@ class ImportTest : BaseUnitTest() { assertFalse(isChecked(habit, 2016, 3, 20)) } + @Test + @Throws(IOException::class) + fun testHabitBullCSV2() { + importFromFile("habitbull2.csv") + assertThat(habitList.size(), equalTo(6)) + val habit = habitList.getByPosition(2) + assertThat(habit.name, equalTo("H3")) + assertThat(habit.description, equalTo("Habit 3")) + assertThat(habit.frequency, equalTo(Frequency.DAILY)) + assertTrue(isChecked(habit, 2019, 4, 11)) + assertTrue(isChecked(habit, 2019, 5, 7)) + assertFalse(isChecked(habit, 2019, 6, 14)) + } + @Test @Throws(IOException::class) fun testLoopDB() { @@ -129,7 +143,7 @@ class ImportTest : BaseUnitTest() { ), RewireDBImporter(habitList, modelFactory, databaseOpener), TickmateDBImporter(habitList, modelFactory, databaseOpener), - HabitBullCSVImporter(habitList, modelFactory) + HabitBullCSVImporter(habitList, modelFactory, StandardLogging()) ) assertTrue(importer.canHandle(file)) importer.importHabitsFromFile(file)