【问题标题】:Date range in date range日期范围内的日期范围
【发布时间】:2014-01-07 18:37:47
【问题描述】:

实际上,这项任务对我来说似乎很容易,但我有点卡住了,希望能得到一些提示:D

我有一些活动的开始和结束时间 - 我想创建一个包含日历周的表格。

因此我写了一个方法来检查一个事件是否在本周之内,像这样为它着色:

private boolean inWeek(Date date, Entry pe) {
    return ((pe.getStartsAt().after(Util.firstDayOfWeek(date)) || pe.getStartsAt().equals(Util.firstDayOfWeek(date)))
     && (pe.getEndsAt().before(Util.lastDayOfWeek(date)) || pe.getEndsAt().equals(Util.lastDayOfWeek(date))));
}

如果事件只持续一周,这个案例还可以。但是如果活动在本周之前开始,或者在本周之后结束,甚至持续数周呢?

它变得非常复杂,我目前的解决方案是这样的:

private boolean inWeek(Date date, Entry pe) {

    return  (  pe.getStartsAt().after(Util.firstDayOfWeek(date)) &&  pe.getEndsAt().after(Util.firstDayOfWeek(date)) && pe.getEndsAt().before(Util.lastDayOfWeek(date))    ) 
    ||      (  pe.getStartsAt().before(Util.lastDayOfWeek(date)) &&  pe.getStartsAt().after(Util.firstDayOfWeek(date)) &&  pe.getEndsAt().after(Util.lastDayOfWeek(date))  )
    ||      (  pe.getStartsAt().after(Util.firstDayOfWeek(date)) &&  pe.getEndsAt().before(Util.lastDayOfWeek(date))  )
    ||      (  pe.getStartsAt().before(Util.firstDayOfWeek(date)) && pe.getEndsAt().after(Util.lastDayOfWeek(date)) );

}

但在某些单元格中仍然没有显示正确的颜色。有人对我有任何提示吗?

(...不建议joda times ^^)

【问题讨论】:

  • 日期参数应该意味着您正在传递什么?
  • 这些只是简单的 java 日期对象。每个“条目”都有一个日期对象作为开始日期和结束日期,“Util”类返回输入日期的日期对象以传递星期一和星期日(在我的情况下是每个单元格)。
  • 你为什么不想使用joda?
  • 这是我使用日期对象的唯一代码部分。你不同意一个完整的库对于一个简单的逻辑方法来说是多余的吗?
  • @AudreyDelany 不,添加Joda-Time 并不过分。我对任何新项目做的第一件事就是添加 Joda-Time。例如,我在开发和测试期间立即使用它来注销命令行。出于您的目的,Joda-Time 包含直接针对您的问题的类。至于“一种简单的方法”,处理日期时间从来没有简单!

标签: java date range between


【解决方案1】:

乔达时间

Joda-Time 2.3 库使这项工作变得更加容易。它包括一个带有overlap 方法的Interval 类。

类似代码和讨论请见the answer by MadProgrammer

要点

Interval 类很聪明,考虑到区间的开头是包含的,而结尾是排除的。您应该根据等于或大于起点但小于终点的逻辑进行比较。为什么?因为新一天的前一刻是无限可分的。您可能会想,“好吧,java.util.Date 和 Joda-Time 解析为毫秒,所以我将使用 .999”。但是,当您将代码移植到 Java 8 的新 java.time.* 类时,您会感到惊讶,其中时间解析为纳秒。

为了支持这种比较,请注意目标周是通过调用withTimeAtStartOfDay 定义的。使用此方法而不是尝试通过设置零时间元素来创建午夜。这种方法很聪明,可以处理夏令时和其他在某些时区的某些日子可能没有 00:00:00 午夜时间的异常情况。

指定时区而不是依赖默认值。其他答案中的所有代码都无法解决时区问题。这意味着他们使用JVM 的默认时区。因此,此应用程序在部署到设置为其他时区的其他机器时会得到不同的结果。使用proper time zone names,切勿使用三字母代码。

