diff --git a/CHANGES.txt b/CHANGES.txt index 418d2e6a5e1..09003279818 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -48,7 +48,22 @@ option can be added to your solrconfig.xml. See the wiki (or the example solrconfig.xml) for more details... http://wiki.apache.org/solr/SolrConfigXml#HTTPCaching +In Solr 1.2, DateField did not enforce the canonical representation of +the ISO 8601 format when parsing incoming data, and did not generation +the canonical format when generating dates from "Date Math" strings +(particularly as it pertains to milliseconds ending in trailing zeros) +-- As a result equivalent dates could not always be compared properly. +This problem is corrected in Solr 1.3, but DateField users that might +have been affected by indexing inconsistent formats of equivilent +dates (ie: 1995-12-31T23:59:59Z vs 1995-12-31T23:59:59.000Z) may want +to consider reindexing to correct these inconsistencies. Users who +depend on some of the the "broken" behavior of DateField in Solr 1.2 +(specificly: accepting any input that ends in a 'Z') should consider +using the LegacyDateField class as a possible alternative. Users that +desire 100% backwards compatibility should consider using the Solr 1.2 +version of DateField. + Detailed Change List -------------------- @@ -383,6 +398,12 @@ Bug Fixes 28. SOLR-509: Moved firstSearcher event notification to the end of the SolrCore constructor (Koji Sekiguchi via gsingers) +29. SOLR-470, SOLR-552, and SOLR-544: Multiple fixes to DateField + regarding lenient parsing of optional milliseconds, and correct + formating using the canonical representation. LegacyDateField has + been added for people who have come to depend on the existing + broken behavior. (hossman) + Other Changes 1. SOLR-135: Moved common classes to org.apache.solr.common and altered the build scripts to make two jars: apache-solr-1.3.jar and diff --git a/src/java/org/apache/solr/schema/DateField.java b/src/java/org/apache/solr/schema/DateField.java index 91d38e7d235..645b15fd423 100644 --- a/src/java/org/apache/solr/schema/DateField.java +++ b/src/java/org/apache/solr/schema/DateField.java @@ -33,7 +33,11 @@ import java.util.TimeZone; import java.util.Locale; import java.text.SimpleDateFormat; import java.text.DateFormat; +import java.text.NumberFormat; +import java.text.DecimalFormat; +import java.text.ParsePosition; import java.text.ParseException; +import java.text.FieldPosition; // TODO: make a FlexibleDateField that can accept dates in multiple // formats, better for human entered dates. @@ -42,21 +46,45 @@ import java.text.ParseException; /** - * FieldType that can represent any Date/Time with millisecond precisison. + * FieldType that can represent any Date/Time with millisecond precision. *

* Date Format for the XML, incoming and outgoing: *

*
* A date field shall be of the form 1995-12-31T23:59:59Z - * The trailing "Z" designates UTC time and is mandatory. - * Optional fractional seconds are allowed: 1995-12-31T23:59:59.999Z + * The trailing "Z" designates UTC time and is mandatory + * (See below for an explanation of UTC). + * Optional fractional seconds are allowed, as long as they do not end + * in a trailing 0 (but any precision beyond milliseconds will be ignored). * All other parts are mandatory. *
*

* This format was derived to be standards compliant (ISO 8601) and is a more - * restricted form of the canonical representation of dateTime from XML - * schema part 2. - * http://www.w3.org/TR/xmlschema-2/#dateTime + * restricted form of the + * canonical + * representation of dateTime from XML schema part 2. Examples... + *

+ * + *

+ * Note that DateField is lenient with regards to parsing fractional + * seconds that end in trailing zeros and will ensure that those values + * are indexed in the correct canonical format. + *

+ *

+ * This FieldType also supports incoming "Date Math" strings for computing + * values by adding/rounding internals of time relative either an explicit + * datetime (in the format specified above) or the literal string "NOW", + * ie: "NOW+1YEAR", "NOW/DAY", "1995-12-31T23:59:59.999Z+5MINUTES", etc... + * -- see {@link DateMathParser} for more examples. + *

+ * + *

+ * Explanation of "UTC"... *

*
* "In 1970 the Coordinated Universal Time system was devised by an @@ -68,14 +96,6 @@ import java.text.ParseException; * acronym UTC was chosen as a compromise." *
* - *

