Ranides Atterwim 11 месяцев назад
Родитель
Сommit
8f28f59641

+ 128 - 17
assira.commons/src/main/java/net/ranides/assira/time/format/DateTimeReader.java

@@ -7,7 +7,9 @@ import net.ranides.assira.generic.ValueUtils;
 import net.ranides.assira.time.DateTimeUtils;
 import net.ranides.assira.time.ZonedDate;
 import net.ranides.assira.time.ZonedTime;
+import org.jetbrains.annotations.NotNull;
 
+import java.text.ParsePosition;
 import java.time.DateTimeException;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
@@ -18,14 +20,73 @@ import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeFormatterBuilder;
 import java.time.format.DateTimeParseException;
+import java.time.format.SignStyle;
+import java.time.temporal.ChronoField;
 import java.time.temporal.TemporalAccessor;
 import java.time.temporal.TemporalQueries;
+import java.util.HashMap;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Objects;
 
+import static java.time.temporal.ChronoField.DAY_OF_MONTH;
+import static java.time.temporal.ChronoField.HOUR_OF_DAY;
+import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
+import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
+import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;
+import static java.time.temporal.ChronoField.YEAR;
+
 @AllArgsConstructor(access = AccessLevel.PRIVATE)
 public class DateTimeReader {
 
+    private static final DateTimeFormatter ECMA_PARSER;
+    static {
+        Map<Long, String> dow = new HashMap<>();
+        dow.put(1L, "Mon");
+        dow.put(2L, "Tue");
+        dow.put(3L, "Wed");
+        dow.put(4L, "Thu");
+        dow.put(5L, "Fri");
+        dow.put(6L, "Sat");
+        dow.put(7L, "Sun");
+        Map<Long, String> moy = new HashMap<>();
+        moy.put(1L, "Jan");
+        moy.put(2L, "Feb");
+        moy.put(3L, "Mar");
+        moy.put(4L, "Apr");
+        moy.put(5L, "May");
+        moy.put(6L, "Jun");
+        moy.put(7L, "Jul");
+        moy.put(8L, "Aug");
+        moy.put(9L, "Sep");
+        moy.put(10L, "Oct");
+        moy.put(11L, "Nov");
+        moy.put(12L, "Dec");
+
+        ECMA_PARSER = new DateTimeFormatterBuilder()
+            .parseCaseInsensitive()
+            .parseLenient()
+            .appendText(ChronoField.DAY_OF_WEEK, dow)
+            .appendLiteral(' ')
+            .appendText(MONTH_OF_YEAR, moy)
+            .appendLiteral(' ')
+            .appendValue(DAY_OF_MONTH, 1, 2, SignStyle.NOT_NEGATIVE)
+            .appendLiteral(' ')
+            .appendValue(YEAR, 4)
+            .appendLiteral(' ')
+            .appendValue(HOUR_OF_DAY, 2)
+            .appendLiteral(':')
+            .appendValue(MINUTE_OF_HOUR, 2)
+            .optionalStart()
+            .appendLiteral(':')
+            .appendValue(SECOND_OF_MINUTE, 2)
+            .optionalEnd()
+            .appendLiteral(" GMT")
+            .appendOffset("+HHMM", "GMT")  // should handle UT/Z/EST/EDT/CST/CDT/MST/MDT/PST/MDT
+            .appendLiteral(" (")
+            .toFormatter();
+    }
+
     private static final DateTimeFormatter DATETIME_PARSER = new DateTimeFormatterBuilder()
         .parseCaseInsensitive()
         .append(DateTimeFormatter.ISO_LOCAL_DATE)
@@ -78,9 +139,11 @@ public class DateTimeReader {
         .optionalEnd()
         .toFormatter();
 
+    public static final DateTimeReader ISO = new DateTimeReader(DateTimeFormatter.ISO_ZONED_DATE_TIME);
+
     public static final DateTimeReader RFC = new DateTimeReader(DateTimeFormatter.RFC_1123_DATE_TIME);
 
-    public static final DateTimeReader ISO = new DateTimeReader(DateTimeFormatter.ISO_ZONED_DATE_TIME);
+    public static final DateTimeReader ECMA262 = new DateTimeReader(ECMA_PARSER).withParseAll(false);
 
     public static final DateTimeReader DATETIME = new DateTimeReader(DATETIME_PARSER);
 
@@ -98,16 +161,54 @@ public class DateTimeReader {
     @Getter
     private final ZoneId targetZone;
 
-    public DateTimeReader(String pattern) {
+    private final boolean parseAll;
+
+    public static DateTimeReader of(String... list) {
+        return of(0, list);
+    }
+
+    public static DateTimeReader of(DateTimeFormatter... list) {
+        return of(0, list);
+    }
+
+    public static DateTimeReader of(DateTimeReader... list) {
+        return of(0, list);
+    }
+
+    private static DateTimeReader of(int offset, DateTimeReader... list) {
+        if(offset == list.length - 1) {
+            return list[offset];
+        } else {
+            return list[offset].withFallback(of(offset + 1, list));
+        }
+    }
+
+    private static DateTimeReader of(int offset, DateTimeFormatter... list) {
+        if(offset == list.length - 1) {
+            return new DateTimeReader(list[offset]);
+        } else {
+            return new DateTimeReader(list[offset]).withFallback(of(offset + 1, list));
+        }
+    }
+
+    private static DateTimeReader of(int offset, String... list) {
+        if(offset == list.length - 1) {
+            return new DateTimeReader(list[offset]);
+        } else {
+            return new DateTimeReader(list[offset]).withFallback(of(offset + 1, list));
+        }
+    }
+
+    private DateTimeReader(String pattern) {
         this(DateTimeFormatter.ofPattern(pattern));
     }
 
     private DateTimeReader(DateTimeFormatter format) {
-        this(null, format, null, null);
+        this(null, format, null, null, true);
     }
 
     public DateTimeReader withFallback(DateTimeReader fallback) {
-        return new DateTimeReader(fallback, parser, sourceZone, targetZone);
+        return new DateTimeReader(fallback, parser, sourceZone, targetZone, parseAll);
     }
 
     public DateTimeReader withFormat(String newFormat) {
@@ -115,11 +216,11 @@ public class DateTimeReader {
     }
 
     public DateTimeReader withFormat(DateTimeFormatter newFormat) {
-        return new DateTimeReader(fallback, newFormat, sourceZone, targetZone);
+        return new DateTimeReader(fallback, newFormat, sourceZone, targetZone, parseAll);
     }
 
     public DateTimeReader withLocale(Locale newLocale) {
-        return new DateTimeReader(fallback, parser.withLocale(newLocale), sourceZone, targetZone);
+        return new DateTimeReader(fallback, parser.withLocale(newLocale), sourceZone, targetZone, parseAll);
     }
 
     public DateTimeReader withLocale(String newLocale) {
@@ -131,7 +232,7 @@ public class DateTimeReader {
     }
 
     public DateTimeReader withZone(ZoneId newZone) {
-        return new DateTimeReader(fallback, parser, newZone, newZone);
+        return new DateTimeReader(fallback, parser, newZone, newZone, parseAll);
     }
 
     public DateTimeReader withZone(String newZone) {
@@ -139,11 +240,11 @@ public class DateTimeReader {
     }
 
     public DateTimeReader withoutZone() {
-        return new DateTimeReader(fallback, parser, null, null);
+        return new DateTimeReader(fallback, parser, null, null, parseAll);
     }
 
     public DateTimeReader withSourceZone(ZoneId newZone) {
-        return new DateTimeReader(fallback, parser, newZone, targetZone);
+        return new DateTimeReader(fallback, parser, newZone, targetZone, parseAll);
     }
 
     public DateTimeReader withSourceZone(String newZone) {
@@ -151,11 +252,11 @@ public class DateTimeReader {
     }
 
     public DateTimeReader withoutSourceZone() {
-        return new DateTimeReader(fallback, parser, null, targetZone);
+        return new DateTimeReader(fallback, parser, null, targetZone, parseAll);
     }
 
     public DateTimeReader withTargetZone(ZoneId newZone) {
-        return new DateTimeReader(fallback, parser, sourceZone, newZone);
+        return new DateTimeReader(fallback, parser, sourceZone, newZone, parseAll);
     }
 
     public DateTimeReader withTargetZone(String newZone) {
@@ -163,7 +264,11 @@ public class DateTimeReader {
     }
 
     public DateTimeReader withoutTargetZone() {
-        return new DateTimeReader(fallback, parser, sourceZone, null);
+        return new DateTimeReader(fallback, parser, sourceZone, null, parseAll);
+    }
+
+    DateTimeReader withParseAll(boolean newParseAll) {
+        return new DateTimeReader(fallback, parser, sourceZone, targetZone, newParseAll);
     }
 
     public ZonedDateTime parse(String input) {
@@ -171,7 +276,7 @@ public class DateTimeReader {
             return null;
         }
         try {
-            return newDateTime(parser.parse(input, v -> v));
+            return newDateTime(parse0(input));
         } catch (DateTimeParseException e) {
             if(fallback != null) {
                 return fallback.parse(input);
@@ -185,7 +290,7 @@ public class DateTimeReader {
             return null;
         }
         try {
-            return newDate(parser.parse(input, v -> v));
+            return newDate(parse0(input));
         } catch (DateTimeParseException e) {
             if(fallback != null) {
                 return fallback.parseDate(input);
@@ -199,7 +304,7 @@ public class DateTimeReader {
             return null;
         }
         try {
-            return newTime(parser.parse(input, v -> v));
+            return newTime(parse0(input));
         } catch (DateTimeParseException e) {
             if(fallback != null) {
                 return fallback.parseTime(input);
@@ -208,6 +313,12 @@ public class DateTimeReader {
         }
     }
 
+    @NotNull
+    private TemporalAccessor parse0(String input) {
+        return parseAll ? parser.parse(input) : parser.parse(input, new ParsePosition(0));
+    }
+
+
     public ZonedDateTime newDateTime(TemporalAccessor temporal) {
         ZoneOffset offset = temporal.query(TemporalQueries.offset());
         ZoneId zone = temporal.query(TemporalQueries.zoneId());
@@ -232,7 +343,7 @@ public class DateTimeReader {
         return adjustZone(ZonedDateTime.of(datetime, ValueUtils.or(zone, offset)));
     }
 
-    public ZonedDate newDate(TemporalAccessor temporal) {
+    private ZonedDate newDate(TemporalAccessor temporal) {
         LocalTime time = temporal.query(TemporalQueries.localTime());
         if(time != null) {
             return ZonedDate.of(newDateTime(temporal));
@@ -268,7 +379,7 @@ public class DateTimeReader {
         return ZonedDate.of(zone, date);
     }
 
-    public ZonedTime newTime(TemporalAccessor temporal) {
+    private ZonedTime newTime(TemporalAccessor temporal) {
         LocalDate date = temporal.query(TemporalQueries.localDate());
         if(date != null) {
             return ZonedTime.of(newDateTime(temporal));

+ 0 - 11
assira.commons/src/test/java/net/ranides/assira/collection/lookups/AVLTreeLookupTest.java

@@ -33,17 +33,6 @@ public class AVLTreeLookupTest {
 			.run();
     }
 
-    @Test
-    public void testHead() {
-        AVLTreeLookup<TPoint> created = $map.list(1, 2, 3, 4).into(new AVLTreeLookup<>($map.comparator()));
-        System.out.println(created);
-        System.out.println(created.tailMap($map.keys().item(1).value()));
-        System.out.println(created.tailMap($map.keys().item(3).value()));
-        System.out.println(created.tailMap($map.keys().item(8).value()));
-
-
-    }
-
     @Test
     public void testConstruct() {
         assertNotNull( new AVLTreeLookup<>() );

+ 38 - 0
assira.commons/src/test/java/net/ranides/assira/time/format/DateTimeReaderTest.java

@@ -1,15 +1,18 @@
 package net.ranides.assira.time.format;
 
 import net.ranides.assira.junit.NewAssert;
+import net.ranides.assira.time.DateTimeLiteral;
 import org.junit.Test;
 
 import java.time.DateTimeException;
+import java.time.ZoneOffset;
 
 import static net.ranides.assira.time.DateTimeZones.TV;
 import static net.ranides.assira.time.format.DateTimeReader.DATE;
 import static net.ranides.assira.time.format.DateTimeReader.DATETIME;
 import static net.ranides.assira.time.format.DateTimeReader.TIME;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThrows;
 
 public class DateTimeReaderTest {
@@ -168,4 +171,39 @@ public class DateTimeReaderTest {
         assertEquals("20:45 [Tuvalu]", TIME.withTargetZone(TV).parseTime("2020-08-16T20:45:00").toString());
     }
 
+    @Test
+    public void testParseDateTimeRfc() {
+        assertEquals(
+            DateTimeLiteral.datetime(ZoneOffset.ofHours(0), 2025, 8, 28, 23, 12, 10),
+            DateTimeReader.RFC.parse("Thu, 28 Aug 2025 23:12:10 GMT")
+        );
+        assertEquals(
+            DateTimeLiteral.datetime(ZoneOffset.ofHours(2), 2025, 8, 28, 23, 12, 10),
+            DateTimeReader.RFC.parse("Thu, 28 Aug 2025 23:12:10 +0200")
+        );
+    }
+
+    @Test
+    public void testParseEcma() {
+        assertThrows(DateTimeException.class, () -> {
+            DateTimeReader.ECMA262.parse("Fri Aug 29 2025 15:55:31 GMT+0200");
+        });
+        assertEquals(
+            DateTimeLiteral.datetime(ZoneOffset.ofHours(2), 2025, 8, 29, 15, 55, 31),
+            DateTimeReader.ECMA262.parse("Fri Aug 29 2025 15:55:31 GMT+0200 (?)")
+        );
+        assertEquals(
+            DateTimeLiteral.datetime(ZoneOffset.ofHours(2), 2025, 8, 29, 15, 55, 31),
+            DateTimeReader.ECMA262.parse("Fri Aug 29 2025 15:55:31 GMT+0200 (czas środkowoeuropejski letni)")
+        );
+    }
+
+    @Test
+    public void testFallback() {
+        DateTimeReader reader = DateTimeReader.of(DateTimeReader.ISO, DateTimeReader.ECMA262, DateTimeReader.RFC);
+
+        assertNotNull(reader.parse("Fri Aug 29 2025 15:55:31 GMT+0200 (czas środkowoeuropejski letni)"));
+        assertNotNull(reader.parse("Thu, 28 Aug 2025 23:12:10 +0200"));
+        assertNotNull(reader.parse("2020-08-16T20:45:00+02:00[Poland]"));
+    }
 }

+ 1 - 1
assira.commons/src/test/java/net/ranides/assira/time/json/ZonedDateDeserializerTest.java

@@ -13,7 +13,7 @@ import java.time.ZoneId;
 
 import static org.junit.Assert.assertEquals;
 
-public class ZonedDateDeserializerTest {
+public class ZonedDateDeserializerTest1 {
 
     public static final ZoneId ZONE_MG = ZoneId.of("Indian/Antananarivo");
     public static final ZoneId ZONE_PL = ZoneId.of("Poland");

+ 1 - 3
assira.commons/src/test/java/net/ranides/assira/time/json/ZonedTimeDeserializerTest.java

@@ -15,12 +15,10 @@ import org.junit.Test;
 import java.time.LocalTime;
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
-import java.time.temporal.ChronoField;
-import java.time.temporal.ChronoUnit;
 
 import static org.junit.Assert.*;
 
-public class ZonedTimeDeserializerTest {
+public class ZonedTimeDeserializerTest2 {
 
     private final ObjectMapper mapper = JsonMapper.builder()
             .addModule(new JavaTimeModule())