Проверьте, находится ли месяц, день или год в диапазоне двух дат в строковом формате - PullRequest
2 голосов
/ 23 января 2020

Первоначально я задавал этот вопрос здесь с другим вариантом использования , но теперь я понимаю, что у меня может быть больше случаев для этой проблемы.

У меня есть фильтр, который является строкой и может принимать только 3 формата даты - YYYY, YYYY-MM, YYYY-MM-DD.

Я хочу проверить, является ли запрашиваемая строка в диапазоне двух строковых дат.

Допустим, у меня есть две строки дат, которые могут быть в любом из трех вышеуказанных форматов даты


Вариант использования 1

Дата начала: 2010-05-15 и дата окончания: 2020-05.

Все перечисленные ниже requested String находятся в диапазоне результатов :

2010, 2020, 2010-05, 2020-05, 2020-05-22, 2010-05-15, 2010-05-22, 2015-02-25

Все requested String ниже НЕ в диапазоне

2009, 2021, 2010-04, 2020-06, 2010-05-14, 2020-06-01


Вариант использования 2

Дата начала: 2010 и дата окончания: 2020-05-15

Все приведенные ниже значения находятся в диапазоне результатов :

2010, 2020, 2010-05, 2020-05, 2010-05-22, 2010-01-01, 2020-05-15, 2015-02-25

Все значения ниже НЕ в диапазоне

2009, 2021, 2020-06, 2020-05-16, 2020-06-01


Вариант использования 3

Дата начала: NULL и дата окончания: 2020-05

Все запрошенные даты, которые являются до 2020-05-31 находятся в диапазоне результатов:

Все значения после 2020-05-31 НЕ находятся в диапазоне.


Вариант использования 4

Дата начала: 2010-05-15 & дата окончания: NULL

Все запрошенные даты, которые после 2010-05-15 находятся в диапазоне результатов:

Все значения до 2010-05-15 НЕ находятся в диапазоне.


Я использую Java время, чтобы проверить, является ли дата до или после указанных дат, но в моем случае у меня есть строка в качестве даты запроса, даты начала и даты окончания, которая может быть в любом из 3 форматов даты.

Я не уверен, что это правильное решение, но я думаю, что это

Просто разбейте все даты (запрошенные, начало и конец) на числа и сравните запрошенный год с годами начала и окончания. Если месяцы в запрашиваемой дате доступны, а также доступны в начале и конце, сравните, находится ли месяц в диапазоне, и то же самое для дат.

Может кто-нибудь помочь решить эту проблему?

Ответы [ 6 ]

3 голосов
/ 23 января 2020

Поскольку ваши даты представлены в формате yyyy-MM-dd, вы должны быть в состоянии выполнить сравнение строк, убедившись, что вы сравниваете только строки одинаковой длины (чтобы позаботиться о ваших правилах сравнения дат различной длины):

private static boolean isDateInRange(String startDate, String endDate, String date) {
    return (null == startDate || compareDateString(startDate, date) <= 0)
            && (null == endDate || compareDateString(date, endDate) <= 0);
}

private static int compareDateString(String date1, String date2) {
    if (date1.length() == date2.length())
        return date1.compareTo(date2);

    int length = Math.min(date1.length(), date2.length());
    return date1.substring(0, length).compareTo(date2.substring(0, length));
}

И вы можете проверить следующим образом:

String startDate = "2010-05-15";
String endDate = "2020-05";
String date = "2020-06";

boolean inRange = isDateInRange(startDate, endDate, date);
3 голосов
/ 23 января 2020

Преобразование всех строковых входов в LocalDate объекты

Хватит думать в строках. Строки предназначены только для пользовательского интерфейса и для сериализации значений в хранилище. Когда ваше приложение работает, вы должны выполнять свою логику c с использованием объектов, java .time объектов, LocalDate в конечном итоге.

Преобразование всех входных данных в LocalDate.

Проверьте длину ввода.

if( input.length() = 4 ) {…}

Если длина ввода составляет 4 символа, проанализируйте как Year. Ловушка для исключения, генерируемого в случае, если неправильный ввод проходит ваш фильтр. Получив год, наберите LocalDate, позвонив по номеру atDay.

try{
    Year year = Year.parse( "2020" ) ; 
     LocalDate ld = year.atDay( 1 ) ;
} catch ( DateTimeParseException e ) {
    …
}