- * This FieldType also supports incoming "Date Math" strings for computing - * values by adding/rounding internals of time relative either an explicit - * datetime (in theformat specified above) or the literal string "NOW", - * ie: "NOW+1YEAR", "NOW/DAY", 1995-12-31T23:59:59.999Z+5MINUTES, etc... - * -- see {@link DateMathParser} for more examples. - *

- * * @version $Id$ * @see XML schema part 2 * @@ -96,12 +116,6 @@ public class DateField extends FieldType { protected static char Z = 'Z'; public String toInternal(String val) { - final int len=val.length(); - if (val.charAt(len-1) == Z) { - // check common case first, simple datetime - // NOTE: not parsed to ensure correctness - return val.substring(0,len-1); - } return toInternal(parseMath(null, val)); } @@ -150,7 +164,7 @@ public class DateField extends FieldType { } public String toInternal(Date val) { - return getThreadLocalDateFormat().format(val); + return formatDate(val); } public String indexedToReadable(String indexedForm) { @@ -161,13 +175,13 @@ public class DateField extends FieldType { return indexedToReadable(f.stringValue()); } public Date toObject(String indexedForm) throws java.text.ParseException { - return getThreadLocalDateFormat().parse(indexedToReadable(indexedForm)); + return parseDate(indexedToReadable(indexedForm)); } @Override public Date toObject(Fieldable f) { try { - return getThreadLocalDateFormat().parse( toExternal(f) ); + return parseDate( toExternal(f) ); } catch( ParseException ex ) { throw new RuntimeException( ex ); @@ -193,25 +207,105 @@ public class DateField extends FieldType { /** * Returns a formatter that can be use by the current thread if needed to * convert Date objects to the Internal representation. + * + * Only the format(Date) can be used safely. + * + * @deprecated - use formatDate(Date) instead */ protected DateFormat getThreadLocalDateFormat() { - return fmtThreadLocal.get(); } - private static ThreadLocalDateFormat fmtThreadLocal - = new ThreadLocalDateFormat(); + /** + * Thread safe method that can be used by subclasses to format a Date + * using the Internal representation. + */ + protected String formatDate(Date d) { + return fmtThreadLocal.get().format(d); + } + + /** + * Thread safe method that can be used by subclasses to parse a Date + * that is already in the internal representation + */ + protected Date parseDate(String s) throws ParseException { + return fmtThreadLocal.get().parse(s); + } + + /** + * Thread safe DateFormat that can format in the canonical + * ISO8601 date format, not including the trailing "Z" (since it is + * left off in the internal indexed values) + */ + private final static ThreadLocalDateFormat fmtThreadLocal + = new ThreadLocalDateFormat(new ISO8601CanonicalDateFormat()); + + private static class ISO8601CanonicalDateFormat extends SimpleDateFormat { + + protected NumberFormat millisParser + = NumberFormat.getIntegerInstance(Locale.US); + + protected NumberFormat millisFormat = new DecimalFormat(".###"); + + public ISO8601CanonicalDateFormat() { + super("yyyy-MM-dd'T'HH:mm:ss", Locale.US); + this.setTimeZone(UTC); + } + + public Date parse(String i, ParsePosition p) { + /* delegate to SimpleDateFormat for easy stuff */ + Date d = super.parse(i, p); + int milliIndex = p.getIndex(); + /* worry aboutthe milliseconds ourselves */ + if (null != d && + -1 == p.getErrorIndex() && + milliIndex + 1 < i.length() && + '.' == i.charAt(milliIndex)) { + p.setIndex( ++milliIndex ); // NOTE: ++ to chomp '.' + Number millis = millisParser.parse(i, p); + if (-1 == p.getErrorIndex()) { + int endIndex = p.getIndex(); + d = new Date(d.getTime() + + (long)(millis.doubleValue() * + Math.pow(10, (3-endIndex+milliIndex)))); + } + } + return d; + } + + public StringBuffer format(Date d, StringBuffer toAppendTo, + FieldPosition pos) { + /* delegate to SimpleDateFormat for easy stuff */ + super.format(d, toAppendTo, pos); + /* worry aboutthe milliseconds ourselves */ + long millis = d.getTime() % 1000l; + if (0l == millis) { + return toAppendTo; + } + int posBegin = toAppendTo.length(); + toAppendTo.append(millisFormat.format(millis / 1000d)); + if (DateFormat.MILLISECOND_FIELD == pos.getField()) { + pos.setBeginIndex(posBegin); + pos.setEndIndex(toAppendTo.length()); + } + return toAppendTo; + } + + public Object clone() { + ISO8601CanonicalDateFormat c + = (ISO8601CanonicalDateFormat) super.clone(); + c.millisParser = NumberFormat.getIntegerInstance(Locale.US); + c.millisFormat = new DecimalFormat(".###"); + return c; + } + } private static class ThreadLocalDateFormat extends ThreadLocal { DateFormat proto; - public ThreadLocalDateFormat() { + public ThreadLocalDateFormat(DateFormat d) { super(); - SimpleDateFormat tmp = - new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US); - tmp.setTimeZone(UTC); - proto = tmp; + proto = d; } - protected DateFormat initialValue() { return (DateFormat) proto.clone(); } diff --git a/src/java/org/apache/solr/schema/LegacyDateField.java b/src/java/org/apache/solr/schema/LegacyDateField.java new file mode 100644 index 00000000000..8a6364fba33 --- /dev/null +++ b/src/java/org/apache/solr/schema/LegacyDateField.java @@ -0,0 +1,117 @@ +/** + * 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.solr.schema; + +import org.apache.solr.common.SolrException; +import org.apache.solr.request.XMLWriter; +import org.apache.solr.request.TextResponseWriter; +import org.apache.lucene.document.Fieldable; +import org.apache.lucene.search.SortField; +import org.apache.solr.search.function.ValueSource; +import org.apache.solr.search.function.OrdFieldSource; +import org.apache.solr.util.DateMathParser; + +import java.util.Map; +import java.io.IOException; +import java.util.Date; +import java.util.TimeZone; +import java.util.Locale; +import java.text.SimpleDateFormat; +import java.text.DateFormat; +import java.text.NumberFormat; +import java.text.ParsePosition; +import java.text.ParseException; + +/** + * This class is NOT recommended for new users and should be + * considered UNSUPPORTED. + *

+ * In Solr 1.2, DateField did not enforce + * the canonical representation of the ISO 8601 format when parsing + * incoming data, and did not generation the canonical format when + * generating dates from "Date Math" strings (particularly as + * it pertains to milliseconds ending in trailing zeros) -- As a result + * equivalent dates could not always be compared properly. + *

+ *

+ * This class is provided as possible alternative for people who depend on + * the "broken" behavior of DateField in Solr 1.2 + * (specificly: accepting any input that ends in a 'Z', and + * formating DateMath expressions using 3 decimals of milliseconds) while + * still supporting some newer functionality of DateField (ie: DateMath on + * explicit strings in addition to "NOW") + *

+ *

+ * Users that desire 100% backwards compatibility should consider using + * the Solr 1.2 version of DateField + *

+ * + * @see SOLR-552 + * @see SOLR-470 + * @see SOLR-521 + * @deprecated use {@link DateField} + */ +@Deprecated +public final class LegacyDateField extends DateField { + + /** + * Overrides the super class to short circut and do no enforcing of + * the canonical format + */ + public String toInternal(String val) { + final int len=val.length(); + if (val.charAt(len-1) == Z) { + // check common case first, simple datetime + // NOTE: not parsed to ensure correctness + return val.substring(0,len-1); + } + return toInternal(parseMath(null, val)); + } + + /** + * This method returns a DateFormat which does NOT respect the + * ISO 8601 canonical format with regards to trailing zeros in milliseconds, + * instead if always formats milliseconds to 3 decimal points. + */ + protected DateFormat getThreadLocalDateFormat() { + return fmtThreadLocal.get(); + } + + protected String formatDate(Date d) { + return getThreadLocalDateFormat().format(d); + } + + private static ThreadLocalDateFormat fmtThreadLocal + = new ThreadLocalDateFormat(); + + private static class ThreadLocalDateFormat extends ThreadLocal { + DateFormat proto; + public ThreadLocalDateFormat() { + super(); + SimpleDateFormat tmp = + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US); + tmp.setTimeZone(UTC); + proto = tmp; + } + + protected DateFormat initialValue() { + return (DateFormat) proto.clone(); + } + } + +} diff --git a/src/test/org/apache/solr/BasicFunctionalityTest.java b/src/test/org/apache/solr/BasicFunctionalityTest.java index 3eaae0708a5..61bcc474f1b 100644 --- a/src/test/org/apache/solr/BasicFunctionalityTest.java +++ b/src/test/org/apache/solr/BasicFunctionalityTest.java @@ -656,38 +656,38 @@ public class BasicFunctionalityTest extends AbstractSolrTestCase { ) // 31 days + pre+post+inner = 34 ,"*[count("+pre+"/int)=34]" - ,pre+"/int[@name='1976-07-01T00:00:00.000Z'][.='0' ]" - ,pre+"/int[@name='1976-07-02T00:00:00.000Z'][.='0' ]" - ,pre+"/int[@name='1976-07-03T00:00:00.000Z'][.='2' ]" + ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='0' ]" + ,pre+"/int[@name='1976-07-02T00:00:00Z'][.='0' ]" + ,pre+"/int[@name='1976-07-03T00:00:00Z'][.='2' ]" // july4th = 2 because exists doc @ 00:00:00.000 on July5 // (date faceting is inclusive) - ,pre+"/int[@name='1976-07-04T00:00:00.000Z'][.='2' ]" - ,pre+"/int[@name='1976-07-05T00:00:00.000Z'][.='2' ]" - ,pre+"/int[@name='1976-07-06T00:00:00.000Z'][.='0']" - ,pre+"/int[@name='1976-07-07T00:00:00.000Z'][.='0']" - ,pre+"/int[@name='1976-07-08T00:00:00.000Z'][.='0']" - ,pre+"/int[@name='1976-07-09T00:00:00.000Z'][.='0']" - ,pre+"/int[@name='1976-07-10T00:00:00.000Z'][.='0']" - ,pre+"/int[@name='1976-07-11T00:00:00.000Z'][.='0']" - ,pre+"/int[@name='1976-07-12T00:00:00.000Z'][.='1' ]" - ,pre+"/int[@name='1976-07-13T00:00:00.000Z'][.='1' ]" - ,pre+"/int[@name='1976-07-14T00:00:00.000Z'][.='0']" - ,pre+"/int[@name='1976-07-15T00:00:00.000Z'][.='2' ]" - ,pre+"/int[@name='1976-07-16T00:00:00.000Z'][.='0']" - ,pre+"/int[@name='1976-07-17T00:00:00.000Z'][.='0']" - ,pre+"/int[@name='1976-07-18T00:00:00.000Z'][.='0']" - ,pre+"/int[@name='1976-07-19T00:00:00.000Z'][.='0']" - ,pre+"/int[@name='1976-07-21T00:00:00.000Z'][.='1' ]" - ,pre+"/int[@name='1976-07-22T00:00:00.000Z'][.='0']" - ,pre+"/int[@name='1976-07-23T00:00:00.000Z'][.='0']" - ,pre+"/int[@name='1976-07-24T00:00:00.000Z'][.='0']" - ,pre+"/int[@name='1976-07-25T00:00:00.000Z'][.='0']" - ,pre+"/int[@name='1976-07-26T00:00:00.000Z'][.='0']" - ,pre+"/int[@name='1976-07-27T00:00:00.000Z'][.='0']" - ,pre+"/int[@name='1976-07-28T00:00:00.000Z'][.='0']" - ,pre+"/int[@name='1976-07-29T00:00:00.000Z'][.='0']" - ,pre+"/int[@name='1976-07-30T00:00:00.000Z'][.='1' ]" - ,pre+"/int[@name='1976-07-31T00:00:00.000Z'][.='0']" + ,pre+"/int[@name='1976-07-04T00:00:00Z'][.='2' ]" + ,pre+"/int[@name='1976-07-05T00:00:00Z'][.='2' ]" + ,pre+"/int[@name='1976-07-06T00:00:00Z'][.='0']" + ,pre+"/int[@name='1976-07-07T00:00:00Z'][.='0']" + ,pre+"/int[@name='1976-07-08T00:00:00Z'][.='0']" + ,pre+"/int[@name='1976-07-09T00:00:00Z'][.='0']" + ,pre+"/int[@name='1976-07-10T00:00:00Z'][.='0']" + ,pre+"/int[@name='1976-07-11T00:00:00Z'][.='0']" + ,pre+"/int[@name='1976-07-12T00:00:00Z'][.='1' ]" + ,pre+"/int[@name='1976-07-13T00:00:00Z'][.='1' ]" + ,pre+"/int[@name='1976-07-14T00:00:00Z'][.='0']" + ,pre+"/int[@name='1976-07-15T00:00:00Z'][.='2' ]" + ,pre+"/int[@name='1976-07-16T00:00:00Z'][.='0']" + ,pre+"/int[@name='1976-07-17T00:00:00Z'][.='0']" + ,pre+"/int[@name='1976-07-18T00:00:00Z'][.='0']" + ,pre+"/int[@name='1976-07-19T00:00:00Z'][.='0']" + ,pre+"/int[@name='1976-07-21T00:00:00Z'][.='1' ]" + ,pre+"/int[@name='1976-07-22T00:00:00Z'][.='0']" + ,pre+"/int[@name='1976-07-23T00:00:00Z'][.='0']" + ,pre+"/int[@name='1976-07-24T00:00:00Z'][.='0']" + ,pre+"/int[@name='1976-07-25T00:00:00Z'][.='0']" + ,pre+"/int[@name='1976-07-26T00:00:00Z'][.='0']" + ,pre+"/int[@name='1976-07-27T00:00:00Z'][.='0']" + ,pre+"/int[@name='1976-07-28T00:00:00Z'][.='0']" + ,pre+"/int[@name='1976-07-29T00:00:00Z'][.='0']" + ,pre+"/int[@name='1976-07-30T00:00:00Z'][.='1' ]" + ,pre+"/int[@name='1976-07-31T00:00:00Z'][.='0']" ,pre+"/int[@name='before' ][.='2']" ,pre+"/int[@name='after' ][.='1']" @@ -708,9 +708,9 @@ public class BasicFunctionalityTest extends AbstractSolrTestCase { ) // 3 gaps + pre+post+inner = 6 ,"*[count("+pre+"/int)=6]" - ,pre+"/int[@name='1976-07-01T00:00:00.000Z'][.='5' ]" - ,pre+"/int[@name='1976-07-06T00:00:00.000Z'][.='0' ]" - ,pre+"/int[@name='1976-07-11T00:00:00.000Z'][.='4' ]" + ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='5' ]" + ,pre+"/int[@name='1976-07-06T00:00:00Z'][.='0' ]" + ,pre+"/int[@name='1976-07-11T00:00:00Z'][.='4' ]" ,pre+"/int[@name='before' ][.='2']" ,pre+"/int[@name='after' ][.='3']" @@ -730,9 +730,9 @@ public class BasicFunctionalityTest extends AbstractSolrTestCase { ) // 3 gaps + pre+post+inner = 6 ,"*[count("+pre+"/int)=6]" - ,pre+"/int[@name='1976-07-01T00:00:00.000Z'][.='5' ]" - ,pre+"/int[@name='1976-07-06T00:00:00.000Z'][.='0' ]" - ,pre+"/int[@name='1976-07-11T00:00:00.000Z'][.='1' ]" + ,pre+"/int[@name='1976-07-01T00:00:00Z'][.='5' ]" + ,pre+"/int[@name='1976-07-06T00:00:00Z'][.='0' ]" + ,pre+"/int[@name='1976-07-11T00:00:00Z'][.='1' ]" ,pre+"/int[@name='before' ][.='2']" ,pre+"/int[@name='after' ][.='6']" diff --git a/src/test/org/apache/solr/schema/DateFieldTest.java b/src/test/org/apache/solr/schema/DateFieldTest.java new file mode 100644 index 00000000000..785db52fedc --- /dev/null +++ b/src/test/org/apache/solr/schema/DateFieldTest.java @@ -0,0 +1,110 @@ +/** + * 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.solr.schema; + +import org.apache.solr.schema.DateField; +import org.apache.solr.util.DateMathParser; +import org.apache.lucene.document.Fieldable; + +import java.util.Date; +import java.util.TimeZone; +import java.util.Locale; +import java.text.DateFormat; + +import junit.framework.TestCase; + +public class DateFieldTest extends LegacyDateFieldTest { + + public void setUp() throws Exception { + super.setUp(); + f = new DateField(); + } + + public void testToInternal() throws Exception { + assertToI("1995-12-31T23:59:59.999", "1995-12-31T23:59:59.999666Z"); + assertToI("1995-12-31T23:59:59.999", "1995-12-31T23:59:59.999Z"); + assertToI("1995-12-31T23:59:59.99", "1995-12-31T23:59:59.99Z"); + assertToI("1995-12-31T23:59:59.9", "1995-12-31T23:59:59.9Z"); + assertToI("1995-12-31T23:59:59", "1995-12-31T23:59:59Z"); + + // here the input isn't in the canonical form, but we should be forgiving + assertToI("1995-12-31T23:59:59.99", "1995-12-31T23:59:59.990Z"); + assertToI("1995-12-31T23:59:59.9", "1995-12-31T23:59:59.900Z"); + assertToI("1995-12-31T23:59:59.9", "1995-12-31T23:59:59.90Z"); + assertToI("1995-12-31T23:59:59", "1995-12-31T23:59:59.000Z"); + assertToI("1995-12-31T23:59:59", "1995-12-31T23:59:59.00Z"); + assertToI("1995-12-31T23:59:59", "1995-12-31T23:59:59.0Z"); + + // kind of kludgy, but we have other tests for the actual date math + assertToI(f.toInternal(p.parseMath("/DAY")), "NOW/DAY"); + + // as of Solr 1.3 + assertToI("1995-12-31T00:00:00", "1995-12-31T23:59:59Z/DAY"); + assertToI("1995-12-31T00:00:00", "1995-12-31T23:59:59.123Z/DAY"); + assertToI("1995-12-31T00:00:00", "1995-12-31T23:59:59.123999Z/DAY"); + } + + public void testToInternalObj() throws Exception { + assertToI("1995-12-31T23:59:59.999", 820454399999l); + assertToI("1995-12-31T23:59:59.99", 820454399990l); + assertToI("1995-12-31T23:59:59.9", 820454399900l); + assertToI("1995-12-31T23:59:59", 820454399000l); + } + + public void assertParseMath(long expected, String input) { + Date d = new Date(0); + assertEquals("Input: "+input, expected, f.parseMath(d, input).getTime()); + } + + // as of Solr1.3 + public void testParseMath() { + assertParseMath(820454699999l, "1995-12-31T23:59:59.999765Z+5MINUTES"); + assertParseMath(820454699999l, "1995-12-31T23:59:59.999Z+5MINUTES"); + assertParseMath(820454699990l, "1995-12-31T23:59:59.99Z+5MINUTES"); + assertParseMath(194918400000l, "1976-03-06T03:06:00Z/DAY"); + + // here the input isn't in the canonical form, but we should be forgiving + assertParseMath(820454699990l, "1995-12-31T23:59:59.990Z+5MINUTES"); + assertParseMath(194918400000l, "1976-03-06T03:06:00.0Z/DAY"); + assertParseMath(194918400000l, "1976-03-06T03:06:00.00Z/DAY"); + assertParseMath(194918400000l, "1976-03-06T03:06:00.000Z/DAY"); + } + + public void assertToObject(long expected, String input) throws Exception { + assertEquals("Input: "+input, expected, f.toObject(input).getTime()); + } + + // as of Solr1.3 + public void testToObject() throws Exception { + assertToObject(820454399987l, "1995-12-31T23:59:59.987666Z"); + assertToObject(820454399987l, "1995-12-31T23:59:59.987Z"); + assertToObject(820454399980l, "1995-12-31T23:59:59.98Z"); + assertToObject(820454399900l, "1995-12-31T23:59:59.9Z"); + assertToObject(820454399000l, "1995-12-31T23:59:59Z"); + } + + public void testFormatter() { + DateFormat fmt = f.getThreadLocalDateFormat(); + assertEquals("1970-01-01T00:00:00.005", fmt.format(new Date(5))); + assertEquals("1970-01-01T00:00:00", fmt.format(new Date(0))); + assertEquals("1970-01-01T00:00:00.37", fmt.format(new Date(370))); + assertEquals("1970-01-01T00:00:00.9", fmt.format(new Date(900))); + + } + +} diff --git a/src/test/org/apache/solr/schema/LegacyDateFieldTest.java b/src/test/org/apache/solr/schema/LegacyDateFieldTest.java new file mode 100644 index 00000000000..201db568c19 --- /dev/null +++ b/src/test/org/apache/solr/schema/LegacyDateFieldTest.java @@ -0,0 +1,105 @@ +/** + * 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.solr.schema; + +import org.apache.solr.schema.DateField; +import org.apache.solr.util.DateMathParser; +import org.apache.lucene.document.Fieldable; + +import java.util.Date; +import java.util.TimeZone; +import java.util.Locale; +import java.text.DateFormat; + +import junit.framework.TestCase; + +public class LegacyDateFieldTest extends TestCase { + // if and when this class is removed, make sure to refactor all + // appropriate code to DateFieldTest + + public static TimeZone UTC = TimeZone.getTimeZone("UTC"); + protected DateField f = null; + protected DateMathParser p = null; + + public void setUp() throws Exception { + super.setUp(); + p = new DateMathParser(UTC, Locale.US); + f = new DateField(); + // so test can be run against Solr 1.2... + try { + Class clazz = Class.forName("org.apache.solr.schema.LegacyDateField"); + f = (DateField) clazz.newInstance(); + } catch (ClassNotFoundException ignored) { + // NOOP + } + } + + public void assertToI(String expected, String input) { + assertEquals("Input: " + input, expected, f.toInternal(input)); + } + + public void testToInternal() throws Exception { + assertToI("1995-12-31T23:59:59.999", "1995-12-31T23:59:59.999Z"); + assertToI("1995-12-31T23:59:59.99", "1995-12-31T23:59:59.99Z"); + assertToI("1995-12-31T23:59:59.9", "1995-12-31T23:59:59.9Z"); + assertToI("1995-12-31T23:59:59", "1995-12-31T23:59:59Z"); + + // this is the broken behavior + assertToI("1995-12-31T23:59:59.9998", "1995-12-31T23:59:59.9998Z"); + assertToI("1995-12-31T23:59:59.9990", "1995-12-31T23:59:59.9990Z"); + assertToI("1995-12-31T23:59:59.990", "1995-12-31T23:59:59.990Z"); + assertToI("1995-12-31T23:59:59.900", "1995-12-31T23:59:59.900Z"); + assertToI("1995-12-31T23:59:59.90", "1995-12-31T23:59:59.90Z"); + assertToI("1995-12-31T23:59:59.000", "1995-12-31T23:59:59.000Z"); + assertToI("1995-12-31T23:59:59.00", "1995-12-31T23:59:59.00Z"); + assertToI("1995-12-31T23:59:59.0", "1995-12-31T23:59:59.0Z"); + + } + + public void assertToI(String expected, long input) { + assertEquals("Input: " + input, expected, f.toInternal(new Date(input))); + } + + public void testToInternalObj() throws Exception { + assertToI("1995-12-31T23:59:59.999", 820454399999l); + + // this is the broken behavior + assertToI("1995-12-31T23:59:59.990", 820454399990l); + assertToI("1995-12-31T23:59:59.900", 820454399900l); + assertToI("1995-12-31T23:59:59.000", 820454399000l); + } + + public void assertItoR(String expected, String input) { + assertEquals("Input: " + input, expected, f.indexedToReadable(input)); + } + + public void testIndexedToReadable() { + assertItoR("1995-12-31T23:59:59.999Z", "1995-12-31T23:59:59.999"); + assertItoR("1995-12-31T23:59:59.99Z", "1995-12-31T23:59:59.99"); + assertItoR("1995-12-31T23:59:59.9Z", "1995-12-31T23:59:59.9"); + assertItoR("1995-12-31T23:59:59Z", "1995-12-31T23:59:59"); + } + public void testFormatter() { + DateFormat fmt = f.getThreadLocalDateFormat(); + assertEquals("1970-01-01T00:00:00.005", fmt.format(new Date(5))); + // all of this is broken behavior + assertEquals("1970-01-01T00:00:00.000", fmt.format(new Date(0))); + assertEquals("1970-01-01T00:00:00.370", fmt.format(new Date(370))); + assertEquals("1970-01-01T00:00:00.900", fmt.format(new Date(900))); + } +}