| // Copyright (c) 2026, the R8 project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| package desugaredlib; |
| |
| import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8DEBUG; |
| import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS; |
| import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11; |
| import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH; |
| |
| import com.android.tools.r8.TestParameters; |
| import com.android.tools.r8.ToolHelper.DexVm.Version; |
| import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase; |
| import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification; |
| import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification; |
| import com.android.tools.r8.utils.StringUtils; |
| import com.google.common.collect.ImmutableList; |
| import java.text.ParseException; |
| import java.text.SimpleDateFormat; |
| import java.time.Instant; |
| import java.time.ZoneId; |
| import java.time.zone.ZoneOffsetTransition; |
| import java.time.zone.ZoneRules; |
| import java.util.Calendar; |
| import java.util.Date; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.TimeZone; |
| import org.junit.Assume; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| import org.junit.runners.Parameterized.Parameters; |
| |
| @RunWith(Parameterized.class) |
| public class TimeZoneTest extends DesugaredLibraryTestBase { |
| |
| private final TestParameters parameters; |
| private final LibraryDesugaringSpecification libraryDesugaringSpecification; |
| private final CompilationSpecification compilationSpecification; |
| |
| private static final String EXPECTED_OUTPUT = StringUtils.lines("9223372036854775807"); |
| private static final String EXPECTED_OUTPUT_NO_TZ_DATA = StringUtils.lines("1771120800000"); |
| private static final String EXPECTED_OUTPUT_NO_TZ_DATA_4_4_4 = StringUtils.lines("1774749600000"); |
| |
| private static final Class<?> MAIN_CLASS = TimeZoneMain.class; |
| |
| @Parameters(name = "{0}, spec: {1}, {2}") |
| public static List<Object[]> data() { |
| return buildParameters( |
| getTestParameters().withDexRuntimes().withAllApiLevels().build(), |
| ImmutableList.of(JDK11, JDK11_PATH), |
| DEFAULT_SPECIFICATIONS); |
| } |
| |
| public TimeZoneTest( |
| TestParameters parameters, |
| LibraryDesugaringSpecification libraryDesugaringSpecification, |
| CompilationSpecification compilationSpecification) { |
| this.parameters = parameters; |
| this.libraryDesugaringSpecification = libraryDesugaringSpecification; |
| this.compilationSpecification = compilationSpecification; |
| } |
| |
| @Test |
| public void test() throws Exception { |
| testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification) |
| .addInnerClassesAndStrippedOuter(getClass()) |
| .addKeepMainRule(MAIN_CLASS) |
| .compile() |
| .withArt6Plus64BitsLib() |
| .run(parameters.getRuntime(), MAIN_CLASS) |
| .assertSuccessWithOutput(getExpectedOutput(true)); |
| } |
| |
| private String getExpectedOutput(boolean desugaring) { |
| if (parameters.isDexRuntime()) { |
| // On 4.4.4 the tzdata is missing and the behavior is different than art VMs. |
| if (parameters.getDexRuntimeVersion() == Version.V4_4_4) { |
| return EXPECTED_OUTPUT_NO_TZ_DATA_4_4_4; |
| } |
| // VMs newer than 12.0.0, the tz data is missing. The desugared library behavior uses |
| // emulated tz data and is as if the tz data was present. The non desugared behavior is |
| // different since the tz data is missing. |
| if (parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V12_0_0)) { |
| if (!desugaring || !libraryDesugaringSpecification.hasCompleteTimeDesugaring(parameters)) { |
| return EXPECTED_OUTPUT_NO_TZ_DATA; |
| } |
| } |
| } |
| return EXPECTED_OUTPUT; |
| } |
| |
| @Test |
| public void testD8() throws Exception { |
| Assume.assumeTrue(compilationSpecification == D8_L8DEBUG); |
| Assume.assumeTrue(libraryDesugaringSpecification == JDK11); |
| Assume.assumeTrue( |
| parameters.isCfRuntime() |
| || !libraryDesugaringSpecification.hasCompleteTimeDesugaring(parameters)); |
| testForD8(parameters) |
| .addInnerClassesAndStrippedOuter(getClass()) |
| .compile() |
| .withArt6Plus64BitsLib() |
| .run(parameters.getRuntime(), MAIN_CLASS) |
| .assertSuccessWithOutput(getExpectedOutput(false)); |
| } |
| |
| public static class TimeZoneMain { |
| |
| public static void main(String[] args) throws ParseException { |
| TimeZone tz = TimeZone.getTimeZone("Africa/Casablanca"); |
| Long now = getTimeInMillisecond("02-13-2026 10:00:00:00", "Africa/Casablanca"); |
| Long nextTimeChange = findNextTimeChange(tz, now); |
| System.out.println(nextTimeChange); |
| } |
| |
| private static Long findNextTimeChange(TimeZone tz, Long startDate) { |
| Long endDate = Long.MAX_VALUE; |
| ZoneRules rules = ZoneId.of(tz.getID(), ZoneId.SHORT_IDS).getRules(); |
| |
| ZoneOffsetTransition nextTransition = rules.nextTransition(Instant.ofEpochMilli(startDate)); |
| if (nextTransition != null) { |
| return java.util.concurrent.TimeUnit.SECONDS.toMillis(nextTransition.toEpochSecond()); |
| } |
| |
| return Long.MAX_VALUE; |
| } |
| |
| private static Long getTimeInMillisecond(String dateString, String zoneId) |
| throws ParseException { |
| SimpleDateFormat sdf = new SimpleDateFormat("MM-dd-yyyy hh:mm:ss", Locale.US); |
| sdf.setTimeZone(TimeZone.getTimeZone(zoneId)); |
| Date date = sdf.parse(dateString); |
| Calendar calendar = Calendar.getInstance(); |
| calendar.setTime(date); |
| return calendar.getTimeInMillis(); |
| } |
| } |
| } |