. Если введено семь символов, проанализируйте как YearMonth. Форма, которая получает LocalDate с помощью метода atDay.

try{
    YearMonth YearMonth = YearMonth.parse( "2020-05" ) ; 
     LocalDate ld = yearMonth.atDay( 1 ) ;
} catch ( DateTimeParseException e ) {
    …
}

Если ввод 10 символов, проанализируйте как LocalDate.

try{
    LocalDate localDate = LocalDate.parse( "2020-05-23" ) ; 
} catch ( DateTimeParseException e ) {
    …
}

Как только вы получите все свои LocalDate объектов на месте, сравните. При определении промежутков времени последовательно используйте подход Half-Open. Начало включительно , а окончание эксклюзив .

Совет: «не раньше» - это более короткий способ задать вопрос «равно или позже».

( ! target.isBefore( start ) ) && target.isBefore( stop ) 
2 голосов
/ 23 января 2020

Answer by Basil Bourque хорош и лаконичен, поэтому его следует использовать, однако он работает только потому, что форматы даты представляют собой простые тексты ISO-8601. Если форматы разные, вам нужно их проанализировать, прежде чем сравнивать. Вот решение, которое анализирует строки, что, конечно, делает логику c более сложной.

Я бы начал с создания вспомогательного класса:

/**
 * Class for storing a {@code Temporal} of varying precision,
 * {@code Year}, {@code YearMonth}, or {@code LocalDate}.
 */
class DynamicTemporal {

    private enum Precision {
        YEAR {
            @Override
            protected int compare(Temporal a, Temporal b) {
                return widen(a).compareTo(widen(b));
            }
            private Year widen(Temporal value) {
                return Year.from(value);
            }
        },
        MONTH {
            @Override
            protected int compare(Temporal a, Temporal b) {
                return widen(a).compareTo(widen(b));
            }
            private YearMonth widen(Temporal value) {
                return YearMonth.from(value);
            }
        },
        DAY {
            @Override
            protected int compare(Temporal a, Temporal b) {
                return widen(a).compareTo(widen(b));
            }
            private LocalDate widen(Temporal value) {
                return (LocalDate) value;
            }
        };
        protected abstract int compare(Temporal a, Temporal b);
    }

    private final Temporal value;
    private final Precision precision;

    public static DynamicTemporal parse(String text) {
        if (text == null || text.equals("NULL"))
            return null;
        if (text.length() >= 10)
            return new DynamicTemporal(LocalDate.parse(text), Precision.DAY);
        if (text.length() >= 7)
            return new DynamicTemporal(YearMonth.parse(text), Precision.MONTH);
        return new DynamicTemporal(Year.parse(text), Precision.YEAR);
    }

    private DynamicTemporal(Temporal value, Precision precision) {
        this.value = value;
        this.precision = precision;
    }

    public int compareTo(DynamicTemporal other) {
        Precision effectivePrecision = (this.precision.compareTo(other.precision) <= 0 ? this.precision : other.precision);
        return effectivePrecision.compare(this.value, other.value);
    }
}

Теперь вы можете легко реализовать isDateInRange() метод:

private static boolean isDateInRange(String start, String end, String date) {
    DynamicTemporal dateTemporal = DynamicTemporal.parse(date);
    DynamicTemporal startTemporal = DynamicTemporal.parse(start);
    DynamicTemporal endTemporal = DynamicTemporal.parse(end);
    if (startTemporal != null && dateTemporal.compareTo(startTemporal) < 0)
        return false; // date is before start
    if (endTemporal != null && dateTemporal.compareTo(endTemporal) > 0)
        return false; // date is after end
    return true;
}

Тест (из вопроса)

