瀏覽代碼

draft: zonedatetime

Ranides Atterwim 1 年之前
父節點
當前提交
4104be43db
共有 18 個文件被更改,包括 995 次插入565 次删除
  1. 41 21
      assira.core/src/main/java/net/ranides/assira/time2/DateTimeFormatters.java
  2. 2 2
      assira.core/src/main/java/net/ranides/assira/time2/DateTimeLiteral.java
  3. 104 27
      assira.core/src/main/java/net/ranides/assira/time2/DateTimeParsers.java
  4. 157 44
      assira.core/src/main/java/net/ranides/assira/time2/DateTimeUtils.java
  5. 77 0
      assira.core/src/main/java/net/ranides/assira/time2/HolidaySchedule.java
  6. 0 16
      assira.core/src/main/java/net/ranides/assira/time2/ZoneAware.java
  7. 19 0
      assira.core/src/main/java/net/ranides/assira/time2/ZoneExplicit.java
  8. 10 0
      assira.core/src/main/java/net/ranides/assira/time2/ZoneHazard.java
  9. 249 15
      assira.core/src/main/java/net/ranides/assira/time2/ZonedDate.java
  10. 281 8
      assira.core/src/main/java/net/ranides/assira/time2/ZonedTime.java
  11. 13 0
      assira.core/src/test/java/net/ranides/assira/time2/DateTimeFormattersTest.java
  12. 7 1
      assira.core/src/test/java/net/ranides/assira/time2/DateTimeParsersTest.java
  13. 35 0
      assira.core/src/test/java/net/ranides/assira/time2/DateTimeUtilsTest.java
  14. 0 104
      assira.drafts/src/main/java/net/ranides/assira/datetime/DateTimeUtils1.java
  15. 0 139
      assira.drafts/src/main/java/net/ranides/assira/datetime/ZonedDate1.java
  16. 0 41
      assira.drafts/src/main/java/net/ranides/assira/datetime/ZonedDateRange1.java
  17. 0 80
      assira.drafts/src/main/java/net/ranides/assira/datetime/ZonedDateTimeRange1.java
  18. 0 67
      assira.drafts/src/main/java/net/ranides/assira/datetime/ZonedTime1.java

+ 41 - 21
assira.core/src/main/java/net/ranides/assira/time2/DateTimeFormatters.java

@@ -1,6 +1,9 @@
 package net.ranides.assira.time2;
 
 import lombok.experimental.UtilityClass;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
