Skip to content

Commit 927354c

Browse files
committed
Improve support for parsing dates and include fallback to manual parsing routine
1 parent fdf552e commit 927354c

File tree

2 files changed

+181
-7
lines changed
  • src/AndroidClient/client/src

2 files changed

+181
-7
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package servicestack.net.client.tests;
2+
3+
import android.app.Application;
4+
import android.test.ApplicationTestCase;
5+
6+
import net.servicestack.client.Utils;
7+
8+
import java.text.SimpleDateFormat;
9+
import java.util.Date;
10+
11+
public class UtilsTests extends ApplicationTestCase<Application> {
12+
public UtilsTests() {
13+
super(Application.class);
14+
}
15+
16+
public void test_Can_parse_Date_with_SubMillis(){
17+
Date date = Utils.parseDate("2015-03-27T03:41:41.987375+00:00");
18+
19+
SimpleDateFormat dateFmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
20+
assertEquals("2015-03-27T03:41:41.987", dateFmt.format(date));
21+
}
22+
23+
public void test_Can_stripSubMillis(){
24+
assertEquals("2015-03-27T03:41:41.987+00:00",Utils.stripSubMillis("2015-03-27T03:41:41.9873754+00:00"));
25+
assertEquals("2015-03-27T03:41:41.987+00:00",Utils.stripSubMillis("2015-03-27T03:41:41.987375+00:00"));
26+
assertEquals("2015-03-27T03:41:41.987+00:00",Utils.stripSubMillis("2015-03-27T03:41:41.98737+00:00"));
27+
assertEquals("2015-03-27T03:41:41.987+00:00",Utils.stripSubMillis("2015-03-27T03:41:41.9873+00:00"));
28+
assertEquals("2015-03-27T03:41:41.987+00:00",Utils.stripSubMillis("2015-03-27T03:41:41.987+00:00"));
29+
}
30+
}

src/AndroidClient/client/src/main/java/net/servicestack/client/Utils.java

Lines changed: 151 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
package net.servicestack.client;
44

5-
import com.google.gson.Gson;
6-
75
import org.json.JSONArray;
86
import org.json.JSONException;
97
import org.json.JSONObject;
@@ -13,13 +11,17 @@
1311
import java.io.InputStream;
1412
import java.io.InputStreamReader;
1513
import java.io.UnsupportedEncodingException;
14+
import java.lang.reflect.Field;
15+
import java.lang.reflect.Modifier;
1616
import java.net.HttpURLConnection;
1717
import java.nio.ByteBuffer;
18-
import java.text.ParseException;
1918
import java.text.SimpleDateFormat;
2019
import java.util.ArrayList;
20+
import java.util.Arrays;
21+
import java.util.Calendar;
2122
import java.util.Date;
2223
import java.util.Iterator;
24+
import java.util.List;
2325
import java.util.UUID;
2426

2527
// Generic Utils
@@ -161,6 +163,14 @@ protected SimpleDateFormat initialValue()
161163
}
162164
};
163165

166+
private static final ThreadLocal<SimpleDateFormat> iso8601FormatterWithMs = new ThreadLocal<SimpleDateFormat>(){
167+
@Override
168+
protected SimpleDateFormat initialValue()
169+
{
170+
return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
171+
}
172+
};
173+
164174
public static String toJsonDate(Date date) {
165175
return "/Date(" + date.getTime() + "-0000)/";
166176
}
@@ -180,19 +190,153 @@ public static Date parseDate(String string) {
180190
return fromIsoDateString(string);
181191
}
182192

