You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@velocity.apache.org by cb...@apache.org on 2019/10/13 11:12:46 UTC

svn commit: r1868398 - in /velocity/tools/trunk/velocity-tools-generic/src: main/java/org/apache/velocity/tools/generic/ComparisonDateTool.java test/java/org/apache/velocity/tools/test/whitebox/GenericToolsTests.java

Author: cbrisson
Date: Sun Oct 13 11:12:46 2019
New Revision: 1868398

URL: http://svn.apache.org/viewvc?rev=1868398&view=rev
Log:
[tools] Add an 'exact' mode to ComparisonDateTool (which uses an actual dates/months difference rather than a global milliseconds difference)

Modified:
    velocity/tools/trunk/velocity-tools-generic/src/main/java/org/apache/velocity/tools/generic/ComparisonDateTool.java
    velocity/tools/trunk/velocity-tools-generic/src/test/java/org/apache/velocity/tools/test/whitebox/GenericToolsTests.java

Modified: velocity/tools/trunk/velocity-tools-generic/src/main/java/org/apache/velocity/tools/generic/ComparisonDateTool.java
URL: http://svn.apache.org/viewvc/velocity/tools/trunk/velocity-tools-generic/src/main/java/org/apache/velocity/tools/generic/ComparisonDateTool.java?rev=1868398&r1=1868397&r2=1868398&view=diff
==============================================================================
--- velocity/tools/trunk/velocity-tools-generic/src/main/java/org/apache/velocity/tools/generic/ComparisonDateTool.java (original)
+++ velocity/tools/trunk/velocity-tools-generic/src/main/java/org/apache/velocity/tools/generic/ComparisonDateTool.java Sun Oct 13 11:12:46 2019
@@ -19,6 +19,7 @@ package org.apache.velocity.tools.generi
  * under the License.
  */
 
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Calendar;
 import java.util.Iterator;
