4

我正在研究一些会计逻辑。逻辑是在一个季度的第一个月发生的会计必须有一个在上一个季度年末的起息日。我知道我可以将月份表示为一个时期。但我不知道如何表示一个季度的循环周期。

我可以使用 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。

4

2 回答 2

8

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

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();
    }
}
于 2014-06-08T10:55:32.850 回答
5

算法

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

或者,在我自己的命名约定中,我使用startandstop来表示包容性而不是排斥性边界,或者以其他方式命名,stopDateOfPreviousQuarter( someDate ).

我会用两种方法做到这一点。

startDateOfQuarter( someDate )

  1. 通过调用monthOfYear()提取过去日期的月份。
  2. 确定那个月是哪个季度(测试在 1-3、4-6、7-9、9-12 之间)。
  3. 获取该季度的开始日期(如下面的代码所示,将 year & month & dayOfMonth 传递给构造函数)。

stopDateOfPreviousQuarter( someDate )

  1. 调用“startDateOfQuarter”方法,传递相关日期。
  2. 在返回的开始日期,调用 Joda-Time 方法minusDays(1)

瞧,您有上一季度最后一天的日期。

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

信息

ISO 8601不承认季度。有些人扩展了规范来这样做,使用“Q”结合一些标识符,如本 wiki中所述。

如2011 年的讨论中所述, Joda-Time 2不支持季度。

ISO 8601 准确定义了 52 或 53个带编号的周。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 忽略闰秒,但我的观点仍然成立。

于 2013-11-12T08:05:43.047 回答