Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright (C) 2006 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.util;
     18 
     19 import android.annotation.NonNull;
     20 import android.annotation.Nullable;
     21 import android.annotation.TestApi;
     22 import android.annotation.UnsupportedAppUsage;
     23 import android.os.Build;
     24 import android.os.SystemClock;
     25 
     26 import libcore.timezone.CountryTimeZones;
     27 import libcore.timezone.CountryTimeZones.TimeZoneMapping;
     28 import libcore.timezone.TimeZoneFinder;
     29 import libcore.timezone.ZoneInfoDB;
     30 
     31 import java.io.PrintWriter;
     32 import java.text.SimpleDateFormat;
     33 import java.util.ArrayList;
     34 import java.util.Calendar;
     35 import java.util.Collections;
     36 import java.util.Date;
     37 import java.util.List;
     38 
     39 /**
     40  * A class containing utility methods related to time zones.
     41  */
     42 public class TimeUtils {
     43     /** @hide */ public TimeUtils() {}
     44     /** {@hide} */
     45     private static SimpleDateFormat sLoggingFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
     46 
     47     /** @hide */
     48     public static final SimpleDateFormat sDumpDateFormat =
     49             new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
     50     /**
     51      * Tries to return a time zone that would have had the specified offset
     52      * and DST value at the specified moment in the specified country.
     53      * Returns null if no suitable zone could be found.
     54      */
     55     public static java.util.TimeZone getTimeZone(
     56             int offset, boolean dst, long when, String country) {
     57 
     58         android.icu.util.TimeZone icuTimeZone = getIcuTimeZone(offset, dst, when, country);
     59         // We must expose a java.util.TimeZone here for API compatibility because this is a public
     60         // API method.
     61         return icuTimeZone != null ? java.util.TimeZone.getTimeZone(icuTimeZone.getID()) : null;
     62     }
     63 
     64     /**
     65      * Tries to return a frozen ICU time zone that would have had the specified offset
     66      * and DST value at the specified moment in the specified country.
     67      * Returns null if no suitable zone could be found.
     68      */
     69     private static android.icu.util.TimeZone getIcuTimeZone(
     70             int offset, boolean dst, long when, String country) {
     71         if (country == null) {
     72             return null;
     73         }
     74 
     75         android.icu.util.TimeZone bias = android.icu.util.TimeZone.getDefault();
     76         return TimeZoneFinder.getInstance()
     77                 .lookupTimeZoneByCountryAndOffset(country, offset, dst, when, bias);
     78     }
     79 
     80     /**
     81      * Returns time zone IDs for time zones known to be associated with a country.
     82      *
     83      * <p>The list returned may be different from other on-device sources like
     84      * {@link android.icu.util.TimeZone#getRegion(String)} as it can be curated to avoid
     85      * contentious mappings.
     86      *
     87      * @param countryCode the ISO 3166-1 alpha-2 code for the country as can be obtained using
     88      *     {@link java.util.Locale#getCountry()}
     89      * @return IDs that can be passed to {@link java.util.TimeZone#getTimeZone(String)} or similar
     90      *     methods, or {@code null} if the countryCode is unrecognized
     91      */
     92     public static @Nullable List<String> getTimeZoneIdsForCountryCode(@NonNull String countryCode) {
     93         if (countryCode == null) {
     94             throw new NullPointerException("countryCode == null");
     95         }
     96         TimeZoneFinder timeZoneFinder = TimeZoneFinder.getInstance();
     97         CountryTimeZones countryTimeZones =
     98                 timeZoneFinder.lookupCountryTimeZones(countryCode.toLowerCase());
     99         if (countryTimeZones == null) {
    100             return null;
    101         }
    102 
    103         List<String> timeZoneIds = new ArrayList<>();
    104         for (TimeZoneMapping timeZoneMapping : countryTimeZones.getTimeZoneMappings()) {
    105             if (timeZoneMapping.showInPicker) {
    106                 timeZoneIds.add(timeZoneMapping.timeZoneId);
    107             }
    108         }
    109         return Collections.unmodifiableList(timeZoneIds);
    110     }
    111 
    112     /**
    113      * Returns a String indicating the version of the time zone database currently
    114      * in use.  The format of the string is dependent on the underlying time zone
    115      * database implementation, but will typically contain the year in which the database
    116      * was updated plus a letter from a to z indicating changes made within that year.
    117      *
    118      * <p>Time zone database updates should be expected to occur periodically due to
    119      * political and legal changes that cannot be anticipated in advance.  Therefore,
    120      * when computing the UTC time for a future event, applications should be aware that
    121      * the results may differ following a time zone database update.  This method allows
    122      * applications to detect that a database change has occurred, and to recalculate any
    123      * cached times accordingly.
    124      *
    125      * <p>The time zone database may be assumed to change only when the device runtime
    126      * is restarted.  Therefore, it is not necessary to re-query the database version
    127      * during the lifetime of an activity.
    128      */
    129     public static String getTimeZoneDatabaseVersion() {
    130         return ZoneInfoDB.getInstance().getVersion();
    131     }
    132 
    133     /** @hide Field length that can hold 999 days of time */
    134     public static final int HUNDRED_DAY_FIELD_LEN = 19;
    135 
    136     private static final int SECONDS_PER_MINUTE = 60;
    137     private static final int SECONDS_PER_HOUR = 60 * 60;
    138     private static final int SECONDS_PER_DAY = 24 * 60 * 60;
    139 
    140     /** @hide */
    141     public static final long NANOS_PER_MS = 1000000;
    142 
    143     private static final Object sFormatSync = new Object();
    144     private static char[] sFormatStr = new char[HUNDRED_DAY_FIELD_LEN+10];
    145     private static char[] sTmpFormatStr = new char[HUNDRED_DAY_FIELD_LEN+10];
    146 
    147     static private int accumField(int amt, int suffix, boolean always, int zeropad) {
    148         if (amt > 999) {
    149             int num = 0;
    150             while (amt != 0) {
    151                 num++;
    152                 amt /= 10;
    153             }
    154             return num + suffix;
    155         } else {
    156             if (amt > 99 || (always && zeropad >= 3)) {
    157                 return 3+suffix;
    158             }
    159             if (amt > 9 || (always && zeropad >= 2)) {
    160                 return 2+suffix;
    161             }
    162             if (always || amt > 0) {
    163                 return 1+suffix;
    164             }
    165         }
    166         return 0;
    167     }
    168 
    169     static private int printFieldLocked(char[] formatStr, int amt, char suffix, int pos,
    170             boolean always, int zeropad) {
    171         if (always || amt > 0) {
    172             final int startPos = pos;
    173             if (amt > 999) {
    174                 int tmp = 0;
    175                 while (amt != 0 && tmp < sTmpFormatStr.length) {
    176                     int dig = amt % 10;
    177                     sTmpFormatStr[tmp] = (char)(dig + '0');
    178                     tmp++;
    179                     amt /= 10;
    180                 }
    181                 tmp--;
    182                 while (tmp >= 0) {
    183                     formatStr[pos] = sTmpFormatStr[tmp];
    184                     pos++;
    185                     tmp--;
    186                 }
    187             } else {
    188                 if ((always && zeropad >= 3) || amt > 99) {
    189                     int dig = amt/100;
    190                     formatStr[pos] = (char)(dig + '0');
    191                     pos++;
    192                     amt -= (dig*100);
    193                 }
    194                 if ((always && zeropad >= 2) || amt > 9 || startPos != pos) {
    195                     int dig = amt/10;
    196                     formatStr[pos] = (char)(dig + '0');
    197                     pos++;
    198                     amt -= (dig*10);
    199                 }
    200                 formatStr[pos] = (char)(amt + '0');
    201                 pos++;
    202             }
    203             formatStr[pos] = suffix;
    204             pos++;
    205         }
    206         return pos;
    207     }
    208 
    209     private static int formatDurationLocked(long duration, int fieldLen) {
    210         if (sFormatStr.length < fieldLen) {
    211             sFormatStr = new char[fieldLen];
    212         }
    213 
    214         char[] formatStr = sFormatStr;
    215 
    216         if (duration == 0) {
    217             int pos = 0;
    218             fieldLen -= 1;
    219             while (pos < fieldLen) {
    220                 formatStr[pos++] = ' ';
    221             }
    222             formatStr[pos] = '0';
    223             return pos+1;
    224         }
    225 
    226         char prefix;
    227         if (duration > 0) {
    228             prefix = '+';
    229         } else {
    230             prefix = '-';
    231             duration = -duration;
    232         }
    233 
    234         int millis = (int)(duration%1000);
    235         int seconds = (int) Math.floor(duration / 1000);
    236         int days = 0, hours = 0, minutes = 0;
    237 
    238         if (seconds >= SECONDS_PER_DAY) {
    239             days = seconds / SECONDS_PER_DAY;
    240             seconds -= days * SECONDS_PER_DAY;
    241         }
    242         if (seconds >= SECONDS_PER_HOUR) {
    243             hours = seconds / SECONDS_PER_HOUR;
    244             seconds -= hours * SECONDS_PER_HOUR;
    245         }
    246         if (seconds >= SECONDS_PER_MINUTE) {
    247             minutes = seconds / SECONDS_PER_MINUTE;
    248             seconds -= minutes * SECONDS_PER_MINUTE;
    249         }
    250 
    251         int pos = 0;
    252 
    253         if (fieldLen != 0) {
    254             int myLen = accumField(days, 1, false, 0);
    255             myLen += accumField(hours, 1, myLen > 0, 2);
    256             myLen += accumField(minutes, 1, myLen > 0, 2);
    257             myLen += accumField(seconds, 1, myLen > 0, 2);
    258             myLen += accumField(millis, 2, true, myLen > 0 ? 3 : 0) + 1;
    259             while (myLen < fieldLen) {
    260                 formatStr[pos] = ' ';
    261                 pos++;
    262                 myLen++;
    263             }
    264         }
    265 
    266         formatStr[pos] = prefix;
    267         pos++;
    268 
    269         int start = pos;
    270         boolean zeropad = fieldLen != 0;
    271         pos = printFieldLocked(formatStr, days, 'd', pos, false, 0);
    272         pos = printFieldLocked(formatStr, hours, 'h', pos, pos != start, zeropad ? 2 : 0);
    273         pos = printFieldLocked(formatStr, minutes, 'm', pos, pos != start, zeropad ? 2 : 0);
    274         pos = printFieldLocked(formatStr, seconds, 's', pos, pos != start, zeropad ? 2 : 0);
    275         pos = printFieldLocked(formatStr, millis, 'm', pos, true, (zeropad && pos != start) ? 3 : 0);
    276         formatStr[pos] = 's';
    277         return pos + 1;
    278     }
    279 
    280     /** @hide Just for debugging; not internationalized. */
    281     public static void formatDuration(long duration, StringBuilder builder) {
    282         synchronized (sFormatSync) {
    283             int len = formatDurationLocked(duration, 0);
    284             builder.append(sFormatStr, 0, len);
    285         }
    286     }
    287 
    288     /** @hide Just for debugging; not internationalized. */
    289     public static void formatDuration(long duration, StringBuilder builder, int fieldLen) {
    290         synchronized (sFormatSync) {
    291             int len = formatDurationLocked(duration, fieldLen);
    292             builder.append(sFormatStr, 0, len);
    293         }
    294     }
    295 
    296     /** @hide Just for debugging; not internationalized. */
    297     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
    298     public static void formatDuration(long duration, PrintWriter pw, int fieldLen) {
    299         synchronized (sFormatSync) {
    300             int len = formatDurationLocked(duration, fieldLen);
    301             pw.print(new String(sFormatStr, 0, len));
    302         }
    303     }
    304 
    305     /** @hide Just for debugging; not internationalized. */
    306     @TestApi
    307     public static String formatDuration(long duration) {
    308         synchronized (sFormatSync) {
    309             int len = formatDurationLocked(duration, 0);
    310             return new String(sFormatStr, 0, len);
    311         }
    312     }
    313 
    314     /** @hide Just for debugging; not internationalized. */
    315     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
    316     public static void formatDuration(long duration, PrintWriter pw) {
    317         formatDuration(duration, pw, 0);
    318     }
    319 
    320     /** @hide Just for debugging; not internationalized. */
    321     public static void formatDuration(long time, long now, PrintWriter pw) {
    322         if (time == 0) {
    323             pw.print("--");
    324             return;
    325         }
    326         formatDuration(time-now, pw, 0);
    327     }
    328 
    329     /** @hide Just for debugging; not internationalized. */
    330     public static String formatUptime(long time) {
    331         final long diff = time - SystemClock.uptimeMillis();
    332         if (diff > 0) {
    333             return time + " (in " + diff + " ms)";
    334         }
    335         if (diff < 0) {
    336             return time + " (" + -diff + " ms ago)";
    337         }
    338         return time + " (now)";
    339     }
    340 
    341     /**
    342      * Convert a System.currentTimeMillis() value to a time of day value like
    343      * that printed in logs. MM-DD HH:MM:SS.MMM
    344      *
    345      * @param millis since the epoch (1/1/1970)
    346      * @return String representation of the time.
    347      * @hide
    348      */
    349     @UnsupportedAppUsage
    350     public static String logTimeOfDay(long millis) {
    351         Calendar c = Calendar.getInstance();
    352         if (millis >= 0) {
    353             c.setTimeInMillis(millis);
    354             return String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c);
    355         } else {
    356             return Long.toString(millis);
    357         }
    358     }
    359 
    360     /** {@hide} */
    361     public static String formatForLogging(long millis) {
    362         if (millis <= 0) {
    363             return "unknown";
    364         } else {
    365             return sLoggingFormat.format(new Date(millis));
    366         }
    367     }
    368 
    369     /**
    370      * Dump a currentTimeMillis style timestamp for dumpsys.
    371      *
    372      * @hide
    373      */
    374     public static void dumpTime(PrintWriter pw, long time) {
    375         pw.print(sDumpDateFormat.format(new Date(time)));
    376     }
    377 
    378     /**
    379      * Dump a currentTimeMillis style timestamp for dumpsys, with the delta time from now.
    380      *
    381      * @hide
    382      */
    383     public static void dumpTimeWithDelta(PrintWriter pw, long time, long now) {
    384         pw.print(sDumpDateFormat.format(new Date(time)));
    385         if (time == now) {
    386             pw.print(" (now)");
    387         } else {
    388             pw.print(" (");
    389             TimeUtils.formatDuration(time, now, pw);
    390             pw.print(")");
    391         }
    392     }}
    393