如果您的应用适用于跨时区的人和地点,您应该考虑将目标周基于UTC/GMT(无时区偏移)。请注意,这就是 StackOverflow 在每天跟踪您的活动时所做的。 “日”由 UTC/GMT 定义。

这些点证明了为什么您不应该使用自己的日期时间逻辑。改用有能力的图书馆。在 Java 中,这意味着 Joda-Time 或 Java 8 中的新 java.time.* 类(受 Joda-Time 启发)。

ISO 周

顺便说一下,ISO 8601 standard defines a "week" 是准确的。各国越来越多的公司和行业正在采用该标准。遵循该标准可能会证明是有用的。 Joda-Time DateTime 实例知道其 ISO 周数。致电myDateTime.weekOfWeakYear().get()

示例代码

DateTimeZone timeZone = DateTimeZone.forID( "Europe/Berlin" );

Interval weekInQuestion = new Interval( new DateTime( 2014, 1, 20, 3, 4, 5, timeZone ).withTimeAtStartOfDay(), new DateTime( 2014, 1, 27, 3, 4, 5, timeZone ).withTimeAtStartOfDay() );

Interval i1 = new Interval( new DateTime( 2014, 1, 2, 3, 4, 5, timeZone ), new DateTime( 2014, 1, 3, 23, 4, 5, timeZone ) );
Interval i2 = new Interval( new DateTime( 2014, 1, 24, 3, 4, 5, timeZone ), new DateTime( 2014, 1, 26, 23, 59, 59, timeZone ) );
Interval i3 = new Interval( new DateTime( 2014, 1, 6, 3, 4, 5, timeZone ), new DateTime( 2014, 1, 30, 3, 4, 5, timeZone ) );

boolean i1HitsWeekInQuestion = i1.overlaps( weekInQuestion );
boolean i2HitsWeekInQuestion = i2.overlaps( weekInQuestion );
boolean i3HitsWeekInQuestion = i3.overlaps( weekInQuestion );

转储到控制台...

System.out.println( "weekInQuestion: " + weekInQuestion );
System.out.println( "i1: " + i1 + " hits week: " + i1HitsWeekInQuestion );
System.out.println( "i2: " + i2 + " hits week: " + i2HitsWeekInQuestion );
System.out.println( "i3: " + i3 + " hits week: " + i3HitsWeekInQuestion );

运行时……

weekInQuestion: 2014-01-20T00:00:00.000+01:00/2014-01-27T00:00:00.000+01:00
i1: 2014-01-02T03:04:05.000+01:00/2014-01-03T23:04:05.000+01:00 hits week: false
i2: 2014-01-24T03:04:05.000+01:00/2014-01-26T23:59:59.000+01:00 hits week: true
i3: 2014-01-06T03:04:05.000+01:00/2014-01-30T03:04:05.000+01:00 hits week: true

