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) 26 public 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) 36 public void __constructor__() { 37 __constructor__(getCurrentTimezone()); 38 } 39 40 @Implementation(maxSdk = KITKAT_WATCH) 41 public 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) 52 public void __constructor__(Time other) { 53 set(other); 54 } 55 56 @Implementation(maxSdk = KITKAT_WATCH) 57 public 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) 72 public 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) 78 public 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) 85 public 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) 108 public 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) 122 public long toMillis(boolean ignoreDst) { 123 Calendar c = getCalendar(); 124 return c.getTimeInMillis(); 125 } 126 127 @Implementation(maxSdk = KITKAT_WATCH) 128 public 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) 142 public void set(int monthDay, int month, int year) { 143 set(0, 0, 0, monthDay, month, year); 144 } 145 146 @Implementation(maxSdk = KITKAT_WATCH) 147 public 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) 166 public static String getCurrentTimezone() { 167 return TimeZone.getDefault().getID(); 168 } 169 170 @Implementation(maxSdk = KITKAT_WATCH) 171 public 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) 180 public 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) 193 public boolean before(Time other) { 194 return Time.compare(time, other) < 0; 195 } 196 197 @Implementation(maxSdk = KITKAT_WATCH) 198 public boolean after(Time other) { 199 return Time.compare(time, other) > 0; 200 } 201 202 @Implementation(maxSdk = KITKAT_WATCH) 203 public 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) 229 public 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) 238 public 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) 254 public 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 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) 315 public String format(String format) { 316 return Strftime.format(format, new Date(toMillis(false)), Locale.getDefault(), TimeZone.getTimeZone(time.timezone)); 317 } 318 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