public static void main(String[] args) {
    test("2010-05-15", "2020-05", true, "2010", "2020", "2010-05", "2020-05", "2020-05-22", "2010-05-15", "2010-05-22", "2015-02-25");
    test("2010-05-15", "2020-05", false, "2009", "2021", "2010-04", "2020-06", "2010-05-14", "2020-06-01");

    test("2010", "2020-05-15", true, "2010", "2020", "2010-05", "2020-05", "2010-05-22", "2010-01-01", "2020-05-15", "2015-02-25");
    test("2010", "2020-05-15", false, "2009", "2021", "2020-06", "2020-05-16", "2020-06-01");

    test("NULL", "2020-05", true, "2020-05-31", "2020-05", "2020", "2020-05-30", "2020-04", "2019");
    test("NULL", "2020-05", false, "2020-06-01", "2020-06", "2021", "2020-06-02", "2020-07", "2022");

    test("2010-05-15", "NULL", true, "2010-05-15", "2010-05", "2010", "2010-05-16", "2010-06", "2011");
    test("2010-05-15", "NULL", false, "2010-05-14", "2010-04", "2009", "2010-05-13", "2010-03", "2008");
}
private static void test(String start, String end, boolean expected, String... dates) {
    for (String date : dates) {
        boolean actual = isDateInRange(start, end, date);
        System.out.printf("isDateInRange(%-10s, %-10s, %-10s) = %-5s   %s%n",
                          start, end, date, actual, (actual == expected ? "OK" : "FAILED!"));
    }
}

Выход

isDateInRange(2010-05-15, 2020-05   , 2010      ) = true    OK
isDateInRange(2010-05-15, 2020-05   , 2020      ) = true    OK
isDateInRange(2010-05-15, 2020-05   , 2010-05   ) = true    OK
isDateInRange(2010-05-15, 2020-05   , 2020-05   ) = true    OK
isDateInRange(2010-05-15, 2020-05   , 2020-05-22) = true    OK
isDateInRange(2010-05-15, 2020-05   , 2010-05-15) = true    OK
isDateInRange(2010-05-15, 2020-05   , 2010-05-22) = true    OK
isDateInRange(2010-05-15, 2020-05   , 2015-02-25) = true    OK
isDateInRange(2010-05-15, 2020-05   , 2009      ) = false   OK
isDateInRange(2010-05-15, 2020-05   , 2021      ) = false   OK
isDateInRange(2010-05-15, 2020-05   , 2010-04   ) = false   OK
isDateInRange(2010-05-15, 2020-05   , 2020-06   ) = false   OK
isDateInRange(2010-05-15, 2020-05   , 2010-05-14) = false   OK
isDateInRange(2010-05-15, 2020-05   , 2020-06-01) = false   OK
isDateInRange(2010      , 2020-05-15, 2010      ) = true    OK
isDateInRange(2010      , 2020-05-15, 2020      ) = true    OK
isDateInRange(2010      , 2020-05-15, 2010-05   ) = true    OK
isDateInRange(2010      , 2020-05-15, 2020-05   ) = true    OK
isDateInRange(2010      , 2020-05-15, 2010-05-22) = true    OK
isDateInRange(2010      , 2020-05-15, 2010-01-01) = true    OK
isDateInRange(2010      , 2020-05-15, 2020-05-15) = true    OK
isDateInRange(2010      , 2020-05-15, 2015-02-25) = true    OK
isDateInRange(2010      , 2020-05-15, 2009      ) = false   OK
isDateInRange(2010      , 2020-05-15, 2021      ) = false   OK
isDateInRange(2010      , 2020-05-15, 2020-06   ) = false   OK
isDateInRange(2010      , 2020-05-15, 2020-05-16) = false   OK
isDateInRange(2010      , 2020-05-15, 2020-06-01) = false   OK
isDateInRange(NULL      , 2020-05   , 2020-05-31) = true    OK
isDateInRange(NULL      , 2020-05   , 2020-05   ) = true    OK
isDateInRange(NULL      , 2020-05   , 2020      ) = true    OK
isDateInRange(NULL      , 2020-05   , 2020-05-30) = true    OK
isDateInRange(NULL      , 2020-05   , 2020-04   ) = true    OK
isDateInRange(NULL      , 2020-05   , 2019      ) = true    OK
isDateInRange(NULL      , 2020-05   , 2020-06-01) = false   OK
isDateInRange(NULL      , 2020-05   , 2020-06   ) = false   OK
isDateInRange(NULL      , 2020-05   , 2021      ) = false   OK
isDateInRange(NULL      , 2020-05   , 2020-06-02) = false   OK
isDateInRange(NULL      , 2020-05   , 2020-07   ) = false   OK
isDateInRange(NULL      , 2020-05   , 2022      ) = false   OK
isDateInRange(2010-05-15, NULL      , 2010-05-15) = true    OK
isDateInRange(2010-05-15, NULL      , 2010-05   ) = true    OK
isDateInRange(2010-05-15, NULL      , 2010      ) = true    OK
isDateInRange(2010-05-15, NULL      , 2010-05-16) = true    OK
isDateInRange(2010-05-15, NULL      , 2010-06   ) = true    OK
isDateInRange(2010-05-15, NULL      , 2011      ) = true    OK
isDateInRange(2010-05-15, NULL      , 2010-05-14) = false   OK
isDateInRange(2010-05-15, NULL      , 2010-04   ) = false   OK
isDateInRange(2010-05-15, NULL      , 2009      ) = false   OK
isDateInRange(2010-05-15, NULL      , 2010-05-13) = false   OK
isDateInRange(2010-05-15, NULL      , 2010-03   ) = false   OK
isDateInRange(2010-05-15, NULL      , 2008      ) = false   OK
1 голос
/ 24 января 2020