【讨论】:

    【解决方案2】:

    我花了相当多的时间在……嗯,时间……我可以告诉你,我更愿意让其他人为我做这项工作。

    为此,如果您愿意尝试一下,我会看看JodaTime

    基本上,这个例子的作用是创建一系列Intervals。一个是“期间”,或一年中的一周(从星期一开始,到星期日结束)。

    一个Interval是重叠区间,跨越“周期”的前一周和后一周,另一个是“周期”内的单日Interval

    import org.joda.time.Interval;
    import org.joda.time.MutableDateTime;
    import org.joda.time.Period;
    import org.joda.time.format.DateTimeFormat;
    import org.joda.time.format.DateTimeFormatter;
    import org.joda.time.format.DateTimeFormatterBuilder;
    import org.joda.time.format.DateTimeParser;
    
    public class TestTimeInterval {
    
        public static void main(String[] args) {
    
            DateTimeFormatter formatter = new DateTimeFormatterBuilder().
                            appendDayOfMonth(2).appendLiteral(" ").
                            appendDayOfWeekText().appendLiteral(" ").
                            appendMonthOfYearText().appendLiteral(" ").
                            appendYear(2, 2).
                            toFormatter();
    
            // Last week
            MutableDateTime targetStart = MutableDateTime.now();
            targetStart.setDayOfWeek(1);
            targetStart.addDays(-6);
    
            // Next week
            MutableDateTime targetEnd = MutableDateTime.now();
            targetEnd.setDayOfWeek(7);
            targetEnd.addDays(7);
    
            System.out.println("Target range = " + formatter.print(targetStart) + " to " + formatter.print(targetEnd));
            Interval targetInterval = new Interval(targetStart, targetEnd);
    
            // This week
            MutableDateTime start = MutableDateTime.now();
            start.setDayOfWeek(1);
    
            MutableDateTime end = MutableDateTime.now();
            end.setDayOfWeek(7);
    
            Interval interval = new Interval(start, end);
    
            System.out.println("Interval range = " + formatter.print(start) + " to " + formatter.print(end));
            System.out.println("Contains interval = " + targetInterval.contains(interval));
    
            // Last week
            targetStart = DateTime.now();
    
            // Next week
            targetEnd = DateTime.now();
    
            System.out.println("Target range = " + formatter.print(targetStart) + " to " + formatter.print(targetEnd));
            targetInterval = new Interval(targetStart, targetEnd);
            System.out.println("Contains interval = " + interval.contains(targetInterval));
        }
    
    }
    

    哪些输出...

    Target range = 10 Tuesday December 2013 to 29 Sunday December 2013
    Period range = 16 Monday December 2013 to 22 Sunday December 2013
    Contains period = true
    Target range = 19 Thursday December 2013 to 19 Thursday December 2013
    Contains period = true
    

    你只需要通过两种方式检查周期间隔即可。

    1. 检查“期间”是否在提供的Interval
    2. 如果提供的Interval 在“期间”内...

    例如...

     if (period.contains(interval) || interval.contains(period)) {
         // Match...
     }
    

    现在,还有很多其他的事情需要考虑,比如,如果时间对 Intervals 来说不重要,你会想要将时间归零(开始时间段应该是午夜/早上,而应该在午夜晚上结束),这样你就可以最大化捕获区域

    更新以更好地利用 JodaTime 库

    @BasilBourque 能够突出显示原始示例的一些问题,我已经对其进行了相应的更新和测试。谢谢@BasilBourque

    虽然与原版类似,但更好地使用 JodaTime

    public static void newWay() {
    
        DateTimeFormatter formatter = new DateTimeFormatterBuilder().
                appendDayOfMonth(2).appendLiteral(" ").
                appendDayOfWeekText().appendLiteral(" ").
                appendMonthOfYearText().appendLiteral(" ").
                appendYear(2, 2).
                toFormatter();
    
        // Last week
        DateTime targetStart = DateTime.now(DateTimeZone.getDefault()).
                withDayOfWeek(DateTimeConstants.MONDAY).
                minusDays(6);
        //MutableDateTime targetStart = MutableDateTime.now();
        //targetStart.setDayOfWeek(1);
        //targetStart.addDays(-6);
    
        // Next week
        DateTime targetEnd = DateTime.now(DateTimeZone.getDefault()).
                withDayOfWeek(DateTimeConstants.SUNDAY).
                plusDays(7);
        //MutableDateTime targetEnd = MutableDateTime.now();
        //targetEnd.setDayOfWeek(7);
        //targetEnd.addDays(7);
    
        System.out.println("Target range = " + formatter.print(targetStart) + " to " + formatter.print(targetEnd));
        Interval targetInterval = new Interval(targetStart, targetEnd);
    
        // This week
        DateTime start = DateTime.now(DateTimeZone.getDefault()).
                withDayOfWeek(DateTimeConstants.MONDAY);
        //MutableDateTime start = MutableDateTime.now();
        //start.setDayOfWeek(1);
    
        DateTime end = DateTime.now(DateTimeZone.getDefault()).
                withDayOfWeek(DateTimeConstants.SUNDAY);
        //MutableDateTime end = MutableDateTime.now();
        //end.setDayOfWeek(7);
    
        Interval interval = new Interval(start, end);
    
        System.out.println("Period range = " + formatter.print(start) + " to " + formatter.print(end));
    
        System.out.println("Contains period = " + targetInterval.contains(interval));
    
        // Last week
        targetStart = DateTime.now();
    
        // Next week
        targetEnd = DateTime.now();
    
        System.out.println("Target range = " + formatter.print(targetStart) + " to " + formatter.print(targetEnd));
        targetInterval = new Interval(targetStart, targetEnd);
        System.out.println("Contains period = " + interval.contains(targetInterval));
    
    }
    

    【讨论】:

    • 时间归零的方法是调用withTimeAtStartOfDaycontains 对你所说的这个“捕获区域”很聪明,因为(参见文档)duration intervals are inclusive of the start instant and exclusive of the end
    • 无需使用可变版本的 Joda-Time 类。您只是不必要地向线程安全问题敞开心扉。设计者希望我们通常使用不可变类。相关类返回新的 DateTime (等)实例,因此您仍然可以使用调用链编写 "fluent" code
    • withTimeAtStartOfDay 方法较新。它取代了 Joda-Time 人不再推荐的旧的“午夜”相关方法。
    • 一周前获取:DateTime dateTimeLastWeek = new DateTime( DateTimeZone.forID( "Europe/Berlin" ).minusWeeks( 1 );,或根据需要致电.minusDays( 6 );。如果您想要一天的第一时间,请在末尾添加对 withTimeAtStartOfDay 的呼叫。请注意,我指定了时区,这是问题和所有答案中缺少的关键元素。
    • 调用 withDayOfWeek 以获取特定日期的新 DateTime 实例。 DateTimeConstants 为一周中的每一天定义了一些英文常量,例如DateTimeConstants.MONDAY。是的,Joda-Time 有很多好东西。在探索探索的过程中,我不断发现更多。
    【解决方案3】:

    一开始我会让代码更具可读性

        private boolean inWeek(Date date, Entry pe) {
    
        Date eventStart = pe.getStartsAt();
        Date eventEnd = pe.getEndsAt();
        Date firstDay = Util.firstDayOfWeek(date);
        Date lastDay = Util.lastDayOfWeek(date);
    
        boolean isInweek =  (eventStart.after(firstDay) && 
                                eventEnd.after(firstDay) && 
                                eventEnd.before(lastDay))
                || 
                            (eventStart.before(lastDay) && 
                                eventStart.after(firstDay) && 
                                eventEnd.after(lastDay))
                || 
                            (eventStart.after(firstDay) && 
                                eventEnd.before(lastDay))
                || 
                            (eventStart.before(firstDay) && 
                                eventEnd.after(lastDay));
    
        return isInweek;
    
    }
    

    这可能会使问题更加明显。

    您在第二个区块中有 eventEnd.after(lastDay)。我觉得不对。

    第一个块是多余的,因为第三个块完全相同。

    这还不够吗?

        private boolean inWeek(Date date, Entry pe) {
    
        Date eventStart = pe.getStartsAt();
        Date eventEnd = pe.getEndsAt();
        Date firstDay = Util.firstDayOfWeek(date);
        Date lastDay = Util.lastDayOfWeek(date);
    
        boolean isInweek =  
                            (eventStart.after(firstDay) && //event is INSIDE of the week.
                                eventEnd.before(lastDay))
                || 
                            (eventStart.before(firstDay) && //event is OUTSIDE of the week 
                                eventEnd.after(lastDay));
    
        return isInweek;
    
    }
    

    【讨论】:

    • 在您的代码中,事件必须在特定周开始和结束,或者在特定周之前开始并在其之后结束。如果它在 firstDay 和 lastDay 之间开始,但在特定周之后的一周结束,或者它从前一周开始并在 firstDay 和 lastDay 之间结束。
    【解决方案4】:

    我尝试了另一种解决方案 - 似乎工作正常,但我不太确定

    private boolean inWeek(Date date, Entry pe) {
            return  ((pe.getEndsAt().after(Util.firstDayOfWeek(date)) || pe.getEndsAt().equals(Util.firstDayOfWeek(date)) )
                &&  (pe.getStartsAt().before(Util.lastDayOfWeek(date)) || pe.getStartsAt().equals(Util.lastDayOfWeek(date)) ));
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-03-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多