View Javadoc

1   /*
2    *  Copyright 2001-2009 Stephen Colebourne
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  package org.joda.time.convert;
17  
18  import org.joda.time.Chronology;
19  import org.joda.time.DateTime;
20  import org.joda.time.Period;
21  import org.joda.time.ReadWritableInterval;
22  import org.joda.time.ReadWritablePeriod;
23  import org.joda.time.ReadablePartial;
24  import org.joda.time.field.FieldUtils;
25  import org.joda.time.format.DateTimeFormatter;
26  import org.joda.time.format.ISODateTimeFormat;
27  import org.joda.time.format.ISOPeriodFormat;
28  import org.joda.time.format.PeriodFormatter;
29  
30  /**
31   * StringConverter converts from a String to an instant, partial,
32   * duration, period or interval..
33   *
34   * @author Stephen Colebourne
35   * @author Brian S O'Neill
36   * @since 1.0
37   */
38  class StringConverter extends AbstractConverter
39          implements InstantConverter, PartialConverter, DurationConverter, PeriodConverter, IntervalConverter {
40  
41      /**
42       * Singleton instance.
43       */
44      static final StringConverter INSTANCE = new StringConverter();
45  
46      /**
47       * Restricted constructor.
48       */
49      protected StringConverter() {
50          super();
51      }
52  
53      //-----------------------------------------------------------------------
54      /**
55       * Gets the millis, which is the ISO parsed string value.
56       * 
57       * @param object  the String to convert, must not be null
58       * @param chrono  the chronology to use, non-null result of getChronology
59       * @return the millisecond value
60       * @throws IllegalArgumentException if the value if invalid
61       */
62      public long getInstantMillis(Object object, Chronology chrono) {
63          String str = (String) object;
64          DateTimeFormatter p = ISODateTimeFormat.dateTimeParser();
65          return p.withChronology(chrono).parseMillis(str);
66      }
67  
68      /**
69       * Extracts the values of the partial from an object of this converter's type.
70       * This method checks if the parser has a zone, and uses it if present.
71       * This is most useful for parsing local times with UTC.
72       * 
73       * @param fieldSource  a partial that provides access to the fields.
74       *  This partial may be incomplete and only getFieldType(int) should be used
75       * @param object  the object to convert
76       * @param chrono  the chronology to use, which is the non-null result of getChronology()
77       * @param parser the parser to use, may be null
78       * @return the array of field values that match the fieldSource, must be non-null valid
79       * @throws ClassCastException if the object is invalid
80       * @throws IllegalArgumentException if the value if invalid
81       * @since 1.3
82       */
83      public int[] getPartialValues(ReadablePartial fieldSource, Object object, Chronology chrono, DateTimeFormatter parser) {
84          if (parser.getZone() != null) {
85              chrono = chrono.withZone(parser.getZone());
86          }
87          long millis = parser.withChronology(chrono).parseMillis((String) object);
88          return chrono.get(fieldSource, millis);
89      }
90  
91      //-----------------------------------------------------------------------
92      /**
93       * Gets the duration of the string using the standard type.
94       * This matches the toString() method of ReadableDuration.
95       * 
96       * @param object  the String to convert, must not be null
97       * @throws ClassCastException if the object is invalid
98       */
99      public long getDurationMillis(Object object) {
100         // parse here because duration could be bigger than the int supported
101         // by the period parser
102         String original = (String) object;
103         String str = original;
104         int len = str.length();
105         if (len >= 4 &&
106             (str.charAt(0) == 'P' || str.charAt(0) == 'p') &&
107             (str.charAt(1) == 'T' || str.charAt(1) == 't') &&
108             (str.charAt(len - 1) == 'S' || str.charAt(len - 1) == 's')) {
109             // ok
110         } else {
111             throw new IllegalArgumentException("Invalid format: \"" + original + '"');
112         }
113         str = str.substring(2, len - 1);
114         int dot = -1;
115         boolean negative = false;
116         for (int i = 0; i < str.length(); i++) {
117             if (str.charAt(i) >= '0' && str.charAt(i) <= '9') {
118                 // ok
119             } else if (i == 0 && str.charAt(0) == '-') {
120             	// ok
121             	negative = true;
122             } else if (i > (negative ? 1 : 0) && str.charAt(i) == '.' && dot == -1) {
123                 // ok
124                 dot = i;
125             } else {
126                 throw new IllegalArgumentException("Invalid format: \"" + original + '"');
127             }
128         }
129         long millis = 0, seconds = 0;
130         int firstDigit = negative ? 1 : 0;
131         if (dot > 0) {
132             seconds = Long.parseLong(str.substring(firstDigit, dot));
133             str = str.substring(dot + 1);
134             if (str.length() != 3) {
135                 str = (str + "000").substring(0, 3);
136             }
137             millis = Integer.parseInt(str);
138         } else if (negative) {
139         	seconds = Long.parseLong(str.substring(firstDigit, str.length()));
140         } else {
141             seconds = Long.parseLong(str);
142         }
143         if (negative) {
144             return FieldUtils.safeAdd(FieldUtils.safeMultiply(-seconds, 1000), -millis);
145         } else {
146             return FieldUtils.safeAdd(FieldUtils.safeMultiply(seconds, 1000), millis);
147         }
148     }
149 
150     //-----------------------------------------------------------------------
151     /**
152      * Extracts duration values from an object of this converter's type, and
153      * sets them into the given ReadWritableDuration.
154      *
155      * @param period  period to get modified
156      * @param object  the String to convert, must not be null
157      * @param chrono  the chronology to use
158      * @return the millisecond duration
159      * @throws ClassCastException if the object is invalid
160      */
161     public void setInto(ReadWritablePeriod period, Object object, Chronology chrono) {
162         String str = (String) object;
163         PeriodFormatter parser = ISOPeriodFormat.standard();
164         period.clear();
165         int pos = parser.parseInto(period, str, 0);
166         if (pos < str.length()) {
167             if (pos < 0) {
168                 // Parse again to get a better exception thrown.
169                 parser.withParseType(period.getPeriodType()).parseMutablePeriod(str);
170             }
171             throw new IllegalArgumentException("Invalid format: \"" + str + '"');
172         }
173     }
174 
175     //-----------------------------------------------------------------------
176     /**
177      * Sets the value of the mutable interval from the string.
178      * 
179      * @param writableInterval  the interval to set
180      * @param object  the String to convert, must not be null
181      * @param chrono  the chronology to use, may be null
182      */
183     public void setInto(ReadWritableInterval writableInterval, Object object, Chronology chrono) {
184         String str = (String) object;
185 
186         int separator = str.indexOf('/');
187         if (separator < 0) {
188             throw new IllegalArgumentException("Format requires a '/' separator: " + str);
189         }
190 
191         String leftStr = str.substring(0, separator);
192         if (leftStr.length() <= 0) {
193             throw new IllegalArgumentException("Format invalid: " + str);
194         }
195         String rightStr = str.substring(separator + 1);
196         if (rightStr.length() <= 0) {
197             throw new IllegalArgumentException("Format invalid: " + str);
198         }
199 
200         DateTimeFormatter dateTimeParser = ISODateTimeFormat.dateTimeParser();
201         dateTimeParser = dateTimeParser.withChronology(chrono);
202         PeriodFormatter periodParser = ISOPeriodFormat.standard();
203         long startInstant = 0, endInstant = 0;
204         Period period = null;
205         Chronology parsedChrono = null;
206         
207         // before slash
208         char c = leftStr.charAt(0);
209         if (c == 'P' || c == 'p') {
210             period = periodParser.withParseType(getPeriodType(leftStr)).parsePeriod(leftStr);
211         } else {
212             DateTime start = dateTimeParser.parseDateTime(leftStr);
213             startInstant = start.getMillis();
214             parsedChrono = start.getChronology();
215         }
216         
217         // after slash
218         c = rightStr.charAt(0);
219         if (c == 'P' || c == 'p') {
220             if (period != null) {
221                 throw new IllegalArgumentException("Interval composed of two durations: " + str);
222             }
223             period = periodParser.withParseType(getPeriodType(rightStr)).parsePeriod(rightStr);
224             chrono = (chrono != null ? chrono : parsedChrono);
225             endInstant = chrono.add(period, startInstant, 1);
226         } else {
227             DateTime end = dateTimeParser.parseDateTime(rightStr);
228             endInstant = end.getMillis();
229             parsedChrono = (parsedChrono != null ? parsedChrono : end.getChronology());
230             chrono = (chrono != null ? chrono : parsedChrono);
231             if (period != null) {
232                 startInstant = chrono.add(period, endInstant, -1);
233             }
234         }
235         
236         writableInterval.setInterval(startInstant, endInstant);
237         writableInterval.setChronology(chrono);
238     }
239 
240     //-----------------------------------------------------------------------
241     /**
242      * Returns String.class.
243      * 
244      * @return String.class
245      */
246     public Class<?> getSupportedType() {
247         return String.class;
248     }
249 
250 }