• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.shadows;
2 
3 import static android.os.Build.VERSION_CODES.KITKAT_WATCH;
4 import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;
5 
6 import android.text.format.Time;
7 import android.util.TimeFormatException;
8 import java.text.ParseException;
9 import java.text.SimpleDateFormat;
10 import java.util.Calendar;
11 import java.util.Date;
12 import java.util.Locale;
13 import java.util.TimeZone;
14 import org.robolectric.annotation.Implementation;
15 import org.robolectric.annotation.Implements;
16 import org.robolectric.annotation.RealObject;
17 import org.robolectric.util.ReflectionHelpers;
18 import org.robolectric.util.Strftime;
19 
20 @Implements(value = Time.class)
21 public class ShadowTime {
22   @RealObject
23   private Time time;
24 
25   @Implementation(maxSdk = KITKAT_WATCH)
setToNow()26   protected void setToNow() {
27     time.set(ShadowSystemClock.currentTimeMillis());
28   }
29 
30   private static final long SECOND_IN_MILLIS = 1000;
31   private static final long MINUTE_IN_MILLIS = SECOND_IN_MILLIS * 60;
32   private static final long HOUR_IN_MILLIS = MINUTE_IN_MILLIS * 60;
33   private static final long DAY_IN_MILLIS = HOUR_IN_MILLIS * 24;
34 
35   @Implementation(maxSdk = KITKAT_WATCH)
__constructor__()36   protected void __constructor__() {
37     __constructor__(getCurrentTimezone());
38   }
39 
40   @Implementation(maxSdk = KITKAT_WATCH)
__constructor__(String timezone)41   protected void __constructor__(String timezone) {
42     if (timezone == null) {
43       throw new NullPointerException("timezone is null!");
44     }
45     time.timezone = timezone;
46     time.year = 1970;
47     time.monthDay = 1;
48     time.isDst = -1;
49   }
50 
51   @Implementation(maxSdk = KITKAT_WATCH)
__constructor__(Time other)52   protected void __constructor__(Time other) {
53     set(other);
54   }
55 
56   @Implementation(maxSdk = KITKAT_WATCH)
set(Time other)57   protected void set(Time other) {
58     time.timezone = other.timezone;
59     time.second = other.second;
60     time.minute = other.minute;
61     time.hour = other.hour;
62     time.monthDay = other.monthDay;
63     time.month = other.month;
64     time.year = other.year;
65     time.weekDay = other.weekDay;
66     time.yearDay = other.yearDay;
67     time.isDst = other.isDst;
68     time.gmtoff = other.gmtoff;
69   }
70 
71   @Implementation(maxSdk = KITKAT_WATCH)
isEpoch(Time time)72   protected static boolean isEpoch(Time time) {
73     long millis = time.toMillis(true);
74     return getJulianDay(millis, 0) == Time.EPOCH_JULIAN_DAY;
75   }
76 
77   @Implementation(maxSdk = KITKAT_WATCH)
getJulianDay(long millis, long gmtoff)78   protected static int getJulianDay(long millis, long gmtoff) {
79     long offsetMillis = gmtoff * 1000;
80     long julianDay = (millis + offsetMillis) / DAY_IN_MILLIS;
81     return (int) julianDay + Time.EPOCH_JULIAN_DAY;
82   }
83 
84   @Implementation(maxSdk = KITKAT_WATCH)
setJulianDay(int julianDay)85   protected long setJulianDay(int julianDay) {
86     // Don't bother with the GMT offset since we don't know the correct
87     // value for the given Julian day.  Just get close and then adjust
88     // the day.
89     //long millis = (julianDay - EPOCH_JULIAN_DAY) * DateUtils.DAY_IN_MILLIS;
90     long millis = (julianDay - Time.EPOCH_JULIAN_DAY) * DAY_IN_MILLIS;
91     set(millis);
92 
93     // Figure out how close we are to the requested Julian day.
94     // We can't be off by more than a day.
95     int approximateDay = getJulianDay(millis, time.gmtoff);
96     int diff = julianDay - approximateDay;
97     time.monthDay += diff;
98 
99     // Set the time to 12am and re-normalize.
100     time.hour = 0;
101     time.minute = 0;
102     time.second = 0;
103     millis = time.normalize(true);
104     return millis;
105   }
106 
107   @Implementation(maxSdk = KITKAT_WATCH)
set(long millis)108   protected void set(long millis) {
109     Calendar c = getCalendar();
110     c.setTimeInMillis(millis);
111     set(
112         c.get(Calendar.SECOND),
113         c.get(Calendar.MINUTE),
114         c.get(Calendar.HOUR_OF_DAY),
115         c.get(Calendar.DAY_OF_MONTH),
116         c.get(Calendar.MONTH),
117         c.get(Calendar.YEAR)
118         );
119   }
120 
121   @Implementation(maxSdk = KITKAT_WATCH)
toMillis(boolean ignoreDst)122   protected long toMillis(boolean ignoreDst) {
123     Calendar c = getCalendar();
124     return c.getTimeInMillis();
125   }
126 
127   @Implementation(maxSdk = KITKAT_WATCH)
set(int second, int minute, int hour, int monthDay, int month, int year)128   protected void set(int second, int minute, int hour, int monthDay, int month, int year) {
129     time.second = second;
130     time.minute = minute;
131     time.hour = hour;
132     time.monthDay = monthDay;
133     time.month = month;
134     time.year = year;
135     time.weekDay = 0;
136     time.yearDay = 0;
137     time.isDst = -1;
138     time.gmtoff = 0;
139   }
140 
141   @Implementation(maxSdk = KITKAT_WATCH)
set(int monthDay, int month, int year)142   protected void set(int monthDay, int month, int year) {
143     set(0, 0, 0, monthDay, month, year);
144   }
145 
146   @Implementation(maxSdk = KITKAT_WATCH)
clear(String timezone)147   protected void clear(String timezone) {
148     if (timezone == null) {
149       throw new NullPointerException("timezone is null!");
150     }
151     time.timezone = timezone;
152     time.allDay = false;
153     time.second = 0;
154     time.minute = 0;
155     time.hour = 0;
156     time.monthDay = 0;
157     time.month = 0;
158     time.year = 0;
159     time.weekDay = 0;
160     time.yearDay = 0;
161     time.gmtoff = 0;
162     time.isDst = -1;
163   }
164 
165   @Implementation(maxSdk = KITKAT_WATCH)
getCurrentTimezone()166   protected static String getCurrentTimezone() {
167     return TimeZone.getDefault().getID();
168   }
169 
170   @Implementation(maxSdk = KITKAT_WATCH)
switchTimezone(String timezone)171   protected void switchTimezone(String timezone) {
172     long date = toMillis(true);
173     long gmtoff = TimeZone.getTimeZone(timezone).getOffset(date);
174     set(date + gmtoff);
175     time.timezone = timezone;
176     time.gmtoff = (gmtoff / 1000);
177   }
178 
179   @Implementation(maxSdk = KITKAT_WATCH)
compare(Time a, Time b)180   protected static int compare(Time a, Time b) {
181     long ams = a.toMillis(false);
182     long bms = b.toMillis(false);
183     if (ams == bms) {
184       return 0;
185     } else if (ams < bms) {
186       return -1;
187     } else {
188       return 1;
189     }
190   }
191 
192   @Implementation(maxSdk = KITKAT_WATCH)
before(Time other)193   protected boolean before(Time other) {
194     return Time.compare(time, other) < 0;
195   }
196 
197   @Implementation(maxSdk = KITKAT_WATCH)
after(Time other)198   protected boolean after(Time other) {
199     return Time.compare(time, other) > 0;
200   }
201 
202   @Implementation(maxSdk = KITKAT_WATCH)
parse(String timeString)203   protected boolean parse(String timeString) {
204     TimeZone tz;
205     if (timeString.endsWith("Z")) {
206       timeString = timeString.substring(0, timeString.length() - 1);
207       tz = TimeZone.getTimeZone("UTC");
208     } else {
209       tz = TimeZone.getTimeZone(time.timezone);
210     }
211     SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd'T'HHmmss", Locale.ENGLISH);
212     SimpleDateFormat dfShort = new SimpleDateFormat("yyyyMMdd", Locale.ENGLISH);
213     df.setTimeZone(tz);
214     dfShort.setTimeZone(tz);
215     time.timezone = tz.getID();
216     try {
217       set(df.parse(timeString).getTime());
218     } catch (ParseException e) {
219       try {
220         set(dfShort.parse(timeString).getTime());
221       } catch (ParseException e2) {
222         throwTimeFormatException(e2.getLocalizedMessage());
223       }
224     }
225     return "UTC".equals(tz.getID());
226   }
227 
228   @Implementation(maxSdk = KITKAT_WATCH)
format2445()229   protected String format2445() {
230     String value = format("%Y%m%dT%H%M%S");
231     if ( "UTC".equals(time.timezone)){
232       value += "Z";
233     }
234     return value;
235   }
236 
237   @Implementation(maxSdk = KITKAT_WATCH)
format3339(boolean allDay)238   protected String format3339(boolean allDay) {
239     if (allDay) {
240       return format("%Y-%m-%d");
241     } else if ("UTC".equals(time.timezone)) {
242       return format("%Y-%m-%dT%H:%M:%S.000Z");
243     } else {
244       String base = format("%Y-%m-%dT%H:%M:%S.000");
245       String sign = (time.gmtoff < 0) ? "-" : "+";
246       int offset = (int) Math.abs(time.gmtoff);
247       int minutes = (offset % 3600) / 60;
248       int hours = offset / 3600;
249       return String.format("%s%s%02d:%02d", base, sign, hours, minutes);
250     }
251   }
252 
253   @Implementation(maxSdk = KITKAT_WATCH)
parse3339(String rfc3339String)254   protected boolean parse3339(String rfc3339String) {
255     SimpleDateFormat formatter =  new SimpleDateFormat();
256     // Special case Date without time first
257     if (rfc3339String.matches("\\d{4}-\\d{2}-\\d{2}")) {
258       final TimeZone tz = TimeZone.getTimeZone(time.timezone);
259       formatter.applyLocalizedPattern("yyyy-MM-dd");
260       // Make sure we inferFromValue the date in the context of the specified time zone
261       // instead of the system default time zone.
262       formatter.setTimeZone(tz);
263       Calendar calendar = Calendar.getInstance(tz, Locale.getDefault());
264       try {
265         calendar.setTime(formatter.parse(rfc3339String));
266       } catch (java.text.ParseException e) {
267         throwTimeFormatException(e.getLocalizedMessage());
268       }
269       time.second = time.minute = time.hour = 0;
270       time.monthDay = calendar.get(Calendar.DAY_OF_MONTH);
271       time.month = calendar.get(Calendar.MONTH);
272       time.year = calendar.get(Calendar.YEAR);
273       time.weekDay = calendar.get(Calendar.DAY_OF_WEEK) - Calendar.SUNDAY;
274       time.yearDay = calendar.get(Calendar.DAY_OF_YEAR);
275       time.isDst = calendar.get(Calendar.DST_OFFSET) != 0 ? 1 : 0;
276       time.allDay = true;
277       return false;
278     }
279 
280     // Store a string normalized for SimpleDateFormat;
281     String dateString = rfc3339String
282         // Look-ahead to remove the colon followed by minutes in timezone
283         .replaceFirst(":(?=\\d{2}$)", "")
284         // Look-behind to pad with minutes any timezone only defines hours
285         .replaceFirst("(?<=[+-]\\d{2})$", "00")
286         // If it ends with a Z, just replace it with no offset
287         .replaceFirst("(Z)$", "+0000");
288 
289     formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
290     formatter.applyLocalizedPattern("yyyy-MM-dd'T'HH:mm:ssZ");
291     long millisInUtc = time.toMillis(false);
292     try {
293       millisInUtc = formatter.parse(dateString).getTime();
294     } catch (java.text.ParseException e1) {
295       // Try again with fractional seconds.
296       formatter.applyLocalizedPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
297       formatter.setLenient(true);
298       try {
299         millisInUtc = formatter.parse(dateString).getTime();
300       } catch (java.text.ParseException e2) {
301         throwTimeFormatException(e2.getLocalizedMessage());
302       }
303     }
304     // Clear to UTC, then set time;
305     clear("UTC");
306     set(millisInUtc);
307     return true;
308   }
309 
throwTimeFormatException(String optionalMessage)310   private void throwTimeFormatException(String optionalMessage) {
311     throw ReflectionHelpers.callConstructor(TimeFormatException.class, from(String.class, optionalMessage == null ? "fail" : optionalMessage));
312   }
313 
314   @Implementation(maxSdk = KITKAT_WATCH)
format(String format)315   protected String format(String format) {
316     return Strftime.format(format, new Date(toMillis(false)), Locale.getDefault(), TimeZone.getTimeZone(time.timezone));
317   }
318 
getCalendar()319   private Calendar getCalendar() {
320     Calendar c = Calendar.getInstance(TimeZone.getTimeZone(time.timezone));
321     c.set(time.year, time.month, time.monthDay, time.hour, time.minute, time.second);
322     c.set(Calendar.MILLISECOND, 0);
323     return c;
324   }
325 }
326