Я не уверен, является ли это верным решением, но я думаю об этом

Просто разбейте все даты (запрошенные, начало и конец) на числа и сравните запрошенный год с годами. в начале и в конце, если месяцы в запрошенной дате доступны, а также доступны в начале и конце, то сравните, находится ли месяц в диапазоне, и то же самое для дат.

Да, ваш подход действителен , Классы из java .time не предлагают идеальной поддержки для этого, но с низкоуровневым интерфейсом TemporalAccessor есть путь через:

class IsInRangeWithMissingFields {

    @Test
    void useCase1() {
        assertTrue(isInRange("2010-05-15", "2020-05", "2010"));
        assertTrue(isInRange("2010-05-15", "2020-05", "2020"));
        assertTrue(isInRange("2010-05-15", "2020-05", "2010-05"));
        assertTrue(isInRange("2010-05-15", "2020-05", "2020-05"));
        assertTrue(isInRange("2010-05-15", "2020-05", "2020-05-22"));
        assertTrue(isInRange("2010-05-15", "2020-05", "2010-05-15"));
        assertTrue(isInRange("2010-05-15", "2020-05", "2010-05-22"));
        assertTrue(isInRange("2010-05-15", "2020-05", "2015-02-25"));
        assertFalse(isInRange("2010-05-15", "2020-05", "2009"));
        assertFalse(isInRange("2010-05-15", "2020-05", "2021"));
        assertFalse(isInRange("2010-05-15", "2020-05", "2010-04"));
        assertFalse(isInRange("2010-05-15", "2020-05", "2020-06"));
        assertFalse(isInRange("2010-05-15", "2020-05", "2010-05-14"));
        assertFalse(isInRange("2010-05-15", "2020-05", "2020-06-01"));
    }

    @Test
    void useCase2() {
        assertTrue(isInRange("2010", "2020-05-15", "2010"));
        assertTrue(isInRange("2010", "2020-05-15", "2020"));
        assertTrue(isInRange("2010", "2020-05-15", "2010-05"));
        assertTrue(isInRange("2010", "2020-05-15", "2020-05"));
        assertTrue(isInRange("2010", "2020-05-15", "2010-05-22"));
        assertTrue(isInRange("2010", "2020-05-15", "2010-01-01"));
        assertTrue(isInRange("2010", "2020-05-15", "2020-05-15"));
        assertTrue(isInRange("2010", "2020-05-15", "2015-02-25"));
        assertFalse(isInRange("2010", "2020-05-15", "2009"));
        assertFalse(isInRange("2010", "2020-05-15", "2021"));
        assertFalse(isInRange("2010", "2020-05-15", "2020-06"));
        assertFalse(isInRange("2010", "2020-05-15", "2020-05-16"));
        assertFalse(isInRange("2010", "2020-05-15", "2020-06-01"));
    }

    @Test
    void useCase3() {
        assertTrue(isInRange(null, "2020-05", "2020-05-31"));
        assertFalse(isInRange(null, "2020-05", "2020-06-01"));
    }

    @Test
    void useCase4() {
        assertTrue(isInRange("2010-05-15", null, "2010-05-15"));
        assertFalse(isInRange("2010-05-15", null, "2010-05-14"));
    }

    private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu[-MM[-dd]]");

