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

Commit 1b2f3740 authored by Rubin Xu's avatar Rubin Xu
Browse files

Tweak SystemUpdatePolicy APIs

* Introduce a FreezePeriod class that represents a single freeze period
  and is defined by two MonthDay instances.
* Add ERROR_UNKNOWN to ValidationFailedException
* Make SystemUpdatePolicy final
* Document SystemUpdatePolicy.InstallationOption and add IntDef to getType()

Test: runtest frameworks-services -c com.android.server.devicepolicy.SystemUpdatePolicyTest
Test: cts-tradefed run cts-dev -m CtsDevicePolicyManagerTestCases -t com.android.cts.devicepolicy.DeviceOwnerTest#testSystemUpdatePolicy
Test: gts-tradefed run gts-dev --module GtsGmscoreHostTestCases --test com.google.android.gts.devicepolicy.DeviceOwnerTest#testSystemUpdatePolicy
Bug: 74976911
Change-Id: I85cf636c3a98c97bd03b7b296c3130028051a791
parent b2e6e45d
Loading
Loading
Loading
Loading
+15 −8
Original line number Diff line number Diff line
@@ -6748,6 +6748,12 @@ package android.app.admin {
    field public static final android.os.Parcelable.Creator<android.app.admin.DnsEvent> CREATOR;
  }
  public class FreezePeriod {
    ctor public FreezePeriod(java.time.MonthDay, java.time.MonthDay);
    method public java.time.MonthDay getEnd();
    method public java.time.MonthDay getStart();
  }
  public abstract class NetworkEvent implements android.os.Parcelable {
    method public int describeContents();
    method public long getId();
@@ -6818,16 +6824,16 @@ package android.app.admin {
    field public static final int SECURITY_PATCH_STATE_UNKNOWN = 0; // 0x0
  }
  public class SystemUpdatePolicy implements android.os.Parcelable {
  public final class SystemUpdatePolicy implements android.os.Parcelable {
    method public static android.app.admin.SystemUpdatePolicy createAutomaticInstallPolicy();
    method public static android.app.admin.SystemUpdatePolicy createPostponeInstallPolicy();
    method public static android.app.admin.SystemUpdatePolicy createWindowedInstallPolicy(int, int);
    method public int describeContents();
    method public java.util.List<android.util.Pair<java.lang.Integer, java.lang.Integer>> getFreezePeriods();
    method public java.util.List<android.app.admin.FreezePeriod> getFreezePeriods();
    method public int getInstallWindowEnd();
    method public int getInstallWindowStart();
    method public int getPolicyType();
    method public android.app.admin.SystemUpdatePolicy setFreezePeriods(java.util.List<android.util.Pair<java.lang.Integer, java.lang.Integer>>);
    method public android.app.admin.SystemUpdatePolicy setFreezePeriods(java.util.List<android.app.admin.FreezePeriod>);
    method public void writeToParcel(android.os.Parcel, int);
    field public static final android.os.Parcelable.Creator<android.app.admin.SystemUpdatePolicy> CREATOR;
    field public static final int TYPE_INSTALL_AUTOMATIC = 1; // 0x1
@@ -6840,11 +6846,12 @@ package android.app.admin {
    method public int getErrorCode();
    method public void writeToParcel(android.os.Parcel, int);
    field public static final android.os.Parcelable.Creator<android.app.admin.SystemUpdatePolicy.ValidationFailedException> CREATOR;
    field public static final int ERROR_COMBINED_FREEZE_PERIOD_TOO_CLOSE = 5; // 0x5
    field public static final int ERROR_COMBINED_FREEZE_PERIOD_TOO_LONG = 4; // 0x4
    field public static final int ERROR_DUPLICATE_OR_OVERLAP = 1; // 0x1
    field public static final int ERROR_NEW_FREEZE_PERIOD_TOO_CLOSE = 3; // 0x3
    field public static final int ERROR_NEW_FREEZE_PERIOD_TOO_LONG = 2; // 0x2
    field public static final int ERROR_COMBINED_FREEZE_PERIOD_TOO_CLOSE = 6; // 0x6
    field public static final int ERROR_COMBINED_FREEZE_PERIOD_TOO_LONG = 5; // 0x5
    field public static final int ERROR_DUPLICATE_OR_OVERLAP = 2; // 0x2
    field public static final int ERROR_NEW_FREEZE_PERIOD_TOO_CLOSE = 4; // 0x4
    field public static final int ERROR_NEW_FREEZE_PERIOD_TOO_LONG = 3; // 0x3
    field public static final int ERROR_UNKNOWN = 1; // 0x1
  }
}
+1 −1
Original line number Diff line number Diff line
@@ -451,7 +451,7 @@ package android.app.admin {
    field public static final int STATE_USER_UNMANAGED = 0; // 0x0
  }

  public class SystemUpdatePolicy implements android.os.Parcelable {
  public final class SystemUpdatePolicy implements android.os.Parcelable {
    method public int describeContents();
    method public android.app.admin.SystemUpdatePolicy.InstallationOption getInstallationOptionAt(long);
    method public void writeToParcel(android.os.Parcel, int);
+86 −34
Original line number Diff line number Diff line
@@ -20,49 +20,88 @@ import android.util.Log;
import android.util.Pair;

import java.time.LocalDate;
import java.time.MonthDay;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

/**
 * An interval representing one freeze period which repeats annually. We use the number of days
 * since the start of (non-leap) year to define the start and end dates of an interval, both
 * inclusive. If the end date is smaller than the start date, the interval is considered wrapped
 * around the year-end. As far as an interval is concerned, February 29th should be treated as
 * if it were February 28th: so an interval starting or ending on February 28th are not
 * distinguishable from an interval on February 29th. When calulating interval length or
 * distance between two dates, February 29th is also disregarded.
 * A class that represents one freeze period which repeats <em>annually</em>. A freeze period has
 * two {@link java.time#MonthDay} values that define the start and end dates of the period, both
 * inclusive. If the end date is earlier than the start date, the period is considered wrapped
 * around the year-end. As far as freeze period is concerned, leap year is disregarded and February
 * 29th should be treated as if it were February 28th: so a freeze starting or ending on February
 * 28th is identical to a freeze starting or ending on February 29th. When calulating the length of
 * a freeze or the distance bewteen two freee periods, February 29th is also ignored.
 *
 * @see SystemUpdatePolicy#setFreezePeriods
 * @hide
 */
public class FreezeInterval {
    private static final String TAG = "FreezeInterval";
public class FreezePeriod {
    private static final String TAG = "FreezePeriod";

    private static final int DUMMY_YEAR = 2001;
    static final int DAYS_IN_YEAR = 365; // 365 since DUMMY_YEAR is not a leap year

    final int mStartDay; // [1,365]
    final int mEndDay; // [1,365]
    private final MonthDay mStart;
    private final MonthDay mEnd;

    /*
     * Start and end dates represented by number of days since the beginning of the year.
     * They are internal representations of mStart and mEnd with normalized Leap year days
     * (Feb 29 == Feb 28 == 59th day of year). All internal calclations are based on
     * these two values so that leap year days are disregarded.
     */
    private final int mStartDay; // [1, 365]
    private final int mEndDay; // [1, 365]

    FreezeInterval(int startDay, int endDay) {
        if (startDay < 1 || startDay > 365 || endDay < 1 || endDay > 365) {
            throw new RuntimeException("Bad dates for Interval: " + startDay + "," + endDay);
    /**
     * Creates a freeze period by its start and end dates. If the end date is earlier than the start
     * date, the freeze period is considered wrapping year-end.
     */
    public FreezePeriod(MonthDay start, MonthDay end) {
        mStart = start;
        mStartDay = mStart.atYear(DUMMY_YEAR).getDayOfYear();
        mEnd = end;
        mEndDay = mEnd.atYear(DUMMY_YEAR).getDayOfYear();
    }

    /**
     * Returns the start date (inclusive) of this freeze period.
     */
    public MonthDay getStart() {
        return mStart;
    }

    /**
     * Returns the end date (inclusive) of this freeze period.
     */
    public MonthDay getEnd() {
        return mEnd;
    }

    /**
     * @hide
     */
    private FreezePeriod(int startDay, int endDay) {
        mStartDay = startDay;
        mStart = dayOfYearToMonthDay(startDay);
        mEndDay = endDay;
        mEnd = dayOfYearToMonthDay(endDay);
    }

    /** @hide */
    int getLength() {
        return getEffectiveEndDay() - mStartDay + 1;
    }

    /** @hide */
    boolean isWrapped() {
        return mEndDay < mStartDay;
    }

    /**
     * Returns the effective end day, taking wrapping around year-end into consideration
     * @hide
     */
    int getEffectiveEndDay() {
        if (!isWrapped()) {
@@ -72,6 +111,7 @@ public class FreezeInterval {
        }
    }

    /** @hide */
    boolean contains(LocalDate localDate) {
        final int daysOfYear = dayOfYearDisregardLeapYear(localDate);
        if (!isWrapped()) {
@@ -84,6 +124,7 @@ public class FreezeInterval {
        }
    }

    /** @hide */
    boolean after(LocalDate localDate) {
        return mStartDay > dayOfYearDisregardLeapYear(localDate);
    }
@@ -95,6 +136,7 @@ public class FreezeInterval {
     * include now, the returned dates represents the next future interval.
     * The result will always have the same month and dayOfMonth value as the non-instantiated
     * interval itself.
     * @hide
     */
    Pair<LocalDate, LocalDate> toCurrentOrFutureRealDates(LocalDate now) {
        final int nowDays = dayOfYearDisregardLeapYear(now);
@@ -138,14 +180,24 @@ public class FreezeInterval {
                + LocalDate.ofYearDay(DUMMY_YEAR, mEndDay).format(formatter);
    }

    // Treat the supplied date as in a non-leap year and return its day of year.
    static int dayOfYearDisregardLeapYear(LocalDate date) {
    /** @hide */
    private static MonthDay dayOfYearToMonthDay(int dayOfYear) {
        LocalDate date = LocalDate.ofYearDay(DUMMY_YEAR, dayOfYear);
        return MonthDay.of(date.getMonth(), date.getDayOfMonth());
    }

    /**
     * Treat the supplied date as in a non-leap year and return its day of year.
     * @hide
     */
    private static int dayOfYearDisregardLeapYear(LocalDate date) {
        return date.withYear(DUMMY_YEAR).getDayOfYear();
    }

    /**
     * Compute the number of days between first (inclusive) and second (exclusive),
     * treating all years in between as non-leap.
     * @hide
     */
    public static int distanceWithoutLeapYear(LocalDate first, LocalDate second) {
        return dayOfYearDisregardLeapYear(first) - dayOfYearDisregardLeapYear(second)
@@ -165,16 +217,16 @@ public class FreezeInterval {
     *     3. At most one wrapped Interval remains, and it will be at the end of the list
     * @hide
     */
    protected static List<FreezeInterval> canonicalizeIntervals(List<FreezeInterval> intervals) {
    static List<FreezePeriod> canonicalizePeriods(List<FreezePeriod> intervals) {
        boolean[] taken = new boolean[DAYS_IN_YEAR];
        // First convert the intervals into flat array
        for (FreezeInterval interval : intervals) {
        for (FreezePeriod interval : intervals) {
            for (int i = interval.mStartDay; i <= interval.getEffectiveEndDay(); i++) {
                taken[(i - 1) % DAYS_IN_YEAR] = true;
            }
        }
        // Then reconstruct intervals from the array
        List<FreezeInterval> result = new ArrayList<>();
        List<FreezePeriod> result = new ArrayList<>();
        int i = 0;
        while (i < DAYS_IN_YEAR) {
            if (!taken[i]) {
@@ -183,14 +235,14 @@ public class FreezeInterval {
            }
            final int intervalStart = i + 1;
            while (i < DAYS_IN_YEAR && taken[i]) i++;
            result.add(new FreezeInterval(intervalStart, i));
            result.add(new FreezePeriod(intervalStart, i));
        }
        // Check if the last entry can be merged to the first entry to become one single
        // wrapped interval
        final int lastIndex = result.size() - 1;
        if (lastIndex > 0 && result.get(lastIndex).mEndDay == DAYS_IN_YEAR
                && result.get(0).mStartDay == 1) {
            FreezeInterval wrappedInterval = new FreezeInterval(result.get(lastIndex).mStartDay,
            FreezePeriod wrappedInterval = new FreezePeriod(result.get(lastIndex).mStartDay,
                    result.get(0).mEndDay);
            result.set(lastIndex, wrappedInterval);
            result.remove(0);
@@ -207,18 +259,18 @@ public class FreezeInterval {
     *
     * @hide
     */
    protected static void validatePeriods(List<FreezeInterval> periods) {
        List<FreezeInterval> allPeriods = FreezeInterval.canonicalizeIntervals(periods);
    static void validatePeriods(List<FreezePeriod> periods) {
        List<FreezePeriod> allPeriods = FreezePeriod.canonicalizePeriods(periods);
        if (allPeriods.size() != periods.size()) {
            throw SystemUpdatePolicy.ValidationFailedException.duplicateOrOverlapPeriods();
        }
        for (int i = 0; i < allPeriods.size(); i++) {
            FreezeInterval current = allPeriods.get(i);
            FreezePeriod current = allPeriods.get(i);
            if (current.getLength() > SystemUpdatePolicy.FREEZE_PERIOD_MAX_LENGTH) {
                throw SystemUpdatePolicy.ValidationFailedException.freezePeriodTooLong("Freeze "
                        + "period " + current + " is too long: " + current.getLength() + " days");
            }
            FreezeInterval previous = i > 0 ? allPeriods.get(i - 1)
            FreezePeriod previous = i > 0 ? allPeriods.get(i - 1)
                    : allPeriods.get(allPeriods.size() - 1);
            if (previous != current) {
                final int separation;
@@ -247,7 +299,7 @@ public class FreezeInterval {
     *
     * @hide
     */
    protected static void validateAgainstPreviousFreezePeriod(List<FreezeInterval> periods,
    static void validateAgainstPreviousFreezePeriod(List<FreezePeriod> periods,
            LocalDate prevPeriodStart, LocalDate prevPeriodEnd, LocalDate now) {
        if (periods.size() == 0 || prevPeriodStart == null || prevPeriodEnd == null) {
            return;
@@ -258,14 +310,14 @@ public class FreezeInterval {
            // Clock was adjusted backwards. We can continue execution though, the separation
            // and length validation below still works under this condition.
        }
        List<FreezeInterval> allPeriods = FreezeInterval.canonicalizeIntervals(periods);
        List<FreezePeriod> allPeriods = FreezePeriod.canonicalizePeriods(periods);
        // Given current time now, find the freeze period that's either current, or the one
        // that's immediately afterwards. For the later case, it might be after the year-end,
        // but this can only happen if there is only one freeze period.
        FreezeInterval curOrNextFreezePeriod = allPeriods.get(0);
        for (FreezeInterval interval : allPeriods) {
        FreezePeriod curOrNextFreezePeriod = allPeriods.get(0);
        for (FreezePeriod interval : allPeriods) {
            if (interval.contains(now)
                    || interval.mStartDay > FreezeInterval.dayOfYearDisregardLeapYear(now)) {
                    || interval.mStartDay > FreezePeriod.dayOfYearDisregardLeapYear(now)) {
                curOrNextFreezePeriod = interval;
                break;
            }
@@ -282,7 +334,7 @@ public class FreezeInterval {
        // Now validate [prevPeriodStart, prevPeriodEnd] against curOrNextFreezeDates
        final String periodsDescription = "Prev: " + prevPeriodStart + "," + prevPeriodEnd
                + "; cur: " + curOrNextFreezeDates.first + "," + curOrNextFreezeDates.second;
        long separation = FreezeInterval.distanceWithoutLeapYear(curOrNextFreezeDates.first,
        long separation = FreezePeriod.distanceWithoutLeapYear(curOrNextFreezeDates.first,
                prevPeriodEnd) - 1;
        if (separation > 0) {
            // Two intervals do not overlap, check separation
@@ -292,7 +344,7 @@ public class FreezeInterval {
            }
        } else {
            // Two intervals overlap, check combined length
            long length = FreezeInterval.distanceWithoutLeapYear(curOrNextFreezeDates.second,
            long length = FreezePeriod.distanceWithoutLeapYear(curOrNextFreezeDates.second,
                    prevPeriodStart) + 1;
            if (length > SystemUpdatePolicy.FREEZE_PERIOD_MAX_LENGTH) {
                throw ValidationFailedException.combinedPeriodTooLong("Combined freeze period "
+65 −40

File changed.

Preview size limit exceeded, changes collapsed.

+40 −49
Original line number Diff line number Diff line
@@ -24,7 +24,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import android.app.admin.FreezeInterval;
import android.app.admin.FreezePeriod;
import android.app.admin.SystemUpdatePolicy;
import android.os.Parcel;
import android.support.test.runner.AndroidJUnit4;
@@ -42,15 +42,15 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.MonthDay;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;


/**
 * Unit tests for {@link android.app.admin.SystemUpdatePolicy}.
 * Throughout this test, we use "MM-DD" format to denote dates without year.
@@ -224,36 +224,36 @@ public final class SystemUpdatePolicyTest {

    @Test
    public void testDistanceWithoutLeapYear() {
        assertEquals(364, FreezeInterval.distanceWithoutLeapYear(
        assertEquals(364, FreezePeriod.distanceWithoutLeapYear(
                LocalDate.of(2016, 12, 31), LocalDate.of(2016, 1, 1)));
        assertEquals(365, FreezeInterval.distanceWithoutLeapYear(
        assertEquals(365, FreezePeriod.distanceWithoutLeapYear(
                LocalDate.of(2017, 1, 1), LocalDate.of(2016, 1, 1)));
        assertEquals(365, FreezeInterval.distanceWithoutLeapYear(
        assertEquals(365, FreezePeriod.distanceWithoutLeapYear(
                LocalDate.of(2017, 2, 28), LocalDate.of(2016, 2, 29)));
        assertEquals(-365, FreezeInterval.distanceWithoutLeapYear(
        assertEquals(-365, FreezePeriod.distanceWithoutLeapYear(
                LocalDate.of(2016, 1, 1), LocalDate.of(2017, 1, 1)));
        assertEquals(1, FreezeInterval.distanceWithoutLeapYear(
        assertEquals(1, FreezePeriod.distanceWithoutLeapYear(
                LocalDate.of(2016, 3, 1), LocalDate.of(2016, 2, 29)));
        assertEquals(1, FreezeInterval.distanceWithoutLeapYear(
        assertEquals(1, FreezePeriod.distanceWithoutLeapYear(
                LocalDate.of(2016, 3, 1), LocalDate.of(2016, 2, 28)));
        assertEquals(0, FreezeInterval.distanceWithoutLeapYear(
        assertEquals(0, FreezePeriod.distanceWithoutLeapYear(
                LocalDate.of(2016, 2, 29), LocalDate.of(2016, 2, 28)));
        assertEquals(0, FreezeInterval.distanceWithoutLeapYear(
        assertEquals(0, FreezePeriod.distanceWithoutLeapYear(
                LocalDate.of(2016, 2, 28), LocalDate.of(2016, 2, 28)));

        assertEquals(59, FreezeInterval.distanceWithoutLeapYear(
        assertEquals(59, FreezePeriod.distanceWithoutLeapYear(
                LocalDate.of(2016, 3, 1), LocalDate.of(2016, 1, 1)));
        assertEquals(59, FreezeInterval.distanceWithoutLeapYear(
        assertEquals(59, FreezePeriod.distanceWithoutLeapYear(
                LocalDate.of(2017, 3, 1), LocalDate.of(2017, 1, 1)));

        assertEquals(365 * 40, FreezeInterval.distanceWithoutLeapYear(
        assertEquals(365 * 40, FreezePeriod.distanceWithoutLeapYear(
                LocalDate.of(2040, 1, 1), LocalDate.of(2000, 1, 1)));

        assertEquals(365 * 2, FreezeInterval.distanceWithoutLeapYear(
        assertEquals(365 * 2, FreezePeriod.distanceWithoutLeapYear(
                LocalDate.of(2019, 3, 1), LocalDate.of(2017, 3, 1)));
        assertEquals(365 * 2, FreezeInterval.distanceWithoutLeapYear(
        assertEquals(365 * 2, FreezePeriod.distanceWithoutLeapYear(
                LocalDate.of(2018, 3, 1), LocalDate.of(2016, 3, 1)));
        assertEquals(365 * 2, FreezeInterval.distanceWithoutLeapYear(
        assertEquals(365 * 2, FreezePeriod.distanceWithoutLeapYear(
                LocalDate.of(2017, 3, 1), LocalDate.of(2015, 3, 1)));

    }
@@ -386,10 +386,10 @@ public final class SystemUpdatePolicyTest {

        // Two freeze periods
        p = SystemUpdatePolicy.createAutomaticInstallPolicy();
        setFreezePeriods(p, "05-01", "06-01", "11-01", "01-29");
        // automatic policy for July, August, September and October
        setFreezePeriods(p, "05-01", "06-01", "10-15", "01-10");
        // automatic policy for July, August, September and October until 15th
        assertInstallationOption(
                SystemUpdatePolicy.TYPE_INSTALL_AUTOMATIC, TimeUnit.DAYS.toMillis(92),
                SystemUpdatePolicy.TYPE_INSTALL_AUTOMATIC, TimeUnit.DAYS.toMillis(31 + 30 + 14),
                millis_2018_08_01, p);
    }

@@ -435,18 +435,18 @@ public final class SystemUpdatePolicyTest {
            String... dates) throws Exception {
        SystemUpdatePolicy p = SystemUpdatePolicy.createPostponeInstallPolicy();
        setFreezePeriods(p, dates);
        p.validateAgainstPreviousFreezePeriod(parseDate(prevStart), parseDate(prevEnd),
                parseDate(now));
        p.validateAgainstPreviousFreezePeriod(parseLocalDate(prevStart),
                parseLocalDate(prevEnd), parseLocalDate(now));
    }

    // "MM-DD" format for date
    private void setFreezePeriods(SystemUpdatePolicy policy, String... dates) throws Exception {
        List<Pair<Integer, Integer>> periods = new ArrayList<>();
        LocalDate lastDate = null;
        List<FreezePeriod> periods = new ArrayList<>();
        MonthDay lastDate = null;
        for (String date : dates) {
            LocalDate currentDate = parseDate(date);
            MonthDay currentDate = parseMonthDay(date);
            if (lastDate != null) {
                periods.add(new Pair<>(lastDate.getDayOfYear(), currentDate.getDayOfYear()));
                periods.add(new FreezePeriod(lastDate, currentDate));
                lastDate = null;
            } else {
                lastDate = currentDate;
@@ -457,7 +457,7 @@ public final class SystemUpdatePolicyTest {
    }

    private void testSerialization(SystemUpdatePolicy policy,
            List<Pair<Integer, Integer>> expectedPeriods) throws Exception {
            List<FreezePeriod> expectedPeriods) throws Exception {
        // Test parcel / unparcel
        Parcel parcel = Parcel.obtain();
        policy.writeToParcel(parcel, 0);
@@ -485,36 +485,27 @@ public final class SystemUpdatePolicyTest {
    }

    private void checkFreezePeriods(SystemUpdatePolicy policy,
            List<Pair<Integer, Integer>> expectedPeriods) {
            List<FreezePeriod> expectedPeriods) {
        int i = 0;
        for (Pair<Integer, Integer> period : policy.getFreezePeriods()) {
            assertEquals(expectedPeriods.get(i).first, period.first);
            assertEquals(expectedPeriods.get(i).second, period.second);
        for (FreezePeriod period : policy.getFreezePeriods()) {
            assertEquals(expectedPeriods.get(i).getStart(), period.getStart());
            assertEquals(expectedPeriods.get(i).getEnd(), period.getEnd());
            i++;
        }
    }

    private LocalDate parseDate(String date) {
        // Use leap year when parsing date string to handle "02-29", but force round down
        // to Feb 28th by overriding the year to non-leap year.
        final int year;
        boolean monthDateOnly = false;
        if (date.length() == 5) {
            year = 2000;
            monthDateOnly = true;
        } else {
            year = Integer.parseInt(date.substring(0, 4));
            date = date.substring(5);
        }
        LocalDate result = LocalDate.of(year, Integer.parseInt(date.substring(0, 2)),
    // MonthDay is of format MM-dd
    private MonthDay parseMonthDay(String date) {
        return MonthDay.of(Integer.parseInt(date.substring(0, 2)),
                Integer.parseInt(date.substring(3, 5)));
        if (monthDateOnly) {
            return result.withYear(2001);
        } else {
            return result;
    }

    // LocalDat is of format YYYY-MM-dd
    private LocalDate parseLocalDate(String date) {
        return parseMonthDay(date.substring(5)).atYear(Integer.parseInt(date.substring(0, 4)));
    }


    private long toMillis(int year, int month, int day) {
        return LocalDateTime.of(year, month, day, 0, 0, 0).atZone(ZoneId.systemDefault())
                .toInstant().toEpochMilli();