@@ -123,7 +124,7 @@ public class ComparisonDateTool extends
     protected static final Map TIME_UNITS;
     static
     {
-        Map units = new LinkedHashMap(8);
+        Map<String, Long> units = new LinkedHashMap<>(8);
         units.put(MILLISECOND_KEY, Long.valueOf(1));
         units.put(SECOND_KEY, Long.valueOf(MILLIS_PER_SECOND));
         units.put(MINUTE_KEY, Long.valueOf(MILLIS_PER_MINUTE));
@@ -149,10 +150,11 @@ public class ComparisonDateTool extends
     protected static final int CURRENT_TYPE = 0;
     protected static final int RELATIVE_TYPE = 1;
     protected static final int DIFF_TYPE = 2;
+    protected static final int EXACT_TYPE = 3;
 
     private String bundleName = DEFAULT_BUNDLE_NAME;
     private ResourceBundle defaultBundle;
-    private Map timeUnits = TIME_UNITS;
+    private Map<String, Long> timeUnits = TIME_UNITS;
     private int depth = 1;
 
 
@@ -178,7 +180,7 @@ public class ComparisonDateTool extends
         String[] skip = values.getStrings(SKIPPED_UNITS_KEY);
         if (skip != null)
         {
-            timeUnits = new LinkedHashMap(TIME_UNITS);
+            timeUnits = new LinkedHashMap<>(TIME_UNITS);
             for (int i=0; i < skip.length; i++)
             {
                 timeUnits.remove(skip[i]);
@@ -360,6 +362,21 @@ public class ComparisonDateTool extends
     }
 
     /**
+     * Returns a {@link Comparison} between the result of
+     * the second specified date and the first specified date.  The default
+     * rendering of that Comparison will be the decreasing sequence of bygone
+     * time units between the dates.
+     *
+     * @param now The date to use as representative of "now"
+     * @param then The secondary date
+     * @return {@link Comparison} object
+     */
+    public Comparison timespan(Object now, Object then)
+    {
+        return compare(now, then, EXACT_TYPE);
+    }
+
+    /**
      * Internal comparison method.
      * @param now The date to use as representative of "now"
      * @param then The secondary date
@@ -372,16 +389,37 @@ public class ComparisonDateTool extends
         Calendar calNow = toCalendar(now);
         if (calThen == null || calNow == null)
         {
+            getLog().warn("cannot compare date objects {} and {}", (now == null ? "null" : now.getClass().getName()), (then == null ? "null" : then.getClass().getName()));
             return null;
         }
 
-        long ms = calThen.getTimeInMillis() - calNow.getTimeInMillis();
-        return new Comparison(ms, type, this.depth, false, null);
+        return new Comparison(calNow, calThen, type, this.depth, false, null);
     }
 
+    // calendar fields for year, month, day of month, hours, minutes, seconds, milliseconds, for exact mode
+    protected static final int[] CALENDAR_FIELDS =
+    {
+        Calendar.YEAR, Calendar.MONTH, Calendar.DAY_OF_MONTH, Calendar.HOUR_OF_DAY, Calendar.MINUTE, Calendar.SECOND, Calendar.MILLISECOND
+    };
+
+    // calendar fields maxima (year and day of month maxima not populated), for exact mode
+    protected static final int[] FIELD_MAXIMA =
+    {
+        0, 12, 0, 24, 60, 60, 1000
+    };
+
+    // unit keys for exact mode
+    protected static final String[] UNIT_KEYS =
+    {
+        YEAR_KEY, MONTH_KEY, DAY_KEY, HOUR_KEY, MINUTE_KEY, SECOND_KEY, MILLISECOND_KEY
+    };
+
     public class Comparison
     {
-        private final long milliseconds;
+        private final Calendar now;
+        private final Calendar then;
+        private final long milliseconds; // cached
+        private int[] exactDifference = null; // cached
         private final int type;
         private final int maxUnitDepth;
         private final boolean abbreviate;
@@ -389,15 +427,18 @@ public class ComparisonDateTool extends
 
         /**
          * Comparison object constructor
-         * @param ms milliseconds
+         * @param now The date to use as representative of "now"
+         * @param then The secondary date
          * @param type comparison type
          * @param depth units depth
          * @param abbr whether to abbreviate units
          * @param loc locale to use
          */
-        public Comparison(long ms, int type, int depth, boolean abbr, Locale loc)
+        public Comparison(Calendar now, Calendar then, int type, int depth, boolean abbr, Locale loc)
         {
-            this.milliseconds = ms;
+            this.now = now;
+            this.then = then;
+            this.milliseconds = then.getTimeInMillis() - now.getTimeInMillis();
             this.type = type;
             this.maxUnitDepth = depth;
             this.abbreviate = abbr;
@@ -412,7 +453,7 @@ public class ComparisonDateTool extends
          */
         public Comparison abbr(boolean abbr)
         {
-            return new Comparison(this.milliseconds, this.type,
+            return new Comparison(this.now, this.then, this.type,
                                   this.maxUnitDepth, abbr, this.locale);
         }
 
@@ -424,7 +465,7 @@ public class ComparisonDateTool extends
          */
         public Comparison depth(int depth)
         {
-            return new Comparison(this.milliseconds, this.type,
+            return new Comparison(this.now, this.then, this.type,
                                   depth, this.abbreviate, this.locale);
         }
 
@@ -438,26 +479,82 @@ public class ComparisonDateTool extends
          */
         public Comparison locale(Locale loc)
         {
-            return new Comparison(this.milliseconds, this.type,
+            return new Comparison(this.now, this.then, this.type,
                                   this.maxUnitDepth, this.abbreviate, loc);
         }
 
         /**
-         * Return the approximate number of years between the dates being compared.
+         * Internal helper returning a cached map of all
+         * exact bygone time units per field.
+         * @return cached exact difference holder, as a calendar object
+         */
+        protected final int[] getExactDifference()
+        {
+            if (exactDifference == null)
+            {
+                exactDifference = new int[CALENDAR_FIELDS.length];
+
+                // make sure we go from past to future in calculation
+                Calendar from = now, to = then;
+                if (milliseconds < 0)
+                {
+                    from = then;
+                    to = now;
+                }
+
+                // fields maxima from year to millisecond
+                int maxima[] = Arrays.copyOf(FIELD_MAXIMA, FIELD_MAXIMA.length);
+                // set day of month maximum
+                Calendar tmp = (Calendar)to.clone();
+                tmp.add(Calendar.MONTH, -1);
+                maxima[2] = tmp.getActualMaximum(Calendar.DAY_OF_MONTH);
+
+                // milliseconds to months
+                int carry = 0;
+                for (int i = CALENDAR_FIELDS.length; i --> 1 ;)
+                {
+                    int start = from.get(CALENDAR_FIELDS[i]);
+                    int end = to.get(CALENDAR_FIELDS[i]);
+                    int diff = end - (start + carry);
+                    if (diff < 0)
+                    {
+                        diff += maxima[i];
+                        carry = 1;
+                    }
+                    else
+                    {
+                        carry = 0;
+                    }
+                    exactDifference[i] = diff;
+                }
+                // years
+                exactDifference[0] = to.get(Calendar.YEAR) - (from.get(Calendar.YEAR) + carry);
+            }
+            return exactDifference;
+        }
+
+        /**
+         * Return the approximate number of years between the dates being compared, or the bygone
+         * number of years in exact mode.
          * @return years
          */
         public long getYears()
         {
-            return ComparisonDateTool.toYears(this.milliseconds);
+            return type == EXACT_TYPE ?
+                getExactDifference()[0]
+                : ComparisonDateTool.toYears(this.milliseconds);
         }
 
         /**
-         * Return the approximate number of months between the dates being compared.
+         * Return the approximate number of months between the dates being compared, or the bygone
+         * number of months after the bygone number of years in exact mode.
          * @return months
          */
         public long getMonths()
         {
-            return ComparisonDateTool.toMonths(this.milliseconds);
+            return type == EXACT_TYPE ?
+                getExactDifference()[1]
+                : ComparisonDateTool.toMonths(this.milliseconds);
         }
 
         /**
@@ -466,6 +563,10 @@ public class ComparisonDateTool extends
          */
         public long getWeeks()
         {
+            if (type == EXACT_TYPE)
+            {
+                getLog().warn("Requesting weeks difference in exact mode still returns the totall relative number of weeks");
+            }
             return ComparisonDateTool.toWeeks(this.milliseconds);
         }
 
@@ -475,7 +576,9 @@ public class ComparisonDateTool extends
          */
         public long getDays()
         {
-            return ComparisonDateTool.toDays(this.milliseconds);
+            return type == EXACT_TYPE ?
+                getExactDifference()[2]
+                : ComparisonDateTool.toDays(this.milliseconds);
         }
 
         /**
@@ -484,7 +587,9 @@ public class ComparisonDateTool extends
          */
         public long getHours()
         {
-            return ComparisonDateTool.toHours(this.milliseconds);
+            return type == EXACT_TYPE ?
+                getExactDifference()[3]
+                : ComparisonDateTool.toHours(this.milliseconds);
         }
 
         /**
@@ -493,7 +598,9 @@ public class ComparisonDateTool extends
          */
         public long getMinutes()
         {
-            return ComparisonDateTool.toMinutes(this.milliseconds);
+            return type == EXACT_TYPE ?
+                getExactDifference()[4]
+                : ComparisonDateTool.toMinutes(this.milliseconds);
         }
 
         /**
@@ -502,7 +609,9 @@ public class ComparisonDateTool extends
          */
         public long getSeconds()
         {
-            return ComparisonDateTool.toSeconds(this.milliseconds);
+            return type == EXACT_TYPE ?
+                getExactDifference()[5]
+                : ComparisonDateTool.toSeconds(this.milliseconds);
         }
 
         /**
@@ -511,7 +620,9 @@ public class ComparisonDateTool extends
          */
         public long getMilliseconds()
         {
-            return this.milliseconds;
+            return type == EXACT_TYPE ?
+                getExactDifference()[6]
+                : this.milliseconds;
         }
 
         /**
@@ -536,7 +647,7 @@ public class ComparisonDateTool extends
          */
         public Comparison getDifference()
         {
-            return new Comparison(this.milliseconds, DIFF_TYPE,
+            return new Comparison(this.now, this.then, DIFF_TYPE,
                                   this.maxUnitDepth, this.abbreviate, this.locale);
         }
 
@@ -550,11 +661,25 @@ public class ComparisonDateTool extends
          */
         public Comparison getRelative()
         {
-            return new Comparison(this.milliseconds, RELATIVE_TYPE,
+            return new Comparison(this.now, this.then, RELATIVE_TYPE,
                                   this.maxUnitDepth, this.abbreviate, this.locale);
         }
 
         /**
+         * Sets this comparison to be rendered as if it where generated using
+         * the {@link ComparisonDateTool#timespan(Object now, Object then)} method.
+         * This effectively means that the comparison will render with a suffix
+         * to describe the relative position of the dates being compared (e.g. "later"
+         * or "ago").
+         * @return new Comparison object
+         */
+        public Comparison getExact()
+        {
+            return new Comparison(this.now, this.then, RELATIVE_TYPE,
+                this.maxUnitDepth, this.abbreviate, this.locale);
+        }
+
+        /**
          * This is equivalent to calling {@link #abbr(boolean abbr)} with
          * {@code true} as the argument, thus setting this comparison to be
          * rendered in abbreviated form.
@@ -575,55 +700,84 @@ public class ComparisonDateTool extends
          * "3 minutes 1 second", and
          * <code>toString(180000, 2, true, null)</code> will return
          * "3 min".
-         * @param diff milliseconds
+         * @param diff milliseconds, or, in exact mode, time unit index
          * @param maxUnitDepth maximum unit depth
          * @return string representation of the difference
          */
         protected String toString(long diff, int maxUnitDepth)
         {
-            // these cases should be handled elsewhere
-            if (diff <= 0)
-            {
-                return null;
-            }
+            long value = 0;
+            long remainder = 0;
+            String unitKey = null;
+
             // can't go any deeper than we have units
             if (maxUnitDepth > timeUnits.size())
             {
                 maxUnitDepth = timeUnits.size();
             }
 
-            long value = 0;
-            long remainder = 0;
-
-            // determine the largest unit and calculate the value and remainder
-            Iterator i = timeUnits.keySet().iterator();
-            String unitKey = (String)i.next();
-            Long unit = (Long)timeUnits.get(unitKey);
-            while (i.hasNext())
+            if (type == EXACT_TYPE)
             {
-                // get the next unit
-                String nextUnitKey = (String)i.next();
-                Long nextUnit = (Long)timeUnits.get(nextUnitKey);
-
-                // e.g. if diff < <nextUnit>
-                if (diff < nextUnit.longValue())
+                // diff is a time unit index
+                for (remainder = diff; remainder < CALENDAR_FIELDS.length; ++remainder)
                 {
-                    // then we're working with <unit>
-                    value = diff / unit.longValue();
-                    remainder = diff - (value * unit.longValue());
-                    break;
+                    long val = getExactDifference()[(int)remainder];
+                    if (value == 0)
+                    {
+                        value = val;
+                        unitKey = UNIT_KEYS[(int)remainder];
+                    }
+                    else if (val > 0)
+                    {
+                        break;
+                    }
                 }
+                remainder %= CALENDAR_FIELDS.length;
 
-                // shift to the next unit
-                unitKey = nextUnitKey;
-                unit = nextUnit;
+                // these cases should be handled elsewhere
+                if (value == 0)
+                {
+                    return null;
+                }
             }
-
-            // if it was years, then we haven't done the math yet
-            if (unitKey.equals(YEAR_KEY))
+            else
             {
-                value = diff / unit.longValue();
-                remainder = diff - (value * unit.longValue());
+                // these cases should be handled elsewhere
+                if (diff <= 0)
+                {
+                    return null;
+                }
+
+                // determine the largest unit and calculate the value and remainder
+                Iterator<String> i = timeUnits.keySet().iterator();
+                unitKey = i.next();
+                long unit = timeUnits.get(unitKey);
+                while (i.hasNext())
+                {
+                    // get the next unit
+                    String nextUnitKey = (String)i.next();
+                    long nextUnit = timeUnits.get(nextUnitKey);
+
+                    // e.g. if diff < <nextUnit>
+                    if (diff < nextUnit)
+                    {
+                        // then we're working with <unit>
+                        value = diff / unit;
+                        remainder = diff % unit;
+                        break;
+                    }
+
+                    // shift to the next unit
+                    unitKey = nextUnitKey;
+                    unit = nextUnit;
+                }
+
+                // if it was years, then we haven't done the math yet
+                if (unitKey.equals(YEAR_KEY))
+                {
+                    value = diff / unit;
+                    remainder = diff % unit;
+                }
             }
 
             // select proper pluralization
@@ -685,7 +839,7 @@ public class ComparisonDateTool extends
             }
 
             // get the base value
-            String friendly = toString(ms, depth);
+            String friendly = toString(type == EXACT_TYPE ? 0 : ms , depth);
 
             // if we only want the difference...
             if (type == DIFF_TYPE)

Modified: velocity/tools/trunk/velocity-tools-generic/src/test/java/org/apache/velocity/tools/test/whitebox/GenericToolsTests.java
URL: http://svn.apache.org/viewvc/velocity/tools/trunk/velocity-tools-generic/src/test/java/org/apache/velocity/tools/test/whitebox/GenericToolsTests.java?rev=1868398&r1=1868397&r2=1868398&view=diff
==============================================================================
--- velocity/tools/trunk/velocity-tools-generic/src/test/java/org/apache/velocity/tools/test/whitebox/GenericToolsTests.java (original)
+++ velocity/tools/trunk/velocity-tools-generic/src/test/java/org/apache/velocity/tools/test/whitebox/GenericToolsTests.java Sun Oct 13 11:12:46 2019
@@ -214,7 +214,7 @@ public class GenericToolsTests {
         assertStringEquals("Bonjour Monde!", frenchHelloWorld);
     }
 
-    public @Test void testComparisonDateTool() { /* TODO still incomplete */
+    public @Test void testComparisonDateTool() {
         ComparisonDateTool dateTool = (ComparisonDateTool)toolbox.get("date");
         assertNotNull(dateTool);
         Calendar date1 = new GregorianCalendar(2007,0,2);
@@ -227,4 +227,16 @@ public class GenericToolsTests {
         // the toolbox config says to skip months, so this should be in weeks
         assertStringEquals("6 weeks later", whenIs);
     }
+
+    public @Test void testComparisonDateTool2() {
+        ComparisonDateTool dateTool = (ComparisonDateTool)toolbox.get("date");
+        assertNotNull(dateTool);
+        Calendar date1 = new GregorianCalendar(1954,5,26);
+        Calendar date2 = new GregorianCalendar(2019,5,20);
+        /* test comparing */
+        ComparisonDateTool.Comparison whenIs = dateTool.timespan(date1, date2);
+        assertEquals(64l, whenIs.getYears());
+        assertEquals(11l, whenIs.getMonths());
+        assertEquals(25l, whenIs.getDays());
+    }
 }