1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.ws.security.util;
19
20 import java.text.DateFormat;
21 import java.text.FieldPosition;
22 import java.text.ParsePosition;
23 import java.text.SimpleDateFormat;
24 import java.text.ParseException;
25 import java.util.Date;
26 import java.util.TimeZone;
27
28 import org.apache.commons.logging.Log;
29 import org.apache.commons.logging.LogFactory;
30
31 /***
32 * A {@link DateFormat} for the format of the dateTime simpleType as specified in the
33 * XML Schema specification. See <a href="http://www.w3.org/TR/xmlschema-2/#dateTime">
34 * XML Schema Part 2: Datatypes, W3C Recommendation 02 May 2001, Section 3.2.7.1</a>.
35 *
36 * @author Ian P. Springer
37 * @author Werner Dittmann
38 */
39 public class XmlSchemaDateFormat extends DateFormat {
40 /***
41 * Logger.
42 */
43 private static Log log = LogFactory.getLog(XmlSchemaDateFormat.class);
44
45 private static boolean doDebug = false;
46
47 static {
48 doDebug = log.isDebugEnabled();
49 }
50
51 /***
52 * Message retriever.
53 */
54
55 /***
56 * DateFormat for Zulu (UTC) form of an XML Schema dateTime string.
57 */
58 private static final DateFormat DATEFORMAT_XSD_ZULU = new SimpleDateFormat(
59 "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
60
61 static {
62 DATEFORMAT_XSD_ZULU.setTimeZone(TimeZone.getTimeZone("UTC"));
63 }
64
65 /***
66 * This method was snarfed from <tt>org.apache.axis.encoding.ser.CalendarDeserializer</tt>,
67 * which was written by Sam Ruby (rubys@us.ibm.com) and Rich Scheuerle (scheu@us.ibm.com).
68 * Better error reporting was added.
69 *
70 * @see DateFormat#parse(java.lang.String)
71 */
72 public Date parse(String src, ParsePosition parse_pos) {
73 Date date;
74
75
76 int index = 0;
77 try {
78 if (src != null) {
79 if ((src.charAt(0) == '+') || (src.charAt(0) == '-')) {
80 src = src.substring(1);
81 }
82
83 if (src.length() < 19) {
84 parse_pos.setIndex(src.length() - 1);
85 handleParseError(parse_pos, "TOO_FEW_CHARS");
86 }
87 validateChar(src, parse_pos, index = 4, '-', "EXPECTED_DASH");
88 validateChar(src, parse_pos, index = 7, '-', "EXPECTED_DASH");
89 validateChar(src, parse_pos, index = 10, 'T', "EXPECTED_CAPITAL_T");
90 validateChar(src, parse_pos, index = 13, ':', "EXPECTED_COLON_IN_TIME");
91 validateChar(src, parse_pos, index = 16, ':', "EXPECTED_COLON_IN_TIME");
92 }
93
94
95 try {
96 synchronized (DATEFORMAT_XSD_ZULU) {
97 date = DATEFORMAT_XSD_ZULU.parse((src == null) ? null
98 : (src.substring(0, 19) + ".000Z"));
99 }
100 } catch (Exception e) {
101 throw new NumberFormatException(e.toString());
102 }
103
104 index = 19;
105
106
107 if (src != null) {
108 if ((index < src.length()) && (src.charAt(index) == '.')) {
109 int milliseconds = 0;
110 int start = ++index;
111
112 while ((index < src.length())
113 && Character.isDigit(src.charAt(index))) {
114 index++;
115 }
116
117 String decimal = src.substring(start, index);
118
119 if (decimal.length() == 3) {
120 milliseconds = Integer.parseInt(decimal);
121 } else if (decimal.length() < 3) {
122 milliseconds = Integer.parseInt((decimal + "000")
123 .substring(0, 3));
124 } else {
125 milliseconds = Integer
126 .parseInt(decimal.substring(0, 3));
127
128 if (decimal.charAt(3) >= '5') {
129 ++milliseconds;
130 }
131 }
132
133
134 date.setTime(date.getTime() + milliseconds);
135 }
136
137
138 if (((index + 5) < src.length())
139 && ((src.charAt(index) == '+') || (src.charAt(index) == '-'))) {
140 validateCharIsDigit(src, parse_pos, index + 1, "EXPECTED_NUMERAL");
141 validateCharIsDigit(src, parse_pos, index + 2, "EXPECTED_NUMERAL");
142 validateChar(src, parse_pos, index + 3, ':', "EXPECTED_COLON_IN_TIMEZONE");
143 validateCharIsDigit(src, parse_pos, index + 4, "EXPECTED_NUMERAL");
144 validateCharIsDigit(src, parse_pos, index + 5, "EXPECTED_NUMERAL");
145
146 final int hours = (((src.charAt(index + 1) - '0') * 10) + src
147 .charAt(index + 2)) - '0';
148 final int mins = (((src.charAt(index + 4) - '0') * 10) + src
149 .charAt(index + 5)) - '0';
150 int millisecs = ((hours * 60) + mins) * 60 * 1000;
151
152
153 if (src.charAt(index) == '+') {
154 millisecs = -millisecs;
155 }
156
157 date.setTime(date.getTime() + millisecs);
158 index += 6;
159 }
160
161 if ((index < src.length()) && (src.charAt(index) == 'Z')) {
162 index++;
163 }
164
165 if (index < src.length()) {
166 handleParseError(parse_pos, "TOO_MANY_CHARS");
167 }
168 }
169 } catch (ParseException pe) {
170 log.error(pe.toString());
171 index = 0;
172 parse_pos.setErrorIndex(index);
173 date = null;
174 }
175 parse_pos.setIndex(index);
176 return (date);
177 }
178
179 /***
180 * @see DateFormat#format(java.util.Date)
181 */
182 public StringBuffer format(Date date, StringBuffer append_buf,
183 FieldPosition field_pos) {
184 String str;
185
186 synchronized (DATEFORMAT_XSD_ZULU) {
187 str = DATEFORMAT_XSD_ZULU.format(date);
188 }
189
190 if (append_buf == null) {
191 append_buf = new StringBuffer();
192 }
193
194 append_buf.append(str);
195
196 return (append_buf);
197 }
198
199 private void validateChar(String str, ParsePosition parse_pos, int index,
200 char expected, String error_reason) throws ParseException {
201 if (str.charAt(index) != expected) {
202 handleParseError(parse_pos, error_reason);
203 }
204 }
205
206 private void validateCharIsDigit(String str, ParsePosition parse_pos,
207 int index, String error_reason) throws ParseException {
208 if (!Character.isDigit(str.charAt(index))) {
209 handleParseError(parse_pos, error_reason);
210 }
211 }
212
213 private void handleParseError(ParsePosition parse_pos, String error_reason)
214 throws ParseException {
215 throw new ParseException("INVALID_XSD_DATETIME", parse_pos.getErrorIndex());
216 }
217
218 }