You are viewing a plain text version of this content. The canonical link for it is here.
Posted to derby-commits@db.apache.org by ka...@apache.org on 2010/07/23 09:57:25 UTC
svn commit: r967000 - in /db/derby/code/trunk/java:
engine/org/apache/derby/iapi/util/CheapDateFormatter.java
testing/org/apache/derbyTesting/unitTests/junit/CheapDateFormatterTest.java
testing/org/apache/derbyTesting/unitTests/junit/_Suite.java
Author: kahatlen
Date: Fri Jul 23 07:57:25 2010
New Revision: 967000
URL: http://svn.apache.org/viewvc?rev=967000&view=rev
Log:
DERBY-4752: CheapDateFormatter returns incorrect and invalid date strings
Use java.util.Calendar to get the calculations right.
Added:
db/derby/code/trunk/java/testing/org/apache/derbyTesting/unitTests/junit/CheapDateFormatterTest.java (with props)
Modified:
db/derby/code/trunk/java/engine/org/apache/derby/iapi/util/CheapDateFormatter.java
db/derby/code/trunk/java/testing/org/apache/derbyTesting/unitTests/junit/_Suite.java
Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/util/CheapDateFormatter.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/util/CheapDateFormatter.java?rev=967000&r1=966999&r2=967000&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/util/CheapDateFormatter.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/util/CheapDateFormatter.java Fri Jul 23 07:57:25 2010
@@ -21,29 +21,22 @@
package org.apache.derby.iapi.util;
+import java.util.Calendar;
+import java.util.Locale;
+import java.util.TimeZone;
+
/**
* This class contains static methods for formatting dates into Strings.
* It can be used where standard Date formatting is judged to be too
* expensive.
*/
public class CheapDateFormatter {
- static final long SECONDS = 1000L;
- static final long MINUTES = SECONDS * 60L;
- static final long HOURS = MINUTES * 60L;
- static final long DAYS = HOURS * 24L;
- static final long NORMAL_YEAR = DAYS * 365L;
- static final long LEAP_YEAR = NORMAL_YEAR + DAYS;
- static final long FOURYEARS = (NORMAL_YEAR * 3L) + LEAP_YEAR;
- static final long END_OF_FIRST_YEAR = NORMAL_YEAR;
- static final long END_OF_SECOND_YEAR = END_OF_FIRST_YEAR + LEAP_YEAR;
- static final long END_OF_THIRD_YEAR = END_OF_SECOND_YEAR + NORMAL_YEAR;
- static final int[] DAYS_IN_MONTH = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
- static final int FEBRUARY = 1;
+ private static final TimeZone GMT = TimeZone.getTimeZone("GMT");
/**
* This method formats the current date into a String. The input is
* a long representing the number of milliseconds since Jan. 1, 1970.
- * The output is a String in the form yyyy/mm/dd hh:mm:ss.ddd GMT.
+ * The output is a String in the form yyyy-mm-dd hh:mm:ss.ddd GMT.
*
* The purpose of this class is to format date strings without paying
* the price of instantiating ResourceBundles and Locales, which the
@@ -57,97 +50,21 @@ public class CheapDateFormatter {
*
* @param time The current time in milliseconds since Jan. 1, 1970
*
- * @return The date formatted as yyyy/mm/dd hh:mm:ss.ddd GMT.
+ * @return The date formatted as yyyy-mm-dd hh:mm:ss.ddd GMT.
*/
public static String formatDate(long time) {
- // Assume not a leap year until we know otherwise
- boolean leapYear = false;
-
- // How many four year periods since Jan. 1, 1970?
- long year = ((time / FOURYEARS) * 4L);
-
- // How much time is left over after the four-year periods?
- long leftover = time % FOURYEARS;
- time -= (year / 4L) * FOURYEARS;
-
- year += 1970L;
-
- // Does time extend past end of first year in four-year period?
- if (leftover >= END_OF_FIRST_YEAR) {
- year++;
- time -= NORMAL_YEAR;
- }
-
- // Does time extend past end of second year in four-year period?
- if (leftover >= END_OF_SECOND_YEAR) {
- year++;
- time -= NORMAL_YEAR;
- }
-
- // Does time extend past end of third year in four-year period?
- if (leftover >= END_OF_THIRD_YEAR) {
- year++;
- time -= LEAP_YEAR;
- }
-
- // It's a leap year if divisible by 4, unless divisible by 100,
- // unless divisible by 400.
- if ((year % 4L) == 0) {
- if ((year % 100L) == 0) {
- if ((year % 400L) == 0) {
- leapYear = true;
- }
- }
- leapYear = true;
- }
-
- // What day of the year is this, starting at 1?
- long days = (time / DAYS) + 1;
-
- // What month is this, starting at 1?
- int month = 1;
- for (int i = 0; i < DAYS_IN_MONTH.length; i++) {
- int daysInMonth;
-
- if (leapYear && (i == FEBRUARY)) {
- // February has 29 days in a leap year
- daysInMonth = 29;
- } else {
- // Get number of days in next month
- daysInMonth = DAYS_IN_MONTH[i];
- }
-
- // Is date after the month we are looking at?
- if (days > daysInMonth) {
- // Count number of months
- month++;
-
- // Subtract number of days in month
- days -= daysInMonth;
- } else {
- // Don't bother to look any more - the date is within
- // the current month.
- break;
- }
- }
-
- // How much time is left after days are accounted for?
- time %= DAYS;
-
- long hours = time / HOURS;
-
- // How much time is left after hours are accounted for?
- time %= HOURS;
-
- long minutes = time / MINUTES;
-
- // How much time is left after minutes are accounted for?
- time %= MINUTES;
-
- long seconds = time / SECONDS;
-
- // How much time is left after seconds are accounted for?
- time %= SECONDS;
+ // Get a GMT calendar with a well-known locale to help us calculate
+ // the components of the date.
+ Calendar cal = Calendar.getInstance(GMT, Locale.US);
+ cal.setTimeInMillis(time);
+
+ int year = cal.get(Calendar.YEAR);
+ int month = cal.get(Calendar.MONTH) + 1; // convert 0-based to 1-based
+ int days = cal.get(Calendar.DAY_OF_MONTH);
+ int hours = cal.get(Calendar.HOUR_OF_DAY);
+ int minutes = cal.get(Calendar.MINUTE);
+ int seconds = cal.get(Calendar.SECOND);
+ int millis = cal.get(Calendar.MILLISECOND);
return year + "-" +
twoDigits(month) + "-" +
@@ -155,22 +72,22 @@ public class CheapDateFormatter {
twoDigits(hours) + ":" +
twoDigits(minutes) + ":" +
twoDigits(seconds) + "." +
- threeDigits(time) + " GMT";
+ threeDigits(millis) + " GMT";
}
- private static String twoDigits(long val) {
+ private static String twoDigits(int val) {
String retval;
if (val < 10) {
retval = "0" + val;
} else {
- retval = Long.toString(val);
+ retval = Integer.toString(val);
}
return retval;
}
- private static String threeDigits(long val) {
+ private static String threeDigits(int val) {
String retval;
if (val < 10) {
@@ -178,7 +95,7 @@ public class CheapDateFormatter {
} else if (val < 100) {
retval = "0" + val;
} else {
- retval = Long.toString(val);
+ retval = Integer.toString(val);
}
return retval;
Added: db/derby/code/trunk/java/testing/org/apache/derbyTesting/unitTests/junit/CheapDateFormatterTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/unitTests/junit/CheapDateFormatterTest.java?rev=967000&view=auto
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/unitTests/junit/CheapDateFormatterTest.java (added)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/unitTests/junit/CheapDateFormatterTest.java Fri Jul 23 07:57:25 2010
@@ -0,0 +1,94 @@
+/*
+
+ Derby - Class org.apache.derbyTesting.unitTests.junit.CheapDateFormatterTest
+
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to you under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ */
+
+package org.apache.derbyTesting.unitTests.junit;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.derby.iapi.util.CheapDateFormatter;
+import org.apache.derbyTesting.junit.BaseTestCase;
+
+/**
+ * Unit tests for the CheapDateFormatter class.
+ */
+public class CheapDateFormatterTest extends BaseTestCase {
+ public CheapDateFormatterTest(String name) {
+ super(name);
+ }
+
+ public static Test suite() {
+ return new TestSuite(CheapDateFormatterTest.class);
+ }
+
+ /**
+ * Tests for the {@code formatDate()} method.
+ */
+ public void testFormatDate() throws ParseException {
+ assertDateString("1970-01-01 00:00:00.000 GMT"); // Epoch
+
+ // DERBY-4752: Times the first day in a leap year used to be
+ // formatted with month 13 in the previous year. Verify that this
+ // works correctly for the first day of the leap year 2016.
+ assertDateString("2015-12-31 23:59:59.999 GMT");
+ assertDateString("2016-01-01 00:00:00.000 GMT");
+ assertDateString("2016-01-01 00:00:00.001 GMT");
+ assertDateString("2016-01-01 08:31:23.456 GMT");
+ assertDateString("2016-01-02 12:00:00.000 GMT");
+
+ // DERBY-4752: We used to get a one day skew each time we passed a
+ // year divisible by four that was not a leap year (like 2100, 2200,
+ // 2300, 2500, ...).
+ assertDateString("2100-05-17 14:10:44.701 GMT");
+ assertDateString("2927-06-07 00:00:00.000 GMT");
+ assertDateString("9999-12-31 23:59:59.999 GMT");
+
+ // DERBY-4752: Years divisible by 100, but not by 400, are not leap
+ // years. Still, formatDate() used to return February 29 for the
+ // following dates:
+ assertDateString("2100-03-01 12:00:00.000 GMT");
+ assertDateString("2200-03-02 12:00:00.000 GMT");
+ assertDateString("2300-03-03 12:00:00.000 GMT");
+ assertDateString("2500-03-04 12:00:00.000 GMT");
+
+ // Year 8000 will be a leap year, unless a better calendar system
+ // has been devised by then.
+ assertDateString("8000-02-28 12:00:00.000 GMT");
+ assertDateString("8000-02-29 12:00:00.000 GMT");
+ assertDateString("8000-01-03 12:00:00.000 GMT");
+ }
+
+ /**
+ * Convert a date string to a long representing milliseconds since Epoch,
+ * feed that value to CheapDateFormatter.formatDate(), and verify that
+ * the exact same date string is returned.
+ *
+ * @param date a string representing the date to test
+ * @throws ParseException if the date string cannot be parsed
+ */
+ private void assertDateString(String date) throws ParseException {
+ DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS z");
+ long time = df.parse(date).getTime();
+ assertEquals(date, CheapDateFormatter.formatDate(time));
+ }
+}
Propchange: db/derby/code/trunk/java/testing/org/apache/derbyTesting/unitTests/junit/CheapDateFormatterTest.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/unitTests/junit/_Suite.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/unitTests/junit/_Suite.java?rev=967000&r1=966999&r2=967000&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/unitTests/junit/_Suite.java (original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/unitTests/junit/_Suite.java Fri Jul 23 07:57:25 2010
@@ -47,6 +47,7 @@ public class _Suite extends BaseTestCase
TestSuite suite = new TestSuite("JUnit unit tests");
suite.addTest(ArrayInputStreamTest.suite());
+ suite.addTest(CheapDateFormatterTest.suite());
suite.addTest(FormatableBitSetTest.suite());
suite.addTest(SystemPrivilegesPermissionTest.suite());
suite.addTest(UTF8UtilTest.suite());