    public static boolean isInRange(String startStr, String endStr, String requestStr) {
        TemporalAccessor request = formatter.parse(requestStr);

        // is request before start?
        if (startStr != null) {
            TemporalAccessor start = formatter.parse(startStr);
            if (request.get(ChronoField.YEAR)
                    < start.get(ChronoField.YEAR)) {
                return false;
            }
            if (request.get(ChronoField.YEAR)
                    == start.get(ChronoField.YEAR)) {
                if (request.isSupported(ChronoField.MONTH_OF_YEAR)
                        && start.isSupported(ChronoField.MONTH_OF_YEAR)) {
                    if (request.get(ChronoField.MONTH_OF_YEAR)
                            < start.get(ChronoField.MONTH_OF_YEAR)) {
                        return false;
                    }
                    if (request.get(ChronoField.MONTH_OF_YEAR)
                            == start.get(ChronoField.MONTH_OF_YEAR)) {
                        if (request.isSupported(ChronoField.DAY_OF_MONTH)
                                && start.isSupported(ChronoField.DAY_OF_MONTH)) {
                            if (request.get(ChronoField.DAY_OF_MONTH)
                                    < start.get(ChronoField.DAY_OF_MONTH)) {
                                return false;
                            }
                        }
                    }
                }
            }
        }

        // is request after end?
        if (endStr != null) {
            TemporalAccessor end = formatter.parse(endStr);
            if (request.get(ChronoField.YEAR) > end.get(ChronoField.YEAR)) {
                return false;
            }
            if (request.get(ChronoField.YEAR) == end.get(ChronoField.YEAR)) {
                if (request.isSupported(ChronoField.MONTH_OF_YEAR)
                        && end.isSupported(ChronoField.MONTH_OF_YEAR)) {
                    if (request.get(ChronoField.MONTH_OF_YEAR)
                            > end.get(ChronoField.MONTH_OF_YEAR)) {
                        return false;
                    }
                    if (request.get(ChronoField.MONTH_OF_YEAR)
                            == end.get(ChronoField.MONTH_OF_YEAR)) {
                        if (request.isSupported(ChronoField.DAY_OF_MONTH)
                                && end.isSupported(ChronoField.DAY_OF_MONTH)) {
                            if (request.get(ChronoField.DAY_OF_MONTH)
                                    > end.get(ChronoField.DAY_OF_MONTH)) {
                                return false;
                            }
                        }
                    }
                }
            }
        }

        return true;
    }

}

Этот модульный тест запускается зеленым цветом мой компьютер.

1 голос
/ 23 января 2020

Конвертировать все в диапазоны дат. Представляет диапазон дат как полуоткрытый интервал от начальной даты включительно до конечной даты эксклюзивно . И представлять даты и даты как два LocalDate объекта.

Например, из вашего варианта использования 1

Дата начала: 2010-05-15 & дата окончания: 2020-05

В качестве диапазона дат он становится с 2010-05-15 включительно до 2020-06-01 эксклюзивным .

Чтобы проверить, находится ли 2010 год в диапазоне, конвертировать 2010 в диапазон с 2010-01-01 по 2011-01-01 эксклюзив . Теперь 2010 год находится в диапазоне, если и только если два диапазона перекрываются. Они делают, если и только первая дата от даты предшествует второй до даты и , вторая секунда от даты предшествует первой дате. Используйте LocalDate.isBefore(). Нам нужно «строго раньше», и этот метод дает нам именно это. Поскольку 2010-05-15 до 2011-01-01 и 2010-01-01 до 2020-06-01, диапазоны перекрываются, поэтому мы заключаем, что 2010 год находится в диапазоне.

Другой пример из случая 1, чтобы проверить, находится ли 2010-05-14 в том же диапазоне, преобразует его в диапазон с 2010-05-14 по 2010-05-15 (эксклюзивно) . Проверьте, если 2010-05-15 до 2010-05-15 и 2010-05-14 до 2020-06-01. Уже первая половина неверна, поэтому мы заключаем, что дата не находится в диапазоне.

Примечание по реализации: я мог бы использовать этот форматер для анализа всех ваших строк:

    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu[-MM[-dd]]");

You может использовать метод parseBest этого форматера для получения LocalDate, YearMonth или Year из любой из упомянутых вами строк. В каждом случае вы можете конвертировать оттуда.

Ссылка: Определите, перекрываются ли два диапазона дат

0 голосов
/ 23 января 2020
    Date min, max;   // assume these are set to something
    Date d;          // the date in question

   return d.after(min) && d.before(max);
...