Commons Lang
  1. Commons Lang
  2. LANG-379

Calculating A date fragment in any time-unit

    Details

    • Type: New Feature New Feature
    • Status: Closed
    • Priority: Minor Minor
    • Resolution: Fixed
    • Affects Version/s: 2.3
    • Fix Version/s: 2.4
    • Component/s: lang.time.*
    • Labels:
      None

      Description

      These DateUtils-features can make it possible to calculate a date-part in any time-unit. For example: the number of minutes of this year, the number of seconds of today, etc.

      I've started with some coding, and if there's enough interest we can make it more solid.

      public static long getFragmentInSeconds(Date date, int fragment)

      { return getFragment(date, fragment, Calendar.SECOND); }

      public static long getFragmentInMinutes(Date date, int fragment)

      { return getFragment(date, fragment, Calendar.MINUTE); }

      public static long getFragmentInHours(Date date, int fragment)

      { return getFragment(date, fragment, Calendar.HOUR_OF_DAY); }

      public static long getFragmentInDays(Date date, int fragment)

      { return getFragment(date, fragment, Calendar.DAY_OF_YEAR); }

      public static long getFragmentInSeconds(Calendar calendar, int fragment)

      { return getFragment(calendar, fragment, Calendar.SECOND); }

      public static long getFragmentInMinutes(Calendar calendar, int fragment)

      { return getFragment(calendar, fragment, Calendar.MINUTE); }

      public static long getFragmentInHours(Calendar calendar, int fragment)

      { return getFragment(calendar, fragment, Calendar.HOUR_OF_DAY); }

      public static long getFragmentInDays(Calendar calendar, int fragment)

      { return getFragment(calendar, fragment, Calendar.DAY_OF_YEAR); }

      private static long getFragment(Date date, int fragment, int unit)

      { Calendar calendar = Calendar.getInstance(); calendar.setTime(date); return getFragment(calendar, fragment, unit); }

      private static long getFragment(Calendar calendar, int fragment, int unit) {
      long millisPerUnit = getMillisPerFragment(unit);
      long result = 0;
      switch (fragment)

      { case Calendar.YEAR: result += (calendar.get(Calendar.DAY_OF_YEAR) * MILLIS_PER_DAY) / millisPerUnit; case Calendar.MONTH: result += (calendar.get(Calendar.DAY_OF_MONTH) * MILLIS_PER_DAY) / millisPerUnit; case Calendar.DAY_OF_YEAR: case Calendar.DATE: result += (calendar.get(Calendar.HOUR_OF_DAY) * MILLIS_PER_HOUR) / millisPerUnit; case Calendar.HOUR_OF_DAY: result += (calendar.get(Calendar.MINUTE) * MILLIS_PER_MINUTE) / millisPerUnit; case Calendar.MINUTE: result += (calendar.get(Calendar.SECOND) * MILLIS_PER_SECOND) / millisPerUnit; case Calendar.SECOND: result += (calendar.get(Calendar.MILLISECOND) * 1) / millisPerUnit; }

      return result;
      }

      private static long getMillisPerFragment(int fragment) {
      long result = Long.MAX_VALUE;
      switch (fragment)

      { case Calendar.DAY_OF_YEAR: case Calendar.DATE: result = MILLIS_PER_DAY; break; case Calendar.HOUR_OF_DAY: result = MILLIS_PER_HOUR; break; case Calendar.MINUTE: result = MILLIS_PER_MINUTE; break; case Calendar.SECOND: result = MILLIS_PER_SECOND; break; case Calendar.MILLISECOND: result = 1; break; }

      return result;
      }

      1. LANG-379.patch
        45 kB
        Henri Yandell
      2. DateUtilsFragmentTest.java
        19 kB
        Robert Scholte
      3. DateUtils.patch
        18 kB
        Robert Scholte
      4. DateUtils_2.patch
        24 kB
        Robert Scholte

        Activity

        Hide
        Robert Scholte added a comment -

        Henri,

        thanks alot

        Show
        Robert Scholte added a comment - Henri, thanks alot
        Hide
        Henri Yandell added a comment -

        svn ci -m "Applying my patch from LANG-379, based on Robert Scholte's patch adding the 'getFragment' family of methods" src

        Sending src/java/org/apache/commons/lang/time/DateUtils.java
        Adding src/test/org/apache/commons/lang/time/DateUtilsFragmentTest.java
        Sending src/test/org/apache/commons/lang/time/TimeTestSuite.java
        Transmitting file data ...
        Committed revision 617360.

        Show
        Henri Yandell added a comment - svn ci -m "Applying my patch from LANG-379 , based on Robert Scholte's patch adding the 'getFragment' family of methods" src Sending src/java/org/apache/commons/lang/time/DateUtils.java Adding src/test/org/apache/commons/lang/time/DateUtilsFragmentTest.java Sending src/test/org/apache/commons/lang/time/TimeTestSuite.java Transmitting file data ... Committed revision 617360.
        Hide
        Henri Yandell added a comment -

        Applying a slightly modified version of Robert's patches. The new test is hooked to the Ant build, tabs are turned to spaces, a bit of English changing and some formatting.

        Will apply to svn tomorrow unless anyone has changes they'd like to see.

        Show
        Henri Yandell added a comment - Applying a slightly modified version of Robert's patches. The new test is hooked to the Ant build, tabs are turned to spaces, a bit of English changing and some formatting. Will apply to svn tomorrow unless anyone has changes they'd like to see.
        Hide
        Robert Scholte added a comment -

        I've added some examples per method in their javadoc-block. Hopefully we're now close enough...

        Show
        Robert Scholte added a comment - I've added some examples per method in their javadoc-block. Hopefully we're now close enough...
        Hide
        Henri Yandell added a comment -

        Looks good - still needs code examples in the javadoc.

        I think this is going to be something that people understand easily when they see the code, and are confused by when they just read the description.

        If possible, just upload the patch for the DateUtils file as I've reworked the Test file a bit already.

        Show
        Henri Yandell added a comment - Looks good - still needs code examples in the javadoc. I think this is going to be something that people understand easily when they see the code, and are confused by when they just read the description. If possible, just upload the patch for the DateUtils file as I've reworked the Test file a bit already.
        Hide
        Robert Scholte added a comment - - edited

        Complete path for DateUtils containing all fragment-methods

        Most important changes
        a lot of javadoc containing description of usage
        added getFragmentInMilliseconds() ( How could I've forgotten this one...)
        removed Calendar.WEEK_OF_* as valid fragment, since this is a weird way of usage

        Show
        Robert Scholte added a comment - - edited Complete path for DateUtils containing all fragment-methods Most important changes a lot of javadoc containing description of usage added getFragmentInMilliseconds() ( How could I've forgotten this one...) removed Calendar.WEEK_OF_* as valid fragment, since this is a weird way of usage
        Hide
        Robert Scholte added a comment -

        separate junit-test for all DateUtils' fragmentmethods

        Show
        Robert Scholte added a comment - separate junit-test for all DateUtils' fragmentmethods
        Hide
        Robert Scholte added a comment -

        Maybe the problem of LANG-400 isn't clear enough for me, but it looks like you're searching for something like

        cal1 = DateUtils.round(cal1, Calendar.DATE);
        cal2 = DateUtils.round(cal2, Calendar.DATE);

        returning 1 of the following statements...
        cal1.after(cal2);
        cal1.before(cal2);

        if this doens't match with the problem try to give an example.
        I agree, both have to do with some kind of date-transformation. But it seems to me LANG-400 requires ignoring fields bottom-up, where this issue ignores field top-down.

        Show
        Robert Scholte added a comment - Maybe the problem of LANG-400 isn't clear enough for me, but it looks like you're searching for something like cal1 = DateUtils.round(cal1, Calendar.DATE); cal2 = DateUtils.round(cal2, Calendar.DATE); returning 1 of the following statements... cal1.after(cal2); cal1.before(cal2); if this doens't match with the problem try to give an example. I agree, both have to do with some kind of date-transformation. But it seems to me LANG-400 requires ignoring fields bottom-up, where this issue ignores field top-down.
        Hide
        Paul Benedict added a comment -

        Robert, can you slug out LANG-400 with these enhancements? Sounds very similar.

        Show
        Paul Benedict added a comment - Robert, can you slug out LANG-400 with these enhancements? Sounds very similar.
        Hide
        Robert Scholte added a comment -

        I've already started working on your requests. There are enough tests to write, so it'll take some time to complete proper tests. Is there some deadline or timeframe within it should be submitted with the next release in mind?

        Show
        Robert Scholte added a comment - I've already started working on your requests. There are enough tests to write, so it'll take some time to complete proper tests. Is there some deadline or timeframe within it should be submitted with the next release in mind?
        Hide
        Henri Yandell added a comment -

        Sounds good to me, I'm +1 for inclusion.

        Could you resubmit the Test patch and grant to the ASF [assuming you intended to do that; we can't apply the patch if it's not granted].

        Could you resubmit the fix patch with some examples.

        Otherwise, I think it's worth adding; as you said, it's a complement to round().

        Show
        Henri Yandell added a comment - Sounds good to me, I'm +1 for inclusion. Could you resubmit the Test patch and grant to the ASF [assuming you intended to do that; we can't apply the patch if it's not granted] . Could you resubmit the fix patch with some examples. Otherwise, I think it's worth adding; as you said, it's a complement to round().
        Hide
        Robert Scholte added a comment - - edited

        Examples can be provided

        And for the "Why"-part: I've come with a couple of reasons. One of the applications I've worked with used events over some period every day. The way this is stored was a startdate, an enddate plus the starttime and endtime in seconds. I was looking for a DateUtils.round, but then the other way around, by ingoring year, months, etc. Since this was part of the core, I saw developers reinventing the proper calculation, often resulting in the wrong solution the first time. So I introduced these methods.
        With this in mind I thought it could be interesting to make the unit variable. And after that the unit.
        You can think of a reverse version counting down the days till the next year or month.

        As I mentioned: The most interesting reason I think for including this, is because it's the opposite of DateUtils.round
        And since Calendar.get() always the value compared to it's parent returns (seconds of minute, hour of day, and so on) there's extra (solid) calculation required to get the right result

        Show
        Robert Scholte added a comment - - edited Examples can be provided And for the "Why"-part: I've come with a couple of reasons. One of the applications I've worked with used events over some period every day. The way this is stored was a startdate, an enddate plus the starttime and endtime in seconds. I was looking for a DateUtils.round, but then the other way around, by ingoring year, months, etc. Since this was part of the core, I saw developers reinventing the proper calculation, often resulting in the wrong solution the first time. So I introduced these methods. With this in mind I thought it could be interesting to make the unit variable. And after that the unit. You can think of a reverse version counting down the days till the next year or month. As I mentioned: The most interesting reason I think for including this, is because it's the opposite of DateUtils.round And since Calendar.get() always the value compared to it's parent returns (seconds of minute, hour of day, and so on) there's extra (solid) calculation required to get the right result
        Hide
        Henri Yandell added a comment -

        Valid point on Calendar/Date - the API already does lots of Calendar.getInstance().setTime. So I'll retract that.

        On ERA/YEAR etc, I get it now. I had unit and fragment confused; fragment is the user-passed in one.

        ===-=

        Second round of thoughts (we've been burnt on time based code in Lang before and so have a strong justify it attitude):

        Code examples in the Javadoc are definitely going to be needed - looking at the javadoc it isn't clear to me what the methods do exactly. I had to walk through the code before I properly understood (and with the drop-through on the switch statement, it involves a little bit of time to notice that's not a standard idiom). So - examples please

        I'm also not sure "Why?" Why would I want the number of seconds of this year? Or the number of minutes this month?

        Show
        Henri Yandell added a comment - Valid point on Calendar/Date - the API already does lots of Calendar.getInstance().setTime. So I'll retract that. On ERA/YEAR etc, I get it now. I had unit and fragment confused; fragment is the user-passed in one. = = =-= Second round of thoughts (we've been burnt on time based code in Lang before and so have a strong justify it attitude): Code examples in the Javadoc are definitely going to be needed - looking at the javadoc it isn't clear to me what the methods do exactly. I had to walk through the code before I properly understood (and with the drop-through on the switch statement, it involves a little bit of time to notice that's not a standard idiom). So - examples please I'm also not sure "Why?" Why would I want the number of seconds of this year? Or the number of minutes this month?
        Hide
        Robert Scholte added a comment -

        I guess Calendar.ERA is a bit too much, because it would mean starting from year 0 (whatever that may be)
        There are two (switch)-blocks required: the first one to bring the fragment back to days (millis per day is the largest available value for calculations, since millis per month is not constant). In the second block we can keep calculating, if the unit is smaller then a day. And we can check if the fragment was valid
        I agree on working with Calendars above Dates, but since most DateUtils methods use Date and if a Calendar is used in a method there's always a Date-equivalent method, I thought it would be nice do the same here.

        Show
        Robert Scholte added a comment - I guess Calendar.ERA is a bit too much, because it would mean starting from year 0 (whatever that may be) There are two (switch)-blocks required: the first one to bring the fragment back to days (millis per day is the largest available value for calculations, since millis per month is not constant). In the second block we can keep calculating, if the unit is smaller then a day. And we can check if the fragment was valid I agree on working with Calendars above Dates, but since most DateUtils methods use Date and if a Calendar is used in a method there's always a Date-equivalent method, I thought it would be nice do the same here.
        Hide
        Henri Yandell added a comment -

        Tempted to only allow Calendar in the API and not Date. Keeping time zone issues in the user's code.

        Not sure what the value is in dealing with:

        + case Calendar.ERA:
        + case Calendar.YEAR:
        + case Calendar.MONTH:
        + case Calendar.WEEK_OF_YEAR:
        + case Calendar.WEEK_OF_MONTH:

        given that this is a private method.

        Show
        Henri Yandell added a comment - Tempted to only allow Calendar in the API and not Date. Keeping time zone issues in the user's code. Not sure what the value is in dealing with: + case Calendar.ERA: + case Calendar.YEAR: + case Calendar.MONTH: + case Calendar.WEEK_OF_YEAR: + case Calendar.WEEK_OF_MONTH: given that this is a private method.
        Hide
        Robert Scholte added a comment -

        As suggested by Paul Benedict, a patch which includes throwing an IllegalArgumentException for both date/calendar is null and unsupported fragments

        Show
        Robert Scholte added a comment - As suggested by Paul Benedict, a patch which includes throwing an IllegalArgumentException for both date/calendar is null and unsupported fragments
        Hide
        Robert Scholte added a comment - - edited

        There are still some small issues to solve. One of them is proper exception handling. This includes UnsupportedFragmentException (or other logic Exception) and IllegalArgumentException if date is null. Another exception must be caught when the fragment is smaller then the unit (or should it return 0?) Like I said: still some enhancements required here.

        Show
        Robert Scholte added a comment - - edited There are still some small issues to solve. One of them is proper exception handling. This includes UnsupportedFragmentException (or other logic Exception) and IllegalArgumentException if date is null. Another exception must be caught when the fragment is smaller then the unit (or should it return 0?) Like I said: still some enhancements required here.
        Hide
        Paul Benedict added a comment -

        Should the switch statements should throw an exception on the default case? It is an assertion error to pass an unsupported fragment.

        Show
        Paul Benedict added a comment - Should the switch statements should throw an exception on the default case? It is an assertion error to pass an unsupported fragment.
        Hide
        Robert Scholte added a comment -

        Some tests

        Show
        Robert Scholte added a comment - Some tests

          People

          • Assignee:
            Unassigned
            Reporter:
            Robert Scholte
          • Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:

              Development