【问题标题】:Check if month or day or year is in range of two dates in string format检查月份或日期或年份是否在字符串格式的两个日期范围内
【发布时间】:2020-01-23 17:44:14
【问题描述】:

I originally asked this question here with a different use case 但现在我意识到我可以有更多的案例来解决这个问题。

我有一个字符串过滤器,只能接受 3 种日期格式 - YYYYYYYY-MMYYYY-MM-DD

我想检查请求的字符串是否在两个字符串值日期的范围内。

假设我有两个日期字符串,可以是上述 3 种日期格式中的任何一种


用例 1

开始日期:2010-05-15 和结束日期:2020-05

以下所有requested String 都是在范围内的结果

201020202010-052020-052020-05-222010-05-152010-05-222015-02-25

下面的所有requested String不在范围内

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


用例 2

开始日期:2010 & 结束日期:2020-05-15

以下所有值都是在范围内的结果

201020202010-052020-052010-05-222010-01-012020-05-152015-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 个日期中的任何一个-格式。

我不确定这是否是一个有效的解决方案,但这就是我的想法

只需将所有日期(请求、开始和结束)分解为数字,并将请求的年份与开始和结束的年份进行比较,如果请求日期中的月份可用并且在开始和结束时也可用,则比较月份是否为在范围内,日期相同。

有人可以帮忙解决这个问题吗?

【问题讨论】:

  • 你有这方面的代码吗?

标签: java date datetime java-time


【解决方案1】:

将所有字符串输入转换为LocalDate对象

停止思考字符串。这些字符串仅用于用户界面和将值序列化以进行存储。当您的应用运行时,您应该使用对象、java.time 对象、LocalDate 最终执行您的逻辑。

将所有输入转换为LocalDate

检查输入的长度。

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

如果输入长度为 4 个字符,则解析为 Year。捕获异常,以防错误输入通过您的过滤器。从这一年开始,拨打atDay 获得LocalDate

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

如果输入是七个字符,则解析为YearMonth。通过atDay 方法获取LocalDate 的表单。

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 对象,就可以进行比较。在定义时间跨度时,始终使用半开放式方法。开头是inclusive,而结尾是exclusive

提示:“不早于”是询问“等于或晚于”的更短方式。

( ! target.isBefore( start ) ) && target.isBefore( stop ) 

【讨论】:

  • “一旦你准备好所有的 LocalDate 对象,再进行比较” 那是行不通的。用例 1 包括在测试 "2010" 是否在 "2010-05-15""2020-05" 范围内时获取 true。由于您只是使用atDay(1) 转换为日期,因此您将比较"2010-01-01" 是否在"2010-05-15""2020-05-01" 的范围内,这将导致不正确 false 结果。
【解决方案2】:

由于您的日期采用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);

【讨论】:

  • 除了不测试startDateendDate 的值"NULL",这是一个很好的解决方案。
  • @Andreas 谢谢,我完全忘记了这一点。会编辑。我认为 OP 的意思是 null,而不是 "NULL",虽然
  • 谢谢!对于这种情况,一个出色而简单的解决方案。我真的不想关心计算年、年月、本地日期并使代码冗长,即使这可能是正确的方法,因为我们正在处理日期。但我喜欢你的代码的简单性,它也很完美。再次感谢。
  • @AMagic 有一个不错的helper class,做年、年、月和localDates 真的没那么糟糕。
  • 顺便说一句:我使用来自my answer 的测试代码测试了这个答案的代码,并且它成功了,除了"NULL" 测试用例。调整此代码以检查 "NULL" 字符串当然很容易,所以这是一个很好的答案。
【解决方案3】:

Answer by Basil Bourque 简洁明了,因此您应该使用它,但它仅适用于日期格式是简单的 ISO-8601 文本。如果格式不同,您需要先解析它们,然后才能比较它们。这里有一个解析字符串的方案,这当然让逻辑更复杂了。

我将从创建一个帮助类开始:

/**
 * 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

【讨论】:

  • 谢谢,安德烈亚斯。非常令人印象深刻的代码。我之前提到过“即使这可能是正确的方法,因为我们正在处理日期”,所以我想这是一个不成熟的陈述。我认为通过使用您的解决方案,如果需要,它将允许其他模块使用帮助程序类。我会将您的答案标记为解决方案。
  • @AMagic 只要你也对 Basil 的答案投赞成票,因为它是一个非常有用的替代方案。
【解决方案4】:

将所有内容转换为日期范围。将日期范围表示为从包含开始日期到结束日期的半开间隔不包含。并将 from 和 to 日期表示为两个 LocalDate 对象。

例如,从您的用例 1 开始

开始日期:2010-05-15 & 结束日期:2020-05

作为一个日期范围,这变为从 2010 年 5 月 15 日(含)到 2020 年 6 月 1 日(不含)

要测试 2010 是否在该范围内,请将 2010 转换为 从 2010-01-01 到 2011-01-01 独有的范围。现在,当且仅当两个范围重叠时,2010 年才在范围内。当且仅当且仅第一个起始日期在第二个到日期之前并且第二个起始日期在第一个到日期之前,他们才会这样做。使用LocalDate.isBefore()。我们需要“严格之前”,而这种方法正是为我们提供了这一点。由于 2010-05-15 在 2011-01-01 之前并且 2010-01-01 在 2020-06-01 之前,范围重叠,因此我们得出结论,2010 年在范围内。 p>

案例 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]]");

您可以使用此格式化程序的parseBest 方法从您提到的任何字符串中获取LocalDateYearMonthYear。在每种情况下,您都可以从那里转换。

链接: Determine Whether Two Date Ranges Overlap

【讨论】:

    【解决方案5】:

    我不确定这是否是一个有效的解决方案,但这就是我 思考

    只需将所有日期(请求、开始和结束)分解为数字和 将请求的年份与开始和结束的年份进行比较,如果月份在 请求的日期可用,也可在开始和结束时使用 然后比较月份是否在范围内,日期是否相同。

    是的,您的方法是有效的。 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;
        }
    
    }
    

    这个单元测试在我的电脑上运行绿色条。

    【讨论】:

      【解决方案6】:
          Date min, max;   // assume these are set to something
          Date d;          // the date in question
      
         return d.after(min) && d.before(max);
      

      【讨论】:

      • 所有三个日期都是字符串类型,格式为YYYYYYYY-MMYYYY-MM-DD。我不能真正将这些解析成最新的一致。 :(
      • 如何测试输入 d = 2010, min = 2010-05-15, max = 2020-05 的真实性?
      • d 优先级更高,如果 d 只是一年,则 min=2010-05-15 包含在其中并在范围内接受。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-22
      相关资源
      最近更新 更多