diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt
index 57f9a40c..5b01d476 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt
@@ -41,8 +41,9 @@ import pl.szczodrzynski.edziennik.data.db.migration.*
     Lesson::class,
     ConfigEntry::class,
     LibrusLesson::class,
+    TimetableManual::class,
     Metadata::class
-], version = 78)
+], version = 79)
 @TypeConverters(
         ConverterTime::class,
         ConverterDate::class,
@@ -80,6 +81,7 @@ abstract class AppDb : RoomDatabase() {
     abstract fun timetableDao(): TimetableDao
     abstract fun configDao(): ConfigDao
     abstract fun librusLessonDao(): LibrusLessonDao
+    abstract fun timetableManualDao(): TimetableManualDao
     abstract fun metadataDao(): MetadataDao
 
     companion object {
@@ -161,7 +163,8 @@ abstract class AppDb : RoomDatabase() {
                 Migration75(),
                 Migration76(),
                 Migration77(),
-                Migration78()
+                Migration78(),
+                Migration79()
         ).allowMainThreadQueries().build()
     }
 }
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/converter/ConverterDateInt.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/converter/ConverterDateInt.kt
index fdcb5793..0ffaadcb 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/converter/ConverterDateInt.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/converter/ConverterDateInt.kt
@@ -8,7 +8,7 @@ import pl.szczodrzynski.edziennik.utils.models.Date
 
 class ConverterDateInt {
     @TypeConverter
-    fun toDate(value: Int): Date = Date.fromValue(value)
+    fun toDate(value: Int): Date? = if (value == 0) null else Date.fromValue(value)
 
     @TypeConverter
     fun toInt(date: Date?): Int = date?.value ?: 0
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/TimetableManualDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/TimetableManualDao.kt
new file mode 100644
index 00000000..fba4d226
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/TimetableManualDao.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) Kuba SzczodrzyƄski 2020-2-22.
+ */
+
+package pl.szczodrzynski.edziennik.data.db.dao
+
+import androidx.lifecycle.LiveData
+import androidx.room.*
+import pl.szczodrzynski.edziennik.data.db.entity.TimetableManual
+import pl.szczodrzynski.edziennik.utils.models.Date
+
+@Dao
+interface TimetableManualDao {
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    fun add(timetableManual: TimetableManual)
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    fun addAll(timetableManualList: List<TimetableManual>)
+
+    @Query("SELECT * FROM timetableManual WHERE profileId = :profileId")
+    fun getAll(profileId: Int): LiveData<List<TimetableManual>>
+
+    @Query("SELECT * FROM timetableManual WHERE profileId = :profileId AND date >= :dateFrom AND date <= :dateTo")
+    fun getAllByDateRange(profileId: Int, dateFrom: Date, dateTo: Date): LiveData<List<TimetableManual>>
+
+    @Query("SELECT * FROM timetableManual WHERE profileId = :profileId AND (date IS NULL OR date = 0 OR date >= :dateFrom)")
+    fun getAllToDisplay(profileId: Int, dateFrom: Date): LiveData<List<TimetableManual>>
+
+    @Delete
+    fun delete(timetableManual: TimetableManual)
+
+    @Query("DELETE FROM timetableManual WHERE profileId = :profileId")
+    fun clear(profileId: Int)
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/TimetableManual.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/TimetableManual.kt
new file mode 100644
index 00000000..2f80e828
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/TimetableManual.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) Kuba SzczodrzyƄski 2020-2-22.
+ */
+
+package pl.szczodrzynski.edziennik.data.db.entity
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.Index
+import androidx.room.PrimaryKey
+import pl.szczodrzynski.edziennik.utils.models.Date
+import pl.szczodrzynski.edziennik.utils.models.Time
+
+@Entity(tableName = "timetableManual",
+        indices = [
+            Index(value = ["profileId", "date"]),
+            Index(value = ["profileId", "weekDay"])
+        ])
+class TimetableManual(
+        val profileId: Int,
+        var type: Int,
+        var repeatBy: Int,
+
+        @PrimaryKey(autoGenerate = true)
+        var id: Int = 0
+) {
+    companion object {
+        const val TYPE_NORMAL = 0
+        const val TYPE_CANCELLED = 1
+        const val TYPE_CHANGE = 2
+        const val TYPE_SHIFTED_SOURCE = 3
+        const val TYPE_SHIFTED_TARGET = 4
+        const val TYPE_REMOVED = 5
+        const val TYPE_CLASSROOM = 6
+        const val REPEAT_WEEKLY = 0
+        const val REPEAT_ONCE = 1
+        const val REPEAT_BY_SUBJECT = 2
+    }
+
+    // `date` for one time lesson
+    @ColumnInfo(typeAffinity = ColumnInfo.INTEGER)
+    var date: Date? = null
+    // `weekDay` for repeating lesson (every week)
+    var weekDay: Int? = null
+
+    var lessonNumber: Int? = null
+    var startTime: Time? = null
+    var endTime: Time? = null
+
+    var subjectId: Long? = null
+    var teacherId: Long? = null
+    var teamId: Long? = null
+    var classroom: String? = null
+
+    fun verifyParams(): Boolean {
+        return when (repeatBy) {
+            REPEAT_WEEKLY -> date == null && weekDay != null
+            REPEAT_ONCE -> date != null && weekDay == null
+            REPEAT_BY_SUBJECT -> date == null && weekDay == null && subjectId != null
+            else -> false
+        }
+    }
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration79.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration79.kt
new file mode 100644
index 00000000..c078b865
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration79.kt
@@ -0,0 +1,27 @@
+package pl.szczodrzynski.edziennik.data.db.migration
+
+import androidx.room.migration.Migration
+import androidx.sqlite.db.SupportSQLiteDatabase
+
+class Migration79 : Migration(78, 79) {
+    override fun migrate(database: SupportSQLiteDatabase) {
+        // manual timetable implementation
+        database.execSQL("""CREATE TABLE timetableManual (
+            profileId INTEGER NOT NULL,
+            id INTEGER PRIMARY KEY NOT NULL,
+            type INTEGER NOT NULL,
+            repeatBy INTEGER NOT NULL DEFAULT 0,
+            date INTEGER DEFAULT NULL,
+            weekDay INTEGER DEFAULT NULL,
+            lessonNumber INTEGER DEFAULT NULL,
+            startTime TEXT DEFAULT NULL,
+            endTime TEXT DEFAULT NULL,
+            subjectId INTEGER DEFAULT NULL,
+            teacherId INTEGER DEFAULT NULL,
+            teamId INTEGER DEFAULT NULL,
+            classroom TEXT DEFAULT NULL
+        )""")
+        database.execSQL("CREATE INDEX index_timetableManual_profileId_date ON timetableManual (profileId, date)")
+        database.execSQL("CREATE INDEX index_timetableManual_profileId_weekDay ON timetableManual (profileId, weekDay)")
+    }
+}