【问题标题】:How should the "first month of each quarteryear" be represented using joda-time?应该如何使用 joda-time 表示“每个季度的第一个月”?
【发布时间】:2014-06-08 18:55:32
【问题描述】:

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

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

好的,这里是 cmets 中要求的更多解释性示例: 会计逻辑是关于你必须申请的一种养老基金。您在接下来的一年,即 2013 年 1 月 1 日申请 2012 年的养老金。它将在 2013 年 4 月 15 日授予您。由于 4 月是季度年的第一个月,因此授予将有其起息日设置为 3 月 31 日。在一个季度内入账的所有养老金在该季度结束后的 1 个月零 5 天后支付。因此,即使您已在 4 月获得养老金,您仍将在 5 月 5 日获得支付。否则,您必须等到 9 月 5 日才能付款。

但我真的不知道这个例子应该有什么帮助。这个问题实际上是关于使用 joda-time 类对“每个季度的第一个月”进行建模,甚至是实现一些 joda-time 的 API。

【问题讨论】:

  • 你能用实际的月份和年份来解释你的问题吗?
  • 例如,我的意思是,假设当前日期是 2013-12-11 ,您想要的结果是去年一月的日期?像这样??

标签: java jodatime


【解决方案1】:

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

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();
    }
}

【讨论】:

    【解决方案2】:

    算法

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

    或者,在我自己的命名约定中,我使用startstop 来表示包容性而不是排斥性边界,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,而不是试图直接获取结束点。获取一个小时、一天、一周、一个月或一个季度的开始。这避免了闰日、闰秒†、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,但我的观点仍然成立。

    【讨论】:

    • 我使用LocalDate 表示此会计逻辑中的时间点。至少在我目前正在工作的部分中,时间是没有用的。但是感谢您的建议。
    • @SpaceTrucker 我的回答中的想法和代码适用于LocalDate 以及DateTime。 “LocalDate”类有一个 minusDays() 方法,“DateTime”也是如此。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多