/* * Copyright 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 com.google.android.material.datepicker; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.util.ObjectsCompat; import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.util.Arrays; import java.util.Calendar; import java.util.Objects; /** * Used to limit the display range of the calendar and set an openAt month. * *
Implements {@link Parcelable} in order to maintain the {@code CalendarConstraints} across * device configuration changes. Parcelable breaks when passed between processes. */ public final class CalendarConstraints implements Parcelable { @NonNull private final Month start; @NonNull private final Month end; @NonNull private final DateValidator validator; @Nullable private Month openAt; private final int firstDayOfWeek; private final int yearSpan; private final int monthSpan; /** * Used to determine whether calendar days are enabled. * *
Extends {@link Parcelable} in order to maintain the {@code DateValidator} across device
* configuration changes. Parcelable breaks when passed between processes.
*/
public interface DateValidator extends Parcelable {
/** Returns true if the provided {@code date} is enabled. */
boolean isValid(long date);
}
private CalendarConstraints(
@NonNull Month start,
@NonNull Month end,
@NonNull DateValidator validator,
@Nullable Month openAt,
int firstDayOfWeek) {
Objects.requireNonNull(start, "start cannot be null");
Objects.requireNonNull(end, "end cannot be null");
Objects.requireNonNull(validator, "validator cannot be null");
this.start = start;
this.end = end;
this.openAt = openAt;
this.firstDayOfWeek = firstDayOfWeek;
this.validator = validator;
if (openAt != null && start.compareTo(openAt) > 0) {
throw new IllegalArgumentException("start Month cannot be after current Month");
}
if (openAt != null && openAt.compareTo(end) > 0) {
throw new IllegalArgumentException("current Month cannot be after end Month");
}
if (firstDayOfWeek < 0
|| firstDayOfWeek > UtcDates.getUtcCalendar().getMaximum(Calendar.DAY_OF_WEEK)) {
throw new IllegalArgumentException("firstDayOfWeek is not valid");
}
monthSpan = start.monthsUntil(end) + 1;
yearSpan = end.year - start.year + 1;
}
boolean isWithinBounds(long date) {
return start.getDay(1) <= date && date <= end.getDay(end.daysInMonth);
}
/**
* Returns the {@link DateValidator} that determines whether a date can be clicked and selected.
*/
public DateValidator getDateValidator() {
return validator;
}
/** Returns the earliest month allowed by this set of bounds. */
@NonNull
Month getStart() {
return start;
}
/** Returns the latest month allowed by this set of bounds. */
@NonNull
Month getEnd() {
return end;
}
/** Returns the openAt month within this set of bounds. */
@Nullable
Month getOpenAt() {
return openAt;
}
/** Sets the openAt month. */
void setOpenAt(@Nullable Month openAt) {
this.openAt = openAt;
}
/** Returns the firstDayOfWeek. */
int getFirstDayOfWeek() {
return firstDayOfWeek;
}
/**
* Returns the total number of {@link java.util.Calendar#MONTH} included in {@code start} to
* {@code end}.
*/
int getMonthSpan() {
return monthSpan;
}
/**
* Returns the total number of {@link java.util.Calendar#YEAR} included in {@code start} to {@code
* end}.
*/
int getYearSpan() {
return yearSpan;
}
/** Returns the earliest time in milliseconds allowed by this set of bounds. */
public long getStartMs() {
return start.timeInMillis;
}
/** Returns the latest time in milliseconds allowed by this set of bounds. */
public long getEndMs() {
return end.timeInMillis;
}
/**
* Returns the openAt time in milliseconds within this set of bounds. Returns null if not
* available.
*/
@Nullable
public Long getOpenAtMs() {
return openAt == null ? null : openAt.timeInMillis;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof CalendarConstraints)) {
return false;
}
CalendarConstraints that = (CalendarConstraints) o;
return start.equals(that.start)
&& end.equals(that.end)
&& ObjectsCompat.equals(openAt, that.openAt)
&& firstDayOfWeek == that.firstDayOfWeek
&& validator.equals(that.validator);
}
@Override
public int hashCode() {
Object[] hashedFields = {start, end, openAt, firstDayOfWeek, validator};
return Arrays.hashCode(hashedFields);
}
/* Parcelable interface */
/** {@link Parcelable.Creator} */
public static final Parcelable.Creator If you have access to java.time in Java 8, you can obtain a long using {@code
* java.time.ZonedDateTime}.
*
* If you don't have access to java.time in Java 8, you can obtain this value using a {@code
* java.util.Calendar} instance from the UTC timezone.
*
* If you have access to java.time in Java 8, you can obtain a long using {@code
* java.time.ZonedDateTime}.
*
* If you don't have access to java.time in Java 8, you can obtain this value using a {@code
* java.util.Calendar} instance from the UTC timezone.
*
* If you have access to java.time in Java 8, you can obtain a long using {@code
* java.time.ZonedDateTime}.
*
* If you don't have access to java.time in Java 8, you can obtain this value using a {@code
* java.util.Calendar} instance from the UTC timezone.
*
* {@code
* LocalDateTime local = LocalDateTime.of(year, month, 1, 0, 0);
* local.atZone(ZoneId.ofOffset("UTC", ZoneOffset.UTC)).toInstant().toEpochMilli();
* }
*
* {@code
* Calendar c = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
* c.set(year, month, 1);
* c.getTimeInMillis();
* }
*/
@NonNull
@CanIgnoreReturnValue
public Builder setStart(long month) {
start = month;
return this;
}
/**
* A UTC timeInMilliseconds contained within the latest month the calendar will page to.
* Defaults December, 2100.
*
* {@code
* LocalDateTime local = LocalDateTime.of(year, month, 1, 0, 0);
* local.atZone(ZoneId.ofOffset("UTC", ZoneOffset.UTC)).toInstant().toEpochMilli();
* }
*
* {@code
* Calendar c = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
* c.set(year, month, 1);
* c.getTimeInMillis();
* }
*/
@NonNull
@CanIgnoreReturnValue
public Builder setEnd(long month) {
end = month;
return this;
}
/**
* A UTC timeInMilliseconds contained within the month the calendar should openAt. Defaults to
* the month containing today if within bounds; otherwise, defaults to the starting month.
*
* {@code
* LocalDateTime local = LocalDateTime.of(year, month, 1, 0, 0);
* local.atZone(ZoneId.ofOffset("UTC", ZoneOffset.UTC)).toInstant().toEpochMilli();
* }
*
* {@code
* Calendar c = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
* c.set(year, month, 1);
* c.getTimeInMillis();
* }
*/
@NonNull
@CanIgnoreReturnValue
public Builder setOpenAt(long month) {
openAt = month;
return this;
}
/**
* Sets what the first day of the week is; e.g., Calendar.SUNDAY in the U.S.,
* Calendar.MONDAY in France.
*/
@NonNull
@CanIgnoreReturnValue
public Builder setFirstDayOfWeek(int firstDayOfWeek) {
this.firstDayOfWeek = firstDayOfWeek;
return this;
}
/**
* Limits valid dates to those for which {@link DateValidator#isValid(long)} is true. Defaults
* to all dates as valid.
*/
@NonNull
@CanIgnoreReturnValue
public Builder setValidator(@NonNull DateValidator validator) {
Objects.requireNonNull(validator, "validator cannot be null");
this.validator = validator;
return this;
}
/** Builds the {@link CalendarConstraints} object using the set parameters or defaults. */
@NonNull
public CalendarConstraints build() {
Bundle deepCopyBundle = new Bundle();
deepCopyBundle.putParcelable(DEEP_COPY_VALIDATOR_KEY, validator);
return new CalendarConstraints(
Month.create(start),
Month.create(end),
(DateValidator) deepCopyBundle.getParcelable(DEEP_COPY_VALIDATOR_KEY),
openAt == null ? null : Month.create(openAt),
firstDayOfWeek);
}
}
}