diff --git a/pom.xml b/pom.xml
index ba3d0a4..c0d7d0a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -33,7 +33,7 @@
 		
 			eu.m724
 		    wtapi
-		    0.8.0
+		    0.8.3
 		
 		
 			eu.m724
@@ -46,6 +46,13 @@
 			3.0.3
 			compile
 		
+		
+		
+			junit
+			junit
+			4.13.2
+			test
+		
 
 	
 	
diff --git a/realweather.iml b/realweather.iml
index a589521..7f2ab86 100644
--- a/realweather.iml
+++ b/realweather.iml
@@ -1,5 +1,10 @@
 
 
+  
+    
+      
+    
+  
   
     
       
diff --git a/src/main/java/eu/m724/realweather/RealWeatherPlugin.java b/src/main/java/eu/m724/realweather/RealWeatherPlugin.java
index f21ab4d..f6e0764 100644
--- a/src/main/java/eu/m724/realweather/RealWeatherPlugin.java
+++ b/src/main/java/eu/m724/realweather/RealWeatherPlugin.java
@@ -135,7 +135,7 @@ public class RealWeatherPlugin extends JavaPlugin {
 		getCommand("rwadmin").setExecutor(new AdminCommand(updater));
 		getCommand("geo").setExecutor(new GeoCommand());
 		
-		if (GlobalConstants.timeConfig.enabled)
+		if (GlobalConstants.timeConfig.enabled())
 			getCommand("localtime").setExecutor(new LocalTimeCommand());
 		
 		if (GlobalConstants.weatherConfig.enabled)
@@ -149,7 +149,7 @@ public class RealWeatherPlugin extends JavaPlugin {
 				GlobalConstants.thunderConfig.enabled ? GlobalConstants.thunderConfig.provider : "off"
 		));
 		metrics.addCustomChart(new SimplePie("real_time", () ->
-				GlobalConstants.timeConfig.enabled ? "on" : "off"
+				GlobalConstants.timeConfig.enabled() ? "on" : "off"
 		));
 
 		DebugLogger.info("ended loading", 1);
diff --git a/src/main/java/eu/m724/realweather/commands/AdminCommand.java b/src/main/java/eu/m724/realweather/commands/AdminCommand.java
index a945dea..75648ef 100644
--- a/src/main/java/eu/m724/realweather/commands/AdminCommand.java
+++ b/src/main/java/eu/m724/realweather/commands/AdminCommand.java
@@ -63,13 +63,13 @@ public class AdminCommand implements CommandExecutor {
 		}
 		
 		componentBuilder.append("\nTime: ").color(ChatColor.GOLD);
-		componentBuilder.append(timeConfig.enabled ? enabledComponent : disabledComponent);
+		componentBuilder.append(timeConfig.enabled() ? enabledComponent : disabledComponent);
 		