@@ -22,70 +25,87 @@ public class DateTimeFormatters {
     public static final DateTimeFormatter DATENAME         = DateTimeFormatter.ofPattern("dd.MM EE");
     public static final DateTimeFormatter DAY_NAME         = DateTimeFormatter.ofPattern("EEEE");
 
-    public static String formatUtc(ZonedDateTime input) {
+    @ZoneExplicit
+    @Contract("!null -> !null; null -> null")
+    public static @Nullable String formatUtc(@Nullable ZonedDateTime input) {
         return format(ISO, DateTimeUtils.withZoneUtc(input));
     }
 
-    public static String formatIso(ZonedDateTime input) {
+    @Contract("!null -> !null; null -> null")
+    public static @Nullable String formatIso(@Nullable ZonedDateTime input) {
         return format(ISO, input);
     }
 
-    public static String formatCompressed(ZonedDateTime input) {
+    @Contract("!null -> !null; null -> null")
+    public static @Nullable String formatCompressed(@Nullable ZonedDateTime input) {
         return format(COMPRESSED, input);
     }
 
-    public static String formatDate(ZonedDateTime input) {
+    @Contract("!null -> !null; null -> null")
+    public static @Nullable String formatDate(@Nullable ZonedDateTime input) {
         return format(DATE, input);
     }
 
-    public static String formatDateTime(ZonedDateTime input) {
+    @Contract("!null -> !null; null -> null")
+    public static @Nullable String formatDateTime(@Nullable ZonedDateTime input) {
         return format(DATETIME, input);
     }
 
-    public static String formatDateTimePrecise(ZonedDateTime input) {
+    @Contract("!null -> !null; null -> null")
+    public static @Nullable String formatDateTimePrecise(@Nullable ZonedDateTime input) {
         return format(DATETIME_PRECISE, input);
     }
 
-    public static String formatTime(ZonedDateTime input) {
+    @Contract("!null -> !null; null -> null")
+    public static @Nullable String formatTime(@Nullable ZonedDateTime input) {
         return format(TIME, input);
     }
 
-    public static String formatTimePrecise(ZonedDateTime input) {
+    @Contract("!null -> !null; null -> null")
+    public static @Nullable String formatTimePrecise(@Nullable ZonedDateTime input) {
         return format(TIME_PRECISE, input);
     }
 
-    public static String formatYear(ZonedDateTime input) {
+    @Contract("!null -> !null; null -> null")
+    public static @Nullable String formatYear(@Nullable ZonedDateTime input) {
         return format(YEAR, input);
     }
 
-    public static String formatDateShort(ZonedDateTime input) {
+    @Contract("!null -> !null; null -> null")
+    public static @Nullable String formatDateShort(@Nullable ZonedDateTime input) {
         return format(DATENAME, input);
     }
 
-    @ZoneAware
-    public static String formatPolishDateShort(ZonedDateTime input) {
-        return format(DATENAME, input, LOCALE_PL);
+    @ZoneExplicit
+    @Contract("!null -> !null; null -> null")
+    public static @Nullable String formatPolishDateShort(@Nullable ZonedDateTime input) {
+        return format(DATENAME, LOCALE_PL, input);
     }
 
-    public static String getDayName(ZonedDateTime input) {
+    @Contract("!null -> !null; null -> null")
+    public static @Nullable String getDayName(@Nullable ZonedDateTime input) {
         return format(DAY_NAME, input);
     }
 
-    @ZoneAware
-    public static String getPolishDayName(ZonedDateTime input) {
-        return format(DAY_NAME, input, LOCALE_PL);
+    @ZoneExplicit
+    @Contract("!null -> !null; null -> null")
+    public static @Nullable String getPolishDayName(@Nullable ZonedDateTime input) {
+        return format(DAY_NAME, LOCALE_PL, input);
     }
 
-    @ZoneAware
-    public static String getPolishTime(ZonedDateTime input) {
+    @ZoneExplicit
+    @Contract("!null -> !null; null -> null")
+    public static @Nullable String getPolishTime(@Nullable ZonedDateTime input) {
         return format(TIME, DateTimeUtils.withZonePolish(input));
     }
 
-    public static String format(DateTimeFormatter format, ZonedDateTime input) {
+    @Contract("_,!null -> !null; _,null -> null")
+    public static @Nullable String format(@NotNull DateTimeFormatter format, @Nullable ZonedDateTime input) {
         return input == null ? null : format.format(input);
     }
 
-    public static String format(DateTimeFormatter format, ZonedDateTime input, Locale locale) {
+    @Contract("_,_,!null -> !null; _,_,null -> null")
+    public static @Nullable String format(@NotNull DateTimeFormatter format, @NotNull Locale locale, @Nullable ZonedDateTime input) {
         return input == null ? null : format.withLocale(locale).format(input);
     }
 

+ 2 - 2
assira.core/src/main/java/net/ranides/assira/time2/DateTimeLiteral.java

@@ -8,11 +8,11 @@ import java.time.ZonedDateTime;
 public class DateTimeLiteral {
 
     public static ZonedTime time(int h, int m, int s) {
-        throw new UnsupportedOperationException("");
+        return ZonedTime.of(datetime(1900, 1, 1, 0, 0));
     }
 
     public static ZonedDate date(int y, int m, int d) {
-        throw new UnsupportedOperationException("");
+        return ZonedDate.of(datetime(y, m, d, 0, 0));
     }
 
     public static ZonedDateTime datetime(int y, int m, int d, int H, int M) {

+ 104 - 27
assira.core/src/main/java/net/ranides/assira/time2/DateTimeParsers.java

@@ -1,9 +1,13 @@
 package net.ranides.assira.time2;
 
 import lombok.experimental.UtilityClass;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
-import java.time.Duration;
+import java.time.LocalDate;
 import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
@@ -32,52 +36,125 @@ public class DateTimeParsers {
         .optionalEnd()
         .toFormatter();
 
-    public static ZonedDateTime parseDateTime(String input) {
-        return parse(ISO_ZONED, input);
+
+    @Contract("!null -> !null; null -> null")
+    public static @Nullable ZonedDateTime parseDateTime(@Nullable String input) {
+        return parseDateTime(ISO_ZONED, input);
     }
 
-    public static ZonedDate parseDate(String input) {
-        return DateTimeUtils.asDate(parseDateTime(input));
+    @Contract("_,!null -> !null; _,null -> null")
+    public static @Nullable ZonedDateTime parseDateTime(@NotNull DateTimeFormatter format, @Nullable String input) {
+        return Objects.isNull(input) ? null : ZonedDateTime.parse(input, format);
     }
 
-    public static ZonedTime parseTime(String input) {
-        return DateTimeUtils.asTime(parseDateTime(input));
+    @ZoneExplicit
+    @Contract("!null -> !null; null -> null")
+    public static @Nullable ZonedDateTime parseUtcDateTime(@Nullable String input) {
+        return parseDateTime(DateTimeUtils.ZONE_UTC, ISO_OPTIONAL, input);
     }
 
-    @ZoneAware
-    public static ZonedDateTime parsePolishDateTime(String input) {
-        return parse(ISO_OPTIONAL, DateTimeUtils.ZONE_PL, input);
+    @ZoneExplicit
+    @Contract("!null -> !null; null -> null")
+    public static @Nullable ZonedDateTime parsePolishDateTime(@Nullable String input) {
+        return parseDateTime(DateTimeUtils.ZONE_PL, ISO_OPTIONAL, input);
     }
 
-    @ZoneAware
-    public static ZonedDate parsePolishDate(String input) {
-        return DateTimeUtils.asDate(parsePolishDateTime(input));
+    @ZoneExplicit
+    @Contract("_,!null -> !null; _,null -> null")
+    public static @Nullable ZonedDateTime parseUtcDateTime(@NotNull DateTimeFormatter format, @Nullable String input) {
+        return parseDateTime(DateTimeUtils.ZONE_UTC, format, input);
     }
 
-    @ZoneAware
-    public static ZonedTime parsePolishTime(String input) {
-        return DateTimeUtils.asTime(parsePolishDateTime(input));
+    @ZoneExplicit
+    @Contract("_,!null -> !null; _,null -> null")
+    public static @Nullable ZonedDateTime parsePolishDateTime(@NotNull DateTimeFormatter format, @Nullable String input) {
+        return parseDateTime(DateTimeUtils.ZONE_PL, format, input);
     }
 
-    public static ZonedDateTime parse(DateTimeFormatter format, String input) {
-        return Objects.isNull(input) ? null : ZonedDateTime.parse(input, format);
+    @ZoneExplicit
+    @Contract("_,_,!null -> !null; _,_,null -> null")
+    public static @Nullable ZonedDateTime parseDateTime(@NotNull ZoneId zone, @NotNull DateTimeFormatter format, @Nullable String input) {
+        if(Objects.isNull(input)) {
+            return null;
+        }
+        TemporalAccessor output = format.parseBest(input, ZonedDateTime::from, LocalDateTime::from);
+        if(output instanceof ZonedDateTime) {
+            return DateTimeUtils.withZone((ZonedDateTime)output, zone);
+        }
+        if(output instanceof LocalDateTime) {
+            return ((LocalDateTime)output).atZone(zone);
+        }
+        throw new IllegalArgumentException();
+    }
+
+    @ZoneExplicit
+    @Contract("!null -> !null; null -> null")
+    public static @Nullable ZonedDate parseUtcDate(@Nullable String input) {
+        return parseDate(DateTimeUtils.ZONE_UTC, input);
+    }
+
+    @ZoneExplicit
+    @Contract("!null -> !null; null -> null")
+    public static @Nullable ZonedDate parsePolishDate(@Nullable String input) {
+        return parseDate(DateTimeUtils.ZONE_PL, input);
+    }
+
+    @ZoneExplicit
+    @Contract("_,!null -> !null; _,null -> null")
+    public static @Nullable ZonedDate parseDate(@NotNull ZoneId zone, @Nullable String input) {
+        if(Objects.isNull(input)) {
+            return null;
+        }
+        return ZonedDate.of(zone, LocalDate.parse(input, DateTimeFormatters.DATE));
+    }
+
+    @ZoneExplicit
+    @Contract("!null -> !null; null -> null")
+    public static @Nullable ZonedTime parseUtcTime(@Nullable String input) {
+        return parseTime(DateTimeUtils.ZONE_UTC, input);
     }
 
-    @ZoneAware
-    public static ZonedDateTime parse(DateTimeFormatter format, ZoneId zone, String input) {
-        ZonedDateTime x = DateTimeUtils.withZoneUtc(null);
+    @ZoneExplicit
+    @Contract("!null -> !null; null -> null")
+    public static @Nullable ZonedTime parsePolishTime(@Nullable String input) {
+        return parseTime(DateTimeUtils.ZONE_PL, input);
+    }
 
+    @ZoneExplicit
+    @Contract("_,!null -> !null; _,null -> null")
+    public static @Nullable ZonedTime parseTime(@NotNull ZoneId zone, @Nullable String input) {
         if(Objects.isNull(input)) {
             return null;
         }
-        TemporalAccessor out = format.parseBest(input, ZonedDateTime::from, LocalDateTime::from);
-        if(out instanceof ZonedDateTime) {
-            return DateTimeUtils.withZone((ZonedDateTime)out, zone);
+        return ZonedTime.of(zone, LocalTime.parse(input, DateTimeFormatters.TIME));
+    }
+
+    @ZoneExplicit
+    @Contract("null,null -> null; !null,_ -> !null; null,!null -> fail")
+    public static @Nullable ZonedDateTime parseExplodedPolishDateTime(@Nullable String dateValue, @Nullable String timeValue) {
+        return parseExplodedDateTime(DateTimeUtils.ZONE_PL, dateValue, timeValue);
+    }
+
+    @ZoneExplicit
+    @Contract("null,null -> null; !null,_ -> !null; null,!null -> fail")
+    public static @Nullable ZonedDateTime parseExplodedUtcDateTime(@Nullable String dateValue, @Nullable String timeValue) {
+        return parseExplodedDateTime(DateTimeUtils.ZONE_UTC, dateValue, timeValue);
+    }
+
+    @ZoneExplicit
+    @Contract("_,null,null -> null; _,!null,_ -> !null; _,null,!null -> fail")
+    public static @Nullable ZonedDateTime parseExplodedDateTime(@NotNull ZoneId zone, @Nullable String dateValue, @Nullable String timeValue) {
+        if(Objects.isNull(dateValue) && Objects.isNull(timeValue)) {
+            return null;
         }
-        if(out instanceof LocalDateTime) {
-            return ((LocalDateTime)out).atZone(zone);
+        if(Objects.isNull(dateValue)) {
+            throw new IllegalArgumentException();
+        }
+        if(Objects.isNull(timeValue)) {
+            return DateTimeParsers.parseDate(zone, dateValue).withZero();
+        } else {
+            return DateTimeParsers.parseDate(zone, dateValue).withTime(DateTimeParsers.parseTime(zone, timeValue));
         }
-        throw new IllegalArgumentException();
     }
 
 }

+ 157 - 44
assira.core/src/main/java/net/ranides/assira/time2/DateTimeUtils.java

@@ -1,11 +1,17 @@
 package net.ranides.assira.time2;
 
 import lombok.experimental.UtilityClass;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
+import java.time.DayOfWeek;
 import java.time.Duration;
+import java.time.LocalTime;
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
 import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalAdjusters;
 import java.util.Locale;
 import java.util.Objects;
 
@@ -15,48 +21,66 @@ public class DateTimeUtils {
     public static final ZoneId ZONE_PL = ZoneId.of("Poland");
     public static final ZoneId ZONE_UTC = ZoneId.of("Etc/GMT0");
 
-    public static final ZonedDateTime MIN = ZonedDateTime.of(1900, 1, 1, 0, 0, 0, 0, ZONE_UTC);
-
-    public static final ZonedDateTime MAX = ZonedDateTime.of(4000, 1, 1, 0, 0, 0, 0, ZONE_UTC);
-
     public static Locale locale() {
         return Locale.getDefault();
     }
 
-    @ZoneAware
-    public static ZonedDateTime withZoneUtc(ZonedDateTime input) {
+    @ZoneExplicit
+    @Contract("!null -> !null; null -> null")
+    public static @Nullable ZonedDateTime withZoneUtc(@Nullable ZonedDateTime input) {
         return withZone(input, ZONE_UTC);
     }
 
-    @ZoneAware
-    public static ZonedDateTime withZonePolish(ZonedDateTime input) {
+    @ZoneExplicit
+    @Contract("!null -> !null; null -> null")
+    public static @Nullable ZonedDateTime withZonePolish(@Nullable ZonedDateTime input) {
         return withZone(input, ZONE_PL);
     }
 
-    @ZoneAware
-    public static ZonedDateTime withZone(ZonedDateTime input, ZoneId zone) {
+    @ZoneExplicit
+    @Contract("!null,_ -> !null; null,_ -> null")
+    public static @Nullable ZonedDateTime withZone(@Nullable ZonedDateTime input, @NotNull ZoneId zone) {
         return Objects.isNull(input) ? null : input.withZoneSameInstant(zone);
     }
 
-    public static ZonedTime asTime(ZonedDateTime input) {
+    @ZoneExplicit
+    @ZoneHazard
+    @Contract("!null,_ -> !null; null,_ -> null")
+    public static @Nullable ZonedDate withZone(@Nullable ZonedDate input, @NotNull ZoneId zone) {
+        return Objects.isNull(input) ? null : input.withZone(zone);
+    }
+
+    @ZoneExplicit
+    @ZoneHazard
+    @Contract("!null,_ -> !null; null,_ -> null")
+    public static @Nullable ZonedTime withZone(@Nullable ZonedTime input, @NotNull ZoneId zone) {
+        return Objects.isNull(input) ? null : input.withZone(zone);
+    }
+
+    @Contract("!null -> !null; null -> null")
+    public static @Nullable ZonedTime asTime(@Nullable ZonedDateTime input) {
         return Objects.isNull(input) ? null : ZonedTime.of(input);
     }
 
-    @ZoneAware
-    public static ZonedTime asPolishTime(ZonedDateTime input) {
+    @ZoneExplicit
+    @Contract("!null -> !null; null -> null")
+    public static @Nullable ZonedTime asPolishTime(@Nullable ZonedDateTime input) {
         return asTime(withZonePolish(input));
     }
 
-    public static ZonedDate asDate(ZonedDateTime input) {
+    @Contract("!null -> !null; null -> null")
+    public static @Nullable ZonedDate asDate(@Nullable ZonedDateTime input) {
         return Objects.isNull(input) ? null : ZonedDate.of(input);
     }
 
-    @ZoneAware
-    public static ZonedDate asPolishDate(ZonedDateTime input) {
+    @ZoneExplicit
+    @Contract("!null -> !null; null -> null")
+    public static @Nullable ZonedDate asPolishDate(@Nullable ZonedDateTime input) {
         return asDate(withZonePolish(input));
     }
 
-    public static ZonedDateTime min(ZonedDateTime date1, ZonedDateTime date2) {
+    @Contract("!null,_ -> !null; _,!null -> !null; null,null -> null")
+    public static @Nullable ZonedDateTime min(@Nullable ZonedDateTime date1, @Nullable ZonedDateTime date2) {
         if(Objects.isNull(date1)) {
             return date2;
         }
@@ -66,7 +90,8 @@ public class DateTimeUtils {
         return date1.isBefore(date2) ? date2 : date1;
     }
 
-    public static ZonedDateTime max(ZonedDateTime date1, ZonedDateTime date2) {
+    @Contract("!null,_ -> !null; _,!null -> !null; null,null -> null")
+    public static @Nullable ZonedDateTime max(@Nullable ZonedDateTime date1, @Nullable ZonedDateTime date2) {
         if(Objects.isNull(date1)) {
             return date2;
         }
@@ -76,27 +101,32 @@ public class DateTimeUtils {
         return date1.isAfter(date2) ? date1 : date2;
     }
 
-    public static ZonedDateTime valueOrMin(ZonedDateTime input) {
-        return Objects.isNull(input) ? MIN : input;
-    }
-
-    public static ZonedDateTime valueOrMax(ZonedDateTime input) {
-        return Objects.isNull(input) ? MAX : input;
-    }
-
-    public static boolean isSameDate(ZonedDateTime dateTime1, ZonedDateTime dateTime2) {
+    @Contract("!null,null -> false; null,!null -> false; null,null -> false")
+    public static boolean isSameDate(@Nullable ZonedDateTime dateTime1, @Nullable ZonedDateTime dateTime2) {
+        if(Objects.isNull(dateTime1) != Objects.isNull(dateTime2)) {
+            return false;
+        }
+        if(Objects.isNull(dateTime1)) {
+            return false;
+        }
         return dateTime1.truncatedTo(ChronoUnit.DAYS).isEqual(dateTime2.truncatedTo(ChronoUnit.DAYS));
     }
 
-    public static boolean isSameDate(ZonedDate dateTime1, ZonedDate dateTime2) {
-        // @todo implement
-        throw new UnsupportedOperationException("");
+    @ZoneHazard
+    @Contract("!null,null -> false; null,!null -> false; null,null -> false")
+    public static boolean isSameDate(@Nullable ZonedDate date1, @Nullable ZonedDate date2) {
+        if(Objects.isNull(date1) != Objects.isNull(date2)) {
+            return false;
+        }
+        if(Objects.isNull(date1)) {
+            return false;
+        }
+        return date1.isEqual(date2);
     }
 
-    public static Duration diff(ZonedDateTime begin, ZonedDateTime end) {
-        if(Objects.isNull(begin) || Objects.isNull(end)) {
-            throw new IllegalArgumentException();
-        }
+    public static @NotNull Duration diff(@NotNull ZonedDateTime begin, @NotNull ZonedDateTime end) {
+        Objects.requireNonNull(begin);
+        Objects.requireNonNull(end);
         if(begin.isAfter(end)) {
             return Duration.of(24, ChronoUnit.HOURS).minus(Duration.between(end, begin));
         } else {
@@ -104,17 +134,100 @@ public class DateTimeUtils {
         }
     }
 
-    @ZoneAware
-    public static Duration diff(ZonedTime begin, ZonedTime end) {
-        if(Objects.isNull(begin) || Objects.isNull(end)) {
-            throw new IllegalArgumentException();
+    @ZoneHazard
+    public static @NotNull Duration diff(@NotNull ZonedTime begin, @NotNull ZonedTime end) {
+        Objects.requireNonNull(begin);
+        Objects.requireNonNull(end);
+        if(begin.isAfter(end)) {
+            return Duration.of(24, ChronoUnit.HOURS).minus(end.until(begin));
+        } else {
+            return end.until(begin);
         }
-//        if(begin.isAfter(end)) {
-//            return Duration.of(24, ChronoUnit.HOURS).minus(Duration.between(end, begin));
-//        } else {
-//            return Duration.between(end, begin);
-//        }
-        throw new UnsupportedOperationException();
+    }
+
+    @Contract("_, null,null -> true")
+    public static boolean isBetween(@NotNull ZonedDateTime input, @Nullable ZonedDateTime begin, @Nullable ZonedDateTime end) {
+        Objects.requireNonNull(input);
+        return (Objects.isNull(begin) || !input.isBefore(begin)) && (Objects.isNull(end) || input.isBefore(end));
+    }
+
+    @Contract("_, null,null -> true")
+    public static boolean isBetweenInclusive(@NotNull ZonedDateTime input, @Nullable ZonedDateTime begin, @Nullable ZonedDateTime end) {
+        Objects.requireNonNull(input);
+        return (Objects.isNull(begin) || !input.isBefore(begin)) && (Objects.isNull(end) || !input.isAfter(end));
+    }
+
+    @Contract("_, null,null -> true")
+    public static boolean isBetween(@NotNull ZonedDateTime input, @Nullable ZonedDate begin, @Nullable ZonedDate end) {
+        Objects.requireNonNull(input);
+        return (Objects.isNull(begin) || !begin.isAfter(input)) && (Objects.isNull(end) || end.isAfter(input));
+    }
+
+    @Contract("_, null,null -> true")
+    public static boolean isBetweenInclusive(@NotNull ZonedDateTime input, @Nullable ZonedDate begin, @Nullable ZonedDate end) {
+        Objects.requireNonNull(input);
+        return (Objects.isNull(begin) || !begin.isAfter(input)) && (Objects.isNull(end) || !end.isBefore(input));
+    }
+
+    @ZoneHazard
+    public static ZonedDateTime asDateTime(@NotNull ZonedDate date, @NotNull ZonedTime time) {
+        Objects.requireNonNull(date);
+        Objects.requireNonNull(time);
+        return date.withTime(time);
+    }
+
+    @Contract("null -> false")
+    public static boolean hasMinTime(@Nullable ZonedDateTime datetime) {
+        return Objects.nonNull(datetime) && LocalTime.MIN.equals(datetime.toLocalTime());
+    }
+
+    @Contract("null -> false")
+    public static boolean hasMaxTime(@Nullable ZonedDateTime datetime) {
+        return Objects.nonNull(datetime) && LocalTime.MAX.equals(datetime.toLocalTime());
+    }
+
+    @ZoneExplicit
+    @Contract("null -> false")
+    public static boolean hasPolishMinTime(@Nullable ZonedDateTime datetime) {
+        return Objects.nonNull(datetime) && LocalTime.MIN.equals(withZonePolish(datetime).toLocalTime());
+    }
+
+    @ZoneExplicit
+    @Contract("null -> false")
+    public static boolean hasPolishMaxTime(@Nullable ZonedDateTime datetime) {
+        return Objects.nonNull(datetime) && LocalTime.MAX.equals(withZonePolish(datetime).toLocalTime());
+    }
+
+    @Contract("null -> null; !null -> !null")
+    public static @Nullable ZonedDateTime withLastDayOfMonth(@Nullable ZonedDateTime datetime) {
+        return Objects.isNull(datetime) ? null : datetime.with(TemporalAdjusters.lastDayOfMonth());
+    }
+
+    @Contract("null -> null; !null -> !null")
+    public static @Nullable ZonedDateTime withLastMinuteOfDay(@Nullable ZonedDateTime datetime) {
+        return Objects.isNull(datetime) ? null : datetime.withHour(23).withMinute(59).withSecond(59);
+    }
+
+    @Contract("null -> false")
+    public static boolean isSaturday(@Nullable ZonedDateTime datetime) {
+        return Objects.nonNull(datetime) && datetime.getDayOfWeek() == DayOfWeek.SATURDAY;
+    }
+
+    @Contract("null -> false")
+    public static boolean isSunday(@Nullable ZonedDateTime datetime) {
+        return Objects.nonNull(datetime) && datetime.getDayOfWeek() == DayOfWeek.SATURDAY;
+    }
+
+    @ZoneExplicit
+    @Contract("null -> false")
+    public static boolean isPolishSaturday(@Nullable ZonedDateTime datetime) {
+        return isSaturday(withZonePolish(datetime));
+    }
+
+    @ZoneExplicit
+    @Contract("null -> false")
+    public static boolean isPolishSunday(@Nullable ZonedDateTime datetime) {
+        return isSunday(withZonePolish(datetime));
     }
 
 }

+ 77 - 0
assira.core/src/main/java/net/ranides/assira/time2/HolidaySchedule.java

@@ -0,0 +1,77 @@
+package net.ranides.assira.time2;
+
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public abstract class HolidaySchedule {
+
+    public abstract ZoneId getZone();
+
+    public abstract Set<ZonedDate> getHolidays();
+
+    @ZoneHazard
+    public abstract boolean contains(ZonedDate date);
+
+    public abstract boolean contains(ZonedDateTime datetime);
+
+    public static class EmptyHolidaySchedule extends HolidaySchedule {
+
+        @Override
+        public ZoneId getZone() {
+            return DateTimeUtils.ZONE_UTC;
+        }
+
+        @Override
+        public Set<ZonedDate> getHolidays() {
+            return Collections.emptySet();
+        }
+
+        @Override
+        public boolean contains(ZonedDate date) {
+            return false;
+        }
+
+        @Override
+        public boolean contains(ZonedDateTime datetime) {
+            return false;
+        }
+    }
+
+    public static class SimpleHolidaySchedule extends HolidaySchedule {
+
+        private final ZoneId zone;
+        private final Set<ZonedDate> set;
+
+        public SimpleHolidaySchedule(ZoneId zone, Collection<ZonedDate> set) {
+            this.zone = zone;
+            this.set = set.stream().map(date -> date.withZone(zone)).collect(Collectors.toSet());
+        }
+
+        @Override
+        public ZoneId getZone() {
+            return zone;
+        }
+
+        @Override
+        public Set<ZonedDate> getHolidays() {
+            return set;
+        }
+
+        @ZoneHazard
+        @Override
+        public boolean contains(ZonedDate date) {
+            return set.contains(DateTimeUtils.withZone(date, getZone()));
+        }
+
+        @Override
+        public boolean contains(ZonedDateTime datetime) {
+            return set.contains(ZonedDate.of(DateTimeUtils.withZone(datetime, getZone())));
+        }
+
+    }
+
+}

+ 0 - 16
assira.core/src/main/java/net/ranides/assira/time2/ZoneAware.java

@@ -1,16 +0,0 @@
-package net.ranides.assira.time2;
-
-import java.lang.annotation.Documented;
-
-/**
- * Please be very careful about the code using annotated method because
- * Annotated method explicitly enforces specific timezone. In most computations you don't need to use that information.
- *
- * If you really need to use this method, think twice about edge cases.
- *
- * Annotated methods can throw exception if timezone conversion is impossible due to incomplete information
- */
-@Documented
-public @interface ZoneAware {
-
-}

+ 19 - 0
assira.core/src/main/java/net/ranides/assira/time2/ZoneExplicit.java

@@ -0,0 +1,19 @@
+package net.ranides.assira.time2;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Marked method explicitly enforces specific timezone.
+ *
+ *
+ * Please be very careful about the code using annotated method because
+ * Annotated method . In most computations you don't need to use that information.
+ *
+ */
+@Documented
+@Retention(RetentionPolicy.CLASS)
+public @interface ZoneExplicit {
+
+}

+ 10 - 0
assira.core/src/main/java/net/ranides/assira/time2/ZoneHazard.java

@@ -0,0 +1,10 @@
+package net.ranides.assira.time2;
+
+/**
+ * Annotated methods can throw exception if timezone conversion is impossible due to incomplete information
+ *
+ * If you need to use this method, think about edge cases.
+ */
+public @interface ZoneHazard {
+
+}

+ 249 - 15
assira.core/src/main/java/net/ranides/assira/time2/ZonedDate.java

@@ -3,26 +3,114 @@ package net.ranides.assira.time2;
 import lombok.RequiredArgsConstructor;
 import org.jetbrains.annotations.NotNull;
 
-import java.time.*;
+import java.time.Duration;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoField;
+import java.time.temporal.ChronoUnit;
 import java.util.Objects;
 
 public abstract class ZonedDate implements Comparable<ZonedDate> {
 
-    public static ZonedDate of(ZonedDateTime input) {
-        throw new UnsupportedOperationException("Not implemented yet");
+    public static @NotNull ZonedDate of(@NotNull ZonedDateTime input) {
+        return new Complete(input);
     }
 
-    public abstract boolean isAfter(ZonedDate other);
+    @ZoneExplicit
+    public static @NotNull ZonedDate of(@NotNull ZoneId zone, @NotNull LocalDate date) {
+        return new Incomplete(zone, date);
+    }
+
+    public static @NotNull ZonedDate now() {
+        return new Complete(ZonedDateTime.now());
+    }
+
+    public abstract ZoneId getZone();
+
+    public final boolean isAfter(ZonedDate other) {
+        return withMin().isAfter(other.withMin());
+    }
+
+    public final boolean isAfter(ZonedDateTime other) {
+        return withMin().isAfter(other);
+    }
+
+    public final boolean isBefore(ZonedDate other) {
+        return withMax().isBefore(other.withMax());
+    }
+
+    public final boolean isBefore(ZonedDateTime other) {
+        return withMax().isBefore(other);
+    }
+
+    public final boolean isEqual(ZonedDate other) {
+        return withMin().isEqual(other.withMin());
+    }
+
+    public final boolean isEqualDate(ZonedDateTime other) {
+        return withMin().isEqual(other.with(LocalTime.MIN));
+    }
+
+    public final boolean isSameZone(ZonedDate other) {
+        return Objects.nonNull(other) && isSameZone(other.getZone());
+    }
+
+    public final boolean isSameZone(ZonedDateTime other) {
+        return Objects.nonNull(other) && isSameZone(other.getZone());
+    }
+
+    public final boolean isSameZone(@NotNull ZoneId zone) {
+        return getZone().equals(zone);
+    }
+
+    public abstract boolean isZoneConvertible(ZoneId zone);
+
+    @ZoneHazard
+    public abstract ZonedDate withZone(ZoneId zone);
+
+    @ZoneHazard
+    public abstract ZonedDateTime withTime(ZonedTime time);
 
-    public abstract boolean isAfter(ZonedDateTime other);
+    public ZonedDateTime withTime(ZonedDateTime time) {
+        return withTime(ZonedTime.of(time).withZone(getZone()));
+    }
+
+    public abstract ZonedDateTime withMin();
+
+    public final ZonedDateTime withZero() {
+        return withMin();
+    }
+
+    public abstract ZonedDateTime withMax();
+
+    public final ZonedDateTime withMidnight() {
+        return withZero();
+    }
+
+    public final ZonedDateTime atStartOfDay() {
+        return withZero();
+    }
+
+    public final ZonedDateTime atEndOfDay() {
+        return withMax();
+    }
+
+    public final Duration until(ZonedDateTime end) {
+        return Duration.ofMillis(until(end, ChronoUnit.MILLIS));
+    }
 
-    public abstract boolean isBefore(ZonedDate other);
+    public abstract long until(ZonedDateTime end, ChronoUnit unit);
 
-    public abstract boolean isBefore(ZonedDateTime other);
+    public abstract Duration until(ZonedDate end);
 
-    public abstract boolean isEqual(ZonedDate other);
+    public abstract long until(ZonedDate end, ChronoUnit unit);
 
-    public abstract boolean isEqual(ZonedDateTime other);
+    public abstract ZonedDate plusDays(int days);
+
+    public abstract ZonedDate minusDays(int days);
 
     public abstract int getYear();
 
@@ -30,18 +118,16 @@ public abstract class ZonedDate implements Comparable<ZonedDate> {
 
     public abstract int getDay();
 
+    @ZoneHazard
     @Override
     public int compareTo(ZonedDate other) {
-        if(isEqual(other)) {
-            return 0;
-        }
         if(isBefore(other)) {
             return -1;
         }
         if(isAfter(other)) {
             return +1;
         }
-        throw new AssertionError("unreachable");
+        return 0;
     }
 
     @Override
@@ -54,15 +140,93 @@ public abstract class ZonedDate implements Comparable<ZonedDate> {
         return Objects.hash(getYear(), getMonth(), getDay());
     }
 
+    public String format(DateTimeFormatter format) {
+        return DateTimeFormatters.format(format, withZero());
+    }
+
+    @Override
+    public String toString() {
+        return DateTimeFormatters.formatDate(withZero()) + "[" + getZone() + "]";
+    }
+
     /**
      * This wrapper allows all operations even if timezones do not match.
      * Because we have full datetime, we can convert between timezones safely.
      */
     @RequiredArgsConstructor
-    private static abstract class Complete extends ZonedDate {
+    private static class Complete extends ZonedDate {
 
         private final ZonedDateTime value;
 
+        @Override
+        public ZoneId getZone() {
+            return value.getZone();
+        }
+
+        @Override
+        public boolean isZoneConvertible(ZoneId zone) {
+            return true;
+        }
+
+        @Override
+        public ZonedDate withZone(ZoneId zone) {
+            return new Complete(value.withZoneSameInstant(zone));
+        }
+
+        @Override
+        public ZonedDateTime withTime(ZonedTime time) {
+            return value.withZoneSameInstant(time.getZone()).with(ChronoField.NANO_OF_DAY, time.toNanoOfDay());
+        }
+
+        @Override
+        public ZonedDateTime withMin() {
+            return value.with(LocalTime.MIN);
+        }
+
+        @Override
+        public ZonedDateTime withMax() {
+            return value.with(LocalTime.MAX);
+        }
+
+        @Override
+        public long until(ZonedDateTime end, ChronoUnit unit) {
+            return value.until(end, unit); // @todo
+        }
+
+        @Override
+        public Duration until(ZonedDate end) {
+            return null; // @todo
+        }
+
+        @Override
+        public long until(ZonedDate end, ChronoUnit unit) {
+            return 0; // @todo
+        }
+
+        @Override
+        public ZonedDate plusDays(int days) {
+            return new Complete(value.plusDays(days));
+        }
+
+        @Override
+        public ZonedDate minusDays(int days) {
+            return new Complete(value.minusDays(days));
+        }
+
+        @Override
+        public int getYear() {
+            return value.getYear();
+        }
+
+        @Override
+        public int getMonth() {
+            return value.getMonthValue();
+        }
+
+        @Override
+        public int getDay() {
+            return value.getDayOfMonth();
+        }
     }
 
     /**
@@ -70,12 +234,82 @@ public abstract class ZonedDate implements Comparable<ZonedDate> {
      * It is impossible to convert between timezones without full datetime.
      */
     @RequiredArgsConstructor
-    private static abstract class Incomplete extends ZonedDate {
+    private static class Incomplete extends ZonedDate {
 
         private final ZoneId zone;
 
         private final LocalDate value;
 
+        @Override
+        public ZoneId getZone() {
+            return zone;
+        }
+
+        @Override
+        public boolean isZoneConvertible(ZoneId zone) {
+            return false;
+        }
+
+        @Override
+        public ZonedDate withZone(ZoneId zone) {
+            throw new UnsupportedOperationException();  // @todo better exception
+        }
+
+        @Override
+        public ZonedDateTime withTime(ZonedTime time) {
+            LocalTime adjusted = LocalTime.ofNanoOfDay(time.withZone(getZone()).toNanoOfDay());
+            return ZonedDateTime.of(value, adjusted, getZone());
+        }
+
+        @Override
+        public ZonedDateTime withMin() {
+            return ZonedDateTime.of(value, LocalTime.MIN, getZone());
+        }
+
+        @Override
+        public ZonedDateTime withMax() {
+            return ZonedDateTime.of(value, LocalTime.MAX, getZone());
+        }
+
+        @Override
+        public long until(ZonedDateTime end, ChronoUnit unit) {
+            return 0; // @todo
+        }
+
+        @Override
+        public Duration until(ZonedDate end) {
+            return null; // @todo
+        }
+
+        @Override
+        public long until(ZonedDate end, ChronoUnit unit) {
+            return 0; // @todo
+        }
+
+        @Override
+        public ZonedDate plusDays(int days) {
+            return new Incomplete(zone, value.plusDays(days));
+        }
+
+        @Override
+        public ZonedDate minusDays(int days) {
+            return new Incomplete(zone, value.minusDays(days));
+        }
+
+        @Override
+        public int getYear() {
+            return value.getYear();
+        }
+
+        @Override
+        public int getMonth() {
+            return value.getMonthValue();
+        }
+
+        @Override
+        public int getDay() {
+            return value.getDayOfMonth();
+        }
     }
 
 }

+ 281 - 8
assira.core/src/main/java/net/ranides/assira/time2/ZonedTime.java

@@ -1,35 +1,90 @@
 package net.ranides.assira.time2;
 
 import lombok.RequiredArgsConstructor;
+import org.jetbrains.annotations.NotNull;
 
+import java.time.Duration;
+import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
 
 public abstract class ZonedTime implements Comparable<ZonedTime> {
 
-    public static ZonedTime of(ZonedDateTime input) {
-        throw new UnsupportedOperationException("Not implemented yet");
+    public static @NotNull ZonedTime of(@NotNull ZonedDateTime input) {
+        return new Complete(input);
     }
 
+    @ZoneExplicit
+    public static @NotNull ZonedTime of(@NotNull ZoneId zone, @NotNull LocalTime time) {
+        return new Incomplete(zone, time);
+    }
+
+    public static @NotNull ZonedTime now() {
+        return new Complete(ZonedDateTime.now());
+    }
+
+    public abstract ZoneId getZone();
+
+    public abstract int getHour();
+
+    public abstract int getMinute();
+
+    public abstract int getSecond();
+
+    public abstract int getNano();
+
+    public abstract long toNanoOfDay();
+
     public abstract boolean isAfter(ZonedTime other);
 
+    public abstract boolean isAfter(ZonedDateTime other);
+
     public abstract boolean isBefore(ZonedTime other);
 
+    public abstract boolean isBefore(ZonedDateTime other);
+
     public abstract boolean isEqual(ZonedTime other);
 
+    public abstract boolean isEqualTime(ZonedDateTime other);
+
+    public abstract boolean isSameZone(ZonedTime other);
+
+    public abstract boolean isSameZone(ZonedDateTime other);
+
+    public abstract boolean isSameZone(ZoneId other);
+
+    public abstract boolean isZoneConvertible(ZoneId zone);
+
+    @ZoneHazard
+    public abstract ZonedTime withZone(ZoneId zone);
+
+    @ZoneHazard
+    public abstract ZonedDateTime withDate(ZonedDate time);
+
+    public ZonedDateTime withDate(ZonedDateTime datetime) {
+        return withDate(ZonedDate.of(datetime).withZone(getZone()));
+    }
+
+    public abstract Duration until(ZonedDateTime end);
+
+    public abstract long until(ZonedDateTime end, ChronoUnit unit);
+
+    public abstract Duration until(ZonedTime end);
+
+    public abstract long until(ZonedTime end, ChronoUnit unit);
+
+    @ZoneHazard
     @Override
     public int compareTo(ZonedTime other) {
-        if(isEqual(other)) {
-            return 0;
-        }
         if(isBefore(other)) {
             return -1;
         }
         if(isAfter(other)) {
             return +1;
         }
-        throw new AssertionError("unreachable");
+        return 0;
     }
 
     /**
@@ -37,10 +92,119 @@ public abstract class ZonedTime implements Comparable<ZonedTime> {
      * Because we have full datetime, we can convert between timezones safely.
      */
     @RequiredArgsConstructor
-    private static abstract class Complete extends ZonedTime {
+    private static class Complete extends ZonedTime {
 
         private final ZonedDateTime value;
 
+        @Override
+        public ZoneId getZone() {
+            return value.getZone();
+        }
+
+        @Override
+        public boolean isAfter(ZonedTime other) {
+            return false;
+        }
+
+        @Override
+        public boolean isAfter(ZonedDateTime other) {
+            return false;
+        }
+
+        @Override
+        public boolean isBefore(ZonedTime other) {
+            return false;
+        }
+
+        @Override
+        public boolean isBefore(ZonedDateTime other) {
+            return false;
+        }
+
+        @Override
+        public boolean isEqual(ZonedTime other) {
+            return false;
+        }
+
+        @Override
+        public boolean isEqualTime(ZonedDateTime other) {
+            return false;
+        }
+
+        @Override
+        public boolean isSameZone(ZonedTime other) {
+            return false;
+        }
+
+        @Override
+        public boolean isSameZone(ZonedDateTime other) {
+            return false;
+        }
+
+        @Override
+        public boolean isSameZone(ZoneId other) {
+            return false;
+        }
+
+        @Override
+        public boolean isZoneConvertible(ZoneId zone) {
+            return false;
+        }
+
+        @Override
+        public ZonedTime withZone(ZoneId zone) {
+            return null;
+        }
+
+        @Override
+        public ZonedDateTime withDate(ZonedDate time) {
+            return null;
+        }
+
+        @Override
+        public Duration until(ZonedDateTime end) {
+            return null;
+        }
+
+        @Override
+        public long until(ZonedDateTime end, ChronoUnit unit) {
+            return 0;
+        }
+
+        @Override
+        public Duration until(ZonedTime end) {
+            return null;
+        }
+
+        @Override
+        public long until(ZonedTime end, ChronoUnit unit) {
+            return 0;
+        }
+
+        @Override
+        public int getHour() {
+            return 0;
+        }
+
+        @Override
+        public int getMinute() {
+            return 0;
+        }
+
+        @Override
+        public int getSecond() {
+            return 0;
+        }
+
+        @Override
+        public int getNano() {
+            return 0;
+        }
+
+        @Override
+        public long toNanoOfDay() {
+            return value.toLocalTime().toNanoOfDay();
+        }
     }
 
     /**
@@ -48,12 +212,121 @@ public abstract class ZonedTime implements Comparable<ZonedTime> {
      * It is impossible to convert between timezones without full datetime.
      */
     @RequiredArgsConstructor
-    private static abstract class Incomplete extends ZonedTime {
+    private static class Incomplete extends ZonedTime {
 
         private final ZoneId zone;
 
         private final LocalTime value;
 
+        @Override
+        public ZoneId getZone() {
+            return null;
+        }
+
+        @Override
+        public boolean isAfter(ZonedTime other) {
+            return false;
+        }
+
+        @Override
+        public boolean isAfter(ZonedDateTime other) {
+            return false;
+        }
+
+        @Override
+        public boolean isBefore(ZonedTime other) {
+            return false;
+        }
+
+        @Override
+        public boolean isBefore(ZonedDateTime other) {
+            return false;
+        }
+
+        @Override
+        public boolean isEqual(ZonedTime other) {
+            return false;
+        }
+
+        @Override
+        public boolean isEqualTime(ZonedDateTime other) {
+            return false;
+        }
+
+        @Override
+        public boolean isSameZone(ZonedTime other) {
+            return false;
+        }
+
+        @Override
+        public boolean isSameZone(ZonedDateTime other) {
+            return false;
+        }
+
+        @Override
+        public boolean isSameZone(ZoneId other) {
+            return false;
+        }
+
+        @Override
+        public boolean isZoneConvertible(ZoneId zone) {
+            return false;
+        }
+
+        @Override
+        public ZonedTime withZone(ZoneId zone) {
+            return null;
+        }
+
+        @Override
+        public ZonedDateTime withDate(ZonedDate time) {
+            return null;
+        }
+
+        @Override
+        public Duration until(ZonedDateTime end) {
+            return null;
+        }
+
+        @Override
+        public long until(ZonedDateTime end, ChronoUnit unit) {
+            return 0;
+        }
+
+        @Override
+        public Duration until(ZonedTime end) {
+            return null;
+        }
+
+        @Override
+        public long until(ZonedTime end, ChronoUnit unit) {
+            return 0;
+        }
+
+        @Override
+        public int getHour() {
+            return 0;
+        }
+
+        @Override
+        public int getMinute() {
+            return 0;
+        }
+
+        @Override
+        public int getSecond() {
+            return 0;
+        }
+
+        @Override
+        public int getNano() {
+            return 0;
+        }
+
+        @Override
+        public long toNanoOfDay() {
+            return value.toNanoOfDay();
+        }
     }
 
 }

+ 13 - 0
assira.core/src/test/java/net/ranides/assira/time2/DateTimeFormattersTest.java

@@ -0,0 +1,13 @@
+package net.ranides.assira.time2;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class DateTimeFormattersTest {
+
+    @Test
+    public void testFormatUtc() {
+        System.out.println( DateTimeFormatters.formatUtc(null).toString().toUpperCase() );
+    }
+}

+ 7 - 1
assira.core/src/test/java/net/ranides/assira/time2/DateTimeParsersTest.java

@@ -1,7 +1,6 @@
 package net.ranides.assira.time2;
 
 import net.ranides.assira.junit.NewAssert;
-import net.ranides.assira.time2.DateTimeParsers;
 import org.junit.Test;
 
 import java.time.ZonedDateTime;
@@ -48,4 +47,11 @@ public class DateTimeParsersTest {
         System.out.println(e);
         System.out.println(f);
     }
+
+    @Test
+    public void testParseDate() {
+        ZonedDate a = DateTimeParsers.parseUtcDate("2024-10-16");
+
+        System.out.println(a);
+    }
 }

+ 35 - 0
assira.core/src/test/java/net/ranides/assira/time2/DateTimeUtilsTest.java

@@ -0,0 +1,35 @@
+package net.ranides.assira.time2;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.time.ZonedDateTime;
+
+import static org.junit.Assert.*;
+
+public class DateTimeUtilsTest {
+
+    @Test
+    public void highlight() {
+        ZonedDate date1 = DateTimeUtils.asDate(ZonedDateTime.now());
+
+        ZonedDate date2 = DateTimeUtils.asPolishDate(ZonedDateTime.now());
+
+
+        ZonedDateTime input = Math.random()> 0.5 ? null : ZonedDateTime.now();
+
+        ZonedDate out = DateTimeUtils.asPolishDate(input);
+
+        System.out.println(out.withMax());
+
+        ZonedDateTime datetime1 = Math.random()> 0.5 ? null : ZonedDateTime.now();
+        ZonedDateTime datetime2 = ZonedDateTime.now();
+
+        System.out.println( DateTimeUtils.diff(datetime1, datetime2));
+
+
+        Assert.assertNotNull(date1);
+        Assert.assertNotNull(date2);
+
+    }
+}

+ 0 - 104
assira.drafts/src/main/java/net/ranides/assira/datetime/DateTimeUtils1.java

@@ -1,104 +0,0 @@
-package net.ranides.assira.datetime;
-
-import java.time.*;
-import java.time.format.DateTimeFormatter;
-import java.time.temporal.ChronoUnit;
-import java.util.Locale;
-import java.util.Objects;
-import java.util.function.Function;
-
-import static java.util.Optional.ofNullable;
-
-public class DateTimeUtils1 {
-
-    public static ZonedDateTime truncateToDate(ZonedDateTime date) {
-        return mapNullable(date, v -> v.withMinute(0).withHour(0).withSecond(0).withNano(0));
-    }
-
-    public static boolean dateBetweenWithMinMax(ZonedDateTime testedDate, ZonedDateTime dateFrom, ZonedDateTime dateTo) {
-        return dateBetweenWithMinMax(testedDate, dateFrom, dateTo, false);
-    }
-
-    public static boolean dateBetweenWithMinMax(ZonedDateTime testedDate, ZonedDateTime dateFrom, ZonedDateTime dateTo, boolean inclusive) {
-        dateFrom = valueOrMin(dateFrom);
-        dateTo = valueOrMax(dateTo);
-
-        if(inclusive) {
-            return !(testedDate.isBefore(dateFrom) || testedDate.equals(dateFrom)) && !(testedDate.isAfter(dateTo) || testedDate.equals(dateTo));
-        }
-
-        return !testedDate.isBefore(valueOrMin(dateFrom)) && !testedDate.isAfter(dateTo);
-    }
-
-    public static ZonedDateTime addTimeToDate(String dateValue, String timeValue) {
-        if(dateValue == null) {
-            return null;
-        }
-        ZonedDate1 zonedDate = parseToPolishDate(dateValue);
-        return timeValue != null ? zonedDate.withTime(parseToPolishTime(timeValue)) : zonedDate.withMidnight();
-    }
-
-    public static ZonedDateTime beginningOfDay(ZonedDateTime dateTime) {
-        return dateTime.with(LocalTime.MIDNIGHT);
-    }
-
-    public static ZonedDateTime beginningOfDay(ZonedDate1 date) {
-        return date.withMax();
-    }
-
-    public static ZonedDateTime endOfDay(ZonedDateTime dateTime) {
-        return dateTime.with(LocalTime.MAX);
-    }
-
-
-
-    public static boolean isMinTime(ZonedDateTime dateTime) {
-        return LocalTime.MIN.equals(dateTime.toLocalTime());
-    }
-
-    public static boolean isMaxTime(ZonedDateTime dateTime) {
-        return LocalTime.MAX.equals(dateTime.toLocalTime());
-    }
-
-    public static ZonedDateTime lastDayOfMonth(ZonedDateTime date) {
-        return date
-                .withDayOfMonth(1)
-                .plusMonths(1)
-                .with(LocalTime.MIDNIGHT)
-                .minusDays(1);
-    }
-
-    public static ZonedDateTime lastMinuteOfDay(ZonedDateTime date) {
-        return date.withHour(23).withMinute(59).withSecond(59);
-    }
-
-    public static boolean isSaturday(ZonedDateTime day) {
-        return day.getDayOfWeek() == DayOfWeek.SATURDAY;
-    }
-
-    public static boolean isSunday(ZonedDateTime day) {
-        return day.getDayOfWeek() == DayOfWeek.SUNDAY;
-    }
-
-    public static boolean isHoliday(ZonedDateTime day) {
-        return false;
-    }
-
-    public static Duration timeDiff(LocalTime from, LocalTime to) {
-        if (from.isAfter(to)) {
-            return Duration.of(24, ChronoUnit.HOURS).minus(Duration.between(to, from));
-        }
-        return Duration.between(from, to);
-    }
-
-    public static Duration timeDiff(ZonedTime1 from, ZonedTime1 to) {
-        if(!from.getZone().equals(to.getZone())) {
-            throw new IllegalArgumentException();
-        }
-        if (from.isAfter(to)) {
-            return Duration.of(24, ChronoUnit.HOURS).minus(Duration.between(to.getTimestamp(), from.getTimestamp()));
-        }
-        return Duration.between(from.getTimestamp(), to.getTimestamp());
-    }
-
-}

+ 0 - 139
assira.drafts/src/main/java/net/ranides/assira/datetime/ZonedDate1.java

@@ -1,139 +0,0 @@
-package net.ranides.assira.datetime;
-
-import lombok.Getter;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.Serializable;
-import java.sql.Timestamp;
-import java.time.*;
-import java.time.format.DateTimeFormatter;
-
-
-/**
- * This class is used in keep track of timezone and detect invalid time conversions.
- *
- * Timezone is NOT used to generate proper "ZonedDateTime".
- *
- * Timezone is used to detect invalid operations (e.g. we compare timestamps created from different timezones)
- */
-public class ZonedDate1 implements Serializable, Comparable<ZonedDate1> {
-
-    public static final ZonedDate1 MIN_UTC = new ZonedDate1(DateTimeUtils1.ZONE_0, LocalDate.MIN);
-
-    public static final ZonedDate1 MAX_UTC = new ZonedDate1(DateTimeUtils1.ZONE_0, LocalDate.MAX);
-
-    @Getter
-    private final ZoneId zone;
-
-    @Getter
-    private final LocalDate timestamp;
-
-    private ZonedDate1(ZoneId zone, LocalDate timestamp) {
-        this.zone = zone;
-        this.timestamp = timestamp;
-    }
-
-    public static ZonedDate1 from(ZonedDateTime source) {
-        return source == null ? null : new ZonedDate1(source.getZone(), source.toLocalDate());
-    }
-
-    public static ZonedDate1 from(ZonedDateTime source, ZoneId target) {
-        return source == null ? null : from(source.withZoneSameInstant(target));
-    }
-
-    public static ZonedDate1 from(Timestamp source) {
-        return source == null ? null : new ZonedDate1(DateTimeUtils1.ZONE_0, source.toLocalDateTime().toLocalDate());
-    }
-
-    public static ZonedDate1 max(ZoneId zone) {
-        return from(ZonedDateTime.of(4000, 1, 1, 0, 0, 0, 0, zone));
-    }
-
-    public static ZonedDate1 min(ZoneId zone) {
-        return from(ZonedDateTime.of(1900, 1, 1, 0, 0, 0, 0, zone));
-    }
-
-    public static ZonedDate1 of(int year, Month month, int day) {
-        return from(ZonedDateTime.of(year, month.getValue(), day, 0, 0, 0, 0, ZoneId.systemDefault()));
-    }
-
-    @Override
-    public int compareTo(@NotNull ZonedDate1 that) {
-        if(!that.zone.equals(zone)) {
-            throw new IllegalArgumentException();
-        }
-        return timestamp.compareTo(that.timestamp);
-    }
-
-    public ZonedDateTime withTime(ZonedTime1 time) {
-        if(!time.zone.equals(zone)) {
-            throw new IllegalArgumentException();
-        }
-        return ZonedDateTime.of(timestamp, time.timestamp, zone);
-    }
-
-    public ZonedDateTime withMidnight() {
-        return ZonedDateTime.of(timestamp, LocalTime.MIDNIGHT, zone);
-    }
-
-    public ZonedDateTime withMin() {
-        return ZonedDateTime.of(timestamp, LocalTime.MIN, zone);
-    }
-
-    public ZonedDateTime withMax() {
-        return ZonedDateTime.of(timestamp, LocalTime.MAX, zone);
-    }
-
-    @Deprecated
-    public ZonedDateTime atStartOfDay() {
-        return withMidnight();
-    }
-
-    @Deprecated
-    public ZonedDateTime atEndOfDay() {
-        return withMidnight();
-    }
-
-    public boolean isBefore(ZonedDate1 other) {
-        return withMidnight().isBefore(other.withMidnight());
-    }
-
-    public boolean isBefore(ZonedDateTime other) {
-        return withMidnight().isBefore(other.with(LocalTime.MIDNIGHT));
-    }
-
-    public boolean isAfter(ZonedDate1 other) {
-        return withMidnight().isAfter(other.withMidnight());
-    }
-
-    public boolean isAfter(ZonedDateTime other) {
-        return withMidnight().isAfter(other.with(LocalTime.MIDNIGHT));
-    }
-
-    public ZonedDate1 plusDays(int days) {
-        return new ZonedDate1(zone, timestamp.plusDays(days));
-    }
-
-    public Object format(DateTimeFormatter dateFormatter) {
-        return timestamp.format(dateFormatter);
-    }
-
-    public ZonedDate1 minusDays(int v) {
-        return new ZonedDate1(zone, timestamp.minusDays(v));
-    }
-
-    public int getYear() {
-        return timestamp.getYear();
-    }
-
-    public boolean isSameDate(ZonedDateTime periodDateTimeStart) {
-        return periodDateTimeStart.withZoneSameInstant(zone).toLocalDate().equals(timestamp);
-    }
-
-    public boolean isEqual(ZonedDate1 time) {
-        if(!time.zone.equals(zone)) {
-            throw new IllegalArgumentException();
-        }
-        return time.timestamp.isEqual(timestamp);
-    }
-}

+ 0 - 41
assira.drafts/src/main/java/net/ranides/assira/datetime/ZonedDateRange1.java

@@ -1,41 +0,0 @@
-package net.ranides.assira.datetime;
-
-import lombok.Builder;
-
-import java.time.ZonedDateTime;
-import java.time.temporal.TemporalField;
-
-@Builder
-public class ZonedDateRange1 {
-
-    private final TemporalField precision;
-    private final boolean inclusive;
-    private final ZonedDate1 begin;
-    private final ZonedDate1 end;
-
-    private ZonedDateRange1(TemporalField precision, boolean inclusive, ZonedDate1 begin, ZonedDate1 end) {
-        this.precision = precision;
-        this.inclusive = inclusive;
-        this.begin = begin;
-        this.end = end;
-    }
-
-    public boolean between(ZonedDateTime testedDate) {
-        // eliminate dates "before"
-        if(begin != null && begin.isBefore(testedDate)) {
-            return false;
-        }
-        if(end == null) {
-            return true;
-        }
-
-        return inclusive ? !end.isAfter(testedDate) : end.isBefore(testedDate);
-    }
-
-    public boolean between(ZonedDate1 testedDate) {
-        return false;
-    }
-
-
-
-}

+ 0 - 80
assira.drafts/src/main/java/net/ranides/assira/datetime/ZonedDateTimeRange1.java

@@ -1,80 +0,0 @@
-package net.ranides.assira.datetime;
-
-import java.io.Serializable;
-import java.time.ZonedDateTime;
-
-public abstract class ZonedDateTimeRange1 implements Serializable {
-
-    public static ZonedDateTimeRange1 of(ZonedDateTime begin, ZonedDateTime end) {
-        if(begin == null) {
-            return new BeginRange(end);
-        }
-        if(end == null) {
-            return new BeginRange(end);
-        }
-        return new FullRange(begin, end);
-    }
-
-    public abstract boolean between(ZonedDateTime testedDate);
-
-    public abstract boolean between(ZonedDate1 testedDate);
-
-    private static class EndRange extends ZonedDateTimeRange1 {
-        private final ZonedDateTime end;
-
-        private EndRange(ZonedDateTime end) {
-            this.end = end;
-        }
-
-        @Override
-        public boolean between(ZonedDateTime testedDate) {
-            return testedDate.isBefore(end);
-        }
-
-        @Override
-        public boolean between(ZonedDate1 testedDate) {
-            if(!testedDate.getZone().equals(end.getZone())) {
-                throw new IllegalArgumentException();
-            }
-            return testedDate.withMidnight().isBefore(end);
-        }
-    }
-
-    private static class BeginRange extends ZonedDateTimeRange1 {
-        private final ZonedDateTime begin;
-
-        private BeginRange(ZonedDateTime begin) {
-            this.begin = begin;
-        }
-
-        @Override
-        public boolean between(ZonedDateTime testedDate) {
-            return !testedDate.isBefore(begin);
-        }
-
-        @Override
-        public boolean between(ZonedDate1 testedDate) {
-            return !testedDate.withMidnight().isBefore(begin);
-        }
-    }
-
-    private static class FullRange extends ZonedDateTimeRange1 {
-        private final ZonedDateTime begin;
-        private final ZonedDateTime end;
-
-        public FullRange(ZonedDateTime begin, ZonedDateTime end) {
-            this.begin = begin;
-            this.end = end;
-        }
-
-        @Override
-        public boolean between(ZonedDateTime testedDate) {
-            return false;
-        }
-
-        @Override
-        public boolean between(ZonedDate1 testedDate) {
-            return false;
-        }
-    }
-}

+ 0 - 67
assira.drafts/src/main/java/net/ranides/assira/datetime/ZonedTime1.java

@@ -1,67 +0,0 @@
-package net.ranides.assira.datetime;
-
-import lombok.Getter;
-
-import java.io.Serializable;
-import java.time.LocalTime;
-import java.time.ZoneId;
-import java.time.ZonedDateTime;
-
-/**
- * This class is used in keep track of timezone and detect invalid time conversions.
- *
- * Timezone is NOT used to generate proper "ZonedDateTime".
- *
- * Timezone is used to detect invalid operations (e.g. we compare timestamps created from different timezones)
- */
-public class ZonedTime1 implements Serializable {
-
-    @Getter
-    final ZoneId zone;
-
-    @Getter
-    final LocalTime timestamp;
-
-    private ZonedTime1(ZoneId zone, LocalTime timestamp) {
-        this.zone = zone;
-        this.timestamp = timestamp;
-    }
-
-    public static ZonedTime1 from(ZonedDateTime source) {
-        return new ZonedTime1(source.getZone(), source.toLocalTime());
-    }
-
-    public static ZonedTime1 of(int h, int m, int s) {
-        return new ZonedTime1(ZoneId.systemDefault(), LocalTime.of(h,m,s));
-    }
-
-    public ZonedDateTime withDate(ZonedDate1 date) {
-        return date.withTime(this);
-    }
-
-    @Deprecated
-    public boolean isBefore(LocalTime value) {
-        return timestamp.isBefore(value);
-    }
-
-    @Deprecated
-    public boolean isAfter(LocalTime value) {
-        return timestamp.isAfter(value);
-    }
-
-    public boolean isBefore(ZonedTime1 value) {
-        if(!zone.equals(value.zone)) {
-            throw new IllegalArgumentException();
-        }
-        return timestamp.isBefore(value.timestamp);
-    }
-
-    @Deprecated
-    public boolean isAfter(ZonedTime1 value) {
-        if(!zone.equals(value.zone)) {
-            throw new IllegalArgumentException();
-        }
-        return timestamp.isAfter(value.timestamp);
-    }
-
-}