如何使用joda-time代表“每个季度的第一个月”?

时间:2013-11-12 07:19:18

标签: java jodatime

我正在研究一些会计逻辑。逻辑是,在一个季度的第一个月发生的会计必须具有在上一个季度末的价值日期。我知道我可以将月份表示为一个时期。但我不知道如何代表四分之一年的经常性时期。

我可以使用joda-time类进行计算,这些类可以在上一季度结束时给我一个值日期。但是,我想将“每个季度的第一个月”表示为我在计算中使用的单个值。

那么在joda-time中是否有一些API非常适合代表这种经常性的时期?

一些示例日期是:

date of invoce | value date  
2013-01-01     | 2012-12-31  
2013-01-31     | 2012-12-31  
2013-02-01     | 2013-02-01  
2013-03-31     | 2013-03-31  
2013-04-01     | 2013-03-31 

好的,这里是评论中要求的更具说明性的例子: 会计逻辑是关于您必须申请的一种养老基金。您在接下来的一年,即2013年1月1日申请2012年的退休金。它将在2013年4月15日授予您。自4月是本季度的第一个月,授予将具有其价值日期定于3月31日。所有在一个季度内计入的养老金都在该季度结束后的1个月和5天内支付。因此,即使您在4月份获得了养老金,您仍然可以在5月5日获得养老金。否则你将不得不等到9月5日之前付款。

但我真的不知道这个例子应该如何帮助。这个问题实际上是关于使用joda-time课程建模“每个季度的每个第一个月”,甚至是实施一些joda-time的API。

2 个答案:

答案 0 :(得分:6)

我也必须实现这一点。这是一个乍一看似乎没问题的实现。

public class QuarterPeriods {

    public static LocalDate quarterStartFor(LocalDate date) {
        return date.withDayOfMonth(1).withMonthOfYear((((date.getMonthOfYear() - 1) / 3) * 3) + 1);
    }

    public static LocalDate quarterEndFor(LocalDate date) {
        return quarterStartFor(date).plusMonths(3).minusDays(1);
    }
}

public class QuarterPeriodsTest {

    @Test
    public void startOfQuarter() throws Exception {
        assertThat(quarterStartFor(StubDates.dateOf("2011/02/02")), equalTo(StubDates.dateOf("2011/01/01")));
        assertThat(quarterStartFor(StubDates.dateOf("2011/01/01")), equalTo(StubDates.dateOf("2011/01/01")));
        assertThat(quarterStartFor(StubDates.dateOf("2011/02/02")), equalTo(StubDates.dateOf("2011/01/01")));
        assertThat(quarterStartFor(StubDates.dateOf("2011/04/01")), equalTo(StubDates.dateOf("2011/04/01")));
        assertThat(quarterStartFor(StubDates.dateOf("2011/07/01")), equalTo(StubDates.dateOf("2011/07/01")));
        assertThat(quarterStartFor(StubDates.dateOf("2011/12/19")), equalTo(StubDates.dateOf("2011/10/01")));
    }

    @Test
    public void endOfQuarter() throws Exception {
        assertThat(quarterEndFor(StubDates.dateOf("2011/02/02")), equalTo(StubDates.dateOf("2011/03/31")));
        assertThat(quarterEndFor(StubDates.dateOf("2011/01/01")), equalTo(StubDates.dateOf("2011/03/31")));
        assertThat(quarterEndFor(StubDates.dateOf("2011/02/02")), equalTo(StubDates.dateOf("2011/03/31")));
        assertThat(quarterEndFor(StubDates.dateOf("2011/04/01")), equalTo(StubDates.dateOf("2011/06/30")));
        assertThat(quarterEndFor(StubDates.dateOf("2011/07/01")), equalTo(StubDates.dateOf("2011/09/30")));
        assertThat(quarterEndFor(StubDates.dateOf("2011/12/19")), equalTo(StubDates.dateOf("2011/12/31")));
    }
}