193+
public final static int isoDateLength = "YYYY-MM-DDT00:00:00+00:00".length();
194+
public final static int isoDateWithMsLength = "YYYY-MM-DDT00:00:00.000+00:00".length();
195+
196+
public final static int isoDateWithSubMsMin = "YYYY-MM-DDT00:00:00.0000+00:00".length();
197+
public final static int isoDateWithSubMsMax = "YYYY-MM-DDT00:00:00.0000000+00:00".length();
198+
199+
public static String stripSubMillis(String iso8601string){
200+
if (iso8601string.length() < isoDateWithSubMsMin || iso8601string.length() > isoDateWithSubMsMax)
201+
return iso8601string;
202+
203+
String[] parts = splitOnFirst(iso8601string, '.');
204+
205+
String suffix = parts[1].substring(parts[1].length() - 6); //+00:00
206+
String ms = parts[1].substring(0, 3);
207+
208+
return parts[0] + "." + ms + suffix;
209+
}
210+
183211
public static Date fromIsoDateString(String iso8601string){
184212
if (iso8601string == null)
185213
return null;
186214

187215
String s = iso8601string.replace("Z", "+00:00");
188216
try {
189217
s = s.substring(0, 22) + s.substring(23); // to get rid of the ":"
218+
s = stripSubMillis(s);
219+
220+
if (s.length() == isoDateWithMsLength)
221+
return iso8601FormatterWithMs.get().parse(s);
222+
190223
return iso8601Formatter.get().parse(s);
191-
} catch (IndexOutOfBoundsException e) {
192-
throw new RuntimeException("Invalid length");
193-
} catch (ParseException e) {
194-
throw new RuntimeException(e);
224+
225+
} catch (Exception e) {
226+
return ParseManual(iso8601string);
227+
}
228+
}
229+
230+
public static Date ParseManual(String dateTimeStr)
231+
{
232+
if (dateTimeStr == null || dateTimeStr.length() < "yyyy-MM-dd".length())
233+
return null;
234+
235+
if (dateTimeStr.endsWith("Z"))
236+
dateTimeStr = dateTimeStr.substring(0, dateTimeStr.length() - 1);
237+
238+
String[] parts = dateTimeStr.split("T");
239+
if (parts.length == 1)
240+
parts = Utils.splitOnFirst(dateTimeStr, ' ');
241+
242+
String[] dateParts = parts[0].split("-");
243+
int hh = 0, min = 0, ss = 0, ms = 0;
244+
double subMs = 0;
245+
int offsetMultiplier = 0;
246+
247+
if (parts.length == 1)
248+
{
249+
return new Date(
250+
Utils.tryParseInt(dateParts[0]) - 1900,
251+
Utils.tryParseInt(dateParts[1]) - 1,
252+
Utils.tryParseInt(dateParts[2]),
253+
0, 0, 0);
195254
}
255+
else if (parts.length == 2)
256+
{
257+
String[] timeStringParts = parts[1].split("\\+");
258+
if (timeStringParts.length == 2)
259+
{
260+
offsetMultiplier = -1;
261+
}
262+
else
263+
{
264+
timeStringParts = parts[1].split("-");
265+
if (timeStringParts.length == 2)
266+
{
267+
offsetMultiplier = 1;
268+
}
269+
}
270+
271+
String timeOffset = timeStringParts.length == 2 ? timeStringParts[1] : null;
272+
String[] timeParts = timeStringParts[0].split(":");
273+
274+
if (timeParts.length == 3)
275+
{
276+
Integer val = null;
277+
if ((val = Utils.tryParseInt(timeParts[0])) != null)
278+
hh = val;
279+
280+
if ((val = Utils.tryParseInt(timeParts[1])) != null)
281+
min = val;
282+
283+
String[] secParts = timeParts[2].split("\\.");
284+
285+
if ((val = Utils.tryParseInt(secParts[0])) != null)
286+
ss = val;
287+
288+
if (secParts.length == 2)
289+
{
290+
String msStr = String.format("%03d", Utils.tryParseInt(secParts[1]));
291+
ms = Utils.tryParseInt(msStr.substring(0, 3));
292+
293+
if (msStr.length() > 3)
294+
{
295+
String subMsStr = msStr.substring(3);
296+
// subMs = Utils.tryParseDouble(subMsStr) / Math.pow(10, subMsStr.length());
297+
}
298+
}
299+
}
300+
301+
Date dateTime = new Date(
302+
Utils.tryParseInt(dateParts[0]) - 1900,
303+
Utils.tryParseInt(dateParts[1]) - 1,
304+
Utils.tryParseInt(dateParts[2]),
305+
hh, min, ss);
306+
307+
Calendar cal = Calendar.getInstance();
308+
cal.setTime(dateTime);
309+
310+
if (ms > 0){
311+
cal.add(Calendar.MILLISECOND, ms);
312+
}
313+
314+
// if (subMs != 0)
315+
// dateTime=dateTime.AddMilliseconds(subMs); //Doesn't support sub millis
316+
317+
if (offsetMultiplier != 0 && timeOffset != null)
318+
{
319+
timeParts = timeOffset.split(":");
320+
if (timeParts.length == 2)
321+
{
322+
hh = Utils.tryParseInt(timeParts[0]);
323+
min = Utils.tryParseInt(timeParts[1]);
324+
}
325+
else
326+
{
327+
hh = Utils.tryParseInt(timeOffset.substring(0, 2));
328+
min = Utils.tryParseInt(timeOffset.substring(2));
329+
}
330+
331+
cal.add(Calendar.HOUR, offsetMultiplier * hh);
332+
cal.add(Calendar.MINUTE, offsetMultiplier * min);
333+
}
334+
335+
dateTime = cal.getTime();
336+
return dateTime;
337+
}
338+
339+
return null;
196340
}
197341

198342
/*String Utils*/

0 commit comments

Comments
 (0)