Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cac13d44 authored by Neil Fuller's avatar Neil Fuller Committed by Android (Google) Code Review
Browse files

Merge "Convert some users of Time.format() to an alt."

parents 60bb23b1 9aa7b74a
Loading
Loading
Loading
Loading
+3 −5
Original line number Diff line number Diff line
@@ -40,7 +40,7 @@ import android.database.DatabaseUtils;
import android.net.Uri;
import android.os.RemoteException;
import android.text.format.DateUtils;
import android.text.format.Time;
import android.text.format.TimeMigrationUtils;
import android.util.Log;

import com.android.internal.util.Preconditions;
@@ -1680,7 +1680,7 @@ public final class CalendarContract {
     * <h3>Writing to Events</h3> There are further restrictions on all Updates
     * and Inserts in the Events table:
     * <ul>
     * <li>If allDay is set to 1 eventTimezone must be {@link Time#TIMEZONE_UTC}
     * <li>If allDay is set to 1 eventTimezone must be "UTC"
     * and the time must correspond to a midnight boundary.</li>
     * <li>Exceptions are not allowed to recur. If rrule or rdate is not empty,
     * original_id and original_sync_id must be empty.</li>
@@ -2608,9 +2608,7 @@ public final class CalendarContract {
        @UnsupportedAppUsage
        public static void scheduleAlarm(Context context, AlarmManager manager, long alarmTime) {
            if (DEBUG) {
                Time time = new Time();
                time.set(alarmTime);
                String schedTime = time.format(" %a, %b %d, %Y %I:%M%P");
                String schedTime = TimeMigrationUtils.formatMillisWithFixedFormat(alarmTime);
                Log.d(TAG, "Schedule alarm at " + alarmTime + " " + schedTime);
            }

+63 −6
Original line number Diff line number Diff line
@@ -26,6 +26,9 @@ import libcore.icu.LocaleData;
import libcore.util.ZoneInfo;

import java.nio.CharBuffer;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Formatter;
import java.util.Locale;
import java.util.TimeZone;
@@ -85,6 +88,59 @@ class TimeFormatter {
        }
    }

    /**
     * The implementation of {@link TimeMigrationUtils#formatMillisWithFixedFormat(long)} for
     * 2038-safe formatting with the pattern "%Y-%m-%d %H:%M:%S" and including the historic
     * incorrect digit localization behavior.
     */
    String formatMillisWithFixedFormat(long timeMillis) {
        // This method is deliberately not a general purpose replacement for
        // format(String, ZoneInfo.WallTime, ZoneInfo): It hard-codes the pattern used; many of the
        // pattern characters supported by Time.format() have unusual behavior which would make
        // using java.time.format or similar packages difficult. It would be a lot of work to share
        // behavior and many internal Android usecases can be covered by this common pattern
        // behavior.

        // No need to worry about overflow / underflow: long millis is representable by Instant and
        // LocalDateTime with room to spare.
        Instant instant = Instant.ofEpochMilli(timeMillis);

        // Date/times are calculated in the current system default time zone.
        LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());

        // You'd think it would be as simple as:
        // DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss", locale);
        // return formatter.format(localDateTime);
        // but we retain Time's behavior around digits.

        StringBuilder stringBuilder = new StringBuilder(19);

        // This effectively uses the US locale because number localization is handled separately
        // (see below).
        stringBuilder.append(localDateTime.getYear());
        stringBuilder.append('-');
        append2DigitNumber(stringBuilder, localDateTime.getMonthValue());
        stringBuilder.append('-');
        append2DigitNumber(stringBuilder, localDateTime.getDayOfMonth());
        stringBuilder.append(' ');
        append2DigitNumber(stringBuilder, localDateTime.getHour());
        stringBuilder.append(':');
        append2DigitNumber(stringBuilder, localDateTime.getMinute());
        stringBuilder.append(':');
        append2DigitNumber(stringBuilder, localDateTime.getSecond());

        String result = stringBuilder.toString();
        return localizeDigits(result);
    }

    /** Zero-pads value as needed to achieve a 2-digit number. */
    private static void append2DigitNumber(StringBuilder builder, int value) {
        if (value < 10) {
            builder.append('0');
        }
        builder.append(value);
    }

    /**
     * Format the specified {@code wallTime} using {@code pattern}. The output is returned.
     */
@@ -99,12 +155,9 @@ class TimeFormatter {

            formatInternal(pattern, wallTime, zoneInfo);
            String result = stringBuilder.toString();
            // This behavior is the source of a bug since some formats are defined as being
            // in ASCII and not localized.
            if (localeData.zeroDigit != '0') {
                result = localizeDigits(result);
            }
            return result;
            // The localizeDigits() behavior is the source of a bug since some formats are defined
            // as being in ASCII and not localized.
            return localizeDigits(result);
        } finally {
            outputBuilder = null;
            numberFormatter = null;
@@ -112,6 +165,10 @@ class TimeFormatter {
    }

    private String localizeDigits(String s) {
        if (localeData.zeroDigit == '0') {
            return s;
        }

        int length = s.length();
        int offsetToLocalizedDigits = localeData.zeroDigit - '0';
        StringBuilder result = new StringBuilder(length);
+40 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package android.text.format;

/**
 * Logic to ease migration away from {@link Time} in Android internal code. {@link Time} is
 * afflicted by the Y2038 issue and deprecated. The methods here are intended to allow minimal
 * changes to classes that use {@link Time} for common behavior.
 *
 * @hide
 */
public class TimeMigrationUtils {

    private TimeMigrationUtils() {}

    /**
     * A Y2038-safe replacement for various users of the {@link Time#format(String)} with the
     * pattern "%Y-%m-%d %H:%M:%S". Note, this method retains the unusual localization behavior
     * originally implemented by Time, which can lead to non-latin numbers being produced if the
     * default locale does not use latin numbers.
     */
    public static String formatMillisWithFixedFormat(long timeMillis) {
        // Delegate to TimeFormatter so that the unusual localization / threading behavior can be
        // reused.
        return new TimeFormatter().formatMillisWithFixedFormat(timeMillis);
    }
}
+120 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.text.format;

import static org.junit.Assert.assertEquals;

import android.platform.test.annotations.Presubmit;

import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.Locale;
import java.util.TimeZone;

@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
public class TimeMigrationUtilsTest {

    private static final int ONE_DAY_IN_SECONDS = 24 * 60 * 60;

    private Locale mDefaultLocale;
    private TimeZone mDefaultTimeZone;

    @Before
    public void setUp() {
        mDefaultLocale = Locale.getDefault();
        mDefaultTimeZone = TimeZone.getDefault();
    }

    @After
    public void tearDown() {
        Locale.setDefault(mDefaultLocale);
        TimeZone.setDefault(mDefaultTimeZone);
    }

    @Test
    public void formatMillisWithFixedFormat_fixes2038Issue() {
        Locale.setDefault(Locale.UK);
        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));

        // The following cannot be represented properly using Time because they are outside of the
        // supported range.
        long y2038Issue1 = (((long) Integer.MIN_VALUE) - ONE_DAY_IN_SECONDS) * 1000L;
        assertEquals(
                "1901-12-12 20:45:52", TimeMigrationUtils.formatMillisWithFixedFormat(y2038Issue1));
        long y2038Issue2 = (((long) Integer.MAX_VALUE) + ONE_DAY_IN_SECONDS) * 1000L;
        assertEquals(
                "2038-01-20 03:14:07", TimeMigrationUtils.formatMillisWithFixedFormat(y2038Issue2));
    }

    /**
     * Compares TimeMigrationUtils.formatSimpleDateTime() with the code it is replacing.
     */
    @Test
    public void formatMillisAsDateTime_matchesOldBehavior() {
        // A selection of interesting locales.
        Locale[] locales = new Locale[] {
                Locale.US,
                Locale.UK,
                Locale.FRANCE,
                Locale.JAPAN,
                Locale.CHINA,
                // Android supports RTL locales like arabic and arabic with latin numbers.
                Locale.forLanguageTag("ar-AE"),
                Locale.forLanguageTag("ar-AE-u-nu-latn"),
        };
        // A selection of interesting time zones.
        String[] timeZoneIds = new String[] {
                "UTC", "Europe/London", "America/New_York", "America/Los_Angeles", "Asia/Shanghai",
        };
        // Some arbitrary times when the two formatters should agree.
        long[] timesMillis = new long[] {
                System.currentTimeMillis(),
                0,
                // The Time class only works in 32-bit range, the replacement works beyond that. To
                // avoid messing around with offsets and complicating the test, below there are a
                // day after / before the known limits.
                (Integer.MIN_VALUE + ONE_DAY_IN_SECONDS) * 1000L,
                (Integer.MAX_VALUE - ONE_DAY_IN_SECONDS) * 1000L,
        };

        for (Locale locale : locales) {
            Locale.setDefault(locale);
            for (String timeZoneId : timeZoneIds) {
                TimeZone timeZone = TimeZone.getTimeZone(timeZoneId);
                TimeZone.setDefault(timeZone);
                for (long timeMillis : timesMillis) {
                    Time time = new Time();
                    time.set(timeMillis);
                    String oldResult = time.format("%Y-%m-%d %H:%M:%S");
                    String newResult = TimeMigrationUtils.formatMillisWithFixedFormat(timeMillis);
                    assertEquals(
                            "locale=" + locale + ", timeZoneId=" + timeZoneId
                                    + ", timeMillis=" + timeMillis,
                            oldResult, newResult);
                }
            }
        }
    }
}
+2 −4
Original line number Diff line number Diff line
@@ -41,7 +41,7 @@ import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.text.format.Time;
import android.text.format.TimeMigrationUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
@@ -582,11 +582,9 @@ public final class DropBoxManagerService extends SystemService {
        }

        int numFound = 0, numArgs = searchArgs.size();
        Time time = new Time();
        out.append("\n");
        for (EntryFile entry : mAllFiles.contents) {
            time.set(entry.timestampMillis);
            String date = time.format("%Y-%m-%d %H:%M:%S");
            String date = TimeMigrationUtils.formatMillisWithFixedFormat(entry.timestampMillis);
            boolean match = true;
            for (int i = 0; i < numArgs && match; i++) {
                String arg = searchArgs.get(i);
Loading