Home | History | Annotate | Download | only in shadows
      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