Home | History | Annotate | Download | only in shadows
      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 
     26     public void __constructor__() {
     27         __constructor__(getCurrentTimezone());
     28     }
     29 
     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 
     40     public void __constructor__(Time other) {
     41         set(other);
     42     }
     43 
     44     @Implementation
     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
     60     public void setToNow() {
     61         set(System.currentTimeMillis());
     62     }
     63 
     64 
     65     @Implementation
     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
     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
     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
    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
    117     public long toMillis(boolean ignoreDst) {
    118         Calendar c = getCalendar();
    119         return c.getTimeInMillis();
    120     }
    121 
    122     @Implementation
    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
    137     public void set(int monthDay, int month, int year) {
    138         set(0, 0, 0, monthDay, month, year);
    139     }
    140 
    141     @Implementation
    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
    161     public static String getCurrentTimezone() {
    162         return TimeZone.getDefault().getID();
    163     }
    164 
    165     @Implementation
    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
    179     public boolean before(Time other) {
    180         return Time.compare(time, other) < 0;
    181     }
    182 
    183     @Implementation
    184     public boolean after(Time other) {
    185         return Time.compare(time, other) > 0;
    186     }
    187 
    188     @Implementation
    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
    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
    222     public String format2445() {
    223         return format("%Y%m%dT%H%M%S");
    224     }
    225 
    226     @Implementation
    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 
    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 
    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          */
    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          */
    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          */
    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          */
    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          */
    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          */
    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 
    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          */
    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