diff --git a/.gitignore b/.gitignore
index ea8c4bf..5f9ff5d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
 /target
+.vscode/
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
index 9f2e1df..363c425 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -110,6 +110,12 @@ dependencies = [
  "syn 2.0.15",
 ]
 
+[[package]]
+name = "either"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
+
 [[package]]
 name = "hashbrown"
 version = "0.12.3"
@@ -150,6 +156,15 @@ dependencies = [
  "hashbrown",
 ]
 
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
 [[package]]
 name = "js-sys"
 version = "0.3.61"
@@ -164,6 +179,7 @@ name = "laika"
 version = "0.1.0"
 dependencies = [
  "chrono",
+ "itertools",
  "serde",
  "sysinfo",
  "toml",
diff --git a/Cargo.toml b/Cargo.toml
index b7f29d8..e87d8f1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,6 +7,7 @@ edition = "2021"
 
 [dependencies]
 chrono = "0.4.24"
+itertools = "0.10.5"
 serde = { version = "1.0.160", features = ["derive"] }
 sysinfo = { version = "0.28.4", default-features = false }
 toml = "0.7.3"
diff --git a/laika.toml b/laika.toml
index 8a1e39d..ba014bd 100644
--- a/laika.toml
+++ b/laika.toml
@@ -2,6 +2,9 @@ command = "calc.exe"
 target = "CalculatorApp.exe"
 workdir = "."
 delay = 2
-start = { hour = 11, minute = 50 }
-end = { hour = 12, minute = 55 }
+#start = { hour = 11, minute = 50 }
+start = "11:50"
+#end = { hour = 12, minute = 55 }
+end = "12:55"
+
 
diff --git a/src/main.rs b/src/main.rs
index 0a72f13..8a74d5c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,16 +2,37 @@
 
 use chrono::prelude::*;
 use chrono::{DateTime, Local, TimeZone};
+use itertools::Itertools;
 use serde::Deserialize;
 use std::path::Path;
+use std::str::FromStr;
 use std::{thread, time::Duration};
 use sysinfo::{System, SystemExt};
 
 use std::{env, fs, path::PathBuf, process::Command};
 
+trait ErrorToString {
+    type Output;
+    fn str_err(self) -> std::result::Result<Self::Output, String>;
+}
+
+impl<T, E> ErrorToString for std::result::Result<T, E>
+where
+    E: std::error::Error,
+{
+    type Output = T;
+    fn str_err(self) -> std::result::Result<Self::Output, String> {
+        self.map_err(|e| e.to_string())
+    }
+}
+
+/// Time with hours (24) and minutes (60)
 #[derive(Deserialize, Default, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
+#[serde(try_from = "String")]
 struct TimeHM {
+    /// hour num (0..23)
     hour: u8,
+    /// minute num (0..59)
     minute: u8,
 }
 
@@ -24,17 +45,60 @@ impl<Tz: TimeZone> From<DateTime<Tz>> for TimeHM {
     }
 }
 
-#[derive(Deserialize, Default)]
+impl FromStr for TimeHM {
+    type Err = String;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        if let Some((str_hour, str_minute)) = s.split(':').collect_tuple() {
+            let hour: u8 = str_hour.parse().str_err()?;
+            let minute: u8 = str_minute.parse().str_err()?;
+            Ok(TimeHM { hour, minute })
+        } else {
+            Err("invalid time, must be hh:mm".into())
+        }
+    }
+}
+
+impl TryFrom<String> for TimeHM {
+    type Error = String;
+
+    fn try_from(value: String) -> Result<Self, Self::Error> {
+        value.parse()
+    }
+}
+
+/// Application config
+#[derive(Deserialize, Default, Debug)]
 struct Config {
+    /// is application active
+    #[serde(default = "default_true")]
+    active: bool,
+    /// need to exit
+    #[serde(default)]
+    exit_now: bool,
+    /// target command
     command: String,
+    /// target process name
+    #[serde(default)]
     target: String,
+    /// workdir for launch target command
+    #[serde(default)]
     workdir: String,
+    /// args for launch target command
+    #[serde(default)]
     args: Option<Vec<String>>,
+    /// start time
     start: TimeHM,
+    /// end time
     end: TimeHM,
+    /// check delay (seconds)
     delay: u32,
 }
 
+fn default_true() -> bool {
+    true
+}
+
 impl Config {
     fn read<P: AsRef<Path>>(path: P) -> Result<Self, String> {
         let data = fs::read_to_string(path).map_err(|e| format!("can't read config: {:?}", e))?;
@@ -75,39 +139,58 @@ impl Config {
                 .unwrap()
         }
     }
+    fn is_valid(&self) -> bool {
+        (!self.command.is_empty()) && self.delay > 0
+    }
+}
+
+struct Laika {
+    config: Config,
+}
+
+impl From<Config> for Laika {
+    fn from(config: Config) -> Self {
+        Laika { config }
+    }
+}
+
+impl Laika {
     fn is_in_time(&self, value: TimeHM) -> bool {
-        self.start <= value && value <= self.end
+        self.config.start <= value && value <= self.config.end
     }
     fn is_active(&self) -> bool {
         let now: DateTime<Local> = Local::now();
-        self.is_in_time(now.into())
+        self.config.active && self.config.is_valid() && self.is_in_time(now.into())
     }
     fn sleep(&self) {
-        thread::sleep(Duration::from_secs(self.delay.into()));
+        thread::sleep(Duration::from_secs(self.config.delay.into()));
     }
     fn is_target_alive(&self) -> bool {
-        let name = self.target_name();
+        let name = self.config.target_name();
+        if name.is_empty() {
+            return false;
+        }
         let sp = System::new_all();
         let procs = sp.processes_by_exact_name(&name);
         procs.count() > 0
     }
     fn relaunch_target(&self) {
-        let mut cmd = Command::new(&self.command);
-        cmd.current_dir(&self.workdir);
-        if let Some(args) = &self.args {
+        let mut cmd = Command::new(&self.config.command);
+        cmd.current_dir(&self.config.workdir);
+        if let Some(args) = &self.config.args {
             cmd.args(args);
         }
         let _res = cmd.spawn();
     }
-    fn is_valid(&self) -> bool {
-        (!self.command.is_empty()) && self.delay > 0
-    }
+
     fn main_loop(self) {
-        if !self.is_valid() {
+        if !self.config.is_valid() {
             return;
         }
         loop {
-            if self.is_active() && !self.is_target_alive() {
+            if self.config.exit_now {
+                return;
+            } else if self.is_active() && !self.is_target_alive() {
                 self.relaunch_target();
             }
             self.sleep();
@@ -116,5 +199,7 @@ impl Config {
 }
 
 fn main() {
-    Config::get().main_loop()
+    let config = Config::get();
+    let laika: Laika = config.into();
+    laika.main_loop();
 }