public class StubDates {

    public static LocalDate dateOf(String date) {
        return DateTimeFormat.forPattern("yyyy/MM/dd").withZone(DateTimeZone.UTC).parseDateTime(date).toLocalDate();
    }
}

答案 1 :(得分:5)

算法

您需要编写一个例程,在该例程中传递日期并返回代表上一季度最后一天的日期。实际上,这听起来像是方法的一个好名字,以及对问题的更好总结:endingDateOfPreviousQuarter( someDate )

或者在我自己的命名约定中命名,我使用startstop来表示包容性而非排他性边界,stopDateOfPreviousQuarter( someDate )

我会用一对方法做到这一点。

startDateOfQuarter( someDate )

  1. 通过调用monthOfYear()提取传递日期的月份。
  2. 确定哪个季度是该月份(测试时间为1-3岁,4-6岁,7-9岁,9-12岁)。
  3. 获取该季度的开始日期(如下面的代码所示,将年和月以及dayOfMonth传递给构造函数)。
  4. stopDateOfPreviousQuarter( someDate )

    1. 调用“startDateOfQuarter”方法,传递相关日期。
    2. 在返回的开始日期,调用Joda-Time方法minusDays(1)
    3. Voilà,您有上一季度最后一天的日期。

      这里的主要想法是,通常最好找到时间元素的开头,然后使用minus,而不是尝试直接获取结束点。获取一小时,一天,一周,一个月或一个季度的开头。这避免了闰日,闰秒†,Daylight Saving Time(DST)的问题,记住哪些月有30对31天的错误,以及分数秒的问题,分辨率不同使得难以确定最终的小时或白天。此外,至少在我的经验中,关注时期的开始可以清晰地思考日期时间。

      信息

      ISO 8601无法识别宿舍。有些人扩展了规范,使用“Q”和一些标识符,如this wiki中所述。

      Joda-Time 2不支持季度,如2011年this discussion中所述。

      ISO 8601精确定义了52或53 numbered weeks的定义。 Joda-Time支持这个概念,由weekOfWeekYear表示。一些企业通过1到52/53范围的子集来定义他们的季度。

      或者您可以在一年的第3个月,第6个月,第9个月和第12个月结束时定义您的宿舍。 Joda-Time具有DateTime类的构造函数,可用于指定月份编号。请注意使用withTimeAtStartOfDay()方法让Joda-Time完成当天第一时刻的工作,因为并非所有时区的所有日子都有午夜。

      org.joda.time.DateTimeZone parisDateTimeZone = org.joda.time.DateTimeZone.forID( "Europe/Paris" );
      org.joda.time.DateTime q1Start =  new org.joda.time.DateTime(2013, 1, 1, 0, 0, parisDateTimeZone ).withTimeAtStartOfDay();
      org.joda.time.DateTime q2Start =  new org.joda.time.DateTime(2013, 4, 1, 0, 0, parisDateTimeZone ).withTimeAtStartOfDay();
      
      System.out.println( "Q1 begins in Paris FR: " + q1Start );
      System.out.println( "Q2 begins in Paris FR: " + q2Start );
      
      // When querying a database or comparing items in a collection to find Q1 data, 
      // Look for: (GreaterThanOrEqualTo q1Start) AND (LessThan q2Start)
      

      如果您绝对只想要日期而没有任何时间元素,请使用Joda-Time的LocalDate课程。该课程采用minusDays()方法,就像DateTime类一样。


      顺便说一下,考虑一下你是处理简单日期(没有时间)还是日期时间。例如,您可能会认为发票仅使用简单的日期,但实际上它们通常在收到时使用时钟机器盖章,其中包括可能因法律和审计原因需要记录的时间。此外,通常数据库将日期值存储为基于UTC的日期时间(没有时区偏移)。


      †Joda-Time忽略了leap-seconds,但我的观点仍然存在。