001: /**
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */package org.apache.solr.schema;
017:
018: import org.apache.solr.core.SolrException;
019: import org.apache.solr.request.XMLWriter;
020: import org.apache.solr.request.TextResponseWriter;
021: import org.apache.lucene.document.Fieldable;
022: import org.apache.lucene.search.SortField;
023: import org.apache.solr.search.function.ValueSource;
024: import org.apache.solr.search.function.OrdFieldSource;
025: import org.apache.solr.util.DateMathParser;
026:
027: import java.util.Map;
028: import java.io.IOException;
029: import java.util.Date;
030: import java.util.TimeZone;
031: import java.util.Locale;
032: import java.text.SimpleDateFormat;
033: import java.text.DateFormat;
034: import java.text.ParseException;
035:
036: // TODO: make a FlexibleDateField that can accept dates in multiple
037: // formats, better for human entered dates.
038:
039: // TODO: make a DayField that only stores the day?
040:
041: /**
042: * FieldType that can represent any Date/Time with millisecond precisison.
043: * <p>
044: * Date Format for the XML, incoming and outgoing:
045: * </p>
046: * <blockquote>
047: * A date field shall be of the form 1995-12-31T23:59:59Z
048: * The trailing "Z" designates UTC time and is mandatory.
049: * Optional fractional seconds are allowed: 1995-12-31T23:59:59.999Z
050: * All other parts are mandatory.
051: * </blockquote>
052: * <p>
053: * This format was derived to be standards compliant (ISO 8601) and is a more
054: * restricted form of the canonical representation of dateTime from XML
055: * schema part 2.
056: * http://www.w3.org/TR/xmlschema-2/#dateTime
057: * </p>
058: * <blockquote>
059: * "In 1970 the Coordinated Universal Time system was devised by an
060: * international advisory group of technical experts within the International
061: * Telecommunication Union (ITU). The ITU felt it was best to designate a
062: * single abbreviation for use in all languages in order to minimize
063: * confusion. Since unanimous agreement could not be achieved on using
064: * either the English word order, CUT, or the French word order, TUC, the
065: * acronym UTC was chosen as a compromise."
066: * </blockquote>
067: *
068: * <p>
069: * This FieldType also supports incoming "Date Math" strings for computing
070: * values by adding/rounding internals of time relative "NOW",
071: * ie: "NOW+1YEAR", "NOW/DAY", etc.. -- see {@link DateMathParser}
072: * for more examples.
073: * </p>
074: *
075: * @author yonik
076: * @version $Id: DateField.java 542679 2007-05-29 22:28:21Z ryan $
077: * @see <a href="http://www.w3.org/TR/xmlschema-2/#dateTime">XML schema part 2</a>
078: *
079: */
080: public class DateField extends FieldType {
081:
082: public static TimeZone UTC = TimeZone.getTimeZone("UTC");
083:
084: // The XML (external) date format will sort correctly, except if
085: // fractions of seconds are present (because '.' is lower than 'Z').
086: // The easiest fix is to simply remove the 'Z' for the internal
087: // format.
088:
089: protected void init(IndexSchema schema, Map<String, String> args) {
090: }
091:
092: public String toInternal(String val) {
093: int len = val.length();
094: if (val.charAt(len - 1) == 'Z') {
095: return val.substring(0, len - 1);
096: } else if (val.startsWith("NOW")) {
097: /* :TODO: let Locale/TimeZone come from init args for rounding only */
098: DateMathParser p = new DateMathParser(UTC, Locale.US);
099: try {
100: return toInternal(p.parseMath(val.substring(3)));
101: } catch (ParseException e) {
102: throw new SolrException(
103: SolrException.ErrorCode.BAD_REQUEST,
104: "Invalid Date Math String:'" + val + '\'', e);
105: }
106: }
107: throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
108: "Invalid Date String:'" + val + '\'');
109: }
110:
111: public String toInternal(Date val) {
112: return getThreadLocalDateFormat().format(val);
113: }
114:
115: public String indexedToReadable(String indexedForm) {
116: return indexedForm + 'Z';
117: }
118:
119: public String toExternal(Fieldable f) {
120: return indexedToReadable(f.stringValue());
121: }
122:
123: public SortField getSortField(SchemaField field, boolean reverse) {
124: return getStringSort(field, reverse);
125: }
126:
127: public ValueSource getValueSource(SchemaField field) {
128: return new OrdFieldSource(field.name);
129: }
130:
131: public void write(XMLWriter xmlWriter, String name, Fieldable f)
132: throws IOException {
133: xmlWriter.writeDate(name, toExternal(f));
134: }
135:
136: public void write(TextResponseWriter writer, String name,
137: Fieldable f) throws IOException {
138: writer.writeDate(name, toExternal(f));
139: }
140:
141: /**
142: * Returns a formatter that can be use by the current thread if needed to
143: * convert Date objects to the Internal representation.
144: */
145: protected DateFormat getThreadLocalDateFormat() {
146:
147: return fmtThreadLocal.get();
148: }
149:
150: private static ThreadLocalDateFormat fmtThreadLocal = new ThreadLocalDateFormat();
151:
152: private static class ThreadLocalDateFormat extends
153: ThreadLocal<DateFormat> {
154: DateFormat proto;
155:
156: public ThreadLocalDateFormat() {
157: super ();
158: SimpleDateFormat tmp = new SimpleDateFormat(
159: "yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US);
160: tmp.setTimeZone(UTC);
161: proto = tmp;
162: }
163:
164: protected DateFormat initialValue() {
165: return (DateFormat) proto.clone();
166: }
167: }
168:
169: }
|