conradchen 62e89933d6 [DatePicker] Fix wrong month title with certain months
The issue is caused by failing to correctly take the daylight saving time into account - internally we use UTC time to represent dates, however when generating month titles we try to calculate the time difference of the UTC time and the local time, which can result in off-by-one errors when the first day of the month is also the first day of the daylight saving time, e.g., April 1, 1979 in Poland.

Fixes this by always using UTC time to generate month titles. Also clears the relevant implementation for a little bit since context is actually not required when we only need to format years and months.

Resolves https://github.com/material-components/material-components-android/issues/1935

PiperOrigin-RevId: 413505587
2021-12-01 19:08:56 -05:00

154 lines
5.4 KiB
Java

/*
* 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 com.google.android.material.R;
import android.content.Context;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.LayoutParams;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.view.ViewCompat;
import com.google.android.material.datepicker.MaterialCalendar.OnDayClickListener;
/**
* Manages the instances of {@link MaterialCalendarGridView} that represent each month in a {@link
* MaterialCalendar}. Caps memory usage via {@link RecyclerView} extension.
*/
class MonthsPagerAdapter extends RecyclerView.Adapter<MonthsPagerAdapter.ViewHolder> {
@NonNull private final CalendarConstraints calendarConstraints;
private final DateSelector<?> dateSelector;
private final OnDayClickListener onDayClickListener;
private final int itemHeight;
MonthsPagerAdapter(
@NonNull Context context,
DateSelector<?> dateSelector,
@NonNull CalendarConstraints calendarConstraints,
OnDayClickListener onDayClickListener) {
Month firstPage = calendarConstraints.getStart();
Month lastPage = calendarConstraints.getEnd();
Month currentPage = calendarConstraints.getOpenAt();
if (firstPage.compareTo(currentPage) > 0) {
throw new IllegalArgumentException("firstPage cannot be after currentPage");
}
if (currentPage.compareTo(lastPage) > 0) {
throw new IllegalArgumentException("currentPage cannot be after lastPage");
}
int daysHeight = MonthAdapter.MAXIMUM_WEEKS * MaterialCalendar.getDayHeight(context);
int labelHeight =
MaterialDatePicker.isFullscreen(context) ? MaterialCalendar.getDayHeight(context) : 0;
this.itemHeight = daysHeight + labelHeight;
this.calendarConstraints = calendarConstraints;
this.dateSelector = dateSelector;
this.onDayClickListener = onDayClickListener;
setHasStableIds(true);
}
public static class ViewHolder extends RecyclerView.ViewHolder {
final TextView monthTitle;
final MaterialCalendarGridView monthGrid;
ViewHolder(@NonNull LinearLayout container, boolean showLabel) {
super(container);
monthTitle = container.findViewById(R.id.month_title);
ViewCompat.setAccessibilityHeading(monthTitle, true);
monthGrid = container.findViewById(R.id.month_grid);
if (!showLabel) {
monthTitle.setVisibility(View.GONE);
}
}
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
LinearLayout container =
(LinearLayout)
LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.mtrl_calendar_month_labeled, viewGroup, false);
if (MaterialDatePicker.isFullscreen(viewGroup.getContext())) {
container.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, itemHeight));
return new ViewHolder(container, /* showLabel= */ true);
} else {
return new ViewHolder(container, /* showLabel= */ false);
}
}
@Override
public void onBindViewHolder(@NonNull MonthsPagerAdapter.ViewHolder viewHolder, int position) {
Month month = calendarConstraints.getStart().monthsLater(position);
viewHolder.monthTitle.setText(month.getLongName());
final MaterialCalendarGridView monthGrid = viewHolder.monthGrid.findViewById(R.id.month_grid);
if (monthGrid.getAdapter() != null && month.equals(monthGrid.getAdapter().month)) {
monthGrid.invalidate();
monthGrid.getAdapter().updateSelectedStates(monthGrid);
} else {
MonthAdapter monthAdapter = new MonthAdapter(month, dateSelector, calendarConstraints);
monthGrid.setNumColumns(month.daysInWeek);
monthGrid.setAdapter(monthAdapter);
}
monthGrid.setOnItemClickListener(
new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (monthGrid.getAdapter().withinMonth(position)) {
onDayClickListener.onDayClick(monthGrid.getAdapter().getItem(position));
}
}
});
}
@Override
public long getItemId(int position) {
return calendarConstraints.getStart().monthsLater(position).getStableId();
}
@Override
public int getItemCount() {
return calendarConstraints.getMonthSpan();
}
@NonNull
CharSequence getPageTitle(int position) {
return getPageMonth(position).getLongName();
}
@NonNull
Month getPageMonth(int position) {
return calendarConstraints.getStart().monthsLater(position);
}
int getPosition(@NonNull Month month) {
return calendarConstraints.getStart().monthsUntil(month);
}
}