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

Commit 646bdf8e authored by Automerger Merge Worker's avatar Automerger Merge Worker
Browse files

Merge "Convert some users of Time.format() to an alt." am: 267b83e7 am: 1b0c2fcd

Change-Id: I1cb9722000898c9d1a7d479e9b0d88f717c57414
parents 3ea281fe 1b0c2fcd
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>
@@ -2609,9 +2609,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