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

Spa*_*ker 3 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 
Run Code Online (Sandbox Code Playgroud)

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

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

tim*_*tea 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();
    }
}
Run Code Online (Sandbox Code Playgroud)


Bas*_*que 5

算法

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

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

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

startDateOfQuarter( someDate )

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

stopDateOfPreviousQuarter( someDate )

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

Voilà,您有上一季度最后一天的日期.

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

信息

ISO 8601不承认宿舍.有些人扩展规范,使用"Q"结合一些标识符,如本维基中所述.

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

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)
Run Code Online (Sandbox Code Playgroud)

如果您绝对只想要日期而没有任何时间元素,请使用Joda-Time的LocalDate类.该类与DateTime类一样运行minusDays()方法.


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


†Joda-Time忽略了闰秒,但我的观点仍然存在.