-		if (timeConfig.enabled) {
+		if (timeConfig.enabled()) {
 			componentBuilder.append(" Scale: ").color(ChatColor.GOLD);
-			componentBuilder.append(Double.toString(timeConfig.scale) + "\n").color(ChatColor.AQUA);
+			componentBuilder.append(Double.toString(timeConfig.scale()) + "\n").color(ChatColor.AQUA);
 			
-			long worldTime = timeConfig.calculateWorldTimeSeconds();
+			long worldTime = 0; // TODO timeConfig.calculateWorldTimeSeconds();
 			Duration worldTimeDuration = Duration.ofSeconds(worldTime);
 			
 			String worldTimeFormatted = String.format("%d:%02d:%02d\n",
@@ -81,7 +81,7 @@ public class AdminCommand implements CommandExecutor {
 			componentBuilder.append(worldTimeFormatted).color(ChatColor.AQUA);
 			
 			componentBuilder.append(" Dynamic: ").color(ChatColor.GOLD);
-			componentBuilder.append(timeConfig.dynamic ? enabledComponent : disabledComponent);
+			componentBuilder.append(timeConfig.dynamic() ? enabledComponent : disabledComponent);
 		}
 
 		componentBuilder.append("\nThunder: ").color(ChatColor.GOLD);
diff --git a/src/main/java/eu/m724/realweather/commands/LocalTimeCommand.java b/src/main/java/eu/m724/realweather/commands/LocalTimeCommand.java
index 358ad27..f0050bd 100644
--- a/src/main/java/eu/m724/realweather/commands/LocalTimeCommand.java
+++ b/src/main/java/eu/m724/realweather/commands/LocalTimeCommand.java
@@ -23,7 +23,7 @@ public class LocalTimeCommand implements CommandExecutor {
 	public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
 		Player player = sender instanceof Player ? (Player) sender : null;
 		
-		long worldTime = timeConfig.calculateWorldTimeSeconds();
+		long worldTime = 0; // TODO timeConfig.calculateWorldTimeSeconds();
 		Duration worldTimeDuration = Duration.ofSeconds(worldTime);
 		
 		String worldTimeFormatted = String.format("%d:%02d:%02d",
@@ -37,10 +37,10 @@ public class LocalTimeCommand implements CommandExecutor {
 		
 		sender.spigot().sendMessage(component);
 		
-		if (timeConfig.dynamic && player != null && player.hasPermission("realweather.dynamic")) {
+		if (timeConfig.dynamic() && player != null && player.hasPermission("realweather.dynamic")) {
 			Coordinates coordinates = mapper.locationToCoordinates(player.getLocation());
 			
-			long offsetTime = timeConfig.calculateTimeOffsetSeconds(coordinates.longitude);
+			long offsetTime = 0; // TODO timeConfig.calculateTimeOffsetSeconds(coordinates.longitude);
 			boolean negative = offsetTime < 0;
 			
 			Duration localTimeDuration = worldTimeDuration.plus(offsetTime, ChronoUnit.SECONDS);
diff --git a/src/main/java/eu/m724/realweather/time/AsyncPlayerTimeTask.java b/src/main/java/eu/m724/realweather/time/AsyncPlayerTimeTask.java
index 740f2c4..43b2f88 100644
--- a/src/main/java/eu/m724/realweather/time/AsyncPlayerTimeTask.java
+++ b/src/main/java/eu/m724/realweather/time/AsyncPlayerTimeTask.java
@@ -10,12 +10,14 @@ import eu.m724.realweather.mapper.Mapper;
 import eu.m724.wtapi.object.Coordinates;
 
 public class AsyncPlayerTimeTask extends BukkitRunnable {
-	private Server server = GlobalConstants.getPlugin().getServer();
-	private Mapper mapper = GlobalConstants.getMapper();
+	private final Server server = GlobalConstants.getPlugin().getServer();
+	private final Mapper mapper = GlobalConstants.getMapper();
 	
-	private TimeConfig timeConfig;
+	private final TimeConverter timeConverter;
+	private final TimeConfig timeConfig;
 	
-	AsyncPlayerTimeTask(TimeConfig timeConfig) {
+	AsyncPlayerTimeTask(TimeConverter timeConverter, TimeConfig timeConfig) {
+		this.timeConverter = timeConverter;
 		this.timeConfig = timeConfig;
 	}
 
@@ -24,9 +26,22 @@ public class AsyncPlayerTimeTask extends BukkitRunnable {
 		for (Player player : server.getOnlinePlayers()) {
 			if (!player.hasPermission("realweather.dynamic")) continue;
 			if (!mapper.getWorlds().contains(player.getWorld())) continue;
-			
+
 			Coordinates coordinates = mapper.locationToCoordinates(player.getLocation());
-			long offsetTicks = timeConfig.calculateTimeOffsetTicks(coordinates.longitude);
+
+			long time = System.currentTimeMillis();
+			time = timeConverter.scale(time);
+
+			if (timeConfig.twilight()) {
+				time = timeConverter.offsetTwilight(time, mapper.getPoint());
+			}
+
+			time = timeConverter.calculateZoneOffset(coordinates.longitude);
+			long ticks = timeConverter.millisToTicks(time);
+
+
+			long offsetTicks = timeConverter.calculateZoneOffset(coordinates.longitude);
+			offsetTicks = timeConverter.millisToTicks(offsetTicks);
 			
 			player.setPlayerTime(Math.floorMod(offsetTicks, 24000), true);
 			DebugLogger.info("Time for %s: %d", 2, player.getName(), offsetTicks);
diff --git a/src/main/java/eu/m724/realweather/time/SyncTimeUpdateTask.java b/src/main/java/eu/m724/realweather/time/SyncTimeUpdateTask.java
index da46348..f9f7cc0 100644
--- a/src/main/java/eu/m724/realweather/time/SyncTimeUpdateTask.java
+++ b/src/main/java/eu/m724/realweather/time/SyncTimeUpdateTask.java
@@ -1,5 +1,6 @@
 package eu.m724.realweather.time;
 
+import eu.m724.wtapi.object.Coordinates;
 import org.bukkit.scheduler.BukkitRunnable;
 
 import eu.m724.realweather.DebugLogger;
@@ -7,17 +8,26 @@ import eu.m724.realweather.GlobalConstants;
 import eu.m724.realweather.mapper.Mapper;
 
 public class SyncTimeUpdateTask extends BukkitRunnable {
-	private TimeConfig timeConfig;
-	private Mapper mapper = GlobalConstants.getMapper();
-	
-	SyncTimeUpdateTask(TimeConfig timeConfig) {
+	private final Mapper mapper = GlobalConstants.getMapper();
+
+	private final TimeConverter timeConverter;
+	private final TimeConfig timeConfig;
+
+	SyncTimeUpdateTask(TimeConverter timeConverter, TimeConfig timeConfig) {
+		this.timeConverter = timeConverter;
 		this.timeConfig = timeConfig;
 	}
 
 	@Override
 	public void run() {
-		// TODO double?
-		long ticks = timeConfig.calculateWorldTimeTicks();
+		long time = System.currentTimeMillis();
+		time = timeConverter.scale(time);
+		if (timeConfig.twilight()) {
+			time = timeConverter.offsetTwilight(time, mapper.getPoint());
+		}
+
+		long ticks = timeConverter.millisToTicks(time);
+
 		DebugLogger.info("Updating time: %d", 2, ticks);
 		
 		mapper.getWorlds().forEach(world -> world.setFullTime(ticks));
diff --git a/src/main/java/eu/m724/realweather/time/TimeConfig.java b/src/main/java/eu/m724/realweather/time/TimeConfig.java
index af8eaaa..49c1e64 100644
--- a/src/main/java/eu/m724/realweather/time/TimeConfig.java
+++ b/src/main/java/eu/m724/realweather/time/TimeConfig.java
@@ -2,54 +2,23 @@ package eu.m724.realweather.time;
 
 import org.bukkit.configuration.ConfigurationSection;
 
-public class TimeConfig {
-	public boolean enabled;
-	
-	// state is per player
-	public boolean dynamic;
-	public boolean twilight;
-	// x day cycles in 1 irl day
-	public double scale;
-	
+import java.util.UUID;
+
+public record TimeConfig(
+		boolean enabled,
+
+		boolean dynamic,
+		boolean twilight,
+
+		double scale
+) {
 	public static TimeConfig fromConfiguration(ConfigurationSection configuration) {
-		TimeConfig timeConfig = new TimeConfig();
+		boolean enabled = configuration.getBoolean("enabled");
+
+		boolean dynamic = configuration.getBoolean("dynamic");
+		boolean twilight = configuration.getBoolean("twilight");
+		double scale = configuration.getDouble("scale");
 		
-		timeConfig.enabled = configuration.getBoolean("enabled");
-		
-		timeConfig.dynamic = configuration.getBoolean("dynamic");
-		timeConfig.twilight = configuration.getBoolean("twilight");
-		timeConfig.scale = configuration.getDouble("scale");
-		
-		return timeConfig;
-	}
-	
-	public long calculateWorldTimeSeconds() {
-		long now = (long) (System.currentTimeMillis() * scale / 1000) % 86400;
-		
-		return now;
-	}
-	
-	public long calculateWorldTimeTicks() {
-		long now = calculateWorldTimeSeconds();
-		long ticks = Math.floorMod(now / 72 * 20 - 6000, 24000);
-		
-		return ticks;
-	}
-	
-	/**
-	 * this method is mostly for info
-	 * @param longitude
-	 * @return 
-	 */
-	public long calculateTimeOffsetSeconds(double longitude) {
-		long offset = (long) ((longitude / 15) * 3600 * scale);
-		
-		return offset;
-	}
-	
-	public long calculateTimeOffsetTicks(double longitude) {
-		long offsetTicks = (long) ((longitude / 15) * 1000 * scale);
-		
-		return offsetTicks;
+		return new TimeConfig(enabled, dynamic, twilight, scale);
 	}
 }
diff --git a/src/main/java/eu/m724/realweather/time/TimeConverter.java b/src/main/java/eu/m724/realweather/time/TimeConverter.java
new file mode 100644
index 0000000..17d6c5f
--- /dev/null
+++ b/src/main/java/eu/m724/realweather/time/TimeConverter.java
@@ -0,0 +1,148 @@
+package eu.m724.realweather.time;
+
+import eu.m724.wtapi.object.Coordinates;
+import eu.m724.wtapi.object.Twilight;
+import eu.m724.wtapi.object.TwilightCalendar;
+import eu.m724.wtapi.provider.twilight.TwilightTimeCache;
+import eu.m724.wtapi.provider.twilight.TwilightTimeProvider;
+import eu.m724.wtapi.provider.twilight.impl.approximate.ApproximateTwilightTimeCache;
+import eu.m724.wtapi.provider.twilight.impl.approximate.ApproximateTwilightTimeProvider;
+import org.checkerframework.checker.units.qual.A;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.ZoneOffset;
+import java.util.HashMap;
+import java.util.Map;
+
+public class TimeConverter {
+    public final double scale;
+    // TODO make this configurable
+    private final ApproximateTwilightTimeProvider provider = new ApproximateTwilightTimeProvider();
+
+    public TimeConverter(double scale) {
+        this.scale = scale;
+    }
+
+    /**
+     * Divides time by predefined scale
+     * ...slowing it down
+     *
+     * @param time unix milliseconds
+     * @return scaled unix milliseconds
+     */
+    public long scale(long time) {
+        return (long) (time / scale);
+    }
+
+    /**
+     * Converts unix timestamp to in game ticks
+     *
+     * @param time unix millis
+     * @return in-game ticks
+     */
+    public long millisToTicks(long time) {
+        return Math.floorMod(time / 3600 - 6000, 24000);
+    }
+
+    /**
+     * Calculates how often (or how rarely) we can update time
+     * That is, to not do stuff when we don't have to
+     * In ticks, because it's used with Bukkit scheduler
+     *
+     * @return update period IN TICKS
+     */
+    public long calculateUpdatePeriod() {
+        return (long) (72 / scale);
+    }
+
+    /**
+     * Calculates time offset (in millis) for specified longitude
+     *
+     * @param longitude the longitude
+     * @return milliseconds of offset from the center of the world (0)
+     */
+    public long calculateZoneOffset(double longitude) {
+        return (long) (longitude * 3600000);
+    }
+
+    // TODO optimize
+    public long offsetTwilight(long unixTimestampMs, Coordinates coordinates) {
+        // Constants
+        final long MILLIS_PER_DAY = 86400000;
+        final long SCALED_SUNRISE_MS = 6 * 3600000;  // 06:00 in milliseconds
+        final long SCALED_SUNSET_MS = 18 * 3600000;  // 18:00 in milliseconds
+        final long SCALED_DAY_LENGTH_MS = SCALED_SUNSET_MS - SCALED_SUNRISE_MS;
+
+        // Convert Unix timestamp to Instant and LocalDate
+        Instant instant = Instant.ofEpochMilli(unixTimestampMs);
+        LocalDate date = LocalDate.ofInstant(instant, ZoneOffset.UTC);
+
+        // Get twilight information for the date
+        TwilightCalendar calendar = provider.getCalendar(date);
+        Twilight twilight = calendar.twilight(coordinates);
+
+        System.out.println("Twilight converted date: " + twilight.date());
+        System.out.println("Sunrise: " + twilight.sunrise());
+        System.out.println("Sunset: " + twilight.sunset());
+
+        if (twilight.isPolarDay()) {
+            long daytime = unixTimestampMs % 86400000;
+            if (daytime < 21600000) {
+                unixTimestampMs += 43200000;
+            } else if (daytime > 64800000) {
+                unixTimestampMs += 43200000;
+            }
+            System.out.println("Polar day");
+
+            return unixTimestampMs;
+        } else if (twilight.isPolarNight()) {
+            long daytime = unixTimestampMs % 86400000;
+
+            if (daytime > 21600000 && daytime < 64800000) {
+                unixTimestampMs += 43200000;
+            }
+
+            System.out.println("Polar night");
+
+            return unixTimestampMs;
+        }
+
+        long sunriseMs = twilight.sunrise().toInstant(ZoneOffset.UTC).toEpochMilli();
+        long sunsetMs = twilight.sunset().toInstant(ZoneOffset.UTC).toEpochMilli();
+
+        // Handle edge cases
+        if (unixTimestampMs == sunriseMs) return SCALED_SUNRISE_MS;
+        if (unixTimestampMs == sunsetMs) return SCALED_SUNSET_MS;
+
+        long startTimeMs, endTimeMs, scaledStartMs;
+
+        if (unixTimestampMs >= sunriseMs && unixTimestampMs < sunsetMs) {
+            // Daytime
+            startTimeMs = sunriseMs;
+            endTimeMs = sunsetMs;
+            scaledStartMs = SCALED_SUNRISE_MS;
+        } else {
+            // Nighttime
+            if (unixTimestampMs >= sunsetMs) {
+                // After sunset
+                startTimeMs = sunsetMs;
+                endTimeMs = calendar.tomorrow().twilight(coordinates).sunrise().toInstant(ZoneOffset.UTC).toEpochMilli();
+            } else {
+                // Before sunrise
+                Twilight yesterdayTwilight = calendar.yesterday().twilight(coordinates);
+                startTimeMs = yesterdayTwilight.sunset().toInstant(ZoneOffset.UTC).toEpochMilli();
+                endTimeMs = sunriseMs;
+            }
+            scaledStartMs = SCALED_SUNSET_MS;
+        }
+
+        double durationMs = endTimeMs - startTimeMs;
+        double progress = (unixTimestampMs - startTimeMs) / durationMs;
+        long scaledOffsetMs = scaledStartMs + (long) (progress * SCALED_DAY_LENGTH_MS);
+
+        // Ensure the result is within the same day (0 to MILLIS_PER_DAY - 1)
+        return scaledOffsetMs % MILLIS_PER_DAY;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/m724/realweather/time/TimeMaster.java b/src/main/java/eu/m724/realweather/time/TimeMaster.java
index 44bcd08..edb5107 100644
--- a/src/main/java/eu/m724/realweather/time/TimeMaster.java
+++ b/src/main/java/eu/m724/realweather/time/TimeMaster.java
@@ -7,31 +7,35 @@ import eu.m724.realweather.DebugLogger;
 import eu.m724.realweather.GlobalConstants;
 import eu.m724.realweather.mapper.Mapper;
 
+import java.time.LocalTime;
+
 public class TimeMaster {
-	private TimeConfig timeConfig;
-	private Mapper mapper = GlobalConstants.getMapper();
-	private Plugin plugin = GlobalConstants.getPlugin();
+	private final Mapper mapper = GlobalConstants.getMapper();
+	private final Plugin plugin = GlobalConstants.getPlugin();
+
+	private final TimeConfig timeConfig;
+	private final TimeConverter timeConverter;
 	
 	public TimeMaster(TimeConfig timeConfig) {
 		this.timeConfig = timeConfig;
+		this.timeConverter = new TimeConverter(timeConfig.scale());
 	}
 	
-	
 	public void init() {
-		if (!timeConfig.enabled)
+		if (!timeConfig.enabled())
 			return;
 		
-		long period = (long) (72 / timeConfig.scale);
+		long period = timeConverter.calculateUpdatePeriod();
 		
-		if (timeConfig.scale * Math.floor(period) != 72.0) {
+		if (timeConfig.scale() * period != 72.0) {
 			// TODO log this properly
 			DebugLogger.info("Warning: RealTime scale is not optimal. Time will be out of sync.", 0);
 		}
 		
-		new SyncTimeUpdateTask(timeConfig).runTaskTimer(plugin, 0, period);
+		new SyncTimeUpdateTask(timeConverter, timeConfig).runTaskTimer(plugin, 0, period);
 		
-		if (timeConfig.dynamic)
-			new AsyncPlayerTimeTask(timeConfig).runTaskTimerAsynchronously(plugin, 0, period);
+		if (timeConfig.dynamic())
+			new AsyncPlayerTimeTask(timeConverter, timeConfig).runTaskTimerAsynchronously(plugin, 0, period);
 			// TODO maybe use a different period?
 			// TODO also make it on player join
 		
diff --git a/src/main/resources/modules/time.yml b/src/main/resources/modules/time.yml
index 8840092..54ebb61 100644
--- a/src/main/resources/modules/time.yml
+++ b/src/main/resources/modules/time.yml
@@ -22,5 +22,4 @@ twilight: true
 
 # x in game day cycles in 1 irl day cycle
 # Time will no longer be in sync
-# Can be decimal
 scale: 1.0
\ No newline at end of file
diff --git a/src/test/java/TimeConverterTest.java b/src/test/java/TimeConverterTest.java
new file mode 100644
index 0000000..698fd53
--- /dev/null
+++ b/src/test/java/TimeConverterTest.java
@@ -0,0 +1,38 @@
+import eu.m724.realweather.time.TimeConverter;
+import eu.m724.wtapi.object.Coordinates;
+import eu.m724.wtapi.object.Twilight;
+import org.junit.Test;
+
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+
+public class TimeConverterTest {
+    @Test
+    public void testTimeConverter() {
+        TimeConverter timeConverter = new TimeConverter(2.0);
+        System.out.println("Scale: " + timeConverter.scale);
+        long time = LocalDateTime.now().withHour(12).toInstant(ZoneOffset.UTC).toEpochMilli();
+        System.out.println("Time: " + LocalDateTime.ofInstant(Instant.ofEpochMilli(time), ZoneOffset.UTC));
+        time = timeConverter.scale(time);
+        System.out.println("Time scaled: " + LocalDateTime.ofInstant(Instant.ofEpochMilli(time), ZoneOffset.UTC));
+        System.out.println("Those times are UTC so add 7 hours");
+        time = timeConverter.offsetTwilight(time, new Coordinates(42, 69)); // TODO change numbers before committing
+
+        time = timeConverter.millisToTicks(time);
+        System.out.println("Day ticks: " + time);
+
+        System.out.println();
+
+        System.out.println("Scale: " + timeConverter.scale);
+        time = LocalDateTime.now().withHour(12).toInstant(ZoneOffset.UTC).toEpochMilli();
+        System.out.println("Time: " + LocalDateTime.ofInstant(Instant.ofEpochMilli(time), ZoneOffset.UTC));
+        time = timeConverter.scale(time);
+        System.out.println("Time scaled: " + LocalDateTime.ofInstant(Instant.ofEpochMilli(time), ZoneOffset.UTC));
+        System.out.println("Those times are UTC so add 7 hours");
+        time = timeConverter.offsetTwilight(time, new Coordinates(70, 0)); // TODO change numbers before committing
+
+        time = timeConverter.millisToTicks(time);
+        System.out.println("Day ticks: " + time);
+    }
+}