• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.xtremelabs.robolectric.shadows;
2 
3 
4 import android.text.format.Time;
5 import android.util.TimeFormatException;
6 import com.xtremelabs.robolectric.internal.Implementation;
7 import com.xtremelabs.robolectric.internal.Implements;
8 import com.xtremelabs.robolectric.internal.RealObject;
9 
10 import java.lang.reflect.Constructor;
11 import java.lang.reflect.InvocationTargetException;
12 import java.text.ParseException;
13 import java.text.SimpleDateFormat;
14 import java.util.*;
15 
16 @Implements(Time.class)
17 public class ShadowTime {
18     @RealObject
19     private Time time;
20 
21     private static final long SECOND_IN_MILLIS = 1000;
22     private static final long MINUTE_IN_MILLIS = SECOND_IN_MILLIS * 60;
23     private static final long HOUR_IN_MILLIS = MINUTE_IN_MILLIS * 60;
24     private static final long DAY_IN_MILLIS = HOUR_IN_MILLIS * 24;
25 
__constructor__()26     public void __constructor__() {
27         __constructor__(getCurrentTimezone());
28     }
29 
__constructor__(String timezone)30     public void __constructor__(String timezone) {
31         if (timezone == null) {
32             throw new NullPointerException("timezone is null!");
33         }
34         time.timezone = timezone;
35         time.year = 1970;
36         time.monthDay = 1;
37         time.isDst = -1;
38     }
39 
__constructor__(Time other)40     public void __constructor__(Time other) {
41         set(other);
42     }
43 
44     @Implementation
set(Time other)45     public void set(Time other) {
46         time.timezone = other.timezone;
47         time.second = other.second;
48         time.minute = other.minute;
49         time.hour = other.hour;
50         time.monthDay = other.monthDay;
51         time.month = other.month;
52         time.year = other.year;
53         time.weekDay = other.weekDay;
54         time.yearDay = other.yearDay;
55         time.isDst = other.isDst;
56         time.gmtoff = other.gmtoff;
57     }
58 
59     @Implementation
setToNow()60     public void setToNow() {
61         set(System.currentTimeMillis());
62     }
63 
64 
65     @Implementation
isEpoch(Time time)66     public static boolean isEpoch(Time time) {
67         long millis = time.toMillis(true);
68         return getJulianDay(millis, 0) == Time.EPOCH_JULIAN_DAY;
69     }
70 
71 
72     @Implementation
getJulianDay(long millis, long gmtoff)73     public static int getJulianDay(long millis, long gmtoff) {
74         long offsetMillis = gmtoff * 1000;
75         long julianDay = (millis + offsetMillis) / DAY_IN_MILLIS;
76         return (int) julianDay + Time.EPOCH_JULIAN_DAY;
77     }
78 
79     @Implementation
setJulianDay(int julianDay)80     public long setJulianDay(int julianDay) {
81         // Don't bother with the GMT offset since we don't know the correct
82         // value for the given Julian day.  Just get close and then adjust
83         // the day.
84         //long millis = (julianDay - EPOCH_JULIAN_DAY) * DateUtils.DAY_IN_MILLIS;
85         long millis = (julianDay - Time.EPOCH_JULIAN_DAY) * DAY_IN_MILLIS;
86         set(millis);
87 
88         // Figure out how close we are to the requested Julian day.
89         // We can't be off by more than a day.
90         int approximateDay = getJulianDay(millis, time.gmtoff);
91         int diff = julianDay - approximateDay;
92         time.monthDay += diff;
93 
94         // Set the time to 12am and re-normalize.
95         time.hour = 0;
96         time.minute = 0;
97         time.second = 0;
98         millis = time.normalize(true);
99         return millis;
100     }
101 
102     @Implementation
set(long millis)103     public void set(long millis) {
104         Calendar c = getCalendar();
105         c.setTimeInMillis(millis);
106         set(
107                 c.get(Calendar.SECOND),
108                 c.get(Calendar.MINUTE),
109                 c.get(Calendar.HOUR_OF_DAY),
110                 c.get(Calendar.DAY_OF_MONTH),
111                 c.get(Calendar.MONTH),
112                 c.get(Calendar.YEAR)
113         );
114     }
115 
116     @Implementation
toMillis(boolean ignoreDst)117     public long toMillis(boolean ignoreDst) {
118         Calendar c = getCalendar();
119         return c.getTimeInMillis();
120     }
121 
122     @Implementation
set(int second, int minute, int hour, int monthDay, int month, int year)123     public void set(int second, int minute, int hour, int monthDay, int month, int year) {
124         time.second = second;
125         time.minute = minute;
126         time.hour = hour;
127         time.monthDay = monthDay;
128         time.month = month;
129         time.year = year;
130         time.weekDay = 0;
131         time.yearDay = 0;
132         time.isDst = -1;
133         time.gmtoff = 0;
134     }
135 
136     @Implementation
set(int monthDay, int month, int year)137     public void set(int monthDay, int month, int year) {
138         set(0, 0, 0, monthDay, month, year);
139     }
140 
141     @Implementation
clear(String timezone)142     public void clear(String timezone) {
143         if (timezone == null) {
144             throw new NullPointerException("timezone is null!");
145         }
146         time.timezone = timezone;
147         time.allDay = false;
148         time.second = 0;
149         time.minute = 0;
150         time.hour = 0;
151         time.monthDay = 0;
152         time.month = 0;
153         time.year = 0;
154         time.weekDay = 0;
155         time.yearDay = 0;
156         time.gmtoff = 0;
157         time.isDst = -1;
158     }
159 
160     @Implementation
getCurrentTimezone()161     public static String getCurrentTimezone() {
162         return TimeZone.getDefault().getID();
163     }
164 
165     @Implementation
compare(Time a, Time b)166     public static int compare(Time a, Time b) {
167         long ams = a.toMillis(false);
168         long bms = b.toMillis(false);
169         if (ams == bms) {
170             return 0;
171         } else if (ams < bms) {
172             return -1;
173         } else {
174             return 1;
175         }
176     }
177 
178     @Implementation
before(Time other)179     public boolean before(Time other) {
180         return Time.compare(time, other) < 0;
181     }
182 
183     @Implementation
after(Time other)184     public boolean after(Time other) {
185         return Time.compare(time, other) > 0;
186     }
187 
188     @Implementation
parse(String timeString)189     public boolean parse(String timeString) {
190         TimeZone tz;
191         if (timeString.endsWith("Z")) {
192             timeString = timeString.substring(0, timeString.length() - 1);
193             tz = TimeZone.getTimeZone("UTC");
194         } else {
195             tz = TimeZone.getTimeZone(time.timezone);
196         }
197         SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd'T'HHmmss", Locale.ENGLISH);
198         SimpleDateFormat dfShort = new SimpleDateFormat("yyyyMMdd", Locale.ENGLISH);
199         df.setTimeZone(tz);
200         dfShort.setTimeZone(tz);
201         time.timezone = tz.getID();
202         try {
203             set(df.parse(timeString).getTime());
204         } catch (ParseException e) {
205             try {
206                 set(dfShort.parse(timeString).getTime());
207             } catch (ParseException e2) {
208                 throwTimeFormatException();
209             }
210         }
211         return "UTC".equals(tz.getID());
212     }
213 
214     @Implementation
format(String format)215     public String format(String format) {
216         Strftime strftime = new Strftime(format, Locale.getDefault());
217         strftime.setTimeZone(TimeZone.getTimeZone(time.timezone));
218         return strftime.format(new Date(toMillis(false)));
219     }
220 
221     @Implementation
format2445()222     public String format2445() {
223         return format("%Y%m%dT%H%M%S");
224     }
225 
226     @Implementation
format3339(boolean allDay)227     public String format3339(boolean allDay) {
228         if (allDay) {
229             return format("%Y-%m-%d");
230         } else if ("UTC".equals(time.timezone)) {
231             return format("%Y-%m-%dT%H:%M:%S.000Z");
232         } else {
233             String base = format("%Y-%m-%dT%H:%M:%S.000");
234             String sign = (time.gmtoff < 0) ? "-" : "+";
235             int offset = (int) Math.abs(time.gmtoff);
236             int minutes = (offset % 3600) / 60;
237             int hours = offset / 3600;
238             return String.format("%s%s%02d:%02d", base, sign, hours, minutes);
239         }
240     }
241 
throwTimeFormatException()242     private void throwTimeFormatException() {
243         try {
244             Constructor<TimeFormatException> c = TimeFormatException.class.getDeclaredConstructor();
245             c.setAccessible(true);
246             throw c.newInstance();
247         } catch (InvocationTargetException e) {
248             throw new RuntimeException(e);
249         } catch (InstantiationException e) {
250             throw new RuntimeException(e);
251         } catch (IllegalAccessException e) {
252             throw new RuntimeException(e);
253         } catch (NoSuchMethodException e) {
254             throw new RuntimeException(e);
255         }
256     }
257 
getCalendar()258     private Calendar getCalendar() {
259         Calendar c = Calendar.getInstance(TimeZone.getTimeZone(time.timezone));
260         c.set(time.year, time.month, time.monthDay, time.hour, time.minute, time.second);
261         c.set(Calendar.MILLISECOND, 0);
262         return c;
263     }
264 
265     // taken from org.apache.catalina.util.Strftime.java
266     // see http://javasourcecode.org/html/open-source/tomcat/tomcat-6.0.32/org/apache/catalina/util/Strftime.java.html
267     /*
268     * Licensed to the Apache Software Foundation (ASF) under one or more
269     * contributor license agreements.  See the NOTICE file distributed with
270     * this work for additional information regarding copyright ownership.
271     * The ASF licenses this file to You under the Apache License, Version 2.0
272     * (the "License"); you may not use this file except in compliance with
273     * the License.  You may obtain a copy of the License at
274     *
275     *      http://www.apache.org/licenses/LICENSE-2.0
276     *
277     * Unless required by applicable law or agreed to in writing, software
278     * distributed under the License is distributed on an "AS IS" BASIS,
279     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
280     * See the License for the specific language governing permissions and
281     * limitations under the License.
282     */
283     public static class Strftime {
284         protected static Properties translate;
285         protected SimpleDateFormat simpleDateFormat;
286 
287         /**
288          * Initialize our pattern translation
289          */
290         static {
291             translate = new Properties();
292             translate.put("a", "EEE");
293             translate.put("A", "EEEE");
294             translate.put("b", "MMM");
295             translate.put("B", "MMMM");
296             translate.put("c", "EEE MMM d HH:mm:ss yyyy");
297 
298             //There's no way to specify the century in SimpleDateFormat.  We don't want to hard-code
299             //20 since this could be wrong for the pre-2000 files.
300             //translate.put("C", "20");
301             translate.put("d", "dd");
302             translate.put("D", "MM/dd/yy");
303             translate.put("e", "dd"); //will show as '03' instead of ' 3'
304             translate.put("F", "yyyy-MM-dd");
305             translate.put("g", "yy");
306             translate.put("G", "yyyy");
307             translate.put("H", "HH");
308             translate.put("h", "MMM");
309             translate.put("I", "hh");
310             translate.put("j", "DDD");
311             translate.put("k", "HH"); //will show as '07' instead of ' 7'
312             translate.put("l", "hh"); //will show as '07' instead of ' 7'
313             translate.put("m", "MM");
314             translate.put("M", "mm");
315             translate.put("n", "\n");
316             translate.put("p", "a");
317             translate.put("P", "a");  //will show as pm instead of PM
318             translate.put("r", "hh:mm:ss a");
319             translate.put("R", "HH:mm");
320             //There's no way to specify this with SimpleDateFormat
321             //translate.put("s","seconds since ecpoch");
322             translate.put("S", "ss");
323             translate.put("t", "\t");
324             translate.put("T", "HH:mm:ss");
325             //There's no way to specify this with SimpleDateFormat
326             //translate.put("u","day of week ( 1-7 )");
327 
328             //There's no way to specify this with SimpleDateFormat
329             //translate.put("U","week in year with first sunday as first day...");
330 
331             translate.put("V", "ww"); //I'm not sure this is always exactly the same
332 
333             //There's no way to specify this with SimpleDateFormat
334             //translate.put("W","week in year with first monday as first day...");
335 
336             //There's no way to specify this with SimpleDateFormat
337             //translate.put("w","E");
338             translate.put("X", "HH:mm:ss");
339             translate.put("x", "MM/dd/yy");
340             translate.put("y", "yy");
341             translate.put("Y", "yyyy");
342             translate.put("Z", "z");
343             translate.put("z", "Z");
344             translate.put("%", "%");
345         }
346 
347 
348         /**
349          * Create an instance of this date formatting class
350          *
351          * @see #Strftime(String, Locale)
352          */
Strftime(String origFormat)353         public Strftime(String origFormat) {
354             String convertedFormat = convertDateFormat(origFormat);
355             simpleDateFormat = new SimpleDateFormat(convertedFormat);
356         }
357 
358         /**
359          * Create an instance of this date formatting class
360          *
361          * @param origFormat the strftime-style formatting string
362          * @param locale     the locale to use for locale-specific conversions
363          */
Strftime(String origFormat, Locale locale)364         public Strftime(String origFormat, Locale locale) {
365             String convertedFormat = convertDateFormat(origFormat);
366             simpleDateFormat = new SimpleDateFormat(convertedFormat, locale);
367         }
368 
369         /**
370          * Format the date according to the strftime-style string given in the constructor.
371          *
372          * @param date the date to format
373          * @return the formatted date
374          */
format(Date date)375         public String format(Date date) {
376             return simpleDateFormat.format(date);
377         }
378 
379         /**
380          * Get the timezone used for formatting conversions
381          *
382          * @return the timezone
383          */
getTimeZone()384         public TimeZone getTimeZone() {
385             return simpleDateFormat.getTimeZone();
386         }
387 
388         /**
389          * Change the timezone used to format dates
390          *
391          * @see SimpleDateFormat#setTimeZone
392          */
setTimeZone(TimeZone timeZone)393         public void setTimeZone(TimeZone timeZone) {
394             simpleDateFormat.setTimeZone(timeZone);
395         }
396 
397         /**
398          * Search the provided pattern and get the C standard
399          * Date/Time formatting rules and convert them to the
400          * Java equivalent.
401          *
402          * @param pattern The pattern to search
403          * @return The modified pattern
404          */
convertDateFormat(String pattern)405         protected String convertDateFormat(String pattern) {
406             boolean inside = false;
407             boolean mark = false;
408             boolean modifiedCommand = false;
409 
410             StringBuffer buf = new StringBuffer();
411 
412             for (int i = 0; i < pattern.length(); i++) {
413                 char c = pattern.charAt(i);
414 
415                 if (c == '%' && !mark) {
416                     mark = true;
417                 } else {
418                     if (mark) {
419                         if (modifiedCommand) {
420                             //don't do anything--we just wanted to skip a char
421                             modifiedCommand = false;
422                             mark = false;
423                         } else {
424                             inside = translateCommand(buf, pattern, i, inside);
425                             //It's a modifier code
426                             if (c == 'O' || c == 'E') {
427                                 modifiedCommand = true;
428                             } else {
429                                 mark = false;
430                             }
431                         }
432                     } else {
433                         if (!inside && c != ' ') {
434                             //We start a literal, which we need to quote
435                             buf.append("'");
436                             inside = true;
437                         }
438 
439                         buf.append(c);
440                     }
441                 }
442             }
443 
444             if (buf.length() > 0) {
445                 char lastChar = buf.charAt(buf.length() - 1);
446 
447                 if (lastChar != '\'' && inside) {
448                     buf.append('\'');
449                 }
450             }
451             return buf.toString();
452         }
453 
quote(String str, boolean insideQuotes)454         protected String quote(String str, boolean insideQuotes) {
455             String retVal = str;
456             if (!insideQuotes) {
457                 retVal = '\'' + retVal + '\'';
458             }
459             return retVal;
460         }
461 
462         /**
463          * Try to get the Java Date/Time formatting associated with
464          * the C standard provided.
465          *
466          * @param buf       The buffer
467          * @param pattern   The date/time pattern
468          * @param index     The char index
469          * @param oldInside Flag value
470          * @return True if new is inside buffer
471          */
translateCommand(StringBuffer buf, String pattern, int index, boolean oldInside)472         protected boolean translateCommand(StringBuffer buf, String pattern, int index, boolean oldInside) {
473             char firstChar = pattern.charAt(index);
474             boolean newInside = oldInside;
475 
476             //O and E are modifiers, they mean to present an alternative representation of the next char
477             //we just handle the next char as if the O or E wasn't there
478             if (firstChar == 'O' || firstChar == 'E') {
479                 if (index + 1 < pattern.length()) {
480                     newInside = translateCommand(buf, pattern, index + 1, oldInside);
481                 } else {
482                     buf.append(quote("%" + firstChar, oldInside));
483                 }
484             } else {
485                 String command = translate.getProperty(String.valueOf(firstChar));
486 
487                 //If we don't find a format, treat it as a literal--That's what apache does
488                 if (command == null) {
489                     buf.append(quote("%" + firstChar, oldInside));
490                 } else {
491                     //If we were inside quotes, close the quotes
492                     if (oldInside) {
493                         buf.append('\'');
494                     }
495                     buf.append(command);
496                     newInside = false;
497                 }
498             }
499             return newInside;
500         }
501     }
502 }
503